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


參考文檔

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 構建簡易家用路由器 構建環境: 系統:Ubuntu 14.04 網卡:兩張有線網卡,TL-WN821N 路由器硬件...
    狂奔辣椒閱讀 974評論 0 1
  • 前言 趁著十一假期在家,折騰了一波軟路由,因為相對于硬路由來說,軟路由更具有可配置性,可以根據自己的需求想怎么玩怎...
    恪晨閱讀 18,734評論 1 16
  • 起因 現在市面上有那么多路由器可以選擇,為什么還要自己造一臺呢?很多國產路由器 fork 自 OpenWRT 的源...
    程序員Delton閱讀 3,490評論 5 10
  • ATC介紹 https://github.com/facebookarchive/augmented-traffi...
    yyming閱讀 1,739評論 6 4
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經沒多少時間了。班主任說已經安排了三個家長分享經驗。 放學鈴聲...
    飄雪兒5閱讀 7,575評論 16 22