github.com/dylandreimerink/gobpfld@v0.6.1-0.20220205171531-e79c330ad608/kernelsupport/kernelsupport.go (about)

     1  package kernelsupport
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"syscall"
     8  )
     9  
    10  // KernelFeatures is a set of flagsets which describe the eBPF support of a kernel version.
    11  // Flags are split amount several sets since the won't all fit in one uint64
    12  type KernelFeatures struct {
    13  	// BPF is set to true if eBPF is supported on the current kernel version and arch combo
    14  	BPF     bool
    15  	Arch    ArchSupport
    16  	Map     MapSupport
    17  	API     APISupport
    18  	Program ProgramSupport
    19  	Attach  AttachSupport
    20  	// TODO helper functions
    21  	Misc MiscSupport
    22  }
    23  
    24  // CurrentFeatures is a singleton containing the result of MustGetKernelFeatures. Assuming kernel features don't
    25  // change during the lifetime. Using this singleton saves a lot of performance.
    26  var CurrentFeatures = MustGetKernelFeatures()
    27  
    28  // CurrentVersion is a singleton containing the result of MustGetKernelVersion. Assuming the kernel version doesn't
    29  // change during the lifetime. Using this singleton saves a lot of performance.
    30  var CurrentVersion = MustGetKernelVersion()
    31  
    32  // MustGetKernelFeatures runs GetKernelFeatures but panics if any error is detected
    33  func MustGetKernelFeatures() KernelFeatures {
    34  	features, err := getKernelFeatures(true)
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  	return features
    39  }
    40  
    41  // GetKernelFeatures returns a list of kernel features for the kernel on which
    42  // the current program is currently running.
    43  func GetKernelFeatures() (KernelFeatures, error) {
    44  	return getKernelFeatures(false)
    45  }
    46  
    47  func getKernelFeatures(must bool) (KernelFeatures, error) {
    48  	utsname, version, err := getKernelVersion(must)
    49  	if err != nil {
    50  		return KernelFeatures{}, err
    51  	}
    52  
    53  	features := KernelFeatures{}
    54  	for _, kvf := range featureMinVersion {
    55  		if version.Higher(kvf.version) {
    56  			features.Arch = features.Arch | kvf.features.Arch
    57  			features.Map = features.Map | kvf.features.Map
    58  			features.API = features.API | kvf.features.API
    59  			features.Program = features.Program | kvf.features.Program
    60  			features.Attach = features.Attach | kvf.features.Attach
    61  			features.Misc = features.Misc | kvf.features.Misc
    62  		}
    63  	}
    64  
    65  	machineBytes := make([]byte, len(utsname.Machine))
    66  	for i, v := range utsname.Machine {
    67  		if v == 0x00 {
    68  			machineBytes = machineBytes[:i]
    69  			break
    70  		}
    71  		machineBytes[i] = byte(v)
    72  	}
    73  	machine := string(machineBytes)
    74  
    75  	// Attempt to match the machine UTS string to an architecture
    76  	switch machine {
    77  	case "x86_64":
    78  		features.BPF = features.Arch.Has(KFeatArchx86_64)
    79  	case "arm64", "armv8b", "aarch64_be", "aarch64":
    80  		features.BPF = features.Arch.Has(KFeatArchARM64)
    81  	case "s309", "s390x":
    82  		features.BPF = features.Arch.Has(KFeatArchs390)
    83  	case "ppc64":
    84  		features.BPF = features.Arch.Has(KFeatArchPP64)
    85  	case "sparc64":
    86  		features.BPF = features.Arch.Has(KFeatArchSparc64)
    87  	case "mips64", "mips32", "mips":
    88  		features.BPF = features.Arch.Has(KFeatArchMIPS)
    89  	case "arm", "arm32", "armv8l":
    90  		features.BPF = features.Arch.Has(KFeatArchARM32)
    91  	case "i386", "i486", "i586", "i686":
    92  		features.BPF = features.Arch.Has(KFeatArchx86)
    93  	case "riscv64":
    94  		features.BPF = features.Arch.Has(KFeatArchRiscVRV64G)
    95  	case "riscv32":
    96  		features.BPF = features.Arch.Has(KFeatArchRiscVRV32G)
    97  	}
    98  
    99  	return features, nil
   100  }
   101  
   102  // MustGetKernelVersion runs GetKernelFeatures but panics if any error is detected
   103  func MustGetKernelVersion() KernelVersion {
   104  	_, features, err := getKernelVersion(true)
   105  	if err != nil {
   106  		panic(err)
   107  	}
   108  	return features
   109  }
   110  
   111  func GetKernelVersion() (KernelVersion, error) {
   112  	_, version, err := getKernelVersion(false)
   113  	return version, err
   114  }
   115  
   116  func getKernelVersion(must bool) (*syscall.Utsname, KernelVersion, error) {
   117  	var utsname syscall.Utsname
   118  	err := syscall.Uname(&utsname)
   119  	if err != nil {
   120  		return nil, KernelVersion{}, fmt.Errorf("error while calling syscall.Uname: %w", err)
   121  	}
   122  
   123  	releaseBytes := make([]byte, len(utsname.Release))
   124  	for i, v := range utsname.Release {
   125  		if v == 0x00 {
   126  			releaseBytes = releaseBytes[:i]
   127  			break
   128  		}
   129  		releaseBytes[i] = byte(v)
   130  	}
   131  	release := string(releaseBytes)
   132  
   133  	version, err := ParseKernelVersion(release, true)
   134  	if err != nil {
   135  		return nil, version, err
   136  	}
   137  
   138  	return &utsname, version, err
   139  }
   140  
   141  func Version(major, minor, patch int) KernelVersion {
   142  	return KernelVersion{
   143  		Major: major,
   144  		Minor: minor,
   145  		Patch: patch,
   146  	}
   147  }
   148  
   149  type KernelVersion struct {
   150  	Major int
   151  	Minor int
   152  	Patch int
   153  }
   154  
   155  // Higher returns true if the 'cmp' version is higher than the 'kv' version
   156  func (kv KernelVersion) Higher(cmp KernelVersion) bool {
   157  	if kv.Major > cmp.Major {
   158  		return true
   159  	}
   160  	if kv.Major < cmp.Major {
   161  		return false
   162  	}
   163  
   164  	// Majors are equal
   165  
   166  	if kv.Minor > cmp.Minor {
   167  		return true
   168  	}
   169  	if kv.Minor < cmp.Minor {
   170  		return false
   171  	}
   172  
   173  	// Minors are equal
   174  
   175  	if kv.Patch >= cmp.Patch {
   176  		return true
   177  	}
   178  
   179  	return false
   180  }
   181  
   182  func ParseKernelVersion(release string, must bool) (version KernelVersion, err error) {
   183  	parts := strings.Split(release, "-")
   184  
   185  	// The base version is before the -, discard anything after the -
   186  	base := parts[0]
   187  	baseParts := strings.Split(base, ".")
   188  	if len(baseParts) > 2 {
   189  		version.Patch, err = strconv.Atoi(baseParts[2])
   190  		if err != nil {
   191  			// The patch version is not critically important in most cases. If 'must' is true this would result
   192  			// in a panic, instread ignore the error.
   193  			// A malformed patch version is possible with incorrect kernel parameters, so handle it gracefully
   194  			if !must {
   195  				return version, fmt.Errorf("error while parsing kernel patch version '%s': %w", baseParts[2], err)
   196  			}
   197  		}
   198  	}
   199  
   200  	if len(baseParts) > 1 {
   201  		version.Minor, err = strconv.Atoi(baseParts[1])
   202  		if err != nil {
   203  			return version, fmt.Errorf("error while parsing kernel minor version '%s': %w", baseParts[1], err)
   204  		}
   205  	}
   206  
   207  	version.Major, err = strconv.Atoi(baseParts[0])
   208  	if err != nil {
   209  		return version, fmt.Errorf("error while parsing kernel major version '%s': %w", baseParts[0], err)
   210  	}
   211  
   212  	return version, nil
   213  }