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

标签 c linux sockets udp bind

我有一个正在处理的守护进程,它监听 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/

    相关文章:

    java - 按位非运算符

    c - 使用 if 语句中定义的变量

    java - 我可以知道调用 JNI C 方法的类的名称吗?

    android - 为 Objective-c 和 java 函数创建一个 dll

    php - Linux : Setting which php bin to use in CLI

    c++ - boost::lockfree::spsc_queue 分配器最大大小?

    linux - 用户地址内存是如何组织的?

    sockets - 使用 os.OpenFile() 而不是 net.Listen()

    java - 如何使用 Java 中的套接字处理来自 Teltonika GPS 设备的数据

    python - ssl.SSLError : [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed, Python 套接字、ssl、gmail smtp 连接