github.com/cilium/cilium@v1.16.2/pkg/bpf/bpffs_linux.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 //go:build linux 5 6 package bpf 7 8 import ( 9 "errors" 10 "fmt" 11 "os" 12 "path/filepath" 13 "sync" 14 15 "golang.org/x/sys/unix" 16 17 "github.com/cilium/cilium/pkg/components" 18 "github.com/cilium/cilium/pkg/defaults" 19 "github.com/cilium/cilium/pkg/mountinfo" 20 ) 21 22 var ( 23 // Path to where bpffs is mounted 24 bpffsRoot = defaults.BPFFSRoot 25 26 // Set to true on first get request to detect misorder 27 lockedDown = false 28 once sync.Once 29 readMountInfo sync.Once 30 mountInfoPrefix string 31 ) 32 33 func lockDown() { 34 lockedDown = true 35 } 36 37 func setBPFFSRoot(path string) { 38 if lockedDown { 39 panic("setBPFFSRoot() call after bpffsRoot was read") 40 } 41 bpffsRoot = path 42 } 43 44 func BPFFSRoot() string { 45 once.Do(lockDown) 46 return bpffsRoot 47 } 48 49 // TCGlobalsPath returns the absolute path to <bpffs>/tc/globals, used for 50 // legacy map pin paths. 51 func TCGlobalsPath() string { 52 once.Do(lockDown) 53 return filepath.Join(bpffsRoot, defaults.TCGlobalsPath) 54 } 55 56 // CiliumPath returns the bpffs path to be used for Cilium object pins. 57 func CiliumPath() string { 58 once.Do(lockDown) 59 return filepath.Join(bpffsRoot, "cilium") 60 } 61 62 // MkdirBPF wraps [os.MkdirAll] with the right permission bits for bpffs. 63 // Use this for ensuring the existence of directories on bpffs. 64 func MkdirBPF(path string) error { 65 return os.MkdirAll(path, 0755) 66 } 67 68 // Remove path ignoring ErrNotExist. 69 func Remove(path string) error { 70 err := os.RemoveAll(path) 71 if err != nil && !errors.Is(err, os.ErrNotExist) { 72 return fmt.Errorf("removing bpffs directory at %s: %w", path, err) 73 } 74 return err 75 } 76 77 func tcPathFromMountInfo(name string) string { 78 readMountInfo.Do(func() { 79 mountInfos, err := mountinfo.GetMountInfo() 80 if err != nil { 81 log.WithError(err).Fatal("Could not get mount info for map root lookup") 82 } 83 84 for _, mountInfo := range mountInfos { 85 if mountInfo.FilesystemType == "bpf" { 86 mountInfoPrefix = filepath.Join(mountInfo.MountPoint, defaults.TCGlobalsPath) 87 return 88 } 89 } 90 91 log.Fatal("Could not find BPF map root") 92 }) 93 94 return filepath.Join(mountInfoPrefix, name) 95 } 96 97 // MapPath returns a path for a BPF map with a given name. 98 func MapPath(name string) string { 99 if components.IsCiliumAgent() { 100 once.Do(lockDown) 101 return filepath.Join(TCGlobalsPath(), name) 102 } 103 return tcPathFromMountInfo(name) 104 } 105 106 // LocalMapName returns the name for a BPF map that is local to the specified ID. 107 func LocalMapName(name string, id uint16) string { 108 return fmt.Sprintf("%s%05d", name, id) 109 } 110 111 // LocalMapPath returns the path for a BPF map that is local to the specified ID. 112 func LocalMapPath(name string, id uint16) string { 113 return MapPath(LocalMapName(name, id)) 114 } 115 116 var ( 117 mountOnce sync.Once 118 ) 119 120 // mountFS mounts the BPFFS filesystem into the desired mapRoot directory. 121 func mountFS(printWarning bool) error { 122 if printWarning { 123 log.Warning("================================= WARNING ==========================================") 124 log.Warning("BPF filesystem is not mounted. This will lead to network disruption when Cilium pods") 125 log.Warning("are restarted. Ensure that the BPF filesystem is mounted in the host.") 126 log.Warning("https://docs.cilium.io/en/stable/operations/system_requirements/#mounted-ebpf-filesystem") 127 log.Warning("====================================================================================") 128 } 129 130 log.Infof("Mounting BPF filesystem at %s", bpffsRoot) 131 132 mapRootStat, err := os.Stat(bpffsRoot) 133 if err != nil { 134 if os.IsNotExist(err) { 135 if err := MkdirBPF(bpffsRoot); err != nil { 136 return fmt.Errorf("unable to create bpf mount directory: %w", err) 137 } 138 } else { 139 return fmt.Errorf("failed to stat the mount path %s: %w", bpffsRoot, err) 140 141 } 142 } else if !mapRootStat.IsDir() { 143 return fmt.Errorf("%s is a file which is not a directory", bpffsRoot) 144 } 145 146 if err := unix.Mount(bpffsRoot, bpffsRoot, "bpf", 0, ""); err != nil { 147 return fmt.Errorf("failed to mount %s: %w", bpffsRoot, err) 148 } 149 return nil 150 } 151 152 // hasMultipleMounts checks whether the current mapRoot has only one mount. 153 func hasMultipleMounts() (bool, error) { 154 num := 0 155 156 mountInfos, err := mountinfo.GetMountInfo() 157 if err != nil { 158 return false, err 159 } 160 161 for _, mountInfo := range mountInfos { 162 if mountInfo.Root == "/" && mountInfo.MountPoint == bpffsRoot { 163 num++ 164 } 165 } 166 167 return num > 1, nil 168 } 169 170 // checkOrMountCustomLocation tries to check or mount the BPF filesystem in the 171 // given path. 172 func checkOrMountCustomLocation(bpfRoot string) error { 173 setBPFFSRoot(bpfRoot) 174 175 // Check whether the custom location has a BPFFS mount. 176 mounted, bpffsInstance, err := mountinfo.IsMountFS(mountinfo.FilesystemTypeBPFFS, bpfRoot) 177 if err != nil { 178 return err 179 } 180 181 // If the custom location has no mount, let's mount BPFFS there. 182 if !mounted { 183 setBPFFSRoot(bpfRoot) 184 if err := mountFS(true); err != nil { 185 return err 186 } 187 188 return nil 189 } 190 191 // If the custom location already has a mount with some other filesystem than 192 // BPFFS, return the error. 193 if !bpffsInstance { 194 return fmt.Errorf("mount in the custom directory %s has a different filesystem than BPFFS", bpfRoot) 195 } 196 197 log.Infof("Detected mounted BPF filesystem at %s", bpffsRoot) 198 199 return nil 200 } 201 202 // checkOrMountDefaultLocations tries to check or mount the BPF filesystem in 203 // standard locations, which are: 204 // - /sys/fs/bpf 205 // - /run/cilium/bpffs 206 // There is a procedure of determining which directory is going to be used: 207 // 1. Checking whether BPFFS filesystem is mounted in /sys/fs/bpf. 208 // 2. If there is no mount, then mount BPFFS in /sys/fs/bpf and finish there. 209 // 3. If there is a BPFFS mount, finish there. 210 // 4. If there is a mount, but with the other filesystem, then it means that most 211 // probably Cilium is running inside container which has mounted /sys/fs/bpf 212 // from host, but host doesn't have proper BPFFS mount, so that mount is just 213 // the empty directory. In that case, mount BPFFS under /run/cilium/bpffs. 214 func checkOrMountDefaultLocations() error { 215 // Check whether /sys/fs/bpf has a BPFFS mount. 216 mounted, bpffsInstance, err := mountinfo.IsMountFS(mountinfo.FilesystemTypeBPFFS, bpffsRoot) 217 if err != nil { 218 return err 219 } 220 221 // If /sys/fs/bpf is not mounted at all, we should mount 222 // BPFFS there. 223 if !mounted { 224 if err := mountFS(false); err != nil { 225 return err 226 } 227 228 return nil 229 } 230 231 if !bpffsInstance { 232 // If /sys/fs/bpf has a mount but with some other filesystem 233 // than BPFFS, it means that Cilium is running inside container 234 // and /sys/fs/bpf is not mounted on host. We should mount BPFFS 235 // in /run/cilium/bpffs automatically. This will allow operation 236 // of Cilium but will result in unmounting of the filesystem 237 // when the pod is restarted. This in turn will cause resources 238 // such as the connection tracking table of the BPF programs to 239 // be released which will cause all connections into local 240 // containers to be dropped. User is going to be warned. 241 log.Warnf("BPF filesystem is going to be mounted automatically "+ 242 "in %s. However, it probably means that Cilium is running "+ 243 "inside container and BPFFS is not mounted on the host. "+ 244 "for more information, see: https://cilium.link/err-bpf-mount", 245 defaults.BPFFSRootFallback, 246 ) 247 setBPFFSRoot(defaults.BPFFSRootFallback) 248 249 cMounted, cBpffsInstance, err := mountinfo.IsMountFS(mountinfo.FilesystemTypeBPFFS, bpffsRoot) 250 if err != nil { 251 return err 252 } 253 if !cMounted { 254 if err := mountFS(false); err != nil { 255 return err 256 } 257 } else if !cBpffsInstance { 258 log.Fatalf("%s is mounted but has a different filesystem than BPFFS", defaults.BPFFSRootFallback) 259 } 260 } 261 262 log.Infof("Detected mounted BPF filesystem at %s", bpffsRoot) 263 264 return nil 265 } 266 267 func checkOrMountFS(bpfRoot string) error { 268 if bpfRoot == "" { 269 if err := checkOrMountDefaultLocations(); err != nil { 270 return err 271 } 272 } else { 273 if err := checkOrMountCustomLocation(bpfRoot); err != nil { 274 return err 275 } 276 } 277 278 multipleMounts, err := hasMultipleMounts() 279 if err != nil { 280 return err 281 } 282 if multipleMounts { 283 return fmt.Errorf("multiple mount points detected at %s", bpffsRoot) 284 } 285 286 return nil 287 } 288 289 // CheckOrMountFS checks or mounts the BPF filesystem and then 290 // opens/creates/deletes all maps which have previously been scheduled to be 291 // opened/created/deleted. 292 // 293 // If printWarning is set, will print a warning if bpffs has not previously been 294 // mounted. 295 func CheckOrMountFS(bpfRoot string) { 296 mountOnce.Do(func() { 297 if err := checkOrMountFS(bpfRoot); err != nil { 298 log.WithError(err).Fatal("Unable to mount BPF filesystem") 299 } 300 }) 301 }