diff --git a/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.go b/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.go index e32aedd3..73842b56 100644 --- a/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.go +++ b/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.go @@ -1,5 +1,5 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build arm64be || armbe || mips || mips64 || mips64p32 || ppc64 || s390 || s390x || sparc || sparc64 +//go:build mips || mips64 || ppc64 || s390x package ebpf diff --git a/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o b/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o index 7c9e91d7..8af544f0 100644 Binary files a/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o and b/service/firewall/interception/ebpf/connection_listener/bpf_bpfeb.o differ diff --git a/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.go b/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.go index b29ea58e..9650a09b 100644 --- a/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.go +++ b/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.go @@ -1,5 +1,5 @@ // Code generated by bpf2go; DO NOT EDIT. -//go:build 386 || amd64 || amd64p32 || arm || arm64 || loong64 || mips64le || mips64p32le || mipsle || ppc64le || riscv64 +//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 package ebpf diff --git a/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.o b/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.o index cece7260..4388c5a8 100644 Binary files a/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.o and b/service/firewall/interception/ebpf/connection_listener/bpf_bpfel.o differ diff --git a/service/firewall/interception/ebpf/connection_listener/worker.go b/service/firewall/interception/ebpf/connection_listener/worker.go index 0768b73d..b52c168b 100644 --- a/service/firewall/interception/ebpf/connection_listener/worker.go +++ b/service/firewall/interception/ebpf/connection_listener/worker.go @@ -7,9 +7,12 @@ import ( "errors" "fmt" "net" + "strings" "sync/atomic" "time" + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/btf" "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/ringbuf" "github.com/cilium/ebpf/rlimit" @@ -35,7 +38,7 @@ func ConnectionListenerWorker(ctx context.Context, packets chan packet.Packet) e // Load pre-compiled programs and maps into the kernel. objs := bpfObjects{} - if err := loadBpfObjects(&objs, nil); err != nil { + if err := loadBpfObjects_Ex(&objs, nil); err != nil { if ebpfLoadingFailed.Add(1) >= 5 { log.Warningf("ebpf: failed to load ebpf object 5 times, giving up with error %s", err) return nil @@ -129,6 +132,97 @@ func ConnectionListenerWorker(ctx context.Context, packets chan packet.Packet) e } } +// loadBpfObjects_Ex loads eBPF objects with kernel-aware attach point selection. +// (it extends the standard loadBpfObjects()) +// +// This enhanced loader automatically detects available kernel functions and selects +// appropriate attach points for maximum compatibility across kernel versions. It handles +// the transition from legacy function names (e.g., ip4_datagram_connect) to modern ones +// (e.g., udp_connect) introduced in Linux 6.13+. +func loadBpfObjects_Ex(objs interface{}, opts *ebpf.CollectionOptions) error { + // Load pre-compiled programs + spec, err := loadBpf() + if err != nil { + return fmt.Errorf("ebpf: failed to load ebpf spec: %w", err) + } + // Modify the attach points of the eBPF programs, if necessary. + if err := modifyProgramsAttachPoints(spec); err != nil { + return fmt.Errorf("ebpf: failed to modify program attach points: %w", err) + } + // Load the eBPF programs and maps into the kernel. + if err := spec.LoadAndAssign(objs, opts); err != nil { + return fmt.Errorf("ebpf: failed to load and assign ebpf objects: %w", err) + } + return nil +} + +// modifyProgramAttachPoints modifies the attach points of the eBPF programs, if necessary. +// This is needed to ensure compatibility with different kernel versions. +func modifyProgramsAttachPoints(spec *ebpf.CollectionSpec) error { + // Load the kernel spec + kspec, err := btf.LoadKernelSpec() + if err != nil { + return err + } + + // Function to update the attach point to a single BPF program + updateIfNeeded := func(bpfProgramName, attachPoint, attachPointLegacy string) error { + ps, ok := spec.Programs[bpfProgramName] + if !ok { + return fmt.Errorf("ebpf: program %q not found in spec", bpfProgramName) + } + var fn *btf.Func + if err := kspec.TypeByName(attachPoint, &fn); err == nil { + if !strings.EqualFold(ps.AttachTo, attachPoint) { + ps.AttachTo = attachPoint + log.Debugf("ebpf: using attach point %q for %q program", attachPoint, bpfProgramName) + } + } else { + if !strings.EqualFold(ps.AttachTo, attachPointLegacy) { + ps.AttachTo = attachPointLegacy + log.Debugf("ebpf: using legacy attach point %q for %q program", attachPointLegacy, bpfProgramName) + } + } + return nil + } + + // 'udp_v4_connect' program is designed to attach to the `udp_connect` function in the kernel. + // If the kernel does not support this function, we fall back to using the `ip4_datagram_connect` function. + // + // Kernel compatibility note: + // - Linux kernels < 6.13: use `ip4_datagram_connect` function + // https://elixir.bootlin.com/linux/v6.12.34/source/net/ipv4/udp.c#L2997 + // - Linux kernels >= 6.13: function renamed to `udp_connect` + // https://elixir.bootlin.com/linux/v6.13-rc1/source/net/ipv4/udp.c#L3131 + const ( + udpV4ConnectProgramName = "udp_v4_connect" + udpV4ConnectAttachPoint = "udp_connect" + udpV4ConnectAttachPointLegacy = "ip4_datagram_connect" + ) + if err := updateIfNeeded(udpV4ConnectProgramName, udpV4ConnectAttachPoint, udpV4ConnectAttachPointLegacy); err != nil { + return err + } + + // 'udp_v6_connect' program is designed to attach to the `udpv6_connect` function in the kernel. + // If the kernel does not support this function, we fall back to using the `ip6_datagram_connect` function. + // + // Kernel compatibility note: + // - Linux kernels < 6.13: use `ip6_datagram_connect` function + // https://elixir.bootlin.com/linux/v6.12.34/source/net/ipv4/udp.c#L2997 + // - Linux kernels >= 6.13: function renamed to `udpv6_connect` + // https://elixir.bootlin.com/linux/v6.13-rc1/source/net/ipv4/udp.c#L3131 + const ( + udpV6ConnectProgramName = "udp_v6_connect" + udpV6ConnectAttachPoint = "udpv6_connect" + udpV6ConnectAttachPointLegacy = "ip6_datagram_connect" + ) + if err := updateIfNeeded(udpV6ConnectProgramName, udpV6ConnectAttachPoint, udpV6ConnectAttachPointLegacy); err != nil { + return err + } + + return nil +} + // isEventValid checks whether the given bpfEvent is valid or not. // It returns true if the event is valid, otherwise false. func isEventValid(event bpfEvent) bool { diff --git a/service/firewall/interception/ebpf/programs/monitor.c b/service/firewall/interception/ebpf/programs/monitor.c index b436c695..f6540d74 100644 --- a/service/firewall/interception/ebpf/programs/monitor.c +++ b/service/firewall/interception/ebpf/programs/monitor.c @@ -81,9 +81,15 @@ int BPF_PROG(tcp_connect, struct sock *sk) { return 0; }; -// Fexit(function exit) of udp_v4_connect will be executed after the ip4_datagram_connect kernel function is called. -// ip4_datagram_connect -> udp_v4_connect -SEC("fexit/ip4_datagram_connect") +// Fexit(function exit) of `udp_v4_connect` will be executed after the `udp_connect` kernel function is called. +// +// Kernel compatibility note: +// - Linux kernels < 6.13: use `ip4_datagram_connect` function +// https://elixir.bootlin.com/linux/v6.12.34/source/net/ipv4/udp.c#L2997 +// - Linux kernels >= 6.13: function renamed to `udp_connect` +// https://elixir.bootlin.com/linux/v6.13-rc1/source/net/ipv4/udp.c#L3131 +// +SEC("fexit/udp_connect") // Note: This attach point name may be overwritten (see worker.go: modifyProgramsAttachPoints()) int BPF_PROG(udp_v4_connect, struct sock *sk) { // Ignore everything else then IPv4 if (sk->__sk_common.skc_family != AF_INET) { @@ -128,9 +134,15 @@ int BPF_PROG(udp_v4_connect, struct sock *sk) { return 0; } -// Fentry(function enter) of udp_v6_connect will be executed after the ip6_datagram_connect kernel function is called. -// ip6_datagram_connect -> udp_v6_connect -SEC("fexit/ip6_datagram_connect") +// Fentry(function enter) of `udp_v6_connect` will be executed after the `udpv6_connect` kernel function is called. +// +// Kernel compatibility note: +// - Linux kernels < 6.13: use `ip6_datagram_connect` function +// https://elixir.bootlin.com/linux/v6.12.34/source/net/ipv4/udp.c#L2997 +// - Linux kernels >= 6.13: function renamed to `udpv6_connect` +// https://elixir.bootlin.com/linux/v6.13-rc1/source/net/ipv4/udp.c#L3131 +// +SEC("fexit/udpv6_connect") // Note: This attach point name may be overwritten (see worker.go: modifyProgramsAttachPoints()) int BPF_PROG(udp_v6_connect, struct sock *sk) { // Ignore everything else then IPv6 if (sk->__sk_common.skc_family != AF_INET6) { diff --git a/service/firewall/interception/ebpf/programs/update.sh b/service/firewall/interception/ebpf/programs/update.sh index 1bc5b167..9ffda433 100755 --- a/service/firewall/interception/ebpf/programs/update.sh +++ b/service/firewall/interception/ebpf/programs/update.sh @@ -14,4 +14,9 @@ headers=( # Fetch libbpf release and extract the desired headers curl -sL "https://github.com/libbpf/libbpf/archive/refs/tags/v${LIBBPF_VERSION}.tar.gz" | \ - tar -xz --xform='s#.*/#bpf/#' "${headers[@]}" \ No newline at end of file + tar -xz --xform='s#.*/#bpf/#' "${headers[@]}" + +# To generate the vmlinux header file +# See "Export kernel information" at https://docs.ebpf.io/concepts/core/btf +# +# bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux-x86.h