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  }