V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
wacke
V2EX  ›  Linux

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

  •  
  •   wacke · 2022-05-31 10:13:32 +08:00 · 3732 次点击
    这是一个创建于 372 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,补丁主要解决多 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 。。。

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

    如果要解决需要用 ip rule 匹配来源地址(或用 iptables/nftables 匹配来源网卡设置 mark )来指定策略路由
    wacke
        7
    wacke  
    OP
       2022-05-31 11:16:02 +08:00
    @lqs 不是的,其他 udp tunnel ,应该是有源进源出的设计的或者说可以 bind 到某个 interface 以使用策略路由,但 wireguard 设计成无法 bind 到 interface ,并且每次都直接从 kernel 获取路由。。。
    FabricPath
        8
    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
        9
    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
        10
    wacke  
    OP
       2022-05-31 11:51:16 +08:00
    @FabricPath 然而,wireguard 也不保存传入连接的 ip mark ,而是每次都重置。。。。这个在 github 的 issue 里也是测试过的,无效。。。
    neoblackcap
        11
    neoblackcap  
       2022-05-31 11:56:24 +08:00
    我不是很熟悉内核,不过这样的需求,是不是你用 eBPF 就可以解决了,不用改内核啊?
    wacke
        12
    wacke  
    OP
       2022-05-31 12:00:04 +08:00
    @neoblackcap 我主要是使用 openwrt+mwan3 ,eBPF 太高大上,玩不来。。。
    TempTXT
        13
    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
        14
    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
        15
    wacke  
    OP
       2022-05-31 13:00:57 +08:00
    @TempTXT github 的 issue 差不多是 3 年前的事情了,mwan3 的维护人员,以及其他一些大神,都进行过一些尝试,基本确认这是 wireguard 本身的问题。。。。直到好几个月之前,我尝试过针对目的端口的策略路由,可行,但是过于繁琐且效能太低。。。在 wireguard 团队拒绝修复的情况下, 直接修改 wireguard 源码实现源进源出效能可靠些。
    wacke
        16
    wacke  
    OP
       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
        17
    TempTXT  
       2022-05-31 13:13:00 +08:00
    @wacke 我瞎猜就是因为这个可行所以 wireguard 才不修复(笑
    wacke
        18
    wacke  
    OP
       2022-05-31 13:15:39 +08:00
    @TempTXT 可能吧。。。wireguard 团队超级固执。。。这个问题,早在 2016 年的样子,就有人提出过了。。。。
    lcdtyph
        19
    lcdtyph  
       2022-05-31 13:16:20 +08:00 via iPhone
    这个 patch 能在多个 peer 同时从不同 wan 连接的情况下运行吗
    wacke
        20
    wacke  
    OP
       2022-05-31 13:19:52 +08:00
    @lcdtyph 我这里测试是可以的。。。
    yanqiyu
        21
    yanqiyu  
       2022-05-31 13:20:33 +08:00
    这种情况是典型的非对称路由?
    这肯定不是好事情,建议能解决非对称路由的问题就先解决它,比如导入一个更合理的路由表解决出入路径不一致的问题
    确实在 wireguard 内部维护一个 saddr 状态也是一个办法,但是上游肯定不会喜欢,因为 wireguard 没必要为了非对称路由擦屁股
    wacke
        22
    wacke  
    OP
       2022-05-31 13:27:33 +08:00
    @yanqiyu 我是个小白。。。没有丰富的路由相关的理论知识,更没有内核协议栈及内核编程的知识背景。。。。只能从实际应用出发,尝试解决相关的问题。。。想要让上游接受,我差太远了。。。。另外就是想等待上游解决,估计还是自己实现来得靠谱。。。这个问题已经等了 5 年以上了。。。
    wacke
        23
    wacke  
    OP
       2022-05-31 13:29:46 +08:00
    @yanqiyu 还有一个情况就是,从我理解的 wireguard 源码来看,是 wireguard 自己重置了 ip mark 以及传入连接的目标 ip 的。。。
    lcdtyph
        24
    lcdtyph  
       2022-05-31 13:56:22 +08:00 via iPhone
    @wacke 你再分析分析这代码,多 peer 快速发包的时候真的能正常工作吗? wg_receive 和 send 又不是一定成对出现的
    wacke
        25
    wacke  
    OP
       2022-05-31 14:05:05 +08:00
    @lcdtyph 所以才求大神帮忙完善啊,我自己测试,至少 2 个 peer 同时连接我路由的 pppoe-wan2 ,是正常的。。。
    wacke
        26
    wacke  
    OP
       2022-05-31 14:12:52 +08:00
    @lcdtyph 这个 patch ,我自己也改了好多个版本了,既要保证传入连接的 ip 被正确应用,还要保证当已保存的 ip 失效时,wireguard 能重置这个失效的 ip ,另外还得保证配合 mwan3 的对于 wireguard 对端的策略路由正常工作,头疼。。。
    此前我尝试使用 `inet_confirm_addr(sock_net(sock), NULL, 0, &src_addr, RT_SCOPE_LINL)` 这个函数,貌似没起作用。。。最后发出来的补丁,是我 debug 了 n 次之后貌似没问题的版本。。。
    geekvcn
        27
    geekvcn  
       2022-05-31 14:13:20 +08:00 via iPhone
    @wacke 你需要自己配置对称路由
    wacke
        28
    wacke  
    OP
       2022-05-31 14:21:31 +08:00
    @geekvcn 那么求解,该如何配置,才能实现 wireguard 作为 server 时,client 从任一 wan 口连入时,wireguard 的握手包从连入的 wan 口出去?针对 wireguard 的监听端口做策略路由吗?
    geekvcn
        29
    geekvcn  
       2022-05-31 16:04:57 +08:00 via iPhone
    @wacke 搜关键词对称路由
    wacke
        30
    wacke  
    OP
       2022-05-31 16:12:08 +08:00
    @geekvcn 好吧。你这回了等于没回。。。
    qakito
        31
    qakito  
       2022-05-31 22:46:05 +08:00
    1. IPv4/IPv6 源地址选择是有 RFC 的,在未指定源地址的情况下,出接口地址优先,你看有没有办法能通过配置 bind 指定接口地址
    2. metric 都不同,根本不是多出口问题,路由始终选择 metric 最低的路由
    3. wireguard 我不熟悉,但是作为一个 tunnel responder ,你的 tunnel source 不应该是客户端在建立 tunnel 时决定的么?
    4.有没有办法在 tunnel 建立的时候动态注入路由
    qakito
        32
    qakito  
       2022-05-31 22:55:25 +08:00
    5. 粗略翻了一下 wireguard 的介绍,wireguard 是没有 tunnel connect 这个过程的,主要问题是 client 在 NAT/防火墙内侧么?回程的源地址改变了无法穿过 NAT/防火墙是么?
    wacke
        33
    wacke  
    OP
       2022-05-31 23:10:26 +08:00
    @qakito 相当一部分的 NAT/防火墙,确实会因为回程地址变了,会丢弃数据包,导致无法握手。。。但其实根源还是 wireguard 本身, 收到数据包后,主动去重置源地址及传入的 ip mark 。。。
    qakito
        34
    qakito  
       2022-05-31 23:17:59 +08:00
    顺便说一句,用全局变量绝对不可行,根本无法重入
    比如你这种多 wan 的情况,clientA -> eth0 和 clientB -> eth1 交替访问
    qakito
        35
    qakito  
       2022-05-31 23:47:05 +08:00
    还有 endpoint->src_if4 是 netdevice 的 ifindex ,不是 src_addr
    wacke
        36
    wacke  
    OP
       2022-06-01 07:52:18 +08:00
    @qakito
    >顺便说一句,用全局变量绝对不可行,根本无法重入
    OK ,我尝试把 static 去掉试试。。。
    >还有 endpoint->src_if4 是 netdevice 的 ifindex ,不是 src_addr
    这个改回原来的 “endpoint->src_if4 = 0;” 吗?

    目前来说,我自己从公司的电脑及手机同时分别连接路由的两个 wan ,没发现有啥异常。。。
    wacke
        37
    wacke  
    OP
       2022-06-01 09:54:19 +08:00
    @qakito

    更新了下补丁,粗略测试,貌似没啥问题。。。

    ```
    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-06-01 08:18:00.990080098 +0800
    @@ -17,6 +17,12 @@
    #include <net/udp_tunnel.h>
    #include <net/ipv6.h>

    +u32 dst_addr;
    +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->src4.s_addr = src_addr;
    endpoint->src_if4 = 0;
    - fl.saddr = 0;
    + 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->src4.s_addr = src_addr;
    endpoint->src_if4 = 0;
    - fl.saddr = 0;
    + 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;

    ```
    qakito
        38
    qakito  
       2022-06-02 16:50:53 +08:00
    @wacke
    不好意思,我现在没有方便的环境调试
    我想等验证后再分享我的想法
    wacke
        39
    wacke  
    OP
       2022-06-02 19:22:52 +08:00
    @qakito
    没有关系,这个补丁,至少在我自己的环境下,实现了我的需求。。。
    qakito
        40
    qakito  
       2022-06-06 23:39:03 +08:00
    看了下 wireguard 的实现
    1. 对方的目的地址在本端是有记录的,就记录在 wg_peer 的 endpoint->src4 里
    这个包从哪个 netdevice 进来的也有记录,同样记录在 wg_peer 的 endpoint->src_if4 里
    只不过在查路由的时候,如果查到的出口与入口不相同(rt->dst.dev->ifindex != endpoint->src_if4),就会让路由模块重新选择源地址
    2. 你的修改只是不让路由模块重新选择源地址,但是出口还是错的,在某些情况下回程包仍有可能被丢弃
    3. 个人认为上游不接受你的修改是因为已经提供了 fwmark 来解决你的问题
    举例来说
    ip route add default dev ens3 table 1000 #创建路由表 1000 ,默认路由出口是 ens3 ,如果是 p2p 接口这样就 ok ,否则要写成下一跳
    wg set wg0 fwmark 1234 # 由 wg0 接口出的包一律打上 fwmark 1234
    ip rule add fwmark 1234 table 1000 # 打上 fwmark 1234 的包查路由表 1000
    qakito
        41
    qakito  
       2022-06-06 23:45:17 +08:00
    @wacke
    如果要改代码,最简单的方式就是把 wg_socket_endpoint_from_skb 函数里的 endpoint->src_if4 = skb->skb_iif 这句注掉,这样就可以跳过源地址重新选择的流程
    wacke
        42
    wacke  
    OP
       2022-06-07 08:21:32 +08:00
    @qakito
    首先非常感谢你的回复,作为一个差不多将编程知识都还给老师的小白,要弄懂 kernel 协议栈,实在有点困难。。。。

    1.我要实现的目标是 wireguard 作为服务端在 openwrt 里实现多 pppoe 的情况下,客户端可以任意连接一个 pppoe 实现接入。
    2.目前在 openwrt+mwan3 的情况下,fwmark 标记入口数据包无效,手动指定 fwmark 则仅能实现 wireguard 固定选择多个 pppoe 中的一个作为默认路由。
    3.从我 github 的 issue 以及 openwrt 论坛的部分讨论帖子来看,这个问题不仅仅是 openwrt+mwan3 特有的,邮件列表里也有很多提交给 wireguard 官方案例,但 wireguard 官方都是没有下文。。。。基于此(主要是补丁肯定很 low ),我也不可能将我的修改提交给 wireguard 。
    4.稍晚点我尝试注释 endpoint->src_if4 = skb->skb_iif 这句试试吧。
    wacke
        43
    wacke  
    OP
       2022-06-07 19:53:54 +08:00
    @qakito 已尝试删除 endpoint->src_if4 = skb->skb_iif ,重新编译 openwrt 后,测试无效。。。从最小跃点数的 wan 连入工作正常,其他就无法握手了。。。
    关于   ·   帮助文档   ·   博客   ·   nftychat   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1030 人在线   最高记录 5634   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 49ms · UTC 23:09 · PVG 07:09 · LAX 16:09 · JFK 19:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.