tcpdump 實現原理【整理】


參考:http://blog.sina.com.cn/s/blog_523491650101au7f.html


 

一、tcpdump

對於本機中進程的系統行為調用跟蹤,strace是一個很好的工具,而在網絡問題的調試中,tcpdump應該說是一個必不可少的工具,和大部分linux下優秀工具一樣,它的特點就是簡單而強大。
默認情況下,tcpdump不會抓取本機內部通訊的報文。根據網絡協議棧的規定,對於報文,即使是目的地是本機,也需要經過本機的網絡協議層,所以本機通訊肯定是通過API進入了內核,並且完成了路由選擇。
二、linux下抓包原理
linux下的抓包是通過注冊一種虛擬的底層網絡協議來完成對網絡報文(准確的說是網絡設備)消息的處理權。當網卡接收到一個網絡報文之后,它會遍歷系統中所有已經注冊的網絡協議,例如以太網協議、x25協議處理模塊來嘗試進行報文的解析處理,這一點和一些文件系統的掛載相似,就是讓系統中所有的已經注冊的文件系統來進行嘗試掛載,如果哪一個認為自己可以處理,那么就完成掛載。
當抓包模塊把自己偽裝成一個網絡協議的時候,系統在收到報文的時候就會給這個偽協議一次機會,讓它來對網卡收到的報文進行一次處理,此時該模塊就會趁機對報文進行窺探,也就是把這個報文完完整整的復制一份,假裝是自己接收到的報文,匯報給抓包模塊。
先看一下網絡層對於接收到的報文的處理方法
static int process_backlog(struct net_device *backlog_dev, int *budget)---netif_receive_skb
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
三、協議族的注冊
對於這種協議,也只有在需要的時候才注冊,因為它畢竟增加了系統報文的處理速度並且會消耗大量的系統skb。當抓包開始的時候,它會創建一個對應的網絡套接口,這種套接口的類型就是af_packet類型。相關實現為
linux-2.6.21\net\packet\af_packet.c
static int packet_create(struct socket *sock, int protocol)
    sk->sk_family = PF_PACKET;
    po->num = proto;
……
    po->prot_hook.func = packet_rcv;
……

    if (proto) {
        po->prot_hook.type = proto;
        dev_add_pack(&po->prot_hook);這個接口會將prot_hook注冊到前面看到的ptype_all隊列中
        sock_hold(sk);
        po->running = 1;
    }
當一個網卡上真正有報文到來的時候,它就會調用這里注冊的packet_rcv函數
……
    res = run_filter(skb, sk, snaplen);如果說filter過濾失敗,說明是抓包不關心的報文,直接放行,返回值非零表示不關心。
    if (!res)
        goto drop_n_restore;
……
    if (skb_shared(skb)) {
        struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);自己復制一份
        if (nskb == NULL)
            goto drop_n_acct;

        if (skb_head != skb->data) {
            skb->data = skb_head;
            skb->len = skb_len;
        }
        kfree_skb(skb);
        skb = nskb;
    }
四、filter的執行
run_filter--->>sk_run_filter
……

    for (pc = 0; pc < flen; pc++) {
        fentry = &filter[pc];

        switch (fentry->code) {
        case BPF_ALU|BPF_ADD|BPF_X:
            A += X;
            continue;
        case BPF_ALU|BPF_ADD|BPF_K:
            A += fentry->k;
            continue;
……
       
        switch (k-SKF_AD_OFF) {
        case SKF_AD_PROTOCOL:
            A = ntohs(skb->protocol);
            continue;
        case SKF_AD_PKTTYPE:
            A = skb->pkt_type;
            continue;
        case SKF_AD_IFINDEX:
            A = skb->dev->ifindex;
            continue;
        default:
            return 0;
        }
這個函數是執行了一個自己定義的指令集和。用戶通過sockopt來注冊這段指令,內核在內核態執行這些指令,完成匹配,其中包含了報文某些字段的加載,條件跳轉、加減乘除以及返回等指令。當轉包套接口接收到報文之后,對這個報文執行這段虛擬程序,直到遇到ret指令作為自己的返回值。通過tcpdump -d 可以顯示出編譯之后生成的指令,下面是一個測試輸出
[root@Harry bash-4.1]# tcpdump -d host 1.2.3.4
tcpdump: WARNING: eth0: no IPv4 address assigned
(000) ldh      [12]
(001) jeq      #0x800           jt 2    jf 6
(002) ld       [26]  加載接收到報文的第26個字節開始的一個int類型
(003) jeq      #0x1020304       jt 12    jf 4 如果和0x1234相等,跳轉到12跳指令,不等繼續第四條指令。 
(004) ld       [30]
(005) jeq      #0x1020304       jt 12    jf 13
(006) jeq      #0x806           jt 8    jf 7
(007) jeq      #0x8035          jt 8    jf 13
(008) ld       [28]
(009) jeq      #0x1020304       jt 12    jf 10
(010) ld       [38]
(011) jeq      #0x1020304       jt 12    jf 13
(012) ret      #65535
(013) ret      #0
[root@Harry bash-4.1]# 
五、tcpdump的處理
對於tcpdump來說,命令行輸入中除了選項之外的參數都將作為一個語法文件來處理。libcap中定義了自己的詞法分析器和語法分析器。其詞法分析器為scanner.l,語法分析文件為scanner.l,可見該語言的定義使用了通用的bison和flex的幫助。但是如果使用了這些工具,就意味着一個配置文件就可以寫的很復雜,例如syslog-ng配置、ld連接配置腳本、bash腳本等各種語法。
這個語法文件如果感興趣可以看看說明文檔即可。
其中pcap庫中沒有對網卡的配置,而且tcpdump的命令處理對於-i是覆蓋的,所以一次tcpdump只能偵聽一個端口。如果命令行沒有指定網絡設備,pcap會枚舉系統中所有的網絡設備,然后取出第一個設備(這個順序在不通的系統有不同排列順序,看一下tcpdump的輸出即可確定是在那個端口)
六、本地報文處理
    if (res.type == RTN_LOCAL) {
        if (!fl.fl4_src)
            fl.fl4_src = fl.fl4_dst;
        if (dev_out)
            dev_put(dev_out);
        dev_out = &loopback_dev;
        dev_hold(dev_out);
        fl.oif = dev_out->ifindex;
        if (res.fi)
            fib_info_put(res.fi);
        res.fi = NULL;
        flags |= RTCF_LOCAL;
        goto make_route;
    }
struct net_device loopback_dev = {
    .name             = "lo",


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM