怎麼會講這個話題,這個說來真的長了。但是,長話短說,也是可以的。
我前面的文章提到,線上的服務用了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功能,結果實驗的時候,死活有問題,然後想著怎麼打紀錄檔,結果打紀錄檔又出問題,折騰了好久(搜尋引擎上關於這個的資料較少),這裡就記錄下來。
在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
下面的一些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規則上,我在網上查到有這種規則,但是,加了後,死活在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(這裡檔名可以自取)
結果弄了後還是沒效果。
後面我換了臺機器,結果發現可以輸出,而且也沒像上面這樣設定,我後面就在想,到底是哪裡的問題。
# xxx為rsyslogd的pid
strace -p xxx -q -f -s 200
strace命令可以attach到一個程序上,監控其系統呼叫。
我當時兩臺機器,一臺可以,一臺不行,於是分別在兩臺機器上開啟該命令,然後執行ping,發現正常的機器上,是可以看到有如下這些系統呼叫的,而異常的機器就沒有:
所以,我開始懷疑是不是防火牆本身的問題,並沒把紀錄檔寫到rsyslogd。
在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的其他方面。