最近自己拉了一条联通的千兆宽带,并成功地找官方客服要到了公网 IPv4。为了有效利用这个公网 IP,同时避免重演之前在房东的光猫上的血泪史,我决定将拨号等各项网络功能全部交由软路由实现,光猫仅保留光电转换功能。这篇文章用于记录我的配置过程,防止以后在软路由配置上重复踩坑,也为需要的人提供参考。
准备工作
在开始配置软路由之前,需要安装一系列工具,并对系统的一些参数进行修改。我使用的工具包括:
pppoeconf
,用于宽带拨号wide-dhcpv6-client
,用于获取 IPv6 PD 前缀并配置到接口dnsmasq
,用于为 LAN 提供 DNS 服务、DHCP 服务和 IPv6 路由通告服务nftables
,用于提供 IPv4 NAT 和防火墙功能
以上四个工具均可通过apt
直接安装。
同时,为了保证系统能够正确获取 IPv6 地址和转发数据包,还需要向/etc/sysctl.conf
中添加以下内容:
net.ipv4.ip_forward=1
net.ipv6.conf.all.disable_ipv6=0
net.ipv6.conf.default.disable_ipv6=0
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.accept_ra=2
net.ipv6.conf.default.accept_ra=2
其中,第一条用于启用对 IPv4 数据包的转发,后面六条分别对应启用 IPv6 功能、启用对 IPv6 数据包的转发和接受 IPv6 的路由器通告。带default
的配置用于系统对接口的默认设置,如果未对其进行修改,可能导致新添加的接口与 all 的配置不一致,从而产生问题。(在我的场景中,拨号后新增的ppp0
接口的accept_ra
项为1
,无法正确从 ISP 的 RA 通告中获取并设置 IPv6 地址,导致其只有公网 IPv4 地址和 FE80 打头的 IPv6 link local 地址,没有 GUA,即公网 IPv6 地址。)
配置网络接口
在进行后续工作前,首先需要正确配置软路由 LAN 口和 WAN 口的地址。方便起见,我直接使用 Debian 12 自带的networking
服务进行网络配置,不使用NetworkManager
等管理器。在 Debian12 中,networking
服务使用的配置文件位于/etc/network/interfaces
,具体配置如下所示。其中,enp1s0
为 LAN 口,LAN 的 IPv4 网段为10.20.0.0/24
,enp4s0
为 WAN 口,连接光猫,IPv4 地址与光猫处于同一网段,方便后续对光猫进行操作。
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
auto enp1s0
allow-hotplug enp1s0
iface enp1s0 inet static
address 10.20.0.1/24
auto enp4s0
allow-hotplug enp4s0
iface enp4s0 inet static
address 192.168.1.99/24
拨号上网
在作为路由器之前,软路由自身首先需要能够上网。为了使用软路由进行拨号,必须保证光猫处于桥接模式,这个可以在装宽带时让师傅进行修改,或是通过各种手段获取光猫的超级密码后进入管理员后台进行修改,这里不作陈述。
在 Debian 12 上进行拨号,我选择的方案是pppoeconf
这个工具,可以直接使用 apt 进行安装。在安装完成后,直接执行sudo pppoeconf
,其会自行检测可用于 pppoe 拨号的网口。按提示输入宽带账号和密码后,其便会自动生成/etc/ppp/peers/dsl-provider
配置文件用于实际拨号,并且会提醒是否立即进行拨号。需要注意的第一点是,pppoeconf
默认生成的配置文件并未启用 IPv6,需要手动在dsl-provider
文件的最后添加一行+ipv6
以启用。在添加后,需要使用sudo systemctl restart networking
重启网络服务确保配置生效。
dsl-provider
文件内容如下,其中enp4s0
为软路由连接光猫的接口:
# Minimalistic default options file for DSL/PPPoE connections
noipdefault
defaultroute
replacedefaultroute
hide-password
#lcp-echo-interval 30
#lcp-echo-failure 4
noauth
persist
#mtu 1492
#persist
#maxfail 0
#holdoff 20
plugin rp-pppoe.so
nic-enp4s0
user "********"
+ipv6
完成拨号后,理论上就能够看到ppp0
接口已经可用,同时具备 ISP 下发的 IPv4 与 IPv6 地址。
需要注意的第二点是,pppoeconf
在配置过程中自动生成的 mss clamping 设置脚本位于/etc/ppp/ip-up.d/0clampmss
中,但该脚本仅设置了对 TCP over IPv4 连接的 mss clamping,而没有对 IPv6 连接进行设置,进而导致部分网站的 IPv6 数据包因 MTU 问题丢包。为了解决这个问题,需要将/etc/ppp/ip-up.d/0clampmss
复制一份到/etc/ppp/ipv6-up.d/0clampmss
,并将脚本中的iptables
命令改为ip6tables
。如此即可实现对 TCP over IPv6 连接的 mss clamping。
IPv6 获取 PD
为了让 LAN 能够正确配置 IPv6 GUA 地址,需要在软路由上向 ISP 请求一个可用的 PD 并下发给 LAN 的所有设备。这里我使用wide-dhcpv6-client
包来实现这一功能,其配置文件位于/etc/wide-dhcpv6/dhcp6c.conf
,内容如下:
interface ppp0 {
# 请求 PD
send ia-pd 0;
};
# 请求到的序号为 0 的 PD 用于如下
id-assoc pd 0 {
# 用该 PD 配置 enp1s0 接口
prefix-interface enp1s0 {
# 该接口在前缀中的 id 序号,从 0 开始
sla-id 1;
# 该接口使用前缀长度,如果 PD 下发的是/60,这里是 4,则最后接口的前缀为 60+4=/64
sla-len 4;
};
};
完成上述后,可以通过sudo systemctl restart wide-dhcpv6-client
来检查是否能够正确获取 PD 并设置 LAN 接口的 IPv6 地址。
由于开机后需要等待拨号ppp0
接口才会出现,所以wide-dhcpv6-client
服务的自启动往往会失败。为了实现正确的自启动,这里我自行构建了一个dhcp6c.service
服务,并设置失败重启,确保在ppp0
接口启用后 DHCPv6 能够正确进行。按照systemd
的惯例,将dhcp6c.service
放置在/etc/systemd/system
目录下,其内容如下:
[Unit]
Description=WIDE DHCPv6 Client
Wants=network-online.target
Before=dnsmasq.service
After=network-online.target
[Service]
EnvironmentFile=/etc/default/wide-dhcpv6-client
ExecStart=/usr/sbin/dhcp6c -f $INTERFACES
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
启用 DNS 和 DHCP 服务
我使用dnsmasq
来同时提供 DNS 和 DHCP 服务,配置文件如下所示:
# = DNS 服务器设置 =
interface=enp1s0
port=53
server=119.29.29.29
server=223.5.5.5
# = DHCP 服务器设置 =
# 启用路由通告
enable-ra
log-dhcp
# DHCPv4 的地址范围
dhcp-range=10.20.0.100,10.20.0.199,12h
# DHCPv6 仅用于通告 DNS,使用 enp1s0 的前缀进行路由通告,让 LAN 机器通过 SLAAC 获取 IPv6 地址
dhcp-range=::,constructor:enp1s0,ra-stateless,12h
# DHCPv4 的路由器地址和 DNS 服务器选项
dhcp-option=option:router,10.20.0.1
dhcp-option=option:dns-server,10.20.0.1
# DHCPv6 的 DNS 服务器选项,按路由器接口地址构造
dhcp-option=option6:dns-server,[::]
设置防火墙
为了避免干扰上述配置过程,我将防火墙的设置放在了最后。防火墙的防护重点如下:
- 保护软路由不受公网攻击
- 保护 LAN 不受公网攻击
除此以外,IPv4 的 NAT 功能也借由nftables
实现。
为了实现上述功能,我的nftables
配置如下:
#!/usr/sbin/nft -f
flush ruleset
# 防火墙功能
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
iifname "lo" accept comment "Accept any localhost traffic"
ct state invalid drop comment "Drop invalid connections"
ct state established,related accept comment "Accept traffic originated from us"
meta l4proto ipv6-icmp accept comment "Accept ICMPv6"
meta l4proto icmp accept comment "Accept ICMP"
ip protocol igmp accept comment "Accept IGMP"
udp dport mdns ip6 daddr ff02::fb accept comment "Accept mDNS"
udp dport mdns ip daddr 224.0.0.251 accept comment "Accept mDNS"
iifname "enp1s0" accept comment "Accept LAN to Router"
# 放行 DHCPv6 的端口,确保 wide-dhcpv6-client 能够正确获取 PD
udp dport {546,547} accept
}
chain forward {
type filter hook forward priority filter; policy drop;
# 由于 pppoe 的协议封装需要占用 8 个字节,导致 MTU 降低,这里根据 ppp0 接口的 MTU
# 修改 tcp 协议的 MSS 来避免数据包因为超过 MTU 而被丢弃
iifname "ppp0" tcp flags syn counter tcp option maxseg size set rt mtu
oifname "ppp0" tcp flags syn counter tcp option maxseg size set rt mtu
iifname enp1s0 accept comment "Accept LAN Outgoing"
ct state established,related accept comment "Accept traffic orininated from LAN"
meta l4proto ipv6-icmp accept comment "Accept ICMPv6"
meta l4proto icmp accept comment "Accept ICMP"
}
chain output {
type filter hook output priority filter;
}
}
# NAT 功能
table inet nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.20.0.0/24 oifname "ppp0" masquerade
}
}
将上述内容保存到/etc/nftables.conf
中即可在 boot 后自动将上述规则添加至nftables
中。