Claude Code Plugins

Community-maintained marketplace

Feedback

ebpf-attach-hook

@cassamajor/xcnf
1
0

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 Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name ebpf-attach-hook
description 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.

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 ...
}

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
}