Claude-skill-registry ebpf-attach-hook
Implement eBPF program attachment logic for various hooks (XDP, TC/tcx, netkit, kprobe, tracepoint, cgroup) with proper error handling, cleanup, and link management. Includes Go code using cilium/ebpf library. Use when attaching CNF programs to kernel hooks.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/ebpf-attach-hook" ~/.claude/skills/majiayu000-claude-skill-registry-ebpf-attach-hook && rm -rf "$T"
manifest:
skills/data/ebpf-attach-hook/SKILL.mdsource content
eBPF Attach Hook Skill
This skill generates Go code for attaching eBPF programs to various kernel hooks in CNF applications.
What This Skill Does
Generates code for:
- Attaching to XDP (eXpress Data Path)
- Attaching to TC/tcx (Traffic Control)
- Attaching to netkit devices
- Attaching to kprobes/kretprobes
- Attaching to tracepoints
- Attaching to cgroup hooks
- Proper cleanup and error handling
- Link management with defer patterns
When to Use
- Attaching CNF programs to network interfaces
- Hooking into kernel functions for tracing
- Setting up packet processing pipelines
- Implementing network policies
- Creating observability tools
- Building traffic control CNFs
Supported Hook Types
Network Hooks
- XDP: Earliest packet processing (driver level)
- TC/tcx: Traffic control (ingress/egress)
- netkit: BPF-programmable network device (primary/peer)
Tracing Hooks
- kprobe: Kernel function entry
- kretprobe: Kernel function return
- tracepoint: Static kernel tracepoints
Control Hooks
- cgroup: Socket operations, device access
- socket filter: Per-socket filtering
- SK_SKB: Socket buffer operations
Information to Gather
Ask the user:
- Hook Type: Which hook to use? (XDP, tcx, netkit, kprobe, etc.)
- Interface: Which interface (for network hooks)?
- Attach Point: Ingress/egress (for TC), primary/peer (for netkit)?
- Program Name: What is the eBPF program called?
- Cleanup: Need signal handling for graceful shutdown?
XDP Attachment
XDP Modes
const ( // Generic XDP (slowest, works everywhere) XDPGenericMode = 1 << iota // Driver XDP (requires driver support) XDPDriverMode // Offload XDP (requires NIC support) XDPOffloadMode )
Basic XDP Attachment
package main import ( "log" "net" "os" "os/signal" "syscall" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" ) func main() { // Load eBPF program spec, err := LoadMyProgram() if err != nil { log.Fatalf("loading spec: %v", err) } objs := &MyProgramObjects{} if err := spec.LoadAndAssign(objs, nil); err != nil { log.Fatalf("loading objects: %v", err) } defer objs.Close() // Get interface iface, err := net.InterfaceByName("eth0") if err != nil { log.Fatalf("finding interface: %v", err) } // Attach XDP program l, err := link.AttachXDP(link.XDPOptions{ Program: objs.XdpProgram, Interface: iface.Index, Flags: link.XDPGenericMode, // or link.XDPDriverMode }) if err != nil { log.Fatalf("attaching XDP: %v", err) } defer l.Close() log.Printf("XDP program attached to %s", iface.Name) // Wait for signal sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig log.Println("Detaching XDP program...") }
XDP with Auto-Mode Selection
func attachXDP(prog *ebpf.Program, ifaceName string) (link.Link, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { return nil, fmt.Errorf("finding interface: %w", err) } // Try driver mode first, fall back to generic l, err := link.AttachXDP(link.XDPOptions{ Program: prog, Interface: iface.Index, Flags: link.XDPDriverMode, }) if err != nil { log.Printf("Driver mode failed, trying generic mode: %v", err) l, err = link.AttachXDP(link.XDPOptions{ Program: prog, Interface: iface.Index, Flags: link.XDPGenericMode, }) if err != nil { return nil, fmt.Errorf("attaching XDP: %w", err) } log.Println("Using generic XDP mode") } else { log.Println("Using driver XDP mode") } return l, nil }
TC/tcx Attachment
tcx (Kernel 6.6+, Preferred)
package main import ( "log" "net" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" ) func attachTCX(prog *ebpf.Program, ifaceName string, ingress bool) (link.Link, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { return nil, fmt.Errorf("finding interface: %w", err) } attach := ebpf.AttachTCXIngress if !ingress { attach = ebpf.AttachTCXEgress } l, err := link.AttachTCX(link.TCXOptions{ Program: prog, Attach: attach, Interface: iface.Index, }) if err != nil { return nil, fmt.Errorf("attaching tcx: %w", err) } direction := "ingress" if !ingress { direction = "egress" } log.Printf("tcx program attached to %s %s", iface.Name, direction) return l, nil } func main() { // Load program spec, _ := LoadMyProgram() objs := &MyProgramObjects{} spec.LoadAndAssign(objs, nil) defer objs.Close() // Attach to ingress ingressLink, err := attachTCX(objs.TcxIngress, "eth0", true) if err != nil { log.Fatal(err) } defer ingressLink.Close() // Attach to egress egressLink, err := attachTCX(objs.TcxEgress, "eth0", false) if err != nil { log.Fatal(err) } defer egressLink.Close() // ... wait for signal ... }
Legacy TC (Kernel < 6.6)
func attachTC(prog *ebpf.Program, ifaceName string, ingress bool) (link.Link, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { return nil, fmt.Errorf("finding interface: %w", err) } attach := ebpf.AttachTCIngress if !ingress { attach = ebpf.AttachTCEgress } l, err := link.AttachTC(link.TCOptions{ Program: prog, Attach: attach, Interface: iface.Index, }) if err != nil { return nil, fmt.Errorf("attaching TC: %w", err) } return l, nil }
Auto-Detect tcx vs TC
func attachTrafficControl(prog *ebpf.Program, ifaceName string, ingress bool) (link.Link, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { return nil, fmt.Errorf("finding interface: %w", err) } attach := ebpf.AttachTCXIngress if !ingress { attach = ebpf.AttachTCXEgress } // Try tcx first (kernel 6.6+) l, err := link.AttachTCX(link.TCXOptions{ Program: prog, Attach: attach, Interface: iface.Index, }) if err == nil { log.Println("Using tcx (modern)") return l, nil } // Fall back to legacy TC log.Printf("tcx failed, using legacy TC: %v", err) tcAttach := ebpf.AttachTCIngress if !ingress { tcAttach = ebpf.AttachTCEgress } l, err = link.AttachTC(link.TCOptions{ Program: prog, Attach: tcAttach, Interface: iface.Index, }) if err != nil { return nil, fmt.Errorf("attaching TC: %w", err) } log.Println("Using legacy TC") return l, nil }
netkit Attachment
package main import ( "log" "net" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" ) func attachNetkit(primaryProg, peerProg *ebpf.Program, ifaceName string) (link.Link, link.Link, error) { iface, err := net.InterfaceByName(ifaceName) if err != nil { return nil, nil, fmt.Errorf("finding interface: %w", err) } // Attach to primary primaryLink, err := link.AttachNetkit(link.NetkitOptions{ Program: primaryProg, Interface: iface.Index, Attach: ebpf.AttachNetkitPrimary, }) if err != nil { return nil, nil, fmt.Errorf("attaching to primary: %w", err) } // Attach to peer peerLink, err := link.AttachNetkit(link.NetkitOptions{ Program: peerProg, Interface: iface.Index, Attach: ebpf.AttachNetkitPeer, }) if err != nil { primaryLink.Close() return nil, nil, fmt.Errorf("attaching to peer: %w", err) } log.Printf("netkit programs attached to %s (primary and peer)", iface.Name) return primaryLink, peerLink, nil } func main() { spec, _ := LoadMyProgram() objs := &MyProgramObjects{} spec.LoadAndAssign(objs, nil) defer objs.Close() primaryLink, peerLink, err := attachNetkit( objs.NetkitPrimary, objs.NetkitPeer, "netkit0", ) if err != nil { log.Fatal(err) } defer primaryLink.Close() defer peerLink.Close() // ... wait for signal ... }
Tip: When you create interfaces programmatically (e.g.,
netkit.CreatePair), use the returned index directly instead of looking up by name. See EXAMPLES.md for the pattern.
kprobe/kretprobe Attachment
package main import ( "log" "github.com/cilium/ebpf/link" ) func attachKprobe(prog *ebpf.Program, symbol string) (link.Link, error) { // Attach kprobe to kernel function l, err := link.Kprobe(symbol, prog, nil) if err != nil { return nil, fmt.Errorf("attaching kprobe to %s: %w", symbol, err) } log.Printf("kprobe attached to %s", symbol) return l, nil } func attachKretprobe(prog *ebpf.Program, symbol string) (link.Link, error) { // Attach kretprobe to kernel function return l, err := link.Kretprobe(symbol, prog, nil) if err != nil { return nil, fmt.Errorf("attaching kretprobe to %s: %w", symbol, err) } log.Printf("kretprobe attached to %s", symbol) return l, nil } func main() { spec, _ := LoadMyProgram() objs := &MyProgramObjects{} spec.LoadAndAssign(objs, nil) defer objs.Close() // Attach to tcp_v4_connect entry kprobeLink, err := attachKprobe(objs.TraceTcpConnect, "tcp_v4_connect") if err != nil { log.Fatal(err) } defer kprobeLink.Close() // Attach to tcp_v4_connect return kretprobeLink, err := attachKretprobe(objs.TraceTcpConnectReturn, "tcp_v4_connect") if err != nil { log.Fatal(err) } defer kretprobeLink.Close() log.Println("Tracing TCP connections...") // ... wait for signal ... }
Tracepoint Attachment
func attachTracepoint(prog *ebpf.Program, group, name string) (link.Link, error) { // Attach to tracepoint l, err := link.Tracepoint(group, name, prog, nil) if err != nil { return nil, fmt.Errorf("attaching tracepoint %s:%s: %w", group, name, err) } log.Printf("tracepoint attached to %s:%s", group, name) return l, nil } func main() { spec, _ := LoadMyProgram() objs := &MyProgramObjects{} spec.LoadAndAssign(objs, nil) defer objs.Close() // Attach to syscalls:sys_enter_execve tracepoint l, err := attachTracepoint(objs.TraceExecve, "syscalls", "sys_enter_execve") if err != nil { log.Fatal(err) } defer l.Close() log.Println("Tracing execve syscalls...") // ... wait for signal ... }
cgroup Attachment
func attachCgroup(prog *ebpf.Program, cgroupPath string, attachType ebpf.AttachType) (link.Link, error) { // Open cgroup directory cgroupFd, err := os.Open(cgroupPath) if err != nil { return nil, fmt.Errorf("opening cgroup: %w", err) } defer cgroupFd.Close() // Attach to cgroup l, err := link.AttachCgroup(link.CgroupOptions{ Path: cgroupPath, Attach: attachType, Program: prog, }) if err != nil { return nil, fmt.Errorf("attaching to cgroup: %w", err) } log.Printf("Program attached to cgroup %s", cgroupPath) return l, nil } func main() { spec, _ := LoadMyProgram() objs := &MyProgramObjects{} spec.LoadAndAssign(objs, nil) defer objs.Close() // Attach to socket connect l, err := attachCgroup( objs.CgroupConnect, "/sys/fs/cgroup/unified/my-cgroup", ebpf.AttachCGroupInet4Connect, ) if err != nil { log.Fatal(err) } defer l.Close() // ... wait for signal ... }
Multi-Interface Attachment
func attachToMultipleInterfaces(prog *ebpf.Program, interfaces []string) ([]link.Link, error) { var links []link.Link for _, ifname := range interfaces { iface, err := net.InterfaceByName(ifname) if err != nil { // Cleanup already attached for _, l := range links { l.Close() } return nil, fmt.Errorf("finding interface %s: %w", ifname, err) } l, err := link.AttachXDP(link.XDPOptions{ Program: prog, Interface: iface.Index, Flags: link.XDPGenericMode, }) if err != nil { // Cleanup already attached for _, l := range links { l.Close() } return nil, fmt.Errorf("attaching to %s: %w", ifname, err) } links = append(links, l) log.Printf("Attached to %s", ifname) } return links, nil } func main() { spec, _ := LoadMyProgram() objs := &MyProgramObjects{} spec.LoadAndAssign(objs, nil) defer objs.Close() interfaces := []string{"eth0", "eth1", "wlan0"} links, err := attachToMultipleInterfaces(objs.XdpProgram, interfaces) if err != nil { log.Fatal(err) } // Cleanup all links defer func() { for _, l := range links { l.Close() } }() // ... wait for signal ... }
Complete CNF Example with Signal Handling
package main import ( "context" "log" "net" "os" "os/signal" "syscall" "github.com/cilium/ebpf" "github.com/cilium/ebpf/link" ) //go:generate go tool bpf2go MyCNF mycnf.c func run(ctx context.Context) error { // Load eBPF objects spec, err := LoadMyCNF() if err != nil { return fmt.Errorf("loading spec: %w", err) } objs := &MyCNFObjects{} if err := spec.LoadAndAssign(objs, nil); err != nil { return fmt.Errorf("loading objects: %w", err) } defer objs.Close() // Get interface iface, err := net.InterfaceByName("eth0") if err != nil { return fmt.Errorf("finding interface: %w", err) } // Attach XDP xdpLink, err := link.AttachXDP(link.XDPOptions{ Program: objs.XdpCnf, Interface: iface.Index, Flags: link.XDPGenericMode, }) if err != nil { return fmt.Errorf("attaching XDP: %w", err) } defer xdpLink.Close() log.Printf("CNF attached to %s", iface.Name) // Wait for cancellation <-ctx.Done() log.Println("Shutting down CNF...") return nil } func main() { // Setup signal handling ctx, cancel := context.WithCancel(context.Background()) defer cancel() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) go func() { <-sig log.Println("Received shutdown signal") cancel() }() // Run CNF if err := run(ctx); err != nil { log.Fatalf("error: %v", err) } }
Best Practices
- Always use defer for cleanup:
defer link.Close() - Handle errors properly: Check all attachment errors
- Use context for graceful shutdown: Especially for long-running CNFs
- Log attachment details: Interface names, modes, etc.
- Try driver mode first: Fall back to generic if needed
- Clean up on partial failure: When attaching to multiple interfaces
- Use signal handling: Allow Ctrl+C to gracefully detach
- Prefer tcx over legacy TC: On kernel 6.6+
- Use netkit for BPF-programmable paths: Better than veth for eBPF
- Test attachment before deployment: Verify hooks work as expected
Error Handling Patterns
// Pattern 1: Cleanup on error func attachWithCleanup() error { var links []link.Link defer func() { for _, l := range links { l.Close() } }() // ... attachment code ... return nil } // Pattern 2: Explicit cleanup func attachExplicit() (link.Link, error) { l, err := link.AttachXDP(...) if err != nil { return nil, err } // If we fail after this, clean up if someCondition { l.Close() return nil, errors.New("failed") } return l, nil }
Debugging Attachment Issues
// Check if program is attached func checkAttachment(ifname string) { // Use bpftool in bash: bpftool net show dev eth0 // Or use netlink to query XDP/TC status } // Verify link is valid func verifyLink(l link.Link) bool { info, err := l.Info() if err != nil { log.Printf("Link info error: %v", err) return false } log.Printf("Link ID: %d, Type: %s", info.ID, info.Type) return true }