github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/probes/probes.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package probes
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  	"sync"
    17  	"text/template"
    18  
    19  	"github.com/cilium/ebpf"
    20  	"github.com/cilium/ebpf/asm"
    21  	"github.com/cilium/ebpf/features"
    22  	"github.com/cilium/ebpf/link"
    23  	"github.com/google/gopacket"
    24  	"github.com/google/gopacket/layers"
    25  	"golang.org/x/sys/unix"
    26  
    27  	"github.com/cilium/cilium/pkg/command/exec"
    28  	"github.com/cilium/cilium/pkg/defaults"
    29  	"github.com/cilium/cilium/pkg/logging"
    30  	"github.com/cilium/cilium/pkg/logging/logfields"
    31  	"github.com/cilium/cilium/pkg/netns"
    32  )
    33  
    34  var (
    35  	log          = logging.DefaultLogger.WithField(logfields.LogSubsys, "probes")
    36  	once         sync.Once
    37  	probeManager *ProbeManager
    38  	tpl          = template.New("headerfile")
    39  )
    40  
    41  func init() {
    42  	const content = `
    43  /* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
    44  /* Copyright Authors of Cilium */
    45  
    46  /* THIS FILE WAS GENERATED DURING AGENT STARTUP. */
    47  
    48  #pragma once
    49  
    50  {{- if not .Common}}
    51  #include "features.h"
    52  {{- end}}
    53  
    54  {{- range $key, $value := .Features}}
    55  {{- if $value}}
    56  #define {{$key}} 1
    57  {{end}}
    58  {{- end}}
    59  `
    60  	var err error
    61  	tpl, err = tpl.Parse(content)
    62  	if err != nil {
    63  		log.WithError(err).Fatal("could not parse headerfile template")
    64  	}
    65  }
    66  
    67  // ErrNotSupported indicates that a feature is not supported by the current kernel.
    68  var ErrNotSupported = errors.New("not supported")
    69  
    70  // KernelParam is a type based on string which represents CONFIG_* kernel
    71  // parameters which usually have values "y", "n" or "m".
    72  type KernelParam string
    73  
    74  // Enabled checks whether the kernel parameter is enabled.
    75  func (kp KernelParam) Enabled() bool {
    76  	return kp == "y"
    77  }
    78  
    79  // Module checks whether the kernel parameter is enabled as a module.
    80  func (kp KernelParam) Module() bool {
    81  	return kp == "m"
    82  }
    83  
    84  // kernelOption holds information about kernel parameters to probe.
    85  type kernelOption struct {
    86  	Description string
    87  	Enabled     bool
    88  	CanBeModule bool
    89  }
    90  
    91  type ProgramHelper struct {
    92  	Program ebpf.ProgramType
    93  	Helper  asm.BuiltinFunc
    94  }
    95  
    96  type miscFeatures struct {
    97  	HaveFibIfindex bool
    98  }
    99  
   100  type FeatureProbes struct {
   101  	ProgramHelpers map[ProgramHelper]bool
   102  	Misc           miscFeatures
   103  }
   104  
   105  // SystemConfig contains kernel configuration and sysctl parameters related to
   106  // BPF functionality.
   107  type SystemConfig struct {
   108  	UnprivilegedBpfDisabled      int         `json:"unprivileged_bpf_disabled"`
   109  	BpfJitEnable                 int         `json:"bpf_jit_enable"`
   110  	BpfJitHarden                 int         `json:"bpf_jit_harden"`
   111  	BpfJitKallsyms               int         `json:"bpf_jit_kallsyms"`
   112  	BpfJitLimit                  int         `json:"bpf_jit_limit"`
   113  	ConfigBpf                    KernelParam `json:"CONFIG_BPF"`
   114  	ConfigBpfSyscall             KernelParam `json:"CONFIG_BPF_SYSCALL"`
   115  	ConfigHaveEbpfJit            KernelParam `json:"CONFIG_HAVE_EBPF_JIT"`
   116  	ConfigBpfJit                 KernelParam `json:"CONFIG_BPF_JIT"`
   117  	ConfigBpfJitAlwaysOn         KernelParam `json:"CONFIG_BPF_JIT_ALWAYS_ON"`
   118  	ConfigCgroups                KernelParam `json:"CONFIG_CGROUPS"`
   119  	ConfigCgroupBpf              KernelParam `json:"CONFIG_CGROUP_BPF"`
   120  	ConfigCgroupNetClassID       KernelParam `json:"CONFIG_CGROUP_NET_CLASSID"`
   121  	ConfigSockCgroupData         KernelParam `json:"CONFIG_SOCK_CGROUP_DATA"`
   122  	ConfigBpfEvents              KernelParam `json:"CONFIG_BPF_EVENTS"`
   123  	ConfigKprobeEvents           KernelParam `json:"CONFIG_KPROBE_EVENTS"`
   124  	ConfigUprobeEvents           KernelParam `json:"CONFIG_UPROBE_EVENTS"`
   125  	ConfigTracing                KernelParam `json:"CONFIG_TRACING"`
   126  	ConfigFtraceSyscalls         KernelParam `json:"CONFIG_FTRACE_SYSCALLS"`
   127  	ConfigFunctionErrorInjection KernelParam `json:"CONFIG_FUNCTION_ERROR_INJECTION"`
   128  	ConfigBpfKprobeOverride      KernelParam `json:"CONFIG_BPF_KPROBE_OVERRIDE"`
   129  	ConfigNet                    KernelParam `json:"CONFIG_NET"`
   130  	ConfigXdpSockets             KernelParam `json:"CONFIG_XDP_SOCKETS"`
   131  	ConfigLwtunnelBpf            KernelParam `json:"CONFIG_LWTUNNEL_BPF"`
   132  	ConfigNetActBpf              KernelParam `json:"CONFIG_NET_ACT_BPF"`
   133  	ConfigNetClsBpf              KernelParam `json:"CONFIG_NET_CLS_BPF"`
   134  	ConfigNetClsAct              KernelParam `json:"CONFIG_NET_CLS_ACT"`
   135  	ConfigNetSchIngress          KernelParam `json:"CONFIG_NET_SCH_INGRESS"`
   136  	ConfigXfrm                   KernelParam `json:"CONFIG_XFRM"`
   137  	ConfigIPRouteClassID         KernelParam `json:"CONFIG_IP_ROUTE_CLASSID"`
   138  	ConfigIPv6Seg6Bpf            KernelParam `json:"CONFIG_IPV6_SEG6_BPF"`
   139  	ConfigBpfLircMode2           KernelParam `json:"CONFIG_BPF_LIRC_MODE2"`
   140  	ConfigBpfStreamParser        KernelParam `json:"CONFIG_BPF_STREAM_PARSER"`
   141  	ConfigNetfilterXtMatchBpf    KernelParam `json:"CONFIG_NETFILTER_XT_MATCH_BPF"`
   142  	ConfigBpfilter               KernelParam `json:"CONFIG_BPFILTER"`
   143  	ConfigBpfilterUmh            KernelParam `json:"CONFIG_BPFILTER_UMH"`
   144  	ConfigTestBpf                KernelParam `json:"CONFIG_TEST_BPF"`
   145  	ConfigKernelHz               KernelParam `json:"CONFIG_HZ"`
   146  }
   147  
   148  // MapTypes contains bools indicating which types of BPF maps the currently
   149  // running kernel supports.
   150  type MapTypes struct {
   151  	HaveHashMapType                bool `json:"have_hash_map_type"`
   152  	HaveArrayMapType               bool `json:"have_array_map_type"`
   153  	HaveProgArrayMapType           bool `json:"have_prog_array_map_type"`
   154  	HavePerfEventArrayMapType      bool `json:"have_perf_event_array_map_type"`
   155  	HavePercpuHashMapType          bool `json:"have_percpu_hash_map_type"`
   156  	HavePercpuArrayMapType         bool `json:"have_percpu_array_map_type"`
   157  	HaveStackTraceMapType          bool `json:"have_stack_trace_map_type"`
   158  	HaveCgroupArrayMapType         bool `json:"have_cgroup_array_map_type"`
   159  	HaveLruHashMapType             bool `json:"have_lru_hash_map_type"`
   160  	HaveLruPercpuHashMapType       bool `json:"have_lru_percpu_hash_map_type"`
   161  	HaveLpmTrieMapType             bool `json:"have_lpm_trie_map_type"`
   162  	HaveArrayOfMapsMapType         bool `json:"have_array_of_maps_map_type"`
   163  	HaveHashOfMapsMapType          bool `json:"have_hash_of_maps_map_type"`
   164  	HaveDevmapMapType              bool `json:"have_devmap_map_type"`
   165  	HaveSockmapMapType             bool `json:"have_sockmap_map_type"`
   166  	HaveCpumapMapType              bool `json:"have_cpumap_map_type"`
   167  	HaveXskmapMapType              bool `json:"have_xskmap_map_type"`
   168  	HaveSockhashMapType            bool `json:"have_sockhash_map_type"`
   169  	HaveCgroupStorageMapType       bool `json:"have_cgroup_storage_map_type"`
   170  	HaveReuseportSockarrayMapType  bool `json:"have_reuseport_sockarray_map_type"`
   171  	HavePercpuCgroupStorageMapType bool `json:"have_percpu_cgroup_storage_map_type"`
   172  	HaveQueueMapType               bool `json:"have_queue_map_type"`
   173  	HaveStackMapType               bool `json:"have_stack_map_type"`
   174  }
   175  
   176  // Features contains BPF feature checks returned by bpftool.
   177  type Features struct {
   178  	SystemConfig `json:"system_config"`
   179  	MapTypes     `json:"map_types"`
   180  }
   181  
   182  // ProbeManager is a manager of BPF feature checks.
   183  type ProbeManager struct {
   184  	features Features
   185  }
   186  
   187  // NewProbeManager returns a new instance of ProbeManager - a manager of BPF
   188  // feature checks.
   189  func NewProbeManager() *ProbeManager {
   190  	newProbeManager := func() {
   191  		probeManager = &ProbeManager{}
   192  		probeManager.features = probeManager.Probe()
   193  	}
   194  	once.Do(newProbeManager)
   195  	return probeManager
   196  }
   197  
   198  // Probe probes the underlying kernel for features.
   199  func (*ProbeManager) Probe() Features {
   200  	var features Features
   201  	out, err := exec.WithTimeout(
   202  		defaults.ExecTimeout,
   203  		"bpftool", "-j", "feature", "probe",
   204  	).CombinedOutput(log, true)
   205  	if err != nil {
   206  		log.WithError(err).Fatal("could not run bpftool")
   207  	}
   208  	if err := json.Unmarshal(out, &features); err != nil {
   209  		log.WithError(err).Fatal("could not parse bpftool output")
   210  	}
   211  	return features
   212  }
   213  
   214  // SystemConfigProbes performs a check of kernel configuration parameters. It
   215  // returns an error when parameters required by Cilium are not enabled. It logs
   216  // warnings when optional parameters are not enabled.
   217  //
   218  // When kernel config file is not found, bpftool can't probe kernel configuration
   219  // parameter real setting, so only return error log when kernel config file exists
   220  // and kernel configuration parameter setting is disabled
   221  func (p *ProbeManager) SystemConfigProbes() error {
   222  	var notFound bool
   223  	if !p.KernelConfigAvailable() {
   224  		notFound = true
   225  		log.Info("Kernel config file not found: if the agent fails to start, check the system requirements at https://docs.cilium.io/en/stable/operations/system_requirements")
   226  	}
   227  	requiredParams := p.GetRequiredConfig()
   228  	for param, kernelOption := range requiredParams {
   229  		if !kernelOption.Enabled && !notFound {
   230  			module := ""
   231  			if kernelOption.CanBeModule {
   232  				module = " or module"
   233  			}
   234  			return fmt.Errorf("%s kernel parameter%s is required (needed for: %s)", param, module, kernelOption.Description)
   235  		}
   236  	}
   237  	optionalParams := p.GetOptionalConfig()
   238  	for param, kernelOption := range optionalParams {
   239  		if !kernelOption.Enabled && !notFound {
   240  			module := ""
   241  			if kernelOption.CanBeModule {
   242  				module = " or module"
   243  			}
   244  			log.Warningf("%s optional kernel parameter%s is not in kernel (needed for: %s)", param, module, kernelOption.Description)
   245  		}
   246  	}
   247  	return nil
   248  }
   249  
   250  // GetRequiredConfig performs a check of mandatory kernel configuration options. It
   251  // returns a map indicating which required kernel parameters are enabled - and which are not.
   252  // GetRequiredConfig is being used by CLI "cilium kernel-check".
   253  func (p *ProbeManager) GetRequiredConfig() map[KernelParam]kernelOption {
   254  	config := p.features.SystemConfig
   255  	coreInfraDescription := "Essential eBPF infrastructure"
   256  	kernelParams := make(map[KernelParam]kernelOption)
   257  
   258  	kernelParams["CONFIG_BPF"] = kernelOption{
   259  		Enabled:     config.ConfigBpf.Enabled(),
   260  		Description: coreInfraDescription,
   261  		CanBeModule: false,
   262  	}
   263  	kernelParams["CONFIG_BPF_SYSCALL"] = kernelOption{
   264  		Enabled:     config.ConfigBpfSyscall.Enabled(),
   265  		Description: coreInfraDescription,
   266  		CanBeModule: false,
   267  	}
   268  	kernelParams["CONFIG_NET_SCH_INGRESS"] = kernelOption{
   269  		Enabled:     config.ConfigNetSchIngress.Enabled() || config.ConfigNetSchIngress.Module(),
   270  		Description: coreInfraDescription,
   271  		CanBeModule: true,
   272  	}
   273  	kernelParams["CONFIG_NET_CLS_BPF"] = kernelOption{
   274  		Enabled:     config.ConfigNetClsBpf.Enabled() || config.ConfigNetClsBpf.Module(),
   275  		Description: coreInfraDescription,
   276  		CanBeModule: true,
   277  	}
   278  	kernelParams["CONFIG_NET_CLS_ACT"] = kernelOption{
   279  		Enabled:     config.ConfigNetClsAct.Enabled(),
   280  		Description: coreInfraDescription,
   281  		CanBeModule: false,
   282  	}
   283  	kernelParams["CONFIG_BPF_JIT"] = kernelOption{
   284  		Enabled:     config.ConfigBpfJit.Enabled(),
   285  		Description: coreInfraDescription,
   286  		CanBeModule: false,
   287  	}
   288  	kernelParams["CONFIG_HAVE_EBPF_JIT"] = kernelOption{
   289  		Enabled:     config.ConfigHaveEbpfJit.Enabled(),
   290  		Description: coreInfraDescription,
   291  		CanBeModule: false,
   292  	}
   293  
   294  	return kernelParams
   295  }
   296  
   297  // GetOptionalConfig performs a check of *optional* kernel configuration options. It
   298  // returns a map indicating which optional/non-mandatory kernel parameters are enabled.
   299  // GetOptionalConfig is being used by CLI "cilium kernel-check".
   300  func (p *ProbeManager) GetOptionalConfig() map[KernelParam]kernelOption {
   301  	config := p.features.SystemConfig
   302  	kernelParams := make(map[KernelParam]kernelOption)
   303  
   304  	kernelParams["CONFIG_CGROUP_BPF"] = kernelOption{
   305  		Enabled:     config.ConfigCgroupBpf.Enabled(),
   306  		Description: "Host Reachable Services and Sockmap optimization",
   307  		CanBeModule: false,
   308  	}
   309  	kernelParams["CONFIG_LWTUNNEL_BPF"] = kernelOption{
   310  		Enabled:     config.ConfigLwtunnelBpf.Enabled(),
   311  		Description: "Lightweight Tunnel hook for IP-in-IP encapsulation",
   312  		CanBeModule: false,
   313  	}
   314  	kernelParams["CONFIG_BPF_EVENTS"] = kernelOption{
   315  		Enabled:     config.ConfigBpfEvents.Enabled(),
   316  		Description: "Visibility and congestion management with datapath",
   317  		CanBeModule: false,
   318  	}
   319  
   320  	return kernelParams
   321  }
   322  
   323  // KernelConfigAvailable checks if the Kernel Config is available on the
   324  // system or not.
   325  func (p *ProbeManager) KernelConfigAvailable() bool {
   326  	// Check Kernel Config is available or not.
   327  	// We are replicating BPFTools logic here to check if kernel config is available
   328  	// https://elixir.bootlin.com/linux/v5.7/source/tools/bpf/bpftool/feature.c#L390
   329  	info := unix.Utsname{}
   330  	err := unix.Uname(&info)
   331  	if err != nil {
   332  		return false
   333  	}
   334  	release := strings.TrimSpace(string(bytes.Trim(info.Release[:], "\x00")))
   335  
   336  	// Any error checking these files will return Kernel config not found error
   337  	if _, err := os.Stat(fmt.Sprintf("/boot/config-%s", release)); err != nil {
   338  		if _, err = os.Stat("/proc/config.gz"); err != nil {
   339  			return false
   340  		}
   341  	}
   342  
   343  	return true
   344  }
   345  
   346  // HaveProgramHelper is a wrapper around features.HaveProgramHelper() to
   347  // check if a certain BPF program/helper copmbination is supported by the kernel.
   348  // On unexpected probe results this function will terminate with log.Fatal().
   349  func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
   350  	err := features.HaveProgramHelper(pt, helper)
   351  	if errors.Is(err, ebpf.ErrNotSupported) {
   352  		return err
   353  	}
   354  	if err != nil {
   355  		log.WithError(err).WithField("programtype", pt).WithField("helper", helper).Fatal("failed to probe helper")
   356  	}
   357  	return nil
   358  }
   359  
   360  // HaveLargeInstructionLimit is a wrapper around features.HaveLargeInstructions()
   361  // to check if the kernel supports the 1 Million instruction limit.
   362  // On unexpected probe results this function will terminate with log.Fatal().
   363  func HaveLargeInstructionLimit() error {
   364  	err := features.HaveLargeInstructions()
   365  	if errors.Is(err, ebpf.ErrNotSupported) {
   366  		return err
   367  	}
   368  	if err != nil {
   369  		log.WithError(err).Fatal("failed to probe large instruction limit")
   370  	}
   371  	return nil
   372  }
   373  
   374  // HaveBoundedLoops is a wrapper around features.HaveBoundedLoops()
   375  // to check if the kernel supports bounded loops in BPF programs.
   376  // On unexpected probe results this function will terminate with log.Fatal().
   377  func HaveBoundedLoops() error {
   378  	err := features.HaveBoundedLoops()
   379  	if errors.Is(err, ebpf.ErrNotSupported) {
   380  		return err
   381  	}
   382  	if err != nil {
   383  		log.WithError(err).Fatal("failed to probe bounded loops")
   384  	}
   385  	return nil
   386  }
   387  
   388  // HaveFibIfindex checks if kernel has d1c362e1dd68 ("bpf: Always return target
   389  // ifindex in bpf_fib_lookup") which is 5.10+. This got merged in the same kernel
   390  // as the new redirect helpers.
   391  func HaveFibIfindex() error {
   392  	return features.HaveProgramHelper(ebpf.SchedCLS, asm.FnRedirectPeer)
   393  }
   394  
   395  // HaveV2ISA is a wrapper around features.HaveV2ISA() to check if the kernel
   396  // supports the V2 ISA.
   397  // On unexpected probe results this function will terminate with log.Fatal().
   398  func HaveV2ISA() error {
   399  	err := features.HaveV2ISA()
   400  	if errors.Is(err, ebpf.ErrNotSupported) {
   401  		return err
   402  	}
   403  	if err != nil {
   404  		log.WithError(err).Fatal("failed to probe V2 ISA")
   405  	}
   406  	return nil
   407  }
   408  
   409  // HaveV3ISA is a wrapper around features.HaveV3ISA() to check if the kernel
   410  // supports the V3 ISA.
   411  // On unexpected probe results this function will terminate with log.Fatal().
   412  func HaveV3ISA() error {
   413  	err := features.HaveV3ISA()
   414  	if errors.Is(err, ebpf.ErrNotSupported) {
   415  		return err
   416  	}
   417  	if err != nil {
   418  		log.WithError(err).Fatal("failed to probe V3 ISA")
   419  	}
   420  	return nil
   421  }
   422  
   423  // HaveTCX returns nil if the running kernel supports attaching bpf programs to
   424  // tcx hooks.
   425  var HaveTCX = sync.OnceValue(func() error {
   426  	prog, err := ebpf.NewProgram(&ebpf.ProgramSpec{
   427  		Type: ebpf.SchedCLS,
   428  		Instructions: asm.Instructions{
   429  			asm.Mov.Imm(asm.R0, 0),
   430  			asm.Return(),
   431  		},
   432  		License: "Apache-2.0",
   433  	})
   434  	if err != nil {
   435  		return err
   436  	}
   437  	defer prog.Close()
   438  
   439  	ns, err := netns.New()
   440  	if err != nil {
   441  		return fmt.Errorf("create netns: %w", err)
   442  	}
   443  	defer ns.Close()
   444  
   445  	// link.AttachTCX already performs its own feature detection and returns
   446  	// ebpf.ErrNotSupported if the host kernel doesn't have tcx.
   447  	return ns.Do(func() error {
   448  		l, err := link.AttachTCX(link.TCXOptions{
   449  			Program:   prog,
   450  			Attach:    ebpf.AttachTCXIngress,
   451  			Interface: 1, // lo
   452  			Anchor:    link.Tail(),
   453  		})
   454  		if err != nil {
   455  			return fmt.Errorf("creating link: %w", err)
   456  		}
   457  		if err := l.Close(); err != nil {
   458  			return fmt.Errorf("closing link: %w", err)
   459  		}
   460  
   461  		return nil
   462  	})
   463  })
   464  
   465  // HaveOuterSourceIPSupport tests whether the kernel support setting the outer
   466  // source IP address via the bpf_skb_set_tunnel_key BPF helper. We can't rely
   467  // on the verifier to reject a program using the new support because the
   468  // verifier just accepts any argument size for that helper; non-supported
   469  // fields will simply not be used. Instead, we set the outer source IP and
   470  // retrieve it with bpf_skb_get_tunnel_key right after. If the retrieved value
   471  // equals the value set, we have a confirmation the kernel supports it.
   472  func HaveOuterSourceIPSupport() (err error) {
   473  	defer func() {
   474  		if err != nil && !errors.Is(err, ebpf.ErrNotSupported) {
   475  			log.WithError(err).Fatal("failed to probe for outer source IP support")
   476  		}
   477  	}()
   478  
   479  	progSpec := &ebpf.ProgramSpec{
   480  		Name:    "set_tunnel_key_probe",
   481  		Type:    ebpf.SchedACT,
   482  		License: "GPL",
   483  	}
   484  	progSpec.Instructions = asm.Instructions{
   485  		asm.Mov.Reg(asm.R8, asm.R1),
   486  
   487  		asm.Mov.Imm(asm.R2, 0),
   488  		asm.StoreMem(asm.RFP, -8, asm.R2, asm.DWord),
   489  		asm.StoreMem(asm.RFP, -16, asm.R2, asm.DWord),
   490  		asm.StoreMem(asm.RFP, -24, asm.R2, asm.DWord),
   491  		asm.StoreMem(asm.RFP, -32, asm.R2, asm.DWord),
   492  		asm.StoreMem(asm.RFP, -40, asm.R2, asm.DWord),
   493  		asm.Mov.Imm(asm.R2, 42),
   494  		asm.StoreMem(asm.RFP, -44, asm.R2, asm.Word),
   495  		asm.Mov.Reg(asm.R2, asm.RFP),
   496  		asm.Add.Imm(asm.R2, -44),
   497  		asm.Mov.Imm(asm.R3, 44), // sizeof(struct bpf_tunnel_key) when setting the outer source IP is supported.
   498  		asm.Mov.Imm(asm.R4, 0),
   499  		asm.FnSkbSetTunnelKey.Call(),
   500  
   501  		asm.Mov.Reg(asm.R1, asm.R8),
   502  		asm.Mov.Reg(asm.R2, asm.RFP),
   503  		asm.Add.Imm(asm.R2, -44),
   504  		asm.Mov.Imm(asm.R3, 44),
   505  		asm.Mov.Imm(asm.R4, 0),
   506  		asm.FnSkbGetTunnelKey.Call(),
   507  
   508  		asm.LoadMem(asm.R0, asm.RFP, -44, asm.Word),
   509  		asm.Return(),
   510  	}
   511  	prog, err := ebpf.NewProgram(progSpec)
   512  	if err != nil {
   513  		return err
   514  	}
   515  	defer prog.Close()
   516  
   517  	pkt := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
   518  	ret, _, err := prog.Test(pkt)
   519  	if err != nil {
   520  		return err
   521  	}
   522  	if ret != 42 {
   523  		return ebpf.ErrNotSupported
   524  	}
   525  	return nil
   526  }
   527  
   528  // HaveSKBAdjustRoomL2RoomMACSupport tests whether the kernel supports the `bpf_skb_adjust_room` helper
   529  // with the `BPF_ADJ_ROOM_MAC` mode. To do so, we create a program that requests the passed in SKB
   530  // to be expanded by 20 bytes. The helper checks the `mode` argument and will return -ENOSUPP if
   531  // the mode is unknown. Otherwise it should resize the SKB by 20 bytes and return 0.
   532  func HaveSKBAdjustRoomL2RoomMACSupport() (err error) {
   533  	defer func() {
   534  		if err != nil && !errors.Is(err, ebpf.ErrNotSupported) {
   535  			log.WithError(err).Fatal("failed to probe for bpf_skb_adjust_room L2 room MAC support")
   536  		}
   537  	}()
   538  
   539  	progSpec := &ebpf.ProgramSpec{
   540  		Name:    "adjust_mac_room",
   541  		Type:    ebpf.SchedCLS,
   542  		License: "GPL",
   543  	}
   544  	progSpec.Instructions = asm.Instructions{
   545  		asm.Mov.Imm(asm.R2, 20), // len_diff
   546  		asm.Mov.Imm(asm.R3, 1),  // mode: BPF_ADJ_ROOM_MAC
   547  		asm.Mov.Imm(asm.R4, 0),  // flags: 0
   548  		asm.FnSkbAdjustRoom.Call(),
   549  		asm.Return(),
   550  	}
   551  	prog, err := ebpf.NewProgram(progSpec)
   552  	if err != nil {
   553  		return err
   554  	}
   555  	defer prog.Close()
   556  
   557  	// This is a Eth + IPv4 + UDP + data packet. The helper relies on a valid packet being passed in
   558  	// since it wants to know offsets of the different layers.
   559  	buf := gopacket.NewSerializeBuffer()
   560  	err = gopacket.SerializeLayers(buf, gopacket.SerializeOptions{},
   561  		&layers.Ethernet{
   562  			DstMAC:       net.HardwareAddr{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   563  			SrcMAC:       net.HardwareAddr{0x0e, 0xf5, 0x16, 0x3d, 0x6b, 0xab},
   564  			EthernetType: layers.EthernetTypeIPv4,
   565  		},
   566  		&layers.IPv4{
   567  			Version:  4,
   568  			IHL:      5,
   569  			Length:   49,
   570  			Id:       0xCECB,
   571  			TTL:      64,
   572  			Protocol: layers.IPProtocolUDP,
   573  			SrcIP:    net.IPv4(0xc0, 0xa8, 0xb2, 0x56),
   574  			DstIP:    net.IPv4(0xc0, 0xa8, 0xb2, 0xff),
   575  		},
   576  		&layers.UDP{
   577  			SrcPort: 23939,
   578  			DstPort: 32412,
   579  		},
   580  		gopacket.Payload("M-SEARCH * HTTP/1.1\x0d\x0a"),
   581  	)
   582  	if err != nil {
   583  		return fmt.Errorf("craft packet: %w", err)
   584  	}
   585  
   586  	ret, _, err := prog.Test(buf.Bytes())
   587  	if err != nil {
   588  		return err
   589  	}
   590  	if ret != 0 {
   591  		return ebpf.ErrNotSupported
   592  	}
   593  	return nil
   594  }
   595  
   596  // HaveDeadCodeElim tests whether the kernel supports dead code elimination.
   597  func HaveDeadCodeElim() error {
   598  	spec := ebpf.ProgramSpec{
   599  		Name: "test",
   600  		Type: ebpf.XDP,
   601  		Instructions: asm.Instructions{
   602  			asm.Mov.Imm(asm.R1, 0),
   603  			asm.JEq.Imm(asm.R1, 1, "else"),
   604  			asm.Mov.Imm(asm.R0, 2),
   605  			asm.Ja.Label("end"),
   606  			asm.Mov.Imm(asm.R0, 3).WithSymbol("else"),
   607  			asm.Return().WithSymbol("end"),
   608  		},
   609  	}
   610  
   611  	prog, err := ebpf.NewProgram(&spec)
   612  	if err != nil {
   613  		return fmt.Errorf("loading program: %w", err)
   614  	}
   615  
   616  	info, err := prog.Info()
   617  	if err != nil {
   618  		return fmt.Errorf("get prog info: %w", err)
   619  	}
   620  	infoInst, err := info.Instructions()
   621  	if err != nil {
   622  		return fmt.Errorf("get instructions: %w", err)
   623  	}
   624  
   625  	for _, inst := range infoInst {
   626  		if inst.OpCode.Class().IsJump() && inst.OpCode.JumpOp() != asm.Exit {
   627  			return fmt.Errorf("Jump instruction found in the final program, no dead code elimination performed")
   628  		}
   629  	}
   630  
   631  	return nil
   632  }
   633  
   634  // HaveIPv6Support tests whether kernel can open an IPv6 socket. This will
   635  // also implicitly auto-load IPv6 kernel module if available and not yet
   636  // loaded.
   637  func HaveIPv6Support() error {
   638  	fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, 0)
   639  	if errors.Is(err, unix.EAFNOSUPPORT) || errors.Is(err, unix.EPROTONOSUPPORT) {
   640  		return ErrNotSupported
   641  	}
   642  	unix.Close(fd)
   643  	return nil
   644  }
   645  
   646  // CreateHeaderFiles creates C header files with macros indicating which BPF
   647  // features are available in the kernel.
   648  func CreateHeaderFiles(headerDir string, probes *FeatureProbes) error {
   649  	common, err := os.Create(filepath.Join(headerDir, "features.h"))
   650  	if err != nil {
   651  		return fmt.Errorf("could not create common features header file: %w", err)
   652  	}
   653  	defer common.Close()
   654  	if err := writeCommonHeader(common, probes); err != nil {
   655  		return fmt.Errorf("could not write common features header file: %w", err)
   656  	}
   657  
   658  	skb, err := os.Create(filepath.Join(headerDir, "features_skb.h"))
   659  	if err != nil {
   660  		return fmt.Errorf("could not create skb related features header file: %w", err)
   661  	}
   662  	defer skb.Close()
   663  	if err := writeSkbHeader(skb, probes); err != nil {
   664  		return fmt.Errorf("could not write skb related features header file: %w", err)
   665  	}
   666  
   667  	xdp, err := os.Create(filepath.Join(headerDir, "features_xdp.h"))
   668  	if err != nil {
   669  		return fmt.Errorf("could not create xdp related features header file: %w", err)
   670  	}
   671  	defer xdp.Close()
   672  	if err := writeXdpHeader(xdp, probes); err != nil {
   673  		return fmt.Errorf("could not write xdp related features header file: %w", err)
   674  	}
   675  
   676  	return nil
   677  }
   678  
   679  // ExecuteHeaderProbes probes the kernel for a specific set of BPF features
   680  // which are currently used to generate various feature macros for the datapath.
   681  // The probe results returned in FeatureProbes are then used in the respective
   682  // function that writes the actual C macro definitions.
   683  // Further needed probes should be added here, while new macro strings need to
   684  // be added in the correct `write*Header()` function.
   685  func ExecuteHeaderProbes() *FeatureProbes {
   686  	probes := FeatureProbes{
   687  		ProgramHelpers: make(map[ProgramHelper]bool),
   688  		Misc:           miscFeatures{},
   689  	}
   690  
   691  	progHelpers := []ProgramHelper{
   692  		// common probes
   693  		{ebpf.CGroupSock, asm.FnGetNetnsCookie},
   694  		{ebpf.CGroupSockAddr, asm.FnGetNetnsCookie},
   695  		{ebpf.CGroupSockAddr, asm.FnGetSocketCookie},
   696  		{ebpf.CGroupSock, asm.FnJiffies64},
   697  		{ebpf.CGroupSockAddr, asm.FnJiffies64},
   698  		{ebpf.SchedCLS, asm.FnJiffies64},
   699  		{ebpf.XDP, asm.FnJiffies64},
   700  		{ebpf.CGroupSockAddr, asm.FnSkLookupTcp},
   701  		{ebpf.CGroupSockAddr, asm.FnSkLookupUdp},
   702  		{ebpf.CGroupSockAddr, asm.FnGetCurrentCgroupId},
   703  		{ebpf.CGroupSock, asm.FnSetRetval},
   704  		{ebpf.SchedCLS, asm.FnRedirectNeigh},
   705  		{ebpf.SchedCLS, asm.FnRedirectPeer},
   706  
   707  		// skb related probes
   708  		{ebpf.SchedCLS, asm.FnSkbChangeTail},
   709  		{ebpf.SchedCLS, asm.FnCsumLevel},
   710  
   711  		// xdp related probes
   712  		{ebpf.XDP, asm.FnXdpGetBuffLen},
   713  		{ebpf.XDP, asm.FnXdpLoadBytes},
   714  		{ebpf.XDP, asm.FnXdpStoreBytes},
   715  	}
   716  	for _, ph := range progHelpers {
   717  		probes.ProgramHelpers[ph] = (HaveProgramHelper(ph.Program, ph.Helper) == nil)
   718  	}
   719  
   720  	probes.Misc.HaveFibIfindex = (HaveFibIfindex() == nil)
   721  
   722  	return &probes
   723  }
   724  
   725  // writeCommonHeader defines macross for bpf/include/bpf/features.h
   726  func writeCommonHeader(writer io.Writer, probes *FeatureProbes) error {
   727  	features := map[string]bool{
   728  		"HAVE_NETNS_COOKIE": probes.ProgramHelpers[ProgramHelper{ebpf.CGroupSock, asm.FnGetNetnsCookie}] &&
   729  			probes.ProgramHelpers[ProgramHelper{ebpf.CGroupSockAddr, asm.FnGetNetnsCookie}],
   730  		"HAVE_SOCKET_COOKIE": probes.ProgramHelpers[ProgramHelper{ebpf.CGroupSockAddr, asm.FnGetSocketCookie}],
   731  		"HAVE_JIFFIES": probes.ProgramHelpers[ProgramHelper{ebpf.CGroupSock, asm.FnJiffies64}] &&
   732  			probes.ProgramHelpers[ProgramHelper{ebpf.CGroupSockAddr, asm.FnJiffies64}] &&
   733  			probes.ProgramHelpers[ProgramHelper{ebpf.SchedCLS, asm.FnJiffies64}] &&
   734  			probes.ProgramHelpers[ProgramHelper{ebpf.XDP, asm.FnJiffies64}],
   735  		"HAVE_CGROUP_ID":   probes.ProgramHelpers[ProgramHelper{ebpf.CGroupSockAddr, asm.FnGetCurrentCgroupId}],
   736  		"HAVE_SET_RETVAL":  probes.ProgramHelpers[ProgramHelper{ebpf.CGroupSock, asm.FnSetRetval}],
   737  		"HAVE_FIB_NEIGH":   probes.ProgramHelpers[ProgramHelper{ebpf.SchedCLS, asm.FnRedirectNeigh}],
   738  		"HAVE_FIB_IFINDEX": probes.Misc.HaveFibIfindex,
   739  	}
   740  
   741  	return writeFeatureHeader(writer, features, true)
   742  }
   743  
   744  // writeSkbHeader defines macros for bpf/include/bpf/features_skb.h
   745  func writeSkbHeader(writer io.Writer, probes *FeatureProbes) error {
   746  	featuresSkb := map[string]bool{
   747  		"HAVE_CSUM_LEVEL": probes.ProgramHelpers[ProgramHelper{ebpf.SchedCLS, asm.FnCsumLevel}],
   748  	}
   749  
   750  	return writeFeatureHeader(writer, featuresSkb, false)
   751  }
   752  
   753  // writeXdpHeader defines macros for bpf/include/bpf/features_xdp.h
   754  func writeXdpHeader(writer io.Writer, probes *FeatureProbes) error {
   755  	featuresXdp := map[string]bool{
   756  		"HAVE_XDP_GET_BUFF_LEN": probes.ProgramHelpers[ProgramHelper{ebpf.XDP, asm.FnXdpGetBuffLen}],
   757  		"HAVE_XDP_LOAD_BYTES":   probes.ProgramHelpers[ProgramHelper{ebpf.XDP, asm.FnXdpLoadBytes}],
   758  		"HAVE_XDP_STORE_BYTES":  probes.ProgramHelpers[ProgramHelper{ebpf.XDP, asm.FnXdpStoreBytes}],
   759  	}
   760  
   761  	return writeFeatureHeader(writer, featuresXdp, false)
   762  }
   763  
   764  func writeFeatureHeader(writer io.Writer, features map[string]bool, common bool) error {
   765  	input := struct {
   766  		Common   bool
   767  		Features map[string]bool
   768  	}{
   769  		Common:   common,
   770  		Features: features,
   771  	}
   772  
   773  	if err := tpl.Execute(writer, input); err != nil {
   774  		return fmt.Errorf("could not write template: %w", err)
   775  	}
   776  
   777  	return nil
   778  }
   779  
   780  // HaveBatchAPI checks if kernel supports batched bpf map lookup API.
   781  func HaveBatchAPI() error {
   782  	spec := ebpf.MapSpec{
   783  		Type:       ebpf.LRUHash,
   784  		KeySize:    1,
   785  		ValueSize:  1,
   786  		MaxEntries: 2,
   787  	}
   788  	m, err := ebpf.NewMapWithOptions(&spec, ebpf.MapOptions{})
   789  	if err != nil {
   790  		return ErrNotSupported
   791  	}
   792  	defer m.Close()
   793  	var cursor ebpf.MapBatchCursor
   794  	_, err = m.BatchLookup(&cursor, []byte{0}, []byte{0}, nil) // only do one batched lookup
   795  	if err != nil {
   796  		if errors.Is(err, ebpf.ErrNotSupported) {
   797  			return ErrNotSupported
   798  		}
   799  		return nil
   800  	}
   801  	return nil
   802  }