下面是我的理解,可能有誤,僅供參考。
要調(diào)優(yōu),三次/四次握手必須爛熟于心。
client server
(SYN_SENT) —> (SYN_RECV)
(ESTABLISHED) <—
—> (ESTABLISHED)
client(主動(dòng)) server
(FIN_WAIT_1) —> (CLOSE_WAIT)
(FIN_WAIT_2) <—
(TIME_WAIT) <— (LAST_ACK)
—> (CLOSED)
大家熟知的 SYN flooding/SYN spoofing 就是在 SYN_RECV 的狀態(tài)下發(fā)起的進(jìn)攻。這種由于 TCP/IP 協(xié)議引起的缺陷只能防治而不好根治,除非換了 TCP/IP。通過(guò)下面的方式,可以在一定程度上緩解 DDOS 攻擊。
- 增大半連接的隊(duì)列,即 backlog queue
- 人工干預(yù)以減少 SYS_RECV 的時(shí)間,可以降低第一個(gè)重傳包的時(shí)間或者減少重傳的次數(shù)
檢測(cè) SYN 攻擊,可以使用 netstat 命令查看當(dāng)前的連接類型以及連接數(shù)目,如果發(fā)現(xiàn)有大量的 SYN_RECV,就值得懷疑了:
$ netstat -tuna | grep :80 | awk '{print $6}' | sort | uniq -c
或者可以通過(guò) wget/curl 從遠(yuǎn)端實(shí)際來(lái)測(cè)試一下訪問(wèn)的速度:
$ time wget -O /dev/null www.example.com
正常情況下,其 real time 在個(gè)位數(shù)(s)左右,如果出現(xiàn)長(zhǎng)達(dá)數(shù)十秒乃至幾百秒的情況,有可能是此類情況。
最簡(jiǎn)單的方式是通過(guò) syncookie 實(shí)現(xiàn),Linux 還實(shí)現(xiàn)了一種稱作 SYN cookie 的機(jī)制,開(kāi)啟:
# echo 1 > /proc/sys/net/ipv4/tcp_syncookies
該機(jī)制會(huì)在服務(wù)器收到 SYN 請(qǐng)求后,構(gòu)造一個(gè)帶有 ISN(initial sequence number)的 SYN/ACK 包,該 ISN 稱為 cookie,其實(shí)就是一個(gè)哈希。通過(guò)此就可以驗(yàn)證客戶端的真實(shí)性了
注意:SYN cookie 機(jī)制不會(huì)使用到 backlog queue,因此不必?fù)?dān)心 queue 被填滿然后服務(wù)器主動(dòng)放棄連接。
使用了 SYN cookie 之后,在 /var/log/kern.log 會(huì)發(fā)現(xiàn)不少如下的 log,起作用了 ;-)
possible SYN flooding on port 80. Sending cookies
除了使用 syncookie,還可以修改 backlog queue 來(lái)達(dá)到目的。backlog queue 是一個(gè)用來(lái)處理在三次握手過(guò)程中帶有 SYN 標(biāo)志的包的數(shù)據(jù)結(jié)構(gòu),可以用來(lái)控制系統(tǒng)同時(shí)處理的最大連接,當(dāng)達(dá)到該閾值后,接下來(lái)的請(qǐng)求會(huì)被系統(tǒng)丟棄。這需要系統(tǒng)開(kāi)辟額外的內(nèi)存來(lái)處理進(jìn)來(lái)的包。如果處 理的不好會(huì)導(dǎo)致系統(tǒng)內(nèi)存耗盡,導(dǎo)致嚴(yán)重的性能問(wèn)題。
tcp_max_syn_backlog 定義了 backlog queue 的半連接數(shù)量:
# echo 90000 > /proc/sys/net/ipv4/tcp_max_syn_backlog
當(dāng)客戶端發(fā)起 SYN 請(qǐng)求后,服務(wù)端會(huì)立刻發(fā)送 SYN+ACK 的回應(yīng),該次半連接會(huì)到 backlog queue 中,服務(wù)器會(huì)等待客戶返回 ACK,如果在一段時(shí)間內(nèi)沒(méi)有應(yīng)答,服務(wù)器會(huì)重新發(fā)送剛剛的 SYN+ACK,經(jīng)歷了幾次還是沒(méi)有回應(yīng)后,服務(wù)器會(huì)主動(dòng)斷開(kāi)此次半連接。
我們就可以修改重發(fā)的次數(shù)來(lái)減少整個(gè)半連接的時(shí)間:
# echo 3 > /proc/sys/net/ipv4/tcp_synack_retries
——————————————————————————-
|Value| Time of retransmission | Total time |
——————————————————————————-
|1 | in 3rd second | 9 seconds |
——————————————————————————-
|2 | in 3rd and 9th second | 21 seconds |
——————————————————————————-
|3 | in 3rd, 9th and 21st second | 45 seconds |
——————————————————————————-
這張表格顯示了不同重傳次數(shù)消耗的總時(shí)間
上面屬于 passive 連接,也就是客戶端連接服務(wù)端,還有個(gè)相反的 active TCP connection 參數(shù):
# echo 3 > /proc/sys/net/ipv4/tcp_syn_retries
tcp_fin_timeout 參數(shù)會(huì)通知 kernel 在 FIN_WAIT_2 狀態(tài) sockets 的存活時(shí)間,根據(jù)理解應(yīng)該是 server 主動(dòng)終止,像下面這樣。
server client
(FIN_WAIT_1) —> (CLOSE_WAIT)
(FIN_WAIT_2) <—
(TIME_WAIT) <— (LAST_ACK)
—> (CLOSED)
當(dāng)處于 CLOST_WAIT 的 client 有意(攻擊)/無(wú)意(client 突然崩潰等)不發(fā) fin 來(lái)繼續(xù)時(shí),server 會(huì)一直停留在 FIN_WAIT_2 狀態(tài),造成資源的浪費(fèi)。
可以適當(dāng)?shù)臏p小該時(shí)間:
# echo 15 > /proc/sys/net/ipv4/tcp_fin_timeout
跟 tcp_fin_timeout 相關(guān)的有 tcp_max_orphans 參數(shù),表示沒(méi)有跟任何用戶文件相關(guān)聯(lián)的 socket 最大個(gè)數(shù),超出的將被內(nèi)核丟棄。
建議該參數(shù)只增加不減小,但增加也意味著內(nèi)存的消耗增加:
# echo 327680 > /proc/sys/net/ipv4/tcp_max_orphans
相關(guān)的 tcp_orphans_retries 關(guān)閉本端 TCP 連接前的重試次數(shù),默認(rèn) 7,高負(fù)載的 webserver 建議可以減小。這里解釋了設(shè)置為 0 的情況。
下面這三個(gè)參數(shù)一起解釋:
tcp_tw_recycle
tcp_tw_reuse
tcp_timestamps
其中 tcp_tw_recycle/tcp_tw_reuse 這兩個(gè)官方的建議保持默認(rèn)為 0,而 tcp_timestamps 這個(gè)參數(shù)在特定的情況開(kāi)啟會(huì)引起很嚴(yán)重的問(wèn)題(via 1, 2)。
基本的情況就是,你的客戶或者你的服務(wù)器在一個(gè) NAT 后面,如果開(kāi)啟這個(gè)參數(shù),會(huì)導(dǎo)致服務(wù)器能收到三次握手的 SYN 但是不會(huì)返回任何的 SYN+ACK,其結(jié)果是客戶無(wú)法訪問(wèn)你的網(wǎng)站。可以通過(guò) tcpdump 或者下面的這個(gè)查看:
# netstat -s | grep timestamp
tcp_timestamps 是 tcp 協(xié)議中的一個(gè)擴(kuò)展項(xiàng),通過(guò)時(shí)間戳的方式來(lái)檢測(cè)過(guò)來(lái)的包以防止 PAWS(Protect Against Wrapped Sequence numbers),可以提高 tcp 的性能,2.6 的內(nèi)核默認(rèn)是打開(kāi)的。只要 client/server/nat/loadbalancer 不同時(shí)打開(kāi)該選項(xiàng)就不會(huì)出現(xiàn)上面的問(wèn)題。與之相關(guān)的包括 tcp_tw_recycle,如果 tcp_timestamps 和 tcp_tw_recycle 同時(shí)開(kāi)啟,就會(huì)開(kāi)啟時(shí)間戳選項(xiàng),導(dǎo)致上面的問(wèn)題。如果有上述的網(wǎng)絡(luò)結(jié)構(gòu),比較合理的方式是禁用 tcp_tw_recyle 而開(kāi)啟 tcp_timestamps。禁用了 tcp_tw_recycle 其 TIME_OUT sockets 回收功能就沒(méi)了,可以配合 tcp_tw_reuse 讓 TIME_WAIT 降下來(lái)。
netdev_max_backlog 這個(gè)參數(shù)跟 TCP 的傳輸隊(duì)列有關(guān),發(fā)送隊(duì)列長(zhǎng)度是 txqueuelen ,netdev_backlog 則決定接收隊(duì)列的長(zhǎng)度。
前者通過(guò) ifconfig 命令改變:
# ifconfig eth0 txqueuelen 10000
對(duì)于高吞吐的網(wǎng)絡(luò)而言,默認(rèn)的 100 肯定是不夠的,一個(gè) rrt 為 120ms 的千兆以太網(wǎng)絡(luò),可以設(shè)置成 10000 以上的值。
對(duì)于接受端而言,需要修改的話就涉及到了 /proc/sys/net/core/netdev_max_backlog 了,如果接收包的速度大于內(nèi)核能處理的速度,則需要隊(duì)列來(lái)維持,此數(shù)值表示最大隊(duì)列值。默認(rèn)為 1000,如果超過(guò)該數(shù)值,會(huì)引起丟包,根據(jù)實(shí)際情況增大。
對(duì)于網(wǎng)絡(luò)不是很好的情況,可以開(kāi)啟 tcp_sack 參數(shù)。該實(shí)現(xiàn)是 TCP 的一個(gè)選項(xiàng),稱為 Selective Acknowlegement(SACK),默認(rèn)開(kāi)啟,對(duì)于千兆網(wǎng)絡(luò)可以關(guān)閉,能提高一定的性能。
keepalive 的情況,Linux 內(nèi)置支持,只需要開(kāi)啟相應(yīng)的內(nèi)核參數(shù)就可以了,主要是下面三個(gè):
tcp_keepalive_time 表示 TCP 發(fā)出第一個(gè) keepalive 信息之前等待的時(shí)間,默認(rèn)為 7200
tcp_keepalive_intvl keepalive 的時(shí)間間隔,默認(rèn)是 75
tcp_keepalive_probes 觸發(fā)的次數(shù),默認(rèn)是 9
ip_local_port_range 128M 內(nèi)存以上的機(jī)器默認(rèn)是 32768 61000,可以進(jìn)一步擴(kuò)大 10240 65535,盡量不要使用 1024 周圍的,避免沖突。
以上只是網(wǎng)絡(luò)內(nèi)核參數(shù)的一小小部分,有待繼續(xù)補(bǔ)充。
ref:
http://www.frozentux.net/ipsysctl-tutorial/chunkyhtml/tcpvariables.html
http://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
http://www.symantec.com/connect/articles/hardening-tcpip-stack-syn-attacks
http://www.saview.net/archives/201