Linux:将 UDP 监听套接字绑定到特定接口(或找出数据报来自的接口)? [英] Linux: Bind UDP listening socket to specific interface (or find out the interface a datagram came in from)?

查看:20
本文介绍了Linux:将 UDP 监听套接字绑定到特定接口(或找出数据报来自的接口)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一个守护进程,它侦听 UDP 广播数据包并通过 UDP 进行响应.当数据包进来时,我想知道数据包来自哪个 IP 地址(或 NIC)TO,以便我可以将该 IP 地址作为源进行响应.(由于涉及很多痛苦的原因,我们系统的一些用户想要将同一台机器上的两个 NIC 连接到同一个子网.我们告诉他们不要,但他们坚持.我不需要提醒这有多丑陋.)

I have a daemon I'm working on that listens for UDP broadcast packets and responds also by 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. (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.)

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

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.

首先,我的监听socket是这样创建的:

First of all, my listening socket is created this way:

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

为了绑定套接字,我尝试的第一件事是这样,其中 nic 是一个 char* 到一个接口的名称:

To bind the socket, the first thing I tried was this, where nic is a char* to the name of an interface:

// 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 套接字.

This has no effect at all and fails silently. Is the ASCII name (e.g. eth0) the correct type of name to pass to this call? 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)." I'm not sure what it means by 'packet sockets', but this is an AF_INET socket.

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

So the next thing I tried was this (based on 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) { ... }

这也失败了,但这次出现错误无法分配请求的地址.我也尝试将族更改为 AF_INET,但失败并出现同样的错误.

That fails too, but this time with the error Cannot assign requested address. I also tried changing the family to AF_INET, but it fails with the same error.

还有一个选项,即将套接字绑定到特定的 IP 地址.我可以查找接口地址并绑定到这些地址.不幸的是,这是一个糟糕的选择,因为由于 DHCP 和热插拔以太网电缆,地址可以随时更改.

One option remains, which is to bind the sockets to specific IP addresses. 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.

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

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.

问题:

  • 是否可以将 UDP 侦听套接字专门绑定到接口?
  • 或者,我是否可以采用一种机制来通知我的程序接口的地址已更改,在发生更改的那一刻(而不是轮询)?
  • 是否有另一种我可以创建的监听套接字(我确实有 root 权限),我可以绑定到一个特定的接口,它的行为与 UDP 相同(即除了原始套接字,我基本上必须实现 UDP我)?例如,我可以将 AF_PACKETSOCK_DGRAM 一起使用吗?我不明白所有选项.
  • Is it possible to bind a UDP listening socket specifically to an interface?
  • 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 (i.e other than raw sockets, where I would basically have to implement UDP myself)? For instance, can I use AF_PACKET with SOCK_DGRAM? I don't understand all the options.

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

Can anyone help me solve this problem? Thanks!

更新:

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

Binding to specific IP addresses does not work properly. Specifically, I cannot then receive broadcast packets, which is specifically what I am trying to receive.

更新:

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

I tried using IP_PKTINFO and recvmsg to get more information on packets being received. 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

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

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. So why in the world is eth0 receiving packets that should be received by eth1? This is definitely a problem.

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

Note that I enabled net.ipv4.conf.all.arp_filter, although I think that applies only to out-going packets.

推荐答案

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

The solution that I found to work is as follows. First of all, we have to change ARP and RP settings. To /etc/sysctl.conf, add the following and reboot (there's also a command to set this dynamically):

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 的评论让我迈出了关键的一步.

The arp filter was necessary to allow responses from eth0 to route over a 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). A comment from EJP led me to this critical step.

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

After that, SO_BINDTODEVICE started working. 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.

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).

Next, I wanted to respond to in-coming datagrams with datagrams whose source address is that of the NIC the original request came from. The answer there is to just look up that NIC's address and bind the out-going socket to that address (using 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 的地址似乎是一种浪费,但是当地址更改时需要更多的代码来获得通知,并且这些事务在不依靠电池运行的系统上每隔几秒钟才会发生一次.)

(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.)

这篇关于Linux:将 UDP 监听套接字绑定到特定接口(或找出数据报来自的接口)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆