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

23

这可以被视为对这个早期的SO问题的延续。

理想情况下,我希望能够将一个进程监禁在仅使用某个接口上,无论如何。它将建立TCP连接,发送UDP数据报和监听UDP广播。目前,我的做法是:

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

现在,这基本上是可行的,但客户端进程也必须愿意配合。也就是说,它需要绑定到它想要使用的接口的特定IP,并且我认为我还需要设置SO_BINDTODEVICE。(但是,我一直在阅读关于是否在使用TCP或UDP时SO_BINDTODEVICE实际上有效的互相矛盾的信息。)幸运的是,客户端应用程序是Python,我可以扩展Socket类以透明地完成所有这些操作。但我不确定它是否是一个完整的解决方案,特别是关于接收广播方面。

我的问题是:

  1. SO_BINDTODEVICE在这里是否起到我想要的作用?还是它只对原始套接字有效?有人评论说:“套接字上的SO_BINDTODEVICE不能保证套接字只接收到在该物理接口的电线/天线上传输的数据包。”如果这确实是真的,那么SO_BINDTODEVICE到底是做什么的?

  2. 是否有一种方法可以使本地IP不必是唯一的?除了DHCP服务器可能分配给一个已经被另一个接口使用的IP地址之外,这将不会是一个问题,从而混淆了路由表。

  • 如何只从特定接口接收广播?绑定到特定IP似乎会忽略广播,这很合理,但不完全符合我的要求。

  • 我在运行Ubuntu 8.04 w/ Linux kernel 2.6.26。通过两个不同的接口同时访问两个不同网络的同一子网是一个不可妥协的需求,因此它(大部分)免受“不要这样做”的影响。:)

    4个回答

    7
    关于我的一般问题,似乎有几种方法可以解决:
    • 复杂的方式涉及路由表更改和每个进程的合作。这是我上面描述的方式。它的一个优点是它可以从用户空间工作。我在上面加了一些附加说明,并回答了我的具体问题。

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

    • 虚拟化进程。这可能不适合很多人,并且会带来自己的一套麻烦,主要是配置方面的。但是值得一提。

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

    经过一些研究和大量的谷歌搜索,我可以得出一些关于Linux内核2.6.26行为的结论。请注意,这些可能都是特定于实现的行为,甚至可能是特定于内核的。在决定基于我的单个数据点实现功能之前,请测试您自己的平台。
    1. 对于UDP,“SO_BINDTODEVICE”确实做到了它所说的。

    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头。


    5
    经过一个困难的周末,我很高兴能够提供一个解决方案,它几乎没有任何麻烦,并且解决了我之前讨论的大部分问题。有一个名为net.ipv4.conf.all.rp_filter的sysctl参数,可以设置为0以禁用源验证:
    rp_filter - 整数 2-执行反向路径源验证,如RFC1812中所指定 建议单个主机和stub网络路由器使用此选项。可能会对运行缓慢不可靠协议(类似于RIP)或使用静态路由的复杂(非环路自由)网络造成麻烦。
    1-(默认值)RP过滤的较弱形式:删除所有看起来源自直接连接的接口但从另一接口输入的数据包。
    0-无源验证。
    此功能也可以在每个接口上使用/proc/sys/net/ipv4/conf//rp_filter进行设置。正如一位帖子所解释的那样,它使IP路由“不那么确定”,因为来自一个子网的数据包不能保证总是经过相同的接口。在这种情况下,这正是所需要的。请做进一步的研究以确定这是否真的是您想要的。广播仍然具有问题,原因我不明白,但我终于对这个问题感到满意,并希望它可以帮助其他人。

    没有IPv6等效于net.ipv4.conf.all.rp_filter的sysctl变量;然而,相应的功能以netfilter的rpfilter模块的形式存在。在iptables-extensions手册中有相关文档。将rp_filter移入netfilter指出,在未来的内核中完全可能从路由缓存代码中删除rp_filter。 - Sam Morris

    3

    这并不是你问题的直接答案,只是提供一个信息。正如你上面提到的,这种解决方案可能对于你需要/想要做的事情来说太麻烦了。

    个人而言,我喜欢创建一个网络堆栈钩子内核模块来实现这个功能。这样我就可以完全控制从用户空间发送和接收的多播和单播帧。您需要使用类似netlink套接字的东西来发送/接收数据到和从驱动程序和用户空间应用程序,但它非常好用且速度非常快。

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

    这里有一篇文章介绍了如何挂接到netfilter堆栈。
    注意:本文挂接到IP堆栈,而且已经有些年头了。我知道API已经改变了,但是这篇文章在理论和实践上仍然适用于许多内容。 如果您想挂接到桥接层,则可以使用类似的机制,但需要指定

    BR_LOCAL_IN instead of NF_IP_LOCAL_IN
    

    注意:这与在接口上打开原始套接字非常相似。您需要自己构建帧。

    2
    你可以尝试将进程的网络命名空间限制为一个单一的接口。你需要一个带有CONFIG_NETNS选项的内核构建(大多数现代发行版都有),以及一些脚本来为你进行分配。这里提供一个配置示例

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接