1. 程式人生 > >tcpkill工作原理分析

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實現