tcpdump如何实现参数-报文长度过滤

用法

tcpdump -s len

原理

tcpdump会把这个长度值编译到ebpf代码中。 ebpf代码执行完后,如果filter成功,则作为返回值(return)返回给调用方(内核协议栈),内核协议栈根据返回值,修改 skb报文到指定的长度,并借助 af socket 返回给用户空间。

tcpdump命令解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ubuntu@VM-0-7-ubuntu:~$ tcpdump  -d dst host 9.9.9.9
Warning: assuming Ethernet
(000) ldh [12]
(001) jeq #0x800 jt 2 jf 4
(002) ld [30]
(003) jeq #0x9090909 jt 8 jf 9
(004) jeq #0x806 jt 6 jf 5
(005) jeq #0x8035 jt 6 jf 9
(006) ld [38]
(007) jeq #0x9090909 jt 8 jf 9
(008) ret #262144
(009) ret #0
ubuntu@VM-0-7-ubuntu:~$ tcpdump -d dst host 9.9.9.9 -s 100
Warning: assuming Ethernet
(000) ldh [12]
(001) jeq #0x800 jt 2 jf 4
(002) ld [30]
(003) jeq #0x9090909 jt 8 jf 9
(004) jeq #0x806 jt 6 jf 5
(005) jeq #0x8035 jt 6 jf 9
(006) ld [38]
(007) jeq #0x9090909 jt 8 jf 9
(008) ret #100
(009) ret #0
ubuntu@VM-0-7-ubuntu:~$
1
151 #define MAXIMUM_SNAPLEN         262144

代码分析

TCPDUMP代码

  1. tcpdump 解析’s’参数,并把长度值存放到 ndo 变量中,并最终存放到 pcap_t的句柄中。
  2. 编译 ebpf 指令时候, 根据pcap——中的报文长度值, 在 bpf 程序最尾部增加一个 RET 指令,并将长度值作为返回值。
  3. ebpf 在内核协议栈中执行, 如果报文不满足 filter 条件, 则返回负值。如果满足 filter 条件,则返回指定的长度值
  4. 内核协议栈,根据返回的长度值,对 skb 报文进行截断。
调用栈
1
2
3
4
5
6
7
8
==> tcpdump main 函数解析:ndo->ndo_snaplen = (int)strtol(optarg, &end, 0);
==> ==> pd = open_interface(device, ndo, ebuf);
==> ==> ==> status = pcap_set_snaplen(pc, ndo->ndo_snaplen);
==> ==> pcap_compile(pd, &fcode, cmdbuf, Oflag, netmask)
==> ==> ==> cstate.snaplen = pcap_snapshot(p);
==> ==> ==> cstate.ic.root = gen_retblk(&cstate, cstate.snaplen);
==> ==> ==> program->bf_insns = icode_to_fcode(&cstate.ic, ...)
==> ==> ==> ==> ==> ==>
tcpdump解析
  • step1:tcpdump main函数,解析snaplen参数,获取长度值,存放ndo 里。创建句柄时候,再赋值到句柄里。
    1
    2
    3
    4
    5
    6
    1846                 case 's':
    1847 ndo->ndo_snaplen = (int)strtol(optarg, &end, 0);
    ...
    2229 pd = open_interface(device, ndo, ebuf);
    ...
    2347 if (pcap_compile(pd, &fcode, cmdbuf, Oflag, netmask) < 0)
1
2
3
4
5
1259 static pcap_t *
1260 open_interface(const char *device, netdissect_options *ndo, char *ebuf)
1261 {
...
1358 status = pcap_set_snaplen(pc, ndo->ndo_snaplen);
1
2
3
4
5
6
7
8
2591 int
2592 pcap_set_snaplen(pcap_t *p, int snaplen)
2593 {
2594 if (pcapint_check_activated(p))
2595 return (PCAP_ERROR_ACTIVATED);
2596 p->snapshot = snaplen;
2597 return (0);
2598 }
1
2
3
4
5
6
7
3490 int
3491 pcap_snapshot(pcap_t *p)
3492 {
3493 if (!p->activated)
3494 return (PCAP_ERROR_NOT_ACTIVATED);
3495 return (p->snapshot);
3496 }
1
2
3
4
5
6
7
8
9
861 int
862 pcap_compile(pcap_t *p, struct bpf_program *program,
863 const char *buf, int optimize, bpf_u_int32 mask)
864 {
...
934 cstate.snaplen = pcap_snapshot(p);
...
971 if (cstate.ic.root == NULL) {
972
kernel 代码
1
2
3
4
5
6
7
8
9
10
2219         snaplen = skb_frags_readable(skb) ? skb->len : skb_headlen(skb);
2220
2221 res = run_filter(skb, sk, snaplen);
2222 if (!res)
2223 goto drop_n_restore;
2224 if (snaplen > res)
2225 snaplen = res;
...
2260 if (pskb_trim(skb, snaplen))
2261 goto drop_n_acct;