unsealed counsel
08 Jan 2022

Multicast routing debugging

Setting up multicast reception on Fedora 34 requires using firewall-cmd to allow multicast traffic through firewalld via rich rules. The following is a description of a debugging session (or two) that helped identify the problem.

The basic problem was that multicast traffic was not received by a userland program on Fedora 34 x86_64 machine. All of the tests below use the excellent mcjoin multicast test program.

1. Checking iptables

My first hypothesis was that multicast was blocked by the standard firewall. As I was familiar only with ipchains and iptables, I verified first that ipchains was not in use. From the iptables man page:

This iptables is very similar to ipchains by Rusty Russell. The main difference is that the chains INPUT and OUTPUT are only traversed for packets coming into the local host and originating from the local host respectively. Hence every packet only passes through one of the three chains (except loopback traffic, which involves both INPUT and OUTPUT chains); previously a forwarded packet would pass through all three.

Trusting the quote above, the simplest way to test whether the INPUT and OUTPUT chains inhibit multicast reception is to use loopback. Done as follows:

sudo ifconfig lo multicast # loopback normally does not allow multicast
sudo route add -host 224.12.12.12 dev lo # cargo-cult, but let's leave it here for now

With the setup above, the route table and the lo interface are ready for multicast testing:

[ravi@rcvr ~]$ ifconfig lo
lo: flags=4169<UP,LOOPBACK,RUNNING,MULTICAST>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 28  bytes 2986 (2.9 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 28  bytes 2986 (2.9 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[ravi@rcvr ~]$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         10.100.66.1     0.0.0.0         UG    100    0        0 enp4s0
10.100.66.0     0.0.0.0         255.255.255.0   U     100    0        0 enp4s0
192.168.211.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0
224.12.12.12    0.0.0.0         255.255.255.255 UH    0      0        0 lo

Multicast published to 224.12.12.12 is received appropriately by the receiver bound to lo:

mcjoin -s 224.12.12.12 -p 1234 -i lo # sender
mcjoin -j 224.12.12.12 -i lo # receiver

The next step is to remove the multicast address from the route table:

sudo route del -host 224.12.12.12 dev lo

which results in

[ravi@rcvr ~]$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         10.100.66.1     0.0.0.0         UG    100    0        0 enp4s0
10.100.66.0     0.0.0.0         255.255.255.0   U     100    0        0 enp4s0
192.168.211.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0

Multicast transmission and reception work as expected again.

My conclusion is that iptables does not block multicast. In case I am mistaken, here are the iptables rules:

ravi@rcvr ~]$ for table in filter nat; do echo "------ $table ------"; sudo iptables -t $table -L -n; done
------ filter ------
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
LIBVIRT_INP  all  --  0.0.0.0/0            0.0.0.0/0

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
LIBVIRT_FWX  all  --  0.0.0.0/0            0.0.0.0/0
LIBVIRT_FWI  all  --  0.0.0.0/0            0.0.0.0/0
LIBVIRT_FWO  all  --  0.0.0.0/0            0.0.0.0/0

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
LIBVIRT_OUT  all  --  0.0.0.0/0            0.0.0.0/0

Chain LIBVIRT_FWI (1 references)
target     prot opt source               destination
ACCEPT     all  --  0.0.0.0/0            192.168.211.0/24     ctstate RELATED,ESTABLISHED
REJECT     all  --  0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable

Chain LIBVIRT_FWO (1 references)
target     prot opt source               destination
ACCEPT     all  --  192.168.211.0/24     0.0.0.0/0
REJECT     all  --  0.0.0.0/0            0.0.0.0/0            reject-with icmp-port-unreachable

Chain LIBVIRT_FWX (1 references)
target     prot opt source               destination
ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0

Chain LIBVIRT_INP (1 references)
target     prot opt source               destination
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:67
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:67

Chain LIBVIRT_OUT (1 references)
target     prot opt source               destination
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:53
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:53
ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0            udp dpt:68
ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:68
------ nat ------
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
LIBVIRT_PRT  all  --  0.0.0.0/0            0.0.0.0/0

Chain LIBVIRT_PRT (1 references)
target     prot opt source               destination
RETURN     all  --  192.168.211.0/24     224.0.0.0/24
RETURN     all  --  192.168.211.0/24     255.255.255.255
MASQUERADE  tcp  --  192.168.211.0/24    !192.168.211.0/24     masq ports: 1024-65535
MASQUERADE  udp  --  192.168.211.0/24    !192.168.211.0/24     masq ports: 1024-65535
MASQUERADE  all  --  192.168.211.0/24    !192.168.211.0/24

All of the libvirt and 192.168.211.0 stuff above is due to the presence of a virtual machine used for testing. In the end, those pieces turned out to be irrelevant, but I left them in to show acutal debugging values.

2. Sending multicast from a separate host

  Sender: sndr Receiver: rcvr
IP address 10.100.66.68 10.100.66.67
Interface enp1s0 enp4s0
Kernel version 5.14.16-201.fc34.x86_64 5.15.11-100.fc34.x86_64
  • Both hosts can ping each other.
  • Traffic between them does not have any VLAN tagging, since they are on the same VLAN.
  • Each of them has a wireless interface without an assigned IP address or an associated access point.
  • Sender sends multicast on 224.17.17.17 port 1234.

Focusing on the receiver:

[ravi@rcvr ~]$ sudo sysctl -ar '\.rp_filter'
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.enp4s0.rp_filter = 2
net.ipv4.conf.lo.rp_filter = 2
net.ipv4.conf.virbr0.rp_filter = 2
net.ipv4.conf.wlp3s0.rp_filter = 2

Packets are sent from the transmitter at 1pps:

./mcjoin -f 1000 -s -t 4 224.17.17.17

The receiver does not see the data in userland both with and without the useless multicast route:

sudo route add -host 224.17.17.17 dev enp4s0 # for testing
sudo route del -host 224.17.17.17 dev enp4s0 # remove testing

Packets are visible on the interface, regardless of whether there has been a multicast join, since they are on the same VLAN and the switch floods those packets:

[ravi@rcvr ~]$ sudo tcpdump -i enp4s0 -nn -e -vv -c 5 multicast and not broadcast
dropped privs to tcpdump
tcpdump: listening on enp4s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
09:24:36.838722 48:9a:e3:30:5c:74 > 01:00:5e:11:11:11, ethertype IPv4 (0x0800), length 142: (tos 0x0, ttl 4, id 25384, offset 0, flags [DF], proto UDP (17), length 128)
    10.100.66.68.36682 > 224.17.17.17.1234: [udp sum ok] UDP, length 100
09:24:37.839498 48:9a:e3:30:5c:74 > 01:00:5e:11:11:11, ethertype IPv4 (0x0800), length 142: (tos 0x0, ttl 4, id 25701, offset 0, flags [DF], proto UDP (17), length 128)
    10.100.66.68.36682 > 224.17.17.17.1234: [udp sum ok] UDP, length 100
09:24:38.840155 48:9a:e3:30:5c:74 > 01:00:5e:11:11:11, ethertype IPv4 (0x0800), length 142: (tos 0x0, ttl 4, id 25798, offset 0, flags [DF], proto UDP (17), length 128)
    10.100.66.68.36682 > 224.17.17.17.1234: [udp sum ok] UDP, length 100
09:24:39.840319 48:9a:e3:30:5c:74 > 01:00:5e:11:11:11, ethertype IPv4 (0x0800), length 142: (tos 0x0, ttl 4, id 26349, offset 0, flags [DF], proto UDP (17), length 128)
    10.100.66.68.36682 > 224.17.17.17.1234: [udp sum ok] UDP, length 100
09:24:40.840963 48:9a:e3:30:5c:74 > 01:00:5e:11:11:11, ethertype IPv4 (0x0800), length 142: (tos 0x0, ttl 4, id 27016, offset 0, flags [DF], proto UDP (17), length 128)
    10.100.66.68.36682 > 224.17.17.17.1234: [udp sum ok] UDP, length 100
5 packets captured
5 packets received by filter
0 packets dropped by kernel

Note that the TTL is 4 at the receiver, which should be more than sufficient.

When the receiver is running, packets do appear:

[ravi@rcvr ~]$ netstat -gn -4
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 10.100.66.67:42580      10.100.66.68:22         ESTABLISHED
[... other irrelevant connections redacted ...]
udp        0      0 10.100.66.67:68         10.100.66.1:67          ESTABLISHED
IPv6/IPv4 Group Memberships
Interface       RefCnt Group
--------------- ------ ---------------------
lo              1      224.0.0.251
lo              1      224.0.0.1
enp4s0          1      224.17.17.17
enp4s0          1      224.0.0.251
enp4s0          1      224.0.0.252
enp4s0          1      224.0.0.1
virbr0          1      224.0.0.251
virbr0          1      224.0.0.1
virbr0          1      224.0.0.106
[ravi@rcvr ~]$ netstat -sgu; sleep 10; netstat -sgu
IcmpMsg:
    InType3: 22
    OutType3: 22
Udp:
    2552 packets received
    0 packets to unknown port received
    0 packet receive errors
    2203 packets sent
    0 receive buffer errors
    0 send buffer errors
UdpLite:
IpExt:
    InMcastPkts: 754
    OutMcastPkts: 1105
    InBcastPkts: 2
    OutBcastPkts: 4
    InOctets: 3587966
    OutOctets: 757004
    InMcastOctets: 83946
    OutMcastOctets: 127091
    InBcastOctets: 4202
    OutBcastOctets: 4242
    InNoECTPkts: 5968
MPTcpExt:
IcmpMsg:
    InType3: 22
    OutType3: 22
Udp:
    2552 packets received
    0 packets to unknown port received
    0 packet receive errors
    2203 packets sent
    0 receive buffer errors
    0 send buffer errors
UdpLite:
IpExt:
    InMcastPkts: 764
    OutMcastPkts: 1105
    InBcastPkts: 2
    OutBcastPkts: 4
    InOctets: 3594625
    OutOctets: 757719
    InMcastOctets: 85226
    OutMcastOctets: 127091
    InBcastOctets: 4202
    OutBcastOctets: 4242
    InNoECTPkts: 5990
MPTcpExt:
[ravi@rcvr ~]$ ip maddr
1:      lo
        inet  224.0.0.251
        inet  224.0.0.1
        inet6 ff02::fb
        inet6 ff02::1
        inet6 ff01::1
2:      enp4s0
        link  01:00:5e:00:00:01
        link  33:33:00:00:00:01
        link  33:33:00:00:00:fb
        link  33:33:ff:13:a0:aa
        link  33:33:00:01:00:03
        link  01:00:5e:00:00:fc
        link  01:00:5e:00:00:fb
        link  01:00:5e:11:11:11
        inet  224.17.17.17
        inet  224.0.0.251
        inet  224.0.0.252
        inet  224.0.0.1
        inet6 ff02::1:3
        inet6 ff02::1:ff13:a0aa
        inet6 ff02::fb
        inet6 ff02::1
        inet6 ff01::1
3:      wlp3s0
        inet6 ff02::1
        inet6 ff01::1
4:      virbr0
        link  01:00:5e:00:00:6a
        link  33:33:00:00:00:6a
        link  01:00:5e:00:00:01
        link  01:00:5e:00:00:fb
        inet  224.0.0.251
        inet  224.0.0.1
        inet  224.0.0.106
        inet6 ff02::6a
        inet6 ff02::1
        inet6 ff01::1

Since packets are sent at the rate of 1 packet per second from the sender, the number of packets matches expectations in netstat -sgu.

Repeating the same experiment above with the following changes yields the same results.

[ravi@rcvr ~]$ sudo sysctl -w net.ipv4.conf.enp4s0.rp_filter=0
net.ipv4.conf.enp4s0.rp_filter = 0
[ravi@rcvr ~]$ sudo sysctl -w net.ipv4.conf.default.rp_filter=0
net.ipv4.conf.default.rp_filter = 0
[ravi@rcvr ~]$ sudo sysctl -w net.ipv4.conf.all.rp_filter=0
net.ipv4.conf.all.rp_filter = 0
[ravi@rcvr ~]$ sudo sysctl -ar '\.rp_filter'
net.ipv4.conf.all.rp_filter = 0
net.ipv4.conf.default.rp_filter = 0
net.ipv4.conf.enp4s0.rp_filter = 0
net.ipv4.conf.lo.rp_filter = 2
net.ipv4.conf.virbr0.rp_filter = 2
net.ipv4.conf.wlp3s0.rp_filter = 2

3. Solution

At this point, I started looking through any documentation for firewalls, as there was nothing else I could see. The man page for firewalld hinted vaguely at a debugging facility:

–set-log-denied=value

Add logging rules right before reject and drop rules in the INPUT, FORWARD and OUTPUT chains for the default rules and also final reject and drop rules in zones for the configured link-layer packet type. The possible values are: all, unicast, broadcast, multicast and off. The default setting is off, which disables the logging.

Useful debugging information came from

sudo firewall-cmd --set-log-denied=multicast

whose output could be observed via journalctl. We still need to figure out the exact set of multicast destinations to allow.

The problem is in netfilter; even though iptables did not return anything useful, firewalld (using nft) was still setting up rejections. All multicast except mDNS was blocked. The solution was to allow multicast via firewalld:

sudo firewall-cmd --add-rich-rule='rule family=ipv4 destination address="224.0.0.0/4" accept'
sudo firewall-cmd --add-protocol=igmp
sudo firewall-cmd --reload

It's annoying that there was no obvious way to query nftables rules directly without dropping into the interactive nft shell, but that's probably only a lack of knowledge on my part. As I was testing this during some VyOS SSDP/DLNA debugging, isolating this as a problem with the local host also took additional time.

Tags: multicast dlna ssdp vyos nftables iptables
Creative Commons License
runes.lexarcana.com by Ravi R Kiran is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License .