net.ListenUDP, Receiving broadcast datagrams

1,152 views
Skip to first unread message

Fabian Wickborn

unread,
Sep 16, 2015, 3:48:16 PM9/16/15
to golang-nuts
Hello Gophers,

I have some trouble receiving broadcast UDP datagrams with Go on an linux/amd64 machine. I'm not sure if what I see is a Go issue or some intrinsic differences between Linux and Windows. I hope somebody here might be able to shed some light on this. I will start with a lengthy description of a system setup and then simply state that what I want works on Windows but not on Linux.

Image two  machines, Machine A and Machine B, both in the same Class-C subnet (192.168.1.0/24). Machine A is dual boot amd64 and alternately runs Windows 7 and Linux. Machine B is a black box for me, but it's providing a service that can be emulated by a tiny Go program listening for UDP datagrams on all network devices port <portB> and sending something back via UDP to 255.255.255.255:<portA>:

func main() {
        portA := 56789
        portB := portA + 1
        fmt.Println("I am machine B")
        ln, _ := net.ListenUDP("udp4", &net.UDPAddr{Port: portB})
        raddr := &net.UDPAddr{IP: []byte{255, 255, 255, 255}, Port: portA}
        b := make([]byte, 128)
        for {
                n, _ := ln.Read(b)
                fmt.Println("Echoing", string(b[:n]))
                ln.WriteToUDP(b[:n], raddr)
        }
}

Machine B is doing a tremendous job. Machine B is not the problem (except for that I have no control over how it's doing things). The above code is only for reproducing the issue.  

Let's hop over to Machine A, shall we. Machine A is a client for the : It sends UDP datagrams to 255.255.255.255:<portB> and listens for UDP datagrams on <portA>. The only difference is that on Machine A, the call to ListenUDP is restricting the listening to the device of Machine A that I will reach Machine B on (that device has the IP 192.168.1.41). Here is the program that is running on Machine A:

func main() {
ipA := net.ParseIP("192.168.1.41")
portA := 10980
portB := portA + 1
fmt.Println("I am machine A, and I will send over", ipA)
laddr := &net.UDPAddr{IP: ipA, Port: portA}
raddr := &net.UDPAddr{IP: []byte{255, 255, 255, 255}, Port: portB}
d, _ := net.ListenUDP("udp4", laddr)
rb := make([]byte, 128)
for {
s := fmt.Sprint(rand.Int())
fmt.Println("Sending", s)
b := []byte(s)
d.WriteToUDP(b, raddr)
d.SetReadDeadline(time.Now().Add(time.Second))
n, err := d.Read(rb)
if e, ok := err.(net.Error); ok && e.Timeout() {
fmt.Println("Timeout")
} else if err != nil {
fmt.Println("Error: err")
} else {
fmt.Println("Received", string(rb[:n]))
time.Sleep(1 * time.Second)
}
}
}

As you can see, I send some data as a broadcast, as I do not know the IP of B, but I know that it will react on broadcast datagrams. I know from sniffing the network that Machine B is answering with a broadcast packet as well. The reason why I bind the ListenUDP to 192.168.1.41 is that there are more network interfaces on Machine A and the default route if over one of these other interfaces. I need to make sure the initial broadcast from A is send on the correct interface.

And finally, here's my issue: Machine A is receiving the answer only if Machine is running Windows. When I run that same code on Linux, that answer datagram is not received.

Is this something that is to be expected for Linux and Windows or am I running into a bug with Go?

Thanks,
Fabian

Mikio Hara

unread,
Sep 17, 2015, 6:41:51 PM9/17/15
to Fabian Wickborn, golang-nuts
On Thu, Sep 17, 2015 at 4:23 AM, Fabian Wickborn <dsimf...@gmail.com> wrote:

> Is this something that is to be expected for Linux and Windows

yes, and for BSD variants, yes.
if you want to know the behavioral difference of ipv4 broadcasts
including both limited and directed broadcasts btw platforms, try
https://github.com/mikioh/-stdyng/tree/master/cmd/bcspkr.

Fabian Wickborn

unread,
Sep 18, 2015, 3:10:21 AM9/18/15
to golang-nuts, dsimf...@gmail.com
Mikio, thank you very much! 

I found the code you provided to be very insightful. Looks like I need to try a different approach on Linux to detect Machine B, then.

Your code in sock_old.go gave me a good example on how to set-up a UDP socket in Go with syscalls directly but still getting something that implements io.ReaderFrom and io.WriterTo. This is very helpful to me as I now grasp how to set SO_REUSEADDR prior to binding a socket to a UDP port. Maybe I can trick Linux to talk to Machine B by using two ports, one for sending the initial limited broadcast over a certain device (with source port <portA>) and the other one listening on <portA> on all devices.

Fabian
Reply all
Reply to author
Forward
0 new messages