cilium/cilium · error
opening XDP program id %d: %w
Error message
opening XDP program id %d: %w
What it means
After the pin is missing, DetachXDP inspects the kernel-attached XDP program by its ID (xdp.ProgId) using ebpf.NewProgramFromID. The program with that ID could not be opened — typically it was already unloaded, or the caller lacks BPF privileges.
Source
Thrown at pkg/datapath/loader/xdp.go:435
err = bpf.UnpinLink(pin)
if err == nil {
return nil
}
if !errors.Is(err, os.ErrNotExist) {
// The pinned link exists, something went wrong unpinning it.
return fmt.Errorf("unpinning XDP program using bpf_link: %w", err)
}
xdp := iface.Attrs().Xdp
if xdp == nil || !xdp.Attached {
return nil
}
// Inspect the attached program to only remove the intended XDP program.
id := xdp.ProgId
prog, err := ebpf.NewProgramFromID(ebpf.ProgramID(id))
if err != nil {
return fmt.Errorf("opening XDP program id %d: %w", id, err)
}
info, err := prog.Info()
if err != nil {
return fmt.Errorf("getting XDP program info %d: %w", id, err)
}
// The program name returned by BPF_PROG_INFO is limited to 20 characters.
// Treat the kernel-provided program name as a prefix that needs to match
// against progName. Empty program names (on kernels before 4.15) will always
// match and be removed.
if !strings.HasPrefix(progName, info.Name) {
return nil
}
// Pin doesn't exist, fall through to detaching using netlink.
if err := netlink.LinkSetXdpFdWithFlags(iface, -1, int(link.XDPGenericMode)); err != nil {
return fmt.Errorf("detaching generic-mode XDP program using netlink: %w", err)
}
View on GitHub (pinned to ac7b90affa)
Solutions
- Treat it as already-detached if the program vanished (race) and retry the detach once
- Grant the agent CAP_BPF and CAP_SYS_ADMIN (or run privileged) so BPF program handles can be opened
- Verify with bpftool prog list that the program ID still exists
- Upgrade cilium/cilium-ebpf and kernel if BPF_PROG_GET_FD_BY_ID is unsupported
Example fix
// before
prog, err := ebpf.NewProgramFromID(ebpf.ProgramID(id))
if err != nil { return fmt.Errorf("opening XDP program id %d: %w", id, err) }
// after
prog, err := ebpf.NewProgramFromID(ebpf.ProgramID(id))
if errors.Is(err, os.ErrNotExist) {
log.Debugf("XDP prog %d already gone; treating as detached", id)
return nil
}
if err != nil { return fmt.Errorf("opening XDP program id %d: %w", id, err) } Defensive patterns
Strategy: fallback
Validate before calling
ids, _ := ebpf.ProgramIDs(ebpf.XDP); contains(ids, progID) or skip
Try / catch
prog, err := ebpf.NewProgramFromID(id)
if errors.Is(err, os.ErrNotExist) { /* program already gone: treat as detached */ return nil } Prevention
- Grant CAP_BPF/CAP_SYS_ADMIN to the process
- Check bpftool prog list before inspecting by ID
- Treat ErrNotExist as success in cleanup paths
When it happens
Trigger: DetachXDP on an interface whose netlink attrs report an attached XDP program with ProgId, but NewProgramFromID fails because the program no longer exists or fd limits/permissions block BPF_PROG_GET_FD_BY_ID.
Common situations: The XDP program was detached/destroyed by another process between reading attrs and opening it; agent container lacks CAP_BPF/CAP_SYS_ADMIN; running kernel doesn't support BPF_PROG_GET_FD_BY_ID (very old kernels).
Related errors
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/f8c32abd0bc14ef4.
Report an issue: GitHub.