1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        使用 eBPF 技術(shù)跟蹤 Netfilter 數(shù)據(jù)流

        共 18243字,需瀏覽 37分鐘

         ·

        2021-09-03 16:21

        1. 網(wǎng)絡層數(shù)據(jù)流向與 Netfilter 體系

        圖 1-1 為網(wǎng)絡層內(nèi)核收發(fā)核心流程圖,在函數(shù)流程圖中我們可以看到 Netfliter 在其中的位置(圖中深色底紋圓角矩形)。圖中對應的 hook 點有 5 個,每個hook 點中保存一組按照優(yōu)先級排序的函數(shù)列表:

        • NF_IP_PREROUTING:接收到的包進入?yún)f(xié)議棧后立即觸發(fā)此 hook 中注冊的對應函數(shù)列表,在進行任何路由判斷 (將包發(fā)往哪里)之前;
        • NF_IP_LOCAL_IN:接收到的包經(jīng)過路由判斷,如果目的是本機,將觸發(fā)此 hook 中注冊的對應函數(shù)列表;
        • NF_IP_FORWARD:接收到的包經(jīng)過路由判斷,如果目的是其他機器,將觸發(fā)此 hook 中注冊的對應函數(shù)列表;
        • NF_IP_LOCAL_OUT:本機產(chǎn)生的準備發(fā)送的包,在進入?yún)f(xié)議棧后立即觸發(fā)此 hook 中注冊的對應函數(shù)列表;
        • NF_IP_POST_ROUTING:本機產(chǎn)生的準備發(fā)送的包或者轉(zhuǎn)發(fā)的包,在經(jīng)過路由判斷之后, 將觸發(fā)此 hook 中注冊的對應函數(shù)列表;

        圖 1-1 網(wǎng)絡層內(nèi)核收發(fā)核心流程圖

        圖 1-1 網(wǎng)絡層內(nèi)核收發(fā)核心流程圖

        從圖 1-1 的數(shù)據(jù)流分為三類,分別用不同的顏色標注,因此我們可以得知:

        1. 本地處理的數(shù)據(jù)包,在 Netfliter 體系中會依次流經(jīng) NF_IP_PREROUTINGNF_IP_LOCAL_IN;
        2. 轉(zhuǎn)發(fā)的數(shù)據(jù)包,在 Netfliter 體系中會依次流經(jīng)NF_IP_FORWARDNF_IP_POST_ROUTING;
        3. 本地發(fā)送的數(shù)據(jù)包,在 Netfliter 體系中會依次流經(jīng) NF_IP_LOCAL_OUTNF_IP_POST_ROUTING;

        2. Netfilter 與 IPtables

        2.1 Netfilter 數(shù)據(jù)結(jié)構(gòu)

        Netfilter 架構(gòu)中對于 hook 點中注冊的函數(shù)管理,采用二維數(shù)組的方式進行組織,縱軸為協(xié)議,橫軸為 hook 點,每個 Network Namespace 對應一個此種格式的二維數(shù)組,詳見圖 2-1。數(shù)組中保存的為 nf_hook_entries 結(jié)構(gòu),對應保存了該 hook 點中注冊的 hook 函數(shù),函數(shù)按照優(yōu)先級的方式進行管理,調(diào)用時也是按照優(yōu)先級進行過濾。

        圖 2-1 Netfilter hook 點函數(shù)數(shù)據(jù)結(jié)構(gòu)

        其中 hooks_ipv4[NF_INET_NUMHOOKS] 位于 net->nf 變量中。hook 函數(shù)的原型定義如下:

        typedef unsigned int nf_hookfn(void *priv,
                  struct sk_buff *skb,
                  const struct nf_hook_state *state)
        ;

        table nat 定義的 hook 函數(shù)為例, struct nf_hook_ops nf_nat_ipv4_ops 如下:

        static const struct nf_hook_ops nf_nat_ipv4_ops[] = {
         {
          .hook  = iptable_nat_do_chain,  // 函數(shù)名
          .pf  = NFPROTO_IPV4,            // 協(xié)議名
          .hooknum = NF_INET_PRE_ROUTING, // hook 點
          .priority = NF_IP_PRI_NAT_DST,   // 優(yōu)先級
         },
         {
          .hook  = iptable_nat_do_chain,
          .pf  = NFPROTO_IPV4,
          .hooknum = NF_INET_POST_ROUTING,
          .priority = NF_IP_PRI_NAT_SRC,
         },
         {
          .hook  = iptable_nat_do_chain,
          .pf  = NFPROTO_IPV4,
          .hooknum = NF_INET_LOCAL_OUT,
          .priority = NF_IP_PRI_NAT_DST,
         },
         {
          .hook  = iptable_nat_do_chain,
          .pf  = NFPROTO_IPV4,
          .hooknum = NF_INET_LOCAL_IN,
          .priority = NF_IP_PRI_NAT_SRC,
         },
        };

        nf_nat_ipv4_ops 結(jié)構(gòu)在函數(shù) iptable_nat_table_init 中初始化,最終通過 nf_register_net_hook 函數(shù)注冊到對應 hook 點的函數(shù)列表中。

        2.2 iptabes

        iptables 是運行在用戶空間的應用軟件,通過控制 Linux 內(nèi)核 中 Netfilter 模塊,來管理網(wǎng)絡數(shù)據(jù)包的處理和轉(zhuǎn)發(fā)。iptables 使用 table 來組織規(guī)則,根據(jù)用來做什么類型的判斷標準,將規(guī)則分為不同 table,當前支持的 tableraw/mangle/nat/filter/security 等。在 table 內(nèi)部采用鏈 (chain)進行組織,其中系統(tǒng)內(nèi)置的 chainNetfilter 中的 hook 點一一對應,例如 chain PREROUTING 對應于 NF_IP_PRE_ROUTING hook,用戶自定義 chain 沒有對應的 Netfilter hook 對應,因此必須通過 jump 跳轉(zhuǎn)的方式進行關聯(lián)。

        iptables 的整體組織如下表,縱軸代表的是 table 名,橫軸是 chain 的名字,與 Netfilter hook 點一一對應??v軸的方向代表了在某個 chain 上調(diào)用的順序,優(yōu)先級自上而下。

        Tables↓ /Chains→PREROUTINGINPUTFORWARDOUTPUTPOSTROUTING
        (routing decision)


        ?
        raw?

        ?
        (connection tracking enabled)?

        ?
        mangle?????
        nat (DNAT)?

        ?
        (routing decision)?

        ?
        filter
        ???
        security
        ???
        nat (SNAT)
        ?

        ?

        2.3 內(nèi)核代碼實現(xiàn)

        此處以 ip_rcv 函數(shù)為例,簡單討論在代碼層面的實現(xiàn):

        int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
            struct net_device *orig_dev)

        {
         struct net *net = dev_net(dev);

         skb = ip_rcv_core(skb, net); // 對于 ip 數(shù)據(jù)進行校驗
         if (skb == NULL)
          return NET_RX_DROP;

         return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
                 net, NULL, skb, dev, NULL,
                 ip_rcv_finish);
        }

        NF_HOOK 宏在啟用 Netfilter 的條件編譯下,會首先調(diào)用 nf_hook 函數(shù),在該函數(shù)中會根據(jù)傳入的協(xié)議和 hook 點,獲取到對應的 hook 函數(shù)列表頭(例如 IPv4 協(xié)議中的 net->nf.hooks_ipv4[hook] ),然后在 nf_hook_slow 中循環(huán)調(diào)用列表中的 hook 函數(shù)(hook 函數(shù)按照優(yōu)先級組織),并基于 hook 函數(shù)返回的結(jié)果決定繼續(xù)調(diào)用列表中后續(xù)的 hook 函數(shù),還是直接返回。

        Netfilterhook 函數(shù)的格式基本如下,直接調(diào)用 ipt_do_table 函數(shù),最后的參數(shù)傳入對應的 table 字段。

        static unsigned int iptable_nat_do_chain(void *priv,
              struct sk_buff *skb,
              const struct nf_hook_state *state)

        {
         return ipt_do_table(skb, state, state->net->ipv4.nat_table);
        }

        所以,如果我們想要獲取到 Netfilter hook 點中對應函數(shù)的過濾的結(jié)果,則需要跟蹤 ipt_do_table 函數(shù)的入?yún)⒑头祷亟Y(jié)果即可。

        unsigned int ipt_do_table(struct sk_buff *skb,          // skb
              const struct nf_hook_state *state,         // 相關狀態(tài)
              struct xt_table *table)
                            // table 表

        3. 使用 eBPF 技術(shù)跟蹤

        經(jīng)過上述分析,我們了解到對于 Netfilter 的底層函數(shù)為 ipt_do_table,那么我們只需要使用 kprobekretprobe 獲取到入?yún)⒑头祷亟Y(jié)果,即可以獲取到對應的過濾結(jié)果,這對于我們分析采用 iptables 管理流量的場景下定位問題非常方便。

        圖 3-1 程序架構(gòu)

        運行效果圖:


        ./iptables_trace_ex.py
        pid     skb        table    hook     verdict
        3956565    ffff8a7571a5eae0  b'filter'    OUTPUT       ACCEPT

        完整代碼如下:

        #!/usr/bin/python
        from bcc import BPF

        prog = """
        #include <bcc/proto.h>
        #include <uapi/linux/ip.h>
        #include <uapi/linux/icmp.h>
        #include <uapi/linux/tcp.h>

        #include <net/inet_sock.h>
        #include <linux/netfilter/x_tables.h>

        #define MAC_HEADER_SIZE 14;
        #define member_address(source_struct, source_member)            \
            ({                                                          \
                void* __ret;                                            \
                __ret = (void*) (((char*)source_struct) + offsetof(typeof(*source_struct), source_member)); \
                __ret;                                                  \
            })
        #define member_read(destination, source_struct, source_member)  \
          do{                                                           \
            bpf_probe_read(                                             \
              destination,                                              \
              sizeof(source_struct->source_member),                     \
              member_address(source_struct, source_member)              \
            );                                                          \
          } while(0)

        struct ipt_do_table_args
        {
            struct sk_buff *skb;
            const struct nf_hook_state *state;
            struct xt_table *table;
            u64 start_ns;
        };

        BPF_HASH(cur_ipt_do_table_args, u32, struct ipt_do_table_args);

        int kprobe__ipt_do_table(struct pt_regs *ctx, struct sk_buff *skb, const struct nf_hook_state *state, struct xt_table *table)
        {
            u32 pid = bpf_get_current_pid_tgid();

            struct ipt_do_table_args args = {
                .skb = skb,
                .state = state,
                .table = table,
            };

            args.start_ns = bpf_ktime_get_ns();
            cur_ipt_do_table_args.update(&pid, &args);

            return 0;
        };

        struct event_data_t {
            void  *skb;
            u32 pid;
            u32 hook;
            u32 verdict;
            u8  pf;
            u8  reserv[3];
            char table[XT_TABLE_MAXNAMELEN];
        };

        BPF_PERF_OUTPUT(open_events);

        int kretprobe__ipt_do_table(struct pt_regs *ctx)
        {
            struct ipt_do_table_args *args;
            u32 pid = bpf_get_current_pid_tgid();
            struct event_data_t evt = {};

            args = cur_ipt_do_table_args.lookup(&pid);
            if (args == 0)
                return 0;

            cur_ipt_do_table_args.delete(&pid);

            evt.pid = pid;
            evt.skb = args->skb;
            member_read(&evt.hook, args->state, hook);
            member_read(&evt.pf, args->state, pf);
            member_read(&evt.table, args->table, name);
            evt.verdict = PT_REGS_RC(ctx);

            open_events.perf_submit(ctx, &evt, sizeof(evt));
            return 0;
        }

        """


        # uapi/linux/netfilter.h
        NF_VERDICT_NAME = [
            'DROP',
            'ACCEPT',
            'STOLEN',
            'QUEUE',
            'REPEAT',
            'STOP',
        ]

        # uapi/linux/netfilter.h
        # net/ipv4/netfilter/ip_tables.c
        HOOKNAMES = [
            "PREROUTING",
            "INPUT",
            "FORWARD",
            "OUTPUT",
            "POSTROUTING",
        ]

        def _get(l, index, default):
            '''
            Get element at index in l or return the default
            '''

            if index < len(l):
                return l[index]
            return default

        def print_event(cpu, data, size):
          event = b["open_events"].event(data)

          hook    = _get(HOOKNAMES, event.hook, "~UNK~")
          verdict = _get(NF_VERDICT_NAME, event.verdict, "~UNK~")

          print("%-10d %-16x  %-12s %-12s %-10s"%(event.pid, event.skb, event.table, hook, verdict))

        b = BPF(text=prog)
        b["open_events"].open_perf_buffer(print_event)

        print("pid skb_addr table  hook verdict")

        while True:
            try:
                b.perf_buffer_poll()
            except KeyboardInterrupt:
                exit()

        可以在樣例程序的基礎上通過 skb 讀取對應的 IP 和端口信息(包括源和目的),這可以實現(xiàn)對于 Netfilter 中的 hook 點跟蹤。完整的可使用代碼參見 skbtracer.py[1],使用幫助如下:

        ./skbtracer.py -h
        usage: skbtracer.py [-h] [-H IPADDR] [--proto PROTO] [--icmpid ICMPID] [-c CATCH_COUNT] [-P PORT] [-p PID] [-N NETNS] [--dropstack] [--callstack] [--iptable] [--route]
                            [--keep] [-T] [-t]

        Trace any packet through TCP/IP stack

        optional arguments:
          -h, --help            show this help message and exit
          -H IPADDR, --ipaddr IPADDR
                                ip address
          --proto PROTO         tcp|udp|icmp|any
          --icmpid ICMPID       trace icmp id
          -c CATCH_COUNT, --catch-count CATCH_COUNT
                                catch and print count
          -P PORT, --port PORT  udp or tcp port
          -p PID, --pid PID     trace this PID only
          -N NETNS, --netns NETNS
                                trace this Network Namespace only
          --dropstack           output kernel stack trace when drop packet
          --callstack           output kernel stack trace
          --iptable             output iptable path
          --route               output route path
          --keep                keep trace packet all lifetime
          -T, --time            show HH:MM:SS timestamp
          -t, --timestamp       show timestamp in seconds at us resolution

        examples:
              skbtracer.py                                      # trace all packets
              skbtracer.py --proto=icmp -H 1.2.3.4 --icmpid 22  # trace icmp packet with addr=1.2.3.4 and icmpid=22
              skbtracer.py --proto=tcp  -H 1.2.3.4 -P 22        # trace tcp  packet with addr=1.2.3.4:22
              skbtracer.py --proto=udp  -H 1.2.3.4 -P 22        # trace udp  packet wich addr=1.2.3.4:22
              skbtracer.py -t -T -p 1 --debug -P 80 -H 127.0.0.1 --proto=tcp --kernel-stack --icmpid=100 -N 10000

        查看 iptables 數(shù)據(jù)流程,需要添加 --iptable 標記。

        4. 相關資料

        • 【BPF 入門系列-8】文件打開記錄跟蹤之 perf_event 篇[2]
        • [譯] 深入理解 iptables 和 netfilter 架構(gòu)[3] 英文[4]
        • Linux 協(xié)議棧--Netfilter 源碼分析[5]

        參考資料

        [1]

        skbtracer.py: https://github.com/DavadDi/skbtracer/blob/main/skbtracer.py

        [2]

        【BPF入門系列-8】文件打開記錄跟蹤之 perf_event 篇: https://www.ebpf.top/post/ebpf_trace_file_open_perf_output/

        [3]

        [譯] 深入理解 iptables 和 netfilter 架構(gòu): https://arthurchiao.art/blog/deep-dive-into-iptables-and-netfilter-arch-zh/

        [4]

        英文: https://www.digitalocean.com/community/tutorials/a-deep-dive-into-iptables-and-netfilter-architecture

        [5]

        Linux協(xié)議棧--Netfilter源碼分析: http://cxd2014.github.io/2017/08/23/netfilter/


        瀏覽 63
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            鸡巴天堂 | 人妻无码久久精品人妻成人 | 国产麻豆免费观看 | 受被攻c哭高h视频在线观看 | 成在线a 秋霞在线观看视频 | 国产在线拍揄自揄拍无码福利 | 逼逼操免费网 | 免费在线人成视频 | 揉捏花蒂抽打虐乳 | 日韩啊v |