在同一台Linux机器的不同接口上发送和接收组播。

7

我有一个包含多台计算机的网络,每台计算机都有多个网络接口,且具有单一目的地。我正在开发一个积极使用组播的应用程序。总体上一切正常。但是,当我通过第二个网络接口从同一台计算机发送组播时,无法在该计算机上接收到组播。网络中的其他计算机可以通过任何网络接口接收组播。

是否可能通过一个接口发送组播并通过另一个接口在同一网络中接收组播?如果可能,在客户端代码、接收方代码或系统设置中哪里出错了?

典型的机器配置:

$ 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

路由表:

$ 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

这些组合按预期工作

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

或者
./sender 239.255.255.251 27335 192.168.88.229
./listener 239.255.255.251 27335 192.168.88.229

或者
./sender 239.255.255.251 27335 192.168.88.230
./listener 239.255.255.251 27335 192.168.88.230

但是这些不起作用:

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

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

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

我在我的任务中,参考了来自以下网站的多个示例代码: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 对所有接口都适用:

$ 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

1
根据 https://dev59.com/mHgPtIcB2Jgan1znW10m,设置 net.ipv4.conf.all.accept_local=1。 - tiluil
2个回答

0

您可以通过让您的监听套接字加入多个接口的组播组来解决此问题。

首先,不要绑定到组播地址,而是绑定到INADDR_ANY

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);

这将防止基于IP的任何过滤。然后,您可以接受逗号分隔的接口IP列表,并在每个接口上进行连接。

char *ptr = strtok(source_iface, ",");
while (ptr) {
    struct ip_mreq mreq;
    memset(&mreq, 0, sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr(group);
    mreq.imr_interface.s_addr = inet_addr(ptr);

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

    ptr=strtok(NULL, ",");
}

我已经应用了这个更改,但是没有任何变化。diff --git a/example/listener.c b/example/listener.c
  • addr.sin_addr.s_addr = inet_addr(group);
  • addr.sin_addr.s_addr = INADDR_ANY; //inet_addr(group);
多播也只有在发送方和接收方接口匹配时才能被听到。据我所记,对于多播配置,s_addr的INADDR_ANY是Windows内核特定的。
- e.proydakov
@e.proydakov 看起来你还需要在多个接口上加入该组。 - dbush
这个解决方案可行。但是看起来数据包是通过发送接口接收的,这与原始任务不符。 - e.proydakov
由于所有接口都在同一个子网上,应该会进行一些ecmp哈希;但根据rt表,没有ecmp!因此,根据mcast rpf规则,所有mcast都将在enp0s31f6上接收,对吗? - Andrew

0
我重新审视了这个问题,希望能找到答案,看起来我找到了。这个问题的答案是一个配置问题。这个内核参数可以解决这个问题:
net.ipv4.conf.all.accept_local=1

接受具有本地源地址的数据包。与适当的路由结合使用,可以将数据包在两个本地接口之间直接传输,并使其被正确接受。默认为FALSE。

类似问题:

当返回到原始位置时,多播数据报被过滤


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接