草庐IT

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

coder 2023-06-19 原文

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

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

首先,我的监听套接字是这样创建的:

s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)

为了绑定(bind)socket,我尝试的第一件事是这个,其中nicchar*到接口(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 套接字。

所以我接下来尝试的是这个(基于 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,但失败并出现相同的错误。

剩下的一种选择是将套接字绑定(bind)到特定的 IP 地址。我可以查找接口(interface)地址并绑定(bind)到这些地址。不幸的是,这是一个糟糕的选择,因为由于 DHCP 和热插拔以太网电缆,地址可能会即时更改。

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

问题:
  • 是否可以将 UDP 监听套接字专门绑定(bind)到接口(interface)?
  • 或者,是否有一种机制可以在发生更改时(而不是轮询)通知我的程序接口(interface)地址已更改?
  • 是否有另一种我可以创建的监听套接字(我确实有 root 权限),我可以绑定(bind)到一个特定的接口(interface),它的行为与 UDP 完全相同(即除了原始套接字,我基本上必须自己实现 UDP)?例如,我可以使用 AF_PACKETSOCK_DGRAM ?我不明白所有的选项。

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

    更新:

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

    更新:

    我尝试使用 IP_PKTINFOrecvmsg以获取有关正在接收的数据包的更多信息。我可以得到接收接口(interface),接收接口(interface)地址,发送方的目标地址,发送方的地址。这是我收到一个广播数据包时得到的报告示例:
    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 开始工作。两个套接字中的每一个都绑定(bind)到自己的 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 的地址似乎是一种浪费,但是本地址更改时需要更多的代码来获得通知,并且这些事务在不依靠电池运行的系统上每几秒钟只发生一次。)

    关于c - Linux:将 UDP 监听套接字绑定(bind)到特定接口(interface)(或找出数据报来自的接口(interface))?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25070649/

    有关c - Linux:将 UDP 监听套接字绑定(bind)到特定接口(interface)(或找出数据报来自的接口(interface))?的更多相关文章

    1. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

      它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

    2. 网络编程套接字 - 2

      网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

    3. postman接口测试工具-基础使用教程 - 2

      1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

    4. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

      我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

    5. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

      s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

    6. ruby-on-rails - Ruby 的 'open_uri' 是否在读取或失败后可靠地关闭套接字? - 2

      一段时间以来,我一直在使用open_uri下拉ftp路径作为数据源,但突然发现我几乎连续不断地收到“530抱歉,允许的最大客户端数(95)已经连接。”我不确定我的代码是否有问题,或者是否是其他人在访问服务器,不幸的是,我无法真正确定谁有问题。本质上,我正在读取FTPURI:defself.read_uri(uri)beginuri=open(uri).readuri=="Error"?nil:urirescueOpenURI::HTTPErrornilendend我猜我需要在这里添加一些额外的错误处理代码...我想确保我采取一切预防措施来关闭所有连接,这样我的连接就不是问题所在,但是我

    7. ruby - 可以正常中断的来自 Rake 的长时间运行的 shell 命令? - 2

      在几个项目中,我希望有一个类似rakeserver的rake任务,它将通过任何需要的方式开始为该应用程序提供服务。这是一个示例:task:serverdo%x{bundleexecrackup-p1234}end这行得通,但是当我准备停止它时,按Ctrl+c并没有正常关闭;它中断了Rake任务本身,它说rakeaborted!并给出堆栈跟踪。在某些情况下,我必须执行Ctrl+c两次。我可能可以用Signal.trap写一些东西来更优雅地中断它。有没有更简单的方法? 最佳答案 trap('SIGINT'){puts"Yourmessa

    8. ruby - Faye WebSocket,关闭处理程序被触发后重新连接到套接字 - 2

      我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d

    9. ruby-on-rails - 如何找出拦截 'method_missing' 的内容 - 2

      使用Ruby1.8.6/Rails2.3.2我注意到在我的任何ActiveRecord模型类上调用的任何方法都返回nil而不是NoMethodError。除了烦人之外,这还破坏了动态查找器(find_by_name、find_by_id等),因为即使存在记录,它们也总是返回nil。不从ActiveRecord::Base派生的标准类不受影响。有没有办法追踪在ActiveRecord::Base之前拦截method_missing的是什么?更新:切换到1.8.7后,我发现(感谢@MichaelKohl)will_paginate插件首先处理method_missing。但是will_pa

    10. ruby - ruby 中的同一个程序如何接受来自用户的输入以及命令行参数 - 2

      我的ruby​​脚本从命令行参数获取某些输入。它检查是否缺少任何命令行参数,然后提示用户输入。但是我无法使用gets从用户那里获得输入。示例代码:test.rbname=""ARGV.eachdo|a|ifa.include?('-n')name=aputs"Argument:#{a}"endendifname==""puts"entername:"name=getsputsnameend运行脚本:rubytest.rbraghav-k错误结果:test.rb:6:in`gets':Nosuchfileordirectory-raghav-k(Errno::ENOENT)fromtes

    随机推荐