github.com/elfadel/cilium@v1.6.12/pkg/bpf/bpffs_linux.go (about)

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