简体   繁体   English

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

[英]Linux: Bind UDP listening socket to specific interface (or find out the interface a datagram came in from)?

I have a daemon I'm working on that listens for UDP broadcast packets and responds also by UDP.我有一个正在处理的守护进程,它侦听 UDP 广播数据包并通过 UDP 进行响应。 When a packet comes in, I'd like to know which IP address (or NIC) the packet came TO so that I can respond with that IP address as the source.当一个包进来,我想知道哪个IP地址(或NIC)的数据包,这样我可以用IP地址作为源地址进行响应。 (For reasons involving a lot of pain, some users of our system want to connect two NICs on the same machine to the same subnet. We tell them not to, but they insist. I don't need to be reminded how ugly that is.) (出于很多痛苦的原因,我们系统的一些用户希望将同一台机器上的两个 NIC 连接到同一个子网。我们告诉他们不要这样做,但他们坚持。我不需要提醒我这是多么丑陋.)

There seems to be no way to examine a datagram and find out directly either its destination address or the interface it came in on.似乎没有办法检查数据报并直接找出它的目标地址或它进入的接口。 Based on a lot of googling, I find that the only way to find out the target of a datagram is to have one listening socket per interface and bind the sockets to their respective interfaces.基于大量的谷歌搜索,我发现找出数据报目标的唯一方法是每个接口有一个侦听套接字并将套接字绑定到它们各自的接口。

First of all, my listening socket is created this way:首先,我的监听套接字是这样创建的:

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

To bind the socket, the first thing I tried was this, where nic is a char* to the name of an interface:为了绑定套接字,我尝试的第一件事是这样,其中nic是接口名称的char*

// Bind to a single interface
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
if (rc != 0) { ... }

This has no effect at all and fails silently.这已经完全没有效果,静静地失败。 Is the ASCII name (eg eth0 ) the correct type of name to pass to this call? ASCII 名称(例如eth0 )是传递给此调用的正确名称类型吗? Why would it fail silently?为什么它会默默地失败? According to man 7 socket , "Note that this only works for some socket types, particularly AF_INET sockets. It is not supported for packet sockets (use normal bind(8) there)."根据man 7 socket ,“请注意,这仅适用于某些套接字类型,尤其是 AF_INET 套接字。数据包套接字不支持它(在那里使用普通的 bind(8))。” I'm not sure what it means by 'packet sockets', but this is an AF_INET socket.我不确定“数据包套接字”是什么意思,但这是一个 AF_INET 套接字。

So the next thing I tried was this (based on bind vs SO_BINDTODEVICE socket ):所以我接下来尝试的是这个(基于bind 与 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) { ... }

That fails too, but this time with the error Cannot assign requested address .这也失败了,但这次出现错误Cannot assign requested address I also tried changing the family to AF_INET, but it fails with the same error.我也尝试将系列更改为 AF_INET,但失败并出现相同的错误。

One option remains, which is to bind the sockets to specific IP addresses.剩下的一种选择是将套接字绑定到特定的 IP 地址。 I can look up interface addresses and bind to those.我可以查找接口地址并绑定到这些地址。 Unfortunately, is a bad option, because due to DHCP and hot-plugging ethernet cables, the addresses can change on the fly.不幸的是,这是一个糟糕的选择,因为由于 DHCP 和热插拔以太网电缆,地址可能会即时更改。

This option may also be bad when it comes to broadcasts and multicasts.当涉及到广播和多播时,这个选项也可能很糟糕。 I'm concerned that binding to a specific address will mean that I cannot receive broadcasts (which are to an address other than what I bound to).我担心绑定到特定地址将意味着我无法接收广播(广播地址不是我绑定的地址)。 I'm actually going to test this later this evening and update this question.我实际上将在今晚晚些时候对此进行测试并更新此问题。

Questions:问题:

  • Is it possible to bind a UDP listening socket specifically to an interface?是否可以将 UDP 侦听套接字专门绑定到接口?
  • Or alternatively, is there a mechanism I can employ that will inform my program that an interface's address has changed, at the moment that change occurs (as opposed to polling)?或者,是否有一种机制可以在发生更改时(而不是轮询)通知我的程序接口地址已更改?
  • Is there another kind of listening socket I can create (I do have root privileges) that I can bind to a specific interface, which behaves otherwise identically to UDP (ie other than raw sockets, where I would basically have to implement UDP myself)?是否有另一种我可以创建的侦听套接字(我确实有 root 权限),我可以绑定到特定的接口,该接口的行为与 UDP 完全相同(即除了原始套接字,我基本上必须自己实现 UDP)? For instance, can I use AF_PACKET with SOCK_DGRAM ?例如,我可以将AF_PACKETSOCK_DGRAM AF_PACKET使用吗? I don't understand all the options.我不明白所有的选项。

Can anyone help me solve this problem?谁能帮我解决这个问题? Thanks!谢谢!

UPDATE:更新:

Binding to specific IP addresses does not work properly.绑定到特定 IP 地址无法正常工作。 Specifically, I cannot then receive broadcast packets, which is specifically what I am trying to receive.具体来说,我无法接收广播数据包,这正是我想要接收的。

UPDATE:更新:

I tried using IP_PKTINFO and recvmsg to get more information on packets being received.我尝试使用IP_PKTINFOrecvmsg来获取有关正在接收的数据包的更多信息。 I can get the receiving interface, the receiving interface address, the target address of the sender, and the address of the sender.我可以得到接收接口,接收接口地址,发送方的目标地址,发送方的地址。 Here's an example of a report I get on receipt of one broadcast packet:这是我收到一个广播数据包时得到的报告示例:

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

What's really odd about this is that the address of eth0 is 10.1.2.9, and the address of ech1 is 10.1.2.47.真正奇怪的是,eth0 的地址是 10.1.2.9,而 ech1 的地址是 10.1.2.47。 So why in the world is eth0 receiving packets that should be received by eth1?那么究竟为什么 eth0 会接收应该由 eth1 接收的数据包? This is definitely a problem.这绝对是个问题。

Note that I enabled net.ipv4.conf.all.arp_filter, although I think that applies only to out-going packets.请注意,我启用了 net.ipv4.conf.all.arp_filter,尽管我认为这仅适用于出站数据包。

The solution that I found to work is as follows.我发现有效的解决方案如下。 First of all, we have to change ARP and RP settings.首先,我们要更改ARP和RP设置。 To /etc/sysctl.conf, add the following and reboot (there's also a command to set this dynamically):在 /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

The arp filter was necessary to allow responses from eth0 to route over a WAN. arp 过滤器对于允许来自 eth0 的响应通过 WAN 进行路由是必要的。 The rp filter option was necessary to strictly associate in-coming packets with the NIC they came in on (as opposed to the weak model that associates them with any NIC that matches the subnet). rp 过滤器选项对于将传入数据包与它们进入的 NIC 严格关联是必要的(而不是将它们与任何匹配子网的 NIC 关联的弱模型)。 A comment from EJP led me to this critical step. EJP 的评论使我迈出了这关键的一步。

After that, SO_BINDTODEVICE started working.之后,SO_BINDTODEVICE 开始工作。 Each of two sockets was bound to its own NIC, and I could therefore tell which NIC a message came from based on the socket it came from.两个套接字中的每一个都绑定到自己的 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))

Next, I wanted to respond to in-coming datagrams with datagrams whose source address is that of the NIC the original request came from.接下来,我想用源地址是原始请求来自的 NIC 的数据报来响应传入的数据报。 The answer there is to just look up that NIC's address and bind the out-going socket to that address (using bind ).答案是查找该 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;
}

(Maybe looking up the NIC's address every time seems like a waste, but it's way more code to get informed when an address changes, and these transactions occur only once every few seconds on a system that doesn't run on battery.) (也许每次查找 NIC 的地址似乎是一种浪费,但是当地址更改时需要更多的代码来获得通知,并且在不依靠电池运行的系统上,这些事务每隔几秒钟才会发生一次。)

You can get the destination address used by the sender via the IP_RECVDSTADDR option if your platform supports it, by using recvmsg() .如果您的平台支持,您可以使用recvmsg()通过IP_RECVDSTADDR选项获取发件人使用的目标地址。 It's rather complicated, described in Unix Network Programming, volume I, 3rd edition, #22.2, and in the man page .它相当复杂,在Unix Network Programming, volume I, 3rd edition, #22.2 和手册页中有所描述

Re your edit, you are up against what is known as the 'weak end system model' of TCP/IP.重新编辑,您遇到了所谓的 TCP/IP 的“弱端系统模型”。 Basically once a packet arrives the system can choose to deliver it via any appropriate interface listening to the correct port.基本上,一旦数据包到达,系统就可以选择通过任何适当的接口侦听正确的端口来传送它。 It's discussed in the TCP/IP RFCs somewhere.它在 TCP/IP RFC 中的某处进行了讨论。

You're passing an illegal value to setsockopt .您将非法值传递给setsockopt

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

The man page says of SO_BIND_TO_DEVICE :手册页说SO_BIND_TO_DEVICE

The passed option is a variable-length null-terminated interface name string with the maximum size of IFNAMSIZ传递的选项是一个可变长度的空终止接口名称字符串,最大大小为 IFNAMIZ

strlen doesn't include the terminating null. strlen不包括终止空值。 You can try:你可以试试:

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

dnsmasq has this working correctly, and uses dnsmasq可以正常工作,并使用

setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE)

I believe you may be approaching the problem from the wrong angle.我相信您可能从错误的角度处理问题。 In the general case, interfaces can have multiple IP address at the same time so knowing which interface you are attached to is not going to give you the IP address (in the general case)在一般情况下,接口可以同时拥有多个 IP 地址,因此知道您连接到哪个接口不会为您提供 IP 地址(在一般情况下)

Instead, don't worry too much about the interfaces being used and focus on the IP addresses being used.相反,不要过分担心正在使用的接口,而应关注正在使用的 IP 地址。 First get the list all your IP addresses using getifaddrs() and bind one socket to each address.首先使用 getifaddrs() 获取所有 IP 地址的列表,并将一个套接字绑定到每个地址。

select() can be used to wait for packets on all your sockets at once. select() 可用于一次等待所有套接字上的数据包。 Use the socket that received the packet to determine the destination address of the packet.使用接收数据包的套接字来确定数据包的目的地址。 Also, the socket that received the packet can be used to send a reply which will automatically set the source address appropriately.此外,接收数据包的套接字可用于发送回复,该回复将自动适当地设置源地址。

You may occasionally need to check for new IP addresses, but you will get an error on a socket if DHCP gives you a new address.您可能偶尔需要检查新 IP 地址,但如果 DHCP 为您提供新地址,则会在套接字上出现错误。

I know this is an old thread but I did not find the answer I was looking for here.我知道这是一个旧线程,但我没有找到我在这里寻找的答案。

Binding a raw socket to an interface such that the socket does not see any packets from another interface (including broadcast, IGMP, etc) worked for me by using the info I found here:通过使用我在此处找到的信息,将原始套接字绑定到接口,以便套接字看不到来自另一个接口(包括广播、IGMP 等)的任何数据包对我有用:

https://cs.wikipedia.org/wiki/Raw_socket https://cs.wikipedia.org/wiki/Raw_socket

The functionality of the BindRawSocketToInterface() function was what I needed. BindRawSocketToInterface() 函数的功能正是我所需要的。

Hope this helps someone else.希望这对其他人有帮助。 Cheers!干杯!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM