用法
tcpdump -s len
原理
tcpdump会把这个长度值编译到ebpf代码中。 ebpf代码执行完后,如果filter成功,则作为返回值(return)返回给调用方(内核协议栈),内核协议栈根据返回值,修改 skb报文到指定的长度,并借助 af socket 返回给用户空间。
tcpdump命令解析
1 | ubuntu@VM-0-7-ubuntu:~$ tcpdump -d dst host 9.9.9.9 |
tcpdump -s len
tcpdump会把这个长度值编译到ebpf代码中。 ebpf代码执行完后,如果filter成功,则作为返回值(return)返回给调用方(内核协议栈),内核协议栈根据返回值,修改 skb报文到指定的长度,并借助 af socket 返回给用户空间。
1 | ubuntu@VM-0-7-ubuntu:~$ tcpdump -d dst host 9.9.9.9 |
__struct_group
浏览IPv6代码时候,看到这样一个新玩法,__struct_group
1 | 118 struct ipv6hdr { |
###用法
再看一下用法:
922 /* copy IPv6 saddr & daddr to flow_keys, possibly using 64bit load/store
923 * Equivalent to : flow->v6addrs.src = iph->saddr;
924 * flow->v6addrs.dst = iph->daddr;
925 */
926 static inline void iph_to_flow_copy_v6addrs(struct flow_keys *flow,
927 const struct ipv6hdr *iph)
928 {
...
932 memcpy(&flow->addrs.v6addrs, &iph->addrs, sizeof(flow->addrs.v6addrs));
...
1
2
3
###
11 /**
12 * __struct_group() - Create a mirrored named and anonyomous struct
13 *
14 * @TAG: The tag name for the named sub-struct (usually empty)
15 * @NAME: The identifier name of the mirrored sub-struct
16 * @ATTRS: Any struct attributes (usually empty)
17 * @MEMBERS: The member declarations for the mirrored structs
18 *
19 * Used to create an anonymous union of two structs with identical layout
20 * and size: one anonymous and one named. The former's members can be used
21 * normally without sub-struct naming, and the latter can be used to
22 * reason about the start, end, and size of the group of struct members.
23 * The named struct can also be explicitly tagged for layer reuse, as well
24 * as both having struct attributes appended.
25 */
26 #define __struct_group(TAG, NAME, ATTRS, MEMBERS...) \
27 union { \
28 struct { MEMBERS } ATTRS; \
29 struct TAG { MEMBERS } ATTRS NAME; \
30 } ATTRS
```
IPv6在扩展头和协议处理上跟IPv4还有些不一样。
nexthdr
类型串联在一起,不像IPv4那样扩展头是单独的option,上层协议类型放到ip头里的proto字段。nexthdr
,本扩展的长度这两个信息,然后跟本扩展头相关的一些数据。协议栈通过一个inet6_protocol
类型的数组,保存IPv6所有的4层处理协议入口。
1 | struct inet6_protocol __rcu *inet6_protos[MAX_INET_PROTOS] __read_mostly; |
inet6_protos
的下标对应的就是每个扩展协议在IPv6扩展头里的nexthdr
值inet6_protocol
结构体struct inet6_protocol
结构体里的handler是扩展头的处理入口函数。INET6_PROTO_NOPOLICY
和INET6_PROTO_FINAL
INET6_PROTO_FINAL
: 这个扩展是否可以作为IPv6的最后一个扩展, 比如IPPROTO_DSTOPTS
不可以。INET6_PROTO_NOPOLICY
: 这个要求必须有对应的IPsec/xfrm XFRM_POLICY_IN
的规则1 | 53 struct inet6_protocol { |
1 | 66 #define INET6_PROTO_NOPOLICY 0x1 |
1 | ==> ipv6_rcv |
1 | 52 struct inet6_protocol { |
1 | 28 int inet6_add_protocol(const struct inet6_protocol *prot, unsigned char protocol) |
1 | 35 int inet6_del_protocol(const struct inet6_protocol *prot, unsigned char protocol) |
1 | ➜ linux git:(master) grep inet6_add_protocol net include -Rw |
IPPROTO_ROUTING
字段处理1 | 835 static const struct inet6_protocol rthdr_protocol = { |
1 | ret = inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING); |
1 | 2406 net_hotdata.tcpv6_protocol = (struct inet6_protocol) { |
1 | 239 static const struct inet6_protocol tunnel6_protocol = { |
这里以tcpdump(PF_PACKET)为例,结合函数调用关系说明,
skb是如何被当做ctx参数传递给bpf程序的
注: 内核版本v6.6
1 | --> packet_rcv |
以xdp SKB模式为例,
1 | --> bpf_prog_run_generic_xdp |
xdp在内核里的有三个关键步骤:
load
: 加载到内核attach
: 绑定到一个网口run
:网口收包时候,调用并执行bpf progload
加载: 通过ebpf系统调用, 把prog加载到内核fd = sys_bpf(BPF_PROG_LOAD, attr, size);
bfp_prog
结构体用以存储bpf prog。bpf_check
检查prog程序的安全性和合法性。bpf_prog_select_runtime
指定bpf prog对应的执行函数bpf_func
这个字段里。这里的function最终指向通用的bfp run函数___bpf_prog_run
。___bpf_prog_run
这个具体封装和实现见另外一篇文章。attach
绑定: 将prog程序绑定到一个特定的网口的struct net_device
上libpf函数do_attach
将上一步加载在内核里的prog跟一个网口绑定, 具体实现是通过下发netlink命令。
这是个generic类型的netlink命令,最终通过dev_change_xdp_fd
将prog挂载到对应netdev下面。
问题描述:
tcpdump在网口抓包和读取pcap文件,相同的filter表达式”icmp“,对vlan报文有不同的处理结果。
在网口上抓包可以看到vlan和非vlan两种流量, 而读取pcap只能看到非vlan流量一种流量。
原因:
kernel里在tcpdump抓包之前会把报文的vlan提前解析掉,并把vlan信息放到skb的metadata里了。
所以tcpdump(内核的af_packet对应的ptype_all)处理的报文都是不带vlan的,因此这两类报文都会被过滤出来。
而读取pcap时候,因为vlan报文并没有被剥离掉,所以vlan报文不满足过滤条件,被丢弃了。
国外已经有人发现并分析过这个问题https://andreaskaris.github.io/blog/networking/bpf-and-tcpdump/
1 | ip netns exec ns1 ip l s veth1 down |