大家好,好久不见,我是某昨。
国庆前总算是下定决心要好好整治一下现在的路由了。以笔记本为核心的代理网络虽然携带方便,但对各种设备的支持实在是算不上完全。笔记本的无线网卡限制太大(信道必须为当前连接 WiFi
的信道,并且不在限制范围内),并且因为笔记本占据了有线接口,因此其他设备就没有使用有线网络的可能了。
新的网络配置使用了一台 TPLink
的 TL-SG2008D
交换机实现单臂路由,树莓派 4B
作软路由,systemd-networkd
管理网络接口,chinadns-ng
配合 dnsmasq
解析 DNS
,ipt2socks
配合 v2ray
实现透明代理,nftables
配置透明代理的规则并劫持局域网的 DNS
请求至路由器的本地 DNS
服务器。
ToC
环境分析
待接入的网络环境为 IPv4+IPv6
双栈。其中 IPv4
有 MAC
地址白名单,且需要经过 Drcom
认证才能访问公网;IPv6
为 SLAAC
,自动下发一段 /64
的 IPv6
地址。IPv4
有出口限速 100M
;IPv6
不限制,能够跑到宿舍交换机的上限千兆。
IPv4
这边没什么好说的,肯定是要 NAT
了,问题在 IPv6
。由于下发的地址是 /64
,因此简单的 SLAAC
配置就不可能了;而 Android
设备又明确表示不支持 DHCPv6
。因此留给我们的只有两条路:要么透传(Passthrough
),要么中继。这里我选择了 Passthrough
。
配置:交换机
由于树莓派只有一个网口,因此我们需要通过 VLAN
的方式使这个网口同时作为 WAN
口和 LAN
口工作。因此我们划分两个 VLAN
:VLAN1
作 WAN
,VLAN2
作 LAN
。端口 1 接外部网线,端口 2 接树莓派,端口 3-8 接局域网内的设备,配置如下图所示:
先看 VLAN1
。没有 Tag
的外部流量从端口 1 进入,被打上端口1的 PVID=1
,发送到 Tagged
的端口 2;端口 2 的流量发出后流向端口 1,由于端口 1 是 Untagged
因此抹除 Tag
发送出去。进出流量都可以正常运转。
再看 VLAN2
。端口 2 发出的报文需要带有对应的 VLAN ID
,因此端口 2 是 Tagged
;其他端口都不需要让网络使用者知道其对应的 VLAN
,因此是 Untagged
。当流量从端口 3-8 进入时,自动打上 PVID=2
,并被发送到端口 2;当流量从端口 2 流出时,同样地会被正常转发到实际的端口。
至此,交换机部分的配置就基本完成了。
安装:树莓派
由于是全新的树莓派,因此需要安装系统。我这里选择的是 ArchLinux ARM
,官网对树莓派的安装有详细教程,这里就不展开了。
配置:网络接口
由于划分了两个 VLAN
,因此对应的就需要两个 vlan
的网口。同时为了实现 IPv6
的 Passthrough
,我们需要将它们桥接在一起。最后形成的配置如下:
eth0
从 eth0
分出两个 VLAN
接口,分别为 eth0.1
和 eth0.2
。
eth0.1
第一个 VLAN
网络设备,VLAN ID=1
。
eth0.1
的网络配置。可以看到它是桥接入 br-lan
的,并且分配了静态的 IP
地址、网关和 DNS
。
eth0.2
第二个网络设备,VLAN ID=2
。
这里给它手动分配了一个 MAC
地址,否则 VLAN
设备的 MAC
地址会和其 Parent
设备,即 eth0
保持一致。MAC
地址的配置
MACAddress
支持 :
分隔、-
分隔和点分隔三种形式。注意这个地址必须是有效的,否则端口是起不来的,同时 journalctl -xe
里会有 Failed to set MAC address, ignoring: Cannot assign requested address
的报错。
eth0.2
的网络配置,相当简单,不需要有网络地址,只作交换用。
br-lan
网络设备 br-lan
,负责桥接起所有的网口。
br-lan
的网络配置,Address
部分填写内网的网段。我这里选用的内网网段是 10.245.0.1/16
。
ebtables
将 eth0.1
加入 br-lan
后,局域网内的设备就可以直接获取到 IPv6
的公网地址了。但 IPv4
的访问也因此中断。因此我们需要让 IPv4
不要被桥接出去,走正常的路由;仅 IPv6
桥接就可以了。
由于 nftables
暂时不支持 ebtables
的 broute
表ebtables
。但 archlinux-arm
并没有提供对应的包,因此这里就需要我们手动构建了。首先是安装 base-devel
:
然后从 AUR
上找到对应的包:https://aur.archlinux.org/packages/ebtables:
下载完之后需要修改 PKGBUILD
,在 arch
中增加 armv7h
,然后就可以构建了:
如此就安装完成了。
我们可以把 ebtables
的配置写成简单的 systemd
服务,如下所示:
然后运行并开机启动就可以了。
效果
br-lan
拥有内网的 IPv4
地址和公网 2001
开头的 IPv6
地址,eth0.1
拥有公网 IPv4
地址。双栈都可以正常访问。
安装:MariaDB
首先是安装:
然后是初始化:
初始化完成后会自动生成两个账户:root
和 mysql
。这两个账户都没有密码,但需要登录时是对应的用户才可以正常访问。随后,我们配置一些必要的安全选项:
最后启动即可:
配置:DHCP
我们选择通过 kea
配置 DHCP
。首先是安装:
配置数据库(可选)
如果我们想使用 mariadb
替代 memfile
,就需要在数据库里新建一张表和一个用户供 kea
使用:
然后通过 kea-admin
初始化:
配置文件
然后编辑 kea-dhcp4
的配置文件:
最后启动服务:
配置:无线网络
我们选用 hostapd
,使用树莓派的板载无线网卡开启 AP
。首先是安装,直接通过 pacman
安装即可:
配置如下:
那堆 wmm
相关的配置在安装自带的配置文件里没有注释掉,因此这里我也保留了。这里有个坑点是树莓派的板载网卡不支持 ACS
,即自动信道探测。
可以看到,AP
接入的方式是 bridge
进了 br-lan
,因此可以直接从 br-lan
的 DHCP
获取到 IPv4
地址;能够直接通过 SLAAC
获取到公网 IPv6
。配置完成后直接启动 hostapd
就可以了:
配置:透明代理
至此,有线网络和无线网络都可以正常工作了,于是问题就只剩下透明代理了。透明代理的实现有两个最重要的问题:DNS
和代理方式。
DNS
我这里选择的是 chinadns-ng+dnsmasq
的方案。黑白名单的设计与我理想中的 DNS
获取方式完全契合,dnsmasq
则在缓存的同时能够给自定义解析域名,对内网服务的部署是一大助力。
剩下的问题就是代理方式了。我选择的方案是 ipt2socks
配合 v2
的本地 socks5
服务器。选用 ipt2socks
是为了和 v2
的实现解耦,v2
则是简单地作代理用,外加给自己的流量打上 SO_MARK
。
另一个相对次要的问题就是 IPv6
。IPv6
的透明代理看上去就很奇怪,并且也不是所有的代理服务器都支持 IPv6
,因此我这里选择仅透明代理 IPv4
。仅透明代理 IPv4
就意味着我们需要尽可能地让应用不走 IPv6
,因此我选择在 DNS
上下手,拦截所有的 AAAA
返回,这样就只有 IP
直连的情况下才能使用 IPv6
网络了。
设置了这么多限制,那当初配置 IPv6
网络的意义又在哪里呢?意义当然还是有的。首先是 PT
,PT
是直接走 IP
的,因此不会受到 DNS
的影响,可以正常运作,这也就意味着 BT
客户端能够正确获取并上报自己的 IPv6
公网 IP
,实现点对点的传输;此外,针对实际需要 IPv6
的服务,我们也可以在 dnsmasq
中指定对应的解析。
chinadns-ng
将仓库 clone
到本地,并通过 make
和 make install
构建安装:
然后新建一个 systemd
的 service
:
这里可以看到我们的命令参数。其中 -c
对应的是国内 DNS
服务器,-t
对应的是国外(可信)DNS
服务器,-g
对应的是黑名单,-m
是白名单,-N
则是不返回 IPv6
的解析结果。
chinadns-ng
还需要用到 ipset
,因此需要提前导入:
为了能够持久化对应的 ipset
,我们需要将 ipset
写入 /etc/ipset.conf
,然后启动 ipset.service
:
注意这里不要 start
这个 ipset.service
,因为对应的 set
已经存在了,start
是必定会失败的。只需要 enable
即可,下次启动时就正常了。
一切部署完毕,启动 chinadns-ng
:
它会默认监听 127.0.0.1
的 65353
端口。
systemd-resolved
在配置 dnsmasq
之前,我们需要先把搅局的 resolved
解决。编辑 /etc/systemd/resolved.conf
:
然后重启 systemd-resolved.service
,它就不会监听 127.0.0.53:53
了:
dnsmasq
解决了 resolved
,接下来就是 dnsmasq
了。dnsmasq
的存在是为了实现 DNS
缓存和自定义解析,可以直接通过 pacman
直接安装:
安装完后修改配置文件:
然后建立 /etc/dnsmasq.d
目录:
最后将 chinadns-ng
的服务器写入额外配置:
并启动即可:
v2
这里给出一个最简单的配置,监听的是 127.0.0.1:1080
。所有传入流量都会走代理,除了 BT
流量会直连。所有的传出流量都带有值为 0xff
的 SO_MARK
。
ipt2socks
这里就直接参考 KAAAsS
的配置了,详细的解释在对应文章里有说明。
nftables
最后就是 nftables
的配置了。不过在此之前,还需要执行 iproute
的命令:
这两条已经是我们的老朋友了,不用多说。下面就是正片:
这个配置参照了 KAAAsS
的配置以及新白话文教程中对应的 nftables
部分。核心思想和 iptables
版本的一样,都是在 Prerouting
部分下手。对于本机发出的包,首先经过 $direct_fwmark
的判断,放行这部分直连;对于没有 $direct_fwmark
且 saddr
为本地、daddr
不为本地的 TCP
、UDP
包执行规则路由(跳转到 doproxy
链)。在 doproxy
链中对需要代理的包会进行一次 mark
,使其来到 Prerouting
链。
对局域网的包而言,我们首先放行了本地发出且不带 $lo_fwmark
的包,然后放行了局域网传来的所有 DNS
包(saddr != local
的 udp dport $dns_port
包),接下来对局域网传来的非本机发出、非本机目的地的包进行规则路由,最后将带有 $lo_fwmark
的包送到了 TProxy
的端口。
对规则路由而言,其判断的本质就是 chnroot
和 privateip_addr
两个 set
。当 daddr
在这两个 set
中时,是无条件走直连的。此外,为了减少匹配次数,这里前后还使用了 conntrack
。meta mark set ct mark
中的 ct
就是 conntrack
的缩写,这句等效于 skb->mark = nf_conn->mark
,意思是说给数据包标记上 conntrack
中的 mark
。如果这个包是之前连接的一部分,那么在这一步中就能从之前的连接中把 mark
恢复,后面的步骤就不需要对其进行标记了,减少了标记的次数。配合这条规则的是最后的 ct mark set mark
,等效 nf_conn->mark = skb->mark
,将当前包的 mark
存储到 nf_conn
中供之后复用。打上 mark
的时机也有讲究,是对 flag
含 SYN
的 TCP
包和 state new
的 UDP
包进行。
最后就是拦截局域网 DNS
了。在 Prerouting
里我们选择放行局域网的 DNS
包,而在此之后我们把这个包 redirect
到 :$dns_port
,即本地的 DNS
服务,这样就可以确保所有去往 :53
的包都由本机的 dnsmasq
处理了。
上述的规则中引用了两个文件,一个是 chnroute.ruleset
,这个文件是通过 chinadns-ng
的 chnroute.ipset
转换过来的,由于原文过长,这里就不贴出来了,内容格式如下:
然后是 private.ruleset
,记录了 IPv4
中的所有私有地址:
最后,为了能够快捷地开关透明代理,我们将其部署成 systemd
的 service
。需要准备的文件如下:
拜 nftables
的配置所赐,在结束的时候我们只需要直接对我们创建的 table
进行一个删除就可以了。
这里需要注意的是 transparent-proxy.service
必须在 systemd-networkd.service
之后启动,并且要跟随 systemd-networkd
一起开启/关闭(PartOf
)。配置完后启动即可:
至此,最后一片拼图也完成了。
结语
终于,我们得到了一个能用的软路由。它可以完美地完成原本由笔记本提供的代理功能,自带的无线网卡虽然表现一般但也能有百兆的速度,一切都正常地运转起来了。
但现在的配置还是有一些令人不大满意的地方,比如:
chinadns-ng
使用了ipset
,但我们的配置使用了nftables
的set
,需要维护两份列表chinadns-ng
的filter ipv6
模式会拦截所有的ipv6
,我们希望能够放行国内域名的ipv6
解析IPv4
路由使用的是ebtables
的broute
表,该filter
目前nftables
尚未支持- 现在公网
IPv4
在eth0.1
上,公网IPv6
在br-lan
上,显得十分诡异 - 等等……
这些问题或许会在后续的使用中慢慢解决,或许不会(
总之,能用万岁!(笑)
参考
- https://archlinuxarm.org/platforms/armv8/broadcom/raspberry-pi-4
- https://wiki.archlinux.org/title/MariaDB\_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)
- https://github.com/zfl9/chinadns-ng
- https://wiki.archlinux.org/title/Ipset\_(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)#%E4%BD%BFipset%E6%8C%81%E4%B9%85%E5%8C%96
- https://blog.kaaass.net/archives/1446
- https://github.com/kaaass/manjaro-settings/blob/master/home/kaaass/shell/transparent\_proxy.sh