Revision df6609fdc9ae3ced94f49b01734e72fd682a1f56 authored by Ryan Drew on 07 March 2024, 20:26:46 UTC, committed by André Martins on 17 June 2024, 07:39:56 UTC
The function tunnel.TunnelMap calls a sync.Once to set the variable tunnel.tunnelMap before returning it. This conflicts with the behavior of the tunnel.SetTunnelMap function however, for if tunnel.SetTunnelMap is called to manually set the tunnel map in a test, and the test then calls tunnel.TunnelMap to grab a reference to the map, tunnel.TunnelMap will overwrite the map created by tunnel.SetTunnelMap. Signed-off-by: Ryan Drew <ryan.drew@isovalent.com>
1 parent 1395179
verifier_test.go
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of Cilium
package verifier_test
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"math"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"testing"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/rlimit"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
"github.com/cilium/cilium/pkg/bpf"
"github.com/cilium/cilium/pkg/datapath/linux/probes"
"github.com/cilium/cilium/pkg/logging"
)
var (
ciliumBasePath = flag.String("cilium-base-path", "", "Cilium checkout base path")
ciKernelVersion = flag.String("ci-kernel-version", "", "CI kernel version to assume for verifier tests (supported values: 54, 510, 61, netnext)")
)
func getCIKernelVersion(t *testing.T) (string, string) {
t.Helper()
var uts unix.Utsname
if err := unix.Uname(&uts); err != nil {
t.Fatalf("uname: %v", err)
}
release := unix.ByteSliceToString(uts.Release[:])
t.Logf("Running kernel version: %s", release)
if ciKernelVersion != nil && *ciKernelVersion != "" {
return *ciKernelVersion, "cli"
}
var ciKernel string
switch {
case strings.HasPrefix(release, "5.4"):
ciKernel = "54"
case strings.HasPrefix(release, "5.10"):
ciKernel = "510"
case strings.HasPrefix(release, "6.1"):
ciKernel = "61"
case strings.HasPrefix(release, "bpf-next"):
ciKernel = "netnext"
default:
t.Fatalf("detected kernel version %s not supported by verifier complexity tests, specify using -ci-kernel-version", release)
}
return ciKernel, "detected"
}
func getDatapathConfigFiles(t *testing.T, ciKernelVersion, bpfProgram string) []string {
t.Helper()
pattern := filepath.Join("bpf", "complexity-tests", ciKernelVersion, bpfProgram, "*.txt")
files, err := filepath.Glob(filepath.Join(*ciliumBasePath, pattern))
if err != nil {
t.Fatal(err)
}
if len(files) == 0 {
t.Fatal("No files match", pattern)
}
return files
}
// readDatapathConfig turns each line in a reader into a single line with each
// element separated by spaces.
func readDatapathConfig(t *testing.T, r io.Reader) string {
scanner := bufio.NewScanner(r)
var lines []string
for scanner.Scan() {
lines = append(lines, strings.TrimSpace(scanner.Text()))
}
if err := scanner.Err(); err != nil {
t.Fatal(err)
}
return strings.Join(lines, " ")
}
// This test tries to compile BPF programs with a set of options that maximize
// size & complexity (as defined in bpf/complexity-tests). Programs are then
// loaded into the kernel to detect complexity & other verifier-related
// regressions.
func TestVerifier(t *testing.T) {
flag.Parse()
logging.DefaultLogger.SetLevel(logrus.DebugLevel)
if ciliumBasePath == nil || *ciliumBasePath == "" {
t.Skip("Please set -cilium-base-path to run verifier tests")
}
t.Logf("Cilium checkout base path: %s", *ciliumBasePath)
if err := rlimit.RemoveMemlock(); err != nil {
t.Fatal(err)
}
kernelVersion, source := getCIKernelVersion(t)
t.Logf("CI kernel version: %s (%s)", kernelVersion, source)
for _, bpfProgram := range []struct {
name string
macroName string
}{
{
name: "bpf_lxc",
macroName: "MAX_LXC_OPTIONS",
},
{
name: "bpf_host",
macroName: "MAX_HOST_OPTIONS",
},
{
name: "bpf_xdp",
macroName: "MAX_XDP_OPTIONS",
},
{
name: "bpf_overlay",
macroName: "MAX_OVERLAY_OPTIONS",
},
{
name: "bpf_sock",
macroName: "MAX_LB_OPTIONS",
},
{
name: "bpf_network",
macroName: "BPF_SIMPLE_OPTIONS",
},
} {
t.Run(bpfProgram.name, func(t *testing.T) {
initObjFile := path.Join(*ciliumBasePath, "bpf", fmt.Sprintf("%s.o", bpfProgram.name))
fileNames := getDatapathConfigFiles(t, kernelVersion, bpfProgram.name)
for _, fileName := range fileNames {
configName := strings.TrimSuffix(filepath.Base(fileName), filepath.Ext(fileName))
t.Run(configName, func(t *testing.T) {
file, err := os.Open(fileName)
if err != nil {
t.Fatalf("Unable to open configuration: %v", err)
}
defer file.Close()
datapathConfig := readDatapathConfig(t, file)
name := fmt.Sprintf("%s_%s", bpfProgram.name, configName)
cmd := exec.Command("make", "-C", "bpf", "clean", fmt.Sprintf("%s.o", bpfProgram.name))
cmd.Dir = *ciliumBasePath
cmd.Env = append(os.Environ(),
fmt.Sprintf("%s=%s", bpfProgram.macroName, datapathConfig),
fmt.Sprintf("KERNEL=%s", kernelVersion),
)
t.Logf("Compiling with %q", cmd.Args)
t.Logf("Env is %q", cmd.Env)
if out, err := cmd.CombinedOutput(); err != nil {
t.Logf("Command output:\n%s", string(out))
t.Fatalf("Failed to compile bpf objects: %v", err)
}
objFile := filepath.Join("./", name+".o")
// Rename object file to avoid subsequent runs to overwrite it,
// so we can keep it for CI's artifact upload.
if err = os.Rename(initObjFile, objFile); err != nil {
t.Fatalf("Failed to rename %s to %s: %v", initObjFile, objFile, err)
}
// Parse the compiled object into a CollectionSpec.
spec, err := bpf.LoadCollectionSpec(objFile)
if err != nil {
t.Fatal(err)
}
// Delete unsupported programs from the spec.
for n, p := range spec.Programs {
err := probes.HaveAttachType(p.Type, p.AttachType)
if errors.Is(err, ebpf.ErrNotSupported) {
t.Logf("%s: skipped unsupported program/attach type (%s/%s)", n, p.Type, p.AttachType)
delete(spec.Programs, n)
continue
}
if err != nil {
t.Fatal(err)
}
}
// Strip all pinning flags so we don't need to specify a pin path.
// This creates new maps for every Collection.
for _, m := range spec.Maps {
m.Pinning = ebpf.PinNone
}
coll, _, err := bpf.LoadCollection(spec, &bpf.CollectionOptions{
CollectionOptions: ebpf.CollectionOptions{
// Enable verifier logs for successful loads.
// Use log level 1 since it's known by all target kernels.
Programs: ebpf.ProgramOptions{
// Maximum log size for kernels <5.2. Some programs generate a
// verifier log of over 8MiB, so avoid retries due to the initial
// size being too small. This saves a lot of time as retrying means
// reloading all maps and progs in the collection.
LogSize: (math.MaxUint32 >> 8), // 16MiB
LogLevel: ebpf.LogLevelBranch,
},
},
})
var ve *ebpf.VerifierError
if errors.As(err, &ve) {
// Write full verifier log to a path on disk for offline analysis.
var buf bytes.Buffer
fmt.Fprintf(&buf, "%+v", ve)
fullLogFile := name + "_verifier.log"
_ = os.WriteFile(fullLogFile, buf.Bytes(), 0444)
t.Log("Full verifier log at", fullLogFile)
// Print unverified instruction count.
t.Log("BPF unverified instruction count per program:")
for n, p := range spec.Programs {
t.Logf("\t%s: %d insns", n, len(p.Instructions))
}
// Include the original err in the output since it contains the name
// of the program that triggered the verifier error.
// ebpf.VerifierError only contains the return code and verifier log
// buffer.
t.Fatalf("Error: %v\nVerifier error tail: %-10v", err, ve)
}
if err != nil {
t.Fatal(err)
}
defer coll.Close()
// Print verifier stats appearing on the last line of the log, e.g.
// 'processed 12248 insns (limit 1000000) ...'.
// Sort by program names for stable output.
names := make([]string, 0, len(coll.Programs))
for n := range coll.Programs {
names = append(names, n)
}
sort.Strings(names)
for _, n := range names {
p := coll.Programs[n]
p.VerifierLog = strings.TrimRight(p.VerifierLog, "\n")
// Offset points at the last newline, increment by 1 to skip it.
// Turn a -1 into a 0 if there are no newlines in the log.
lastOff := strings.LastIndex(p.VerifierLog, "\n") + 1
t.Logf("%s: %v (%d unverified insns)", n, p.VerifierLog[lastOff:], len(spec.Programs[n].Instructions))
}
})
}
})
}
}
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...