土制 Linux 路由器(Chapter 1):设置一个简单的家用 Linux 路由器

系列链接:

  1. 土制 Linux 路由器(Chapter 1):设置一个简单的家用 Linux 路由器
  2. 土制 Linux 路由器(Chapter 2):策略路由
  3. Work in progress…

写在前面的话

因为跟着群友们玩了一段时间的网络,以及自己的开源软件洁癖,于是自己开始学习如何用常见的发行版例如 Debian 来制作纯正的开源路由器(?
前段时间有群友想知道如何把一个Linux变成路由器,再加上自己本身也想把自己折腾的东西整理出来,于是本人还是决定开坑了。
此系列文章主要记录个人配置 Linux 路由器的心得,注定也会比较干。
本篇主要内容则是基础上网加上简单的防火墙配置,可以达成接路由器下面的设备可以上网的成果。
需要注意的是,在写这篇文章的时候,作者已经尽量采用较新的软件,但技术总是更新的,可能过时的东西会越来越多(笑

通过阅读此篇,你可以做到: 路由器通过 PPPoE 上网,下面设备通过路由器也可以上网,并可获得双栈的连接。

如果文中出现错误,还请看到的各位大佬不吝指正。 文中已经尽量压缩了内容,但可能废话还是会很多 那么就开始正片吧(雾

环境介绍

使用的软件和发行版

  1. Linux 发行版采用 Alpine Linux
  2. 防火墙前端采用 nftables
  3. DHCP 服务器采用 Kea DHCP Server
  4. 递归 DNS 服务器采用 Knot Resolver
  5. PPPoE 拨号采用 PPP
  6. DHCPv6 客户端采用 DHCPCD
  7. IPv6 对局域网发送路由通告采用 radvd

使用的网络接口和连接信息

路由器采用 eth0 接口来拨号。上网用户名为 test ,密码是 123456
路由器的下游接口为 eth1 ,内网网段为 192.168.10.0/24,路由器在内网的 IP 为 192.168.10.1/24
上游设备 PPPoE Server 和 DHCPv6 Server 由 RouterOS 提供。

1.png

注意事项

  • 以上软件可以在 AlpineLinux 上通过 apk add nftables kea-dhcp4 kea-dhcp6 ppp knot dhcpcd radvd 来取得。
  • AlpineLinux 安装过程中需要网络,真实环境下操作请预先安装以上包,正片中不再赘述以上安装过程。

正文

Part 1:让 Alpine Linux 先使用上网络吧

一、 通过 PPPoE 连线

  1. /etc/modules中添加pppoe以在开机时自动加载pppoe这个内核模块。
    可以执行 echo pppoe >> /etc/modules 来添加。
    如果是首次配置并且未重启,需要手动启用 pppoe 模块,执行 modprobe pppoe 即可。
  2. /etc/ppp/peers/dsl-provider 中添加如下内容:
user "test"
password "123456"
#nic-eth2
#ifname ppp0
#ipparam ppp0
plugin rp-pppoe.so eth0
noauth
updetach
linkname "pppoe"
usepeerdns
defaultroute
+ipv6 # enable IPv6
persist

mtu 1492
mru 1492
lcp-echo-interval "1"
lcp-echo-failure "5"
lcp-echo-adaptive

此处假定用户名是 test 密码是 123456,并且假定使用 eth0 这个接口进行拨号。 dsl-provider 只是一个名称,可以随自己喜好更改。
3. 测试拨号是否成功。
执行 pppd call dsl-provider 来使用 dsl-provider 这个配置来拨号。

如果没有问题,大致会输出以下信息:

Plugin rp-pppoe.so loaded.
PPPoE plugin from pppd 2.4.9
PPP session is 17
Connected to 52:54:00:cc:91:aa via interface eth0
Using interface ppp0
Connect: ppp0 <--> eth0
CHAP authentication succeeded
peer from calling number 52:54:00:CC:91:AA authorized
local  LL address fe80::6ce5:14ed:9bae:6691
remote LL address fe80::0000:0000:00f0:000f

执行 ip address show dev ppp0 可以看到ppp0接口的话,就算拨号成功了。:

alpinerouter:~# ip a show dev ppp0
8: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1480 qdisc pfifo_fast state UNKNOWN group default qlen 3
    link/ppp 
    inet 100.127.255.255 peer 172.16.0.1/32 scope global ppp0
       valid_lft forever preferred_lft forever
    inet6 fe80::e986:994f:ae8b:bb0 peer fe80::f0:10/128 scope link 
       valid_lft forever preferred_lft forever

也可以在 RouterOS 上的 Active Connections 看到 PPPoE 连接已经启用了。
此时IPv4连接就建好了。
等等,what about ipv6?
不要急,我们将其持久化之后再来配置。

  1. 使 PPPoE 持久化 编辑/etc/network/interfaces 添加 eth0ppp0 的配置:
auto eth0
iface eth0 inet manual

auto ppp0
    iface ppp0 inet ppp
    requires eth0
    pre-up /sbin/ip link set eth0 up
    provider dsl-provider
  • 注意 provider 选项填写在 /etc/ppp/peers/ 内配置文件的名称。 重新启动之后,用 ip link list 应该就能看到 ppp 接口啦
alpinerouter:~# ip link list
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:45:27:66 brd ff:ff:ff:ff:ff:ff
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:87:14:a2 brd ff:ff:ff:ff:ff:ff
6: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1480 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 3
    link/ppp
  1. 检查网络是否联通,一般可以使用ping,traceroute等方法,如果路由器通了就没有问题了。

让 Linux 当路由器,我们还需要启用它的转发功能:
执行以下命令:
sysctl net.ipv4.ip_forward=1
sysctl net.ipv6.conf.all.forwarding=1
即可。如果需要持久化,则执行以下命令:
echo net.ipv4.ip_forward = 1 >> /etc/sysctl.d/90-forwarding.conf
echo net.ipv6.conf.all.forwarding = 1 >> /etc/sysctl.d/90-forwarding.conf
这样每次启动之后就会自动保存了。

二、 通过静态或者动态的 DHCP 连线

如果是一般的静态地址上网或者使用DHCP,在 /etc/network/interfaces 写入配置就可以了

  • 此处假定是静态地址119.108.233.2/24,并且网关为 119.108.233.1 大概格式如下:
auto eth0
iface eth0 inet static
    address 119.108.233.2/24
    gateway 119.108.233.1
  • 如果是 DHCP 获取地址,将上面的 static 换成 dhcp ,并去掉下面的 address 和 gateway 即可。
  • IPv6 地址如果是使用 SLAAC 获取的话就在下面加上一行:
    iface eth0 inet6 auto
    即可。

Part 2: 让下面的设备也能上网吧~

一、给局域网分配地址

此处我们做实验的这台机器的 eth0 给PPPoE使用了,接下来就给 eth1 分一个内网的网关地址吧(将下面一段写入 /etc/network/interfaces 即可)

auto eth1
iface eth1 inet static
    address 192.168.10.1/24
iface eth1 inet6 auto

二、NAT 简述

不过在配置 NAT 之前,还是需要读者知道 NAT 的含义,可以在这里去了解,其中也讲述了回环 NAT 的配置问题,不过此篇不会覆盖到,不过还是推荐读者去阅读它。
以下是关于它的简单的解释:

  • SNAT(Source NAT) 是对从内部(一般是保留的)地址转到外部的时候所做的操作,让源 IP 从内部地址变成外部的地址,也就是把从里面来的东西改一下,之后这个数据包就可以在外部被转发了
  • DNAT(Destination NAT)则相反,是外界访问内部数据时,改变它的目标IP地址(也可以一并改变它的目标端口),即更改发到私有 IP 地址的目标地址,也就是把从外面要发到里面的东西改一下,让其转发到内部的主机上去,端口转发(port forward)的原理也是这个。
  • 一个源 IP 地址为保留地址的数据包在公网上基本也只会被丢弃。

三、nftables 的配置

既然已经2022年了,不用下 nftables 怎么行呢?nftables 相比 iptables 也有更多好玩的新特性

  • 它的链和表都是完全可调整的,初始状态下规则(rule)、链(chain)和表(table )都不存在,可以通过自己的需求来加减,hook 则可以在链中配置,但链可以不带任何 hook ;
  • 一条规则可以执行多个操作,有 MapsVerdict Maps
  • ipv4 和 ipv6 可以合并管理,比如对于丢弃到某个端口的连接,可以在 inet(即ip和ip6) 地址族中写一条规则即可,而不是像 iptables 那样需要同时通过 iptables 和 ip6tables 加这条规则。
  • 流控方面,nftables 可以替代 tc filter,可以对于不同的数据流或数据包进行分组设置priority,将其塞到特定的class里面。
  • 数据操作也是可行的!
  • 等等….(喵?)
  • 关于 nftables 的新特性,以及详细用法,这里先不细讲,可以通过这里去了解。

关于nftables配置文件的位置:
Debian系在 /etc/nftables.conf
AlpineLinux在 /etc/nftables.nft
配置文件位置可能因发行版有所不同,但是语法都是一样的。

  • 一些说明:
    • 表(table)是用来装链(chain)的,链是用来装规则(rule)的,
    • 每个链的挂载点(hook)是可选的,可以不挂载任何netfilter hooks,链可以拥有一个优先级(priority),和一个默认的操作(policy)。

nftables 的 NAT 配置

  1. 我们先建立一个表(table).
    nft add table ip nat

Note1: 其中 “ip” 是 ipv4 这个地址族(address family),“nat” 则是这个表(table)的名称,名称可以自己随意定,此处使用 nat 是为了表明这个表内的链和规则是为了 nat 而写的,不过对于实际上 netfilter 怎样处理没有什么影响。
Note2: 对于nat类型的chain,我们只能将其塞进地址族为 ip 或者 ip6 的 table 内,想要偷懒使用 inet 是不可以的。 ,参考此处

  1. 之后我们在这个表(table)内建立一个链(chain)
    nft 'add chain nat postrouting { type nat hook postrouting priority 100 ; }'

Note: 我们在这里需要做到的是 SNAT 操作,而对于变动的外部地址,最方便的一个操作是 masquerade,自动根据外部接口地址来进行 SNAT。而这个操作需要在数据包被路由之后,在被送出本地系统之前进行,所以建立一个挂载postrouting的chain。(需要知道被SNAT到的地址是什么,则需要在被路由之后才能知道出接口的地址才能进行自动 SNAT)

  1. 我们在postrouting链中塞一个 masquerade 的规则。(假定我们的出接口是ppp0)
    nft add rule nat postrouting oifname "ppp0" masquerade
  2. 执行nft list ruleset就可以看到完整的规则啦:
table ip nat {
    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
            oifname "ppp0" masquerade
    }
}

这一段也可以写进/etc/nftables.nft里面,之后使用rc-update add nftables就可以让其开机自动保存啦

nftables 的 NAT 加上过滤的配置文件

这里我直接放一个最终的配置文件吧,就不多写了。 (假定上网用接口是ppp0,局域网接口是eth1)

#!/usr/sbin/nft -f
# Content of /etc/nftables.nft: 
flush ruleset
table ip nat {
    chain postrouting {
        type nat hook postrouting priority srcnat; policy accept;
        oifname "ppp0" masquerade
    }
}
table inet filter {
    chain input {
        type filter hook input priority 0;
        policy accept
            ct state vmap { related:accept, established:accept }
            ip6 nexthdr { icmpv6 } accept
            ip protocol { icmp } accept
            iifname "lo" accept
            iifname "ppp0" meta l4proto tcp tcp dport { 11451, 5201 } accept comment "allow incoming tcp port 11451 and 5201 for both ipv4 and ipv6 from wan"
            iifname "ppp0" drop comment "drop other traffic from wan"
    }
    chain forward {
        type filter hook forward priority 0;
        policy accept
            ct state vmap { related:accept, established:accept }
            ip6 nexthdr { icmpv6 } accept
            ip protocol { icmp } accept
            iifname "eth1" oifname "ppp0" accept
            iifname "ppp0" drop comment "drop other traffic from wan"
    }
}

执行 nft -f /etc/nftables.nft 即可保存。

PPPoE 下的 PMTUD 问题的缓解措施

  1. 对于 IPv4:
    如果你在使用过程中遇见了连接到网络超时的现象,并且抓包可以看到 previous segment not captured 和一堆连接重置,可以尝试在 nftables 配置文件中加上以下的规则:
table ip mangle {
    chain forward {
        type filter hook forward priority mangle;
        policy accept
            tcp flags syn tcp option maxseg size set rt mtu counter comment "IPv4: Clamp MSS to PMTU"
    }
}
  1. 对于 IPv6:
    在 IPv6 下,已经有了通过 ICMPv6 实现的 PMTU Discovery 机制。
    但对于一些配置不当的防火墙,IPv6也可能会出现 Path MTU Discovery 不工作的问题,原因一般是没有放行 Packet Too Big 这个类型的 ICMPv6 的数据包。即使本地放行了这种类型的ICMPv6,而对方没有放行,IPv6的 PMTUD 也是不能工作的。
    检测 MTU 问题的时候,最好的诊断方法就是 ping 配合 size 参数,但是对方把 ICMP echo-request/echo-reply 给橄榄之后这种方法也不会起作用。

在此呼吁各位网络管理者不要随便就把 ICMP(v6) 直接给干掉,如果担心被 flood ping,可以设定一个频率限制,而 ICMPv6 中作为 IPv6 的重要组成部分,更不要轻易忽略它甚至把它给干掉,这样的话一般设定情况下,客户可是可能不能访问你的网站的喔

在对方把 ICMPv6 一刀切的时候,你可以通过在 RADVD 中设定 AdvLinkMTU 为 1492(1500-8) 或者更小来缓解,这样的话,下面的设备获取的默认路由的 MTU 就是你设定的 MTU 了。
不过还有一种解决方式就是 clamp mss to pmtu,这里就不多讲述了。(感觉用这种方法超坏的!)

nft 命令的一些常用用法(cheatsheet)

列出表和它们的地址族: nft list tables
列出所有规则和统计数据: nft list ruleset
清空所有规则: nft flush ruleset
检查配置文件的语法: nft -c -f <file path>
载入配置文件: nft -f <file path>
更多的可以去这里找到。

这样,我们的基本的路由器配置就结束了,不过为了下面的设备可以正常上网,你还需要一个 DHCP Server 来给下面的地址分配网关,DNS,地址等信息。

四、Kea-DHCP 的配置

Kea DHCPv4 Server 对应的配置文件在 /etc/kea/kea-dhcp4.conf
直接放配置文件吧:

{
# DHCPv4 configuration starts on the next line
"Dhcp4": {

# First we set up global values
"valid-lifetime": 4000,
"renew-timer": 1000,
"rebind-timer": 2000,

# Next we set up the interfaces to be used by the server.
"interfaces-config": {
    "interfaces": [ "eth1" ]
},

# And we specify the type of lease database
"lease-database": {
    "type": "memfile",
    "persist": true,
    "lfc-interval": 1800,
    "name": "/var/lib/kea/dhcp4.leases"
},

# Finally, we list the subnets from which we will be leasing addresses.
"subnet4": [
        {
            "subnet": "192.168.10.0/24",
        "pools": [
            {
                "pool": "192.168.10.20 - 192.168.10.200"
            }
        ],
        "option-data": [
            {
                "name": "domain-name-servers",
                "data": "223.5.5.5"
            },
            {
                "name": "routers",
                "data": "192.168.10.1"
            }
        ],
        "reservations": [
            {
                "hw-address": "58:11:22:33:44:55",
                "ip-address": "192.168.10.190"
            }
        ]
    }
]
# DHCPv4 configuration ends with the next line
}

}

之后 service kea-dhcp4 start 就可以开启 Kea DHCPv4 Server 了。
同样地,我们也可以用rc-update add kea-dhcp4将其加入自启动列表。

五、IPv6 的配置

  1. 使用 dhcpcd 作为 IPv6 DHCP 客户端来获取地址和地址块。
    废话不多说,直接上配置吧:
denyinterfaces eth0
duid
noipv6rs
waitip 6
option rapid_commit
debug
logfile /var/log/dhcpcd.log

interface ppp0
    ipv6only
    ipv6rs
    iaid 1
    slaac private temporary
    ia_na 1
    ia_pd 1 eth1/0/64/1

之后执行dhcpcd ppp0就可以了。

我们只需要通过这个从 ppp0 获取 IPv6 地址,所以加上了 ipv6only
其中,我们需要默认在其他接口上不进行 IPv6 Router Solicit,只在 ppp0 这个上网的接口上进行 Router Solicit。
并且我们需要从运营商获取地址块之后,分一段 /64 到 eth1,并且将加上去的地址块的 :1 分到 eth1 上,于是就有了 ia_pd 1 eth1/0/64/1

Note: 如果需要加入自动启动的话,可以在 /etc/ppp/ip-up 以及 /etc/ppp/ip-down 文件中的 ppp0 接口下分别加入 dhcpcd ppp0dhcpcd ppp0 -k,来让其随 ppp0 接口开启和关闭。

不出意外的话,我们可以在 ppp0 接口上看到 IPv6 地址(通过SLAAC),在 eth1 上也可以看到一个前缀长度为 64 的 IPv6 地址(通过DHCPv6 Prefix Delegation)。

alpinerouter:~# ip addr show dev eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 52:54:00:87:14:a2 brd ff:ff:ff:ff:ff:ff
    inet 192.168.10.1/24 scope global eth1
       valid_lft forever preferred_lft forever
    inet6 240e:114:514::1/64 scope global dynamic noprefixroute 
       valid_lft 258863sec preferred_lft 232943sec
    inet6 fe80::5054:ff:fe87:14a2/64 scope link 
       valid_lft forever preferred_lft forever
alpinerouter:~# ip addr show dev ppp0
5: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1480 qdisc pfifo_fast state UNKNOWN group default qlen 3
    link/ppp 
    inet 100.127.255.255 peer 172.16.0.1/32 scope global ppp0
       valid_lft forever preferred_lft forever
    inet6 240e:1919:810:10:acea:fcf4:a541:5125/64 scope global temporary dynamic 
       valid_lft 604464sec preferred_lft 86001sec
    inet6 240e:1919:810:10:51cd:ad00:b79:161e/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 2591664sec preferred_lft 604464sec
    inet6 fe80::c40b:96a8:edde:d0d6 peer fe80::f0:11/128 scope link 
       valid_lft forever preferred_lft forever

在执行ip -6 route show我们也可以看到一个 unreachable 的路由,这就是运营商给我们分配的IPv6地址池。这个地址池大小/56,/60不等,具体取决于当地的运营商。

我们也可以看到指向局域网的一个/64的路由,这就是这个接口下局域网的 IPv6 地址范围了。

alpinerouter:~# ip -6 route show
240e:114:514::/64 dev eth1 proto dhcp metric 1003 pref medium
unreachable 240e:114:514::/56 dev lo proto dhcp metric 1001 pref medium
240e:1919:810:10::/64 dev ppp0 proto ra metric 1005 pref medium
fe80::f0:11 dev ppp0 proto kernel metric 256 pref medium
fe80::c40b:96a8:edde:d0d6 dev ppp0 proto kernel metric 256 pref medium
fe80::/64 dev eth1 proto kernel metric 256 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::f0:11 dev ppp0 proto ra metric 1005 pref medium
  1. 配置 RADVD 让下面的设备可以通过 SLAAC 获取 IPv6 地址:
    配置文件在/etc/radvd.conf
interface eth1 {
    AdvSendAdvert on;
    AdvOtherConfigFlag off;
    AdvManagedFlag off;
    prefix ::/64 {
        AdvOnLink on;
        AdvAutonomous on;
        AdvValidLifetime 7200;
        AdvPreferredLifetime 3600;
    };
    RDNSS 2400:3200::1 {
        AdvRDNSSPreference 8;
        AdvRDNSSLifetime 1200;
    };
};

随后通过service radvd startrc-update add radvd来启用并加入自启名单。

对于其中的一些flags:

  • AdvAutonomous 是允许设备通过这个宣告的前缀来用 SLAAC 的方式为自己分配地址。
  • OtherConfig 是指,让设备在 SLAAC 获取地址的方式下,通过 DHCPv6 服务器来获取其他的信息(例如 DNS 服务器等)。
  • Managed 是指允许设备通过 DHCPv6 服务器来获取地址和 DNS 等信息。

通过一系列组合,读者可以尝试用 DHCPv6 Stateful + SLAAC 或者分别以 DHCPv6 Stateful 和 SLAAC 的方式让下面的设备获取地址。

Note 1:
与 IPv4 获取网关的方式不同, 在 IPv6 下,默认路由不是通过 DHCPv6 服务器来发放的,而是通过路由宣告(Router Advertisement)来发放的。
并且有状态的 DHCPv6 服务器也不是必要选项,因为本身路由通告(Router Advertisement)就可以直接发放 IPv6 的前缀和 DNS 解析服务器的地址了。
此外,单独的 DHCPv6 服务器是不会有任何作用的 ,需要配合 Router Advertisement ,并且设定正确的 Flags 才能令设备从 DHCPv6 服务器获取相关的信息。

Note 2:
SLAAC 下设备一般是 EUI64 生成地址(特点是中间会带 ff:fe ),而这种方式是通过接口 MAC 来直接计算的,反推也非常容易,较为隐私不友好。
不过后来又推出了 IPv6 隐私扩展(IPv6 Privacy Extension)解决了这个痛点。
如果读者使用 Linux,也可以通过 sysctl 的相关参数来开启隐私扩展。

六、开启 knot-resolver(可选)

直接通过 service kresd startrc-update add kresd 就可以开启并让其自动启动了。
它的默认监听是在localhost,需要在 /etc/knot-resolver/kresd.conf 中修改它的监听地址为路由器在局域网中的地址即可。
如果需要让它作为默认的 DNS 服务器的话,可以在 Kea-DHCPv4 的配置文件中修改 DNS 服务器的相关设定。
这样就配置完毕啦。

资料引用

  • 感谢下面的资料:
  1. nftables: Performing Network Address Translation (NAT)
  2. dhcpcd(8) manpages
  3. IPv6 RFCs(system.de)
  4. Kea Administrator Reference Manual
  5. ifupdown-ng manpages
  • 题外补充:
  1. 有关端口转发的配置:
    Multiple NATs using nftables maps
  2. 关于路由器之间的 IPv6 子网大小是否应该使用 /127 :
    [RFC 3627] Use of /127 Prefix Length Between Routers Considered Harmful
    [RFC 6164] Using 127-Bit IPv6 Prefixes on Inter-Router Links
  3. Misc:
    LARTC: Linux Advanced Routing & Traffic Control

写在最后

  • 吐槽1:之前看到 Google 的报告,提到互联网上大部分东西都是重复的,这体现了人类的本性(作者乃复读机是也!),不过本文也是如此,只是自己从其他地方(指一堆用户手册和一些 RFC ,以及博主个人总结的经验)学到了之后又在这里记录下来了而已。希望能对读者有所帮助,不过如果读者发现本文中出现了失误,也非常欢迎读者指出w。
  • 吐槽2: 这些配置都比较通用,如果自己配置路由器时希望路由器同时干一些别的活的话,此处还是推荐 Debian。
  • 吐槽3: 感觉是一个技术力超级低的文章呜呜呜。。。(已经尽力了,咕…)

更新

  • 2023.01.09 补充了 pppoe 模块加载的细节以及 IPv6 部分,修正了部分错误
  • 2022.12.10 整理了格式并补充说明了 dhcpcd。
  • 2022.12.1 初次发表
Built with Hugo
Theme Stack designed by Jimmy