Search code examples
clinuxsocketsudpmulticast

Sending and receiving multicast on the same Linux machine from different interfaces


I have a network with several computers when each computer has several network interfaces with a single destination. I'm developing an application that actively uses multicast. In general everything works as expected. Except for the moment that I cannot receive a multicast on the same machine from which I'm sending via a second network interface. Other computers on the network can receive multicast through any network interface. Is it possible to send a multicast through one interface and receive it through another within the same network? If so, where did I make a mistake: in the client code, in the recipient code, or in the system settings?

A typical machine configuration:

$ ifconfig 
enp0s31f6: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 192.168.88.230  netmask 255.255.255.0  broadcast 192.168.88.255

enp10s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 192.168.88.229  netmask 255.255.255.0  broadcast 192.168.88.255

wlp6s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
    inet 192.168.88.48  netmask 255.255.255.0  broadcast 192.168.88.255

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
    inet 127.0.0.1  netmask 255.0.0.0

Routing table:

$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.88.1    0.0.0.0         UG    100    0        0 enp0s31f6
0.0.0.0         192.168.88.1    0.0.0.0         UG    101    0        0 enp10s0
0.0.0.0         192.168.88.1    0.0.0.0         UG    600    0        0 wlp6s0
169.254.0.0     0.0.0.0         255.255.0.0     U     1000   0        0 enp0s31f6
192.168.88.0    0.0.0.0         255.255.255.0   U     100    0        0 enp0s31f6
192.168.88.0    0.0.0.0         255.255.255.0   U     101    0        0 enp10s0
192.168.88.0    0.0.0.0         255.255.255.0   U     600    0        0 wlp6s0

These combinations work as expected

./sender 239.255.255.251 27335 192.168.88.48
./listener 239.255.255.251 27335 192.168.88.48

or

./sender 239.255.255.251 27335 192.168.88.229
./listener 239.255.255.251 27335 192.168.88.229

or

./sender 239.255.255.251 27335 192.168.88.230
./listener 239.255.255.251 27335 192.168.88.230

But these don't work:

./sender 239.255.255.251 27335 192.168.88.48
./listener 239.255.255.251 27335 192.168.88.229

or

./sender 239.255.255.251 27335 192.168.88.48
./listener 239.255.255.251 27335 192.168.88.230

or

./sender 239.255.255.251 27335 192.168.88.229
./listener 239.255.255.251 27335 192.168.88.230

For my task, I have adapted the code from several examples from https://tldp.org/HOWTO/Multicast-HOWTO-6.html

sender.c

//
// Simple sender.c program for UDP
//

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

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    if (argc < 3) {
        printf("Command line args should be multicast group and port\n");
        printf("(e.g. for SSDP, `sender 239.255.255.250 1900 [interface_ip]`)\n");
        return 1;
    }

    const char* group = argv[1]; // e.g. 239.255.255.250 for SSDP
    const int port = atoi(argv[2]); // 0 if error, which is an invalid port
    const char* source_iface = (argc == 4 ? argv[3] : NULL);
   
    //
    // create what looks like an ordinary UDP socket
    //
    const int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("socket");
        return 1;
    }

    struct ip_mreq mreq;
    memset(&mreq, 0, sizeof(mreq));
    mreq.imr_interface.s_addr = source_iface ? inet_addr(source_iface) : htonl(INADDR_ANY);

    if (
        setsockopt(
            fd, IPPROTO_IP, IP_MULTICAST_IF, (char*) &mreq, sizeof(mreq)
        ) < 0
    ){
        perror("setsockopt");
        return 1;
    }

    //
    // set up destination address
    //
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(group);
    addr.sin_port = htons(port);

    //
    // now just sendto() our destination
    //
    for (unsigned i = 0; ; i++) {
        char buffer[64];
        memset(buffer, '\0', sizeof(buffer));
        snprintf(buffer, sizeof(buffer), "Hello, World! Sequence: %u", i & 0xFF);

        const int nbytes = sendto(
            fd,
            buffer,
            sizeof(buffer),
            0,
            (struct sockaddr*) &addr,
            sizeof(addr)
        );
        if (nbytes < 0) {
            perror("sendto");
            return 1;
        }

        const int delay_secs = 1;
        sleep(delay_secs);
    }

    return 0;
}

listener.c

//
// Simple listener.c program for UDP multicast
//

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

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define MSGBUFSIZE 4096

int main(int argc, char *argv[])
{
    if (argc < 3) {
       printf("Command line args should be multicast group and port and [interface] optional\n");
       printf("(e.g. for SSDP, `listener 239.255.255.250 1900 [192.168.1.1]`)\n");
       return 1;
    }

    const char* group = argv[1]; // e.g. 239.255.255.250 for SSDP
    const int port = atoi(argv[2]); // 0 if error, which is an invalid port
    const char* source_iface = (argc == 4) ? argv[3] : NULL;

    //
    // create what looks like an ordinary UDP socket
    //
    const int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("socket");
        return 1;
    }

    //
    // allow multiple sockets to use the same PORT number
    //
    const u_int yes = 1;
    if (
        setsockopt(
            fd, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes)
        ) < 0
    ){
       perror("Reusing ADDR failed");
       return 1;
    }

    //
    // set up destination address
    //
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(group);
    addr.sin_port = htons(port);

    //
    // bind to receive address
    //
    if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) {
        perror("bind");
        return 1;
    }

    //
    // use setsockopt() to request that the kernel join a multicast group
    //
    struct ip_mreq mreq;
    memset(&mreq, 0, sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr(group);
    mreq.imr_interface.s_addr = source_iface ? inet_addr(source_iface) : htonl(INADDR_ANY);
    if (
        setsockopt(
            fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof(mreq)
        ) < 0
    ){
        perror("setsockopt");
        return 1;
    }

    //
    // now just enter a read-print loop
    //
    while (1) {
        char msgbuf[MSGBUFSIZE];
        unsigned addrlen = sizeof(addr);
        int const nbytes = recvfrom(
            fd,
            msgbuf,
            MSGBUFSIZE,
            0,
            (struct sockaddr *) &addr,
            &addrlen
        );
        if (nbytes < 0) {
            perror("recvfrom");
            return 1;
        }
        msgbuf[nbytes] = '\0';
        printf("from: %s message: %s\n", inet_ntoa(addr.sin_addr), msgbuf);
     }

    return 0;
}

tcpdump looks good for all interfaces:

$ sudo tcpdump -i wlp6s0 -s0 -vv host 239.255.255.251
tcpdump: listening on wlp6s0, link-type EN10MB (Ethernet), capture size 262144 bytes
18:03:40.547226 IP (tos 0x0, ttl 1, id 55512, offset 0, flags [DF], proto UDP (17), 
length 92)
    i7-6700k-system.48397 > 239.255.255.251.27335: [udp sum ok] UDP, length 64
18:03:41.547602 IP (tos 0x0, ttl 1, id 55691, offset 0, flags [DF], proto UDP (17), 
length 92)
    i7-6700k-system.48397 > 239.255.255.251.27335: [udp sum ok] UDP, length 64

$ sudo tcpdump -i enp0s31f6 -s0 -vv host 239.255.255.251
tcpdump: listening on enp0s31f6, link-type EN10MB (Ethernet), capture size 262144 bytes
18:07:42.639153 IP (tos 0x0, ttl 1, id 20849, offset 0, flags [DF], proto UDP (17), 
length 92)
    i7-6700k-system.48397 > 239.255.255.251.27335: [udp sum ok] UDP, length 64
18:07:43.639911 IP (tos 0x0, ttl 1, id 20997, offset 0, flags [DF], proto UDP (17), 
length 92)
    i7-6700k-system.48397 > 239.255.255.251.27335: [udp sum ok] UDP, length 64

$ sudo tcpdump -i enp10s0 -s0 -vv host 239.255.255.251
tcpdump: listening on enp10s0, link-type EN10MB (Ethernet), capture size 262144 bytes
18:08:57.666159 IP (tos 0x0, ttl 1, id 30039, offset 0, flags [DF], proto UDP (17), 
length 92)
    i7-6700k-system.48397 > 239.255.255.251.27335: [udp sum ok] UDP, length 64
18:08:58.666518 IP (tos 0x0, ttl 1, id 30171, offset 0, flags [DF], proto UDP (17), 
length 92)
    ci7-6700k-system.48397 > 239.255.255.251.27335: [udp sum ok] UDP, length 64

Solution

  • I revisited this issue in a hope to find an answer and it seems that I did. The answer to this problem is a matter of configuration. This Kernel parameter does the trick:

    net.ipv4.conf.all.accept_local=1
    

    Accept packets with local source addresses. In combination with suitable routing, this can be used to direct packets between two local interfaces over the wire and have them accepted properly. default FALSE

    Similar problem:

    Multicast datagrams filtered when routed back to the origin