diff --git a/src/glb-redirect/Makefile b/src/glb-redirect/Makefile index 61cf6b1..7783359 100644 --- a/src/glb-redirect/Makefile +++ b/src/glb-redirect/Makefile @@ -34,6 +34,29 @@ endif endif endif +# Probe the kernel headers for the socket-lookup helper arity. Upstream removed +# the 'struct inet_hashinfo *hashinfo' argument (callers no longer pass +# &tcp_hashinfo), but distributions may backport that change, so a +# LINUX_VERSION_CODE check in the C code is not reliable on its own. When we can +# read net/inet_hashtables.h we pass a definitive answer through to the C code; +# otherwise it falls back to a version heuristic. The old declaration carries a +# 'struct inet_hashinfo *hashinfo' parameter -- the presence of the 'inet_hashinfo' +# type name in the inet_lookup_established() declaration distinguishes the two +# forms. NB: as with the cookie probe above, keep the awk free of parentheses +# and commas -- embedding either inside $(shell ...) breaks make's +# function/conditional parser, so we terminate accumulation on the first ';'. +HASHINFO_INET_H := $(firstword $(wildcard $(srctree)/include/net/inet_hashtables.h $(srctree)/source/include/net/inet_hashtables.h)) +ifneq ($(HASHINFO_INET_H),) +HASHINFO_DECL := $(shell awk '/inet_lookup_established/{f=1} f{b=b $$0} f&&/;/{print b; exit}' $(HASHINFO_INET_H)) +ifneq ($(HASHINFO_DECL),) +ifeq ($(findstring inet_hashinfo,$(HASHINFO_DECL)),) +ccflags-y += -DGLB_INET_LOOKUP_NO_HASHINFO_ARG +else +ccflags-y += -DGLB_INET_LOOKUP_HAS_HASHINFO_ARG +endif +endif +endif + all: lib kmod kmod: @@ -47,6 +70,16 @@ clean: .PHONY: lib lib: libxt_GLBREDIRECT.so +# Test/debug helper: echo the socket-lookup arity flag that the header probe +# above resolved (empty if it could not classify, in which case the C code +# falls back to a LINUX_VERSION_CODE heuristic). Exercised by +# script/test against fixture headers, so both detection +# branches are validated without needing a full kernel build. Point it at a +# fixture tree with `make srctree= print-lookup-arg`. +.PHONY: print-lookup-arg +print-lookup-arg: + @echo "$(filter -DGLB_INET_LOOKUP_%,$(ccflags-y))" + .PHONY: install install: lib kmod install -d $(DESTDIR)$(shell pkg-config --variable=xtlibdir xtables) diff --git a/src/glb-redirect/ipt_GLBREDIRECT.c b/src/glb-redirect/ipt_GLBREDIRECT.c index 74b1957..6abf8ff 100644 --- a/src/glb-redirect/ipt_GLBREDIRECT.c +++ b/src/glb-redirect/ipt_GLBREDIRECT.c @@ -113,6 +113,35 @@ struct glbgue_stats { # define GLB_COOKIE_V6_CHECK(iph, th) __cookie_v6_check((iph), (th)) #endif +/* + * The socket-lookup helpers inet_lookup_established(), inet_lookup_listener(), + * __inet6_lookup_established() and inet6_lookup_listener() lost their + * 'struct inet_hashinfo *hashinfo' argument upstream in Linux 6.18 (commit + * cb16f4b6c73d, "tcp: Don't pass hashinfo to socket lookup helpers"): the + * global tcp_hashinfo is now reached internally via the net namespace, so + * callers no longer pass &tcp_hashinfo. v6.17 still carries the argument. As + * with the cookie-check change above, distributions may backport this + * independently of the mainline version, so the Makefile probes the kernel + * headers for the actual arity and defines one of the macros below; if it + * could not probe the header, we fall back to the mainline version boundary. + */ +#if !defined(GLB_INET_LOOKUP_HAS_HASHINFO_ARG) && !defined(GLB_INET_LOOKUP_NO_HASHINFO_ARG) +# if LINUX_VERSION_CODE >= KERNEL_VERSION(6,18,0) +# define GLB_INET_LOOKUP_NO_HASHINFO_ARG +# else +# define GLB_INET_LOOKUP_HAS_HASHINFO_ARG +# endif +#endif + +#ifdef GLB_INET_LOOKUP_HAS_HASHINFO_ARG + /* Expands to the leading '&tcp_hashinfo,' argument (note the trailing + * comma) for kernels that still expect it. */ +# define GLB_TCP_HASHINFO_ARG &tcp_hashinfo, +#else + /* The hashinfo argument was removed; expand to nothing. */ +# define GLB_TCP_HASHINFO_ARG +#endif + struct glbgue_stats __percpu *percpu_stats; static unsigned int is_valid_locally(struct net *net, struct sk_buff *skb, int inner_ip_ofs, struct iphdr *iph_v4, struct ipv6hdr *iph_v6, struct tcphdr *th); @@ -571,12 +600,12 @@ static unsigned int is_valid_locally(struct net *net, struct sk_buff *skb, int i struct sock *nsk; if (likely(iph_v4 != NULL)) { - nsk = inet_lookup_established(net, &tcp_hashinfo, + nsk = inet_lookup_established(net, GLB_TCP_HASHINFO_ARG iph_v4->saddr, th->source, iph_v4->daddr, th->dest, inet_iif(skb)); } else if (likely(iph_v6 != NULL)) { - nsk = __inet6_lookup_established(net, &tcp_hashinfo, + nsk = __inet6_lookup_established(net, GLB_TCP_HASHINFO_ARG &iph_v6->saddr, th->source, &iph_v6->daddr, ntohs(th->dest), #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0) @@ -607,7 +636,7 @@ static unsigned int is_valid_locally(struct net *net, struct sk_buff *skb, int i if (likely(iph_v4 != NULL)) { /* IPv4 */ - listen_sk = inet_lookup_listener(net, &tcp_hashinfo, + listen_sk = inet_lookup_listener(net, GLB_TCP_HASHINFO_ARG #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,6,0) skb, ip_hdrlen(skb) + __tcp_hdrlen(th), #endif @@ -647,7 +676,7 @@ static unsigned int is_valid_locally(struct net *net, struct sk_buff *skb, int i } else if (likely(iph_v6 != NULL)) { /* IPv6 */ - listen_sk = inet6_lookup_listener(net, &tcp_hashinfo, + listen_sk = inet6_lookup_listener(net, GLB_TCP_HASHINFO_ARG #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,6,0) skb, ip_hdrlen(skb) + __tcp_hdrlen(th), #endif diff --git a/src/glb-redirect/script/test b/src/glb-redirect/script/test index a8236d9..6da4109 100755 --- a/src/glb-redirect/script/test +++ b/src/glb-redirect/script/test @@ -110,6 +110,82 @@ compile_check() { echo "compile-check: ok (libxt_GLBREDIRECT.so + ipt_GLBREDIRECT.ko built)" } +# --------------------------------------------------------------------------- +# Verify the Makefile's socket-lookup arity probe classifies kernel headers +# correctly. +# +# Upstream dropped the `struct inet_hashinfo *hashinfo` argument from +# inet_lookup_established() and friends (callers no longer pass &tcp_hashinfo). +# ipt_GLBREDIRECT.c adapts via the GLB_TCP_HASHINFO_ARG macro, whose value is +# chosen by a header probe in the Makefile (with a LINUX_VERSION_CODE fallback +# in the C code). compile_check() above only builds against the running kernel, +# so it exercises just one of the two API forms; this drives the probe against +# fixture headers for BOTH forms, so a regression in either branch is caught +# regardless of the host kernel. It needs only `make` + `awk`, so it runs even +# where full kernel headers / the build toolchain are unavailable. +# --------------------------------------------------------------------------- +lookup_arg_detection_check() { + local fixtures fail=0 + + # Write a minimal net/inet_hashtables.h containing just the + # inet_lookup_established declaration in the requested form. + # $1 = variant name (subdir under $fixtures) + # $2 = "old" (carries hashinfo arg) | "new" (hashinfo arg removed) + write_lookup_header() { + local dir="$fixtures/$1/include/net" + mkdir -p "$dir" + if [ "$2" = "old" ]; then + cat > "$dir/inet_hashtables.h" <<'EOF' +struct sock *__inet_lookup_established(struct net *net, + struct inet_hashinfo *hashinfo, + const __be32 saddr, const __be16 sport, + const __be32 daddr, const u16 hnum, + const int dif, const int sdif); +EOF + else + cat > "$dir/inet_hashtables.h" <<'EOF' +struct sock *__inet_lookup_established(const struct net *net, + const __be32 saddr, const __be16 sport, + const __be32 daddr, const u16 hnum, + const int dif, const int sdif); +EOF + fi + } + + # Ask the real Makefile probe how it classified a given fixture tree, and + # assert the expected macro was selected. + # $1 = variant, $2 = expected macro substring, $3 = human label + check_lookup() { + local got + got="$(make -s srctree="$fixtures/$1" print-lookup-arg 2>/dev/null || true)" + if [[ "$got" == *"$2"* ]]; then + echo "lookup-arg-detection: ok: $3 header => $2" + else + echo "lookup-arg-detection: FAIL: $3 header classified as [${got:-}], expected $2" >&2 + fail=1 + fi + } + + fixtures="$(mktemp -d)" + trap 'rm -rf -- "$fixtures"' EXIT + + write_lookup_header old old + write_lookup_header new new + + check_lookup old GLB_INET_LOOKUP_HAS_HASHINFO_ARG old + check_lookup new GLB_INET_LOOKUP_NO_HASHINFO_ARG new + + rm -rf -- "$fixtures" + trap - EXIT + + if [ "$fail" -ne 0 ]; then + echo "lookup-arg-detection: FAILED" >&2 + return 1 + fi + echo "lookup-arg-detection: ok" +} + compile_check +lookup_arg_detection_check PYTHONPATH=$(pwd)/../scapy-glb-gue/:$PYTHONPATH pytest -v tests/