github.com/cilium/cilium@v1.16.2/pkg/datapath/loader/loader.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package loader 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 "net" 11 "net/netip" 12 "path/filepath" 13 "strings" 14 "sync" 15 "sync/atomic" 16 17 "github.com/cilium/ebpf" 18 "github.com/cilium/hive/cell" 19 "github.com/sirupsen/logrus" 20 "github.com/vishvananda/netlink" 21 22 "github.com/cilium/cilium/pkg/bpf" 23 "github.com/cilium/cilium/pkg/byteorder" 24 "github.com/cilium/cilium/pkg/datapath/linux/linux_defaults" 25 "github.com/cilium/cilium/pkg/datapath/linux/route" 26 "github.com/cilium/cilium/pkg/datapath/linux/sysctl" 27 "github.com/cilium/cilium/pkg/datapath/loader/metrics" 28 "github.com/cilium/cilium/pkg/datapath/tables" 29 datapath "github.com/cilium/cilium/pkg/datapath/types" 30 "github.com/cilium/cilium/pkg/defaults" 31 iputil "github.com/cilium/cilium/pkg/ip" 32 "github.com/cilium/cilium/pkg/lock" 33 "github.com/cilium/cilium/pkg/logging" 34 "github.com/cilium/cilium/pkg/logging/logfields" 35 "github.com/cilium/cilium/pkg/mac" 36 "github.com/cilium/cilium/pkg/maps/callsmap" 37 "github.com/cilium/cilium/pkg/maps/policymap" 38 "github.com/cilium/cilium/pkg/option" 39 "github.com/cilium/cilium/pkg/time" 40 wgTypes "github.com/cilium/cilium/pkg/wireguard/types" 41 ) 42 43 const ( 44 subsystem = "datapath-loader" 45 46 symbolFromEndpoint = "cil_from_container" 47 symbolToEndpoint = "cil_to_container" 48 symbolFromNetwork = "cil_from_network" 49 50 symbolFromHostNetdevEp = "cil_from_netdev" 51 symbolToHostNetdevEp = "cil_to_netdev" 52 symbolFromHostEp = "cil_from_host" 53 symbolToHostEp = "cil_to_host" 54 55 symbolToWireguard = "cil_to_wireguard" 56 57 symbolFromHostNetdevXDP = "cil_xdp_entry" 58 59 symbolFromOverlay = "cil_from_overlay" 60 symbolToOverlay = "cil_to_overlay" 61 62 dirIngress = "ingress" 63 dirEgress = "egress" 64 ) 65 66 const ( 67 secctxFromIpcacheDisabled = iota + 1 68 secctxFromIpcacheEnabled 69 ) 70 71 var log = logging.DefaultLogger.WithField(logfields.LogSubsys, subsystem) 72 73 // loader is a wrapper structure around operations related to compiling, 74 // loading, and reloading datapath programs. 75 type loader struct { 76 cfg Config 77 78 nodeConfig atomic.Pointer[datapath.LocalNodeConfiguration] 79 80 // templateCache is the cache of pre-compiled datapaths. Only set after 81 // a call to Reinitialize. 82 templateCache *objectCache 83 84 ipsecMu lock.Mutex // guards reinitializeIPSec 85 86 hostDpInitializedOnce sync.Once 87 hostDpInitialized chan struct{} 88 89 sysctl sysctl.Sysctl 90 prefilter datapath.PreFilter 91 compilationLock datapath.CompilationLock 92 configWriter datapath.ConfigWriter 93 nodeHandler datapath.NodeHandler 94 } 95 96 type Params struct { 97 cell.In 98 99 Config Config 100 Sysctl sysctl.Sysctl 101 Prefilter datapath.PreFilter 102 CompilationLock datapath.CompilationLock 103 ConfigWriter datapath.ConfigWriter 104 NodeHandler datapath.NodeHandler 105 } 106 107 // newLoader returns a new loader. 108 func newLoader(p Params) *loader { 109 return &loader{ 110 cfg: p.Config, 111 templateCache: newObjectCache(p.ConfigWriter, filepath.Join(option.Config.StateDir, defaults.TemplatesDir)), 112 sysctl: p.Sysctl, 113 hostDpInitialized: make(chan struct{}), 114 prefilter: p.Prefilter, 115 compilationLock: p.CompilationLock, 116 configWriter: p.ConfigWriter, 117 nodeHandler: p.NodeHandler, 118 } 119 } 120 121 func upsertEndpointRoute(ep datapath.Endpoint, ip net.IPNet) error { 122 endpointRoute := route.Route{ 123 Prefix: ip, 124 Device: ep.InterfaceName(), 125 Scope: netlink.SCOPE_LINK, 126 Proto: linux_defaults.RTProto, 127 } 128 129 return route.Upsert(endpointRoute) 130 } 131 132 func removeEndpointRoute(ep datapath.Endpoint, ip net.IPNet) error { 133 return route.Delete(route.Route{ 134 Prefix: ip, 135 Device: ep.InterfaceName(), 136 Scope: netlink.SCOPE_LINK, 137 }) 138 } 139 140 func (l *loader) bpfMasqAddrs(ifName string) (masq4, masq6 netip.Addr) { 141 if l.cfg.DeriveMasqIPAddrFromDevice != "" { 142 ifName = l.cfg.DeriveMasqIPAddrFromDevice 143 } 144 145 addrs := l.getNodeConfig().NodeAddresses 146 147 find := func(devName string) bool { 148 for _, addr := range addrs { 149 if addr.DeviceName != devName { 150 continue 151 } 152 if !addr.Primary { 153 continue 154 } 155 if addr.Addr.Is4() && !masq4.IsValid() { 156 masq4 = addr.Addr 157 } else if addr.Addr.Is6() && !masq6.IsValid() { 158 masq6 = addr.Addr 159 } 160 done := (!option.Config.EnableIPv4Masquerade || masq4.IsValid()) && 161 (!option.Config.EnableIPv6Masquerade || masq6.IsValid()) 162 if done { 163 return true 164 } 165 } 166 return false 167 } 168 169 // Try to find suitable masquerade address first from the given interface. 170 if !find(ifName) { 171 // No suitable masquerade addresses were found for this device. Try the fallback 172 // addresses. 173 find(tables.WildcardDeviceName) 174 } 175 176 return 177 } 178 179 // patchHostNetdevDatapath calculates the changes necessary 180 // to attach the host endpoint datapath to different interfaces. 181 func (l *loader) patchHostNetdevDatapath(ep datapath.Endpoint, ifName string) (map[string]uint64, map[string]string, error) { 182 opts := ELFVariableSubstitutions(ep) 183 strings := ELFMapSubstitutions(ep) 184 185 iface, err := netlink.LinkByName(ifName) 186 if err != nil { 187 return nil, nil, err 188 } 189 190 // The THIS_INTERFACE_MAC value is specific to each attachment interface. 191 mac := mac.MAC(iface.Attrs().HardwareAddr) 192 if mac == nil { 193 // L2-less device 194 mac = make([]byte, 6) 195 opts["ETH_HLEN"] = uint64(0) 196 } 197 opts["THIS_INTERFACE_MAC_1"] = uint64(sliceToBe32(mac[0:4])) 198 opts["THIS_INTERFACE_MAC_2"] = uint64(sliceToBe16(mac[4:6])) 199 200 ifIndex := uint32(iface.Attrs().Index) 201 202 if !option.Config.EnableHostLegacyRouting { 203 opts["SECCTX_FROM_IPCACHE"] = uint64(secctxFromIpcacheEnabled) 204 } else { 205 opts["SECCTX_FROM_IPCACHE"] = uint64(secctxFromIpcacheDisabled) 206 } 207 208 opts["NATIVE_DEV_IFINDEX"] = uint64(ifIndex) 209 210 if option.Config.EnableBPFMasquerade && ifName != defaults.SecondHostDevice { 211 ipv4, ipv6 := l.bpfMasqAddrs(ifName) 212 213 if option.Config.EnableIPv4Masquerade && ipv4.IsValid() { 214 opts["IPV4_MASQUERADE"] = uint64(byteorder.NetIPv4ToHost32(ipv4.AsSlice())) 215 } 216 if option.Config.EnableIPv6Masquerade && ipv6.IsValid() { 217 ipv6Bytes := ipv6.AsSlice() 218 opts["IPV6_MASQUERADE_1"] = sliceToBe64(ipv6Bytes[0:8]) 219 opts["IPV6_MASQUERADE_2"] = sliceToBe64(ipv6Bytes[8:16]) 220 } 221 } 222 223 callsMapHostDevice := bpf.LocalMapName(callsmap.HostMapName, templateLxcID) 224 strings[callsMapHostDevice] = bpf.LocalMapName(callsmap.NetdevMapName, uint16(ifIndex)) 225 226 return opts, strings, nil 227 } 228 229 func isObsoleteDev(dev string, devices []string) bool { 230 // exclude devices we never attach to/from_netdev to. 231 for _, prefix := range defaults.ExcludedDevicePrefixes { 232 if strings.HasPrefix(dev, prefix) { 233 return false 234 } 235 } 236 237 // exclude devices that will still be managed going forward. 238 for _, d := range devices { 239 if dev == d { 240 return false 241 } 242 } 243 244 return true 245 } 246 247 // removeObsoleteNetdevPrograms removes cil_to_netdev and cil_from_netdev from devices 248 // that cilium potentially doesn't manage anymore after a restart, e.g. if the set of 249 // devices changes between restarts. 250 // 251 // This code assumes that the agent was upgraded from a prior version while maintaining 252 // the same list of managed physical devices. This ensures that all tc bpf filters get 253 // replaced using the naming convention of the 'current' agent build. For example, 254 // before 1.13, most filters were named e.g. bpf_host.o:[to-host], to be changed to 255 // cilium-<device> in 1.13, then to cil_to_host-<device> in 1.14. As a result, this 256 // function only cleans up filters following the current naming scheme. 257 func removeObsoleteNetdevPrograms(devices []string) error { 258 links, err := netlink.LinkList() 259 if err != nil { 260 return fmt.Errorf("retrieving all netlink devices: %w", err) 261 } 262 263 // collect all devices that have netdev programs attached on either ingress or egress. 264 ingressDevs := []netlink.Link{} 265 egressDevs := []netlink.Link{} 266 for _, l := range links { 267 if !isObsoleteDev(l.Attrs().Name, devices) { 268 continue 269 } 270 271 // Remove the per-device bpffs directory containing pinned links and 272 // per-endpoint maps. 273 bpffsPath := bpffsDeviceDir(bpf.CiliumPath(), l) 274 if err := bpf.Remove(bpffsPath); err != nil { 275 log.WithError(err).WithField(logfields.Device, l.Attrs().Name) 276 } 277 278 ingressFilters, err := netlink.FilterList(l, directionToParent(dirIngress)) 279 if err != nil { 280 return fmt.Errorf("listing ingress filters: %w", err) 281 } 282 for _, filter := range ingressFilters { 283 if bpfFilter, ok := filter.(*netlink.BpfFilter); ok { 284 if strings.HasPrefix(bpfFilter.Name, symbolFromHostNetdevEp) { 285 ingressDevs = append(ingressDevs, l) 286 } 287 } 288 } 289 290 egressFilters, err := netlink.FilterList(l, directionToParent(dirEgress)) 291 if err != nil { 292 return fmt.Errorf("listing egress filters: %w", err) 293 } 294 for _, filter := range egressFilters { 295 if bpfFilter, ok := filter.(*netlink.BpfFilter); ok { 296 if strings.HasPrefix(bpfFilter.Name, symbolToHostNetdevEp) { 297 egressDevs = append(egressDevs, l) 298 } 299 } 300 } 301 } 302 303 for _, dev := range ingressDevs { 304 err = removeTCFilters(dev, directionToParent(dirIngress)) 305 if err != nil { 306 log.WithError(err).Errorf("couldn't remove ingress tc filters from %s", dev.Attrs().Name) 307 } 308 } 309 310 for _, dev := range egressDevs { 311 err = removeTCFilters(dev, directionToParent(dirEgress)) 312 if err != nil { 313 log.WithError(err).Errorf("couldn't remove egress tc filters from %s", dev.Attrs().Name) 314 } 315 } 316 317 return nil 318 } 319 320 // reloadHostDatapath (re)attaches programs from bpf_host.c to: 321 // - cilium_host: cil_to_host ingress and cil_from_host to egress 322 // - cilium_net: cil_to_host to ingress 323 // - native devices: cil_from_netdev to ingress and (optionally) cil_to_netdev to egress if certain features require it 324 func (l *loader) reloadHostDatapath(ep datapath.Endpoint, spec *ebpf.CollectionSpec, devices []string) error { 325 // Replace programs on cilium_host. 326 host, err := netlink.LinkByName(ep.InterfaceName()) 327 if err != nil { 328 return fmt.Errorf("retrieving device %s: %w", ep.InterfaceName(), err) 329 } 330 331 coll, commit, err := loadDatapath(spec, ELFMapSubstitutions(ep), ELFVariableSubstitutions(ep)) 332 if err != nil { 333 return err 334 } 335 defer coll.Close() 336 337 // Attach cil_to_host to cilium_host ingress. 338 if err := attachSKBProgram(host, coll.Programs[symbolToHostEp], symbolToHostEp, 339 bpffsDeviceLinksDir(bpf.CiliumPath(), host), netlink.HANDLE_MIN_INGRESS, option.Config.EnableTCX); err != nil { 340 return fmt.Errorf("interface %s ingress: %w", ep.InterfaceName(), err) 341 } 342 // Attach cil_from_host to cilium_host egress. 343 if err := attachSKBProgram(host, coll.Programs[symbolFromHostEp], symbolFromHostEp, 344 bpffsDeviceLinksDir(bpf.CiliumPath(), host), netlink.HANDLE_MIN_EGRESS, option.Config.EnableTCX); err != nil { 345 return fmt.Errorf("interface %s egress: %w", ep.InterfaceName(), err) 346 } 347 348 if err := commit(); err != nil { 349 return fmt.Errorf("committing bpf pins: %w", err) 350 } 351 352 // Replace program on cilium_net. 353 net, err := netlink.LinkByName(defaults.SecondHostDevice) 354 if err != nil { 355 return fmt.Errorf("retrieving device %s: %w", defaults.SecondHostDevice, err) 356 } 357 358 secondConsts, secondRenames, err := l.patchHostNetdevDatapath(ep, defaults.SecondHostDevice) 359 if err != nil { 360 return err 361 } 362 363 coll, commit, err = loadDatapath(spec, secondRenames, secondConsts) 364 if err != nil { 365 return err 366 } 367 defer coll.Close() 368 369 // Attach cil_to_host to cilium_net. 370 if err := attachSKBProgram(net, coll.Programs[symbolToHostEp], symbolToHostEp, 371 bpffsDeviceLinksDir(bpf.CiliumPath(), net), netlink.HANDLE_MIN_INGRESS, option.Config.EnableTCX); err != nil { 372 return fmt.Errorf("interface %s ingress: %w", defaults.SecondHostDevice, err) 373 } 374 375 if err := commit(); err != nil { 376 return fmt.Errorf("committing bpf pins: %w", err) 377 } 378 379 // Replace programs on physical devices, ignoring devices that don't exist. 380 for _, device := range devices { 381 iface, err := netlink.LinkByName(device) 382 if err != nil { 383 log.WithError(err).WithField("device", device).Warn("Link does not exist") 384 continue 385 } 386 387 linkDir := bpffsDeviceLinksDir(bpf.CiliumPath(), iface) 388 389 netdevConsts, netdevRenames, err := l.patchHostNetdevDatapath(ep, device) 390 if err != nil { 391 return err 392 } 393 394 coll, commit, err := loadDatapath(spec, netdevRenames, netdevConsts) 395 if err != nil { 396 return err 397 } 398 defer coll.Close() 399 400 // Attach cil_from_netdev to ingress. 401 if err := attachSKBProgram(iface, coll.Programs[symbolFromHostNetdevEp], symbolFromHostNetdevEp, 402 linkDir, netlink.HANDLE_MIN_INGRESS, option.Config.EnableTCX); err != nil { 403 return fmt.Errorf("interface %s ingress: %w", device, err) 404 } 405 406 if option.Config.AreDevicesRequired() { 407 // Attaching bpf_host to cilium_wg0 is required for encrypting KPR 408 // traffic. Only ingress prog (aka "from-netdev") is needed to handle 409 // the rev-NAT xlations. 410 if device != wgTypes.IfaceName { 411 // Attach cil_to_netdev to egress. 412 if err := attachSKBProgram(iface, coll.Programs[symbolToHostNetdevEp], symbolToHostNetdevEp, 413 linkDir, netlink.HANDLE_MIN_EGRESS, option.Config.EnableTCX); err != nil { 414 return fmt.Errorf("interface %s egress: %w", device, err) 415 } 416 } 417 } else { 418 // Remove any previously attached device from egress path if BPF 419 // NodePort and host firewall are disabled. 420 if err := detachSKBProgram(iface, symbolToHostNetdevEp, linkDir, netlink.HANDLE_MIN_EGRESS); err != nil { 421 log.WithField("device", device).Error(err) 422 } 423 } 424 425 if err := commit(); err != nil { 426 return fmt.Errorf("committing bpf pins: %w", err) 427 } 428 } 429 430 // call at the end of the function so that we can easily detect if this removes necessary 431 // programs that have just been attached. 432 if err := removeObsoleteNetdevPrograms(devices); err != nil { 433 log.WithError(err).Error("Failed to remove obsolete netdev programs") 434 } 435 436 l.hostDpInitializedOnce.Do(func() { 437 log.Debug("Initialized host datapath") 438 close(l.hostDpInitialized) 439 }) 440 441 return nil 442 } 443 444 // reloadDatapath loads programs in spec into the device used by ep. 445 // 446 // spec is modified by the method and it is the callers responsibility to copy 447 // it if necessary. 448 func (l *loader) reloadDatapath(ep datapath.Endpoint, spec *ebpf.CollectionSpec) error { 449 device := ep.InterfaceName() 450 nodeConfig := l.getNodeConfig() 451 452 // Replace all occurrences of the template endpoint ID with the real ID. 453 for _, name := range []string{ 454 policymap.PolicyCallMapName, 455 policymap.PolicyEgressCallMapName, 456 } { 457 pm, ok := spec.Maps[name] 458 if !ok { 459 continue 460 } 461 462 for i, kv := range pm.Contents { 463 if kv.Key == (uint32)(templateLxcID) { 464 pm.Contents[i].Key = (uint32)(ep.GetID()) 465 } 466 } 467 } 468 469 if ep.IsHost() { 470 devices := nodeConfig.DeviceNames() 471 472 if option.Config.NeedBPFHostOnWireGuardDevice() { 473 devices = append(devices, wgTypes.IfaceName) 474 } 475 476 if err := l.reloadHostDatapath(ep, spec, devices); err != nil { 477 return err 478 } 479 } else { 480 coll, commit, err := loadDatapath(spec, ELFMapSubstitutions(ep), ELFVariableSubstitutions(ep)) 481 if err != nil { 482 return err 483 } 484 defer coll.Close() 485 486 iface, err := netlink.LinkByName(device) 487 if err != nil { 488 return fmt.Errorf("retrieving device %s: %w", device, err) 489 } 490 491 linkDir := bpffsEndpointLinksDir(bpf.CiliumPath(), ep) 492 if err := attachSKBProgram(iface, coll.Programs[symbolFromEndpoint], symbolFromEndpoint, 493 linkDir, netlink.HANDLE_MIN_INGRESS, option.Config.EnableTCX); err != nil { 494 return fmt.Errorf("interface %s ingress: %w", device, err) 495 } 496 497 if ep.RequireEgressProg() { 498 if err := attachSKBProgram(iface, coll.Programs[symbolToEndpoint], symbolToEndpoint, 499 linkDir, netlink.HANDLE_MIN_EGRESS, option.Config.EnableTCX); err != nil { 500 return fmt.Errorf("interface %s egress: %w", device, err) 501 } 502 } else { 503 if err := detachSKBProgram(iface, symbolToEndpoint, linkDir, netlink.HANDLE_MIN_EGRESS); err != nil { 504 log.WithField("device", device).Error(err) 505 } 506 } 507 508 if err := commit(); err != nil { 509 return fmt.Errorf("committing bpf pins: %w", err) 510 } 511 } 512 513 if ep.RequireEndpointRoute() { 514 scopedLog := ep.Logger(subsystem).WithFields(logrus.Fields{ 515 logfields.Interface: device, 516 }) 517 if ip := ep.IPv4Address(); ip.IsValid() { 518 if err := upsertEndpointRoute(ep, *iputil.AddrToIPNet(ip)); err != nil { 519 scopedLog.WithError(err).Warn("Failed to upsert route") 520 } 521 } 522 if ip := ep.IPv6Address(); ip.IsValid() { 523 if err := upsertEndpointRoute(ep, *iputil.AddrToIPNet(ip)); err != nil { 524 scopedLog.WithError(err).Warn("Failed to upsert route") 525 } 526 } 527 } 528 529 return nil 530 } 531 532 func (l *loader) replaceOverlayDatapath(ctx context.Context, cArgs []string, iface string) error { 533 if err := compileOverlay(ctx, cArgs); err != nil { 534 return fmt.Errorf("compiling overlay program: %w", err) 535 } 536 537 device, err := netlink.LinkByName(iface) 538 if err != nil { 539 return fmt.Errorf("retrieving device %s: %w", iface, err) 540 } 541 542 spec, err := bpf.LoadCollectionSpec(overlayObj) 543 if err != nil { 544 return fmt.Errorf("loading eBPF ELF %s: %w", overlayObj, err) 545 } 546 547 coll, commit, err := loadDatapath(spec, nil, nil) 548 if err != nil { 549 return err 550 } 551 defer coll.Close() 552 553 linkDir := bpffsDeviceLinksDir(bpf.CiliumPath(), device) 554 if err := attachSKBProgram(device, coll.Programs[symbolFromOverlay], symbolFromOverlay, 555 linkDir, netlink.HANDLE_MIN_INGRESS, option.Config.EnableTCX); err != nil { 556 return fmt.Errorf("interface %s ingress: %w", device, err) 557 } 558 if err := attachSKBProgram(device, coll.Programs[symbolToOverlay], symbolToOverlay, 559 linkDir, netlink.HANDLE_MIN_EGRESS, option.Config.EnableTCX); err != nil { 560 return fmt.Errorf("interface %s egress: %w", device, err) 561 } 562 563 if err := commit(); err != nil { 564 return fmt.Errorf("committing bpf pins: %w", err) 565 } 566 567 return nil 568 } 569 570 func (l *loader) replaceWireguardDatapath(ctx context.Context, cArgs []string, iface string) (err error) { 571 if err := compileWireguard(ctx, cArgs); err != nil { 572 return fmt.Errorf("compiling wireguard program: %w", err) 573 } 574 device, err := netlink.LinkByName(iface) 575 if err != nil { 576 return fmt.Errorf("retrieving device %s: %w", iface, err) 577 } 578 579 spec, err := bpf.LoadCollectionSpec(wireguardObj) 580 if err != nil { 581 return fmt.Errorf("loading eBPF ELF %s: %w", wireguardObj, err) 582 } 583 584 coll, commit, err := loadDatapath(spec, nil, nil) 585 if err != nil { 586 return err 587 } 588 defer coll.Close() 589 590 linkDir := bpffsDeviceLinksDir(bpf.CiliumPath(), device) 591 if err := attachSKBProgram(device, coll.Programs[symbolToWireguard], symbolToWireguard, 592 linkDir, netlink.HANDLE_MIN_EGRESS, option.Config.EnableTCX); err != nil { 593 return fmt.Errorf("interface %s egress: %w", device, err) 594 } 595 if err := commit(); err != nil { 596 return fmt.Errorf("committing bpf pins: %w", err) 597 } 598 return nil 599 } 600 601 // ReloadDatapath reloads the BPF datapath programs for the specified endpoint. 602 // 603 // It attempts to find a pre-compiled 604 // template datapath object to use, to avoid a costly compile operation. 605 // Only if there is no existing template that has the same configuration 606 // parameters as the specified endpoint, this function will compile a new 607 // template for this configuration. 608 // 609 // This function will block if the cache does not contain an entry for the 610 // same EndpointConfiguration and multiple goroutines attempt to concurrently 611 // CompileOrLoad with the same configuration parameters. When the first 612 // goroutine completes compilation of the template, all other CompileOrLoad 613 // invocations will be released. 614 func (l *loader) ReloadDatapath(ctx context.Context, ep datapath.Endpoint, stats *metrics.SpanStat) (hash string, err error) { 615 dirs := directoryInfo{ 616 Library: option.Config.BpfDir, 617 Runtime: option.Config.StateDir, 618 State: ep.StateDir(), 619 Output: ep.StateDir(), 620 } 621 622 cfg := l.getNodeConfig() 623 624 spec, hash, err := l.templateCache.fetchOrCompile(ctx, cfg, ep, &dirs, stats) 625 if err != nil { 626 return "", err 627 } 628 629 stats.BpfLoadProg.Start() 630 err = l.reloadDatapath(ep, spec) 631 stats.BpfLoadProg.End(err == nil) 632 return hash, err 633 } 634 635 // Unload removes the datapath specific program aspects 636 func (l *loader) Unload(ep datapath.Endpoint) { 637 if ep.RequireEndpointRoute() { 638 if ip := ep.IPv4Address(); ip.IsValid() { 639 removeEndpointRoute(ep, *iputil.AddrToIPNet(ip)) 640 } 641 642 if ip := ep.IPv6Address(); ip.IsValid() { 643 removeEndpointRoute(ep, *iputil.AddrToIPNet(ip)) 644 } 645 } 646 647 log := log.WithField(logfields.EndpointID, ep.StringID()) 648 649 // Remove legacy tc attachments. 650 link, err := netlink.LinkByName(ep.InterfaceName()) 651 if err == nil { 652 if err := removeTCFilters(link, netlink.HANDLE_MIN_INGRESS); err != nil { 653 log.WithError(err).Errorf("Removing ingress filter from interface %s", ep.InterfaceName()) 654 } 655 if err := removeTCFilters(link, netlink.HANDLE_MIN_EGRESS); err != nil { 656 log.WithError(err).Errorf("Removing egress filter from interface %s", ep.InterfaceName()) 657 } 658 } 659 660 // If Cilium and the kernel support tcx to attach TC programs to the 661 // endpoint's veth device, its bpf_link object is pinned to a per-endpoint 662 // bpffs directory. When the endpoint gets deleted, we can remove the whole 663 // directory to clean up any leftover pinned links and maps. 664 665 // Remove the links directory first to avoid removing program arrays before 666 // the entrypoints are detached. 667 if err := bpf.Remove(bpffsEndpointLinksDir(bpf.CiliumPath(), ep)); err != nil { 668 log.WithError(err) 669 } 670 // Finally, remove the endpoint's top-level directory. 671 if err := bpf.Remove(bpffsEndpointDir(bpf.CiliumPath(), ep)); err != nil { 672 log.WithError(err) 673 } 674 } 675 676 // EndpointHash hashes the specified endpoint configuration with the current 677 // datapath hash cache and returns the hash as string. 678 func (l *loader) EndpointHash(cfg datapath.EndpointConfiguration) (string, error) { 679 return l.templateCache.baseHash.hashEndpoint(l.templateCache, l.getNodeConfig(), cfg) 680 } 681 682 // CallsMapPath gets the BPF Calls Map for the endpoint with the specified ID. 683 func (l *loader) CallsMapPath(id uint16) string { 684 return bpf.LocalMapPath(callsmap.MapName, id) 685 } 686 687 // CustomCallsMapPath gets the BPF Custom Calls Map for the endpoint with the 688 // specified ID. 689 func (l *loader) CustomCallsMapPath(id uint16) string { 690 return bpf.LocalMapPath(callsmap.CustomCallsMapName, id) 691 } 692 693 // HostDatapathInitialized returns a channel which is closed when the 694 // host datapath has been loaded for the first time. 695 func (l *loader) HostDatapathInitialized() <-chan struct{} { 696 return l.hostDpInitialized 697 } 698 699 func (l *loader) WriteEndpointConfig(w io.Writer, e datapath.EndpointConfiguration) error { 700 return l.configWriter.WriteEndpointConfig(w, l.getNodeConfig(), e) 701 } 702 703 func (l *loader) getNodeConfig() *datapath.LocalNodeConfiguration { 704 const retryInterval = 100 * time.Millisecond 705 const numRetries = 5 * (time.Minute / retryInterval) 706 for range numRetries { 707 cfg := l.nodeConfig.Load() 708 if cfg != nil { 709 return cfg 710 } 711 // LocalNodeConfiguration is set when Reinitialize() is called the first time 712 // with the initial configuration. This may take some time as it's derived 713 // from e.g. Kubernetes Node object. Since there are multiple call sites 714 // towards the loader, we wait here for the initialization to finish to 715 // deal with calls into the loader that occur prior to Reinitialize(). 716 time.Sleep(retryInterval) 717 } 718 // As a fallback proceed with an empty configuration. As this is not really useful 719 // nor expected log this as an error. If we reach this we may end regenerating endpoints 720 // with incomplete information. 721 log.Error("Failed to load node configuration in time. Proceeding with empty config.") 722 return &datapath.LocalNodeConfiguration{} 723 }