Skip to content
Advertisement

How to use the options to socket() for IPv6 multicast UDP?

I have a linux network interface that IPv6 multicast traffic is arriving on. There are both ICMPv6 packets and UDP packets arriving for the same multicast group.

I’m trying to receive the UDP traffic. The code below is in Python but I don’t believe this is important; the Python library here is a pretty thin wrapper around the BSD sockets API. This is what I’m doing:

import socket
import struct
import select
import ipaddress

# .packet is a byte array
mcast_group = ipaddress.IPv6Address("ff22:5eea:675c:d1fc:17c::1").packed
address = ipaddress.IPv6Address("...ipv6 global scope address of interface...")

sock = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
group = struct.pack("16s i",
            mcast_group,
            3 #Interface ID
        )
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
sock.bind((address.compressed, 0, 0, 0))

recv_socks, _, _ = select.select([sock], [], [], 10)
if recv_socks:
    print("Received packet")

With the code above, I appear to be receiving both the UDP and ICMPv6 packets. If I change the second parameter to socket.socket() from socket.SOCK_RAW to socket.SOCK_DGRAM then I receive no packets at all.

I’ve confirmed that both packet types are arriving on that interface, addressed to that multicast group, using wireshark.

There is something here that I’ve fundamentally not understood about socket(). Shouldn’t specifying IPPROTO_UDP mean that I only get UDP packets? Shouldn’t specifying SOCK_DGRAM still allow UDP packets through?

The Linux kernel is 4.9 on aarch64, in case that’s important.

Advertisement

Answer

Eventually figured this out. Here’s the correct way of doing this:

import socket
import struct
import select
import ipaddress

# .packet is a byte array
mcast_group = ipaddress.IPv6Address("ff22:5eea:675c:d1fc:17c::1")
address = ipaddress.IPv6Address("...ipv6 global scope address of interface...")
port_no = 5555
interface_idx = 3

sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
group = struct.pack("16s i",
            mcast_group.packed,
            3 #Interface ID
        )
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group)
sock.bind((str(mcast_group), port_no, 0, interface_idx))

recv_socks, _, _ = select.select([sock], [], [], 10)
if recv_socks:
    print("Received packet")

Important differences from the code in the question:

  • UDP (SOCK_DGRAM) sockets need to be bound to a port number. AFAICT this is why I was getting ICMP traffic as well as UDP with SOCK_RAW and no traffic at all with SOCK_DGRAM.
  • Instead of binding the socket to a unicast address on the interface of interest, bind it to the multicast group but use the interface index as the scope_id (the fourth in the address tuple passed to socket.bind()).
User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement