2

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.

dbush
  • 205,898
  • 23
  • 218
  • 273
Tom
  • 7,269
  • 1
  • 42
  • 69
  • This other example binds to the multicast group address, not the computer's address. Try that: https://stackoverflow.com/questions/603852/how-do-you-udp-multicast-in-python (but it's IPv4) – user253751 May 31 '22 at 17:18
  • Your multicast group, `ff22:5eea:675c:d1fc:17c::1`, has an invalid flags (`2`). IPv6 multicast uses a complex set of flags and scopes. The valid flags are Well-Known (`0`), Transient (`1`), Prefix-based (`3`) , and Embedded RP (`7`). Also, the four bits following the scope are reserved flags the must be set to `0`, and the next four bits are Reserved and must be `0`. You probably want something like `ff12:00ea:675c:d1fc:17c::1`. – Ron Maupin May 31 '22 at 17:59
  • _[RFC 7371, Updates to the IPv6 Multicast Addressing Architecture](https://www.rfc-editor.org/rfc/rfc7371.html)_ explains about the flags. In general, you really just want to use the Transient flag (`1`) for your own multicast groups unless you really know about Prefix-Based or Embedded RP. That would let you create an unimaginable number of multicast groups (local scope) in the `ff12::/24` range. – Ron Maupin May 31 '22 at 18:15
  • @user253751 - The reason I don't want to do that is that I specifically want to receive multicast traffic on one interface. If I bind to the multicast group, I'll get multicast traffic for all the interfaces, won't I? Or can I use the scope_id field in the address to specify the interface? – Tom Jun 01 '22 at 09:26
  • For a specific interface, you use `socket.IP_MULTICAST_IF`. – Ron Maupin Jun 01 '22 at 19:11
  • Thanks for your help. Between your help and some more messing around, I've figured it out. – Tom Jun 02 '22 at 10:07

1 Answers1

0

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()).
Tom
  • 7,269
  • 1
  • 42
  • 69