From cccbbb453f2f390e9ec1a81e4a720336a6654a45 Mon Sep 17 00:00:00 2001 From: Jerome Marchand Date: Tue, 7 Oct 2025 16:58:04 +0200 Subject: [PATCH] tools/tcpaccept: Fix tcpaccept on recent kernel The tcpaccept use the relative offset of gso_max_segs and sk_lingertime_offset to check whether sk_protocol is its own field of part of a bitfield and find it's location. This is not very robust. Use BPF.kernel_struct_has_field() to find out whether it's its own field and revert to the old workaround if it's part of a bitfield or BTF is unavailable. Closes: #5316 Signed-off-by: Jerome Marchand --- tools/tcpaccept.py | 86 +++++++++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/tools/tcpaccept.py b/tools/tcpaccept.py index 10c7901f271b..c4af2a5eea3e 100755 --- a/tools/tcpaccept.py +++ b/tools/tcpaccept.py @@ -117,42 +117,8 @@ // check this is TCP u16 protocol = 0; - // workaround for reading the sk_protocol bitfield: - - // Following comments add by Joe Yin: - // Unfortunately,it can not work since Linux 4.10, - // because the sk_wmem_queued is not following the bitfield of sk_protocol. - // And the following member is sk_gso_max_segs. - // So, we can use this: - // bpf_probe_read_kernel(&protocol, 1, (void *)((u64)&newsk->sk_gso_max_segs) - 3); - // In order to diff the pre-4.10 and 4.10+ ,introduce the variables gso_max_segs_offset,sk_lingertime, - // sk_lingertime is closed to the gso_max_segs_offset,and - // the offset between the two members is 4 - int gso_max_segs_offset = offsetof(struct sock, sk_gso_max_segs); - int sk_lingertime_offset = offsetof(struct sock, sk_lingertime); - - - // Since kernel v5.6 sk_protocol is its own u16 field and gso_max_segs - // precedes sk_lingertime. - if (sk_lingertime_offset - gso_max_segs_offset == 2) - protocol = newsk->sk_protocol; - else if (sk_lingertime_offset - gso_max_segs_offset == 4) - // 4.10+ with little endian -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - protocol = *(u8 *)((u64)&newsk->sk_gso_max_segs - 3); - else - // pre-4.10 with little endian - protocol = *(u8 *)((u64)&newsk->sk_wmem_queued - 3); -#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - // 4.10+ with big endian - protocol = *(u8 *)((u64)&newsk->sk_gso_max_segs - 1); - else - // pre-4.10 with big endian - protocol = *(u8 *)((u64)&newsk->sk_wmem_queued - 1); -#else -# error "Fix your compiler's __BYTE_ORDER__?!" -#endif + ##GET_SK_PROTOCOL## if (protocol != IPPROTO_TCP) return 0; @@ -196,6 +162,51 @@ } """ +get_sk_protocol_field = """ + protocol = newsk->sk_protocol; +""" + +get_sk_protocol_bitfield = """ + // workaround for reading the sk_protocol bitfield: + + // Following comments add by Joe Yin: + // Unfortunately,it can not work since Linux 4.10, + // because the sk_wmem_queued is not following the bitfield of sk_protocol. + // And the following member is sk_gso_max_segs. + // So, we can use this: + // bpf_probe_read_kernel(&protocol, 1, (void *)((u64)&newsk->sk_gso_max_segs) - 3); + // In order to diff the pre-4.10 and 4.10+ ,introduce the variables gso_max_segs_offset,sk_lingertime, + // sk_lingertime is closed to the gso_max_segs_offset,and + // the offset between the two members is 4 + + int gso_max_segs_offset = offsetof(struct sock, sk_gso_max_segs); + int sk_lingertime_offset = offsetof(struct sock, sk_lingertime); + + + // Since kernel v5.6 sk_protocol is its own u16 field and gso_max_segs + // precedes sk_lingertime. + // We keep this workaround in case BTF is unavailable + if (sk_lingertime_offset - gso_max_segs_offset == 2) + protocol = newsk->sk_protocol; + else if (sk_lingertime_offset - gso_max_segs_offset == 4) + // 4.10+ with little endian +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + protocol = *(u8 *)((u64)&newsk->sk_gso_max_segs - 3); + else + // pre-4.10 with little endian + protocol = *(u8 *)((u64)&newsk->sk_wmem_queued - 3); +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // 4.10+ with big endian + protocol = *(u8 *)((u64)&newsk->sk_gso_max_segs - 1); + else + // pre-4.10 with big endian + protocol = *(u8 *)((u64)&newsk->sk_wmem_queued - 1); +#else +# error "Fix your compiler's __BYTE_ORDER__?!" +#endif +""" + + bpf_text += bpf_text_kprobe # code substitutions @@ -216,6 +227,11 @@ bpf_text = bpf_text.replace('##FILTER_FAMILY##', 'if (family != AF_INET6) { return 0; }') +if BPF.kernel_struct_has_field("sock", "sk_protocol") == 1: + bpf_text = bpf_text.replace('##GET_SK_PROTOCOL##', get_sk_protocol_field) +else: + bpf_text = bpf_text.replace('##GET_SK_PROTOCOL##', get_sk_protocol_bitfield) + bpf_text = filter_by_containers(args) + bpf_text if debug or args.ebpf: print(bpf_text)