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 }