github.com/elfadel/cilium@v1.6.12/pkg/datapath/loader/cache.go (about) 1 // Copyright 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 package loader 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "path/filepath" 22 "sync" 23 24 "github.com/cilium/cilium/common" 25 "github.com/cilium/cilium/pkg/controller" 26 "github.com/cilium/cilium/pkg/datapath" 27 "github.com/cilium/cilium/pkg/defaults" 28 "github.com/cilium/cilium/pkg/elf" 29 "github.com/cilium/cilium/pkg/lock" 30 "github.com/cilium/cilium/pkg/logging/logfields" 31 "github.com/cilium/cilium/pkg/option" 32 "github.com/cilium/cilium/pkg/serializer" 33 34 "github.com/fsnotify/fsnotify" 35 "github.com/sirupsen/logrus" 36 ) 37 38 var ( 39 once sync.Once 40 41 // templateCache is the cache of pre-compiled datapaths. 42 templateCache *objectCache 43 templateWatcherQueueSize = 10 44 45 ignoredELFPrefixes = []string{ 46 "2/", // Calls within the endpoint 47 "HOST_IP", // Global 48 "IPV6_NODEPORT", // Global 49 "ROUTER_IP", // Global 50 "SNAT_IPV6_EXTERNAL", // Global 51 "cilium_ct", // All CT maps, including local 52 "cilium_encrypt_state", // Global 53 "cilium_events", // Global 54 "cilium_ipcache", // Global 55 "cilium_lb", // Global 56 "cilium_lxc", // Global 57 "cilium_metrics", // Global 58 "cilium_nodeport_neigh", // All nodeport neigh maps 59 "cilium_policy", // Global 60 "cilium_proxy", // Global 61 "cilium_signals", // Global 62 "cilium_snat", // All SNAT maps 63 "cilium_tunnel", // Global 64 "from-container", // Prog name 65 "to-container", // Prog name 66 } 67 ) 68 69 // Init initializes the datapath cache with base program hashes derived from 70 // the LocalNodeConfiguration. 71 func Init(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) { 72 once.Do(func() { 73 templateCache = NewObjectCache(dp, nodeCfg) 74 ignorePrefixes := ignoredELFPrefixes 75 if !option.Config.EnableIPv4 { 76 ignorePrefixes = append(ignorePrefixes, "LXC_IPV4") 77 } 78 if !option.Config.EnableIPv6 { 79 ignorePrefixes = append(ignorePrefixes, "LXC_IP_") 80 } 81 elf.IgnoreSymbolPrefixes(ignorePrefixes) 82 }) 83 templateCache.Update(nodeCfg) 84 } 85 86 // RestoreTemplates populates the object cache from templates on the filesystem 87 // at the specified path. 88 func RestoreTemplates(stateDir string) error { 89 // Simplest implementation: Just garbage-collect everything. 90 // In future we should make this smarter. 91 path := filepath.Join(stateDir, defaults.TemplatesDir) 92 err := os.RemoveAll(path) 93 if err == nil || os.IsNotExist(err) { 94 return nil 95 } 96 return &os.PathError{ 97 Op: "failed to remove old BPF templates", 98 Path: path, 99 Err: err, 100 } 101 } 102 103 // objectCache is a map from a hash of the datapath to the path on the 104 // filesystem where its corresponding BPF object file exists. 105 type objectCache struct { 106 lock.Mutex 107 datapath.Datapath 108 109 workingDirectory string 110 baseHash *datapathHash 111 112 // newTemplates is notified whenever template is added to the objectCache. 113 newTemplates chan string 114 templateWatcherDone chan struct{} 115 116 // toPath maps a hash to the filesystem path of the corresponding object. 117 toPath map[string]string 118 119 // compileQueue maps a hash to a queue which ensures that only one 120 // attempt is made concurrently to compile the corresponding template. 121 compileQueue map[string]*serializer.FunctionQueue 122 } 123 124 func newObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration, workingDir string) *objectCache { 125 oc := &objectCache{ 126 Datapath: dp, 127 workingDirectory: workingDir, 128 newTemplates: make(chan string, templateWatcherQueueSize), 129 templateWatcherDone: make(chan struct{}), 130 toPath: make(map[string]string), 131 compileQueue: make(map[string]*serializer.FunctionQueue), 132 } 133 oc.Update(nodeCfg) 134 controller.NewManager().UpdateController("template-dir-watcher", 135 controller.ControllerParams{ 136 DoFunc: oc.watchTemplatesDirectory, 137 // No run interval but needs to re-run on errors. 138 }) 139 140 return oc 141 } 142 143 // NewObjectCache creates a new cache for datapath objects, basing the hash 144 // upon the configuration of the datapath and the specified node configuration. 145 func NewObjectCache(dp datapath.Datapath, nodeCfg *datapath.LocalNodeConfiguration) *objectCache { 146 return newObjectCache(dp, nodeCfg, option.Config.StateDir) 147 } 148 149 // Update may be called to update the base hash for configuration of datapath 150 // configuration that applies across the node. 151 func (o *objectCache) Update(nodeCfg *datapath.LocalNodeConfiguration) { 152 newHash := hashDatapath(o.Datapath, nodeCfg, nil, nil) 153 154 o.Lock() 155 defer o.Unlock() 156 o.baseHash = newHash 157 } 158 159 // serialize finds the channel that serializes builds against the same hash. 160 // Returns the channel and whether or not the caller needs to compile the 161 // datapath for this hash. 162 func (o *objectCache) serialize(hash string) (fq *serializer.FunctionQueue, found bool) { 163 o.Lock() 164 defer o.Unlock() 165 166 fq, compiled := o.compileQueue[hash] 167 if !compiled { 168 fq = serializer.NewFunctionQueue(1) 169 o.compileQueue[hash] = fq 170 } 171 return fq, compiled 172 } 173 174 func (o *objectCache) lookup(hash string) (string, bool) { 175 o.Lock() 176 defer o.Unlock() 177 path, exists := o.toPath[hash] 178 return path, exists 179 } 180 181 func (o *objectCache) insert(hash, objectPath string) error { 182 o.Lock() 183 defer o.Unlock() 184 o.toPath[hash] = objectPath 185 186 scopedLog := log.WithField(logfields.Path, objectPath) 187 select { 188 case o.newTemplates <- objectPath: 189 case <-o.templateWatcherDone: 190 // This means that the controller was stopped and Cilium is 191 // shutting down; don't bother complaining too loudly. 192 scopedLog.Debug("Failed to watch for template filesystem changes") 193 default: 194 // Unusual case; send on channel was blocked. 195 scopedLog.Warn("Failed to watch for template filesystem changes") 196 } 197 return nil 198 } 199 200 func (o *objectCache) delete(hash string) { 201 o.Lock() 202 defer o.Unlock() 203 delete(o.toPath, hash) 204 delete(o.compileQueue, hash) 205 } 206 207 // build attempts to compile and cache a datapath template object file 208 // corresponding to the specified endpoint configuration. 209 func (o *objectCache) build(ctx context.Context, cfg *templateCfg, hash string) error { 210 templatePath := filepath.Join(o.workingDirectory, defaults.TemplatesDir, hash) 211 headerPath := filepath.Join(templatePath, common.CHeaderFileName) 212 objectPath := filepath.Join(templatePath, endpointObj) 213 214 if err := os.MkdirAll(templatePath, defaults.StateDirRights); err != nil { 215 return &os.PathError{ 216 Op: "failed to create template directory", 217 Path: templatePath, 218 Err: err, 219 } 220 } 221 222 f, err := os.Create(headerPath) 223 if err != nil { 224 return &os.PathError{ 225 Op: "failed to open template header for writing", 226 Path: headerPath, 227 Err: err, 228 } 229 } 230 231 if err = o.Datapath.WriteEndpointConfig(f, cfg); err != nil { 232 return &os.PathError{ 233 Op: "failed to write template header", 234 Path: headerPath, 235 Err: err, 236 } 237 } 238 239 cfg.stats.bpfCompilation.Start() 240 err = compileTemplate(ctx, templatePath) 241 cfg.stats.bpfCompilation.End(err == nil) 242 if err != nil { 243 return &os.PathError{ 244 Op: "failed to compile template program", 245 Path: templatePath, 246 Err: err, 247 } 248 } 249 250 log.WithFields(logrus.Fields{ 251 logfields.Path: objectPath, 252 logfields.BPFCompilationTime: cfg.stats.bpfCompilation.Total(), 253 }).Info("Compiled new BPF template") 254 255 o.insert(hash, objectPath) 256 return nil 257 } 258 259 // fetchOrCompile attempts to fetch the path to the datapath object 260 // corresponding to the provided endpoint configuration, or if this 261 // configuration is not yet compiled, compiles it. It will block if multiple 262 // threads attempt to concurrently fetchOrCompile a template binary for the 263 // same set of EndpointConfiguration. 264 // 265 // Returns the path to the compiled template datapath object and whether the 266 // object was compiled, or an error. 267 func (o *objectCache) fetchOrCompile(ctx context.Context, cfg datapath.EndpointConfiguration, stats *SpanStat) (path string, compiled bool, err error) { 268 var hash string 269 hash, err = o.baseHash.sumEndpoint(o, cfg, false) 270 if err != nil { 271 return "", false, err 272 } 273 274 // Capture the time spent waiting for the template to compile. 275 if stats != nil { 276 stats.bpfWaitForELF.Start() 277 defer func() { 278 // Wrap to ensure that "err" is compared upon return. 279 stats.bpfWaitForELF.End(err == nil) 280 }() 281 } 282 283 scopedLog := log.WithField(logfields.BPFHeaderfileHash, hash) 284 285 // Serializes attempts to compile this cfg. 286 fq, compiled := o.serialize(hash) 287 if !compiled { 288 fq.Enqueue(func() error { 289 defer fq.Stop() 290 templateCfg := wrap(cfg, stats) 291 err := o.build(ctx, templateCfg, hash) 292 if err != nil { 293 scopedLog.WithError(err).Error("BPF template object creation failed") 294 o.Lock() 295 delete(o.compileQueue, hash) 296 o.Unlock() 297 } 298 return err 299 }, serializer.NoRetry) 300 } 301 302 // Wait until the build completes. 303 if err = fq.Wait(ctx); err != nil { 304 scopedLog.WithError(err).Warning("Error while waiting for BPF template compilation") 305 return "", false, fmt.Errorf("BPF template compilation failed: %s", err) 306 } 307 308 // Fetch the result of the compilation. 309 path, ok := o.lookup(hash) 310 if !ok { 311 err := fmt.Errorf("Could not locate previously compiled BPF template") 312 scopedLog.WithError(err).Warning("BPF template compilation unsuccessful") 313 return "", false, err 314 } 315 316 return path, !compiled, nil 317 } 318 319 func (o *objectCache) watchTemplatesDirectory(ctx context.Context) error { 320 templateWatcher, err := fsnotify.NewWatcher() 321 if err != nil { 322 return err 323 } 324 defer func() { 325 close(o.templateWatcherDone) 326 templateWatcher.Close() 327 }() 328 329 for { 330 select { 331 // Watch for new templates being compiled and add to the filesystem watcher 332 case templatePath := <-o.newTemplates: 333 if err = templateWatcher.Add(templatePath); err != nil { 334 log.WithFields(logrus.Fields{ 335 logfields.Path: templatePath, 336 }).WithError(err).Warning("Failed to watch templates directory") 337 } else { 338 log.WithFields(logrus.Fields{ 339 logfields.Path: templatePath, 340 }).Debug("Watching template path") 341 } 342 // Handle filesystem deletes for current templates 343 case event, open := <-templateWatcher.Events: 344 if !open { 345 break 346 } 347 if event.Op&fsnotify.Remove != 0 { 348 log.WithField(logfields.Path, event.Name).Debug("Detected template removal") 349 templateHash := filepath.Base(filepath.Dir(event.Name)) 350 o.delete(templateHash) 351 } else { 352 log.WithField("event", event).Debug("Ignoring template FS event") 353 } 354 case err, _ = <-templateWatcher.Errors: 355 return err 356 case <-ctx.Done(): 357 return ctx.Err() 358 } 359 } 360 } 361 362 // EndpointHash hashes the specified endpoint configuration with the current 363 // datapath hash cache and returns the hash as string. 364 func EndpointHash(cfg datapath.EndpointConfiguration) (string, error) { 365 return templateCache.baseHash.sumEndpoint(templateCache, cfg, true) 366 }