背景
自从买了macbook air m1之后,家里的台式机就很少用了。以前用来挂机刷碧蓝,但是现在也不怎么玩碧蓝了,所以就改成了做 frp。后来恰逢联通和广电整蛊,我气不过决定换成电信的宽带。师傅装好宽带之后,我看着光猫上3个LAN口(1个接了无线路由器)+1个IPTV口陷入了沉思——为什么不试试把台式机直接接光猫呢,这不就可以让它不占用路由器的WAN口带宽了吗?当然可以的,台式机也成功从光猫获得了IPv4和IPv6地址 1 ,而且一切服务都正常。
我又想,既然在这个架构下台式机和无线路由器是平起平坐的,那么为什么不简化一下,让无线路由器只负责提供无线功能,路由功能由台式机负责呢?这样做的好处就是可以x86软路由玩起来,花样很多。所以下一步就是……心心念念的,我(台式机)做master,家里其他设备做slave了(大误)。正经地说,软路由透明代理就是为了让PS5和Switch能够无感地加速下载,特别是让PS5可以正常地上传截图。
当然,这样的结构只靠台式机主板的一个千兆网口是不行的,还需要另一个网口。如果用传统的路由器上的接口命名来称呼的话,台式机主板的网口用来做WAN,连光猫的LAN口;而另外买一个PCIE网卡插在主板上所提供的网口就是LAN口,连无线路由器的WAN口。示意图如下:
由于本人学艺不精,在调试软路由+docker+透明代理的时候失去耐心,所以直接把docker砍了,nextcloud根据 archlinux wiki 重新部署了一次——nftables真香。同时,虽然archlinux的wiki建议不要在路由器上提供网络应用(nextcloud),但是我没钱再搞一台电脑也没精力研究用虚拟机做路由器的技术,所以不听老人言吃亏在眼前导致透明代理不可以处理台式机自身产生的流量,否则在v2ray的日志里就会大量出现访问127.0.0.1:443的请求,导致无法使用nextcloud服务。
综上所述,本文会介绍:
- x86软路由(开启DHCP),用nftables实现路由和防火墙功能;无线接入点由Bridge模式的无线路由器提供
- nftables劫持内网DNS流量至软路由的53端口,由cloudflared发送DoH查询(解决DNS污染问题)
- nftables实现透明代理,代理服务由V2Ray提供
第2、3步都是独立可选的,只不过是软路由最经典的玩法。注意下文中用尖括号( <>
)括起来的内容都要换成你自己的环境里实际的数值。
1 实现上网功能
在开始前,请先把无线路由器配置好,让其他设备可以正常连接无线(不需要可以上网),最重要的是把无线路由器的WAN口设定为桥接模式(Bridge)。
1.1 固定网卡名称
台式机上有两块网卡,一个是主板自带的,以后要做WAN口用;一个是PCIE网卡提供的,以后做LAN口。为了之后使用nftables编制规则的时候可以用iifname这样的设备名matcher来方便地处理流量,现在先加两条udev规则把设备名固定下来。首先通过执行 ip addr
确定两个网口的MAC地址,然后在 /etc/udev/rules.d/
内新建一个 10-network.rules
文件:
1
2
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="<WAN口的MAC>", NAME="netwan"
SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="<LAN口的MAC>", NAME="netlan"
保存后要么直接重启,要么执行 sudo udevadm trigger --verbose --subsystem-match=net --action=add
来手工触发重命名。现在执行 ip addr
就可以看到两个网口的名字已经改成了 netwan 和 netlan了。
1.2 配置DHCP Server服务
对于小家庭来说,dhcpd虽然老,但是老当益壮啊!Archlinux的dhcpd在dhcp包里,装好之后先把原版配置文件 /etc/dhcpd.conf
备份一下,然后直接贴上以下内容 2 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 告诉DHCP client们,DNS服务器是软路由自己
option domain-name-servers 10.0.0.1;
# 子网掩码
option subnet-mask 255.255.0.0;
# 告诉DHCP client们,网关地址是10.0.0.1(就是软路由自己)
option routers 10.0.0.1;
# 正式设定子网的IP地址等信息
subnet 10.0.0.0 netmask 255.255.0.0 {
# DHCP可以分配的IP地址范围
range 10.0.1.4 10.0.1.250;
# 给无线路由器设定一个固定的IP地址
host ap {
hardware ethernet <无线路由器的WAN口MAC地址>;
fixed-address 10.0.2.1;
}
}
保存之后先不要启动dhcpd,还需要配置systemd-networkd把WAN口和LAN口配置一下。之前我是使用dhcpcd来获取WAN口地址的,但是现在由于需要做路由器,干脆让systemd-networkd一次性配置好。
在 /etc/systemd/network
里建立两个文件, 20-wired.network
用于配置与光猫连接的WAN口:
1
2
3
4
[Match]
MACAddress=<WAN口的MAC>
[Network]
DHCP=yes
21-lan.network
配置与无线路由器连接的LAN口:
1
2
3
4
5
6
7
8
9
10
11
12
13
[Match]
MACAddress=<LAN口的MAC>
[Link]
Multicast=yes
[Network]
Address=10.0.0.1/16
MulticastDNS=yes
IPMasquerade=both
IPv6SendRA=yes
DHCPv6PrefixDelegation=yes
[IPv6SendRA]
Managed=yes
OtherInformation=yes
注意第6行的Address,要和前面 /etc/dhcpd.conf
里的 option routers 10.0.0.1;
和 option subnet-mask 255.255.0.0;
对得上(ip-cidr表示法的基本知识而已)。
接下来就是三步走:
-
systemctl enable --now systemd-networkd
让networkd配置好网口,此时软路由本身就已经可以上网了 - 把软路由LAN口跟无线路由器WAN口接起来,用
ip addr
看 netlan 的 inet 是不是10.0.0.1/16
-
systemctl enable --now dhcpd4.service
启动dhcpd服务,没有报错而且能够从无线客户端(比如手机)通过10.0.2.1访问无线路由器的管理介面就代表成功了
1.3 配置nftables和转发
为了让其他设备可以上网,需要把内核的转发打开,在 /etc/sysctl.d
新建一个 30-ipforward.conf
,贴上以下内容:
1
2
net.ipv4.ip_forward=1
net.ipv6.ip_forward=1
执行 sysctl -p
让配置文件生效。如果没有配置任何防火墙规则的话,到这里连接无线路由器的设备们就已经可以正常上网了。
但是台式机本身不是只做路由器,还要承担其他任务,更何况即使是普通的路由器也会有基本的防火墙来保护内网设备的,所以接下来需要设置一些nftables规则来保护自己。为了省事,这里直接修改/etc/nftables.conf了:
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 先把 table 清空
table inet filter
delete table inet filter
# 实现防火墙的过滤功能的规则主要都放在这个table里
table inet filter {
chain INPUT {
# 定义本chain应用对象:入站流量过滤,默认策略:drop
type filter hook input priority filter; policy drop;
ct state invalid drop comment "early drop of invalid connections"
ct state {established, related} accept comment "allow tracked connections"
# 接受本地loopback
iifname "lo" accept
# 接受所有icmp
meta l4proto icmp accept
meta l4proto ipv6-icmp accept
# DHCP
tcp dport {67,68} accept
udp dport {67,68} accept
# 接受来自lan的流量,此处直接用设备名netlan匹配
# 接受HTTP和HTTPS流量
iifname "netlan" tcp dport {http,https} accept
iifname "netlan" udp dport {http,https} accept
iifname "netlan" udp dport 53 accept
# 记录剩余的所有将会被reject的包
log prefix "[nftables input]Rejected: "
reject with icmpx type admin-prohibited
}
chain FORWARD {
# 定义本chain应用对象:转发流量过滤,默认策略:drop
# 算是路由器本职工作了
type filter hook forward priority filter; policy drop;
ct state vmap { established : accept, related : accept, invalid : drop }
iifname netlan accept
# 记录剩余的所有将会被reject的包
log prefix "[nftables forward]Rejected: "
reject with icmpx type admin-prohibited
}
chain OUTPUT {
# 允许所有本机产生的出站流量
type filter hook output priority filter; policy accept;
}
}
table inet mynat
delete table inet mynat
table inet mynat {
chain preroutelog {
type nat hook prerouting priority -199;
}
chain prerouting {
type nat hook prerouting priority dstnat;
}
}
执行 sudo nft -f /etc/nftables.conf
,此时防火墙已经正式生效,同时各设备上网都正常。执行 sudo systemctl enable nftables
让防火墙规则开机自动设置。
可以说大功告成(第一次)。
2 cloudflared与DNS流量劫持
这一节介绍如何部署DNS over Https客户端cloudflared并劫持内网的所有普通DNS流量交给cloudflared解析。
2.1 部署cloudflared
只做简单介绍,配置文件内各项具体内容看不懂请自行google。
直接安装archlinux源里的cloudflared,然后创建配置文件夹 /etc/cloudflared/
及配置文件 config.yml
:
1
2
3
4
5
proxy-dns: true
proxy-dns-upstream:
- <DoH服务器地址>
proxy-dns-port: 53
proxy-dns-address: 0.0.0.0
cloudflared包没有systemd service文件提供,自己手写一个 /etc/systemd/system/cloudflared.service
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=cloudflared service
After=network.target nss-lookup.target
[Service]
# 让非root身份启动的service也可以绑定在53端口
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
# 让非root身份启动的service也可以绑定在53端口
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/usr/bin/cloudflared
Restart=on-failure
RestartSec=10s
DynamicUser=yes
[Install]
WantedBy=multi-user.target
执行 sudo systemctl enable --now cloudflared
,检查 sudo systemctl status cloudflared
是否 Active: active (running)
。此时将 /etc/resolv.conf
内 nameserver
只保留 127.0.0.1
,软路由本身也应该能够正常解析DNS。
2.2 nftables劫持DNS流量
在前文 /etc/nftables.conf
的基础上,对nat表的 PREROUTING 链添加以下两条规则:
1
2
iifname "netlan" meta nfproto ipv4 tcp dport 53 counter redirect to 53 comment "Hijack DNS"
iifname "netlan" meta nfproto ipv4 udp dport 53 counter redirect to 53 comment "Hijack DNS"
整个 /etc/nftables.conf
现在看起来是这样的:
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 上来先把ruleset清空
flush ruleset
# 实现防火墙的过滤功能的规则主要都放在这个table里
table inet filter {
chain INPUT {
# 定义本chain应用对象:入站流量过滤,默认策略:drop
type filter hook input priority filter; policy drop;
# 接受所有icmp
meta l4proto icmp accept
meta l4proto ipv6-icmp accept
# 接受本地loopback
iifname "lo" accept
# 接受关联连接
ct state established,related accept
# 接受来自lan的流量,此处直接用设备名netlan匹配
iifname "netlan" accept
# 接受HTTP和HTTPS流量
tcp dport {80,443} accept
udp dport {80,443} accept
# 记录剩余的所有将会被reject的包
log prefix "[nftables input]Rejected: " reject
}
chain FORWARD {
# 定义本chain应用对象:转发流量过滤,默认策略:drop
# 算是路由器本职工作了
type filter hook forward priority filter; policy drop;
# 这里使用了 https://wiki.nftables.org/wiki-nftables/index.php/Concatenations 的语法
# 允许内网向外网的出站流量+允许内网设备间的通信
iifname . oifname { netlan . netwan, netlan . netlan} accept
# 允许外网向内网的入站流量,但是仅限关联连接
iifname "netwan" oifname "netlan" ct state established,related accept
# 记录剩余的所有将会被reject的包
log prefix "[nftables forward]Rejected: " reject
}
chain OUTPUT {
# 允许所有本机产生的出站流量
type filter hook output priority filter; policy accept;
}
}
# 实现路由器的NAT功能的规则主要都放在这个table里
table inet nat {
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
iifname "netlan" meta nfproto ipv4 tcp dport 53 counter redirect to 53 comment "Hijack DNS"
iifname "netlan" meta nfproto ipv4 udp dport 53 counter redirect to 53 comment "Hijack DNS"
}
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
oifname "netlan" masquerade
}
chain OUTPUT {
type nat hook output priority -100; policy accept;
}
}
执行一下 sudo nft -f /etc/nftables.conf
,现在所有从LAN口进入软路由的DNS都会交给cloudflared了,也就是连接无线路由器的设备们都间接地获得了DoH服务。
3 v2ray透明代理(仅TCP及局域网设备)
透明代理是我花费了最多时间来搞通的,网上有很多教程但是多数都没办法直接应用。一句忠告,不要碰UDP透明代理。
3.1 修改v2ray配置文件
根据 v2fly配置指南
_ 修改 config.json:
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
33
34
35
36
37
38
{
"routing": {...},
"inbounds": [
{
...
},
{
"port": 12345, //开放的端口号
"protocol": "dokodemo-door",
"settings": {
"network": "tcp",
"followRedirect": true // 这里要为 true 才能接受来自 iptables 的流量
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls"]
},
"streamSettings": {
"sockopt": {
"tproxy": "redirect"
}
}
}
],
"outbounds": [
{
...
"streamSettings": {
...
"sockopt": {
"mark": 255 //这里是 SO_MARK,用于 iptables 识别,每个 outbound 都要配置;255可以改成其他数值,但要与下面的 iptables 规则对应;如果有多个 outbound,最好奖所有 outbound 的 SO_MARK 都设置成一样的数值
}
}
}
...
]
}
主要是打开一个用于接收来自LAN的流量的dokodemo-door端口,以及把所有outbound都打上标记用于防火墙分流。修改完配置文件restart一下v2ray,正常来说不影响网络,各设备依然可以正常上网。
3.2 修改nftables做透明代理
直接放完整的 /etc/nftables.conf
。如果没有做 2 cloudflared与DNS流量劫持
_,请删掉第47和48行。
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 上来先把ruleset清空
flush ruleset
# 实现防火墙的过滤功能的规则主要都放在这个table里
table inet filter {
chain INPUT {
# 定义本chain应用对象:入站流量过滤,默认策略:drop
type filter hook input priority filter; policy drop;
# 接受所有icmp
meta l4proto icmp accept
meta l4proto ipv6-icmp accept
# 接受本地loopback
iifname "lo" accept
# 接受关联连接
ct state established,related accept
# 接受来自lan的流量,此处直接用设备名netlan匹配
iifname "netlan" accept
# 接受HTTP和HTTPS流量
tcp dport {80,443} accept
udp dport {80,443} accept
# 记录剩余的所有将会被reject的包
log prefix "[nftables input]Rejected: " reject
}
chain FORWARD {
# 定义本chain应用对象:转发流量过滤,默认策略:drop
# 算是路由器本职工作了
type filter hook forward priority filter; policy drop;
# 这里使用了 https://wiki.nftables.org/wiki-nftables/index.php/Concatenations 的语法
# 允许内网向外网的出站流量+允许内网设备间的通信
iifname . oifname { netlan . netwan, netlan . netlan} accept
# 允许外网向内网的入站流量,但是仅限关联连接
iifname "netwan" oifname "netlan" ct state established,related accept
# 记录剩余的所有将会被reject的包
log prefix "[nftables forward]Rejected: " reject
}
chain OUTPUT {
# 允许所有本机产生的出站流量
type filter hook output priority filter; policy accept;
}
}
# 实现路由器的NAT功能的规则主要都放在这个table里
table inet nat {
chain PREROUTING {
type nat hook prerouting priority dstnat; policy accept;
iifname "netlan" meta nfproto ipv4 tcp dport 53 counter redirect to 53 comment "Hijack DNS"
iifname "netlan" meta nfproto ipv4 udp dport 53 counter redirect to 53 comment "Hijack DNS"
# tcp包进来之后先给V2Ray链做判断
ip protocol tcp jump V2RAY
}
chain POSTROUTING {
type nat hook postrouting priority srcnat; policy accept;
oifname "netlan" masquerade
}
chain OUTPUT {
type nat hook output priority -100; policy accept;
}
chain V2RAY {
# 先放行需要直连的ip
ip daddr 192.168.0.0/16 return
ip daddr 10.0.0.0/16 return
# 如果tcp包有标记255,代表着已经经过v2ray处理,所以放行
ip protocol tcp meta mark 0x000000ff return
# 剩下的未处理过的tcp包都转发给v2ray判断
ip protocol tcp redirect to :12345
}
}
执行一下 sudo nft -f /etc/nftables.conf
,现在所有LAN上的设备都可以直接访问墙外了。大功告成。
4 后话
可把我整麻了。参考文献按主题分: