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 }