Repository: cilium/ebpf
Pull request #1313 (2024-01-17 16:34:10)

link: add netfilter support
Add netfilter support to attach BPF programs to netfilter hooks.
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=84601d6ee68ae820dec97450934797046d62db4b


Patch:
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index 4b31d30..d2d96ad 100644
--- a/internal/cmd/gentypes/main.go
+++ b/internal/cmd/gentypes/main.go
@@ -430,6 +430,17 @@ import (
 				rename("cnt", "count"),
 			},
 		},
+		{
+			"LinkCreateNetfilter", retFd, "link_create", "BPF_LINK_CREATE",
+			[]patch{
+				chooseNth(4, 5),
+				replace(enumTypes["AttachType"], "attach_type"),
+				modify(func(m *btf.Member) error {
+					return rename("flags", "netfilter_flags")(m.Type.(*btf.Struct))
+				}, "netfilter"),
+				flattenAnon,
+			},
+		},
 		{
 			"LinkCreateTracing", retFd, "link_create", "BPF_LINK_CREATE",
 			[]patch{
@@ -564,6 +575,7 @@ import (
 		{"TcxLinkInfo", "tcx", []patch{
 			replace(enumTypes["AttachType"], "attach_type"),
 		}},
+		{"NetfilterLinkInfo", "netfilter", nil},
 	}
 
 	sort.Slice(linkInfoExtraTypes, func(i, j int) bool {
diff --git a/internal/sys/types.go b/internal/sys/types.go
index d1ba720..db7baa0 100644
--- a/internal/sys/types.go
+++ b/internal/sys/types.go
@@ -720,6 +720,26 @@ func LinkCreateKprobeMulti(attr *LinkCreateKprobeMultiAttr) (*FD, error) {
 	return NewFD(int(fd))
 }
 
+type LinkCreateNetfilterAttr struct {
+	ProgFd         uint32
+	TargetFd       uint32
+	AttachType     AttachType
+	Flags          uint32
+	Pf             uint32
+	Hooknum        uint32
+	Priority       int32
+	NetfilterFlags uint32
+	_              [32]byte
+}
+
+func LinkCreateNetfilter(attr *LinkCreateNetfilterAttr) (*FD, error) {
+	fd, err := BPF(BPF_LINK_CREATE, unsafe.Pointer(attr), unsafe.Sizeof(*attr))
+	if err != nil {
+		return nil, err
+	}
+	return NewFD(int(fd))
+}
+
 type LinkCreatePerfEventAttr struct {
 	ProgFd     uint32
 	TargetFd   uint32
@@ -1201,6 +1221,13 @@ type NetNsLinkInfo struct {
 	AttachType AttachType
 }
 
+type NetfilterLinkInfo struct {
+	Pf       uint32
+	Hooknum  uint32
+	Priority int32
+	Flags    uint32
+}
+
 type RawTracepointLinkInfo struct {
 	TpName    Pointer
 	TpNameLen uint32
diff --git a/link/link.go b/link/link.go
index 16e7e06..03368a2 100644
--- a/link/link.go
+++ b/link/link.go
@@ -102,6 +102,8 @@ func wrapRawLink(raw *RawLink) (_ Link, err error) {
 		return nil, fmt.Errorf("recovering perf event fd: %w", ErrNotSupported)
 	case TCXType:
 		return &tcxLink{*raw}, nil
+	case NetfilterType:
+		return &netfilterLink{*raw}, nil
 	default:
 		return raw, nil
 	}
@@ -137,6 +139,7 @@ type CgroupInfo sys.CgroupLinkInfo
 type NetNsInfo sys.NetNsLinkInfo
 type XDPInfo sys.XDPLinkInfo
 type TCXInfo sys.TcxLinkInfo
+type NetfilterInfo sys.NetfilterLinkInfo
 
 // Tracing returns tracing type-specific link info.
 //
@@ -178,6 +181,14 @@ func (r Info) TCX() *TCXInfo {
 	return e
 }
 
+// Netfilter returns netfilter type-specific link info.
+//
+// Returns nil if the type-specific link info isn't available.
+func (r Info) Netfilter() *NetfilterInfo {
+	e, _ := r.extra.(*NetfilterInfo)
+	return e
+}
+
 // RawLink is the low-level API to bpf_link.
 //
 // You should consider using the higher level interfaces in this
@@ -330,6 +341,8 @@ func (l *RawLink) Info() (*Info, error) {
 		// Extra metadata not supported.
 	case TCXType:
 		extra = &TCXInfo{}
+	case NetfilterType:
+		extra = &NetfilterInfo{}
 	default:
 		return nil, fmt.Errorf("unknown link info type: %d", info.Type)
 	}
diff --git a/link/netfilter.go b/link/netfilter.go
new file mode 100644
index 0000000..250c876
--- /dev/null
+++ b/link/netfilter.go
@@ -0,0 +1,70 @@
+package link
+
+import (
+	"fmt"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/internal/sys"
+)
+
+const NetfilterIPDefrag NetfilterAttachFlags = 0 // Enable IP packet defragmentation
+
+type NetfilterAttachFlags uint32
+
+type NetfilterOptions struct {
+	// Program must be a netfilter BPF program.
+	Program *ebpf.Program
+	// The protocol family.
+	ProtocolFamily uint32
+	// The number of the hook you are interested in.
+	HookNumber uint32
+	// Priority within hook
+	Priority int32
+	// Extra link flags
+	Flags uint32
+	// Netfilter flags
+	NetfilterFlags NetfilterAttachFlags
+}
+
+type netfilterLink struct {
+	RawLink
+}
+
+// AttachNetfilter links a netfilter BPF program to a netfilter hook.
+func AttachNetfilter(opts NetfilterOptions) (Link, error) {
+	if opts.Program == nil {
+		return nil, fmt.Errorf("netfilter program is nil")
+	}
+
+	if t := opts.Program.Type(); t != ebpf.Netfilter {
+		return nil, fmt.Errorf("invalid program type %s, expected netfilter", t)
+	}
+
+	progFd := opts.Program.FD()
+	if progFd < 0 {
+		return nil, fmt.Errorf("invalid program: %s", sys.ErrClosedFd)
+	}
+
+	attr := sys.LinkCreateNetfilterAttr{
+		ProgFd:         uint32(opts.Program.FD()),
+		AttachType:     sys.BPF_NETFILTER,
+		Flags:          opts.Flags,
+		Pf:             uint32(opts.ProtocolFamily),
+		Hooknum:        uint32(opts.HookNumber),
+		Priority:       opts.Priority,
+		NetfilterFlags: uint32(opts.NetfilterFlags),
+	}
+
+	fd, err := sys.LinkCreateNetfilter(&attr)
+	if err != nil {
+		return nil, fmt.Errorf("attach netfilter link: %w", err)
+	}
+
+	return &netfilterLink{RawLink{fd, ""}}, nil
+}
+
+func (*netfilterLink) Update(new *ebpf.Program) error {
+	return fmt.Errorf("netfilter update: %w", ErrNotSupported)
+}
+
+var _ Link = (*netfilterLink)(nil)
diff --git a/link/syscalls.go b/link/syscalls.go
index 8c1aec5..53476c9 100644
--- a/link/syscalls.go
+++ b/link/syscalls.go
@@ -26,6 +26,7 @@ const (
 	KprobeMultiType   = sys.BPF_LINK_TYPE_KPROBE_MULTI
 	TCXType           = sys.BPF_LINK_TYPE_TCX
 	UprobeMultiType   = sys.BPF_LINK_TYPE_UPROBE_MULTI
+	NetfilterType     = sys.BPF_LINK_TYPE_NETFILTER
 )
 
 var haveProgAttach = internal.NewFeatureTest("BPF_PROG_ATTACH", "4.10", func() error {


Test patch:
diff --git a/link/link_test.go b/link/link_test.go
index 785c639..baf65c2 100644
--- a/link/link_test.go
+++ b/link/link_test.go
@@ -209,6 +209,11 @@ func testLink(t *testing.T, link Link, prog *ebpf.Program) {
 			if tcx.Ifindex == 0 {
 				t.Fatalf("Failed to get link TCX extra info")
 			}
+		case sys.BPF_LINK_TYPE_NETFILTER:
+			nf := info.Netfilter()
+			if nf.Priority == 0 {
+				t.Fatalf("Failed to get link Netfilter extra info")
+			}
 		}
 	})
 
diff --git a/link/netfilter_test.go b/link/netfilter_test.go
new file mode 100644
index 0000000..1674ce6
--- /dev/null
+++ b/link/netfilter_test.go
@@ -0,0 +1,31 @@
+package link
+
+import (
+	"testing"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/internal/testutils"
+)
+
+const (
+	NFPROTO_IPV4      = 0x2
+	NF_INET_LOCAL_OUT = 0x3
+)
+
+func TestAttachNetfilter(t *testing.T) {
+	testutils.SkipOnOldKernel(t, "6.4", "BPF_LINK_TYPE_NETFILTER")
+
+	prog := mustLoadProgram(t, ebpf.Netfilter, ebpf.AttachNetfilter, "")
+
+	l, err := AttachNetfilter(NetfilterOptions{
+		Program:        prog,
+		ProtocolFamily: NFPROTO_IPV4,
+		HookNumber:     NF_INET_LOCAL_OUT,
+		Priority:       -128,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	testLink(t, l, prog)
+}