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

  1. Treat it as already-detached if the program vanished (race) and retry the detach once
  2. Grant the agent CAP_BPF and CAP_SYS_ADMIN (or run privileged) so BPF program handles can be opened
  3. Verify with bpftool prog list that the program ID still exists
  4. 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

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.