How to Send Multicast Packets via a Specfic Interface in Linux

How to send multicast packets via a specfic interface in Linux

I'm not entirely convinced my solution is correct, but I can at least shed a little more light on what is going on.

Background

Linux actually has multiple routing tables, and they are searched one at a time in a specific priority order until a table with a matching route is found. You can optionally search some of the routing tables based on source address or protocol; see the ip-rule(8) man page.

The trouble is the "local" routing table, which has priority 0, the highest possible. The "local" table is populated automatically by the kernel and holds "obvious" interface and broadcast routes. For IPv6 under Linux, this apparently includes the entire multicast block.

The Problem

I'm going to be using the iproute2 tool rather than the more traditional route, because it will show me everything I need to know.

On my Linux box:

$ ip -6 route show table local
local ::1 via :: dev lo proto none metric 0
local fe80::213:a9ff:fe91:5bcb via :: dev lo proto none metric 0
local fe80::250:b6ff:fe44:37d1 via :: dev lo proto none metric 0
ff00::/8 dev eth0 metric 256
ff00::/8 dev eth1 metric 256

$ ip -6 route show table main
fe80::/64 dev eth0 proto kernel metric 256
fe80::/64 dev eth1 proto kernel metric 256
ff15::/16 dev eth1 metric 1024
ff00::/8 dev eth1 metric 1024

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

...And my multicast packets for ff15::1 (5==site-local, >link-local) end up on eth0, because the "local" routing table matches first and overrides the "main" table, even though the "main" table has a more specific route. This overriding behavior is correct in the greater scheme of policy routing, but the choice of auto-adding ff00::/8 to the local table is questionable to me.

My Solution

I don't have enough experience to know if this is a good idea, but:

# ip -6 route add ff15::/16 dev eth1 table local

and now my ff15::1 packets are routed through eth1.

This agrees somewhat with the semantics of the local table, in that it's routed directly through a device. It doesn't feel exactly right (considering automatic management and "you shouldn't have to look at this table"), but it's the best solution I've found.

How to specify the multicast SEND interface in Python?

The question that you've linked How to Multicast (send) to first NIC? mentions that you could use IP_MULTICAST_IF:

sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(addr))

Sending IPv6 multicast packets through a specific network interface

I didn't really like the previous solution, so I kept looking for others.

The first option that worked for me is to bind the sender socket to the specific network interface address. The network interfaces addresses can be found using netifaces module, and I used this helpful answer to specify the Ethernet address.

Another option that might work is the IPV6_MULTICAST_IF option-

#x is the relevant interface index
sock.setsockopt(socket.IPPROTO_IPV6,socket.IPV6_MULTICAST_IF,x)

In Windows, python 2.7, one should add the line

socket.IPPROTO_IPV6=41

before this code (since the relevant enum is not well defined).

Additional information can be found here (Windows) or here (Linux).

Although it seems like a simpler solution, I didn't completely managed to make it work, and not sure what is the proper way to find the right interface index (on Windows, Linux has several options).

How to filter a multicast receiving socket by interface?

Yes, you can do what you want on Linux, without root privileges:

Bind to INADDR_ANY and set the IP_PKTINFO socket option. You then have to use recvmsg() to receive your multicast UDP packets and to scan for the IP_PKTINFO control message. This gives you some side band information of the received UDP packet:

struct in_pktinfo {
unsigned int ipi_ifindex; /* Interface index */
struct in_addr ipi_spec_dst; /* Local address */
struct in_addr ipi_addr; /* Header Destination address */
};

The ipi_ifindex is the interface index the packet was received on. (You can turn this into an interface name using if_indextoname() or the other way round with if_nametoindex().

As you said on Windows the same network functions have different semantics, especially for UDP and even more for multicast.

The Linux bind() semantics for the IP address for UDP sockets are mostly useless. It is essentially just a destination address filter. You will almost always want to bind to INADDR_ANY for UDP sockets since you either do not care to which address a packet was sent or you want to receive packets for multiple addresses (e.g. receiving unicast and multicast).

Multicast route to all interfaces

You can use route to add mutlicast routes to multiple devices.

sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0
sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev wlan0

This was reflected in the routing table and allowed our services operating on the separate NICs to work.

However, its not clear to me that you should do this.

The reason I am not sure if the is "valid" is that I tried to port from using route which is apparently deprecated/obsolete to using "ip route", eg

ip route add 224.0.0.0/4 dev eth0
ip route add 224.0.0.0/4 dev wlan0

However ip route won't let you add the second route. I have asked about how to properly use ip route without having to use explicit multicast group addresses, but if you are comfortable using route, you should be able to add the route to each device you need to use.



Related Topics



Leave a reply



Submit