我想通过任何指定的以太网接口建立传出的 TCP 路径,即使指定的以太网接口未配置为默认网关。
我的设备有两个以太网接口(eth0、eth1)。
我想强制为 eth0 (192.168.73.x) 建立 TCP 连接,即使默认网关设置为 eth1 (192.168.83.1) 而不是像下面的路由表中那样设置为 eth0。
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.83.1 0.0.0.0 UG 0 0 0 eth1
192.168.73.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
192.168.83.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
为了实现这一点,我创建了一个套接字并使用绑定 api 连接了我的源 IP 地址(=eth0)。
(我也尝试过使用 SO_BINDTODEVICE,但没有效果)
然后我使用目标 IP 地址调用连接 API 来连接到外部目标的公共地址
代码片段如下。
1>
sock = socket(AF_INET, SOCK_STREAM, 0);
2>
struct sockaddr_in my_addr;
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(0);
my_addr.sin_addr.s_addr = htonl(myIP); // eth0's IP address (192.168.73.238)
bind(sock, (struct ::sockaddr *)&my_addr, sizeof(my_addr) )
3>
struct sockaddr_in dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = inet_addr(destIp); // Destination IP
dest_addr.sin_port = htons(destinationPort); // Destination Port
connect(sock, (struct sockaddr *)(&dest_addr),sizeof(struct sockaddr_in))
连接返回的错误代码是“没有到主机的路由”
但这在我最新的装有 ubuntu linux 18.04(内核 4.15.18)的设备上失败了,而同样的逻辑在我装有旧内核版本(如 3.10 版本)的旧设备上却成功了。
一旦我将默认网关从 eth1 更改为 eth0,并且路由表变为如下所示,我就可以通过 eth0 发送传出数据包。
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.83.1 0.0.0.0 UG 0 0 0 eth1
192.168.73.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
192.168.83.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
实际上,无论我的默认网关配置如何,我都希望能够建立传出路径。
但它在最新的 ubuntu 18.04(内核 4.15.18)中失败了。
是否有任何 sysctl 选项我可以尝试使其工作?
或者我可以尝试使用内核构建选项吗?
任何建议将被认真考虑。
答案1
TL;DR:您必须根据拓扑定义每个所需目的地所需的路由/网关;也许您的程序必须以 root 身份运行。
这是系统的路由怪癖,而不是您的编程。每当有数据包准备发送时,系统都会将网络掩码应用于目标 IP 地址,以确定该 IP 是否位于已知网络中,然后选择输出接口。
例如,如果您将某个数据包发送到 192.168.73.25,则应用接口的已知掩码 255.255.255.0 会得到网络 192.168.73.0(存在于 eth0 中),因此数据包将通过 eth0 发送(如您配置的第一个路由表中所示)。如果没有接口具有所需的目标网络,则数据包将发送到默认网关,我们已经知道该网关位于 192.168.83.1,可通过 eth1 看到。
所有这些都可以通过路由表来控制,从而改变系统的决策。因此,如果可以通过 eth0 到达目的地,则必须设置一条新路由来表明这一点。例如,如果您想将流量发送到 192.168.12.35,并且您知道可以从连接到 eth1 的网络中的路由器(例如,地址为 192.168.83.67)访问此地址,那么您可以添加一条新路由,如下所示:
ip route add 192.168.12.65/32 via 192.168.83.67 dev eth1
请注意,我们不能简单地说“通过 eth1”,因为目标 IP 不在 eth1 的网络地址范围 192.168.83.1-254 内(如 eth1 掩码所示),并且通过该接口发送数据包不会自动找到知道在哪里重新发送数据包的人(也可能不知道如何返回答案)。当然,为了使更改在重新启动之间永久生效,我们必须在 netplan 配置中配置路由。
如果您的拓扑结构包含两个 ISP(每个 ISP 都有一个通往互联网的网关),但只有一个配置为默认网关,那么您必须设计特殊的路由表,通过目标 IP 或端口或其他条件选择数据包,并将其发送到所需的网关。为此,我建议阅读Linux 高级路由和流量控制方法,或者实施互联网服务提供商 (ISP) 余额林肯·D·斯坦 (Lincoln D. Stein) 的脚本可以解决此类情况。
对于太长的文字或假设您还不知道路由机制,我深感抱歉。
注意:如果您知道需要哪些路由,您的程序可以通过编程方式更改路由表,就像 ip 命令一样,但它可能必须以 root 权限运行。
编辑:阅读有关 C 路由编程的文章,我发现了有趣的代码Linux 杂志,并且在奥列格·库特科夫博客。