我正在使用一个守护程序,该守护程序侦听UDP广播数据包,并通过UDP进行响应。当一个包进来,我想知道哪个IP地址(或NIC)的包来到 TO 这样我就可以与该IP地址作为源地址进行响应。(出于很多麻烦的原因,我们系统的某些用户希望将同一台计算机上的两个NIC连接到同一子网。我们告诉他们不要,但是他们坚持要这样做。我不需要提醒我这是多么的丑陋。 )
似乎没有办法检查数据报并直接找出其目的地址或它进入的接口。基于大量的搜索,我发现找出数据报目标的唯一方法是每个接口有一个侦听套接字,并将套接字绑定到它们各自的接口。
首先,我的监听套接字是通过以下方式创建的:
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
为了绑定套接字,我首先尝试的是接口名称,其中nica char*是接口名称:
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套接字。数据包套接字不支持(在此使用普通的bind(8))。” 我不确定“数据包套接字”是什么意思,但这是一个AF_INET套接字。
eth0
man 7 socket
所以我接下来尝试的是这个(基于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,但失败并出现相同的错误。
Cannotassignrequestedaddress
剩下的一种选择是将套接字绑定到特定的IP地址。我可以查找接口地址并绑定到这些地址。不幸的是,这是一个不好的选择,因为由于DHCP和热插拔的以太网电缆,地址可以随时更改。
当涉及到广播和多播时,此选项可能也很糟糕。我担心绑定到特定地址将意味着我无法接收广播(广播到的地址不是我绑定的地址)。我实际上将在今晚晚些时候对此进行测试并更新此问题。
问题:
AF_PACKET
SOCK_DGRAM
谁能帮我解决这个问题?谢谢!
更新:
绑定到特定的IP地址无法正常工作。具体来说,我无法再接收广播数据包,这正是我要接收的数据包。
我尝试使用IP_PKTINFO和recvmsg获取有关正在接收的数据包的更多信息。我可以获得接收接口,接收接口地址,发送方的目标地址和发送方的地址。这是我收到一个广播数据包的报告的示例:
IP_PKTINFO
recvmsg
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,尽管我认为这仅适用于传出数据包。
我发现可以使用的解决方案如下。首先,我们必须更改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)。
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地址似乎是一种浪费,但是当地址更改时,这是通过更多的代码来获得通知的方式,并且在不使用电池供电的系统上,这些事务每隔几秒钟发生一次。)