弄了个 wireguard 补丁,求熟悉 Linux kernel 协议栈的大神帮忙完善。。。。

2022-05-31 10:13:32 +08:00
 wacke

如题,补丁主要解决多 wan 下 wireguard 始终只使用最小跃点数的 wan 作为源 ip 的问题,本人不太熟悉 kernel 的网络协议栈,so 写出来的补丁比较 low ,求大神帮忙完善。

具体的始末可参考我在 github 的 issue:https://github.com/openwrt/packages/issues/9538

补丁如下:

diff -uNr linux-5.15.12_orig/drivers/net/wireguard/socket.c linux-5.15.12_wg/drivers/net/wireguard/socket.c
--- linux-5.15.12_orig/drivers/net/wireguard/socket.c	2021-12-29 19:29:03.000000000 +0800
+++ linux-5.15.12_wg/drivers/net/wireguard/socket.c	2022-05-27 15:27:40.000000000 +0800
@@ -17,6 +17,12 @@
 #include <net/udp_tunnel.h>
 #include <net/ipv6.h>
 
+static u32 dst_addr;
+static u32 src_addr;
+
+int receive = 0;
+int send = 0;
+
 static int send4(struct wg_device *wg, struct sk_buff *skb,
 		 struct endpoint *endpoint, u8 ds, struct dst_cache *cache)
 {
@@ -37,6 +43,13 @@
 
 	rcu_read_lock_bh();
 	sock = rcu_dereference_bh(wg->sock4);
+    
+	if ((receive) && (!send) || (send) && (!receive)) {
+		src_addr = dst_addr;
+	}
+	else {
+		src_addr = 0;
+	}
 
 	if (unlikely(!sock)) {
 		ret = -ENONET;
@@ -52,9 +65,11 @@
 		security_sk_classify_flow(sock, flowi4_to_flowi_common(&fl));
 		if (unlikely(!inet_confirm_addr(sock_net(sock), NULL, 0,
 						fl.saddr, RT_SCOPE_HOST))) {
-			endpoint->src4.s_addr = 0;
-			endpoint->src_if4 = 0;
-			fl.saddr = 0;
+			endpoint->src4.s_addr = src_addr;
+			endpoint->src_if4 = src_addr;
+			fl.saddr = src_addr;
+			send = 1;
+			receive = 0;
 			if (cache)
 				dst_cache_reset(cache);
 		}
@@ -62,9 +77,11 @@
 		if (unlikely(endpoint->src_if4 && ((IS_ERR(rt) &&
 			     PTR_ERR(rt) == -EINVAL) || (!IS_ERR(rt) &&
 			     rt->dst.dev->ifindex != endpoint->src_if4)))) {
-			endpoint->src4.s_addr = 0;
-			endpoint->src_if4 = 0;
-			fl.saddr = 0;
+			endpoint->src4.s_addr = src_addr;
+			endpoint->src_if4 = src_addr;
+			fl.saddr = src_addr;
+			send = 1;
+			receive = 0;
 			if (cache)
 				dst_cache_reset(cache);
 			if (!IS_ERR(rt))
@@ -77,8 +94,12 @@
 					    wg->dev->name, &endpoint->addr, ret);
 			goto err;
 		}
-		if (cache)
+		if (cache) {
+			if (receive) {
+				fl.saddr = src_addr;
+			}
 			dst_cache_set_ip4(cache, &rt->dst, fl.saddr);
+		}
 	}
 
 	skb->ignore_df = 1;
@@ -315,6 +336,11 @@
 static int wg_receive(struct sock *sk, struct sk_buff *skb)
 {
 	struct wg_device *wg;
+	struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
+
+	dst_addr = ip_header->daddr;
+	receive = 1;
+	send = 0;
 
 	if (unlikely(!sk))
 		goto err;

补丁主要的核心就是通过读取 kernel skb_buff 中的目的地址,并把该地址应用到 wireguard 的源地址上,补丁我自己测试貌似没啥大问题,只是考虑到我本人的编程水平,求大神帮忙完善。

ps:wireguard 官方貌似不认为这是 bug 。。。

4769 次点击
所在节点    Linux
44 条回复
Kinnice
2022-05-31 10:30:27 +08:00
因为这不是 bug 呀,跃点数最小说明这条线路是最优先的,若要指定出口的 wan ,这属于 feature 。
Kinnice
2022-05-31 10:34:07 +08:00
@Kinnice 感觉像是 mwan3 的问题,在 Ubuntu 下,多个出口 wan ,并未产生这个问题。
wacke
2022-05-31 10:38:28 +08:00
@Kinnice 呃,这里解决的是,多 wan 环境下,把 wireguard 作为服务器时,只有最小跃点数的 wan 才能正常建立连接。。。。
Kinnice
2022-05-31 10:40:44 +08:00
@wacke 哦哦,看到了,从一个跃点大的连进来,然后出却是还是从跃点低的出去,属实有点奇怪。
wacke
2022-05-31 10:41:35 +08:00
@Kinnice 最奇怪的是,wireguard 团队不认为这是个异常状态,拒绝修复。。。
lqs
2022-05-31 11:06:11 +08:00
Linux kernel 的路由就是这么设计的,其他服务也有同样的问题,不应该只在 wireguard 里做特殊处理

如果要解决需要用 ip rule 匹配来源地址(或用 iptables/nftables 匹配来源网卡设置 mark )来指定策略路由
wacke
2022-05-31 11:16:02 +08:00
@lqs 不是的,其他 udp tunnel ,应该是有源进源出的设计的或者说可以 bind 到某个 interface 以使用策略路由,但 wireguard 设计成无法 bind 到 interface ,并且每次都直接从 kernel 获取路由。。。
FabricPath
2022-05-31 11:19:38 +08:00
因为 linux route 是工作在 L3 的组件,对于 route 系统来说,是 per-packet 处理的,不关注你的连接。如楼上所说,如果你需要让他从哪儿进来从哪儿出去,那就需要在创建 ct 的时候,设置 ct-mark ,在出向的时候把 ct-mark copy 到 skb mark ,再通过 ip rule 配置不同的 skb mark 走不同的接口出来。
FabricPath
2022-05-31 11:21:39 +08:00
关联的两个 iptables action 是 -j CONNMARK --save-mark (把 skb-mark 复制到 ct-mark ),-j CONNMARK --restore-mark (把 ct-mark 复制到 skb-mark )
wacke
2022-05-31 11:51:16 +08:00
@FabricPath 然而,wireguard 也不保存传入连接的 ip mark ,而是每次都重置。。。。这个在 github 的 issue 里也是测试过的,无效。。。
neoblackcap
2022-05-31 11:56:24 +08:00
我不是很熟悉内核,不过这样的需求,是不是你用 eBPF 就可以解决了,不用改内核啊?
wacke
2022-05-31 12:00:04 +08:00
@neoblackcap 我主要是使用 openwrt+mwan3 ,eBPF 太高大上,玩不来。。。
TempTXT
2022-05-31 12:49:54 +08:00
有试过配置原路返回路由吗?例如 eth1 的 IP 为 x.x.x.x ,gw 是 y.y.y.y ,那么对于源 IP 为 x.x.x.x 的数据包下一跳是 y.y.y.y 。如果没有配置策略,都从默认路由表查找路由,多条默认路由按照 metric 优先顺序匹配,这是符合路由查找规则的。
TempTXT
2022-05-31 13:00:21 +08:00
ip route add table [table_name] default via [dev_gateway] dev [dev] ###创建个不同于默认路由表的新路由表,这一表上的默认路由不同于默认路由表,应用给不同的 wan 口设备。
ip rule add from [dev_ip] table [table_name] ###wg 的应答包,源 IP 为不同的 wan 的 ip ,这些数据包应该从新路由表查找。
wacke
2022-05-31 13:00:57 +08:00
@TempTXT github 的 issue 差不多是 3 年前的事情了,mwan3 的维护人员,以及其他一些大神,都进行过一些尝试,基本确认这是 wireguard 本身的问题。。。。直到好几个月之前,我尝试过针对目的端口的策略路由,可行,但是过于繁琐且效能太低。。。在 wireguard 团队拒绝修复的情况下, 直接修改 wireguard 源码实现源进源出效能可靠些。
wacke
2022-05-31 13:04:05 +08:00
@TempTXT
https://github.com/openwrt/packages/issues/9538#issuecomment-983330533
https://github.com/openwrt/packages/issues/9538#issuecomment-1003715531
这个是我此前根据 mwan3 的策略路由搞的 wireguard 源 ip 策略路由,确实可行,但是效能太低了。。。
TempTXT
2022-05-31 13:13:00 +08:00
@wacke 我瞎猜就是因为这个可行所以 wireguard 才不修复(笑
wacke
2022-05-31 13:15:39 +08:00
@TempTXT 可能吧。。。wireguard 团队超级固执。。。这个问题,早在 2016 年的样子,就有人提出过了。。。。
lcdtyph
2022-05-31 13:16:20 +08:00
这个 patch 能在多个 peer 同时从不同 wan 连接的情况下运行吗
wacke
2022-05-31 13:19:52 +08:00
@lcdtyph 我这里测试是可以的。。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/856378

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX