最近遇到个问题,只要 UU 开着 Openwrt 端的主机加速,在外面用 XStreaming 串流的时候延迟就会明显提高。由于 XStreaming 在设备端我让他直连的,起码不是用户端导致串流延迟。

那问题就只可能出在 UU 所在的 Openwrt 上,我目前的拓扑是这样的:

XBOX 192.168.31.13
  ↓
UU 主机加速 192.168.31.49
  ↓
旁路由 192.168.31.2
  ↓
主路由 192.168.31.1
  ↓
光猫
  ↓
Internet

很抽象……但确实有必要,因为我的旁路由上运行了其他的调整路由的工具,把 UU 主机加速装在上面会冲突,导致 UU 失效,所以我是在 PVE 上额外开了一个新的 vm 来跑 Openwrt 然后再跑 UU 加速。别问为什么不主路由拨号,问就是客观条件导致不能这么做😡

经过和 GPT 大哥共同分析后,得出了结论,只要 UU 主机加速处于加速状态,原本 XBOX 的远程串流媒体流就会进入 UU Openwrt 的 tun164,然后被封装成另一条 UDP 大流发到本市另一个服务器,大概就是网易租用的服务器……虽然也在本地不至于出境,但这中间就肯定会增加延迟。

既然是这样,那只要让 XBOX 的 9002 端口的媒体流永远不进 tun163/tun164(取决于设备是第一台加速的还是第二台加速的)就好了,那就得动 UU Openwrt 的路由。

😡然后这个时候就想到了我既然要单独改路由为什么不让 9002 的部分直接送主路由 192.168.31.1 呢,这样连中间的延迟都省了😡我靠我怎么才想到😡

由于我是主路由 dmz 到旁路由,旁路由上有 lucky 做端口转发,所以目前的问题在于解决 UU 抢走 9002,以及主路由上需要直接让 9002 转发到 XBOX 而不是被 dmz 到旁路由,同时不影响其他流量继续经过 UU,也就是需要变成这样:

XBOX 192.168.31.13
  ↓(UDP 9002)  ↓(其他流量)
  ↓         UU 主机加速 192.168.31.49
  ↓            ↓
  ↓         旁路由 192.168.31.2
  ↓            ↓
主路由 192.168.31.1
  ↓
光猫
  ↓
Internet

首先,为了最终的验证,以及实际上不要留下没必要的东西,先需要把原本在旁路由上的端口转发关闭,因为我原本的 9002 端口转发是这样的:

Internet 对我家 IP 9002 端口发起 UDP 请求
      ↓
光猫接到之后根据光猫上的端口转发设置,转发给主路由 9002 端口
      ↓
主路由接到之后根据 dmz 配置转发给旁路由 9002 端口
      ↓
旁路由接到之后根据 Lucky 的配置,转发给 192.168.31.13:9002

所以先要做的,就是把 Lucky 针对 9002 的端口转发关掉,那么在现在的情况下,9002 一路打到旁路由上就会停住,所以需要让主路由在 dmz 之前劫走 UDP 9002 的请求。

你可能会想,后台加一下就行,但是我们聪明的小米有着自己的小脑筋:

😡哈皮吧,Openwrt 又不是做不到,不知道为啥这么设计

所以就需要 ssh 进去路由器了。众所周知,小米用的 Openwrt 18.06,所以就是普通的 iptables:

root@XiaoQiang:~# iptables -t nat -L zone_wan_prerouting -n -v --line-numbers
Chain zone_wan_prerouting (1 references)
num   pkts bytes target     prot opt in     out     source               destination         
1    26447 3437K MINIUPNPD  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
2    26447 3437K prerouting_wan_rule  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* !fw3: Custom wan prerouting rule chain */
3     6131 1287K DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* !fw3: @redirect[0] */ to:192.168.31.2
4     8294 1764K DNAT       udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp spt:!67 /* !fw3: @redirect[1] */ to:192.168.31.2

所以需要做的就是在主路由层直接转发 9002 端口的 UDP 请求送到 XBOX 上去,并且必须早于 dmz 规则。

在主路由上执行:

iptables -t nat -I zone_wan_prerouting 1 \
    -p udp --dport 9002 \
    -j DNAT --to-destination 192.168.31.13:9002

规则就会变成:

root@XiaoQiang:~# iptables -t nat -L zone_wan_prerouting -n -v --line-numbers
Chain zone_wan_prerouting (1 references)
num   pkts bytes target     prot opt in     out     source               destination         
1        0     0 DNAT       udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp dpt:9002 to:192.168.31.13:9002
2    26496 3440K MINIUPNPD  all  --  *      *       0.0.0.0/0            0.0.0.0/0           
3    26496 3440K prerouting_wan_rule  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* !fw3: Custom wan prerouting rule chain */
4     6131 1287K DNAT       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            /* !fw3: @redirect[0] */ to:192.168.31.2
5     8308 1767K DNAT       udp  --  *      *       0.0.0.0/0            0.0.0.0/0            udp spt:!67 /* !fw3: @redirect[1] */ to:192.168.31.2

处理完主路由的问题后,就需要处理 UU 的 Openwrt 了。由于 XBOX 的网关和 DNS 是指向 UU Openwrt 的,而 UU 原本的规则会把 XBOX 的 UDP 流量标记为 0x164:

table 164 / 180
 ↓
tun164
 ↓
UU 加速节点

如果不处理,虽然主路由进来的流量是直达 XBOX 的,但是 XBOX 出去的流量会变成 XBOX → UU 节点 → 客户端(XStreaming 之类)。

所以现在要做的就是在 UU Openwrt 上添加一个专门的路由表,首先是最初始的配置。

在 UU Openwrt 上执行:

ip route add table 200 \
    192.168.31.0/24 dev br-lan scope link

ip route add table 200 \
    default via 192.168.31.1 dev br-lan

这两句的目的是,在这个路由表下破坏原本 UU Openwrt 的网络流向。原本由于 UU Openwrt 的网关是 192.168.31.2,因此任何流量原本都是需要让 UU Openwrt 发送到 旁路由,再由 旁路由 送给 主路由 的。这不符合这个文章想做的精简链路,通过这个规则可以绕过原本的默认设计,让流量直接送主路由(当然,必须用了这个路由表的流量才会直接送主路由)

接下来就是用 nftable 建一个表,并建立 PREROUTING 链,用于给 UDP 9002 的流量打上额外的 mark。

在 UU Openwrt 上执行:

nft add table ip RP_BYPASS

nft 'add chain ip RP_BYPASS prerouting { type filter hook prerouting priority 30000; policy accept; }'

nft add rule ip RP_BYPASS prerouting \
    ip saddr 192.168.31.13 \
    udp sport 9002 \
    counter \
    meta mark set 0x9002

UU 在启用加速的时候,会有 PREROUTING priority -199,同时给几乎所有 IP 的流量打上 mark 0x164(我这里164大概是因为 XBOX 是第二台加速设备,所以从 163 变成 164)。根据 nf 桌子的设计,优先级数字越小越早用上,所以要做的就是设置一个优先级足够大的规则,让流量即使被打上 mark 0x164,最后也会被优先级更大的规则覆盖上 mark 0x9002。

识别条件设置 IP 为 192.168.31.13,端口为 9002 的 UDP 流量,够精准也够用,毕竟不可能拿客户端 IP 当识别条件……

有了这俩之后就是把两者相结合了。

在 UU Openwrt 上执行:

ip rule add priority 1 \
    from 192.168.31.13 \
    fwmark 0x9002 \
    lookup 200

目的是让 Openwrt 知道,假设有流量被标记为 mark 0x9002,那就要查 table 200,也就是我们前面的设计,直接流向 192.168.31.1。

通过查询 UU 加速后的规则可以看到:

32762: from 192.168.31.13 lookup 180
32763: from 192.168.31.13 fwmark 0x164 lookup 164

UU 设置了优先级为 32762 和 32793 的规则(这个优先级并不固定,有时候甚至会有小于 100 的),根据 ip rule 的设计,数字越小优先级越高,所以设置这么一个优先级为 1 的就够了。


如此,XBOX 产生的远程串流流量,就会变成这样:

XBOX 远程串流数据上行
       ↓
[所在 PREROUTING 链优先级为 -199]
UU 给所有来自 192.168.31.13 的 UDP 目标端口 1025-65535 的流量全打上 mark 0x164,
       ↓
[所在 PREROUTING 链优先级为 30000](数字越小优先级越高)
重新为 192.168.31.13 的 UDP 源端口 9002 的流量覆盖上 mark 0x9002
       ↓
开始策略路由
       ↓
[调整前] UU 的优先级 32762 规则会导致开始查询 table 180,没找到有效路由则再试图匹配优先级 32763 的规则查询 table 164
[调整后] 新的规则优先级为 1,数字越小优先级越高,优先执行
       ↓
查找 table 200
       ↓
default via 192.168.31.1
       ↓
直接去主路由
客户端对公网 IP:9002 发送 UDP 数据
       ↓
光猫的 WAN 9002 端口接收到数据,根据端口转发规则转发到主路由 WAN 9002 端口
       ↓
主路由进入 NAT PREROUTING
       ↓
主路由优先命中新 DNAT 规则,UDP 9002 端口的数据全部直接送 192.168.31.13:9002
       ↓
XBOX 远程串流数据下行

当前面的配置使得远程串流正常进行之后,下一步就是固定我们的配置了。

在主路由上执行:

cat > /etc/firewall.xbox-rp <<'EOF'
#!/bin/sh

iptables -t nat -D zone_wan_prerouting \
    -p udp --dport 9002 \
    -j DNAT --to-destination 192.168.31.13:9002 \
    2>/dev/null

iptables -t nat -I zone_wan_prerouting 1 \
    -p udp --dport 9002 \
    -j DNAT --to-destination 192.168.31.13:9002
EOF

chmod +x /etc/firewall.xbox-rp

在主路由上执行:

SEC=$(uci add firewall include)
uci set firewall.$SEC.path='/etc/firewall.xbox-rp'
uci set firewall.$SEC.reload='1'
uci commit firewall

可以重启防火墙验证一下。

在主路由上执行:

/etc/init.d/firewall restart
iptables -t nat -L zone_wan_prerouting -n -v --line-numbers

正常情况下就能看到文章前期的 DNAT 规则了(

然后接着就是 UU 所在的 Openwrt 了:

在 UU Openwrt 上执行:

cat > /etc/firewall.xbox-rp <<'EOF'
#!/bin/sh

ip route replace table 200 \
    192.168.31.0/24 dev br-lan scope link

ip route replace table 200 \
    default via 192.168.31.1 dev br-lan

ip rule del priority 1 \
    from 192.168.31.13 \
    fwmark 0x9002 \
    lookup 200 \
    2>/dev/null

ip rule add priority 1 \
    from 192.168.31.13 \
    fwmark 0x9002 \
    lookup 200

nft delete table ip RP_BYPASS 2>/dev/null

nft add table ip RP_BYPASS

nft 'add chain ip RP_BYPASS prerouting { type filter hook prerouting priority 30000; policy accept; }'

nft add rule ip RP_BYPASS prerouting \
    ip saddr 192.168.31.13 \
    udp sport 9002 \
    counter \
    meta mark set 0x9002
EOF

chmod +x /etc/firewall.xbox-rp

在 UU Openwrt 上执行:

SEC=$(uci add firewall include)
uci set firewall.$SEC.type='script'
uci set firewall.$SEC.path='/etc/firewall.xbox-rp'
uci set firewall.$SEC.fw4_compatible='1'
uci commit firewall

可以重启防火墙验证一下。

在 UU Openwrt 上执行:

nft list table ip RP_BYPASS
ip -4 rule show
ip -4 route show table 200

正常情况下能看到我们设置的 RP_BYPASS,策略路由里优先级 1 的规则,以及 table 200 的配置了(

中国移动 5G 远程串流家里的中国电信宽带,延迟尚可