Repository: cilium/ebpf
Pull request #685 (2022-05-26 19:55:55)

prog: Allow empty data for prog.Test and prog.Run
Certain prog types (SK_LOOKUP, RAW_TRACEPOINT and
RAW_TRACEPOINT_WRITABLE) require that data_in is NULL. The previous
check prevented 0 length data_in.

Fix by removing check.

Followup from #647.

dxuuu: Ping
lmb: Sorry for the long delay, I wasn't aware that you had made changes and then I was busy with life when you pinged me. Thanks for following up :)

Patch:
diff --git a/internal/cmd/gentypes/main.go b/internal/cmd/gentypes/main.go
index fda25c1..d1b0a8c 100644
--- a/internal/cmd/gentypes/main.go
+++ b/internal/cmd/gentypes/main.go
@@ -6,6 +6,7 @@ package main
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"os"
 	"sort"
@@ -173,12 +174,19 @@ import (
 				replace(enumTypes["LinkType"], "type"),
 				replace(linkID, "id"),
 				name(3, "extra"),
-				replaceUnionWithBytes("extra"),
+				replaceWithBytes("extra"),
 			},
 		},
 		{"FuncInfo", "bpf_func_info", nil},
 		{"LineInfo", "bpf_line_info", nil},
 		{"XdpMd", "xdp_md", nil},
+		{
+			"SkLookup", "bpf_sk_lookup",
+			[]patch{
+				choose(0, "cookie"),
+				replaceWithBytes("remote_ip4", "remote_ip6", "local_ip4", "local_ip6"),
+			},
+		},
 	}
 
 	sort.Slice(structs, func(i, j int) bool {
@@ -687,25 +695,22 @@ func name(member int, name string) patch {
 	}, member)
 }
 
-func replaceUnionWithBytes(name string) patch {
-	return func(s *btf.Struct) error {
-		for i, m := range s.Members {
-			if m.Name != name {
-				continue
-			}
-			if u, ok := m.Type.(*btf.Union); ok {
-				s.Members[i] = btf.Member{
-					Name: name,
-					Type: &btf.Array{
-						Type:   &btf.Int{Size: 1},
-						Nelems: u.Size,
-					},
-					Offset:       s.Members[i].Offset,
-					BitfieldSize: s.Members[i].BitfieldSize,
-				}
-				return nil
-			}
+func replaceWithBytes(members ...string) patch {
+	return modify(func(m *btf.Member) error {
+		if m.BitfieldSize != 0 {
+			return errors.New("replaceWithBytes: member is a bitfield")
 		}
-		return fmt.Errorf("the union name: %s not found", name)
-	}
+
+		size, err := btf.Sizeof(m.Type)
+		if err != nil {
+			return fmt.Errorf("replaceWithBytes: size of %s: %w", m.Type, err)
+		}
+
+		m.Type = &btf.Array{
+			Type:   &btf.Int{Size: 1},
+			Nelems: uint32(size),
+		}
+
+		return nil
+	}, members...)
 }
diff --git a/internal/sys/types.go b/internal/sys/types.go
index b239a05..a27e4f4 100644
--- a/internal/sys/types.go
+++ b/internal/sys/types.go
@@ -538,6 +538,21 @@ type ProgInfo struct {
 	_                    [4]byte
 }
 
+type SkLookup struct {
+	Cookie         uint64
+	Family         uint32
+	Protocol       uint32
+	RemoteIp4      [4]uint8
+	RemoteIp6      [16]uint8
+	RemotePort     uint16
+	_              [2]byte
+	LocalIp4       [4]uint8
+	LocalIp6       [16]uint8
+	LocalPort      uint32
+	IngressIfindex uint32
+	_              [4]byte
+}
+
 type XdpMd struct {
 	Data           uint32
 	DataEnd        uint32
diff --git a/prog.go b/prog.go
index 7730ffb..263107c 100644
--- a/prog.go
+++ b/prog.go
@@ -488,7 +488,10 @@ func (p *Program) Test(in []byte) (uint32, []byte, error) {
 	// size will be. Hence we allocate an output buffer which we hope will always be large
 	// enough, and panic if the kernel wrote past the end of the allocation.
 	// See https://patchwork.ozlabs.org/cover/1006822/
-	out := make([]byte, len(in)+outputPad)
+	var out []byte
+	if len(in) > 0 {
+		out = make([]byte, len(in)+outputPad)
+	}
 
 	opts := RunOptions{
 		Data:    in,
@@ -589,10 +592,6 @@ var haveProgTestRun = internal.FeatureTest("BPF_PROG_TEST_RUN", "4.12", func() e
 })
 
 func (p *Program) testRun(opts *RunOptions) (uint32, time.Duration, error) {
-	if len(opts.Data) == 0 {
-		return 0, 0, fmt.Errorf("missing input")
-	}
-
 	if uint(len(opts.Data)) > math.MaxUint32 {
 		return 0, 0, fmt.Errorf("input is too long")
 	}


Test patch:
diff --git a/prog_test.go b/prog_test.go
index 2350488..fff5338 100644
--- a/prog_test.go
+++ b/prog_test.go
@@ -136,6 +136,43 @@ func TestProgramRunWithOptions(t *testing.T) {
 	}
 }
 
+func TestProgramRunEmptyData(t *testing.T) {
+	testutils.SkipOnOldKernel(t, "5.13", "sk_lookup BPF_PROG_RUN")
+
+	ins := asm.Instructions{
+		// Return SK_DROP
+		asm.LoadImm(asm.R0, 0, asm.DWord),
+		asm.Return(),
+	}
+
+	prog, err := NewProgram(&ProgramSpec{
+		Name:         "test",
+		Type:         SkLookup,
+		AttachType:   AttachSkLookup,
+		Instructions: ins,
+		License:      "MIT",
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer prog.Close()
+
+	opts := RunOptions{
+		Context: sys.SkLookup{
+			Family: syscall.AF_INET,
+		},
+	}
+	ret, err := prog.Run(&opts)
+	testutils.SkipIfNotSupported(t, err)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if ret != 0 {
+		t.Error("Expected return value to be 0, got", ret)
+	}
+}
+
 func TestProgramBenchmark(t *testing.T) {
 	prog := mustSocketFilter(t)