tcpkill工作原理分析
此文已由作者張耕源授權網易雲社群釋出。
歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。
日常工作生活中大家在維護自己的伺服器、VPS有時會碰到這樣的情況:伺服器上突然出現了許多來自未知ip的網路連線與流量,我們需要第一時間切斷這些可能有害的網路連線。除了iptables/ipset, blackhole routing這些常規手段,我們還可以藉助一些更輕量級的小工具來臨時處理這些情況,如tcpkill。
tcpkill使用簡介
tcpkill是一個網路分析工具集dsniff中的一個小工具。在Debian/Ubuntu上可以直接通過dsniff包安裝:
# aptitude install dsniff
tcpkill使用的語法和tcpdump幾乎一致:
tcpkill [-i interface] [-1...9] expression
其中第一個引數 -i 指定網絡卡裝置。
第二個引數指定“kill”的強制等級,越高越強,預設為3,我們在後面瞭解tcpkill的工作原理後會知道這個引數的具體作用。
第三個引數則是匹配需要kill的tcp連線通配表示式,語法與tcpdump使用的pcap-filter完全一樣。
如果我們需要使用tcpkill臨時禁止伺服器與主機10.0.0.1的tcp連線,可以在伺服器上輸入命令:
# tcpkill host 10.0.0.1
tcpkill會一直阻止主機10.0.0.1與伺服器的網路連線,直到你結束這個tcpkill程序為止。
tcpkill原理分析
在使用tcpkill時,會發現一件奇怪的事情,執行tcpkill命令後並不會馬上中斷匹配的tcp連線,只有當該連線有新的tcp包傳送接收時,tcpkill才會“kill”這個tcp連線。這個奇怪的現象燃起了我們的好奇心,於是探索一下tcpkill到底是如何工作的。
下面以Linux下的nc命令為例。
我們在兩個主機hostA與hostB間通過nc命令建立一個tcp連線:
hostA在本地tcp 5555埠監聽
hostA$ nc -l -p 5555
hostB通過本地6666埠連線hostA的5555埠
hostB$ nc hostA 5555 -p 6666
此時在hostA上已經可以觀察到一條與hostB的ESTABLISHED連線
hostA$ netstat -anp|grep 5555tcp 0 0 hostA:5555 hostB:6666 ESTABLISHED 19638/nc
在hostA上通過tcpdump也可以觀察到3次握手已經完成
hostA# tcpdump -i eth1 port 5555IP hostB.6666 > hostA.5555: Flags [S], seq 750827752, ...IP hostA.5555 > hostB.6666: Flags [S.], seq 1191909671, ack 750827753, ...IP hostB.6666 > hostA.5555: Flags [.], ack 1, win 115, ...
如果此時執行tcpkill命令嘗試“kill”這個tcp連線
hostA# tcpkill -1 -i eth1 port 5555tcpkill: listening on eth1 [port 5555]
會發現hostA與hostB上的nc命令並沒有受到任何影響而退出,hostA上觀察到該tcp連線還是ESTABLISHED狀態,tcpdump與tcpkill也沒有任何新的輸出。
hostA$ netstat -anp|grep 5555tcp 0 0 hostA:5555 hostB:6666 ESTABLISHED 19638/nc
執行tcpkill命令後,建立好的tcp連線並沒有受到任何影響。
如果我們此時在hostB的nc上輸入任意字元傳送,則會發現這時tcp連線中斷,nc傳送失敗退出。
hostB$ nc hostA 5555 -p 6666a<CR>(exit) hostB$
hostA上的nc監聽程序也因為連線中斷而退出
hostA$ nc -l -p 5555(exit) hostA$
netstat已經觀察不到這個tcp連線,而tcpdump此時則捕獲了一個新tcp rst包:
hostA# tcpdump -i eth1 port 5555IP hostB.6666 > hostA.5555: Flags [S], seq 750827752, ...IP hostA.5555 > hostB.6666: Flags [S.], seq 1191909671, ack 750827753, ...IP hostB.6666 > hostA.5555: Flags [.], ack 1, win 115, ...IP hostA.5555 > hostB.6666: Flags [R], seq 1191909672, ...
此時tcpkill的輸出
hostA# tcpkill -1 -i eth1 port 5555tcpkill: listening on eth1 [port 5555]hostB:6666 > hostA:5555: R 1191909672:1191909672(0) win 0hostA:5555 > hostB:6666: R 750827755:750827755(0) win 0
相信看到這裡,已經可以明白tcpkill的工作原理,實際上就是通過雙向fake tcp rst包重置目標連線雙方的網路連線,和某牆的原理一樣。
而之所以tcpkill不會馬上中斷目標tcp連線,是因為偽造tcp rst包時,需要填入正確的sequence number,這需要通過攔截雙方的tcp通訊才能實時得到。所以執行tcpkill後,只有目標連線有新tcp包傳送/接受才會導致tcp連線中斷。
最後分析一下tcpkill第二個引數的具體作用。manpage裡的說明比較模糊,只能看出和receive window有關:
-1...9 Specify the degree of brute force to use in killing a connection. Fast connections may require a higher number in order to land a RST in the moving receive window. Default is 3.
直接看原始碼(只有100多行)
...intmain(int argc, char *argv[]){ ... /* 通過libpcap抓取所有符合條件的包,回撥函式為tcp_kill_cb */ pcap_loop(pd, -1, tcp_kill_cb, (u_char *)&sock); ... }static voidtcp_kill_cb(u_char *user, const struct pcap_pkthdr *pcap, const u_char *pkt){ ... /* 只處理tcp包 */ ip = (struct libnet_ip_hdr *)pkt; if (ip->ip_p != IPPROTO_TCP) return; /* 不處理tcp syn/fin/rst包 */ tcp = (struct libnet_tcp_hdr *)(pkt + (ip->ip_hl << 2)); if (tcp->th_flags & (TH_SYN|TH_FIN|TH_RST)) return; /* 偽造ip包 */ libnet_build_ip(TCP_H, 0, 0, 0, 64, IPPROTO_TCP, ip->ip_dst.s_addr, ip->ip_src.s_addr, NULL, 0, buf); /* 偽造tcp rst包 */ libnet_build_tcp(ntohs(tcp->th_dport), ntohs(tcp->th_sport), 0, 0, TH_RST, 0, 0, NULL, 0, buf + IP_H); /* fake tcp rst包的sequence number即為抓到的包的ack number */ seq = ntohl(tcp->th_ack); ... /* 這裡Opt_severity即為tcpkill的第二個引數 */ win = ntohs(tcp->th_win); for (i = 0; i < Opt_severity; i++) { ip->ip_id = libnet_get_prand(PRu16); seq += (i * win); tcp->th_seq = htonl(seq); libnet_do_checksum(buf, IPPROTO_TCP, TCP_H); /* 傳送偽造的tcp rst包 */ if (libnet_write_ip(*sock, buf, sizeof(buf)) < 0) warn("write_ip"); fprintf(stderr, "%s R %lu:%lu(0) win 0\n", ctext, seq, seq); } }
從上面可以看出,tcpkill的第二個引數,實際上就是沿tcp連線視窗滑動而傳送的tcp rst包個數。將這個引數設定較大主要是為了應對高速tcp連線的情況。
引數的大小從中斷tcp連線的原理上沒有區別,只是傳送rst包數量的差異,通常情況下使用預設值3已經完全沒有問題了。所以使用tcpkill時請不要像網路上某些中文教程中一樣不適當的使用引數 -9 。
更多網易技術、產品、運營經驗分享請點選。
相關文章:
【推薦】 從加班論客戶端開發中的建模
【推薦】 Openwrt自定義CGI實現