Linux:如何强制使用特定的网络接口?

时间:2009-02-18 23:00:25

标签: linux sockets

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

理想情况下,无论如何,我都希望将流程监禁到仅使用某个界面。它将进行TCP连接,发送UDP数据报和监听UDP广播。目前,我正在做的是:

  1. 确定要使用的接口的IP。
  2. 创建IP策略规则,将来自接口的所有数据包路由到该IP
  3. 创建另一个IP策略规则,将来自该IP的所有数据包路由到该接口
  4. 为每个规则设置默认路由表
  5. 现在,这主要是有效的,但客户流程也必须愿意发挥作用。也就是说,它需要绑定到它想要使用的接口的特定IP,我想我也需要设置SO_BINDTODEVICE。 (但是,我一直在阅读有关SO_BINDTODEVICE在使用TCP或UDP时是否真的有效的相互矛盾的信息。)幸运的是,客户端应用程序是Python,我可以扩展套接字类以透明地完成所有这些操作。但我不确定这是一个完整的解决方案,特别是在接收广播方面。

    我的问题是:

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

    2. 有没有办法做到这一点,本地IP不必是唯一的?除了一个接口上的DHCP服务器可以为其分配另一个接口正在使用的IP这一事实之外,这不会是一个问题,因此会混淆路由表。

    3. 如何仅从特定界面接收广播?绑定到特定IP似乎使它忽略广播,这是有道理的,但不是我正在寻找的。

    4. 我正在使用Linux内核2.6.26的Ubuntu 8.04上运行。能够通过两个不同的接口同时访问两个不同网络上的同一子网是一个不可协商的要求,因此(大部分)免于“不要那样做”。 :)

4 个答案:

答案 0 :(得分:4)

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

  • 涉及路由表的复杂方式改变和协作来自每个进程。这就是我上面描述的方式。它具有的一个优点是它可以在用户空间中运行。我已在其上添加了一些注释,并在下面回答了我的具体问题。

  • 如果设置了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()调用绑定到广播地址。设备绑定需要在其他任何事情之前完成。然后,套接字将仅接收到达该接口的广播。

  4. 我首先创建了一个套接字来测试它。然后我使用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%肯定这一点,如果你想继续下去,我只是提到它作为调查点。所有这些都说:使用风险自负。为了节省时间,我在接口上打开了一个原始套接字,并自己解析了以太网和IP头。

答案 1 :(得分:3)

经过一个艰苦的周末之后,我很高兴能够提出一个解决方案,解决我之前讨论的大部分内容,几乎没有麻烦。

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

    rp_filter - INTEGER
         2 - do source validation by reversed path, as specified in RFC1812
             Recommended option for single homed hosts and stub network
             routers. Could cause troubles for complicated (not loop free)
             networks running a slow unreliable protocol (sort of RIP),
             or using static routes.

         1 - (DEFAULT) Weaker form of RP filtering: drop all the packets
             that look as sourced at a directly connected interface, but
             were input from another interface.

         0 - No source validation.

这也可以使用/ proc / sys / net / ipv4 / conf / <interface> / rp_filter在每个接口的基础上设置。

正如一张海报所解释的那样,它使得IP路由“不太确定”,因为来自一个子网的数据包不能保证总是出同一个接口。在这种情况下,这正是它所需要的。请做进一步的研究,以确定这是否真的是你想要的。

由于我不理解的原因,广播仍然存在问题,但我对这个问题感到满意,希望能帮助其他人。

答案 2 :(得分:2)

不是您问题的直接答案,而只是一个FYI。如上所述,对于您需要/想要做的事情,这个解决方案可能太多了。

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

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

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

BR_LOCAL_IN instead of NF_IP_LOCAL_IN

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

答案 3 :(得分:2)

您可以尝试将进程的网络名称空间限制为单个接口。您需要使用CONFIG_NETNS(现代发行版的大多数内核)和一些脚本为您进行内核构建。 Sample configuration