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.md
source 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:

  1. Attaching to XDP (eXpress Data Path)
  2. Attaching to TC/tcx (Traffic Control)
  3. Attaching to netkit devices
  4. Attaching to kprobes/kretprobes
  5. Attaching to tracepoints
  6. Attaching to cgroup hooks
  7. Proper cleanup and error handling
  8. 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:

  1. Hook Type: Which hook to use? (XDP, tcx, netkit, kprobe, etc.)
  2. Interface: Which interface (for network hooks)?
  3. Attach Point: Ingress/egress (for TC), primary/peer (for netkit)?
  4. Program Name: What is the eBPF program called?
  5. 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

  1. Always use defer for cleanup:
    defer link.Close()
  2. Handle errors properly: Check all attachment errors
  3. Use context for graceful shutdown: Especially for long-running CNFs
  4. Log attachment details: Interface names, modes, etc.
  5. Try driver mode first: Fall back to generic if needed
  6. Clean up on partial failure: When attaching to multiple interfaces
  7. Use signal handling: Allow Ctrl+C to gracefully detach
  8. Prefer tcx over legacy TC: On kernel 6.6+
  9. Use netkit for BPF-programmable paths: Better than veth for eBPF
  10. 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
}