我的家庭网络架构

前言

很久没写博客了,最近正好有空可以谈谈我最近更新的家庭网络架构。我所有的网络组件都部署在一台 J1900 的机器上,安装的操作系统是 debian 10,主板配有四个网口。我将其中三个网口合并为一个 bridge 用来构建我的私有子网,供主机和路由器插线连接,另一个网口连接光猫作为出口。

科学上网

通过在软路由上使用 iptables + tproxy 重定向 TCP/UDP 流量到 clash,可以实现全局翻墙,所有设备无需设置。在讨论我遇到的问题之前,有必要了解一下 clash 在软路由上怎么进行规则判断的,换言之 clash 怎么知道我们当前的连接是否需要走代理。

DNS

软路由上的流量被重定向后,clash 只能获取的目标 ip,但是根据 ip 地区判断是远远不够的,那么该如何获取 ip 对应的域名呢?clash 自带了一个 DNS server,在软路由上将所有 DNS 请求转发给它,那么它就能记录域名和 ip 的对应关系。但是由于存在许多问题,这种 redir-host 模式在较新的 clash 中被删除了。它最大的问题就是无法应对 DNS 污染,例如最上游 DNS 被污染时,返回 googleip127.0.0.1 这种,那么该请求直接在主机上请求失败了。

FakeIP

在使用 redir-host 的过程中,我经常遇到网络不正常的情况,迫于无奈只能切换到 fake-ip 模式。它的原理就是 clashDNS server 返回一个虚假的内网 ip,例如 198.168.0.0/16,同时记录 iphostname 的对应关系,这样就可以避免 DNS 污染的问题。当然 fake-ip 也有缺陷,例如 QQ 的某些域名指向就是 127.0.0.1,因为需要和 QQ 客户端通信,另外 Windows 的网络探测服务也无法在 fake-ip 模式下工作。对于这些特殊的域名,我们可以在 clash 配置中过滤掉,让它们返回真实的查询结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
dns:
enable: true
ipv6: false
listen: 0.0.0.0:5300
enhanced-mode: fake-ip
fake-ip-range: 198.18.0.1/16
nameserver:
- https://doh.pub/dns-query
- https://dns.alidns.com/dns-query
fake-ip-filter:
- '*.lan'
- 'wspeed.qq.com'
- 'internal-api-lark-file.feishu.cn'
- 'jrlt.beacon.qq.com'
- 'localhost.ptlogin2.qq.com'
- '+.srv.nintendo.net'
- '+.stun.playstation.net'
- '+.msftconnecttest.com'
- '+.msftncsi.com'
- '+.xboxlive.com'
- 'msftconnecttest.com'
- 'xbox.*.microsoft.com'
- '*.battlenet.com.cn'
- '*.battlenet.com'
- '*.blzstatic.cn'
- '*.battle.net'

游戏加速

游戏加速说到底还是代理,只不过是延迟很低的线路,使用小米等路由器同时开启翻墙和游戏加速插件肯定会有冲突,但是在自己配置的 Linux 路由器里,我可以精准地控制每一条规则。

docker

一年前我的方案是将 openwrt 版本的 UU加速器 跑在 docker 里,它其实是一个为主机加速提供的方案,所以我写了个 python 脚本将我的软路由伪装成 PS4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
import socket

UDP_IP = "0.0.0.0"
UDP_PORT = 987

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))

while True:
data, addr = sock.recvfrom(1024)
print("received message from %s[%d]: %s" % (addr[0], addr[1], data.decode('utf8')))

response = "HTTP/1.1 200 OK\r\nhost-id:0123456789AB\r\nhost-type:PS4\r\nhost-name:MyPS4\r\nhost-request-port:%d\r\ndevice-discovery-protocol-version:00020020\r\nsystem-version:07020001\r\nrunning-app-name:Youtube\r\nrunning-app-titleid:CUSA01116\r\n\r\n" % addr[1]
sock.sendto(response.encode('utf8'), addr)

想象一下,UU加速器 在容器 172.17.0.3 中广播 UDP 包,然后这个脚本在软路由的 172.17.0.1 上收到了这个包,并成功回复让对方认为自己是一台 PS4。然后在手机 APP 上就可以开启加速,这个时候我们只需要在软路由上将需要加速的包路由给 172.17.0.3 即可,流量会顺利地从容器的 tun 网卡进入 UU加速器 的专线。

1
2
3
# https://github.com/Hackerl/docker-uuplugin
docker build -t docker-uuplugin . --build-arg UU_IP=172.17.0.3
docker run -d -p 16363:16363 --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun -it docker-uuplugin

启动后添加路由规则:

1
2
ip rule add fwmark 0x163 table 0x163
ip route add default via 172.17.0.3 table 0x163

这个路由规则的意思是如果流量带有 0x163 标志,那么就路由给 172.17.0.3,接着我们可以在 iptables 里给特定流量打上标志:

1
2
3
4
5
6
iptables -t mangle -N UUPLUGIN
iptables -t mangle -A UUPLUGIN -p udp -s 192.168.10.0/24 --dport 32768:65534 -j MARK --set-mark 0x163
iptables -t mangle -A UUPLUGIN -p udp -s 192.168.10.0/24 --dport 10000:32768 -j MARK --set-mark 0x163
iptables -t mangle -A UUPLUGIN -p udp -s 192.168.10.0/24 --dport 1025:9999 -j MARK --set-mark 0x163
iptables -t mangle -A UUPLUGIN -m mark --mark 0x163 -j ACCEPT
iptables -t mangle -A PREROUTING -j UUPLUGIN

例如我们给 UDP 高端口号的包打上标志,这其实是大多数网络游戏的通讯方式,例如英雄联盟。

虚拟机

使用 docker 运行 UU加速器 进行游戏加速也存在问题,一就是可选择的节点并不多,毕竟这是主机加速方案,二就是它的 iptables 限制了只代理某些源 ip。由于第二个限制,我们没有办法直接在它的容器中部署 socks5 代理,以集成到 clash 规则中,当然我可以修改它的规则,但是每次启动加速都要修改的话太麻烦了。此时我有了一个大胆的想法,我可不可以在软路由上跑一个 windows 虚拟机,然后将流量转发到虚拟机中去加速,一想到 J1900 廉价的性能我心里就充满了问号。
我首先安装了一个 windows 7 虚拟机,连上 VNC 后果然很卡,然后部署了一个简单的 socks5 代理,接着将英雄联盟台服的域名全部抠出来,写好 clash 规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
script:
engine: expr
shortcuts:
game: network == 'udp' and dst_port >= 1025

rules:
- SCRIPT,game,GAME
- DOMAIN,lolstatic-a.akamaihd.net,GAME
- DOMAIN-KEYWORD,riot,GAME
- DOMAIN-SUFFIX,pvp.net,GAME
- DOMAIN-SUFFIX,leagueoflegends.com,GAME
- DOMAIN-SUFFIX,newrelic.com,GAME
- DOMAIN,lolesports.com,GAME

尝试了一下确实可以工作,所有英雄联盟相关的流量都由虚拟机代理。那么接下来要做的就是怎么把这台虚拟机压缩一下,J1900 的确有点带不动,而且为了一个游戏加速我也并不是很乐意耗费这么多 CPU 和内存。我在网上找到了一个 Windows 7 Super-Nano Lite,镜像居然只有 316MB,确定这可以跑起来吗?我安装了一下确实跑起来了,但是包括网络驱动之类的东西全部被删除了。

磁盘占用只有 600MB,内存也只用了 200MB,为了进一步优化性能,我使用 virt-install 安装虚拟机时设置的网络和磁盘驱动都是 virtio

1
2
wget https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.173-4/virtio-win-0.1.173_x86.vfd
sudo virt-install --name=win7 --virt-type kvm --memory 1024 --vcpus=1 --os-type=windows --os-variant win7 --disk path=win7.qcow2,format=qcow2,bus=virtio --cdrom Windows7SuperNanoLite.iso --graphics vnc,listen=0.0.0.0 --network default,model=virtio --disk path=virtio-win-0.1.173_x86.vfd,device=floppy --noautoconsole

在使用 VNC 安装的时候会提示找不到驱动器,因为 windows 7 无法识别 virtio 设备,所有在界面上点击加载驱动,加载 virtio-win-0.1.173_x86.vfd 软盘中的磁盘和网络驱动。安装后的系统是无法显示中文的,因为中文包也被删除了。找一台完整的 windows 7 机器,拷贝 C:\Windows\System32\C_936.NLS 以及 C:\Windows\Fonts\msyh.ttf 到虚拟机中,中文就不会乱码了。安装完加速器后,需要部署一个支持 TCP/UDPsocks5 代理,我找了一圈发现没几个支持 UDP 的,最后选择了 3proxy,配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/local/bin/3proxy

system "echo 3proxy up!"

########### SERVICE ###########
# Set timeouts
timeouts 1 5 30 60 180 1800 15 60

# Service installation, daemon for nix, service for win32
#daemon
service

########### LOGGING ###########
# Set up logs
#log "/var/logs/3proxy/%Y%m%d.log" D
log "3proxy-%Y%m%d.log" D
logformat "- +_L%t.%. %Y-%m-%d %N.%p %E %U %C:%c %R:%r %O %I %h %T"
archiver rar rar a -df -inul %A %F
rotate 30

########### IFACE ###########
# External is the interface you will send data out from, set with a static IP
external 0.0.0.0
# Internal is the interface you will listen on, in this case localhost (no physical nic)
internal 0.0.0.0

########### SOCKS ###########
# Socks5 proxy setup
auth none
flush
maxconn 300
socks

搞完这个之后在英雄联盟台服开了把大乱斗,延迟居然才 30 比国服还低,看了下软路由的资源占用,虚拟机低负载时 qemu 进程稳定占用 45% 左右的单核 CPU,算是可以接受的范围,毕竟它只是 J1900 啊!有了这个比较稳定的虚拟方案后,其实可以将一些 windows 独占的软件放到里面,通过软路由上的 smb 共享文件,但是我现在使用的百度云、迅雷都是使用的 docker + noVNC 方案,暂时没有其余想折腾的东西了。