How to read multicast packets from specific interface?

1,636 views
Skip to first unread message

Everton Marques

unread,
Feb 10, 2016, 7:24:04 AM2/10/16
to golang-nuts
I'm trying to read multicast packets directed to 224.0.0.9:520, but only when received from interface eth2 (its IP address is 1.0.0.2/24).

My sample code is based on: https://godoc.org/golang.org/x/net/ipv4

Roughly steps are:
1) Find interface eth2 with: net.InterfaceByName("eth2")
2) Bind socket to 1.0.0.2:520 with: net.ListenPacket("udp", "1.0.0.2:520")
3) Join multicast group 224.0.0.9: JoinGroup(ifi, 224.0.0.9)
4) Read data with: ReadFrom(buf)

Full code is available here: http://play.golang.org/p/YOwu38OQdw

In order to test, I send UDP packets from the attached host (1.0.0.1/24):
$ echo x | nc -s 1.0.0.1 -u 224.0.0.9 520

Then I verify those packets are actually reaching the destination host:
$ sudo tcpdump -n -i eth2
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth2, link-type EN10MB (Ethernet), capture size 262144 bytes
12:08:34.745171 IP 1.0.0.1.51920 > 224.0.0.9.520:  [|rip]

However my Go code running on host 1.0.0.2 is unable to read those packets:
$ sudo go run rip_mcast_listener.go
[sudo] password for lab:
2016/02/10 12:12:29 interfaceAdd: join error for 'fe80::a00:27ff:fe52:9575' on 'eth2': join: udp/[fe80::a00:27ff:fe52:9575]:520 listen error: listen udp [fe80::a00:27ff:fe52:9575]:520: bind: invalid argument
2016/02/10 12:12:29 main: waiting forever
2016/02/10 12:12:29 udpReader: reading from '1.0.0.2' on 'eth2'


go version is: go version go1.6rc2 linux/386

Can anyone please spot where the error is?

Thanks,
Everton

Mikio Hara

unread,
Feb 11, 2016, 1:05:36 AM2/11/16
to Everton Marques, golang-nuts
On Wed, Feb 10, 2016 at 9:24 PM, Everton Marques
<everton...@gmail.com> wrote:

> 2) Bind socket to 1.0.0.2:520 with: net.ListenPacket("udp", "1.0.0.2:520")

What does the output of "netstat -s" say? I guess you may see some
numbers on "udp: broadcast/multicast datagrams undelivered" line
because the kernel cannot find out any deliverable socket. Please try
net.ListenPacket("udp4", "0.0.0.0:520") instead, and use
ipv4.ControlMessage for message classifying/filtering.

If you want to have purpose-specific multiple sockets, kind of a
single socket that can receive unicast packets and send both unicast
and multicast packets, and multiple per-interface multicast
receive-only sockets, you can use net.FilePacketConn;
https://github.com/mikioh/-stdyng/tree/master/cmd/mcrcvr might be a
help.

It's the same as building routing/signaling protocols in other languages.

Everton Marques

unread,
Feb 11, 2016, 8:43:41 AM2/11/16
to golang-nuts, everton...@gmail.com

On Thursday, February 11, 2016 at 4:05:36 AM UTC-2, Mikio Hara wrote:
On Wed, Feb 10, 2016 at 9:24 PM, Everton Marques
<everton...@gmail.com> wrote:

> 2) Bind socket to 1.0.0.2:520 with: net.ListenPacket("udp", "1.0.0.2:520")

What does the output of "netstat -s" say? I guess you may see some
numbers on "udp: broadcast/multicast datagrams undelivered" line
because the kernel cannot find out any deliverable socket. Please try
net.ListenPacket("udp4", "0.0.0.0:520") instead, and use
ipv4.ControlMessage for message classifying/filtering.


In order to try your suggestion, I have added command line arguments to the sample code:

usage:   rip_mcast_listener interface protocol address:port
example: rip_mcast_listener eth2      udp      1.0.0.2:520

Your suggestion (:520) actually works:

$ sudo go run rip_mcast_listener.go eth2 udp :520
2016/02/11 13:24:32 join: eth2 :520 multicastInterface=nil
2016/02/11 13:24:32 udpReader: reading multicast
2016/02/11 13:24:50 udpReader: recv 2 bytes from 1.0.0.1 to 224.0.0.9 on eth2 (ifindex=4)

While this (1.0.0.2:520) does not:

$ sudo go run rip_mcast_listener.go eth2 udp 1.0.0.2:520
2016/02/11 13:25:08 join: eth2 1.0.0.2:520 multicastInterface=nil
2016/02/11 13:25:08 udpReader: reading multicast

Funny thing is both calls add 224.0.0.9 to output of netstat -ng:

$ netstat -ng | grep 224.0.0.9
eth2            1      224.0.0.9

It seems JoinGroup(iface) may be enough to restrict reading to eth2 ???

p.JoinGroup(iface, &net.UDPAddr{IP: net.IPv4(224, 0, 0, 9)})

Full new code available here: http://play.golang.org/p/G-1hUG_5nC

 

If you want to have purpose-specific multiple sockets, kind of a
single socket that can receive unicast packets and send both unicast
and multicast packets, and multiple per-interface multicast
receive-only sockets, you can use net.FilePacketConn;
https://github.com/mikioh/-stdyng/tree/master/cmd/mcrcvr might be a
help.

It's the same as building routing/signaling protocols in other languages.


Nice!

Thank you a lot,
Everton

Everton Marques

unread,
Feb 11, 2016, 1:10:37 PM2/11/16
to golang-nuts, everton...@gmail.com
On Thursday, February 11, 2016 at 4:05:36 AM UTC-2, Mikio Hara wrote:

If you want to have purpose-specific multiple sockets, kind of a
single socket that can receive unicast packets and send both unicast
and multicast packets, and multiple per-interface multicast
receive-only sockets, you can use net.FilePacketConn;
https://github.com/mikioh/-stdyng/tree/master/cmd/mcrcvr might be a
help.



"multiple per-interface multicast receive-only sockets"

This is actually what I was searching for.

I think I have found a Golang trick for doing it:

"It is possible for multiple UDP listeners that listen on the same UDP port
to join the same multicast group. The net package will provide a socket
that listens to a wildcard address with reusable UDP port when an
appropriate multicast address prefix is passed to the net.ListenPacket or
net.ListenUDP."


The tricky part is:
net.ListenPacket("udp", "unicast_ip:520") --> does not work at all for multicast
net.ListenPacket("udp", ":520") --> works, but does not allow other multicast sockets bound to port 520
net.ListenPacket("udp", "224.0.0.9:520") --> works, and allows one multicast socket per interface

Everton

Everton Marques

unread,
Feb 11, 2016, 3:31:31 PM2/11/16
to golang-nuts, everton...@gmail.com
> The tricky part is:
> net.ListenPacket("udp", "unicast_ip:520") --> does not work at all for multicast
> net.ListenPacket("udp", ":520") --> works, but does not allow other multicast sockets bound to port 520
> net.ListenPacket("udp", "224.0.0.9:520") --> works, and allows one multicast socket per interface

Correcting myself:

net.ListenPacket("udp", "unicast_ip:520") --> does not work at all for multicast
net.ListenPacket("udp", ":520") --> works, but does not allow other multicast sockets bound to port 520
net.ListenPacket("udp", "224.0.0.9:520") --> works, allows multiple multicast sockets bound to 520, but socket reads from all interfaces :(


However I am still unable to restrict socket to reading from specific interface. :(

Results were:

# Binding to unicast address 1.0.0.2 of eth2 does NOT work at all:

$ go run mcast_listener.go eth2 udp 224.0.0.9 1.0.0.2:2000
2016/02/11 20:16:26 readLoop: reading

# Binding to 0.0.0.0 works but reads from all interfaces:

$ go run mcast_listener.go eth2 udp 224.0.0.9 :2000
2016/02/11 20:14:42 readLoop: reading
2016/02/11 20:14:54 readLoop: recv 2 bytes from 1.0.0.1 to 224.0.0.9 on eth2

$ go run mcast_listener.go lo udp 224.0.0.9 :2000
2016/02/11 20:14:48 readLoop: reading
2016/02/11 20:14:54 readLoop: recv 2 bytes from 1.0.0.1 to 224.0.0.9 on eth2

# Binding to 224.0.0.9 works but reads from all interfaces:

$ go run mcast_listener.go eth2 udp 224.0.0.9 224.0.0.9:2000
2016/02/11 20:17:56 readLoop: reading
2016/02/11 20:18:23 readLoop: recv 2 bytes from 1.0.0.1 to 224.0.0.9 on eth2

$ go run mcast_listener.go lo udp 224.0.0.9 224.0.0.9:2000
2016/02/11 20:18:18 readLoop: reading
2016/02/11 20:18:23 readLoop: recv 2 bytes from 1.0.0.1 to 224.0.0.9 on eth2

Full mcast_listener.go code here: http://play.golang.org/p/w1VI6k4sRR

Any other suggestion on how to create multiple per-interface multicast listener sockets (that don't receive datagrams from other interfaces)?

Thanks,
Everton

Mikio Hara

unread,
Feb 11, 2016, 8:24:56 PM2/11/16
to Everton Marques, golang-nuts
Looks like my hands slipped in the previous message.

> If you want to have ...
> and multicast packets, and multiple per-interface multicast
> receive-only sockets

I mean; s/per-interface/per-group/.

On Fri, Feb 12, 2016 at 5:31 AM, Everton Marques
<everton...@gmail.com> wrote:

> However I am still unable to restrict socket to reading from specific
> interface. :(

I'm not sure what you are struggling for. Looks like you want to
override the packet delivery path inside the kernel. In general it's
not possible only using socket system calls on vanilla kernels because
the path in vanilla kernels looks like the following:
1) check with destination IP address field on the received IP packet, then
2) check with destination port field on the received UDP packet

net.ListenPacket and ipv4.JoinGroup just create an IP address filter
for (1), also net.ListenPacket creates a UPD port filter for (2). You
can use some filtering stuff inside kernel, or install some kernel
extension that tweaks UDP packet delivery path.

Everton Marques

unread,
Feb 12, 2016, 12:18:40 PM2/12/16
to golang-nuts
On Thu, Feb 11, 2016 at 11:24 PM, Mikio Hara <mikioh...@gmail.com> wrote:
On Fri, Feb 12, 2016 at 5:31 AM, Everton Marques
<everton...@gmail.com> wrote:

> However I am still unable to restrict socket to reading from specific
> interface. :(

I'm not sure what you are struggling for. Looks like you want to
override the packet delivery path inside the kernel.

Task at hand is quite simple, unfortunately I am failing to describe it sanely. :-)

I want to open 2 UDP sockets, on the same host, like this:
1.1) Socket S1 should receive packets addressed to 224.0.0.9:520, but only if packets arrive at eth1
1.2) Socket S2 should receive packets addressed to 224.0.0.9:520, but only if packets arrive at eth2

My Go code was failing as follows:
2.1) Socket S1 receives packets addressed to 224.0.0.9:520 (but S1 also receives packets from all other interfaces!)
2.2) Socket S2 receives packets addressed to 224.0.0.9:520 (but S1 also receives packets from all other interfaces!)

Fortunately I was able to do exactly what I need, on Linux, but coding in C. This did the trick:
setsockopt(s1, SOL_SOCKET, SO_BINDTODEVICE, "eth1", strlen("eth1"))
setsockopt(s2, SOL_SOCKET, SO_BINDTODEVICE, "eth2", strlen("eth2"))

Right now, I also added SO_BINDTODEVICE to Go code:
syscall.SetsockoptString(s, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, ifname)

Still testing, but it finally seems to work as desired. :)

Thanks a lot,
Everton

Reply all
Reply to author
Forward
0 new messages