上篇文章 給你們介紹了 nftables
的優勢以及基本的使用方法,它的優勢在於直接在用戶態把網絡規則編譯成字節碼,而後由內核的虛擬機執行,儘管和 iptables 同樣都是基於 netfilter
,但 nftables 的靈活性更高。linux
以前用 iptables 匹配大量數據時,還得須要 ipset
配合,而 nftables 直接內置了集合和字典,能夠直接匹配大量的數據,這一點比 iptables 方便多了,拿來練練魔法真是極好的,很少解釋,請直接看 Linux全局智能分流方案。web
本文將會教你如何配置 nftables 來爲服務器實現一個簡單的防火牆,本文以 CentOS 7 爲例,其餘發行版相似。算法
首先須要安裝 nftables:bash
$ yum install -y nftables複製代碼
因爲 nftables 默認沒有內置的鏈,但提供了一些示例配置,咱們能夠將其 include 到主配置文件中。主配置文件爲 /etc/sysconfig/nftables.conf
,將下面一行內容取消註釋:服務器
# include "/etc/nftables/inet-filter"複製代碼
而後啓動 nftables 服務:微信
$ systemctl start nftables複製代碼
如今再次查看規則,就會發現多了一張 filter
表和幾條鏈:網絡
$ nft list ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
}複製代碼
在 nftables 中,ipv4
和 ipv6
協議能夠被合併到一個單一的地址簇 inet
中,使用了 inet 地址簇,就不須要分別爲 ipv4 和 ipv6 指定兩個不一樣的規則了。多線程
和 iptables 同樣,nftables
的 filter 表包含三條鏈:INPUT
、FORWARD
和 OUTPUT
,通常配置防火牆只須要配置 INPUT
鏈就行了。ssh
首先容許訪問 localhost:tcp
$ nft add rule inet filter input iif "lo" accept
$ nft add rule inet filter input iif != "lo" ip daddr 127.0.0.0/8 drop複製代碼
能夠再優化一下,加上註解(comment)和計數器(counter):
$ nft add rule inet filter input \
iif "lo" \
accept \
comment \"Accept any localhost traffic\"
$ nft add rule inet filter input \
iif != "lo" ip daddr 127.0.0.0/8 \
counter \
drop \
comment \"drop connections to loopback not coming from loopback\"複製代碼
查看規則:
$ nft list chain inet filter input
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
iif "lo" accept comment "Accept any localhost traffic"
iif != "lo" ip daddr 127.0.0.0/8 counter packets 0 bytes 0 drop comment "drop connections to loopback not coming from loopback"
}
}複製代碼
接下來的規則用到一個內核模塊叫 conntrack(connection tracking)
,它被用來跟蹤一個鏈接的狀態。最多見的使用場景是 NAT
,爲何須要跟蹤記錄鏈接的狀態呢?由於 nftables 須要記住數據包的目標地址被改爲了什麼,而且在返回數據包時再將目標地址改回來。
和 iptables 同樣,一個 TCP 鏈接在 nftables 中總共有四種狀態:NEW
,ESTABLISHED
,RELATED
和 INVALID
。
除了本地產生的包由 OUTPUT
鏈處理外,全部鏈接跟蹤都是在 PREROUTING
鏈裏進行處理的,意思就是, iptables 會在 PREROUTING
鏈裏重新計算全部的狀態。若是咱們發送一個流的初始化包,狀態就會在 OUTPUT
鏈裏被設置爲 NEW
,當咱們收到迴應的包時,狀態就會在 PREROUTING 鏈裏被設置爲 ESTABLISHED
。若是收到迴應的第一個包不是本地產生的,那就會在 PREROUTING 鏈裏被設置爲 NEW
狀態。綜上,全部狀態的改變和計算都是在 nat 表中的 PREROUTING
鏈和 OUTPUT
鏈裏完成的。
還有其餘兩種狀態:
RELATED
: RELATED 狀態有點複雜,當一個鏈接與另外一個已是 ESTABLISHED
的鏈接有關時,這個鏈接就被認爲是 RELATED。這意味着,一個鏈接要想成爲 RELATED,必須首先有一個已是 ESTABLISHED
的鏈接存在。這個 ESTABLISHED 鏈接再產生一個主鏈接以外的新鏈接,這個新鏈接就是 RELATED 狀態了。INVAILD
: 表示分組對應的鏈接是未知的,說明數據包不能被識別屬於哪一個鏈接或沒有任何狀態。有幾個緣由能夠產生這種狀況,好比,內存溢出,收到不知屬於哪一個鏈接的 ICMP 錯誤信息。咱們須要 DROP 這個狀態的任何東西,並打印日誌:$ nft add rule inet filter input \
ct state invalid \
log prefix \"Invalid-Input: \" level info flags all \
counter \
drop \
comment \"Drop invalid connections\"複製代碼
查看規則:
$ nft list chain inet filter input
table inet filter {
chain input {
type filter hook input priority 0; policy accept;
iif "lo" accept comment "Accept any localhost traffic"
iif != "lo" ip daddr 127.0.0.0/8 counter packets 0 bytes 0 drop comment "drop connections to loopback not coming from loopback"
ct state invalid log prefix "Invalid-Input: " level info flags all counter packets 0 bytes 0 drop comment "Drop invalid connections"
}
}複製代碼
爲了防止有惡意攻擊者利用 ping 泛洪(ping flood)來進行攻擊,能夠利用令牌桶模型來對 ping 包限速。ping 泛洪的原理很簡單,就是採用多線程的方法一次性發送多個 ICMP
請求報文,讓目的主機忙於處理大量這些報文而形成速度緩慢甚至宕機。
先來介紹一下令牌桶模型。
熟悉 iptables 的朋友應該知道,iptables 經過 hashlimit
模塊來實現限速的功能,而 hashlimit
的匹配方式就是基於令牌桶(Token bucket)的模型,nftables 也相似, 令牌桶是一種網絡通信中常見的緩衝區工做原理,它有兩個重要的參數,令牌桶容量 n
和令牌產生速率 s
:
令牌桶容量 n
:能夠把令牌當成是門票,而令牌桶則是負責製做和發放門票的管理員,它手裏最多有n張令牌。初始時,管理員開始手裏有 n 張令牌,每當一個數據包到達後,管理員就看看手裏是否還有可用的令牌。若是有,就把令牌發給這個數據包,limit 就告訴nftables,這個數據包被匹配了,而當管理員把手上全部的令牌都發完了,再來的數據包就拿不到令牌了;這時,limit 模塊就告訴 nftables ,這個數據包不能被匹配。令牌產生速率 s
:當令牌桶中的令牌數量少於 n,它就會以速率 s 來產生新的令牌,直到令牌數量到達 n 爲止。經過令牌桶機制,能夠有效的控制單位時間內經過(匹配)的數據包數量,又能夠允許短期內突發的大量數據包的經過(只要數據包數量不超過令牌桶 n),真是妙哉啊。
nftables 比 iptables 作的更絕,它不只能夠基於數據包來限速,也能夠基於字節來限速。爲了更精確地驗證令牌桶模型,咱們選擇基於字節來限速:
$ nft add rule inet filter input \
ip protocol icmp icmp type echo-request \
limit rate 20 bytes/second burst 500 bytes \
counter \
accept \
comment \"No ping floods\"複製代碼
上面的規則表示:
echo-request
類型的 ICMP 包創建一個匹配項;500
個字節;20
字節/s再添加一條規則,拒毫不知足上訴條件的數據包:
$ nft add rule inet filter input \
ip protocol icmp icmp type echo-request \
drop \
comment \"No ping floods\"複製代碼
同時還要接收狀態爲 ESTABLISHED 和 RELATED 的數據包:
$ nft add rule inet filter input \
ct state \{ established, related \} \
counter \
accept \
comment \"Accept traffic originated from us\"複製代碼
下面來作個實驗,直接 ping 該服務器的 IP 地址,ping 包大小設置爲 100 字節,每秒發送一次:
$ ping -s 92 192.168.57.53 -i 1
PING 192.168.57.53 (192.168.57.53) 92(120) bytes of data.
100 bytes from 192.168.57.53: icmp_seq=1 ttl=64 time=0.402 ms
100 bytes from 192.168.57.53: icmp_seq=2 ttl=64 time=0.373 ms
100 bytes from 192.168.57.53: icmp_seq=3 ttl=64 time=0.465 ms
100 bytes from 192.168.57.53: icmp_seq=4 ttl=64 time=0.349 ms
100 bytes from 192.168.57.53: icmp_seq=5 ttl=64 time=0.411 ms
100 bytes from 192.168.57.53: icmp_seq=11 ttl=64 time=0.425 ms
100 bytes from 192.168.57.53: icmp_seq=17 ttl=64 time=0.383 ms
100 bytes from 192.168.57.53: icmp_seq=23 ttl=64 time=0.442 ms
100 bytes from 192.168.57.53: icmp_seq=29 ttl=64 time=0.464 ms
...複製代碼
首先咱們能看到前 5 個包的迴應都很是正常,而後從第 6 個包開始,咱們每 6 秒能收到一個正常的迴應。這是由於咱們設定了令牌桶的容量爲 500
個字節,令牌產生速率爲 20
字節/s,而發包的速率是每秒鐘 100
個字節,即每一個包 100
個字節,當發完 5 個包後,令牌桶的容量變爲 0,這時開始以 20
字節/s 的速率產生新令牌(和前面提到的令牌桶算法不太同樣,只有當令牌桶容量爲 0 纔開始產生新的令牌),5 秒鐘以後,令牌桶的容量變爲 100
個字節,因此 6 秒鐘後又能收到正常回應。
接收其餘類型的 ICMP
協議數據包:
$ nft add rule inet filter input \
ip protocol icmp icmp type \{ destination-unreachable, router-advertisement, router-solicitation, time-exceeded, parameter-problem \} \
accept \
comment \"Accept ICMP\"複製代碼
接收 IGMP
協議數據包:
$ nft add rule inet filter input \
ip protocol igmp \
accept \
comment \"Accept IGMP\"複製代碼
這一步咱們將 TCP 和 UDP 的流量拆分,而後分別處理。先建立兩條鏈:
$ nft add chain inet filter TCP
$ nft add chain inet filter UDP複製代碼
而後建立一個命名字典:
$ nft add map inet filter input_vmap \{ type inet_proto : verdict \; \}複製代碼
字典的鍵表示協議類型,值表示判決動做。
往字典中添加元素:
$ nft add element inet filter input_vmap \{ tcp : jump TCP, udp : jump UDP \}複製代碼
最後建立一條規則拆分 TCP 和 UDP 的流量:
$ nft add rule inet filter input meta l4proto vmap @input_vmap複製代碼
其中,meta l4proto
用來匹配協議的類型。
最後再瞄一眼規則:
$ nft list ruleset
table inet filter {
map input_vmap {
type inet_proto : verdict
elements = { tcp : jump TCP, udp : jump UDP }
}
chain input {
type filter hook input priority 0; policy accept;
iif "lo" accept comment "Accept any localhost traffic"
iif != "lo" ip daddr 127.0.0.0/8 counter packets 0 bytes 0 drop comment "drop connections to loopback not coming from loopback"
ct state invalid log prefix "Invalid-Input: " level info flags all counter packets 95 bytes 6479 drop comment "Drop invalid connections"
icmp type echo-request limit rate 20 bytes/second burst 500 bytes counter packets 17 bytes 2040 accept comment "No ping floods"
icmp type echo-request drop comment "No ping floods"
ct state { established, related } counter packets 172135 bytes 99807569 accept comment "Accept traffic originated from us"
icmp type { destination-unreachable, router-advertisement, router-solicitation, time-exceeded, parameter-problem } accept comment "Accept ICMP"
ip protocol igmp accept comment "Accept IGMP"
meta l4proto vmap @input_vmap
}
chain forward {
type filter hook forward priority 0; policy accept;
}
chain output {
type filter hook output priority 0; policy accept;
}
chain TCP {
}
chain UDP {
}
}複製代碼
這一步咱們來處理 TCP 流量,首當其衝的就是 ssh
了,必須得給這位大哥放行啊:
$ nft add rule inet filter TCP \
tcp dport 22 \
ct state new \
limit rate 15/minute \
log prefix \"New SSH connection: \" \
counter \
accept \
comment \"Avoid brute force on SSH\"複製代碼
其次須要放行 Web 服務,和上面同樣,爲了易於管理,方便後續動態添加端口,須要先建立一個命名集合:
$ nft add set inet filter web \{ type inet_service \; flags interval \; \}複製代碼
查看集合:
$ nft list set inet filter web
table inet filter {
set web {
type inet_service
flags interval
}
}複製代碼
向集合中添加元素:
$ nft add element inet filter web \{ 80, 443 \}複製代碼
查看集合:
$ nft list set inet filter web
table inet filter {
set web {
type inet_service
flags interval
elements = { http, https }
}
}複製代碼
放行 Web 服務:
$ nft add rule inet filter TCP \
tcp dport @web \
counter \
accept \
comment \"Accept web server\"複製代碼
若是你還有其餘不可描述的應用,好比 xxx 之類的代理,能夠按照上面的方式添加規則,先建立集合:
$ nft add set inet filter xxx \{ type inet_service \; flags interval \; \}複製代碼
再添加元素:
$ nft add element inet filter xxx \{ 9000-9005, 9007 \}複製代碼
查看集合:
$ nft list set inet filter xxx
table inet filter {
set xxx {
type inet_service
flags interval
elements = { 9000-9005, 9007 }
}
}複製代碼
如今體會到 nftables 集合的強大了吧,能夠是區間,能夠是單個元素組成的集合,也能夠混合,iptables 麻煩讓一讓。
放行不可描述的服務:
$ nft add rule inet filter TCP \
tcp dport @xxx \
counter \
accept \
comment \"Accept xxx\"複製代碼
這一步咱們來處理 UDP 流量,好比上面舉例的不可描述的應用,除了 TCP 端口還有 UDP 端口,具體用處我就不解釋了,本身面向谷歌找答案吧。
到了這一步,連集合都不用建立, 直接複用以前建立的集合,放行不可描述應用的 UDP 數據:
$ nft add rule inet filter UDP \
udp dport @xxx \
counter \
accept \
comment \"Accept xxx\"複製代碼
查看規則:
$ nft list chain inet filter UDP
table inet filter {
chain UDP {
udp dport @xxx counter packets 0 bytes 0 accept comment "Accept xxx"
}
}複製代碼
其餘 UDP 數據均可按此套路模塊化,簡直不要太賞心悅目。
爲了使系統或 nftables 重啓後可以繼續生效,咱們須要將這些規則持久化,直接將規則寫入 /etc/nftables/inet-filter
:
$ echo "#! /usr/sbin/nft -f" > /etc/nftables/inet-filter
$ nft list ruleset >> /etc/nftables/inet-filter複製代碼
開機自動加載 nftables 服務:
$ systemctl enable nftables複製代碼
默認狀況下,開啓日誌記錄後,日誌會直接進入 syslog,和系統日誌混在一塊兒,很差讀取。最好的辦法是將 nftables 的日誌重定向到單獨的文件。
以本文爲例,咱們只開啓了 ct state invalid
和 ssh
的日誌記錄,先在 /var/log
目錄中建立一個名爲 nftables
的目錄,並在其中建立兩個名爲 invalid.log
和 ssh.log
的文件,分別存儲各自的日誌。
$ mkdir /var/log/nftables
$ touch /var/log/nftables/{ssh.log,invalid.log}複製代碼
確保系統中已安裝 rsyslog。如今進入 /etc/rsyslog.d
目錄並建立一個名爲 nftables.conf
的文件,其內容以下:
:msg,regex,"Invalid-Input: " -/var/log/nftables/invalid.log
:msg,regex,"New SSH connection: " -/var/log/nftables/ssh.log複製代碼
最後,爲了確保日誌是可管理的,須要在 /etc/logrotate.d
中建立一個 nftables
文件:
$ cat /etc/logrotate.d/nftables
/var/log/nftables/* { rotate 5 daily maxsize 50M missingok notifempty delaycompress compress postrotate invoke-rc.d rsyslog rotate > /dev/null endscript }複製代碼
從新經過 ssh 鏈接服務器,就能看到日誌了:
$ tail -f /var/log/nftables/ssh.log
Dec 19 17:15:33 [localhost] kernel: New SSH connection: IN=ens192 OUT= MAC=00:50:56:bd:2f:3d:00:50:56:bd:d7:24:08:00 SRC=192.168.57.2 DST=192.168.57.53 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=43312 DF PROTO=TCP SPT=41842 DPT=22 WINDOW=29200 RES=0x00 SYN URGP=0複製代碼
本文教你如何使用 nftables 搭建一個簡單的防火牆,並經過集合和字典將規則集模塊化,後續可動態添加端口和 IP 等元素,而不用修改規則。更復雜的規則將會在後面的文章介紹,下篇文章將會教你如何使用 nftables 來防 DDoS
攻擊,敬請期待。
掃一掃下面的二維碼關注微信公衆號,在公衆號中回覆◉加羣◉便可加入咱們的雲原生交流羣,和孫宏亮、張館長、陽明等大佬一塊兒探討雲原生技術