选择UDP/多播套接字的接口 - C语言

21
我正在尝试修改一个多播监听器/发送器示例,将UDP/多播套接字绑定到特定接口,而不使用 INADDR_ANY 宏。
我拥有接口的IPv4地址。 我尝试了以下操作,但套接字没有接收到任何UDP(单播、广播、多播)数据包。
struct sockaddr_in addr;
int fd, nbytes;
socklen_t  addrlen;
struct ip_mreq mreq;

// my_ipv4Addr equals current IP as String, e.g. "89.89.89.89"

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

// set up addresses
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
// [-]    addr.sin_addr.s_addr = htonl(INADDR_ANY); 
addr.sin_addr.s_addr = inet_addr(my_ipv4Addr); 
addr.sin_port = htons(port);

// bind socket
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
    perror("bind");
    exit(1);
}

// use setsockopt() to request that the kernel join a multicast group
mreq.imr_multiaddr.s_addr = inet_addr(group);
// [-]    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
mreq.imr_interface.s_addr = inet_addr(my_ipv4Addr);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))< 0) {
    perror("setsockopt");
    exit(1);
}

编辑:

让我解释一下我的程序目的。 我正在编写一个小工具,用于检查网络是否支持广播/组播。 因此,我拥有一个带有两个接口的系统,并通过Interface1发送多播数据包,并尝试使用Interface2接收它。 但是:数据包应通过网络传输,而不是loopback设备。

想法是在thread1/interface1上阻止组播回环:

u_char loop = 0;
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));

我需要监听线程2/接口2的特定内容。Tcpdump显示数据包已经到达,但是根据我上述的配置被丢弃了。


3
我认为你需要绑定到一个设备而不是一个IP,例如使用setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, "eth0", 4) - Gene
如果让内核为您选择一个接口会发生什么?即使用 mreq.imr_interface.s_addr = htonl(INADDR_ANY); - shinkou
@Gene 正确。使用 setsockopt( ... SO_BINDTODEVICE ...) 我可以将 UDP 套接字绑定到一个接口,但是如何显式地为多播做到这一点呢?@shinkou 将其设置为 INADDR_ANY(UDP 和多播订阅)并不会改变套接字不接收数据包的事实。数据包被丢弃为 martian(在 /var/log/syslog 中看到),直到我设置了 net.ipv4.{all,default,interfaces}.rp_filter = 0。似乎与 Linux 相关,因为数据包可以通过 tcpdump 在出站和入站接口上看到。现在没有头绪了... - user22602
你的路由表长什么样?当你运行监听器时,netstat -ng 会说些什么? - Nikolai Fetissov
你可能需要执行 setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, ..) 来控制数据包从哪个接口发送... - Chris Dodd
4个回答

14

使用以下代码:

通过 addr.sin_addr.s_addr=inet_addr(my_ipv4Addr);,你只能向多播组发送数据包,但是你无法接收任何来自“my_ipv4Addr”发送的数据包。

所以addr.sin_addr.s_addr必须是htonl(INADDR_ANY)

通过mreq.imr_interface.s_addr=inet_addr(my_ipv4Addr);,你可以接收多播组中的所有数据包,但它会通过默认接口(例如eth0)发送数据包,而不是你指定的接口(例如eth1)。因此这没有作用。

使用setsockopt(sockfd,SOL_SOCKET,SO_BINDTODEVICE,ETH1,strlen(ETH1));,你可以通过接口ETH1发送数据包,但你只能接收与ETH1关联的IP地址发送的数据包,不能接收来自其他客户端的数据包。

通过mreq.imr_interface.s_addr=inet_addr(my_ipv4Addr);setsockopt(sockfd,IPPROTO_IP,IP_MULTICAST_IF,&mreq.imr_interface,sizeof(struct in_addr));,你可以通过与my_ipv4addr相关联的接口发送数据包,并且可以从多播组中的任何客户端接收数据包。


5
当绑定一个用于接收多播流量的套接字时,如果你绑定到本地地址,这会阻止非Windows系统接收到多播数据包。
我第一次发现这个问题是在发布带有绑定到特定地址功能的UFTP 3.6版本时。Windows可以很好地处理它,但在Linux系统上,多播数据包无法被应用程序接收到。我不得不在下一个版本中删除该功能。
请注意,你可以直接绑定到多播地址:
addr.sin_addr.s_addr = inet_addr(group);

在这种情况下,您将只在该套接字上接收到该组播地址的流量。但是,您仍然需要加入特定接口的多播组才能接收。如果您计划在同一套接字上从多个多播地址接收数据,则需要绑定到INADDR_ANY

这个答案是不正确的。我建议查看相关的stackoverflow页面。https://dev59.com/Q2gv5IYBdhLWcg3wTvLL - f3xy
@f3xy没错。我刚在RedHat 5系统上验证了一下。如果你绑定到INADDR_ANY,你将会接收到组播。如果你绑定到特定的接口,你就不会接收到。 - dbush
语句"When binding a socket for receiving multicast traffic, you should always bind to INADDR_ANY."是不正确的。如果你阅读我引用的stackoverflow,它还引用了Richard Stevens的UNIX网络编程,你会发现你所做的陈述是不正确的。虽然可以绑定INADDR_ANY并接收多播,但最好的方法是绑定您希望接收的多播地址和端口。也许你可以编辑你的答案?我只是不想让任何人被误导。 - f3xy
1
@f3xy 你说得对,绑定到多播地址是可以的。只有当你绑定到本地接口时才无法接收多播。我会进行编辑以反映这一点。 - dbush
1
非常感谢您的理解。UFTP看起来相当不错,我得去了解一下。 - f3xy
没错。在 Linux 上绑定 UDP 套接字到一个 IP 地址只有针对指定 IP 地址的过滤功能,而不是绑定功能。 - Johannes Overmann

4
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); 
//addr.sin_addr.s_addr = inet_addr(my_ipv4Addr); 
addr.sin_port = htons(port);

mreq.imr_multiaddr.s_addr = inet_addr(group);
// [-]    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
mreq.imr_interface.s_addr = inet_addr(my_ipv4Addr);

你只需要像我这样编辑你的代码即可。

如果将套接字绑定到INADDR_ANY,它将接收发送到该特定端口的每个数据包。我会将绑定地址保留为my_IPV4Addr。 - user666412

0
要么你为了理解简化了代码,要么我漏掉了什么。
这是结构体。
struct ip_mreqn {
    struct in_addr imr_multiaddr; /* IP multicast group
                                     address */
    struct in_addr imr_address;   /* IP address of local
                                     interface */
    int            imr_ifindex;   /* interface index */
};

ip man page - IP_ADD_MEMBERSHIP

但是你正在引用

mreq.imr_interface.s_addr = inet_addr(my_ipv4Addr);

imr_interface是什么?它能编译通过吗?

如果你只是为了更好的可读性而写了上面的名称,那么你是否尝试过将接口索引即imr_ifindex填充到你想要连接的特定接口中。

我猜想,如果你只留下imrr_address并分配接口索引,它应该会绑定到该接口并接收数据包。看看这是否有帮助。


2
ip_mreqn是新的数据结构,问题使用ip_mreq,Linux支持两种。 - Steve-o

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