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

这可以被认为是这个早先的SO问题的延续。

理想情况下,我想将一个进程仅仅用于一个特定的界面,无论如何。 它将进行TCP连接,发送UDP数据报,并侦听UDP广播。 目前,我正在做的是:

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

现在,这个工作,大多数,但客户端的过程也必须愿意玩。 也就是说,它需要绑定到它想要使用的接口的特定IP,我想我也需要设置SO_BINDTODEVICE 。 (但是,我一直在阅读有关使用TCP或UDP时SO_BINDTODEVICE实际上是否工作的冲突信息。)幸运的是,客户端应用程序是Python,我可以扩展套接字类来完成所有这些。 但我不确定这是一个完整的解决scheme,特别是在接收广播方面。

我的问题是:

  1. SO_BINDTODEVICE做我想要的吗? 还是只对原始sockets有效? 有人评论道:“套接字上的SO_BINDTODEVICE并不能保证套接字只接收那些到达该物理接口的线/天线的数据包。 如果确实如此,那么SO_BINDTODEVICE做什么的?

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

  3. 如何只接收来自特定界面的广播? 绑定到一个特定的IP似乎使它忽略广播,这是有道理的,但不是我所期待的。

我在Ubuntu 8.04 w / Linux内核2.6.26上运行。 能够通过两个不同的接口同时在两个不同的networking上访问同一个子网是一个不可协商的要求,因此使它(大部分)免于“不这样做”。 🙂

Solutions Collecting From Web of "Linux:如何强制使用特定的networking接口?"

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

  • 涉及到路由表的复杂的方式从每个进程变化和合作。 这是我上面描述的方式。 它有它的一个好处,它从用户空间工作。 我已经提出了一些额外的笔记,并回答了我下面的具体问题。

  • 编写一个自定义的内核模型,如果SO_BINDTODEVICE被设置,它会完全忽略路由表。 但是客户端进程仍然需要调用setsockopt(SOL_SOCKET, SO_BINDTODEVICE, dev) 。 这个选项绝对不是对于心灵的模糊。

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

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

在做了一些研究和大量的谷歌搜索之后,我可以得出一些关于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%确定的,如果你想继续这一点,我只是把它作为一个调查点。 所有这一切说:使用风险自负。 为了时间的关系,我在接口上打开了一个原始套接字,并自己解析了以太网和IP头。

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

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

     rp_filter  -  INTEGER
          2  - 按照RFC1812的规定,通过反向路径进行源验证
             推荐选择单宿主机和存根网络
             路由器。 可能会造成麻烦复杂(不循环)
             运行缓慢不可靠协议的网络(RIP的种类),
             或者使用静态路由。

          1  - (默认)较差的RP过滤形式:丢弃所有数据包
             这看起来像直接连接的接口,但是
             从另一个界面输入。

          0  - 没有源验证。

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

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

广播仍然有问题,原因我不明白,但我终于满意这个问题,我希望它可以帮助别人。

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

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

你也可以通过这种方式来连接到堆栈的任何一层……以太网或IP。 因此完全控制你发送/接收的内容。

这里有一个关于如何连接到netfilter栈的例子。
注意:这篇文章钩入了IP协议栈,也是旧的。 我知道API已经改变了,但是很多这篇文章仍然适用于实践和理论。 如果你想钩入桥接层,你可以使用类似的机制,但是指定

 BR_LOCAL_IN instead of NF_IP_LOCAL_IN 

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

你可以尝试限制进程的网络命名空间到一个单一的接口。 你需要用CONFIG_NETNS(大多数现代发行版的内核)和一些脚本来为你完成任务。 示例配置