一尘不染

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

linux

我正在使用一个守护程序,该守护程序侦听UDP广播数据包,并通过UDP进行响应。当一个包进来,我想知道哪个IP地址(或NIC)的包来到 TO
这样我就可以与该IP地址作为源地址进行响应。(出于很多麻烦的原因,我们系统的某些用户希望将同一台计算机上的两个NIC连接到同一子网。我们告诉他们不要,但是他们坚持要这样做。我不需要提醒我这是多么的丑陋。

似乎没有办法检查数据报并直接找出其目的地址或它进入的接口。基于大量的搜索,我发现找出数据报目标的唯一方法是每个接口有一个侦听套接字,并将套接字绑定到它们各自的接口。

首先,我的监听套接字是通过以下方式创建的:

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

为了绑定套接字,我首先尝试的是接口名称,其中nica 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套接字。数据包套接字不支持(在此使用普通的bind(8))。”
我不确定“数据包套接字”是什么意思,但这是一个AF_INET套接字。

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

struct sockaddr_ll sock_address;
memset(&sock_address, 0, sizeof(sock_address));
sock_address.sll_family = PF_PACKET;
sock_address.sll_protocol = htons(ETH_P_ALL);
sock_address.sll_ifindex = if_nametoindex(nic);
rc=bind(s, (struct sockaddr*) &sock_address, sizeof(sock_address));
if (rc < 0) { ... }

那也失败了,但是这次出现了错误Cannotassignrequestedaddress。我也尝试将系列更改为AF_INET,但失败并出现相同的错误。

剩下的一种选择是将套接字绑定到特定的IP地址。我可以查找接口地址并绑定到这些地址。不幸的是,这是一个不好的选择,因为由于DHCP和热插拔的以太网电缆,地址可以随时更改。

当涉及到广播和多播时,此选项可能也很糟糕。我担心绑定到特定地址将意味着我无法接收广播(广播到的地址不是我绑定的地址)。我实际上将在今晚晚些时候对此进行测试并更新此问题。

问题:

  • 是否可以将UDP侦听套接字专门绑定到接口?
  • 或者,是否可以使用一种机制在发生更改时(而不是轮询)通知程序接口地址已更改?
  • 我可以创建另一种侦听套接字(我具有root特权),我可以将其绑定到特定接口,该接口的行为与UDP相同(即,除了原始套接字之外,我基本上必须自己实现UDP)?例如,我可以用AF_PACKETSOCK_DGRAM?我不明白所有的选择。

谁能帮我解决这个问题?谢谢!

更新:

绑定到特定的IP地址无法正常工作。具体来说,我无法再接收广播数据包,这正是我要接收的数据包。

更新:

我尝试使用IP_PKTINFOrecvmsg获取有关正在接收的数据包的更多信息。我可以获得接收接口,接收接口地址,发送方的目标地址和发送方的地址。这是我收到一个广播数据包的报告的示例:

Got message from eth0
Peer address 192.168.115.11
Received from interface eth0
Receiving interface address 10.1.2.47
Desination address 10.1.2.47

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

请注意,我启用了net.ipv4.conf.all.arp_filter,尽管我认为这仅适用于传出数据包。


阅读 343

收藏
2020-06-07

共1个答案

一尘不染

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

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

必须使用arp过滤器,以允许eth0的响应通过WAN路由。必须使用rp筛选器选项,才能将传入的数据包与传入的NIC严格关联(与弱模型相比,该模型将传入的数据包与与子网匹配的任何NIC关联起来)。EJP的评论使我迈出了关键的一步。

之后,SO_BINDTODEVICE开始工作。两个套接字中的每个套接字都绑定到其自己的NIC,因此我可以根据消息来自哪个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)
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;
}

(也许每次都查找NIC地址似乎是一种浪费,但是当地址更改时,这是通过更多的代码来获得通知的方式,并且在不使用电池供电的系统上,这些事务每隔几秒钟发生一次。)

2020-06-07