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

Sag*_*313 7 networking linux ipv6 tcpdump

我正在使用“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 文件

A.B*_*A.B 3

tcpdump从版本 4.99.x 开始,IPv6的限制

对于 IPv4,要获取数据包中的有效负载偏移量,只需使用IPv4 的 IHL + TCP 数据偏移量来或多或少地计算有效负载偏移量,同时考虑 IPv4 选项和 TCP 选项。这很简单,可以在生成 BPF 字节码过滤器时直接包含在tcpdump中(例如,那里有一些 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
Run Code Online (Sandbox Code Playgroud)

所以可能会错过交通。

从 tcpdump 4.99.x 开始,唯一对 IPv6 扩展标头进行充分处理的地方记录在手册页的末尾:

ip6 proto应该追逐标头链,但目前它没有。ip6 protochain是为此行为提供的。

事实上,目前从 tcpdump 4.99.x 起ip6 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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

Linux 的各种网络设施和工具中都描述了同样类型的问题。示例:tc u32讲述:

icmp_code VAL_MASK_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'
Run Code Online (Sandbox Code Playgroud)

可以这样记录(仍然仅适用于 0 扩展标头情况,其中 TCP 标头始终从位置 40 开始):

Linux 内核 >= 5.15.54 + tcpdump的nftables

我提出了一种简单的替代仅适用于 Linux 的方法,使用nftables并需要足够新的内核(Linux 内核版本>= 5.15.545.16向后移植)以及nftables >= 1.0.1以支持@ih(内部标头/有效负载)原始有效负载,而不仅仅是@th(传输头),因为nftables本身太有限,无法以@th有用的方式进行任意处理。

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

下面的nftables示例尝试模仿 OP 的精确示例,但如果不进行一些调整,就无法始终在所有细节上都做到这一点。无方向或端口,捕获 NAT 之前的传入数据包和 NAT 之后的传出数据包(因此选择了挂钩和优先级)。选定的数据包最终发送到nflog设施

在第二种调用形式中(如果指定了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
        }
}
Run Code Online (Sandbox Code Playgroud)

在 Linux 上,nflog工具可用作(libpcap和)tcpdump的伪接口:

$ 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
Run Code Online (Sandbox Code Playgroud)

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

tcpdump -n -s 0 -A -i nflog:4242
Run Code Online (Sandbox Code Playgroud)

注意:比直接捕获有更多的延迟。

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