C Linux:将UDP侦听套接字绑定到特定接口(或查找数据报来自的接口)?

C Linux:将UDP侦听套接字绑定到特定接口(或查找数据报来自的接口)?,c,linux,sockets,udp,bind,C,Linux,Sockets,Udp,Bind,我有一个正在处理的守护进程,它监听UDP广播数据包并通过UDP进行响应。当一个数据包进入时,我想知道该数据包到达的是哪个IP地址(或NIC),这样我就可以用该IP地址作为源进行响应。(出于非常痛苦的原因,我们系统的一些用户希望将同一台机器上的两个NIC连接到同一个子网。我们告诉他们不要,但他们坚持。我不需要提醒我这有多难看。) 似乎没有办法检查数据报并直接找出它的目标地址或它所处的接口。通过大量的谷歌搜索,我发现找到数据报目标的唯一方法是每个接口有一个监听套接字,并将套接字绑定到各自的接口 首先

我有一个正在处理的守护进程,它监听UDP广播数据包并通过UDP进行响应。当一个数据包进入时,我想知道该数据包到达的是哪个IP地址(或NIC),这样我就可以用该IP地址作为源进行响应。(出于非常痛苦的原因,我们系统的一些用户希望将同一台机器上的两个NIC连接到同一个子网。我们告诉他们不要,但他们坚持。我不需要提醒我这有多难看。)

似乎没有办法检查数据报并直接找出它的目标地址或它所处的接口。通过大量的谷歌搜索,我发现找到数据报目标的唯一方法是每个接口有一个监听套接字,并将套接字绑定到各自的接口

首先,我的侦听套接字是这样创建的:

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
为了绑定套接字,我尝试的第一件事是,其中
nic
是接口名称的
char*

// Bind to a single interface
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
if (rc != 0) { ... }
这一点都没有效果,只是默默地失败了。ASCII名称(例如
eth0
)是否是传递到此调用的正确名称类型?为什么它会默默地失败?根据
man 7 socket
,“注意,这只适用于某些套接字类型,特别是AF_INET套接字。数据包套接字不支持它(使用普通绑定(8)。”我不确定“数据包套接字”是什么意思,但这是一个AF_INET套接字

所以我接下来尝试的是这个(基于):

真正奇怪的是,eth0的地址是10.1.2.9,而ech1的地址是10.1.2.47。那么,为什么eth0要接收应该由eth1接收的数据包呢?这绝对是个问题


请注意,我启用了net.ipv4.conf.all.arp_过滤器,尽管我认为这只适用于外出的数据包。

如果您的平台支持,您可以使用
recvmsg()
通过
IP_RECVDSTADDR
选项获取发送方使用的目标地址。它相当复杂,在Unix网络编程,第一卷,第三版,#22.2和中有描述


如果重新编辑,您将面临所谓的TCP/IP“弱端系统模型”。基本上,一旦数据包到达,系统可以选择通过任何适当的接口发送数据包,监听正确的端口。TCP/IP RFCs中讨论了这个问题。

您正在将非法值传递给
setsockopt

rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
手册页上提到了
SO\u BIND\u TO\u DEVICE

传递的选项是一个长度可变的以null结尾的接口名称字符串,其最大大小为IFNAMSIZ

strlen
不包括终止null。您可以尝试:

rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, 1 + strlen(nic));


我相信你可能从错误的角度看待这个问题。在一般情况下,接口可以同时具有多个IP地址,因此知道连接到哪个接口不会给您提供IP地址(在一般情况下)

相反,不要太担心正在使用的接口,而要关注正在使用的IP地址。首先使用getIFADRS()获取所有IP地址的列表,并将一个套接字绑定到每个地址

select()可用于同时在所有套接字上等待数据包。使用接收数据包的套接字来确定数据包的目标地址。此外,接收数据包的套接字可用于发送应答,该应答将自动适当地设置源地址


您可能偶尔需要检查新的IP地址,但如果DHCP给您一个新地址,您将在套接字上得到一个错误。

我发现有效的解决方案如下。首先,我们必须更改ARP和RP设置。在/etc/sysctl.conf中,添加以下命令并重新启动(还有一个命令用于动态设置):

arp过滤器对于允许来自eth0的响应通过WAN路由是必需的。rp filter选项对于将传入数据包与其传入的NIC严格关联是必需的(与将它们与任何与子网匹配的NIC关联的弱模型相反)。来自EJP的一条评论将我引向了这一关键步骤

之后,SO_BINDTODEVICE开始工作。两个套接字中的每一个都绑定到自己的NIC,因此我可以根据消息来自的套接字来判断消息来自哪个NIC

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, IF_NAMESIZE);
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(LISTEN_PORT);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
rc=bind(s, (struct sockaddr *)&si_me, sizeof(si_me))
接下来,我想用源地址是原始请求来自的NIC的数据报来响应即将到来的数据报。答案是只需查找该NIC的地址并将输出套接字绑定到该地址(使用
bind

s=socket(AF_INET、SOCK_DGRAM、IPPROTO_UDP)
获取nic地址(nic,(结构sockaddr*)和sa)
sa.sinu端口=0;
rc=bind(s,(struct sockaddr*)和sa,sizeof(struct sockaddr));
发送至(s,…);
int get_nic_addr(const char*nic,struct sockaddr*sa)
{
结构ifreq-ifr;
int-fd,r;
fd=插座(AF INET,插座DGRAM,0);
如果(fd<0)返回-1;
ifr.ifr\u addr.sa\u family=AF\u INET;
strncpy(ifr.ifr_名称、nic、IFNAMSIZ);
r=ioctl(fd、SIOCGIFADDR和ifr);
如果(r<0){…}
关闭(fd);
*sa=*(结构sockaddr*)和ifr.ifr\u addr;
返回0;
}

(也许每次查找NIC地址都是一种浪费,但当地址发生变化时,需要更多的代码才能得到通知,而且在不使用电池的系统上,这些事务每隔几秒钟才发生一次。)

我知道这是一个旧线程,但我没有找到我在这里寻找的答案

通过使用我在此处找到的信息,将原始套接字绑定到接口,使套接字不会看到来自另一个接口(包括广播、IGMP等)的任何数据包:

bindrawsocketthinterface()函数的功能正是我所需要的

希望这对其他人有帮助。 干杯

rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, 1 + strlen(nic));
setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE)
net.ipv4.conf.default.arp_filter = 1
net.ipv4.conf.default.rp_filter = 2
net.ipv4.conf.all.arp_filter = 1
net.ipv4.conf.all.rp_filter = 2
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, IF_NAMESIZE);
memset((char *) &si_me, 0, sizeof(si_me));
si_me.sin_family = AF_INET;
si_me.sin_port = htons(LISTEN_PORT);
si_me.sin_addr.s_addr = htonl(INADDR_ANY);
rc=bind(s, (struct sockaddr *)&si_me, sizeof(si_me))
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
get_nic_addr(nics, (struct sockaddr *)&sa)
sa.sin_port = 0;
rc = bind(s, (struct sockaddr *)&sa, sizeof(struct sockaddr));
sendto(s, ...);

int get_nic_addr(const char *nic, struct sockaddr *sa)
{
    struct ifreq ifr;
    int fd, r;
    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) return -1;
    ifr.ifr_addr.sa_family = AF_INET;
    strncpy(ifr.ifr_name, nic, IFNAMSIZ);
    r = ioctl(fd, SIOCGIFADDR, &ifr);
    if (r < 0) { ... }
    close(fd);
    *sa = *(struct sockaddr *)&ifr.ifr_addr;
    return 0;
}