cilium/cilium · error

updating link %s for program %s: %w

Error message

updating link %s for program %s: %w

What it means

This is the default branch of the UpdateLink switch in attachXDPProgram: any error that is neither nil, ENOENT (no link yet), nor ENOLINK (defunct) is reported as a failure to update the existing bpf_link pin. It means Cilium found an existing pinned link but could not swap in the new program via link update.

Source

Thrown at pkg/datapath/loader/xdp.go:338

	case errors.Is(err, unix.ENOLINK):
		if err := os.Remove(pin); err != nil {
			return fmt.Errorf("unpinning defunct link %s: %w", pin, err)
		}

		logger.Info("Unpinned defunct link for program",
			logfields.Link, pin,
			logfields.ProgName, progName,
		)

	// No existing link found, continue trying to create one.
	case errors.Is(err, os.ErrNotExist):
		logger.Info("No existing link found for program",
			logfields.Link, pin,
			logfields.ProgName, progName,
		)

	default:
		return fmt.Errorf("updating link %s for program %s: %w", pin, progName, err)
	}

	if err := bpf.MkdirBPF(bpffsDir); err != nil {
		return fmt.Errorf("creating bpffs link dir for xdp attachment to device %s: %w", iface.Attrs().Name, err)
	}

	// Create a new link. This will only succeed on nodes that support bpf_link
	// and don't have any XDP programs attached through netlink.
	l, err := link.AttachXDP(link.XDPOptions{
		Program:   prog,
		Interface: iface.Attrs().Index,
		Flags:     flags,
	})
	if err == nil {
		defer func() {
			// The program was successfully attached using bpf_link. Closing a link
			// does not detach the program if the link is pinned.
			if err := l.Close(); err != nil {

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Unpin the offending link (rm <pin> under /sys/fs/bpf/cilium/links/) and restart so a fresh link is created
  2. Grant required capabilities (CAP_BPF, CAP_NET_ADMIN, CAP_SYS_ADMIN) to the agent process
  3. Check wrapped error for EPERM/EINVAL and align program versions — wipe /var/run/cilium state after upgrades
  4. Verify the pin path contains an actual bpf_link (bpftool link show) not a leftover file
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure pin is a real link before update
if _, err := os.Stat(pin); err == nil {
    if fi, _ := os.Stat(pin); fi.Mode().IsRegular() {
        os.Remove(pin) // wrong type; let a fresh link be created
    }
}

Try / catch

// Go: on update failure, unpin and retry once
if err := bpf.UpdateLink(pin, prog); err != nil {
    os.Remove(pin)
    err = bpf.UpdateLink(pin, prog)
    if err != nil { return fmt.Errorf("updating link %s for program %s: %w", pin, progName, err) }
}

Prevention

When it happens

Trigger: bpf.UpdateLink(pin, prog) returns EPERM (insufficient privileges), EINVAL (program type mismatch with existing link), EEXIST-class errors, or I/O errors on bpffs while updating an existing pinned link for progName.

Common situations: Older Cilium version pinned a link and the running process lacks capabilities to update it; pin file on bpffs corrupted or replaced by a non-link file; kernel version mismatch between the pinned link and the new program semantics.

Related errors


AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31). Data as JSON: /api/errors/0d51bc1f3ee700a7. Report an issue: GitHub.