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