最近自己拉了一条联通的千兆宽带,并成功地找官方客服要到了公网 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
等管理器。具体配置如下,其中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 能够正确进行。dhcp6c.service
内容如下:
[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;
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
中。