4

I have a program that sends multicast traffic via IPv6. It sends traffic out of the default network interface. It works fine normally (traffic goes out eth0) however if I have Docker loaded, then it instead sends traffic out the docker0 interface instead of eth0 so the traffic is no longer seen on the network.

An example of this is if I run the command ping ff05::1 and while that's running I check the interface with tcpdump -n host ff05::1 -i XXX with XXX as eth0 or docker0 then the packets don't always leave via eth0 as desired.

Now I could fix this problem by going through every single program I'm using and specifying the interface to bind on (e.g. with ping's -I option above), however this is problematic as I want to share the same configuration files across multiple computers, but I'd have to put different IPv6 addresses and interface names for each one, and remember to update them when those with dynamic IP addresses change.

Instead, I would like to understand how Linux selects the interface to use when binding to a socket, in the hope that I can manually set the preferred interface on each machine. Then the programs I am using will send and receive their multicast traffic on the correct interface without requiring any special configuration.

I have tried adding routes for the network (ip -6 route add ff05::/16 dev eth0) however this did not make any difference. The problem seems to be that Linux picks an interface to bind to (before routing happens) and then uses that interface's IP address as the source address on the outgoing packets, locking it to that interface regardless of routing rules.

So, using the ping command above as an example, how is the default outgoing interface selected (when -I is not used) and how does one go about changing it?

2 Answers 2

5

Note: One should use FF05::2 (site-local all routers multicast address) or FF02::1 (link-local all nodes multicast address, which should probably use interface scoping) because FF05::1 (hypothetical site-local all nodes multicast address) is not defined and will probably never get an answer anyway (ip -6 maddress doesn't show it on any Linux system at least while routers do show ff05::2).

Each time a new interface is added and brought up, among various automatic addresses, an IPv6 multicast route is added... in the local table, hence not visible in the main routing table:

$ ip route get ff05::02
multicast ff05::2 from :: dev dummy0 table local proto kernel src fe80::8c5f:87ff:fe50:d08a metric 256 pref medium


$ ip -6 route show type multicast table local
multicast ff00::/8 dev dummy0 proto kernel metric 256 pref medium
multicast ff00::/8 dev lxcbr0 proto kernel metric 256 pref medium
multicast ff00::/8 dev eth0 proto kernel metric 256 pref medium
...

order among equals is quite random and difficult to reproduce. Each time an interface is brought down/up the multicast entry is deleted and added back, but order can change then. Only kernel can add "equal routes" like this, user is not allowed to do it (=> RTNETLINK answers: File exists). The local table being used before any other table in rules precedence (priority 0), a similar entry in the main table likely has no effect:

$ ip -6 rule
0:  from all lookup local
32766:  from all lookup main

Anyway. One just has to manually add a multicast route in the local table with lower metric to select deterministically which is the "default" multicast route:

# ip route add multicast ff00::/8 dev eth0 table local metric 100

$ ip route get ff05::02
multicast ff05::2 from :: dev eth0 table local src 2001:db8:123:4567:52fb:e94:d3b7:5ba8 metric 100 pref medium

This route will disappear when the interface is brought down (and up), so should be added along the tool configuring this interface.


Note: IPv4 doesn't define any multicast route by default anywhere (and follows some kernel built-in logic to know if the address is multicast), so the multicast route follows the default route. If one wants to set aside a multicast block (eg: using a tool that generates multicast flows but not wanting to flood the LAN having no IGMP-snooping capable switch with this flow), one can do the opposite and add multicast type route for these flows on a different (here virtual) interface. As there's no local table interaction, the main table is good enough (but table local could also be added):

# ip route add multicast 239.255.16.0/20 dev dummy0

$ ip route get 239.255.16.2
multicast 239.255.16.2 dev dummy0 src 172.20.21.22 uid 0 
    cache <mc> 
1
  • That was extremely helpful and very informative, many thanks for such a great answer!
    – Malvineous
    Commented Aug 8, 2021 at 13:49
1

To add to A.B's fine explanation, you can tell systemd to add the multicast route as described, by editing the config file /etc/systemd/network/*.network for the network interface that should handle multicast traffic, and adding this to the end of it (making an additional [Route] section if you already have one):

# Have all IPv6 multicast traffic go out on this interface.
[Route]
Type=multicast
Destination=ff00::/8
Metric=100
Table=local

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .