2

I want to develop a UDP server based on Linux. There are some IP set on that host machine (such as 1.1.1.1,1.1.1.2, 2001::1:1:1:1), and I want server listen on all IP as follows (9090 as sample)

udp6 0 0 :::9090 :::*

The server code as follows

package main

import (
    "fmt"
    "net"
)

func main() {

    udpAddr, err := net.ResolveUDPAddr("udp", ":9090")
    conn, err := net.ListenUDP("udp", udpAddr)

    if err != nil {
        fmt.Println(err)
        return
    }

    var data [1024]byte
    n, addr, err := conn.ReadFromUDP(data[:])
    if err != nil {
        fmt.Println(err)

    }
    fmt.Println(n)
    fmt.Println(addr)
   //  this is not my wanted result. it will print [::]:9090
    fmt.Println(conn.LocalAddr())

}

When the client dial this server (dst_string is 1.1.1.1:9090);

Actual result: the server will print conn.LocalAddr() with

[::]:9090

excepted result:

the server should print

1.1.1.1:9090

How to achieve that?

BTW: I know if UDP server only listen 1.1.1.1:9090 can make that. But server has many IP, I want the server listen all IP and LocalAddr() can print 1.1.1.1:9090

FObersteiner
  • 22,500
  • 8
  • 42
  • 72
jiasheng
  • 41
  • 6

3 Answers3

2

Let's cite this post from a PowerDNS developer:

There are two ways to listen on all addresses, one of which is to enumerate all interfaces, grab all their IP addresses, and bind to all of them. Lots of work, and non-portable work too. We really did not want to do that. You also need to monitor new addresses arriving.

Secondly, just bind to 0.0.0.0 and ::! This works very well for TCP and other connection-oriented protocols, but can fail silently for UDP and other connectionless protocols. How come? When a packet comes in on 0.0.0.0, we don’t know which IP address it was sent to. And this is a problem when replying to such a packet – what would the correct source address be? Because we are connectionless (and therefore stateless), the kernel doesn’t know what to do.

So it picks the most appropriate address, and that may be the wrong one. There are some heuristics that make some kernels do the right thing more reliably, but there are no guarantees.

When receiving packets on datagram sockets, we usually use recvfrom(2), but this does not provide the missing bit of data: which IP address the packet was actually sent to. There is no recvfromto(). Enter the very powerful recvmsg(2). Recvmsg() allows for the getting of a boatload of parameters per datagram, as requested via setsockopt().

One of the parameters we can request is the original destination IP address of the packet.
<…>

IPv4

<..> For Linux, use the setsockopt() called IP_PKTINFO, which will get you a parameter over recvmsg() called IP_PKTINFO, which carries a struct in_pktinfo, which has a 4 byte IP address hiding in its ipi_addr field.

It appears, that the closest to recvmsg() thing there exists in the net package is net.IPConn.ReadMsgIP, and its documentation states that

The packages golang.org/x/net/ipv4 and golang.org/x/net/ipv6 can be used to manipulate IP-level socket options in oob.

Hence, looks like a way forward.


I'd also make the following points explicit (they are not obvious from the text above):

  • It seems, the net package of the Go's stdlib does not have a standard and easy-to-use way to have what you want.
  • It appears that the approach to getting the destination address of a datagram when receiving them on a wildcard address is not really standardized, and hence implementations vary between different kernels.
  • While it looks that net.IPConn.ReadMsgIP wraps recvmsg(2), I'd first verify that in the source code of the Go standard library. Pay attention to the fact that the stdlib contains code for all platforms it supports, so make sure you understand what build constraints are.
  • https://godoc.org/golang.org/x/net/ may help. And so do the syscall package and https://godoc.org/golang.org/x/sys — if the stock one falls short.
kostix
  • 51,517
  • 14
  • 93
  • 176
1

Thanks response of kostix very much. And according to IP_PKTINFO prompt, I found the following code can resolve my ipv4 issue directly https://gist.github.com/omribahumi/5da8517497042152691e

But for ipv6, the result is still NOT excepted

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "net"
    "syscall"
)

func main() {
    serverAddr, _ := net.ResolveUDPAddr("udp", ":9999")
    sConn, _ := net.ListenUDP("udp", serverAddr)
    file, _ := sConn.File()

    syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IP_PKTINFO, 1)

    data := make([]byte, 1024)
    oob := make([]byte, 2048)

    sConn.ReadMsgUDP(data, oob)

    oob_buffer := bytes.NewBuffer(oob)
    msg := syscall.Cmsghdr{}
    binary.Read(oob_buffer, binary.LittleEndian, &msg)

    if msg.Level == syscall.IPPROTO_IPV6 && msg.Type == syscall.IP_PKTINFO {
        packet_info := syscall.Inet6Pktinfo{}
        binary.Read(oob_buffer, binary.LittleEndian, &packet_info)
        fmt.Println(packet_info)
        // the ipv6 address is not my wanted result
        fmt.Println(packet_info.Addr)
    }

}

The result as follows

root@test-VirtualBox:/home/test/mygo/src/udp# go run s2.go
{[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 0}
[64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

the tcpdump sniffer as follows

22:20:47.009712 IP6 ::1.43305 > ::1.1025: UDP, length 6
jiasheng
  • 41
  • 6
1

I resolve this ipv6 issue by Setting the source IP for a UDP socket via set ipv6 options err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1)

thanks everyone

jiasheng
  • 41
  • 6