使用 tcpdump 过滤 IPv6 数据包中的 HTTP 方法

使用 tcpdump 过滤 IPv6 数据包中的 HTTP 方法

我使用“tcpdump”捕获流量,并希望按 HTTP 方法进行过滤。当我有 IPv4 数据包时,我使用:tcpdump -s 0 -A 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420'过滤 HTTP GET 数据包。

不幸的是,该tcp[]过滤器不适用于 IPv6 数据包。根据文档

针对传输层报头的算术表达式(例如tcp[0])对 IPv6 数据包不起作用。

因此,我正在寻找另一种方法,使用 tcpdump 仅过滤 IPv6 流量的 HTTP 方法。

我曾尝试寻找解决方案,但似乎它们包含的 grep 用法不适合我的需要,因为我想将所有过滤后的流量直接输出到 pcap 文件

答案1

tcpdump自 4.99.x 版本起 IPv6的限制

对于 IPv4,要获取数据包中的有效载荷偏移量,可以使用以下方法或多或少地计算有效载荷偏移量IPv4 的国际人道法+ TCP 的数据偏移量还考虑 IPv4 选项和 TCP 选项。这很简单,可以包含在tcpdump直接在生成 BPF 字节码过滤器时(例如,一些 BPF 文档可用那里)。

相反,对于 IPv6,在固定报头和上报头之间可以有可变(可能在 0 到 9 之间)数量的扩展报头。这意味着可能包含 10 种情况(0、1、2、... 9 个扩展报头)的代码来查找数据包中的有效负载偏移量,假设某些情况没有其他特殊性,甚至没有尝试针对格式错误的数据包(这可能导致误报匹配)进行保护:我只能假设这没有实现,因为复杂性和不愿意提供忽略此类报头的有缺陷的实现。

即使是一个简单的测试,例如ip6 and tcp dst port 80不考虑扩展头,如下面的生成的 BPF 代码所示:

$ tcpdump --version 
tcpdump version 4.99.3
libpcap version 1.10.3 (with TPACKET_V3)
OpenSSL 3.0.9 30 May 2023
$ tcpdump -y EN10MB -d ip6 and tcp dst port 80
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 7
(002) ldb      [20]
(003) jeq      #0x6             jt 4    jf 7
(004) ldh      [56]
(005) jeq      #0x50            jt 6    jf 7
(006) ret      #262144
(007) ret      #0

所以可能会错过交通。

从 tcpdump 4.99.x 开始,唯一可以充分处理 IPv6 扩展头的地方似乎记录在手册页的末尾:

ip6 proto应该追逐头链,但目前还没有。ip6 protochain为此行为提供了。

事实上,目前 tcpdump 4.99.xip6 and tcp相当于ip6 proto 6(它们产生相同的字节码):

$ tcpdump -y EN10MB -d ip6 and tcp
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 8
(002) ldb      [20]
(003) jeq      #0x6             jt 7    jf 4
(004) jeq      #0x2c            jt 5    jf 8
(005) ldb      [54]
(006) jeq      #0x6             jt 7    jf 8
(007) ret      #262144
(008) ret      #0

whileip6 protochain 6进行了更彻底的检查,并且是在任何涉及上层(最终传输头)的测试中应该进行的,例如 TCP:

$ tcpdump -y EN10MB -d ip6 protochain 6
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 35
(002) ldb      [20]
(003) ldx      #0x28
(004) jeq      #0x6             jt 32   jf 5
(005) jeq      #0x3b            jt 32   jf 6
(006) jeq      #0x0             jt 10   jf 7
(007) jeq      #0x3c            jt 10   jf 8
(008) jeq      #0x2b            jt 10   jf 9
(009) jeq      #0x2c            jt 10   jf 19
(010) ldb      [x + 14]
(011) st       M[0]
(012) ldb      [x + 15]
(013) add      #1
(014) mul      #8
(015) add      x
(016) tax      
(017) ld       M[0]
(018) ja       4
(019) jeq      #0x33            jt 20   jf 32
(020) txa      
(021) ldb      [x + 14]
(022) st       M[0]
(023) txa      
(024) add      #1
(025) tax      
(026) ldb      [x + 14]
(027) add      #2
(028) mul      #4
(029) tax      
(030) ld       M[0]
(031) ja       4
(032) add      #0
(033) jeq      #0x6             jt 34   jf 35
(034) ret      #262144
(035) ret      #0

上面的 4 个测试(包括jt 10和之间的行(10)并(018) ja 4循环遍历 4 个特定的 IPv6 扩展标头以跳过它们,同时(19)处理(31)IPSec AH。我不知道如何在其他地方生成这样的代码tcpdump语言(而不是直接带通滤波器代码)。使用这种方法ip6 protochain 6 and ip6 and tcp dst port 80只会从头开始,甚至更糟,而不是利用刚刚获得的 TCP 标头。

Linux 的各种网络设施和工具中都描述了同一类问题。例如:tc u32告诉:

icmp_code 值掩码 8

假设下一个标头协议为 icmp 或 ipv6-icmp,并匹配类型或代码字段值。这是很危险的,因为代码假设IPv4 的最小报头大小和缺少 IPv6 扩展标头

所有这些甚至没有考虑到对于 HTTP 而言,当重新使用连接时,下一个 HTTP 查询甚至可能不在数据包边界的开始处,或者如果此类数据出现在数据包边界的开始处,则嵌入为数据而不是查询的查询可能会匹配。

tcpdump使用版本 4.99.x测试 0 扩展头的情况

这是使用 0 个扩展头进行的测试,即与tcpdump目前确实如此,只需进行最低限度的验证:

tcpdump -n -s 0 -A 'ip6[6] == 6 and ( ip6[4:2] - ((ip6[52] & 0xf0) >> 2) >= 4 ) and ip6[40 + ((ip6[52] & 0xf0) >> 2) :4] == 0x47455420'

可以像这样记录(仍然只针对 0 扩展头的情况,其中 TCP 头始终从位置 40 开始):

  • 下一个标头是 TCP

    ip6[6] == 6
    
  • IPv6有效载荷长度:

    ip6[4:2]
    
  • TCP数据偏移量(固定 IPv6 的大小为 40 + 12 = 52)(并进行适当调整以获取字节)

    (ip6[52] & 0xf0) >> 2
    
  • TCP 有效载荷长度 = IPv6 有效载荷长度 - TCP 数据偏移量

  • 测试 TCP 有效负载是否至少有 4 个字节,长度至少为“GET”

    ip6[4:2] - ((ip6[52] & 0xf0) >> 2)  >= 4
    
  • 以 ASCII/UTF-8 编码的 4 字节字符串“GET”可以用 4 字节值 0x47455420 表示

  • TCP 有效载荷偏移量为 IPv6 固定报头长度 (40) + TCP 数据偏移量

    40 + ((ip6[52] & 0xf0) >> 2)
    
  • 测试从 IPv6 固定报头长度 (40) + 数据偏移量开始的第一个 4 字节字是否等于字符串“GET”的值

    ip6[40 + ((ip6[52] & 0xf0) >> 2) :4] = 0x47455420
    

nftablesLinux 内核 >= 5.15.54 +tcpdump

我建议使用一种简单的替代 Linux 专用方法nftables并且需要足够新的内核(Linux 内核版本>= 5.15.54反向移植自5.16) 也nftables>= 1.0.1支持@ih(内部标头/有效载荷)原始有效载荷,而不仅仅是@th(传输头),因为nftables其本身太过有限,无法以@th有用的方式进行任意处理。

IPv6 数据报已被解析,包括任何 IPv6 扩展报头,并且 @th 和 @in 指针已经可用于nftables:无需进一步处理即可获得所有情况。我依靠nftables如果数据包太短,则检查失败,而无需计算实际的 TCP 有效负载大小(与tcpdump和 BPF,nftables无法进行减法,因此无法正确检查大小)。

nftables下面的示例试图模仿 OP 的精确示例,但如果不进行一些调整,就无法在所有细节上保持一致。没有方向也没有端口,在 NAT 之前捕获传入数据包,在 NAT 之后捕获传出数据包(因此选择了钩子和优先级)。最终将选定的数据包发送至日志设施

在第二种调用形式中(如果nflog_group指定后,Linux 内核会将数据包传递给 nfnetlink_log,后者会通过 netlink 套接字将日志发送到指定组。一个用户空间进程可以订阅该组以接收日志 [...]

table ip6 special_log
delete table ip6 special_log

table ip6 special_log {
        chain log_to_nflog {
                log group 4242
        }

        chain ih_filter {
                meta l4proto tcp @ih,0,32 0x47455420 counter jump log_to_nflog
        }

        chain c_ingress {
                type filter hook prerouting priority -150; policy accept;
                jump ih_filter
        }
        chain c_egress {
                type filter hook postrouting priority 150; policy accept;
                jump ih_filter
        }
}

在 Linux 上,日志设施可作为伪接口使用(libpcap和)tcpdump

# tcpdump -D |grep nflog
12.nflog (Linux netfilter log (NFLOG) interface) [none]

选择的数据包nftables因此可以显示(重复使用相同的 NFLOG 组:4242):

tcpdump -n -s 0 -A -i nflog:4242

警告:与直接捕获相比,延迟更大。

注意:正确处理nftables具有扩展报头的情况实际上是用 UDP(而不是 TCP)和碎片数据包(由片段头) 使用socat(在单独的网络命名空间中以防止nf_defrag_ipv6启动),因为使用 UDP 比使用 TCP 更容易生成碎片数据包,并且我没有找到其他方法在某处使用扩展头。

相关内容