github.com/cilium/cilium@v1.16.2/pkg/bpf/bpf_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"
    13  
    14  	"github.com/cilium/ebpf"
    15  	"github.com/sirupsen/logrus"
    16  	"golang.org/x/sys/unix"
    17  
    18  	"github.com/cilium/cilium/pkg/logging/logfields"
    19  	"github.com/cilium/cilium/pkg/metrics"
    20  	"github.com/cilium/cilium/pkg/spanstat"
    21  )
    22  
    23  // createMap wraps a call to ebpf.NewMapWithOptions while measuring syscall duration.
    24  func createMap(spec *ebpf.MapSpec, opts *ebpf.MapOptions) (*ebpf.Map, error) {
    25  	if opts == nil {
    26  		opts = &ebpf.MapOptions{}
    27  	}
    28  
    29  	var duration *spanstat.SpanStat
    30  	if metrics.BPFSyscallDuration.IsEnabled() {
    31  		duration = spanstat.Start()
    32  	}
    33  
    34  	m, err := ebpf.NewMapWithOptions(spec, *opts)
    35  
    36  	if metrics.BPFSyscallDuration.IsEnabled() {
    37  		metrics.BPFSyscallDuration.WithLabelValues(metricOpCreate, metrics.Error2Outcome(err)).Observe(duration.End(err == nil).Total().Seconds())
    38  	}
    39  
    40  	return m, err
    41  }
    42  
    43  func objCheck(m *ebpf.Map, path string, mapType ebpf.MapType, keySize, valueSize, maxEntries, flags uint32) bool {
    44  	scopedLog := log.WithField(logfields.Path, path)
    45  	mismatch := false
    46  
    47  	if m.Type() != mapType {
    48  		scopedLog.WithFields(logrus.Fields{
    49  			"old": m.Type(),
    50  			"new": mapType,
    51  		}).Warning("Map type mismatch for BPF map")
    52  		mismatch = true
    53  	}
    54  
    55  	if m.KeySize() != keySize {
    56  		scopedLog.WithFields(logrus.Fields{
    57  			"old": m.KeySize(),
    58  			"new": keySize,
    59  		}).Warning("Key-size mismatch for BPF map")
    60  		mismatch = true
    61  	}
    62  
    63  	if m.ValueSize() != valueSize {
    64  		scopedLog.WithFields(logrus.Fields{
    65  			"old": m.ValueSize(),
    66  			"new": valueSize,
    67  		}).Warning("Value-size mismatch for BPF map")
    68  		mismatch = true
    69  	}
    70  
    71  	if m.MaxEntries() != maxEntries {
    72  		scopedLog.WithFields(logrus.Fields{
    73  			"old": m.MaxEntries(),
    74  			"new": maxEntries,
    75  		}).Warning("Max entries mismatch for BPF map")
    76  		mismatch = true
    77  	}
    78  	if m.Flags() != flags {
    79  		scopedLog.WithFields(logrus.Fields{
    80  			"old": m.Flags(),
    81  			"new": flags,
    82  		}).Warning("Flags mismatch for BPF map")
    83  		mismatch = true
    84  	}
    85  
    86  	if mismatch {
    87  		if m.Type() == ebpf.ProgramArray {
    88  			return false
    89  		}
    90  
    91  		scopedLog.Warning("Removing map to allow for property upgrade (expect map data loss)")
    92  
    93  		// Kernel still holds map reference count via attached prog.
    94  		// Only exception is prog array, but that is already resolved
    95  		// differently.
    96  		os.Remove(path)
    97  		return true
    98  	}
    99  
   100  	return false
   101  }
   102  
   103  // OpenOrCreateMap attempts to load the pinned map at "pinDir/<spec.Name>" if
   104  // the spec is marked as Pinned. Any parent directories of pinDir are
   105  // automatically created. Any pinned maps incompatible with the given spec are
   106  // removed and recreated.
   107  //
   108  // If spec.Pinned is 0, a new Map is always created.
   109  func OpenOrCreateMap(spec *ebpf.MapSpec, pinDir string) (*ebpf.Map, error) {
   110  	var opts ebpf.MapOptions
   111  	if spec.Pinning != 0 {
   112  		if pinDir == "" {
   113  			return nil, errors.New("cannot pin map to empty pinDir")
   114  		}
   115  		if spec.Name == "" {
   116  			return nil, errors.New("cannot load unnamed map from pin")
   117  		}
   118  
   119  		if err := MkdirBPF(pinDir); err != nil {
   120  			return nil, fmt.Errorf("creating map base pinning directory: %w", err)
   121  		}
   122  
   123  		opts.PinPath = pinDir
   124  	}
   125  
   126  	m, err := createMap(spec, &opts)
   127  	if errors.Is(err, ebpf.ErrMapIncompatible) {
   128  		// Found incompatible map. Open the pin again to find out why.
   129  		m, err := ebpf.LoadPinnedMap(path.Join(pinDir, spec.Name), nil)
   130  		if err != nil {
   131  			return nil, fmt.Errorf("open pin of incompatible map: %w", err)
   132  		}
   133  		defer m.Close()
   134  
   135  		log.WithField(logfields.Path, path.Join(pinDir, spec.Name)).
   136  			WithFields(logrus.Fields{
   137  				"old": fmt.Sprintf("Type:%s KeySize:%d ValueSize:%d MaxEntries:%d Flags:%d",
   138  					m.Type(), m.KeySize(), m.ValueSize(), m.MaxEntries(), m.Flags()),
   139  				"new": fmt.Sprintf("Type:%s KeySize:%d ValueSize:%d MaxEntries:%d Flags:%d",
   140  					spec.Type, spec.KeySize, spec.ValueSize, spec.MaxEntries, spec.Flags),
   141  			}).Info("Unpinning map with incompatible properties")
   142  
   143  		// Existing map incompatible with spec. Unpin so it can be recreated.
   144  		if err := m.Unpin(); err != nil {
   145  			return nil, err
   146  		}
   147  
   148  		return createMap(spec, &opts)
   149  	}
   150  
   151  	return m, err
   152  }
   153  
   154  // GetMtime returns monotonic time that can be used to compare
   155  // values with ktime_get_ns() BPF helper, e.g. needed to check
   156  // the timeout in sec for BPF entries. We return the raw nsec,
   157  // although that is not quite usable for comparison. Go has
   158  // runtime.nanotime() but doesn't expose it as API.
   159  func GetMtime() (uint64, error) {
   160  	var ts unix.Timespec
   161  
   162  	err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts)
   163  	if err != nil {
   164  		return 0, fmt.Errorf("Unable get time: %w", err)
   165  	}
   166  
   167  	return uint64(unix.TimespecToNsec(ts)), nil
   168  }