Home > front end >  How does the GO socket API correlate to the POSIX socket API
How does the GO socket API correlate to the POSIX socket API

Time:09-28

I am learning sockets in go, and I am a bit confused as to why the API is so different from the established socket api (in C for example). I know how to use sockets in C quite comfortably, and was hoping to leverage some of my knowledge in GO.

Specifically, I am wanting to use connected UDP sockets in my application. Connecting a UDP socket has the added benefit of redirecting all incoming traffic from the connected client to that specific socket.

If it helps to understand, here is the "flow" of what I'd like to accomplish in GO (and have accomplished in C and C# in the past):

  1. Client sends a UDP packet to a known IP:PORT
  2. When a packet is received on this socket, it is known that the client has not established a UDP "connection" because the packet would have arrived at the "connected" socket instead (the connection is an implementation detail of my software)
  3. A new socket is created, bind(), and connect() to the client's remote endpoint using the same port as the original socket. For this, SO_REUSEADDR is required.
  4. All future packets from the client are received in the newly created socket.

The advantages to this approach are:

  1. The client only ever needs to communicate with one IP:PORT
  2. No need to include a "session ID" or similar mechanism in each UDP packet
  3. The OS takes care of dispatching datagrams to the correct socket allowing the application to rely on the OS.

So in GO (a language that I have grown to love over the last few days) the socket API is quite different from what I was expecting. I have been able to listen for dgrams over UDP, but my difficulty arises when trying to create a new socket (UDPConn??) when a client first communicates with the server.

My code:

buf := make([]byte, 32)
laddr, _ := net.ResolveUDPAddr("udp", ep.address)
var lc = net.ListenConfig{
            Control: func(network, address string, c syscall.RawConn) error {
                var opErr error
                if err := c.Control(func(fd uintptr) {
                    opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
                }); err != nil {
                    return err
                }
                return opErr
            },
        }
conn, err := lc.ListenPacket(context.Background(), "udp", ep.address)
if err != nil {
    //TODO log error
}
for {
    buf := make([]byte, 32)
    l, raddr, _ := conn.ReadFrom(buf)
    laddr, _ := net.ResolveUDPAddr("udp", ep.address)
    <-- NEED TO SET SO_REUSEADDR BEFORE DIAL/CONNECT -->
    net.DialUDP("udp", laddr, raddr.(*net.UDPAddr))
    fmt.Println("Message from: ", raddr.String(), "Reads: ", string(buf[0:l]))
}

So my thinking was that dial is equivalent to connect(). I'm not sure I'm correct, but regardless, the issue now is that I can't set socket options when calling dial. I've probably wrongly assumed that dial() will connect(), but I don't see any other way of:

  1. Creating a UDP socket
  2. Binding it
  3. Connecting it to a remote client

Output from strace when running the above:

socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(6555), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(6555), sin_addr=inet_addr("127.0.0.1")}, [112->16]) = 0
recvfrom(3, 0xc000118040, 32, 0, 0xc00007f6f8, [112]) = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(3, "hi\n", 32, 0, {sa_family=AF_INET, sin_port=htons(36682), sin_addr=inet_addr("127.0.0.1")}, [112->16]) = 3
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 7
setsockopt(7, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
bind(7, {sa_family=AF_INET, sin_port=htons(6555), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EADDRINUSE (Address already in use)

As you can see, the second socket never gets created because I have to set SO_RESUSEADDR.

My question: How can one create a socket, set SO_REUSEADDR, bind() it and connect() it to a specific endpoint.

Thanks!

CodePudding user response:

I think the go api should fit nicely with your use case without the need to manually manage everything.

I think you need a net.ListenUDP call to open your listening socket.

Upon receipt of a packet, call net.DialUDP to create the client connection.

CodePudding user response:

How can one create a socket, set SO_REUSEADDR, bind() it and connect() it to a specific endpoint.

The Socket of syscall could be one option.

Sample codes

    import (
       . "syscall"
    )

    var clientsock int
    var serveraddr SockaddrInet4
    var err error

    if clientsock, err = Socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); err != nil {
        fmt.Println("socket error:", err.Error())
        return
    }
    if err := SetsockoptInt(clientsock, SOL_SOCKET, SO_REUSEADDR, 1); err != nil {
        fmt.Printf("reuse addr error: %v\n", err)
        return
    }

    defer Shutdown(clientsock, SHUT_RDWR)

    if err := Bind(clientsock, &SockaddrInet4{Port: 8888}); err != nil {
        fmt.Printf("bind error: %v\n", err)
        return
    }

    serveraddr.Addr = [4]byte{127, 0, 0, 1}

    if err = Connect(clientsock, &serveraddr); err != nil {
        fmt.Println("connect error:", err.Error())
        return
    }
  • Related