Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I (IGMP) join a stream on two NICs and answer (IGMP) queries on both NICs in Linux?

I made a Linux application to receive multicast traffic. It works when I connect to one interface. When I connect to a stream, in Wireshark, I see an IGMP join, and when the switch sends IGMP queries, Linux replies with an IGMP report for the stream.

However, I need more bandwidth than my one interface can provide. To have more bandwidth, I have multiple interfaces on the same network. I therefore duplicated my code to have two interfaces connect to a stream. In that case, in Wireshark, I see an IGMP join on both interfaces, but when the switch sends IGMP queries, Linux only replies with an IGMP report on one interface. Therefore, the switch timeout occurs and I lose the stream on the interface that is not reporting.

Here is a reproducible example. It doesn't receive any data, but it is enough to see the problem happen in Wireshark:

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
   // Create and fill internet socket address structure for both sockets.
   struct sockaddr_in internetSocketAdressStructure;
   bzero(&internetSocketAdressStructure, sizeof(internetSocketAdressStructure));
   internetSocketAdressStructure.sin_family = AF_INET;
   internetSocketAdressStructure.sin_addr.s_addr=htonl(INADDR_ANY);
   internetSocketAdressStructure.sin_port = htons(10000);

   // Create first socket.
   int firstSocketToUse;
   if ((firstSocketToUse = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
      perror("First socket failed");
      exit(1);
   }

   // Set first socket for address reuse.
   int reuseAddressForFirstSocket;
   reuseAddressForFirstSocket = 1;
   if (setsockopt(firstSocketToUse, SOL_SOCKET, SO_REUSEADDR, ( char* )&reuseAddressForFirstSocket, sizeof(reuseAddressForFirstSocket) ) == -1 ) {
      perror("Error setting first socket for address reuse");
      exit(1);
   }

   // Bind first socket.
   if (bind(firstSocketToUse, (struct sockaddr *)&internetSocketAdressStructure, sizeof(internetSocketAdressStructure))==-1) {
      perror("First bind failed");
      exit(1);
   }

   // Join stream on first socket.
   struct ip_mreq multicastRequestOnFirstInterface;
   multicastRequestOnFirstInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
   multicastRequestOnFirstInterface.imr_interface.s_addr = inet_addr("25.25.40.116");
   if (setsockopt(firstSocketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnFirstInterface, sizeof(multicastRequestOnFirstInterface)) == -1) {
      perror("Error joining multicast group on Interface 1");
      exit(1);
   }

   // Create second socket.
   int secondSocketToUse;
   if ((secondSocketToUse = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
      perror("Second socket failed");
      exit(1);
   }

   // Set second socket for address reuse.
   int reuseAddressForSecondSocket;
   reuseAddressForSecondSocket = 1;
   if (setsockopt(secondSocketToUse, SOL_SOCKET, SO_REUSEADDR, ( char* )&reuseAddressForSecondSocket, sizeof(reuseAddressForSecondSocket) ) == -1 ) {
      perror("Error setting second socket for address reuse");
      exit(1);
   }

   // Bind second socket.
   if (bind(secondSocketToUse, (struct sockaddr *)&internetSocketAdressStructure, sizeof(internetSocketAdressStructure))==-1) {
      perror("Second bind failed");
      exit(1);
   }

   // Join stream on second socket.
   struct ip_mreq multicastRequestOnSecondInterface;
   multicastRequestOnSecondInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
   multicastRequestOnSecondInterface.imr_interface.s_addr = inet_addr("25.25.40.134");
   if (setsockopt(secondSocketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnSecondInterface, sizeof(multicastRequestOnSecondInterface)) == -1) {
      perror("Error joining multicast group on Interface 2");
      exit(1);
   }

   // Wait forever.
   while(1) {}
}

I saw a post on Stackoverflow suggesting to do this with only one socket, so I tried that, but the same issue occurs. Here is the code for that:

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
   // Create and fill internet socket address structure for the socket.
   struct sockaddr_in internetSocketAdressStructure;
   bzero(&internetSocketAdressStructure, sizeof(internetSocketAdressStructure));
   internetSocketAdressStructure.sin_family = AF_INET;
   internetSocketAdressStructure.sin_addr.s_addr=htonl(INADDR_ANY);
   internetSocketAdressStructure.sin_port = htons(10000);

   // Create socket.
   int socketToUse;
   if ((socketToUse = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
      perror("socket failed");
      exit(1);
   }

   // Bind socket.
   if (bind(socketToUse, (struct sockaddr *)&internetSocketAdressStructure, sizeof(internetSocketAdressStructure))==-1) {
      perror("bind failed");
      exit(1);
   }

   // Set first socket for address reuse.
   int reuseAddress;
   reuseAddress = 1;
   if (setsockopt(socketToUse, SOL_SOCKET, SO_REUSEADDR, ( char* )&reuseAddress, sizeof(int) ) == -1 ) {
      perror("Error setting socket for address reuse");
      exit(1);
   }

   // Join stream on first interface.
   struct ip_mreq multicastRequestOnFirstInterface;
   multicastRequestOnFirstInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
   multicastRequestOnFirstInterface.imr_interface.s_addr = inet_addr("25.25.40.116");
   if (setsockopt(socketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnFirstInterface, sizeof(multicastRequestOnFirstInterface)) == -1)
   {
      perror("Error joining multicast group on first interface");
      exit(1);
   }

   // Join stream on second interface.
   struct ip_mreq multicastRequestOnSecondInterface;
   multicastRequestOnSecondInterface.imr_multiaddr.s_addr = inet_addr("239.120.15.2");
   multicastRequestOnSecondInterface.imr_interface.s_addr = inet_addr("25.25.40.134");
   if (setsockopt(socketToUse, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&multicastRequestOnSecondInterface, sizeof(multicastRequestOnSecondInterface)) == -1)
   {
      perror("Error joining multicast group on second interface");
      exit(1);
   }

   // Wait forever.
   while(1) {}
}

I've also tried to put the interfaces in promiscuous mode:

sudo ip link set interface1 promisc on
sudo ip link set interface2 promisc on

and add the noprefixroute option to each interface:

sudo ip addr change 25.25.40.134 dev interface1 noprefixroute
sudo ip addr change 25.25.40.116 dev interface2 noprefixroute

Both those things failed to solve my problem.

That being said, I found sources (1, 2) that might indicate that what I'm trying to do is impossible, though these sources seem somewhat old.

I was able to fix the issue by putting both ports on the switch to different vlans, though that is a clunky/awful solution that might not be acceptable.

Is connecting to multicast streams on the same network through two interfaces and configure the network to answer the IGMP queries on both interfaces possible?

like image 595
PhilippeAtM Avatar asked Jan 23 '26 12:01

PhilippeAtM


1 Answers

It is explained as clear as day here: https://access.redhat.com/solutions/53031

And less clearly here: https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt

Considering a computer with 2 net interfaces, interfaceA and interfaceB. Considering that Linux decides to use interfaceB to send packets to ip address X. Considering a packet that is received on interfaceA from ip address X. Linux will drop the packet.

Unless you run sysctl net.ipv4.conf.all.rp_filter=2 in a terminal or add that line to /etc/sysctl.conf.

It enables receiving packets from an ip address on other interfaces than the one it uses to send packets to that ip address!

like image 69
PhilippeAtM Avatar answered Jan 26 '26 02:01

PhilippeAtM