Tim*_*ler 6 c sockets linux udp bind
我有一个守护进程,我正在努力监听UDP广播数据包,并通过UDP进行响应.当一个包进来,我想知道哪个IP地址(或NIC)的包来到TO这样我就可以与该IP地址作为源地址进行响应.(由于涉及很多痛苦的原因,我们系统的一些用户想要将同一台机器上的两个NIC连接到同一个子网.我们告诉他们不要,但是他们坚持.我不需要提醒我有多难看.)
似乎无法检查数据报并直接找出其目标地址或其所在的接口.基于大量的谷歌搜索,我发现找出数据报目标的唯一方法是每个接口有一个监听套接字,并将套接字绑定到各自的接口.
首先,我的监听套接字以这种方式创建:
s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
Run Code Online (Sandbox Code Playgroud)
要绑定套接字,我尝试的第一件事就是这个,其中nic是char*接口名称:
// Bind to a single interface
rc=setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, nic, strlen(nic));
if (rc != 0) { ... }
Run Code Online (Sandbox Code Playgroud)
这根本没有效果,并且无声地失败.ASCII名称(例如eth0)是否传递给此调用的正确名称类型?为什么会无声地失败?根据man 7 socket,"请注意,这仅适用于某些套接字类型,尤其是AF_INET套接字.数据包套接字不支持(在那里使用普通绑定(8))." 我不确定"数据包套接字"是什么意思,但这是一个AF_INET套接字.
所以接下来我尝试的是这个(基于bind vs SO_BINDTODEVICE套接字):
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) { ... }
Run Code Online (Sandbox Code Playgroud)
这也失败了,但这次是错误的Cannot assign requested address.我也尝试将系列更改为AF_INET,但它失败并出现相同的错误.
仍有一个选项,即将套接字绑定到特定的IP地址.我可以查找接口地址并绑定到那些地址.不幸的是,这是一个糟糕的选择,因为由于DHCP和热插拔以太网电缆,地址可以随时更改.
广播和多播时,此选项也可能不好.我担心绑定到特定地址将意味着我无法接收广播(这些广播不是我所绑定的地址).我实际上将在今晚晚些时候测试这个并更新这个问题.
问题:
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
Run Code Online (Sandbox Code Playgroud)
真正奇怪的是,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
Run Code Online (Sandbox Code Playgroud)
必须使用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))
Run Code Online (Sandbox Code Playgroud)
接下来,我想用源地址是原始请求来自的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;
}
Run Code Online (Sandbox Code Playgroud)
(也许每次都查找NIC的地址似乎是一种浪费,但是当地址更改时,这是通过更多的代码来获得通知的方式,并且在不使用电池供电的系统上,这些事务每隔几秒钟发生一次。)
| 归档时间: |
|
| 查看次数: |
20557 次 |
| 最近记录: |