某不科学的 OpenBSD 旁路网关

记录一下如何把电子垃圾变成家庭网关。

其实并没有什么技术含量,但是互联网上几乎没有相关的内容(基本都是基于 OpenWrt 或者 VyOS 之类的比较专用的方案)。

希望本文可以给同样想折腾的后来者提供一些帮助,如果有幸能给你带来一些启发那就再好不过了。

前置条件

  • 闲置的 x86 / ARM 盒子 (¥100~200) , 单网口也可以
  • 一点好奇心和耐心

拓扑

                         ┌──────────────────────┐                        
                         │                      │                        
                         │                      │                        
                         │     光猫或主路由       │                        
                         │     192.168.1.1      │                        
                         │                      │                        
                         └──────────┬───────────┘                        
                                    │                                    
                                    │                                    
                                    │                                    
                                    │                                    
                                    │                                    
                         ┌──────────┴────────┐                           
                         │                   │                           
                         │                   │                           
           ┌─────────────┼   一般睿智的交换机  ┼───────────────────┐       
           │             │                   │                   │
           │             └───────────────┬───┘                   │                 
           │                             │                       │        
           │                             │                       │       
           │                             │                       │
           │                             │                       │       
           │                             │                       │       
           │                             │                       │       
┌──────────┴───────┐              ┌──────┴─────┐           ┌─────┴──────┐
│                  │              │            │           │            │
│      旁路网关     │              │            │           │            │
│                  │              │ 192.168.1.x│           │192.168.1.y │
│                  │              │            │           │            │
│    192.168.1.254 │              │            │           │            │
└──────────────────┘              └────────────┘           └────────────┘

灵魂绘图 (x

总之是不能再简单的最常见的家庭网络拓扑了...

DHCP 服务器可以用光猫/主路由的, 你也可以关掉光猫/主路由自带的 DHCP 服务器在自己的旁路网关上面跑一个

总之请确保设备通过 DHCP 获取到的地址信息里网关是指向旁路网关的(192.168.1.254), 当然你也可以手动指定

旁路网关设置

思路上和 tproxy + 4层 socket 实现的透明代理方案有一些不同,我这里是 wireguard*一把梭

这样的话旁路网关的配置基本上和路由是一样的,配置路由表特殊流量走 wireguard 的 TUN 接口就可以了。

wg0: flags=80c3<UP,BROADCAST,RUNNING,NOARP,MULTICAST> mtu 1420
        index 4 priority 0 llprio 3
        wgport 1111
        wgpubkey whatever
        groups: wg egress
        inet 10.114.5.14 netmask 0xffffffff

*:wireguard 的协议特征的确很明显,并不适合用来对抗审查。但是目前 ipv6 环境下似乎没有做针对性的检测,所以暂时可以用。相同的方案并不限于 wireguard,您也可以考虑使用其他协议。

# 启动时自动配置 wireguard 接口和路由
# crontab -e 添加在 crontab 里 

@reboot /usr/local/bin/wg setconf wg0 /path/to/wg.conf
@reboot /sbin/route add default 10.114.5.14
# 启用 ip forwarding
wyse# cat /etc/sysctl.conf
net.inet.ip.forwarding=1
# /etc/pf.conf
# 等价于 iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE

pass out on wg0 inet to any nat-to (wg0)

TCP MSS Clamping

当你完成了以上步骤后会发现网络已经可以使用了,但是速度非常慢。

原因在于客户端并不知道网络环境里存在一个旁路网关,所以 MTU 还是 1500,而旁路网关对收到的数据包做封装是有开销的(大约 40-80 bytes),于是旁路网关封装好的包可能会超过链路的 MTU 造成丢包或分片,或者需要网关的 CPU 来拆包重新封装。

为了避免这个问题,这里可以用 pf 来修改 TCP 握手时的 MSS 值

# /etc/pf.conf
# 您可以根据自己的环境调整 MSS 值,如果不确定的话可以使用 1220 (for ipv6 uplinks)
match out on wg0 inet to any scrub (max-mss 1352)

或者,如果你的 DHCP 服务器和客户端均支持 Option 26 的话,也可以在分配地址时为接口的 MTU 设置一个合适的值,MSS 会根据 MTU 自动计算,无需通过 pf 修改。

中国路由直连 (可选)

经典的 chnroute

我用的是 https://github.com/misakaio/chnroutes2

#!/bin/sh

while IFS= read -r line; do
  case "$line" in \#*) continue ;; esac
  route add $line 192.168.1.1
done < $1

chnroute.sh

简单的修改路由的脚本,如果你对上述项目不放心的话可以加一个判断 $line 是否为合法的 ip CIDR

DNS 分流 (可选)

https://github.com/felixonmars/dnsmasq-china-list

这里用 OpenBSD 自带的 unbound 做一下中国域名的分流

# /var/unbound/etc/unbound.conf
# $OpenBSD: unbound.conf,v 1.21 2020/10/28 11:35:58 sthen Exp $

server:
	interface: <your interface>

	access-control: 0.0.0.0/0 allow

	hide-identity: yes
	hide-version: yes

	private-address: ::/0 # filter AAAA

remote-control:
	control-enable: yes
	control-interface: /var/run/unbound.sock

include: /var/unbound/etc/*.unbound.conf # chroot jail 

# Use an upstream forwarder (recursive resolver) for some or all zones.
#
forward-zone:
	name: "."				# use for ALL queries
	forward-addr: 8.8.8.8	
#!/bin/sh
cd /path/to/dnsmasq-china-list && gmake raw

sed -e 's|\(.*\)|forward-zone:\
	  name: "\1."\
	  forward-addr: <china public DNS or your local DNS server>\
	|' /path/to/dnsmasq-china-list/accelerated-domains.china.raw.txt > /path/to/dnsmasq-china-list/accelerated-domains.china.unbound.conf
	
sed -e 's|\(.*\)|forward-zone:\
	  name: "\1."\
	  forward-addr: <china public DNS or your local DNS server>\
	|' /path/to/dnsmasq-china-list/google.china.raw.txt > /path/to/dnsmasq-china-list/google.china.unbound.conf
	
sed -e 's|\(.*\)|forward-zone:\
	  name: "\1."\
	  forward-addr: <china public DNS or your local DNS server>\
	|' /path/to/dnsmasq-china-list/apple.china.raw.txt > /path/to/dnsmasq-china-list/apple.china.unbound.conf

unbound.sh

这里有一点需要注意一下,BSD 的 make 还有 sed 和 GNU 的不太一样,请不要用项目自带的 Makefile 来生成 unbound 配置文件,因为 BSD 的 sed 不认识 \n...需要写成 \[换行]

自动更新 (可选)

# /etc/daily.local
cd /path/to/dnsmasq-china-list && git pull && sh /path/to/unbound.sh && cp /path/to/dnsmasq-china-list/*.unbound.conf /var/unbound/etc && unbound-control reload
cd /path/to/chnroutes2 && git pull && sh /path/to/chnroute.sh /path/to/chnroutes2/chnroutes.txt