IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 使用 eBPF 技术跟踪 Netfilter 数据流 -> 正文阅读

[网络协议]使用 eBPF 技术跟踪 Netfilter 数据流

1. 网络层数据流向与 Netfilter 体系

图 1-1 为网络层内核收发核心流程图,在函数流程图中我们可以看到 Netfliter 在其中的位置(图中深色底纹圆角矩形)。图中对应的 hook 点有 5 个,每个hook 点中保存一组按照优先级排序的函数列表:

  • NF_IP_PREROUTING:接收到的包进入协议栈后立即触发此 hook 中注册的对应函数列表,在进行任何路由判断 (将包发往哪里)之前;

  • NF_IP_LOCAL_IN:接收到的包经过路由判断,如果目的是本机,将触发此 hook 中注册的对应函数列表;

  • NF_IP_FORWARD:接收到的包经过路由判断,如果目的是其他机器,将触发此 hook 中注册的对应函数列表;

  • NF_IP_LOCAL_OUT:本机产生的准备发送的包,在进入协议栈后立即触发此 hook 中注册的对应函数列表;

  • NF_IP_POST_ROUTING:本机产生的准备发送的包或者转发的包,在经过路由判断之后, 将触发此 hook 中注册的对应函数列表;

network_l3_flow

network_l3_flow

图 1-1 网络层内核收发核心流程图

从图 1-1 的数据流分为三类,分别用不同的颜色标注,因此我们可以得知:

  1. 本地处理的数据包,在 Netfliter 体系中会依次流经 NF_IP_PREROUTINGNF_IP_LOCAL_IN

  2. 转发的数据包,在 Netfliter 体系中会依次流经NF_IP_FORWARDNF_IP_POST_ROUTING

  3. 本地发送的数据包在 Netfliter 体系中会依次流经 NF_IP_LOCAL_OUTNF_IP_POST_ROUTING

2. Netfilter 与 IPtables

2.1 Netfilter 数据结构

Netfilter 架构中对于 hook 点中注册的函数管理,采用二维数组的方式进行组织,纵轴为协议,横轴为 hook 点,每个 Network Namespace 对应一个此种格式的二维数组,详见图 2-1。数组中保存的为 nf_hook_entries 结构,对应保存了该 hook 点中注册的 hook 函数,函数按照优先级的方式进行管理,调用时也是按照优先级进行过滤。

netfilter_data_struct

netfilter_data_struct

其中 hooks_ipv4[NF_INET_NUMHOOKS] 位于 net->nf 变量中。 hook 函数的原型定义如下:

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

table nat 定义的 hook 函数为例, struct nf_hook_ops nf_nat_ipv4_ops 如下:

static?const?struct?nf_hook_ops?nf_nat_ipv4_ops[]?=?{
?{
??.hook??=?iptable_nat_do_chain,??//?函数名
??.pf??=?NFPROTO_IPV4,????????????//?协议名
??.hooknum?=?NF_INET_PRE_ROUTING,?//?hook?点
??.priority?=?NF_IP_PRI_NAT_DST,???//?优先级
?},
?{
??.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 结构在函数 iptable_nat_table_init 中初始化,最终通过 nf_register_net_hook 函数注册到对应 hook 点的函数列表中。

2.2 iptabes

iptables 是运行在用户空间的应用软件,通过控制 Linux 内核 中 Netfilter 模块,来管理网络数据包的处理和转发。iptables 使用 table 来组织规则,根据用来做什么类型的判断标准,将规则分为不同 table,当前支持的 tableraw/mangle/nat/filter/security 等。在 table 内部采用链 (chain)进行组织,其中系统内置的 chainNetfilter 中的 hook 点一一对应,例如 chain PREROUTING 对应于 NF_IP_PRE_ROUTING hook,用户自定义 chain 没有对应的 Netfilter hook 对应,因此必须通过 jump 跳转的方式进行关联。

iptables 的整体组织如下表,纵轴代表的是 table 名,横轴是 chain 的名字,与 Netfilter hook 点一一对应。纵轴的方向代表了在某个 chain 上调用的顺序,优先级自上而下。

Tables↓ /Chains→PREROUTINGINPUTFORWARDOUTPUTPOSTROUTING
(routing decision)?
raw??
(connection tracking enabled)??
mangle?????
nat (DNAT)??
(routing decision)??
filter???
security???
nat (SNAT)??

2.3 内核代码实现

此处以 ip_rcv 函数为例,简单讨论在代码层面的实现:

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?数据进行校验
?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 的条件编译下,会首先调用 nf_hook 函数,在该函数中会根据参入的协议和 hook 点,获取到对应的 hook 函数列表头(例如 IPv4 协议中的 net->nf.hooks_ipv4[hook] ),然后在 nf_hook_slow 中循环调用列表中的 hook 函数(hook 函数按照优先级组织),并基于 hook 函数返回的结果决定继续调用列表中后续的 hook 函数,还是直接返回。

Netfilterhook 函数的格式基本如下,直接调用 ipt_do_table 函数,最后的参数传入对应的 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 点中对应函数的过滤的结果,则需要跟踪 ipt_do_table 函数的入参和返回结果即可。

unsigned?int?ipt_do_table(struct?sk_buff?*skb,??????????//?skb?
??????const?struct?nf_hook_state?*state,?????????//?相关状态
??????struct?xt_table?*table)????????????????????//?table?表

3. 使用 eBPF 技术跟踪

经过上述分析,我们了解到对于 Netfilter 的底层函数为 ipt_do_table,那么我们只需要使用 kprobekretprobe 获取到入参和返回结果,即可以获取到对应的过滤结果,这对于我们分析采用 iptables 管理流量的场景下定位问题非常方便。

ipt_tracer

ipt_tracer

运行效果图:

./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 和端口信息(包括源和目的),这可以实现对于 Netfilter 中的 hook 点跟踪。完整的可使用代码参见 skbtracer.py,使用帮助如下:

./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 数据流程,需要添加 --iptable 标记。

4. 参考资料

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-08-27 12:13:42  更:2021-08-27 12:15:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 21:08:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计