标签 OpenWrt 下的文章

正常的透明代理

  1. 工作在 L3 的 VPN
  2. tun2socks 
  3. Linux 内核的 TPROXY

曲折的透明代理

  1. 支持 UDP 的 SOCKS5 代理软件
  2. 用户空间的 WireGuard 服务端结合 SOCKS5 转发
  3. 安装了 WireGuard 模块的 OpenWRT 路由器

刚好也是三项,但这不是三个方案,是一个方案的三个组件。

听起来似乎完全没必要做这么复杂,但为了利用上唯一能刷 OpenWRT 的低配路由器、不干扰本就复杂的服务器路由,同时保障信道安全、可达,最终扭曲成了这样。

即使你没有这种特殊情况,或许也能用上某个组件就是了,例如在没有 root 的环境下提供 WireGuard 服务。

支持 UDP 的 SOCKS5 代理

这个相对好找,不过建议使用底层为 UDP 且保留了 UDP 不可靠传输特性的代理软件。否则发挥不了 UDP 的优势,性能上可能有负面影响。

用户空间的 WireGuard 服务端

服务端在一台单独的服务器上运行,建议配置成静态 IP。服务器可以通过 DMZ 或 VLAN 方式接入路由器 LAN 口,也可以接入上一层路由、通过 WAN 口通信。

我选择了开源的 Wiretap,好处是运行在用户空间不用动内核,坏处是性能会比原生网络栈差一些,但应该比虚拟机强。我对 Wiretap 进行了轻度的魔改以支持代理,但 ICMP 包的处理上还没完善。 https://github.com/l2dy/wiretap/tree/tproxy

别忘了在防火墙上放行对应的 UDP 端口,否则是连不上的(废话)。

OpenWRT 路由器

有条件的话买一个高配路由器安装上代理软件并配置 TPROXY 是最佳方案。

但如果你坚持使用低内存或闪存空间有限的低配路由器,WireGuard 是 OpenWRT 上最轻量的 VPN 方案之一了。由于 WireGuard 协议不具备混淆功能、特征过于明显,建议仅在本地使用。当然如果信道完全可信,也可以考虑 tun2socks 方案,省下加密的开销。

那么 DNS 怎么办呢?由于 LuCI 不支持给 WireGuard 接口配置 DNS,需要配置全局 DNS 并关闭从 DHCP 获取 DNS 的选项(设置 noresolv '1')。在 WireGuard 接口配置了 0.0.0.0/0 路由后,DNS 也会走 VPN。这个方案的前提是 SOCKS5 代理支持 UDP,但如果不支持的话也可以魔改服务端,把请求转发到本地的可信 DNS。

TCP 层由于 Wiretap 已经在用户空间 ACK 了相关的包,不会像远程 L3 VPN 那样出现 TCP meltdown。但为了 UDP 还是需要合理调整 MTU 值,否则底层可能会把 UDP 包拆分传输,也可能直接丢弃。

参考资料

IPv6 relay 模式怎么配置网上有一大把教程,就不多说了。重点在于为什么客户端能获取地址却 ping 不通公网,但 ping 一下路由器的 wan IPv6 地址后就可以上网了。通过在客户端设备上抓包和查看 ip neigh(或 ndp -an)可以发现,区别在于 IPv6 的邻居发现过程。

正确方式

IP6 fe80::xxxx > [solicited-node multicast address]: ICMP6, neighbor solicitation, who has [client address], length 32
IP6 fe80::yyyy > fe80::xxxx: ICMP6, neighbor advertisement, tgt is [client address], length 32

错误方式(路由器在 lan 侧找不到客户端 IP)

IP6 fd..:[ULA-prefix address] > [solicited-node multicast address]: ICMP6, neighbor solicitation, who has [target address], length 32

为什么错误方式下邻居发现会失败呢?原因是纯 relay 模式下客户端不会获取到 ULA-prefix1 下的地址/路由/邻居信息,当客户端尝试回复从路由器发出的 ULA-prefix 下地址的 neighbor solicitation 时,匹配不到这个 ULA 地址的路由只好放弃。

那么为什么 ping 一下网又通了呢?这是因为 odhcpd 从 wan 那边获取到了客户端的信息,从而配置上了 NDP 代理和路由。将 odhcpd 的 loglevel 配置成 7(LOG_DEBUG,数字来自syslog(3))后可以看到对应的 proxy 日志。

所以只要去掉路由器 lan 上的 ULA 就可以了,也就是在 network 配置中注释掉 ip6assign。重启路由器后,客户端就可以正常上网了。

另一种可能的方式是同时启用 lan 上 ULA 和 relay 的地址分配,不过 relay 模式下不好实现。

最后剩下一个问题,中继模式下首次请求延迟会比较高,之后就正常了,这部分原理可以参考 https://blog.icpz.dev/articles/notes/odhcpd-relay-mode-discuss/

参考资料

https://openwrt.org/docs/guide-user/network/ipv6/start#ipv6_relay

# cat /etc/config/dhcp
config dhcp lan
    option dhcpv6 relay
    option ra relay
    option ndp relay
    ...
 
config dhcp wan6
    option dhcpv6 relay
    option ra relay
    option ndp relay
    option master 1
    option interface wan6

笔者注:如非必要建议关闭 dhcpv6,原因见 https://issuetracker.google.com/issues/36949085


https://datatracker.ietf.org/doc/html/rfc4291#section-2.7.1

Solicited-Node multicast address are computed as a function of a
node's unicast and anycast addresses. A Solicited-Node multicast
address is formed by taking the low-order 24 bits of an address
(unicast or anycast) and appending those bits to the prefix
FF02:0:0:0:0:1:FF00::/104 resulting in a multicast address in the
range

     FF02:0:0:0:0:1:FF00:0000

to

     FF02:0:0:0:0:1:FFFF:FFFF

https://openwrt.org/docs/guide-user/base-system/basic-networking

network.globals.ula_prefix='fd27:70fa:5c1d::/48'

https://datatracker.ietf.org/doc/html/rfc4861#section-7.2.4

If the source of the solicitation is the unspecified address, the
node MUST set the Solicited flag to zero and multicast the
advertisement to the all-nodes address. Otherwise, the node MUST set
the Solicited flag to one and unicast the advertisement to the Source
Address of the solicitation.


P.S. Hybrid 模式是什么?其实就是智能开启 relay,代码如下,不要误以为是同时开启 relay 和 server 模式。

            /* Resolve hybrid mode */
            if (i->dhcpv6 == MODE_HYBRID)
                i->dhcpv6 = (master && master->dhcpv6 == MODE_RELAY) ?
                        MODE_RELAY : MODE_SERVER;

            if (i->ra == MODE_HYBRID)
                i->ra = (master && master->ra == MODE_RELAY) ?
                        MODE_RELAY : MODE_SERVER;

            if (i->ndp == MODE_HYBRID)
                i->ndp = (master && master->ndp == MODE_RELAY) ?
                        MODE_RELAY : MODE_DISABLED;

odhcpd 的 README 里有这么一段,说明客户端地址在路由器上的路由是它配置的。

4. Proxy for Neighbor Discovery messages (solicitations and advertisements)
   a) support for auto-learning routes to the local routing table
   b) support for marking interfaces "external" not proxying NDP for them
      and only serving NDP for DAD and for traffic to the router itself
      [Warning: you should provide additional firewall rules for security]