背景
怎麼會講這個話題,這個說來真的長了。但是,長話短說,也是可以的。
我前面的文章提到,線上的服務用了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線程。
再後來,也試過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
默認規則
安裝完成後,或者啓動後,執行如下命令,會發現有一些默認規則。
所謂規則,在防火牆中,就是:針對某個滿足條件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日誌:
能看到對於某一條客戶端報文(這裏其實就是客戶端發過來的第一次tcp握手,注意看,第二行的ID都是35701,所以都是針對同一條報文的日誌),一共打印了三行:
raw:PREROUTING:policy:2
nat:PREROUTING:policy:1
filter:INPUT:rule:6
這裏其實就是表示,一個順序經過了三個規則。
首先是raw:PREROUTING
,即raw表的PREROUTING鏈的默認規則(policy:2),看下圖,raw表只有我們那個trace規則,除此之外,啥都沒有,所以匹配了PREROUTING鏈的默認策略:
按照網上的說法,下圖紅框處就表示默認策略:
他麼的,爲啥說是網上的說法呢,因爲,我本來想實驗一下,把這塊改成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規則鏈的第六條規則:
第六條規則,就是簡單粗暴地拒絕,爲啥會執行到第六條呢,這個是按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
,重新執行下,就好了:
可以看到,雖然錯誤的規則還存在,但是我們把正確的規則放到了num=1,它會先執行,它匹配上了,後面就不會執行了。
我們重新看trace日誌,這次就是匹配上rule1了:
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
這個進程,主要會寫如下文件(默認配置情況下):
比如我們上面的防火牆跟蹤日誌就在/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,發現正常的機器上,是可以看到有如下這些系統調用的,而異常的機器就沒有:
所以,我開始懷疑是不是防火牆本身的問題,並沒把日誌寫到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
,然後比較差異,確實有些差異:
然後在網上看到一些文章,說和內核參數有關(sysctl -a),於是也比較了一下,發現其中一個參數可能有影響:
這個參數我在網上查了好久,甚至沒查到枚舉值有哪些,總的來說,信息非常少。
後來大概知道,如下這個參數值,會把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]/
就是以後就兩個模塊,一個nfnetlink_log,一個nf_log_syslog(這個一看就是寫到rsyslog的)。
再下來,是一個bug提交,和我的問題一模一樣,最後解決辦法也一樣,是吐槽trace的文檔寫得太草了:
https://bugzilla.netfilter.org/show_bug.cgi?id=1076
問題是幾年前提的,看起來還是沒人改,bug也沒關閉。
就這樣吧,下篇再繼續講這個iptables的其他方面。