我正在开发一个守护进程,监听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套接字。对于数据包套接字,它不受支持(在那里使用普通的bind(8))。 "我不确定它所说的“数据包套接字”是什么意思,但这是一个AF_INET套接字。
所以我尝试了下面这个(基于bind vs SO_BINDTODEVICE socket):
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) { ... }
这次出现了错误Cannot assign requested address
。我还尝试将协议族更改为AF_INET,但仍然出现相同的错误。
还有一种选择是将套接字绑定到特定的IP地址。我可以查找接口地址并将其绑定到那些地址。不幸的是,这是一个糟糕的选择,因为由于DHCP和热插拔以太网电缆,地址可能会动态变化。
当涉及广播和多播时,这个选项也可能不好。我担心绑定到特定地址意味着我无法接收广播(发送到与我绑定地址不同的地址)。今晚我实际上会测试这个问题并更新这个问题。
问题:
- 是否可能将UDP监听套接字专门绑定到一个接口?
- 或者,是否有一种机制可以通知我的程序,在发生该更改的那一刻,某个接口的地址已更改(而不是轮询)?
- 是否有另一种类型的监听套接字(我具有root权限)可以创建,可以绑定到特定接口,除了原本的UDP之外,它表现得完全相同(例如,我可以使用
AF_PACKET
和SOCK_DGRAM
吗? 我不理解所有选项)。
有人能帮我解决这个问题吗?谢谢!
更新:
将其绑定到特定的IP地址并不能正常工作。具体来说,我无法接收广播包,这正是我想要接收的。
更新:
我尝试使用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,尽管我认为它只适用于出站数据包。
dnsmasq
已经实现了这个功能(通过bind-interfaces
配置选项进行控制)。 - Ben Voigt