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