<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 18243字,需瀏覽 37分鐘

           ·

          2021-09-03 16:21

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

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

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

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

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

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

          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 對應(yīng)一個此種格式的二維數(shù)組,詳見圖 2-1。數(shù)組中保存的為 nf_hook_entries 結(jié)構(gòu),對應(yīng)保存了該 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ù)注冊到對應(yīng) hook 點的函數(shù)列表中。

          2.2 iptabes

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

          iptables 的整體組織如下表,縱軸代表的是 table 名,橫軸是 chain 的名字,與 Netfilter hook 點一一對應(yīng)??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 點,獲取到對應(yīng)的 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ù)傳入對應(yīng)的 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 點中對應(yīng)函數(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,         // 相關(guān)狀態(tài)
                struct xt_table *table)
                              // table 表

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

          經(jīng)過上述分析,我們了解到對于 Netfilter 的底層函數(shù)為 ipt_do_table,那么我們只需要使用 kprobekretprobe 獲取到入?yún)⒑头祷亟Y(jié)果,即可以獲取到對應(yīng)的過濾結(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()

          可以在樣例程序的基礎(chǔ)上通過 skb 讀取對應(yīng)的 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 標(biāo)記。

          4. 相關(guān)資料

          • 【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
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大香蕉AV在线 | 无码视频免费在线播放 | 想xx视频 | 国产精品久久久久久黄无缝 | 国产精品又污又黄又爽污污 |