V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jinliming2
V2EX  ›  程序员

求助 QUIC 协议走 Wireguard 代理不通的问题

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

    环境

    电脑 --- 路由器 --- 跳板服务 --- 目标服务器

    电脑 IP:10.10.*.*

    路由器 Wireguard IP:192.168.69.1

    要访问的远程服务器 IP:17.*.*.*

    问题

    电脑在内网,路由器上配置了 Wireguard ,Wireguard 另一端跳板服务在公网。现在内网的电脑想通过 Wireguard 访问到公网的服务器的 QUIC 协议的服务。

    现在情况是,如果不走 Wireguard ,电脑通过路由器是可以正常访问到目标服务的,没有问题。但是配置走了 Wireguard ,就无法正常访问了。

    conntrack 和 Wireshark 抓包的结果:

    看起来是电脑在发送 QUIC 请求,服务端返回了响应包,但是电脑对收到的每个响应包都会回一个 ICMP Port unreachable 的响应,导致连接立即被中断 DESTROY 。

    尝试排查

    因为 QUIC 协议是基于 UDP 的,所以我拿 Node.JS 简单写了个测试程序,在服务器上监听 UDP 端口,做一个 echo 服务,电脑上给服务端发 UDP 请求,服务端给返回多个响应包:

    服务端:

    import dgram from 'node:dgram';
    
    const server = dgram.createSocket('udp4');
    server.bind(443);
    
    server.addListener('message', async (msg, rinfo) => {
        console.log(`Received ${msg.length} bytes from ${rinfo.address}:${rinfo.port}: ${msg}`);
        for (let i = 0; i < 5; ++i) {
            // 延迟 1 秒
            await new Promise(r => setTimeout(r, 1e3));
            server.send(Buffer.concat([msg, Buffer.from(i.toString())]), rinfo.port, rinfo.address, (error, bytes) => {
                console.log('send to', rinfo.address, rinfo.port, error, 'bytes', bytes);
            });
        }
    });
    

    客户端:

    import dgram from 'node:dgram';
    
    const socket = dgram.createSocket('udp4');
    
    socket.addListener('message', (msg, rinfo) => {
        console.log(`Received ${msg.length} bytes from ${rinfo.address}:${rinfo.port}: ${msg}`);
    });
    
    socket.send("asd", 443, " [脱敏,远端 IP ] ");
    

    结果是正常的,电脑上运行客户端代码,会用一个随机端口给服务器的 443 发请求,服务端收到的包显示远端 IP 是 Wireguard 远端的 IP ,表示数据包确实是走 Wireguard 转发到服务端的,然后服务端回复给 Wireguard 远端的 5 个包也都能正常被内网的电脑收到。


    目前没有其他头绪了,来求助问一下,还有什么方向可以排查的吗?可能是什么问题呢?

    5 条回复    2024-09-23 03:24:29 +08:00
    cnbatch
        1
    cnbatch  
       42 天前
    可能是 WireGuard 的 MTU 值不够小
    RobinHuuu
        2
    RobinHuuu  
       42 天前 via Android
    使用 ping 排查 mtu 问题
    pagxir
        3
    pagxir  
       42 天前 via Android
    如果全部遵守规范的话,那就只可能是 17 的那个机器没有 quic 服务,或者没在这个 IP 上运行 quic 服务,或者是防火墙不允许。
    qilme
        4
    qilme  
       41 天前 via Android
    是否有哪个环节用了 http/socks5 代理,这两个不支持 quic
    jinliming2
        5
    jinliming2  
    OP
       39 天前
    @cnbatch @RobinHuuu 经过排查,不是 MTU 的问题,但是感谢提供 ping 的思路,通过 ping 发现具体的问题了。

    通过 ping ,发现只有 icmp_seq 序号为偶数( 0 、2 、4……)的 ping 包才能收到响应,而奇数的包都是超时。就去排查 nftables 规则,发现问题所在:

    我给指定服务器做转发,是在 nat 的 prerouting 链上判断目标地址为 17 服务器的包打上 meta mark ,然后在 ip rule 里匹配 fwmark lookup 表,default 路由到 Wireguard 的 wg0 出口。

    看了下文档,nat 链只在第一个包会命中,后续包就跳过了。因为有 conntrack 跟踪数据包信息,所以 UDP 包也是只在第一个包会命中,后续包不会命中,导致只有第一个握手包发给了 Wireguard ,后续包都从物理网卡直接出去了,导致了 conntrack 同一个内网 IP + 端口对应不同的出口,触发了 conntrack 的 destroy ,所以服务端响应的包就找不到 NAT 回复的目标了。

    我自己写的 UDP 测试程序之所以没问题,是因为每次电脑端每次发送都是用的随机 UDP 端口发一个包,conntrack 只跟踪第一个包,所以正常。如果给客户端代码也加上 bind ,使用相同端口来请求,就会发现和 ping 一样的,一次通,一次不通,分别是 conntrack 的 NEW 和 DESTROY 。

    解法是在 meta mark set 的同时加上 ct mark set meta mark 给 conntrack 也打上标记,然后加了个 filter prerouting 的 mangle 链,匹配 ct mark 也设置上 meta mark set 就好了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5635 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 38ms · UTC 03:35 · PVG 11:35 · LAX 20:35 · JFK 23:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.