Repository: cilium/ebpf
Pull request #1420 (2024-04-08 06:32:46)

when ringbuffer map size is not a power-of-2 multiple of page size, prompt users in a user-friendly way
example:
```c
struct {
	__uint(type, BPF_MAP_TYPE_RINGBUF);
	__uint(max_entries, 4097);
} rb SEC(".maps");
```
then output: 
```
program dev_hard_start_xmit: map rb: EINVAL (ring map size 4097 not a power of two page size(pageSize 4096))
```

lmb: I've pushed a small refactoring to reuse an existing implementation of `pow` we already had in the code base.

Patch:
diff --git a/btf/types.go b/btf/types.go
index 31acbf9..48dc388 100644
--- a/btf/types.go
+++ b/btf/types.go
@@ -655,18 +655,13 @@ func alignof(typ Type) (int, error) {
 		return 0, fmt.Errorf("can't calculate alignment of %T", t)
 	}
 
-	if !pow(n) {
+	if !internal.IsPow(n) {
 		return 0, fmt.Errorf("alignment value %d is not a power of two", n)
 	}
 
 	return n, nil
 }
 
-// pow returns true if n is a power of two.
-func pow(n int) bool {
-	return n != 0 && (n&(n-1)) == 0
-}
-
 // Copy a Type recursively.
 //
 // typ may form a cycle.
diff --git a/internal/align.go b/internal/math.go
similarity index 63%
rename from internal/align.go
rename to internal/math.go
index edc898f..e95c8ef 100644
--- a/internal/align.go
+++ b/internal/math.go
@@ -6,3 +6,8 @@ import "golang.org/x/exp/constraints"
 func Align[I constraints.Integer](n, alignment I) I {
 	return (n + alignment - 1) / alignment * alignment
 }
+
+// IsPow returns true if n is a power of two.
+func IsPow[I constraints.Integer](n I) bool {
+	return n != 0 && (n&(n-1)) == 0
+}
diff --git a/map.go b/map.go
index 1eeee17..b2744db 100644
--- a/map.go
+++ b/map.go
@@ -490,6 +490,15 @@ func handleMapCreateError(attr sys.MapCreateAttr, spec *MapSpec, err error) erro
 			return fmt.Errorf("map create: %w", haveFeatErr)
 		}
 	}
+	// BPF_MAP_TYPE_RINGBUF's max_entries must be a power-of-2 multiple of kernel's page size.
+	if errors.Is(err, unix.EINVAL) &&
+		(attr.MapType == sys.BPF_MAP_TYPE_RINGBUF || attr.MapType == sys.BPF_MAP_TYPE_USER_RINGBUF) {
+		pageSize := uint32(os.Getpagesize())
+		maxEntries := attr.MaxEntries
+		if maxEntries%pageSize != 0 || !internal.IsPow(maxEntries) {
+			return fmt.Errorf("map create: %w (ring map size %d not a multiple of page size %d)", err, maxEntries, pageSize)
+		}
+	}
 	if attr.BtfFd == 0 {
 		return fmt.Errorf("map create: %w (without BTF k/v)", err)
 	}


Test patch:
diff --git a/btf/types_test.go b/btf/types_test.go
index 69063d9..e1a7b75 100644
--- a/btf/types_test.go
+++ b/btf/types_test.go
@@ -38,29 +38,6 @@ func TestSizeof(t *testing.T) {
 	}
 }
 
-func TestPow(t *testing.T) {
-	tests := []struct {
-		n int
-		r bool
-	}{
-		{0, false},
-		{1, true},
-		{2, true},
-		{3, false},
-		{4, true},
-		{5, false},
-		{8, true},
-	}
-
-	for _, tt := range tests {
-		t.Run(fmt.Sprintf("%d", tt.n), func(t *testing.T) {
-			if want, got := tt.r, pow(tt.n); want != got {
-				t.Errorf("unexpected result for n %d; want: %v, got: %v", tt.n, want, got)
-			}
-		})
-	}
-}
-
 func TestCopy(t *testing.T) {
 	_ = Copy((*Void)(nil))
 
diff --git a/internal/math_test.go b/internal/math_test.go
new file mode 100644
index 0000000..4a16d11
--- /dev/null
+++ b/internal/math_test.go
@@ -0,0 +1,29 @@
+package internal
+
+import (
+	"fmt"
+	"testing"
+)
+
+func TestPow(t *testing.T) {
+	tests := []struct {
+		n int
+		r bool
+	}{
+		{0, false},
+		{1, true},
+		{2, true},
+		{3, false},
+		{4, true},
+		{5, false},
+		{8, true},
+	}
+
+	for _, tt := range tests {
+		t.Run(fmt.Sprintf("%d", tt.n), func(t *testing.T) {
+			if want, got := tt.r, IsPow(tt.n); want != got {
+				t.Errorf("unexpected result for n %d; want: %v, got: %v", tt.n, want, got)
+			}
+		})
+	}
+}