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

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

  •  
  •   wacke · 75 天前 · 3235 次点击
    这是一个创建于 75 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,补丁主要解决多 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  
       75 天前 via Android
    因为这不是 bug 呀,跃点数最小说明这条线路是最优先的,若要指定出口的 wan ,这属于 feature 。
    Kinnice
        2
    Kinnice  
       75 天前 via Android
    @Kinnice 感觉像是 mwan3 的问题,在 Ubuntu 下,多个出口 wan ,并未产生这个问题。
    wacke
        3
    wacke  
    OP
       75 天前
    @Kinnice 呃,这里解决的是,多 wan 环境下,把 wireguard 作为服务器时,只有最小跃点数的 wan 才能正常建立连接。。。。
    Kinnice
        4
    Kinnice  
       75 天前 via Android
    @wacke 哦哦,看到了,从一个跃点大的连进来,然后出却是还是从跃点低的出去,属实有点奇怪。
    wacke
        5
    wacke  
    OP
       75 天前
    @Kinnice 最奇怪的是,wireguard 团队不认为这是个异常状态,拒绝修复。。。
    lqs
        6
    lqs  
       75 天前
    Linux kernel 的路由就是这么设计的,其他服务也有同样的问题,不应该只在 wireguard 里做特殊处理

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

    目前来说,我自己从公司的电脑及手机同时分别连接路由的两个 wan ,没发现有啥异常。。。
    wacke
        37
    wacke  
    OP
       74 天前
    @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  
       72 天前
    @wacke
    不好意思,我现在没有方便的环境调试
    我想等验证后再分享我的想法
    wacke
        39
    wacke  
    OP
       72 天前
    @qakito
    没有关系,这个补丁,至少在我自己的环境下,实现了我的需求。。。
    qakito
        40
    qakito  
       68 天前
    看了下 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  
       68 天前
    @wacke
    如果要改代码,最简单的方式就是把 wg_socket_endpoint_from_skb 函数里的 endpoint->src_if4 = skb->skb_iif 这句注掉,这样就可以跳过源地址重新选择的流程
    wacke
        42
    wacke  
    OP
       68 天前
    @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
       67 天前
    @qakito 已尝试删除 endpoint->src_if4 = skb->skb_iif ,重新编译 openwrt 后,测试无效。。。从最小跃点数的 wan 连入工作正常,其他就无法握手了。。。
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2402 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 06:19 · PVG 14:19 · LAX 23:19 · JFK 02:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.