debian搭建简易路由笔记(未完成外网部分)

debian 简易路由(网关计算机)笔记

目标,利用已有的软件包,多网卡配置一个路由器

还需要继续的配置学习:

  • tc流量控制(学习了部分),完成基础流量优先级分类

  • 多个wan口配置及均衡负载,暂未学习

  • PPPOE拨号(学习了部分),已初步可用

  • IPTV及多播(涉及软件包:igmpproxy 或者 pimd ),暂未学习

我在github看到一个关于pppoe集成到systemd的讨论,觉得可以抄一波作业。


路由能力

  • 动态NAT,debian默认是nat4(对称nat),内核支持的masquerade是对称nat

  • LAN口可以DHCP动态分配IP地址,代理DNS服务。

  • 防火墙,实现常规的流量及安全过滤功能。

  • 可以设置端口映射

  • docker扩展

硬件

  • 主机一台

  • 网卡2口,一口做wan,一口做lan

涉及软件包

系统 :debian10

防火墙 :nftables(代替iptables)

DHCP/DNS服务 :dnsmasq

网卡接口管理服务 :systemd-udevd systemd-networkd

流量控制和策略路由 :iproute2


需要注意的地方

  • ssh的22端口不应该暴露到公网去。

  • 应该尽可能地过滤掉从公网发往本地的请求,路由器上网发起请求大部分情况下都是由内网往公网方向发起的连接。

配置思路

  • (1)使用 systemd.link 配置网卡命名规则,固化网络接口名, nftables 规则匹配规则

  • (2)使用 networkctl (system-networkd的命令行前端)管理网络服务

  • (3)使用 nftables 配置 nat 规则以及其他防火墙规则

  • (4)使用 dnsmasq 绑定 lan 口网卡,启用DHCP服务/DNS服务

安装软件包

sudo apt install nftables dnsmasq iproute2

新建 systemd.link 规则修改网卡名

步骤流程:

  • (1) 卸载掉多余的网络管理软件(主要针对 networkingNetworkManager ),

  • (2) 通过添加systemd.link规则规则,达到修改网卡名的目的(防火墙规则需要判断网卡名)

  • (3) 编写防火墙规则,使得内网流量出去时会被动态做NAT。

  • (4) 创建DHCP服务、DNS服务,为局域网下的主机分配动态分配IP。

1.通过 ip addr 查询 网卡MAC地址

打印网络接口相关信息,获取MAC地址

#ip addr
2: enp4s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 23:33:33:33:33:04 brd ff:ff:ff:ff:ff:ff
    
3: enp5s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 23:33:33:33:33:05 brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.33/24 brd 192.168.31.255 scope global dynamic 

link/ehter 项目对应的是 mac地址,记住他们

  • wan网络接口: 23:33:33:33:33:04

  • lan网络接口: 23:33:33:33:33:05

后续有用到这两个MAC地址的配置根据实际情况替换。

2.通过mac地址匹配udev规则来修改网卡名

这一小节需要参考的文档有 systemd.link 中文手册 ,利用mac地址匹配网卡设备,更改网卡名。

使用systemd.link为 wan、lan 网口创建网络接口名称命名规则的配置,创建配置文件:

sudo touch /lib/systemd/network/01-wan.link
sudo touch /lib/systemd/network/01-lan.link

文件内容如下,主要通过MAC地址进行匹配,参数含义参考 systemd.link 手册,link配置由 systemd-udevd 读取。

#/lib/systemd/network/01-wan.link
[Match]
MACAddress=23:33:33:33:33:04

[Link]
Name=wan


#/lib/systemd/network/01-lan.link
[Match]
MACAddress=23:33:33:33:33:05

[Link]
Name=lan  # 新的接口名称

切换 systemd-networkd 接管网卡,卸载 networkingNetWorkManager 服务

  • 使用 ifupdown 包做网络管理,对应的服务为networking.service

  • 使用 network-manager 对应的则是NetworkManager.service

网络管理软件可能通过两个源头来触发网络配置:服务与udev规则;

如果禁用服务后仍然受影响,需要考虑是否因为udev的规则触发事件导致配置变化。

应该避免多个网络管理服务对同一个网络接口重复配置,这样会出现配置上到冲突。

debian默认使用ifupdown作为网络管理服务,当安装桌面后,还将使用network-manager。

桌面环境通过NetworkManager来配置网络的,如需systemd-networkd与NetworkManager共存,可以用nmcli配置NetworkManager放弃对网络接口的管理(unmanaged)。

简单粗暴的做法是仅保留一个网络管理服务。

1.卸载其他网络管理

sudo apt purge ifupdown network-manager

此处使用 systemd-networkd 作为网络管理服务,它包括:

  • systemd-networkd.service(网络管理服务)

  • systemd-networkd-wait-online.service(等待网络在线服务,用于到达network-online.target阻塞作用)

它还需要域名解释的相关服务:systemd-resolved.service

三个相关服务,对应的命令有 networkctlresolvectl

2.编写 systemd.network 配置文件

创建 10-wan.network10-lan.network 两个配置,文件名可自定义,后缀要求是 .network

sudo touch /etc/systemd/network/10-wan.network
sudo touch /etc/systemd/network/10-lan.network

3.编辑规则

wan口通过DHCP方式上网的配置

wan口暂时设置为DHCP方式上网

wan口网络配置文件内容如下:

#/etc/systemd/network/10-wan.network

[Match]
MACAddress=23:33:33:33:33:04

[Link]
RequiredForOnline=no

[Network]
DHCP=yes
IPForward=yes
NTP=ntp.aliyun.com

lan口网络配置文件内容如下:

# /etc/systemd/network/lan.network

# lan口静态ip 192.168.31.1
[Match]
MACAddress=23:33:33:33:33:05

[Link]
RequiredForOnline=no

[Network]
Address=192.168.31.1/24
IPForward=yes

4.启用 systemd-networkd 服务,允许开机自启

sudo systemctl daemon-reload
sudo systemctl --now enable systemd-networkd

然后重启使新的网络接口名生效。

5.使用 systemd-resolved.service 管理主机DNS,配置关闭监听53端口及5355端口

使用systemd-networkd管理网络,还需要启用systemd-resolved管理本机的DNS服务,确保域名解正常。

编辑systemd-resolved.service 配置文件如下:

# /etc/systemd/resolved.conf

[Resolve]
DNS=223.5.5.5 223.6.6.6
Cache=yes
MulticastDNS=no
DNSStubListener=no
ReadEtcHosts=yes
LLMNR=no

选项配置如下:

  • (1) DNS: 空格分隔的上级DNS服务器,这里使用阿里云dns

  • (2) Cache:设置缓存解释成功的域名

  • (3) DNSStubListener:禁用本地DNS服务器,这个选项设置为no避免监听127.0.0.1:53

  • (4) ReadEtcHosts:设置为yes,发送查询请求前,会优先查询/etc/hosts

  • (5) LLMNR:链路本地多播名称解析,设置为no,它将不会监听5355端口。

  • (6) MulticastDNS:多播的DNS查询关闭

启动并允许开机启动:

sudo systemctl --now enable systemd-resolved.service

由于使用了 systemd-resolved ,dns更新与传统的保持一致。

/etc/resolv.conf -> /run/systemd/resolve/resolv.conf ,保证更新一致。

sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf

sysctl调整参数

对于内核网络调整方面接触较少,本节网上抄作业,但是调整参数选项的含义需要参考linux内核网络文档

主要的调整目标为:

  • 允许流量转发

  • 不允许外网ping内网的IP地址

在linux下,IP并不是绑定网卡的,可能出现这样的情况,A网卡收到一个arp查询,查询的目的IP是B网卡的,但是它可能响应A网卡的MAC地址,而不是B网卡的。

我不想出现,外网企图询问lan口ip,也会获得一个应答。下面的调整,将检查查询请求的源地址,它查询的目的IP,不符合规则,则不做出响应。

添加以下配置:

# /etc/sysctl.conf

net.ipv4.tcp_syncookies=1
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.default.arp_ignore = 1
net.ipv4.conf.default.rp_filter = 0

调整说明:

  • accept_source_route = 0 不接受路由报头。

  • arp_announce = 2 这个选项控制系统发送ARP请求时,如何选择ARP数据包源地址,定义接口上发送的ARP请求中的IP报文宣布本端源IP地址的不同限制级别,当值为2时,总是为目的地址使用最佳的本地地址。在这种模式下,忽略IP包中的源地址,并尝试选择喜欢的本地地址与目标主机进行对话。通过在包含目标IP地址的出接口的所有子网中查找主IP地址来选择这种本地地址。如果没有找到合适的本地地址,我们就选择出接口或所有其他接口上的第一个本地地址,希望能够收到对请求的回应,甚至有时不管我们宣布的源IP地址是什么。

  • arp_ignore = 1 这个选项控制系统收到ARP请求的响应,对于收到ARP请求,定义不同的应答方式,值为1时,只有当目的IP地址为入接口上配置的本端地址时才进行应答。

  • rp_filter = 0 严格模式RFC3704中定义的严格反向路径对每个入方向的报文进行FIB测试,如果接口不是最佳反向路径则报文检查失败。缺省情况下,失败的报文将被丢弃。

编写防火墙规则

1.编辑/etc/nftables.conf,写入具体规则

  • 默认从wan侧的访问应该被过滤掉,如访问debian的ssh。

  • 从内网(lan侧)主动发起的访问允许通过。

  • 已经建立的连接双向均可通过,外网<->内网。

  • 允许局域网内访问网关计算机。

  • ICMP协议放行(可以被ping)。

  • 不是从wan口流入到内核的,又路由到从wan口流出的流量(即内网上网流量),在 postrouting 钩子下的链要做 masquerade 规则,做特殊的SNAT,在本路由器处,做源地址转换,src替换成 wan 口的网络地址,dst不不变,响应包从外网返回时,将目的地址dst替换本地局域网的ip,src不变。

此处的规则有个坑,因为 input 钩子下的规则,默认的行为是把阻止链接,如果网关计算机要监听某个端口,需要在 /etc/nftables.conf 里添加放行规则。

/etc/nftables.conf 文件内容(comment关键字可以为规则添加注释,增强可读性)

flush ruleset

# IPv4协议簇的表,用于储存nat相关的规则,此处只用到 prerouting 和 postrouting 的钩子
# 这里有个问题,如果{后的空行,因为对齐的原因被补全了个tab,可能出现语法错误,如果单独空行,不应该用任何的空白符号占用它,但是可以用回车换行,单行即无注释又无定义语句,且有空白字符占用空行,可能出现多余的字符造成nft语法解释错误

table ip nat {
        chain prerouting-public {
            type nat hook prerouting priority 100; policy accept
            #如果需要端口转发,则在PREROUTING钩子的链做DNAT
            iif wan tcp dport 65533 dnat to 192.168.31.2:22 comment "dnat: :65533 => 192.168.31.2:22"
            # 把wan口进来的流量,目标端口是65533的发往192.168.31.2的22端口
        }
        chain postrouting-public {
                type nat hook postrouting priority 100; policy accept;
                #做动态SNAT (备注②)
                meta oif wan iif != wan ip saddr 192.168.31.0/24 masquerade comment "外网NAT规则"
        }
}


# IPv4防火墙规律规则,主要针对进本机的流量
table ip filter {
        ct helper ftp-standard {
                type "ftp" protocol tcp;
        }
        chain input-public {
                #默认的策略是不允许流量通过
                type filter hook input priority 0; policy drop;

                iif {lo,lan} accept comment "允许lo口、lan口流进的内网流量通过"
                #备注①
                ct state {established,related} accept comment "允许从内部主动发起的链接通过"
                ip protocol { icmp, igmp } counter accept  comment "ICMP/IGMP放行"
                tcp dport ftp ct helper set "ftp-standard" comment "ftp放行"
                ct state invalid counter drop comment "记录并抛弃不符合ct规则的流量"
        }

    chain forward-public {
                type filter hook input priority 0; policy drop;
                iif {lo,lan} accept comment "允许lo口、lan口流进的内网流量通过"
                #备注①
                ct state {established,related} accept comment "允许从内部主动发起的链接通过"
        ct status dnat accept comment "dnat状态的连接可以通过,作用于端口映射"
    }
}


# IPv6防火墙规律规则,主要针对进本机的流量
table ip6 filter {
        chain input-public {
                type filter hook input priority 0; policy drop
                ct state established,related accept comment "允许从路由器内部主动发起的链接通过"
                ct state invalid counter drop comment "记录并抛弃不符合ct规则的流量"
                iif {lo, lan} accept comment "允许lo口、lan口流进的内网流量通过"
                ip6 nexthdr { icmp } accept comment "ICMP放行"
        }

    chain forward-public {
                type filter hook input priority 0; policy drop;
                iif {lo,lan} accept comment "允许lo口、lan口流进的内网流量通过"
    }

}

聊一下 ct state,通过nft describe 命令可以看到它的合法的值

~ $ nft describe ct state
ct expression, datatype ct_state (conntrack state) (basetype bitmask, integer), 32 bits

pre-defined symbolic constants (in hexadecimal):
        invalid                         0x00000001
        new                             0x00000008
        established                     0x00000002
        related                         0x00000004
        untracked                       0x00000040

备注①:ct state {established,related} accept; 这条规则匹配链接状态的,链接状态和iptables的保持一致,有4种状态,ESTABLISHEDNEWRELATEDINVALID ,untracked状态是被放弃连接跟踪的,他是无状态的,没法判断它到底属于哪种。

客户端发出请求,服务端返回结果,源地址/目的地址刚好是相反的,第一个穿越防火墙的数据包,链接状态是 NEW

后续的数据包(无论是请求还是应答)链接状态是 ESTABLISHED ,有一种情况,类似于FTP,区分数据端口和控制端口的,被动产生的数据包,他需要helper获取数据端口,并在规则中放行。

第一个包,但是不属于任何链接中的,它的状态是 RELATED ,而这个链接产生后,这条链路后续的数据包状态都是 ESTABLISHED

invalid 是所有状态之外情况的数据包。

又有新的问题来了,对于端口映射这种情况,怎么区分是端口映射的数据包并且让他通过防火墙?

利用另外一种状态的判断,ct status

~ $ nft describe ct status
ct expression, datatype ct_status (conntrack status) (basetype bitmask, integer), 32 bits

pre-defined symbolic constants (in hexadecimal):
        expected                        0x00000001
        seen-reply                      0x00000002
        assured                         0x00000004
        confirmed                       0x00000008
        snat                            0x00000010
        dnat                            0x00000020
        dying                           0x00000200

由于nat发生在prerouting上的规则,dnat后,转发至其他主机,可能会走forward,forward默认规则设定成了DROP,因此在forward处还得放行ct state不为established,但是ct status 为dnat的数据包。

不满足条件的数据包在forward处丢弃了。

带状态的防火墙具体的情况分析:

  • input hook 拦截的是流往本机的,

  • (1) debian路由器本机作为客户端,主动去访问服务端(访问网站的情况),此时请求包发送出去,第一个数据包是 NEW 状态, netfilter 跟踪这条链接的状态。

  • (2) 请求发出后,被回应时,这条连接下一个返回的数据包,已经是 ESTABLISHED 状态,往后都保持这个状态,但是如果此时防火墙,没有配置允许相关状态的链接通过,默认策略又设定为drop,会导致返回的包可以发出去,但是回来时被 input 默认 DROP 策略过滤。

  • (3) 允许 ESTABLISHED 状态的数据包通过,可以让本机主动发出的请求后,回来的数据包也可以顺利通过防火墙。

备注②:masquerade 是一种特殊的SNAT,按nftables的wiki说明,它只有在postrouting的钩子下才有意义的,此时,已经选中了路由,准备要发往对应网卡发送队列了,masquerade会把源ip替换成该网卡对应的ip,然后记录这条信息,当数据返回的时候(prerouting),它会查找记录,又会把NAT前的源ip替换成目的的IP(DNAT),这样,相当于“隐藏”了网关计算机自身,把链路转发到masquerade前的源ip主机。

nftables 不像 iptables 有内置的链,它是没有预设的链的,链的名称可以用户自定义,但是nftables兼容iptables的配置,如果使用docker的时候,就容易出现一个问题,docker会改动防火墙规则,同时它会清空我的链,所以我并不喜欢让docker使用iptables做端口映射,内网直接通过路由转发来访问容器IP,

如果要处理docker的iptables和本机的nftables冲突,有2种选择

  • 放弃docker的端口映射(--publish,-p)功能,使用路由转发的方式,每个容器都是一台独立的“主机”,有容器子网的IP。

  • 让dockerd跑在另一个名称空间(name space), 不同的ns,拥有独立的防火墙规则,互不影响,例如 systemd-nspawn ,用特权容器跑docker。

DNS/DHCP服务配置

使用dnsmasq守护进程作为网关计算机的DNS以及DHCP服务。

/etc/systemd/resolved.conf的默认设置会占用了53端口和5355端口,需要额外设置,详情看前面。

1.安装

sudo apt install dnsmasq

2.配置

dnsmasq 默认的配置都不要了,把sysV启动的软连接全部删了,把 dnsmasq.service 的服务也删除了。可以满足多个网卡配置dnsmasq,不应该使用包内自带的服务配置,由自己编辑一个适合情况的配置文件。

#删除sysV init 开机自启的软连接
find /etc/rc[0-9S].d -name "*dnsmasq*" -exec sudo rm {} \;
#删除dnsmasq默认包的服务配置
sudo rm sudo rm /lib/systemd/system/dnsmasq.service
#重载配置
sudo systemctl daemon-reload

编写新的dnsmasq服务的service模板,以适应多个网卡单独启动不同的dnsmasq服务。

sudo touch /etc/systemd/system/dnsmasq@.service

编写 /etc/systemd/system/dnsmasq@.service 内容:

# /etc/systemd/system/dnsmasq@.service

[Unit]
Description=IPv4 DHCP server on %I
Wants=network.target
After=network.target

[Service]
Type=forking
PIDFile=/run/dnsmasq@%I.pid
ExecStart=/usr/sbin/dnsmasq --except-interface=lo --pid-file=/run/dnsmasq@%I.pid --log-facility=/var/log/dnsmasq/%I.log   --interface=%I --conf-file=/opt/router-config/dnsmasq/%I/dnsmasq.conf  --dhcp-leasefile=/opt/router-config/dnsmasq/%I/dnsmasq.leases --dhcp-hostsfile=/opt/router-config/dnsmasq/%I/hosts.d/ --resolv-file=/opt/router-config/dnsmasq/%I/resolv.conf --conf-dir=/opt/router-config/dnsmasq/%I/conf.d
KillSignal=SIGTERM
KillMode=control-group
ExecReload=/bin/kill -HUP $MAINPID

[Install]
WantedBy=multi-user.target

%I 是模板实例化后,被替换的名称,例如,dnsmasq@lan.service%I 就会被替换成 lan

软连接新建一个实例,这个服务是针对 lan 网卡的。创建 dnsmasq 配置存放的文件夹,我放在 /opt/router-config/dnsmasq/lan/ 下,

# 针对lan创建dnsmasq服务软连接,实例化模板
sudo ln -s /etc/systemd/system/dnsmasq@.service /etc/systemd/system/dnsmasq@lan.service
# 针对lan创建网卡的配置文件夹
sudo mkdir -p /opt/router-config/dnsmasq/lan/
# 创建dnsmasq配置文件
sudo touch /opt/router-config/dnsmasq/lan/dnsmasq.conf
# 创建租约记录文件(分配的客户端,都会被记录到这)
sudo touch /opt/router-config/dnsmasq/lan/dnsmasq.leases
# 创建静态分配的IP主机配置(dhcp-host)
sudo mkdir -p /opt/router-config/dnsmasq/lan/
# 创建额外配置目录conf.d
sudo mkdir -p /opt/router-config/dnsmasq/lan/conf.d
# 创建自定义的resolv.conf文件
sudo touch /opt/router-config/dnsmasq/lan/resolv.conf
# 创建日志存放的文件夹
sudo mkdir -p /var/log/dnsmasq
# 创建pid文件
sudo touch /run/dnsmasq@lan.pid 
  • /var/log/dnsmasq 为日志文件夹,文件夹下,网卡名.log 为不同服务实例独立的日志。例如 lan.log

  • /run/dnsmasq@lan.pid 为守护进程的pid记录文件。

  • /opt/router-config/dnsmasq/lan/ 目录结构如下:

lan/
├── conf.d
│   └── anti-ad-for-dnsmasq.conf
├── dnsmasq.conf
├── dnsmasq.leases
├── hosts.d
│   └── phone.ip
└── resolv.conf

  • hosts.d 文件夹记录了绑定mac分配IP的配置。

  • conf.d dnsmasq服务的补充配置,这里用来设定屏蔽广告域名。(anti-ad域名屏蔽列表

配置说明:

--except-interface 排除监听的网卡,此处设定不监听lo(127.0.0.1)。

--pid-file 守护进程的pid文件路径,此处systemd.service需要通过pid文件确定主进程的PID,精确控制停止进程。

--log-facility 服务日志保存路径

--interface dnsmasq服务绑定的网卡名,模板用%I绑定

--conf-file 配置文件的路径

--dhcp-leasefile 租约记录文件的路径,这个文件由dnsmasq自己管理,不改动内容

--dhcp-hostsfile 针对特定的mac,分配ip绑定的主机配置,如果是文件夹,则读取文件夹下所有的文件,此处使用的是文件夹,一个文件对应一个主机。

--resolv-file 自定义resolv.conf路径,上游DNS服务器的配置文件,此处是阿里云的dns

--conf-dir 导入目的目录下的所有配置文件,可以用来导入额外的配置,例如屏蔽部分域名。

dnsmasq.conf 配置文件格式,和命令行参数保持一致,但是不同的地方是,没有 -- 前缀,例如 port = 53 等价命令行 --port=53,一行一个参数,对于没有右值的,则直接填左值,例如 log-dhcp 等价命令行参数 --log-dhcp 。其他的选项就可以根据manpage来配置,主要分成两部分,一部分配置dns服务器的,另外一部分则是关于DHCP的配置。此处只配置DHCP部分。

主配置文件内容:

# /opt/router-config/dnsmasq/lan/dnsmasq.conf

# DNS服务监听的端口,一般设置成53,绑定的地址为网卡的ip
port = 53
listen-address=192.168.31.1
# 缓存条目1000条
cache-size=10000

# DNS查询所有上游的服务器
all-servers
# 允许dbus总线
enable-dbus
# 本地hosts域名
bogus-priv

##########DHCP#####################
# DHCP 记录日志
log-dhcp
# dhcp选项 router为默认网关
dhcp-option=option:router, 192.168.31.1
# netmask 子网掩码
dhcp-option=option:netmask, 255.255.255.0
# domain
dhcp-option=option:domain-name, "my-router"
# dhcp分配给客户端的dns服务器
dhcp-option=option:dns-server, 192.168.31.1
# MTU值
dhcp-option=option:mtu, 1500

# 动态分配ip的范围,从2~254,租约时间为10小时
dhcp-range=192.168.31.2, 192.168.31.254, 10h

更多dhcp-option的选项可以通过dnsmasq --help dhcp查询到。

/opt/router-config/dnsmasq/lan/resolv.conf 内容(它定义了上游查询服务器,最多只能使用2个):

nameserver 223.5.5.5 223.6.6.6

MYCOMPUTER内容,针对mac地址为 23:33:33:33:33:44 的主机固定分配IP为 192.168.31.5

/opt/router-config/dnsmasq/lan/hosts.d/MYCOMPUTER 文件内容:

23:33:33:33:33:44, 192.168.31.5

文件格式是 --dhcp-host 选项的右值,以上相当于 --dhcp-host=23:33:33:33:33:44, 192.168.31.5 ,它可以是IPv4的,也可以是IPv6的,具体参考manpage。也是一行一个参数,不同的主机,可以分开不同的文件写配置,也会被读入。

文件配置完毕后,需要尝试启动 dnsmasq@lan.service ,并且允许它开机自启。

sudo systemctl daemon-reload

# 允许开机自启,并启动服务
sudo systemctl --now enable dnsmasq@lan.service

# 查看服务工作状况
sudo systemctl status dnsmasq@lan.service

手动 dnsmasq 确实太繁杂了,除了 dnsmasq 还可以用 systemd-networkd 配置简单的DHCP+DNS server,或者使用 AdguardHome 有个webui界面。


PPPoE方式上网

经过一段时间的努力,开始改造成PPPoE拨号上网,和DHCP方式上网不一样,PPPoE方式上网需要额外的配置

PPPoE需要的软件包

sudo apt install pppoe

pppoe包包含了一些启动脚本

/usr/sbin/pppoe
/usr/sbin/pppoe-connect
/usr/sbin/pppoe-relay
/usr/sbin/pppoe-server
/usr/sbin/pppoe-sniff
/usr/sbin/pppoe-start
/usr/sbin/pppoe-status
/usr/sbin/pppoe-stop

pppoe包依赖ppp包,而ppp包又包含了一些ppp协议相关支持

PPPoE方式上网需要改动wan口的配置,上述wan采用的是dhcp/静态IP上网的方式,pppoe上网可能需要解决一些问题

  • ppp拨号
  • 是否需要访问光猫管理接口

pppoe上网,wan口是不需要配置ip地址的,因为流量经过pppoe协议封装。我将做以下调整

  • 添加 netdev 配置 ,将一个wan口扩展成两个 macvtap 虚拟的网络接口,一个用于pppoe拨号,一个用于连接光猫的管理接口,注意他们的mac地址是不一样的。

  • 添加 .network 配置,设定wan口与其他虚拟网络接口的绑定关系,再设置每个接口的IP设置。

由于pppoe拨号的接口是不需要IP地址的,将DHCP关闭。

假设光猫的管理接口是192.168.1.1,虚拟接口与他同网段192.168.1.2的静态IP

注意,以下配置中包含多个文件的片段,留意注释中的文件路径。

# /etc/systemd/network/01-wan-modem.netdev
[NetDev]
Description=wan-modem虚拟网卡用于访问光猫
Name=wan-modem
Kind=macvtap
MACAddress=23:33:33:33:33:06

[MACVTAP]
Mode=private

#######################################
# /etc/systemd/network/01-wan-ppp.netdev
[NetDev]
Description=wan-ppp 虚拟网卡用于PPPOE拨号
Name=wan-ppp
Kind=macvtap
MACAddress=23:33:33:33:33:07

[MACVTAP]
Mode=private


######################################
#  /etc/systemd/network/01-wan.network
[Match]
MACAddress=23:33:33:33:33:04

[Link]
RequiredForOnline=yes

[Network]
Description= wan interface
DHCP=no

########################################
# /etc/systemd/network/01-wan-ppp.network
[Match]
Name=wan-ppp

[Network]
DHCP=no

##########################################
# /etc/systemd/network/01-wan-modem.network
[Match]
Name=wan-modem

[Network]
Address=192.168.1.2/24

更改拨号配置文件

# /etc/ppp/chap-secrets

# 设定宽带用户username 密码是passwd
"username" * "passwd"

##################################################
# /etc/ppp/pppoe.conf

# 指定pppoe拨号的网络接口,需要修改成实际的拨号网卡
ETH="wan-ppp"
# 指定拨号使用的上网账号,修改成实际的宽带用户名
USER="username"

DEMAND=no
PEERDNS=no
DNSTYPE="NOCHANGE"
CONNECT_TIMEOUT=30
CONNECT_POLL=2
PING="."

# 指定PIDFILE
CF_BASE=`basename $CONFIG`
PIDFILE="/var/run/$CF_BASE-pppoe.pid"


SYNCHRONOUS=no
CLAMPMSS=1480
LCP_INTERVAL=30
LCP_FAILURE=3
PPPOE_TIMEOUT=100

FIREWALL=NONE
LINUX_PLUGIN="/lib/pppd/2.4.9/rp-pppoe.so"

由于配置了DNS不会被修改,pppoe不会获取并配置dns到本地,我需要配置一个静态的dns服务器,这里使用 223.5.5.5 ,由于使用了

# /etc/systemd/system/pppoe.service
[Unit]
Description=PPPOE service
BindsTo=sys-subsystem-net-devices-wan.device
After=sys-subsystem-net-devices-wan.device


[Service]
Type=forking
ExecStart=/usr/sbin/pppoe-start
ExecReload=/usr/sbin/pppoe-stop;/usr/sbin/pppoe-start
ExecStop=-/usr/sbin/pppoe-stop
Restart=always

[Install]
WantedBy=multi-user.target

这个配置文件可能还是存在一些问题的,但是勉强能用,主要是让这个服务,要在wan接口启动后,才能启动它(我感觉在network-pre.target之后也行。。。),启动并允许开启自启

sudo systemctl --now enable pppoe

当然这样LAN下的主机没法直接上网,因为老的nft规则,只是针对wan口做了masquerade,引进了wan-modem, wan-ppp两个虚拟接口

所以要进行修改新的规则以适应实际情况。

ppp0网络接口是拨号后创建的,它在系统运行期间,因为pppoe重新拨号可能会丢接口,如果此时使用iif,它找不到这个网口,会报错,所以iif用iifname代替,匹配网络接口名的字符串。

wiki描述,iif效率更高一些,实际使用起来,因流量较少,可以忽略影响。

table ip nat {
        chain prerouting-public {
            type nat hook prerouting priority 100; policy accept
            #如果需要端口转发,则在PREROUTING钩子的链做DNAT
            iif wan tcp dport 65533 dnat to 192.168.31.2:22 comment "dnat: :65533 => 192.168.31.2:22"
            # 把wan口进来的流量,目标端口是65533的发往192.168.31.2的22端口
        }
        chain postrouting-public {
                type nat hook postrouting priority 100; policy accept;
                #做动态SNAT (备注②)
                meta iif wan-modem oif != wan-modem masquerade comment "内网访问光猫网段"
                meta iifname "ppp0" oifname != "ppp0" masquerade comment "pppoe拨号上网NAT规则"
        }

    chain forward-public {
                type filter hook input priority 0; policy drop;
                iif {lo,lan} accept comment "允许lo口、lan口流进的内网流量通过"
                ct state {established,related} accept comment "允许从内部主动发起的链接通过"
        ct status dnat accept comment "允许dnat状态数据通过"
    }
}

TC流量控制

有时需要有限部分用户的流量,例如一些软件的p2p上传流量吃非常狠,例如pt/bt。

流量控制只能控制自己发送给别人情况。不能阻止别人发送给自己。

一般家用带宽,下行1000M,上行可能只有50M。

如果上行被p2p软件占满,也会影响下载的情况。目前做到初步调整流量的优先级

linux的网络接口,可以配置发送流量的排序关系,qdisc(队列规定),主要有三个配置的元素

  • qdisc 队列规定
  • class 分类
  • filter 过滤器

粗略看待,它有两个操作:

  • 把流量放进去
  • 把流量拿出来

流量放进去的顺序是abc,但是不同类型的qdisc,拿出来的顺序可能不一样的。

取出可能是abc(例如先进先出的pfifo),又或者可能是bac,cba,都有可能。可以粗暴的理解,qdisc是决定流量排序规律的。

所有从上层下来的流量,先放入qdisc,下层驱动要发送出去的时候,在从qdisc里取出来,进出的顺序可能被重新排列,大概这么一个过程。

不同规律的qdisc效果不一样,例如先进先出的的pfifo,你按什么顺序放进去。拿出来也是什么顺序。

他的目的是调度上层要发出去的流量。可能是排序,可能是丢弃(限速是通过丢包实现的),

少量丢包是很正常的情况,可能是发送方发太狠了。

class 分类,是针对某个qdisc的,他不是所有的qdisc都有class,有些qdsic是没有分类的,而有些qdisc,他可以自定义多个 class 不同 class 的效果不一样,

例如可能存在发送优先级,例如 prio 的qdisc,他默认分三个 class ,

  • 1:1 band 0
  • 1:2 band 1
  • 1:3 band 2

它按 主要编号:次要编号 这样的格式标识,次要编号0为qdsic本身,其他则是该qdisc的class标识,例如 1:0 是qdisc,而 1:1 是它的一个class,

它会按优先级 先从 1:1 先取出, 高优先级的class上没有数据包,才会继续提优先级稍低的 1:2

qdisc可以嵌套的, 例如,在分到 1:1 的流量,可以再套多一个 prio 就可以形成以下结构

1:0(qdisc prio) 
|
|--1:1(class)--10:0(qdisc prio)
|               |
|               |--10:1
|               |--10:2
|               |--10:3
|--1:2(class)
|--1:3(class)

标识(主要编号:次要编号)是唯一的,这里结构上像一棵树,取出的时候,内核只会和 1:0 交互,按qdisc规则取出,1:1 优先被取,但是 1:1 下又有一个qdisc,它进一步从 10:1 取,然后取 10:1 上的。

1:1 下挂的qdisc,编号不一定是 10:0 ,唯一就好啦。

但是流量放进去,不一定按 1:0 => 1:1 => 10:0 => 10:2 这样的顺序。

有几种影响流量放哪个子类的:

  • netfilter 直接对流量打标,iptables/nftables可以对流量标记,流量会直接放到具体的编号分类,例如 10:2 它会一步到位放到 10:2

  • filter规则,例如,1:0关联的filter,它可以直接让流量入 10:3

  • qdisc默认的存放规则,例如 prio 队列规定,可以映射IP协议的TOS字段,根据优先级把流量分发到不同的 band

band 对应它qdisc的class,按prio的说明,band 0就是:1, band 1是 :2,依次类推

prio默认只分了三个band,0 1 2 ,对应三个class 1:1 1:2 1:3

靠近树根的qdisc是 root 每个网卡只有一个的,内核与 root 交互。存入或者取出流量,树中其他的qdisc,通过 parent 关联到根部的 class,就可以连成一棵大树啦。

# 对ppp0创建一个prio
tc qdisc add handle 1: root prio bands 7
  • handle 1: 指定这个qdisc的标识,也就是上面说的 1:0 ,可以简写成 1:

  • root 指定它是这网卡的 root qdisc,内核和这个qdsic交互。

  • prio 指定要采用哪种qdisc,这里选的 prio ,特性是对流量优先级做简单分类

  • bands 7 这是 qdisc 的参数,不同的qdisc参数不一样,这里是指定7个band,对应的class是 1:11:7

疑惑,它怎么确定流量放哪个 class ?

prio 的说明,它有个 priomap 参数,一共16个数字

TOS     Bits  Means                    Linux Priority    Band
------------------------------------------------------------
0x0     0     Normal Service           0 Best Effort     1
0x2     1     Minimize Monetary Cost   0 Best Effort     1
0x4     2     Maximize Reliability     0 Best Effort     1
0x6     3     mmc+mr                   0 Best Effort     1
0x8     4     Maximize Throughput      2 Bulk            2
0xa     5     mmc+mt                   2 Bulk            2
0xc     6     mr+mt                    2 Bulk            2
0xe     7     mmc+mr+mt                2 Bulk            2
0x10    8     Minimize Delay           6 Interactive     0
0x12    9     mmc+md                   6 Interactive     0
0x14    10    mr+md                    6 Interactive     0
0x16    11    mmc+mr+md                6 Interactive     0
0x18    12    mt+md                    4 Int. Bulk       1
0x1a    13    mmc+mt+md                4 Int. Bulk       1
0x1c    14    mr+mt+md                 4 Int. Bulk       1
0x1e    15    mmc+mr+mt+md             4 Int. Bulk       1

它是和IP数据包,8位的TOS字段对应的映射,通过抓包发现TOS大部分时候取值都是0。

考虑7个class这样分配:

  • 1:1 ~ 1:2 保留,用来做优先上网设备的流量分类

  • 1:3 1:4 1:5 对应原来3 band的 0 1 2,主要用来做跑常规的上网上行流量

  • 1:6 1:7为低优先级流量,用来做p2p网络流量分类,例如bt/pt,放在1:7

那么正常的映射关系就不能按原来的 0 1 2走前3个class,计划7个class,对应7个band,做以下调整:

  • 0x0 0x2 0x4 0x6 属于普通流量。走的默认band1,3 band分类走band 1,也就是1:2,7band分类计划走1:4,对应的band 3。

  • 0x8 0xa 0xc 0xe 属于低优先级流量,3band分类时走band 2(也就是1:3),而7 band分类时,我想要它走1:7,对应的band 6,。

  • 0x10 0x12 0x14 0x16 属于高优先级流量,3band分类时走1:1,7band分类计划走1:3,对应band 2。

  • 0x18 0x1a 0x1c 0x1e 也是设置成普通流量优先级,对应的band 3。

于是得到16个数字

3 3 3 3
6 6 6 6
2 2 2 2
3 3 3 3

创建 ppp0 的root qdisc的tc命令修改成

tc qdisc add handle 1: root prio bands 7 priomap 3 3 3 3 6 6 6 6 2 2 2 2 3 3 3

到此,正常的流量走的的 1:4,通过bmon命令应该可以看到1:4有上传流量(测速试试)。

但是prio实际测试。它并不能保证高优先级分类的一定能吃满带宽,它没办法去抢低优先级p2p流量的带宽。但是至少它不会被压榨的根本没法用的地步。

如果对p2p流量进行限速,则需要对 1:7,再挂一个qdisc,例如tbf,它的作用是限制流量通过的速率,如果超出速率,还继续往tbf塞流量,它可能会丢掉,达到限速的效果。

但是这些qdisc它并不知道实际可用的物理带宽。如果是固定的带宽,可以创建tbf时指定。

限速10Mbit

tc qdisc add dev ppp0 parent 1:7 handle 70: tbf rate 10mbit burst 1000kb limit 100mbit

和先前创建root qdisc类似,但是它指定的不是root,而是parent 1:7,1:7是它的父级。

  • handle 70: 指定这个qdisc的表示,70: 70:0都是一个意思

  • tbf 选定的qdisc类型,tbf的作用是限制速率。后续跟着它的设置参数

  • rate 10mbit 限制10Mbps

  • burst 10000kb按手册的说法是令牌桶的大小

  • limit 100mbit 排队等待令牌的字节数

我粗浅的理解,是这样的,把tbf想象成一个桶,有按一定的速度往桶里放球(令牌)。

而流量要进tbf,也是放在一个队列里(和这个桶无关)。

桶只能装busrt指定若干个球,如果桶满了。暂时不会往里放。

tbf流量从队列里出队,每次出去一些流量,必须从桶里拿走一些小球。

如果没有球拿,那流量就不可以出去。在队列堆积着。

如果球放入的速度和流量出去的速度刚好相同,这些流量那就完美通过tbf,不会被丢。

如果小球放入的速度太快,桶一直不会是空的状态,那么流量也是有多少走多少,也不会出现丢,没有"限速"的感觉,因为流量太少了。

所以限速调的很宽,桶里一直有球的。流量都能按最大的速率进出,就没有限速的效果,只有小球不够用,才有限速的效果,

如果小球放入的速度很低,即限速太死了,就会出现这种情况:

桶里没球了,流量堆积在队列里,塞不下了。再往里灌流量。直接会被丢掉。这个是时候发生了丢包。

如果发送方能降低发送速率以匹配限速是最好的,如果没有措施,丢包情况会很糟糕。

但是总体出去的流量速度得到了限制,限速的效果有了。

如果桶调的很大,如果长时间没有流量,桶满了,突然有一波流量要进出。他就会在短时间内消耗掉小球,速率很快,一直等到通空了,只能等待一点点放进桶里的小球。

如果排队的队列调得(limit)很大,(burst)桶又很小,限速(rate)也很慢,短时间内冲去一波大流量,桶空了,队列迅速堆积,但是它就是没有堆满,它以低速率出去,高速率进来,这段时间的流量,延迟就高了,但是它丢包情况可能还好。后续队列满,上层流量往里再冲,就得丢包了。

看tbf的说明,rate burst limit要按一定的配比,工作才更完美。

对于英特尔的 10mbit/s,如果您想达到配置的速率,至少需要 10KB 的burst!

tbf限速,要考虑丢包和延迟的平衡。

除了对应qdisc的分类规则,还可以通过tc-filter或者nftables对数据包打标,让流量落入具体某个标识的qdisc。

对于debian本机的流量,可以通过多种条件匹配,例如:

  • 进程的cgroup
  • 进程的用户身份:uid或者gid

域名或者根据端口限速是麻烦的。因为域名对应的ip可能发生变化,需要不断更新。

如果本地有下载服务。那种p2p的,可以根据p2p源端口限速,或者通过cgroup

cgroups


table inet  qos {
    chain qos-bt {
        type filter hook output priority 0; policy accept;
        iifname != "ppp0" accept comment "不从ppp0接口发出的流量不处理"
        # 让uid用户1000的流量优先级提高到1:2
        meta skuid 1000 meta priority set 1:2 counter accept comment "用户上网流量优先"
        # 限制bt下载软件上传端口网速,假设qbitorrent使用51234端口
        udp sport 51234 meta priority set 1:7 counter accept comment "qb上传优先级调低"
    } 
}

上述在 output hook中的规则只能处理debian本机发出的流量。应该根据不同的流量特点。在合适的hook中添加规则,例如socket cgroupv2 相关的只能在ouput处有效。

不同的nft版本支持的特性在变化,应该要参考使用的nft在哪个版本引入支持。


TODO:

  • IPv6分配,由于IPv4和IPv6的DHCP分配地址方式不一样,具体详细的选项,还需要琢磨下手册。

  • 网卡启动和关闭时,触发服务重启

笔记最后更新时间:2022-2-10

笔记最后更新时间:2023-12-21


参考文档

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容

  • 构建简易家用路由器 构建环境: 系统:Ubuntu 14.04 网卡:两张有线网卡,TL-WN821N 路由器硬件...
    狂奔辣椒阅读 856评论 0 1
  • 前言 趁着十一假期在家,折腾了一波软路由,因为相对于硬路由来说,软路由更具有可配置性,可以根据自己的需求想怎么玩怎...
    恪晨阅读 18,466评论 1 16
  • 起因 现在市面上有那么多路由器可以选择,为什么还要自己造一台呢?很多国产路由器 fork 自 OpenWRT 的源...
    程序员Delton阅读 3,363评论 5 10
  • ATC介绍 https://github.com/facebookarchive/augmented-traffi...
    yyming阅读 1,589评论 6 3
  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 7,401评论 16 21