【问题标题】:Linux: How do I force a specific network interface to be used?Linux:如何强制使用特定的网络接口?
【发布时间】:2010-10-08 11:21:46
【问题描述】:

这可以被认为是this earlier SO question的延续。

理想情况下,无论如何,我都希望让进程只使用某个接口。它将建立 TCP 连接、发送 UDP 数据报并侦听 UDP 广播。目前,我正在做的是:

  1. 确定要使用的接口的 IP。
  2. 创建一个 IP 策略规则,将来自接口的所有数据包路由到该 IP
  3. 创建另一个 IP 策略规则以将来自该 IP 的所有数据包路由到该接口
  4. 为每个规则设置默认路由表

现在,这主要是可行的,但客户端进程也必须愿意配合。也就是需要绑定到它要使用的接口的具体IP,我想我也需要设置SO_BINDTODEVICE。 (然而,我一直在阅读关于SO_BINDTODEVICE 在使用 TCP 或 UDP 时是否真的有效的相互矛盾的信息。)幸运的是,客户端应用程序是 Python,我可以扩展套接字类以透明地完成所有这些工作。但我不确定它是否是一个完整的解决方案,尤其是在接收广播方面。

我的问题是:

  1. SO_BINDTODEVICE 在这里做我想做的事吗?还是仅对原始套接字有效?有人评论说,“套接字上的SO_BINDTODEVICE 并不能保证套接字只会接收到达该物理接口的电线/天线上的数据包。”如果这确实是真的,那么做什么 SO_BINDTODEVICE 做什么?

  2. 有没有办法让本地 IP 不必是唯一的?除了一个接口上的 DHCP 服务器可能会为其分配一个由另一个接口使用的 IP,从而混淆路由表之外,这不会是一个问题。

  3. 如何仅从特定接口接收广播?绑定到特定 IP 似乎使其忽略广播,这是有道理的,但并不是我想要的。

我在带有 Linux 内核 2.6.26 的 Ubuntu 8.04 上运行。能够同时通过两个不同的接口访问两个不同网络上的同一个子网是一个不可协商的要求,因此它(大部分)不受“不要那样做”的影响。 :)

【问题讨论】:

    标签: linux sockets


    【解决方案1】:

    您可以尝试将进程的网络命名空间限制为一个接口。您需要一个带有 CONFIG_NETNS(现代发行版的大多数内核)的内核构建和一些脚本来为您完成分配。 Sample configuration

    【讨论】:

      【解决方案2】:

      经过一个艰苦的周末,我很高兴提出一个解决方案,解决了我之前讨论的大部分问题,几乎零麻烦。

      有一个名为 net.ipv4.conf.all.rp_filter 的 sysctl 可以设置为 0 来禁用源验证:

      rp_filter - 整数 2 - 通过反向路径进行源验证,如 RFC1812 中所述 单宿主主机和存根网络的推荐选项 路由器。可能会导致复杂的麻烦(不是无循环) 运行缓慢不可靠协议(类似于 RIP)的网络, 或使用静态路由。 1 - (默认)较弱的 RP 过滤形式:丢弃所有数据包 看起来来自直接连接的接口,但是 是从另一个界面输入的。 0 - 无源验证。

      这也可以使用 /proc/sys/net/ipv4/conf/<interface>/rp_filter 为每个接口设置。

      正如一位发帖人所解释的那样,它使 IP 路由“不确定性降低”,因为不能保证来自一个子网的数据包总是从同一个接口发出。在这种情况下,这正是它所需要的。请进行更多研究以确定这是否真的是您想要的。

      由于我不明白的原因,广播仍然存在问题,但我终于对这个问题感到满意,希望它可以帮助其他人。

      【讨论】:

      • net.ipv4.conf.all.rp_filter sysctl 变量没有 IPv6 等效项;然而,相应的功能以用于 netfilter 的rpfilter 模块的形式存在。它记录在iptables-extensions 手册页中。 Moving rp_filter into netfilter 指出,rp_filter 可能会在未来的内核中完全从路由缓存代码中删除。
      【解决方案3】:

      至于我的一般问题,似乎有几种方法可以做到:

      • 涉及路由表更改和每个进程的协作的复杂方式。这就是我上面描述的方式。它的一个优点是它可以在用户空间工作。我在上面写了一些额外的注释,并在下面回答了我的具体问题。

      • 如果设置了SO_BINDTODEVICE,则编写一个完全忽略路由表的自定义内核模型。然而,客户端进程仍然需要调用setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev)。这个选项绝对不适合胆小的人。

      • 虚拟化进程。这可能不适合很多人,并且会带来一系列令人头疼的问题,主要是配置问题。但值得一提。

      选项 1 和 2 需要进程选择加入才能按照我们的意愿工作。这可以通过创建一个动态库来部分缓解,该库劫持 socket() 调用以创建套接字,然后在返回描述符之前立即将其绑定到设备。这在here有更详细的描述。

      在做了一些研究和大量谷歌搜索之后,我可以得出一些关于 Linux 内核 2.6.26 的行为的结论。请注意,这些可能都是特定于实现的行为,甚至可能是特定于内核的行为。在决定基于我的单点数据实现功能之前测试您自己的平台。

      1. SO_BINDTODEVICE 确实做到了它所说的,至少对于 UDP。

      2. 每个接口的唯一 IP 似乎是必要的,因为我们正在使用路由表。自定义内核模块可以绕过这个限制。

      3. 要在特定接口上接收广播,首先使用SO_BINDTODEVICE 绑定到设备,然后使用通常的bind() 调用绑定到广播地址。设备绑定需要在其他任何事情之前完成。然后,套接字将只接收到达该接口的广播。

      我首先创建了一个套接字来测试它。然后我使用setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev) 将其绑定到特定接口。最后,我将它绑定到广播地址。我从另一台计算机发送了一个广播,该广播将通过非绑定接口接收。设备绑定套接字没有收到此广播,这是有道理的。去掉setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev)调用,就会收到广播。

      还应该提到,您也可以在这里使用setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)。注意SO_REUSEADDR 的语义随着广播地址的变化而变化。具体来说,如果它们都设置了SO_REUSEADDR,则在同一台机器上将两个套接字绑定到广播地址和相同端口是合法的。

      更新:SO_BINDTODEVICE 带有广播似乎充满危险,特别是接收广播帧。我正在观察在一个接口上接收到的广播帧,但同时在另一个接口上消失了。它们似乎受本地路由表的影响,但不受 IP 策略规则的影响。但是,我对此不是 100% 确定的,如果您希望继续,我只是将其作为调查点提及。这一切都在说:使用风险自负。由于时间关系,我在接口上打开了一个raw socket,自己解析了Ethernet和IP header。

      【讨论】:

        【解决方案4】:

        不是直接回答您的问题,仅供参考。正如你上面提到的,这个解决方案对于你需要/想要做的事情来说可能太多了。

        我个人喜欢创建一个允许我这样做的网络堆栈挂钩内核模块的想法。这样我就可以完全控制来自用户空间的多播和单播帧。您必须使用 netlink sockets 之类的东西来向驱动程序和用户空间应用程序发送/接收数据,但它工作得很好而且速度非常快。

        您还可以通过这种方式连接到堆栈的任何级别...以太网或 IP。因此可以完全控制您发送/接收的内容。

        这是一个示例article,它讨论了挂钩到 netfilter 堆栈。
        注意:这篇文章挂钩到 IP 堆栈,而且它也很旧。我知道 API 发生了变化,但是这篇文章的很多内容在实践和理论上仍然适用。 如果你想挂钩到桥接层,你会使用类似的机制,但指定

        BR_LOCAL_IN instead of NF_IP_LOCAL_IN
        

        注意:这与在接口上打开原始套接字非常相似。您必须自己构建框架。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-05-28
          • 2011-04-17
          • 1970-01-01
          • 2017-03-25
          • 2020-05-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多