github.com/cilium/cilium@v1.16.2/pkg/datapath/loader/tcx.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package loader 5 6 import ( 7 "errors" 8 "fmt" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "github.com/vishvananda/netlink" 14 "golang.org/x/sys/unix" 15 16 "github.com/cilium/ebpf" 17 "github.com/cilium/ebpf/link" 18 19 "github.com/cilium/cilium/pkg/bpf" 20 ) 21 22 func parentToAttachType(parent uint32) ebpf.AttachType { 23 switch parent { 24 case netlink.HANDLE_MIN_INGRESS: 25 return ebpf.AttachTCXIngress 26 case netlink.HANDLE_MIN_EGRESS: 27 return ebpf.AttachTCXEgress 28 } 29 panic(fmt.Sprintf("invalid tc direction: %d", parent)) 30 } 31 32 // upsertTCXProgram updates or creates a new tcx attachment for prog to device. 33 // Returns [link.ErrNotSupported] if tcx is not supported on the node. 34 func upsertTCXProgram(device netlink.Link, prog *ebpf.Program, progName, bpffsDir string, parent uint32) error { 35 err := updateTCX(prog, progName, bpffsDir) 36 if err == nil { 37 // Link was updated, nothing left to do. 38 return nil 39 } 40 if !errors.Is(err, os.ErrNotExist) { 41 // Unrecoverable error, surface to the caller. 42 return fmt.Errorf("updating tcx program: %w", err) 43 } 44 45 return attachTCX(device, prog, progName, bpffsDir, parentToAttachType(parent)) 46 } 47 48 // attachTCX creates a new tcx attachment for prog to device at the given attach 49 // type. It pins the resulting link object to progName in bpffsDir. 50 // 51 // progName is typically the Program's key in CollectionSpec.Programs. 52 func attachTCX(device netlink.Link, prog *ebpf.Program, progName, bpffsDir string, attach ebpf.AttachType) error { 53 if err := bpf.MkdirBPF(bpffsDir); err != nil { 54 return fmt.Errorf("creating bpffs link dir for tcx attachment to device %s: %w", device.Attrs().Name, err) 55 } 56 57 l, err := link.AttachTCX(link.TCXOptions{ 58 Program: prog, 59 Attach: attach, 60 Interface: device.Attrs().Index, 61 Anchor: link.Tail(), 62 }) 63 if err != nil { 64 return fmt.Errorf("attaching tcx: %w", err) 65 } 66 67 defer func() { 68 // The program was successfully attached using tcx. Closing a link does not 69 // detach the program if the link is pinned. 70 if err := l.Close(); err != nil { 71 log.Warnf("Failed to close tcx link for program %s", progName) 72 } 73 }() 74 75 pin := filepath.Join(bpffsDir, progName) 76 if err := l.Pin(pin); err != nil { 77 return fmt.Errorf("pinning link at %s for program %s : %w", pin, progName, err) 78 } 79 80 log.Infof("Program %s attached to device %s using tcx", progName, device.Attrs().Name) 81 82 return nil 83 } 84 85 // updateTCX attempts to update an existing tcx link called progName in 86 // bpffsDir. If the link is defunct, the pin is removed. 87 // 88 // Returns nil if the update was successful. Returns an error wrapping 89 // [os.ErrNotExist] if the link is defunct or missing. 90 func updateTCX(prog *ebpf.Program, progName, bpffsDir string) error { 91 // Attempt to open and update an existing link. 92 pin := filepath.Join(bpffsDir, progName) 93 err := bpf.UpdateLink(pin, prog) 94 switch { 95 // Link exists, but is defunct, and needs to be recreated. The program 96 // no longer gets triggered at this point and the link needs to be removed 97 // to proceed. 98 case errors.Is(err, unix.ENOLINK): 99 if err := os.Remove(pin); err != nil { 100 return fmt.Errorf("unpinning defunct link %s: %w", pin, err) 101 } 102 103 log.Infof("Unpinned defunct link %s for program %s", pin, progName) 104 105 // Wrap in os.ErrNotExist so the caller needs to look for one error. 106 return fmt.Errorf("unpinned defunct link: %w", os.ErrNotExist) 107 108 // No existing link found, continue trying to create one. 109 case errors.Is(err, os.ErrNotExist): 110 log.Debugf("No existing link found at %s for program %s", pin, progName) 111 return err 112 113 case err != nil: 114 return fmt.Errorf("updating link %s for program %s: %w", pin, progName, err) 115 } 116 117 log.Infof("Updated link %s for program %s", pin, progName) 118 return nil 119 } 120 121 // hasCiliumTCXLinks returns true if device has a Cilium-managed tcx program 122 // with the given attach type. 123 func hasCiliumTCXLinks(device netlink.Link, attach ebpf.AttachType) (bool, error) { 124 result, err := link.QueryPrograms(link.QueryOptions{ 125 Target: int(device.Attrs().Index), 126 Attach: attach, 127 }) 128 if errors.Is(err, unix.EINVAL) { 129 // Attach type likely not supported, kernel doesn't support tcx. 130 return false, nil 131 } 132 if err != nil { 133 return false, fmt.Errorf("querying %s tcx programs for device %s: %w", attach, device.Attrs().Name, err) 134 } 135 if result == nil || len(result.Programs) == 0 { 136 return false, nil 137 } 138 139 for _, p := range result.Programs { 140 prog, err := ebpf.NewProgramFromID(p.ID) 141 if err != nil { 142 return false, fmt.Errorf("opening program with id %d: %w", p.ID, err) 143 } 144 defer prog.Close() 145 146 pi, err := prog.Info() 147 if err != nil { 148 continue 149 } 150 if strings.HasPrefix(pi.Name, "cil_") { 151 return true, nil 152 } 153 } 154 155 return false, nil 156 }