大家好,好久不见,我是某昨。
国庆前总算是下定决心要好好整治一下现在的路由了。以笔记本为核心的代理网络虽然携带方便,但对各种设备的支持实在是算不上完全。笔记本的无线网卡限制太大(信道必须为当前连接 WiFi
的信道,并且不在限制范围内),并且因为笔记本占据了有线接口,因此其他设备就没有使用有线网络的可能了。
新的网络配置使用了一台 TPLink
的 TL-SG2008D
交换机实现单臂路由,树莓派 4B
作软路由,systemd-networkd
管理网络接口,chinadns-ng
配合 dnsmasq
解析 DNS
,ipt2socks
配合 v2ray
实现透明代理,nftables
配置透明代理的规则并劫持局域网的 DNS
请求至路由器的本地 DNS
服务器。
环境分析
待接入的网络环境为 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
[Match]
Name=eth0
[Network]
VLAN=eth0.1
VLAN=eth0.2
从 eth0
分出两个 VLAN
接口,分别为 eth0.1
和 eth0.2
。
eth0.1
[NetDev]
Name=eth0.1
Kind=vlan
[VLAN]
Id=1
第一个 VLAN
网络设备,VLAN ID=1
。
[Match]
Name=eth0.1
[Network]
Bridge=br-lan
Address=49.140.123.234/24
Gateway=49.140.123.254
DNS=127.0.0.1
eth0.1
的网络配置。可以看到它是桥接入 br-lan
的,并且分配了静态的 IP
地址、网关和 DNS
。
eth0.2
[NetDev]
Name=eth0.2
Kind=vlan
MACAddress=2a-62-d5-f6-e8-7f
[VLAN]
Id=2
第二个网络设备,VLAN ID=2
。
这里给它手动分配了一个 MAC
地址,否则 VLAN
设备的 MAC
地址会和其 Parent
设备,即 eth0
保持一致。MAC
地址的配置理论上不是必须的。但我之前配了就懒得动了(
MACAddress
支持 :
分隔、-
分隔和点分隔三种形式。注意这个地址必须是有效的,否则端口是起不来的,同时 journalctl -xe
里会有 Failed to set MAC address, ignoring: Cannot assign requested address
的报错。
[Match]
Name=eth0.2
[Network]
Bridge=br-lan
eth0.2
的网络配置,相当简单,不需要有网络地址,只作交换用。
br-lan
[NetDev]
Name=br-lan
Kind=bridge
网络设备 br-lan
,负责桥接起所有的网口。
[Match]
Name=br-lan
[Network]
IPMasquerade=ipv4
[Address]
Address=10.245.0.1/16
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
:
pacman -S base-devel
然后从 AUR
上找到对应的包:https://aur.archlinux.org/packages/ebtables:
git clone https://aur.archlinux.org/ebtables.git
cd ebtables
下载完之后需要修改 PKGBUILD
,在 arch
中增加 armv7h
,然后就可以构建了:
makepkg -si .
如此就安装完成了。
我们可以把 ebtables
的配置写成简单的 systemd
服务,如下所示:
[Unit]
After=network.target
Wants=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/ebtables -t broute -A BROUTING -p ! ipv6 -j DROP -i eth0.1
ExecStop=/usr/bin/ebtables -t broute -D BROUTING -p ! ipv6 -j DROP -i eth0.1
[Install]
WantedBy=multi-user.target
然后运行并开机启动就可以了。
systemctl start route-ipv4
systemctl enable route-ipv4
效果
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether [redacted] brd ff:ff:ff:ff:ff:ff
inet6 fe80::[redacted]/64 scope link
valid_lft forever preferred_lft forever
3: br-lan: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether [redacted] brd ff:ff:ff:ff:ff:ff
inet 10.245.0.1/16 brd 10.245.255.255 scope global br-lan
valid_lft forever preferred_lft forever
inet6 2001:[redacted]/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 2591981sec preferred_lft 604781sec
inet6 fe80::[redacted]/64 scope link
valid_lft forever preferred_lft forever
4: eth0.1@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-lan state UP group default qlen 1000
link/ether [redacted] brd ff:ff:ff:ff:ff:ff
inet 49.[redacted]/24 brd 49.[redacted] scope global eth0.1
valid_lft forever preferred_lft forever
5: eth0.2@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-lan state UP group default qlen 1000
link/ether [redacted] brd ff:ff:ff:ff:ff:ff
6: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master br-lan state UP group default qlen 1000
link/ether [redacted] brd ff:ff:ff:ff:ff:ff
inet6 fe80::[redacted]/64 scope link
valid_lft forever preferred_lft forever
从没有被 [redacted] 的部分可以看到,br-lan
拥有内网的 IPv4
地址和公网 2001
开头的 IPv6
地址,eth0.1
拥有公网 IPv4
地址。双栈都可以正常访问。
安装:MariaDB
首先是安装:
pacman -S mariadb
然后是初始化:
mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql
初始化完成后会自动生成两个账户:root
和 mysql
。这两个账户都没有密码,但需要登录时是对应的用户才可以正常访问。随后,我们配置一些必要的安全选项:
mysql_secure_installation
最后启动即可:
systemctl start mariadb
systemctl enable mariadb
配置:DHCP
我们选择通过 kea
配置 DHCP
。首先是安装:
pacman -S kea
配置数据库(可选)
如果我们想使用 mariadb
替代 memfile
,就需要在数据库里新建一张表和一个用户供 kea
使用:
CREATE DATABASE kea;
CREATE USER 'kea'@'localhost' IDENTIFIED BY 'kea';
GRANT ALL ON kea.* TO 'kea'@'localhost';
quit
然后通过 kea-admin
初始化:
kea-admin db-init mysql -u kea -p kea -n kea
配置文件
然后编辑 kea-dhcp4
的配置文件:
{
"Dhcp4": {
"interfaces-config": {
// 需要监听的端口是 br-lan
"interfaces": [ "br-lan" ],
"dhcp-socket-type": "raw"
},
"valid-lifetime": 3600,
"renew-timer": 900,
"rebind-timer": 1800,
"subnet4": [
{
// 分配网段
"subnet": "10.245.0.0/24",
// IP 池
"pools": [ { "pool": "10.245.0.2 - 10.245.0.254" } ],
"option-data": [
{
// 默认网关
"name": "routers",
"data": "10.245.0.1"
},
{
// 默认 DNS
"name": "domain-name-servers",
"data": "10.245.0.1"
}
]
}
],
"control-socket": {
"socket-type": "unix",
"socket-name": "/tmp/kea4-ctrl-socket"
},
"lease-database": {
"type": "memfile",
"lfc-interval": 3600
},
// 如果上面使用了数据库,这里可以取消注释
//"hosts-database": {
// "type": "mysql",
// "name": "kea",
// "user": "kea",
// "password": "kea",
// "host": "localhost",
// "port": 3306
//},
"option-data": [
{
"name": "domain-search",
"data": "mmf.lan, lan.mmf.moe"
}
],
"loggers": [{
"name": "kea-dhcp4",
"output_options": [
{
"output": "/var/log/kea-dhcp4.log"
}
],
"severity": "INFO",
"debuglevel": 0
}]
}
}
最后启动服务:
systemctl start kea-dhcp4
systemctl enable kea-dhcp4
配置:无线网络
我们选用 hostapd
,使用树莓派的板载无线网卡开启 AP
。首先是安装,直接通过 pacman
安装即可:
pacman -S hostapd
配置如下:
interface=wlan0
bridge=br-lan
driver=nl80211
own_ip_addr=127.0.0.1
ctrl_interface=/run/hostapd
ctrl_interface_group=0
#
# 基本信息
#
ssid=SSID Of Your WiFi
wpa_passphrase=Your password
# 国家代码
country_code=CN
# 信道为 40,5200 MHz,在中国和日本的信道重合范围内
channel=40
# IEEE 802.11a
hw_mode=a
ieee80211n=1
ht_capab=[HT40+][SHORT-GI-40][DSSS_CCK-40]
ieee80211ac=1
#
# WPA2 认证配置
#
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
# 1=WPA
auth_algs=1
#
# 日志
#
logger_syslog=-1
logger_syslog_level=2
logger_stdout=-1
logger_stdout_level=2
#
# MAC 地址黑名单
#
macaddr_acl=0
deny_mac_file=/etc/hostapd/hostapd.deny
#
# WMM 相关配置
#
wmm_enabled=1
wmm_ac_bk_cwmin=4
wmm_ac_bk_cwmax=10
wmm_ac_bk_aifs=7
wmm_ac_bk_txop_limit=0
wmm_ac_bk_acm=0
wmm_ac_be_aifs=3
wmm_ac_be_cwmin=4
wmm_ac_be_cwmax=10
wmm_ac_be_txop_limit=0
wmm_ac_be_acm=0
wmm_ac_vi_aifs=2
wmm_ac_vi_cwmin=3
wmm_ac_vi_cwmax=4
wmm_ac_vi_txop_limit=94
wmm_ac_vi_acm=0
wmm_ac_vo_aifs=2
wmm_ac_vo_cwmin=2
wmm_ac_vo_cwmax=3
wmm_ac_vo_txop_limit=47
wmm_ac_vo_acm=0
那堆 wmm
相关的配置在安装自带的配置文件里没有注释掉,因此这里我也保留了。这里有个坑点是树莓派的板载网卡不支持 ACS
,即自动信道探测。这也是我试了好几遍才得出的结论(
可以看到,AP
接入的方式是 bridge
进了 br-lan
,因此可以直接从 br-lan
的 DHCP
获取到 IPv4
地址;能够直接通过 SLAAC
获取到公网 IPv6
。配置完成后直接启动 hostapd
就可以了:
systemctl start hostapd
systemctl enable 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
构建安装:
git clone https://github.com/zfl9/chinadns-ng
cd chinadns-ng
make
sudo make install
然后新建一个 systemd
的 service
:
[Unit]
After=network-online.target
[Service]
Type=simple
User=nobody
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
ExecStart=/usr/local/bin/chinadns-ng -c 10.10.10.10,223.5.5.5 -t 1.0.0.1,8.8.4.4 -g /srv/proxy/chinadns-ng/gfwlist.txt -m /srv/proxy/chinadns-ng/chnlist.txt -N
[Install]
WantedBy=multi-user.target
这里可以看到我们的命令参数。其中 -c
对应的是国内 DNS
服务器,-t
对应的是国外(可信)DNS
服务器,-g
对应的是黑名单,-m
是白名单,-N
则是不返回 IPv6
的解析结果。
chinadns-ng
还需要用到 ipset
,因此需要提前导入:
ipset -R <chnroute.ipset
ipset -R <chnroute6.ipset
为了能够持久化对应的 ipset
,我们需要将 ipset
写入 /etc/ipset.conf
,然后启动 ipset.service
:
ipset save > /etc/ipset.conf
systemctl enable ipset
注意这里不要 start
这个 ipset.service
,因为对应的 set
已经存在了,start
是必定会失败的。只需要 enable
即可,下次启动时就正常了。
一切部署完毕,启动 chinadns-ng
:
systemctl start chinadns-ng
systemctl enable chinadns-ng
它会默认监听 127.0.0.1
的 65353
端口。
systemd-resolved
在配置 dnsmasq
之前,我们需要先把搅局的 resolved
解决。编辑 /etc/systemd/resolved.conf
:
[Resolve]
DNSStubListener=no
然后重启 systemd-resolved.service
,它就不会监听 127.0.0.53:53
了:
systemctl restart systemd-resolved
dnsmasq
解决了 resolved
,接下来就是 dnsmasq
了。dnsmasq
的存在是为了实现 DNS
缓存和自定义解析,可以直接通过 pacman
直接安装:
pacman -S dnsmasq
安装完后修改配置文件:
# 监听 53 端口
port=53
# 禁用 /etc/resolv.conf
no-resolv
no-poll
# 监听内网
listen-address=127.0.0.1,10.245.0.1
# 指定额外配置文件夹
conf-dir=/etc/dnsmasq.d/
然后建立 /etc/dnsmasq.d
目录:
mkdir /etc/dnsmasq.d
最后将 chinadns-ng
的服务器写入额外配置:
server=127.0.0.1#65353
并启动即可:
systemctl start dnsmasq
systemctl enable dnsmasq
v2
这里给出一个最简单的配置,监听的是 127.0.0.1:1080
。所有传入流量都会走代理,除了 BT
流量会直连。所有的传出流量都带有值为 0xff
的 SO_MARK
。
{
"log": {
"loglevel": "error"
},
"inbounds": [
{
"port": 1080,
"listen": "127.0.0.1",
"protocol": "socks",
"sniffing": {
"enabled": true,
"destOverride": [
"http",
"tls"
]
},
"settings": {
"auth": "noauth",
"udp": true
}
}
],
"outbounds": [
{
"tag": "proxy",
"protocol": "vmess",
"settings": {
"vnext": [
{
"address": "your-domain",
"port": 443,
"users": [
{
"id": "your-uuid",
"alterId": 64
}
]
}
]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"wsSettings": {
"path": "/your-path"
},
"sockopt": {
"mark": 255
}
}
},
{
"tag": "direct",
"protocol": "freedom",
"settings": {
"domainStrategy": "UseIP"
},
"streamSettings": {
"sockopt": {
"mark": 255
}
}
}
],
"routing": {
"domainStrategy": "IPOnDemand",
"rules": [
{
"type": "field",
"protocol": [
"bittorrent"
],
"outboundTag": "direct"
}
]
}
}
ipt2socks
这里就直接参考 KAAAsS
的配置了,详细的解释在对应文章里有说明。
[Unit]
Description=utility for converting iptables(redirect/tproxy) to socks5
After=network.target
[Service]
User=nobody
EnvironmentFile=/usr/local/etc/ipt2socks/ipt2socks.conf
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/usr/local/bin/ipt2socks -s $server_addr -p $server_port -l $listen_port -j $thread_nums $extra_args
Restart=on-failure
RestartSec=5
LimitNOFILE=20480
[Install]
WantedBy=multi-user.target
# ipt2socks configure file
#
# detailed helps could be found at: https://github.com/zfl9/ipt2socks
# Socks5 server ip
server_addr=127.0.0.1
# Socks5 server port
server_port=1080
# Listen port number
listen_port=60080
# Number of the worker threads
thread_nums=1
# Extra arguments
extra_args=
nftables
最后就是 nftables
的配置了。不过在此之前,还需要执行 iproute
的命令:
ip -4 rule add fwmark 1 table 100
ip -4 route add local default dev lo table 100
这两条已经是我们的老朋友了,不用多说。下面就是正片:
define lo_fwmark = 1
define tproxy_port = 60080
define dns_port = 53
define direct_fwmark = 0xff
#
# 新建表
#
add table proxy
#
# 国内路由
#
include "/srv/proxy/config/chnroute.ruleset"
#
# 私有网段
#
include "/srv/proxy/config/private.ruleset"
#
# 代理规则
#
add chain proxy doproxy
# 从 connmark 中恢复 mark
add rule proxy doproxy meta mark set ct mark
# 避免回环
add rule proxy doproxy mark $lo_fwmark return
# 私有地址直连
add rule proxy doproxy ip daddr @private_addr return
# 国内 IP 直连
add rule proxy doproxy ip daddr @chnroute counter return
# 重路由 TCP(SYN)
add rule proxy doproxy tcp flags syn counter meta mark set $lo_fwmark
# 重路由 UDP
add rule proxy doproxy meta l4proto udp ct state new counter meta mark set $lo_fwmark
# 将 mark 存储到 connmark 中
add rule proxy doproxy ct mark set mark
#
# 局域网代理
#
add chain proxy prerouting { type filter hook prerouting priority 0 ; }
# 放行本地不带 mark 的包
add rule proxy prerouting iifname "lo" mark != $lo_fwmark return
# 放行非本地发出的 DNS 包,供后续劫持
add rule proxy prerouting fib saddr type != local udp dport $dns_port return
# 对非本机发出、非本机接收的包进行规则路由
add rule proxy prerouting meta l4proto {tcp, udp} fib saddr type != local fib daddr type != local jump doproxy
# 对带有 $lo_fwmark 的包 转发至 ipt2socks 端口
add rule proxy prerouting meta l4proto {tcp, udp} mark $lo_fwmark tproxy to 127.0.0.1:$tproxy_port meta mark set $lo_fwmark
#
# 劫持局域网 DNS
#
add chain proxy dns { type nat hook prerouting priority 0 ; }
add rule proxy dns fib saddr type != local udp dport 53 counter redirect to :$dns_port
#
# 本机代理
#
add chain proxy output { type route hook output priority 0 ; }
# 直连 direct_fwmark 流量
add rule proxy output mark $direct_fwmark return
# 对本机发出的剩余包进行规则路由
add rule proxy output meta l4proto {tcp, udp} fib saddr type local fib daddr type != local jump doproxy
这个配置参照了 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
转换过来的,由于原文过长,这里就不贴出来了,内容格式如下:
#!/usr/sbin/nft -f
add set v2ray chnroute { type ipv4_addr; flags interval; }
add element v2ray chnroute { 1.0.1.0/24 }
add element v2ray chnroute { 1.0.2.0/23 }
add element v2ray chnroute { 1.0.8.0/21 }
add element v2ray chnroute { 1.0.32.0/19 }
add element v2ray chnroute { 1.1.0.0/24 }
add element v2ray chnroute { 1.1.2.0/23 }
add element v2ray chnroute { 1.1.4.0/22 }
然后是 private.ruleset
,记录了 IPv4
中的所有私有地址:
#!/usr/sbin/nft -f
add set v2ray private_addr { type ipv4_addr; flags interval; }
add element v2ray private_addr { 0.0.0.0/8 }
add element v2ray private_addr { 10.0.0.0/8 }
add element v2ray private_addr { 100.64.0.0/10 }
add element v2ray private_addr { 127.0.0.0/8 }
add element v2ray private_addr { 169.254.0.0/16 }
add element v2ray private_addr { 172.16.0.0/12 }
add element v2ray private_addr { 192.0.0.0/24 }
add element v2ray private_addr { 192.0.2.0/24 }
add element v2ray private_addr { 192.88.99.0/24 }
add element v2ray private_addr { 192.168.0.0/16 }
add element v2ray private_addr { 198.18.0.0/15 }
add element v2ray private_addr { 198.51.100.0/24 }
add element v2ray private_addr { 203.0.113.0/24 }
add element v2ray private_addr { 224.0.0.0/4 }
add element v2ray private_addr { 240.0.0.0/4 }
add element v2ray private_addr { 255.255.255.255/32 }
最后,为了能够快捷地开关透明代理,我们将其部署成 systemd
的 service
。需要准备的文件如下:
#!/bin/bash
ip -4 rule add fwmark 1 table 100
ip -4 route add local default dev lo table 100
/srv/proxy/config/proxy.nft
#!/bin/bash
ip -4 rule delete fwmark 1 table 100
ip -4 route delete local default dev lo table 100
nft delete table proxy
拜 nftables
的配置所赐,在结束的时候我们只需要直接对我们创建的 table
进行一个删除就可以了。
[Unit]
Description=iproute2 and nftables rules for transparent proxy
PartOf=systemd-networkd.service
After=network.target systemd-networkd.service
Wants=network.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/srv/proxy/transparent_proxy.sh
ExecStop=/srv/proxy/transparent_proxy_off.sh
[Install]
WantedBy=multi-user.target
这里需要注意的是 transparent-proxy.service
必须在 systemd-networkd.service
之后启动,并且要跟随 systemd-networkd
一起开启/关闭(PartOf
)。配置完后启动即可:
systemctl start transparent-proxy
systemctl enable transparent-proxy
至此,最后一片拼图也完成了。
结语
终于,我们得到了一个能用的软路由。它可以完美地完成原本由笔记本提供的代理功能,自带的无线网卡虽然表现一般但也能有百兆的速度,一切都正常地运转起来了。
但现在的配置还是有一些令人不大满意的地方,比如:
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