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  }