github.com/cilium/cilium@v1.16.2/pkg/datapath/loader/xdp.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package loader 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 14 "github.com/cilium/ebpf" 15 "github.com/cilium/ebpf/link" 16 "github.com/vishvananda/netlink" 17 "golang.org/x/sys/unix" 18 19 "github.com/cilium/cilium/pkg/bpf" 20 "github.com/cilium/cilium/pkg/identity" 21 "github.com/cilium/cilium/pkg/mac" 22 "github.com/cilium/cilium/pkg/option" 23 ) 24 25 func xdpConfigModeToFlag(xdpMode string) link.XDPAttachFlags { 26 switch xdpMode { 27 case option.XDPModeNative, option.XDPModeLinkDriver, option.XDPModeBestEffort: 28 return link.XDPDriverMode 29 case option.XDPModeGeneric, option.XDPModeLinkGeneric: 30 return link.XDPGenericMode 31 } 32 return 0 33 } 34 35 // These constant values are returned by the kernel when querying the XDP program attach mode. 36 // Important: they differ from constants that are used when attaching an XDP program to a netlink device. 37 const ( 38 xdpAttachedNone uint32 = iota 39 xdpAttachedDriver 40 xdpAttachedGeneric 41 ) 42 43 // xdpAttachedModeToFlag maps the attach mode that is returned in the metadata when 44 // querying netlink devices to the attach flags that were used to configure the 45 // xdp program attachement. 46 func xdpAttachedModeToFlag(mode uint32) link.XDPAttachFlags { 47 switch mode { 48 case xdpAttachedDriver: 49 return link.XDPDriverMode 50 case xdpAttachedGeneric: 51 return link.XDPGenericMode 52 } 53 return 0 54 } 55 56 // maybeUnloadObsoleteXDPPrograms removes bpf_xdp.o from previously used 57 // devices. 58 // 59 // bpffsBase is typically set to /sys/fs/bpf/cilium, but can be a temp directory 60 // during tests. 61 func (l *loader) maybeUnloadObsoleteXDPPrograms(xdpDevs []string, xdpMode, bpffsBase string) { 62 links, err := netlink.LinkList() 63 if err != nil { 64 log.WithError(err).Warn("Failed to list links for XDP unload") 65 } 66 67 for _, link := range links { 68 linkxdp := link.Attrs().Xdp 69 if linkxdp == nil || !linkxdp.Attached { 70 // No XDP program is attached 71 continue 72 } 73 if strings.Contains(link.Attrs().Name, "cilium") { 74 // Ignore devices created by cilium-agent 75 continue 76 } 77 78 used := false 79 for _, xdpDev := range xdpDevs { 80 if link.Attrs().Name == xdpDev && 81 xdpAttachedModeToFlag(linkxdp.AttachMode) == xdpConfigModeToFlag(xdpMode) { 82 // XDP mode matches; don't unload, otherwise we might introduce 83 // intermittent connectivity problems 84 used = true 85 break 86 } 87 } 88 if !used { 89 if err := l.DetachXDP(link.Attrs().Name, bpffsBase, symbolFromHostNetdevXDP); err != nil { 90 log.WithError(err).Warn("Failed to detach obsolete XDP program") 91 } 92 } 93 } 94 } 95 96 // xdpCompileArgs derives compile arguments for bpf_xdp.c. 97 func xdpCompileArgs(xdpDev string, extraCArgs []string) ([]string, error) { 98 link, err := netlink.LinkByName(xdpDev) 99 if err != nil { 100 return nil, err 101 } 102 103 args := []string{ 104 fmt.Sprintf("-DSECLABEL=%d", identity.ReservedIdentityWorld), 105 fmt.Sprintf("-DTHIS_INTERFACE_MAC={.addr=%s}", mac.CArrayString(link.Attrs().HardwareAddr)), 106 fmt.Sprintf("-DCALLS_MAP=cilium_calls_xdp_%d", link.Attrs().Index), 107 } 108 args = append(args, extraCArgs...) 109 if option.Config.EnableNodePort { 110 args = append(args, []string{ 111 fmt.Sprintf("-DTHIS_MTU=%d", link.Attrs().MTU), 112 fmt.Sprintf("-DNATIVE_DEV_IFINDEX=%d", link.Attrs().Index), 113 "-DDISABLE_LOOPBACK_LB", 114 }...) 115 } 116 if option.Config.IsDualStack() { 117 args = append(args, fmt.Sprintf("-DSECLABEL_IPV4=%d", identity.ReservedIdentityWorldIPv4)) 118 args = append(args, fmt.Sprintf("-DSECLABEL_IPV6=%d", identity.ReservedIdentityWorldIPv6)) 119 } else { 120 args = append(args, fmt.Sprintf("-DSECLABEL_IPV4=%d", identity.ReservedIdentityWorld)) 121 args = append(args, fmt.Sprintf("-DSECLABEL_IPV6=%d", identity.ReservedIdentityWorld)) 122 } 123 124 return args, nil 125 } 126 127 // compileAndLoadXDPProg compiles bpf_xdp.c for the given XDP device and loads it. 128 func compileAndLoadXDPProg(ctx context.Context, xdpDev, xdpMode string, extraCArgs []string) error { 129 args, err := xdpCompileArgs(xdpDev, extraCArgs) 130 if err != nil { 131 return fmt.Errorf("failed to derive XDP compile extra args: %w", err) 132 } 133 134 dirs := &directoryInfo{ 135 Library: option.Config.BpfDir, 136 Runtime: option.Config.StateDir, 137 Output: option.Config.StateDir, 138 State: option.Config.StateDir, 139 } 140 prog := &progInfo{ 141 Source: xdpProg, 142 Output: xdpObj, 143 OutputType: outputObject, 144 Options: args, 145 } 146 147 objPath, err := compile(ctx, prog, dirs) 148 if err != nil { 149 return err 150 } 151 if err := ctx.Err(); err != nil { 152 return err 153 } 154 155 iface, err := netlink.LinkByName(xdpDev) 156 if err != nil { 157 return fmt.Errorf("retrieving device %s: %w", xdpDev, err) 158 } 159 160 spec, err := bpf.LoadCollectionSpec(objPath) 161 if err != nil { 162 return fmt.Errorf("loading eBPF ELF %s: %w", objPath, err) 163 } 164 165 coll, commit, err := loadDatapath(spec, nil, nil) 166 if err != nil { 167 return err 168 } 169 defer coll.Close() 170 171 if err := attachXDPProgram(iface, coll.Programs[symbolFromHostNetdevXDP], symbolFromHostNetdevXDP, 172 bpffsDeviceLinksDir(bpf.CiliumPath(), iface), xdpConfigModeToFlag(xdpMode)); err != nil { 173 return fmt.Errorf("interface %s: %w", xdpDev, err) 174 } 175 176 if err := commit(); err != nil { 177 return fmt.Errorf("committing bpf pins: %w", err) 178 } 179 180 return nil 181 } 182 183 // attachXDPProgram attaches prog with the given progName to link. 184 // 185 // bpffsDir should exist and point to the links/ subdirectory in the per-device 186 // bpffs directory. 187 func attachXDPProgram(iface netlink.Link, prog *ebpf.Program, progName, bpffsDir string, flags link.XDPAttachFlags) error { 188 if prog == nil { 189 return fmt.Errorf("program %s is nil", progName) 190 } 191 192 // Attempt to open and update an existing link. 193 pin := filepath.Join(bpffsDir, progName) 194 err := bpf.UpdateLink(pin, prog) 195 switch { 196 // Update successful, nothing left to do. 197 case err == nil: 198 log.Infof("Updated link %s for program %s", pin, progName) 199 200 return nil 201 202 // Link exists, but is defunct, and needs to be recreated. The program 203 // no longer gets triggered at this point and the link needs to be removed 204 // to proceed. 205 case errors.Is(err, unix.ENOLINK): 206 if err := os.Remove(pin); err != nil { 207 return fmt.Errorf("unpinning defunct link %s: %w", pin, err) 208 } 209 210 log.Infof("Unpinned defunct link %s for program %s", pin, progName) 211 212 // No existing link found, continue trying to create one. 213 case errors.Is(err, os.ErrNotExist): 214 log.Infof("No existing link found at %s for program %s", pin, progName) 215 216 default: 217 return fmt.Errorf("updating link %s for program %s: %w", pin, progName, err) 218 } 219 220 if err := bpf.MkdirBPF(bpffsDir); err != nil { 221 return fmt.Errorf("creating bpffs link dir for xdp attachment to device %s: %w", iface.Attrs().Name, err) 222 } 223 224 // Create a new link. This will only succeed on nodes that support bpf_link 225 // and don't have any XDP programs attached through netlink. 226 l, err := link.AttachXDP(link.XDPOptions{ 227 Program: prog, 228 Interface: iface.Attrs().Index, 229 Flags: flags, 230 }) 231 if err == nil { 232 defer func() { 233 // The program was successfully attached using bpf_link. Closing a link 234 // does not detach the program if the link is pinned. 235 if err := l.Close(); err != nil { 236 log.Warnf("Failed to close bpf_link for program %s", progName) 237 } 238 }() 239 240 if err := l.Pin(pin); err != nil { 241 return fmt.Errorf("pinning link at %s for program %s : %w", pin, progName, err) 242 } 243 244 // Successfully created and pinned bpf_link. 245 log.Infof("Program %s attached using bpf_link", progName) 246 247 return nil 248 } 249 250 // Kernels before 5.7 don't support bpf_link. In that case link.AttachXDP 251 // returns ErrNotSupported. 252 // 253 // If the kernel supports bpf_link, but an older version of Cilium attached a 254 // XDP program, link.AttachXDP will return EBUSY. 255 if !errors.Is(err, unix.EBUSY) && !errors.Is(err, link.ErrNotSupported) { 256 // Unrecoverable error from AttachRawLink. 257 return fmt.Errorf("attaching program %s using bpf_link: %w", progName, err) 258 } 259 260 log.Debugf("Performing netlink attach for program %s", progName) 261 262 // Omitting XDP_FLAGS_UPDATE_IF_NOEXIST equals running 'ip' with -force, 263 // and will clobber any existing XDP attachment to the interface, including 264 // bpf_link attachments created by a different process. 265 if err := netlink.LinkSetXdpFdWithFlags(iface, prog.FD(), int(flags)); err != nil { 266 return fmt.Errorf("attaching XDP program %s to interface %s using netlink: %w", progName, iface.Attrs().Name, err) 267 } 268 269 // Nothing left to do, the netlink device now holds a reference to the prog 270 // the program stays active. 271 log.Infof("Program %s was attached using netlink", progName) 272 273 return nil 274 } 275 276 // DetachXDP removes an XDP program from a network interface. On kernels before 277 // 4.15, always removes the XDP program regardless of progName. 278 // 279 // bpffsBase is typically /sys/fs/bpf/cilium, but can be overridden to a tempdir 280 // during tests. 281 func (l *loader) DetachXDP(ifaceName string, bpffsBase, progName string) error { 282 iface, err := netlink.LinkByName(ifaceName) 283 if err != nil { 284 return fmt.Errorf("getting link '%s' by name: %w", ifaceName, err) 285 } 286 287 pin := filepath.Join(bpffsDeviceLinksDir(bpffsBase, iface), progName) 288 err = bpf.UnpinLink(pin) 289 if err == nil { 290 return nil 291 } 292 if !errors.Is(err, os.ErrNotExist) { 293 // The pinned link exists, something went wrong unpinning it. 294 return fmt.Errorf("unpinning XDP program using bpf_link: %w", err) 295 } 296 297 xdp := iface.Attrs().Xdp 298 if xdp == nil || !xdp.Attached { 299 return nil 300 } 301 302 // Inspect the attached program to only remove the intended XDP program. 303 id := xdp.ProgId 304 prog, err := ebpf.NewProgramFromID(ebpf.ProgramID(id)) 305 if err != nil { 306 return fmt.Errorf("opening XDP program id %d: %w", id, err) 307 } 308 info, err := prog.Info() 309 if err != nil { 310 return fmt.Errorf("getting XDP program info %d: %w", id, err) 311 } 312 // The program name returned by BPF_PROG_INFO is limited to 20 characters. 313 // Treat the kernel-provided program name as a prefix that needs to match 314 // against progName. Empty program names (on kernels before 4.15) will always 315 // match and be removed. 316 if !strings.HasPrefix(progName, info.Name) { 317 return nil 318 } 319 320 // Pin doesn't exist, fall through to detaching using netlink. 321 if err := netlink.LinkSetXdpFdWithFlags(iface, -1, int(link.XDPGenericMode)); err != nil { 322 return fmt.Errorf("detaching generic-mode XDP program using netlink: %w", err) 323 } 324 325 if err := netlink.LinkSetXdpFdWithFlags(iface, -1, int(link.XDPDriverMode)); err != nil { 326 return fmt.Errorf("detaching driver-mode XDP program using netlink: %w", err) 327 } 328 329 return nil 330 }