github.com/cilium/ebpf@v0.15.1-0.20240517100537-8079b37aa138/features/prog.go (about)

     1  package features
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  
     8  	"github.com/cilium/ebpf"
     9  	"github.com/cilium/ebpf/asm"
    10  	"github.com/cilium/ebpf/btf"
    11  	"github.com/cilium/ebpf/internal"
    12  	"github.com/cilium/ebpf/internal/sys"
    13  	"github.com/cilium/ebpf/internal/unix"
    14  )
    15  
    16  // HaveProgType probes the running kernel for the availability of the specified program type.
    17  //
    18  // Deprecated: use HaveProgramType() instead.
    19  var HaveProgType = HaveProgramType
    20  
    21  // HaveProgramType probes the running kernel for the availability of the specified program type.
    22  //
    23  // See the package documentation for the meaning of the error return value.
    24  func HaveProgramType(pt ebpf.ProgramType) (err error) {
    25  	return haveProgramTypeMatrix.Result(pt)
    26  }
    27  
    28  func probeProgram(spec *ebpf.ProgramSpec) error {
    29  	if spec.Instructions == nil {
    30  		spec.Instructions = asm.Instructions{
    31  			asm.LoadImm(asm.R0, 0, asm.DWord),
    32  			asm.Return(),
    33  		}
    34  	}
    35  	prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
    36  		LogDisabled: true,
    37  	})
    38  	if err == nil {
    39  		prog.Close()
    40  	}
    41  
    42  	switch {
    43  	// EINVAL occurs when attempting to create a program with an unknown type.
    44  	// E2BIG occurs when ProgLoadAttr contains non-zero bytes past the end
    45  	// of the struct known by the running kernel, meaning the kernel is too old
    46  	// to support the given prog type.
    47  	case errors.Is(err, unix.EINVAL), errors.Is(err, unix.E2BIG):
    48  		err = ebpf.ErrNotSupported
    49  	}
    50  
    51  	return err
    52  }
    53  
    54  var haveProgramTypeMatrix = internal.FeatureMatrix[ebpf.ProgramType]{
    55  	ebpf.SocketFilter:  {Version: "3.19"},
    56  	ebpf.Kprobe:        {Version: "4.1"},
    57  	ebpf.SchedCLS:      {Version: "4.1"},
    58  	ebpf.SchedACT:      {Version: "4.1"},
    59  	ebpf.TracePoint:    {Version: "4.7"},
    60  	ebpf.XDP:           {Version: "4.8"},
    61  	ebpf.PerfEvent:     {Version: "4.9"},
    62  	ebpf.CGroupSKB:     {Version: "4.10"},
    63  	ebpf.CGroupSock:    {Version: "4.10"},
    64  	ebpf.LWTIn:         {Version: "4.10"},
    65  	ebpf.LWTOut:        {Version: "4.10"},
    66  	ebpf.LWTXmit:       {Version: "4.10"},
    67  	ebpf.SockOps:       {Version: "4.13"},
    68  	ebpf.SkSKB:         {Version: "4.14"},
    69  	ebpf.CGroupDevice:  {Version: "4.15"},
    70  	ebpf.SkMsg:         {Version: "4.17"},
    71  	ebpf.RawTracepoint: {Version: "4.17"},
    72  	ebpf.CGroupSockAddr: {
    73  		Version: "4.17",
    74  		Fn: func() error {
    75  			return probeProgram(&ebpf.ProgramSpec{
    76  				Type:       ebpf.CGroupSockAddr,
    77  				AttachType: ebpf.AttachCGroupInet4Connect,
    78  			})
    79  		},
    80  	},
    81  	ebpf.LWTSeg6Local:          {Version: "4.18"},
    82  	ebpf.LircMode2:             {Version: "4.18"},
    83  	ebpf.SkReuseport:           {Version: "4.19"},
    84  	ebpf.FlowDissector:         {Version: "4.20"},
    85  	ebpf.CGroupSysctl:          {Version: "5.2"},
    86  	ebpf.RawTracepointWritable: {Version: "5.2"},
    87  	ebpf.CGroupSockopt: {
    88  		Version: "5.3",
    89  		Fn: func() error {
    90  			return probeProgram(&ebpf.ProgramSpec{
    91  				Type:       ebpf.CGroupSockopt,
    92  				AttachType: ebpf.AttachCGroupGetsockopt,
    93  			})
    94  		},
    95  	},
    96  	ebpf.Tracing: {
    97  		Version: "5.5",
    98  		Fn: func() error {
    99  			return probeProgram(&ebpf.ProgramSpec{
   100  				Type:       ebpf.Tracing,
   101  				AttachType: ebpf.AttachTraceFEntry,
   102  				AttachTo:   "bpf_init",
   103  			})
   104  		},
   105  	},
   106  	ebpf.StructOps: {
   107  		Version: "5.6",
   108  		Fn: func() error {
   109  			err := probeProgram(&ebpf.ProgramSpec{
   110  				Type:    ebpf.StructOps,
   111  				License: "GPL",
   112  			})
   113  			if errors.Is(err, sys.ENOTSUPP) {
   114  				// ENOTSUPP means the program type is at least known to the kernel.
   115  				return nil
   116  			}
   117  			return err
   118  		},
   119  	},
   120  	ebpf.Extension: {
   121  		Version: "5.6",
   122  		Fn: func() error {
   123  			// create btf.Func to add to first ins of target and extension so both progs are btf powered
   124  			btfFn := btf.Func{
   125  				Name: "a",
   126  				Type: &btf.FuncProto{
   127  					Return: &btf.Int{},
   128  					Params: []btf.FuncParam{
   129  						{Name: "ctx", Type: &btf.Pointer{Target: &btf.Struct{Name: "xdp_md"}}},
   130  					},
   131  				},
   132  				Linkage: btf.GlobalFunc,
   133  			}
   134  			insns := asm.Instructions{
   135  				btf.WithFuncMetadata(asm.Mov.Imm(asm.R0, 0), &btfFn),
   136  				asm.Return(),
   137  			}
   138  
   139  			// create target prog
   140  			prog, err := ebpf.NewProgramWithOptions(
   141  				&ebpf.ProgramSpec{
   142  					Type:         ebpf.XDP,
   143  					Instructions: insns,
   144  				},
   145  				ebpf.ProgramOptions{
   146  					LogDisabled: true,
   147  				},
   148  			)
   149  			if err != nil {
   150  				return err
   151  			}
   152  			defer prog.Close()
   153  
   154  			// probe for Extension prog with target
   155  			return probeProgram(&ebpf.ProgramSpec{
   156  				Type:         ebpf.Extension,
   157  				Instructions: insns,
   158  				AttachTarget: prog,
   159  				AttachTo:     btfFn.Name,
   160  			})
   161  		},
   162  	},
   163  	ebpf.LSM: {
   164  		Version: "5.7",
   165  		Fn: func() error {
   166  			return probeProgram(&ebpf.ProgramSpec{
   167  				Type:       ebpf.LSM,
   168  				AttachType: ebpf.AttachLSMMac,
   169  				AttachTo:   "file_mprotect",
   170  				License:    "GPL",
   171  			})
   172  		},
   173  	},
   174  	ebpf.SkLookup: {
   175  		Version: "5.9",
   176  		Fn: func() error {
   177  			return probeProgram(&ebpf.ProgramSpec{
   178  				Type:       ebpf.SkLookup,
   179  				AttachType: ebpf.AttachSkLookup,
   180  			})
   181  		},
   182  	},
   183  	ebpf.Syscall: {
   184  		Version: "5.14",
   185  		Fn: func() error {
   186  			return probeProgram(&ebpf.ProgramSpec{
   187  				Type:  ebpf.Syscall,
   188  				Flags: unix.BPF_F_SLEEPABLE,
   189  			})
   190  		},
   191  	},
   192  }
   193  
   194  func init() {
   195  	for key, ft := range haveProgramTypeMatrix {
   196  		ft.Name = key.String()
   197  		if ft.Fn == nil {
   198  			key := key // avoid the dreaded loop variable problem
   199  			ft.Fn = func() error { return probeProgram(&ebpf.ProgramSpec{Type: key}) }
   200  		}
   201  	}
   202  }
   203  
   204  type helperKey struct {
   205  	typ    ebpf.ProgramType
   206  	helper asm.BuiltinFunc
   207  }
   208  
   209  var helperCache = internal.NewFeatureCache(func(key helperKey) *internal.FeatureTest {
   210  	return &internal.FeatureTest{
   211  		Name: fmt.Sprintf("%s for program type %s", key.helper, key.typ),
   212  		Fn: func() error {
   213  			return haveProgramHelper(key.typ, key.helper)
   214  		},
   215  	}
   216  })
   217  
   218  // HaveProgramHelper probes the running kernel for the availability of the specified helper
   219  // function to a specified program type.
   220  // Return values have the following semantics:
   221  //
   222  //	err == nil: The feature is available.
   223  //	errors.Is(err, ebpf.ErrNotSupported): The feature is not available.
   224  //	err != nil: Any errors encountered during probe execution, wrapped.
   225  //
   226  // Note that the latter case may include false negatives, and that program creation may
   227  // succeed despite an error being returned.
   228  // Only `nil` and `ebpf.ErrNotSupported` are conclusive.
   229  //
   230  // Probe results are cached and persist throughout any process capability changes.
   231  func HaveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
   232  	if helper > helper.Max() {
   233  		return os.ErrInvalid
   234  	}
   235  
   236  	return helperCache.Result(helperKey{pt, helper})
   237  }
   238  
   239  func haveProgramHelper(pt ebpf.ProgramType, helper asm.BuiltinFunc) error {
   240  	if ok := helperProbeNotImplemented(pt); ok {
   241  		return fmt.Errorf("no feature probe for %v/%v", pt, helper)
   242  	}
   243  
   244  	if err := HaveProgramType(pt); err != nil {
   245  		return err
   246  	}
   247  
   248  	spec := &ebpf.ProgramSpec{
   249  		Type: pt,
   250  		Instructions: asm.Instructions{
   251  			helper.Call(),
   252  			asm.LoadImm(asm.R0, 0, asm.DWord),
   253  			asm.Return(),
   254  		},
   255  		License: "GPL",
   256  	}
   257  
   258  	switch pt {
   259  	case ebpf.CGroupSockAddr:
   260  		spec.AttachType = ebpf.AttachCGroupInet4Connect
   261  	case ebpf.CGroupSockopt:
   262  		spec.AttachType = ebpf.AttachCGroupGetsockopt
   263  	case ebpf.SkLookup:
   264  		spec.AttachType = ebpf.AttachSkLookup
   265  	case ebpf.Syscall:
   266  		spec.Flags = unix.BPF_F_SLEEPABLE
   267  	}
   268  
   269  	prog, err := ebpf.NewProgramWithOptions(spec, ebpf.ProgramOptions{
   270  		LogDisabled: true,
   271  	})
   272  	if err == nil {
   273  		prog.Close()
   274  	}
   275  
   276  	switch {
   277  	// EACCES occurs when attempting to create a program probe with a helper
   278  	// while the register args when calling this helper aren't set up properly.
   279  	// We interpret this as the helper being available, because the verifier
   280  	// returns EINVAL if the helper is not supported by the running kernel.
   281  	case errors.Is(err, unix.EACCES):
   282  		// TODO: possibly we need to check verifier output here to be sure
   283  		err = nil
   284  
   285  	// EINVAL occurs when attempting to create a program with an unknown helper.
   286  	case errors.Is(err, unix.EINVAL):
   287  		// TODO: possibly we need to check verifier output here to be sure
   288  		err = ebpf.ErrNotSupported
   289  	}
   290  
   291  	return err
   292  }
   293  
   294  func helperProbeNotImplemented(pt ebpf.ProgramType) bool {
   295  	switch pt {
   296  	case ebpf.Extension, ebpf.LSM, ebpf.StructOps, ebpf.Tracing:
   297  		return true
   298  	}
   299  	return false
   300  }