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 文件
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_codeVAL_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 开始):
下一个标头是 TCP
ip6[6] == 6
Run Code Online (Sandbox Code Playgroud)
IPv6有效负载长度:
ip6[4:2]
Run Code Online (Sandbox Code Playgroud)
TCP数据偏移(固定 IPv6 的大小为 40 + 12 = 52)(并进行适当调整以获取字节)
(ip6[52] & 0xf0) >> 2
Run Code Online (Sandbox Code Playgroud)
TCP 负载长度 = IPv6 负载长度 - TCP 数据偏移量
测试至少有 4 个字节的 TCP 有效负载,至少具有“GET”的长度
ip6[4:2] - ((ip6[52] & 0xf0) >> 2) >= 4
Run Code Online (Sandbox Code Playgroud)
以 ASCII/UTF-8 编码的 4 字节字符串“GET”可以用 4 字节值 0x47455420 表示
TCP 负载偏移量为 IPv6 固定标头长度 (40) + TCP 数据偏移量
40 + ((ip6[52] & 0xf0) >> 2)
Run Code Online (Sandbox Code Playgroud)
测试从 IPv6 固定标头长度 (40) + 数据偏移开始的第一个 4 字节字是否等于字符串“GET”的值
ip6[40 + ((ip6[52] & 0xf0) >> 2) :4] = 0x47455420
Run Code Online (Sandbox Code Playgroud)
我提出了一种简单的替代仅适用于 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设施:
在第二种调用形式中(如果指定了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,并且我没有找到其他方法在某处具有扩展标头。