iptables防火牆調試,想打印個日誌就這麼難

背景

怎麼會講這個話題,這個說來真的長了。但是,長話短說,也是可以的。

我前面的文章提到,線上的服務用了c3p0數據庫連接池,會偶發連接泄露問題,而分析到最後,又懷疑是db側主動關閉連接,或者是服務所在機器和db之間有防火牆,防火牆主動關閉了連接。導致我們這邊socket看着還健康,實際在對端已經失效了,然後我們在這個socket發消息過去,對方一直不回覆,我方沒設置超時時間,導致長時間阻塞在read方法上(等待對方響應)。

然後,爲了模擬這種場景,我是想了一些辦法,我搜到的傳統一點的辦法是,我們是oracle,支持sleep命令,執行後,數據庫就像死了一樣,這樣應該可以模擬出read timeout。但是吧,開發環境就那一個數據庫,很多人用,我去sleep它,不太好,我自己搭吧,有點懶,而且,感覺這個方法不夠通用,換個別的數據庫超時,或者是什麼服務超時,豈不是又要另尋他法?

個人偏愛通用的東西,比如這種超時模擬的場景,不只是可以模擬db,也可以模擬其他遠端服務。

一開始,我想的辦法是nginx四層代理(使用stream模塊),比如客戶端連接nginx,然後又nginx轉發流量到後面的db啥的,然後當後端db返回響應時,我在nginx上sleep個n秒再返回,就能達成客戶端超時的場景,但是,經過我後面多方研究,找到了一個在nginx或者openresty上支持sleep的模塊(https://github.com/openresty/stream-echo-nginx-module),但是,這個模塊明確寫了,不能和proxy模塊一起用:

  • The commands of this module cannot be mixed with other response-generating modules like the standard ngx_stream_proxy module in the same server {} block, for obvious reasons.

另外,它也說了,它雖然sleep,但是不會阻塞nginx的eventloop線程。

image-20230722164116965

再後來,也試過stream-lua-nginx-module模塊,打算用lua代碼來sleep,發現依然不行,應該怎麼說呢,我的理解是,nginx本身就是一個非阻塞的模型,所以,想要靠sleep這些去阻塞它,反而做不到。

所以,後面我放棄了這條路。

再後來,我是偶然間來了靈感,認爲可以靠機器上的防火牆,將db返回的數據包給丟掉,這樣,就可以模擬出讀超時的場景了,事實上,這條路是正確的,但是,主要是網上資料比較少,所以也踩了好多坑才折騰出來。

最近就打算好好介紹下這個防火牆,但是,我發現想要比較系統地介紹它,還是比較困難,我自己又現學了好一陣,每次想寫的時候,發現還是有太多不瞭解的,於是擱筆。

今天是打算先介紹下iptables的nat功能,結果實驗的時候,死活有問題,然後想着怎麼打日誌,結果打日誌又出問題,折騰了好久(搜索引擎上關於這個的資料較少),這裏就記錄下來。

iptables簡單介紹

在centos 6時代,我記得機器上默認的防火牆就是iptables;centos 7,基本換成了firewalld。我這次最先折騰的是firewalld,後來一直沒成功,後來換到iptables結果就好了,因此最近都是在折騰iptables,這裏就先介紹iptables,後續再去研究firewalld。

安裝、啓動

一般來說,先停止firewalld,避免互相影響。

systemctl status firewalld
systemctl stop firewalld

安裝iptables並啓動:

yum install -y iptables-services
// 啓動
systemctl start iptables
// 查看狀態
systemctl status iptables
如果提示綠色的“active (exited)”,則iptables已經啓動成功。
// 停止
systemctl stop iptables

默認規則

安裝完成後,或者啓動後,執行如下命令,會發現有一些默認規則。

image-20230722172302338

所謂規則,在防火牆中,就是:針對某個滿足條件1、條件2、條件n的包,需要採取什麼動作,而我們可能有很多這樣的規則,形成一個或多個list。

而上面看到的默認規則,存放在文件 /etc/sysconfig/iptables

vim /etc/sysconfig/iptables

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
// 1 state如果爲ESTABLISHED,可以粗略理解爲tcp中三次握手後的數據包,對這種包,放行。當然,防火牆不止處理tcp,所以這個概念還是略有差異
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
// 2 對於ping包,放行
-A INPUT -p icmp -j ACCEPT
// 3 本機的loopback網卡的包,放行
-A INPUT -i lo -j ACCEPT
// 4 對於本機的ssh連接請求包,放行
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
// 5 其他的包,都拒絕
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

使用trace定位規則問題

下面的一些iptables的東西,我們還沒介紹,所以可能導致看不太懂,可以簡單看下,我們本篇就是介紹,如何保證debug的日誌能看到。

一般來說,對於開發同學來說,場景也就是比較簡單,如禁止/允許訪問某端口,或者只禁止允許某些ip等,比如,假設我們希望放行8080端口,要怎麼設置呢?

我們得設想一下,客戶端訪問8080端口的話,傳過來的packet一般長啥樣,是不是tcp協議這一層的目標端口是8080呢,對吧?那我們只要設置一個規則,將這種包篩選出來,放行即可。

我先隨便啓動一個8080的監聽:

python -m SimpleHTTPServer 8080

然後我們新建一個rule,準備放行8080端口:

iptables -i filter -I INPUT 1 -p tcp -m tcp --dport=8080 -j ACCEPT

然後本機執行telnet,結果發現連不上。。。

爲啥呢,因爲我命令寫錯了,假設我不知道我命令寫錯了,感覺沒匹配上我的這個規則呢,那我怎麼知道,iptables到底最終匹配到哪個rule了呢,會不會還沒到這個rule就出問題了呢?

不要着急,我們加上下面這個命令:

iptables -t raw -I PREROUTING -p tcp -m tcp --dport 8080 -j TRACE

這個命令,分段來看:

-t raw : 指定要操作的table,其實就是傳說中的三表五鏈中的三表之一,table這裏可以先不管
-I PREROUTING: -I表示插入規則,後面指定要插入的鏈(鏈就是規則的集合)
上面兩個選項,暫時不理解可以先不管,其實就是指定了一個時機,這個時機,發生在防火牆處理網絡報文的最前面。
此時,我們指定一個rule:
-p tcp -m tcp --dport 8080 -j TRACE
即,對於目標端口爲8080的報文,-j TRACE ,表示進行跟蹤,這樣的話,防火牆會在日誌打印出最終匹配上了哪些rule

增加了上述命令後,我們重試一次telnet,然後打開/var/log/messages日誌:

image-20230722175600395

能看到對於某一條客戶端報文(這裏其實就是客戶端發過來的第一次tcp握手,注意看,第二行的ID都是35701,所以都是針對同一條報文的日誌),一共打印了三行:

raw:PREROUTING:policy:2
nat:PREROUTING:policy:1
filter:INPUT:rule:6

這裏其實就是表示,一個順序經過了三個規則。

首先是raw:PREROUTING,即raw表的PREROUTING鏈的默認規則(policy:2),看下圖,raw表只有我們那個trace規則,除此之外,啥都沒有,所以匹配了PREROUTING鏈的默認策略:

image-20230722180021766

按照網上的說法,下圖紅框處就表示默認策略:

image-20230722181715290

他麼的,爲啥說是網上的說法呢,因爲,我本來想實驗一下,把這塊改成DROP再看看效果:

 // 不要執行!!!這個命令就是設置默認策略爲drop
 iptables -P PREROUTING DROP -t raw 

結果,他麼的,馬上shell就斷了,然後這是工作服務器,我都以爲必須找運維才能解決了,後來好歹想起來有個平臺,可以重啓服務器,重啓後,iptables我是沒設置開機啓動的,這條rule也是沒有持久化的,所以我才能連上shell,在這裏繼續做實驗。

扯遠了,執行完第一條鏈的默認策略ACCEPT後,進入表nat的PREROUTING鏈:

[root@hx168-access ~]# iptables -nvL -t nat
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination    

這個也是空的,沒有rule,所以最終是執行了PREROUTING:policy:1默認策略:ACCEPT,然後進入到下面的:

filter:INPUT:rule:6。

這就是filter表的INPUT規則鏈的第六條規則:

image-20230722182526725

第六條規則,就是簡單粗暴地拒絕,爲啥會執行到第六條呢,這個是按1-6順序執行下來的,說明前面的5條,都沒匹配上條件,那肯定就是條件估計寫得有問題。

經過我認真檢查,發現自己手寫還是容易出錯,正確的應該是下面這樣:

// 正確版本
iptables -I INPUT  -p tcp --dport 8080 -j ACCEPT
// 錯誤版本
iptables  -i filter -I INPUT 1 -p tcp -m tcp --dport=8080 -j ACCEPT

錯誤的地方就是 -i filter,我本來是準備顯式指定下要操作的表(其實正確的版本里就沒指定,默認就是filter),結果語法寫錯了,應該是-t filter,重新執行下,就好了:

image-20230722183047050

可以看到,雖然錯誤的規則還存在,但是我們把正確的規則放到了num=1,它會先執行,它匹配上了,後面就不會執行了。

我們重新看trace日誌,這次就是匹配上rule1了:

image-20230722183252336

trace規則如何設置才能正確輸出

rsyslog進程

今天其實花了很多時間在trace規則上,我在網上查到有這種規則,但是,加了後,死活在log日誌裏看不到內容,查了半天,到處改改改也沒搞出來。

這個/var/log/messages文件,一般是什麼服務在往裏面寫呢?是rsyslogd,它是一個後臺進程,

簡單一句話的介紹是,它是一個支持將消息記錄到日誌的系統工具,消息可以通過網絡或者本機unix socket的方式發送給它。

Rsyslogd is a system utility providing support for message logging. Support of both internet and unix domain sockets enables this utility to support both local and remote logging.

一般來說,都是通過本機unix socket方式。

根據man rsyslogd的說法,這個進程主要涉及如下幾個重要文件:

主配置文件:
/etc/rsyslog.conf
Configuration file for rsyslogd.  See rsyslog.conf(5) for exact information.

unix socket的位置,本進程就從這裏獲取要寫日誌的消息
/dev/log
The Unix domain socket to from where local syslog messages are read.

進程pid:
/var/run/syslogd.pid
The file containing the process id of rsyslogd.

進程id這個是可以看到,是匹配的:4773:

[root@hx168-access ~]# cat /var/run/syslogd.pid
4773
[root@hx168-access ~]# ps -ef|grep rsys
root       4773      1  0 18:44 ?        00:00:00 /usr/sbin/rsyslogd -n

這個進程,主要會寫如下文件(默認配置情況下):

image-20230722192650305

比如我們上面的防火牆跟蹤日誌就在/var/log/messages

日誌輸出遇阻

今天我遇到的問題就是,看到網上說,要改配置,將內核日誌給輸出纔行:

vim /etc/rsyslog.conf
增加如下一行:
kern.*     /var/log/iptables.log(這裏文件名可以自取)

結果弄了後還是沒效果。

後面我換了臺機器,結果發現可以輸出,而且也沒像上面這樣配置,我後面就在想,到底是哪裏的問題。

strace排查日誌daemon是否收到日誌

 # xxx爲rsyslogd的pid
 strace -p xxx -q -f -s 200

strace命令可以attach到一個進程上,監控其系統調用。

我當時兩臺機器,一臺可以,一臺不行,於是分別在兩臺機器上開啓該命令,然後執行ping,發現正常的機器上,是可以看到有如下這些系統調用的,而異常的機器就沒有:

image-20230722230348064

所以,我開始懷疑是不是防火牆本身的問題,並沒把日誌寫到rsyslogd。

比較兩臺機器加載的mod

man iptables-extensions文檔,講解了TRACE這個target操作的信息。

TRACE
This target marks packets so that the kernel will log every rule which match the packets as those traverse the tables, chains, rules.

A logging backend, such as nf_log_ipv4(6) or nfnetlink_log, must be loaded for this to be visible. The packets are logged with the string prefix: "TRACE:
tablename:chainname:type:rulenum " where type can be "rule" for plain rule, "return" for implicit rule at the end of a user defined chain and "policy" for
the policy of the built in chains.
It can only be used in the raw table.

這裏面提到:

A logging backend, such as nf_log_ipv4(6) or nfnetlink_log, must be loaded for this to be visible.

必須先在機器上加載nf_log_ipv4或者nfnetlink_log這兩個模塊之一,其他細節就沒有了。

我在網上當時也查了不少文章,就是看看大家是怎麼配置這個TRACE規則的,然後跟着做了些操作,比如,加載模塊的命令是modprobe,我搜索shell history,發現執行了如下這麼多次:

  678  modprobe ipt_LOG
  688  modprobe ipt_LOG ip6t_LOG nfnetlink_log
  741  modprobe nfnetlink_log
  748  modprobe nf_log_ipv4

模塊相關的命令有這幾個:

加載或刪除模塊
modprobe - Add and remove modules from the Linux Kernel

顯示已加載的模塊
lsmod - Show the status of modules in the Linux Kernel
lsmod is a trivial program which nicely formats the contents of the /proc/modules, showing what kernel modules are currently loaded.

查看模塊信息
modinfo - Show information about a Linux Kernel module

我分別在兩臺機器執行了lsmod | sort,然後比較差異,確實有些差異:

image-20230722231603033

然後在網上看到一些文章,說和內核參數有關(sysctl -a),於是也比較了一下,發現其中一個參數可能有影響:

image-20230722231715861

這個參數我在網上查了好久,甚至沒查到枚舉值有哪些,總的來說,信息非常少。

後來大概知道,如下這個參數值,會把log不是發往rsyslogd,而是發往一個叫ulogd的後臺程序。

net.netfilter.nf_log.2 = nfnetlink_log

這個ulogd大概介紹下,https://www.netfilter.org/projects/ulogd/:

ulogd is a userspace logging daemon for netfilter/iptables related logging.

它是一個用戶態的日誌後臺,供netfilter/iptables相關的日誌使用。不管怎麼說,反正不會發往rsyslog這邊就對了。

這個爲啥會變成這樣呢,大概是因爲我執行了modprobe nfnetlink_log吧。正確的模塊其實應該是nf_log_ipv4.

所以我這邊修改了下值:

echo nf_log_ipv4 >  /proc/sys/net/netfilter/nf_log/2
然後再查看:
sysctl -a
發現就已經變成nf_log_ipv4了

這裏額外補充下,在如下目錄下,有12個文件:

[root@hx168-access ~]# ll /proc/sys/net/netfilter/nf_log
-rw-r--r-- 1 root root 0 Jul 22 23:25 2
...
-rw-r--r-- 1 root root 0 Jul 22 23:25 12

其中,2就是表示IP協議族,表示遇到IP協議族時,日誌打印使用的模塊,其他的協議族都不用特別關心。

網上關於這個問題的討論

首先是一個feature,貌似是這塊net.netfilter.nf_log.2可以取的值太多了,要合併掉幾個:

https://patchwork.kernel.org/project/netdevbpf/patch/[email protected]/

image-20230722233203106

就是以後就兩個模塊,一個nfnetlink_log,一個nf_log_syslog(這個一看就是寫到rsyslog的)。

再下來,是一個bug提交,和我的問題一模一樣,最後解決辦法也一樣,是吐槽trace的文檔寫得太草了:

https://bugzilla.netfilter.org/show_bug.cgi?id=1076

image-20230722233439568

問題是幾年前提的,看起來還是沒人改,bug也沒關閉。

就這樣吧,下篇再繼續講這個iptables的其他方面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章