pwru 是 cilium 推出的基于 ebpf 开发的网络数据包排查工具,它提供了更细粒度的网络数据包排查方案。本文将介绍 pwru 的使用方法和经典场景,并介绍其实现原理。
安装部署 部署要求 pwru 要求内核代码在 5.5 版本之上,--output-skb 要求内核版本在 5.9 之上,并且要求内核开启以下配置:
option note
config_debug_info_btf=y available since >= 5.3
config_kprobes=y
config_perf_events=y
config_bpf=y
config_bpf_syscall=y
使用方法 usage of ./pwru: --filter-dst-ip string filter destination ip addr --filter-dst-port uint16 filter destination port --filter-func string filter kernel functions to be probed by name (exact match, supports re2 regular expression) --filter-mark uint32 filter skb mark --filter-netns uint32 filter netns inode --filter-proto string filter l4 protocol (tcp, udp, icmp) --filter-src-ip string filter source ip addr --filter-src-port uint16 filter source port --output-limit-lines uint exit the program after the number of events has been received/printed --output-meta print skb metadata --output-relative-timestamp print relative timestamp per skb --output-skb print skb --output-stack print stack --output-tuple print l4 tuple 案例演示 下图案例演示了 pwru 展现出快速定位出数据包被 iptables 规则 drop 掉的原因:
在不设置 iptables 规则之前:
添加了 iptables 规则之后
iptables -t filter -i output 1 -m tcp --proto tcp --dst 1.1.1.1/32 -j drop 可以看到在 nf_hook_slow 函数后发生了变化:
我们可以看到数据包在 nf_hook_slow 判决为 nf_drop,调用了 kfree_skb
int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state, const struct nf_hook_entries *e, unsigned int s){ unsigned int verdict; int ret; for (; s num_hook_entries; s++) { verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state); switch (verdict & nf_verdict_mask) { case nf_accept: break; case nf_drop: kfree_skb(skb); ret = nf_drop_geterr(verdict); if (ret == 0) ret = -eperm; return ret; case nf_queue: ret = nf_queue(skb, state, s, verdict); if (ret == 1) continue; return ret; default: /* implicit handling for nf_stolen, as well as any other * non conventional verdicts. */ return 0; } } return 1;} 原理实现 pwru 本质上是向 kprobe 注册了一些 ebpf code,根据 pwru 传入的参数可以更新 ebpf map,改变限制条件,从而更新输出。
比如在 filtercfg 里面制定了过滤的 ip 地址和协议等条件
type filtercfg struct { filtermark uint32 //filter l3 filteripv6 uint8 filtersrcip [16]byte filterdstip [16]byte //filter l4 filterproto uint8 filtersrcport uint16 filterdstport uint16 //todo: if there are more options later, then you can consider using a bit map outputrelativets uint8 outputmeta uint8 outputtuple uint8 outputskb uint8 outputstack uint8 pad byte} 会根据 pwru 传入的参数更新这个 ebpf map
func configbpfmap(flags *flags, cfgmap *ebpf.map) { cfg := filtercfg{ filtermark: flags.filtermark, } if flags.filtersrcport > 0 { cfg.filtersrcport = byteorder.hosttonetwork16(flags.filtersrcport) } if flags.filterdstport > 0 { cfg.filterdstport = byteorder.hosttonetwork16(flags.filterdstport) } switch strings.tolower(flags.filterproto) { case tcp: cfg.filterproto = syscall.ipproto_tcp case udp: cfg.filterproto = syscall.ipproto_udp case icmp: cfg.filterproto = syscall.ipproto_icmp case icmp6: cfg.filterproto = syscall.ipproto_icmpv6 } // ... if err := cfgmap.update(uint32(0), cfg, 0); err != nil { log.fatalf(failed to set filter map: %v, err) }} 在 ebpf code 中,可以看到会读取配置 bpf_map_lookup_elem,然后进而执行真正的 filter:
struct config { u32 mark; u8 ipv6; union addr saddr; union addr daddr; u8 l4_proto; u16 sport; u16 dport; u8 output_timestamp; u8 output_meta; u8 output_tuple; u8 output_skb; u8 output_stack; u8 pad;} __attribute__((packed));static __always_inline inthandle_everything(struct sk_buff *skb, struct pt_regs *ctx) { struct event_t event = {}; u32 index = 0; struct config *cfg = bpf_map_lookup_elem(&cfg_map, &index); if (cfg) { if (!filter(skb, cfg)) return 0; set_output(ctx, skb, &event, cfg); } event.pid = bpf_get_current_pid_tgid(); event.addr = pt_regs_ip(ctx); event.skb_addr = (u64) skb; event.ts = bpf_ktime_get_ns(); bpf_perf_event_output(ctx, &events, bpf_f_current_cpu, &event, sizeof(event)); return 0;} 可以看到,这里通过 bpf_perf_event_output 将过滤结果以 perf event 传递上来。
rd, err := perf.newreader(events, os.getpagesize())if err != nil { log.fatalf(creating perf event reader: %s, err)}defer rd.close() // ... var event pwru.eventfor { record, err := rd.read() if err != nil { if perf.isclosed(err) { return } log.printf(reading from perf event reader: %s, err) } if record.lostsamples != 0 { log.printf(perf event ring buffer full, dropped %d samples, record.lostsamples) continue } if err := binary.read(bytes.newbuffer(record.rawsample), binary.littleendian, &event); err != nil { log.printf(parsing perf event: %s, err) continue } output.print(&event) select { case <-ctx.done(): break default: continue }}
原文标题:pwru: 一款基于 ebpf 的细粒度网络数据包排查工具
文章出处:【微信公众号:马哥linux运维】欢迎添加关注!文章转载请注明出处。
成都首家无人超市亮相,引数千吃瓜群众围观,开门俩小时门禁系统被刷崩!
Marvell收购Cavium的背后,只是为了“抱团取火”
无纺布干态落絮测试仪的主要组成部分介绍
中国未来5-10年光纤需求量巨大,注重竞争力优先
SNP一秒解答SAP云迁移的四种部署模式
pwru的使用方法、经典场景及实现原理
中国物联网芯片变得强大了?
超声波塑料焊接工艺在纺织行业的应用
如何使用union来了解内存?
谈中国重化工业发展的环境问题分析与对策研究
ChinaJoy完美落幕 手机玩云顶之弈成最热门话题
光电收发模块如何运作?硅光子目前技术瓶颈在哪?
基于单片机控制的交通灯设计
这四个国家看清局势_华为的翻盘机会来了
LG手机王者归来,1.08亿+高通骁龙865+5500mAh
联想新品Z6 Pro发布 后置四摄全程DC调光
变频器可靠运行这四大要素不可少
中兴通讯已率先发布了5G端到端切片方案
2021年中国连接器市场现状及发展趋势
4G DTU为什么要具有透传的功能,它有哪些优势