cilium/cilium · error

updating tcx program: %w

Error message

updating tcx program: %w

What it means

upsertTCXProgram wraps errors from updateTCX that are neither successful updates nor os.ErrNotExist (missing/defunct pin). Such an error means an existing pinned tcx link could not be inspected or updated, so the upsert aborts instead of attaching a new link.

Source

Thrown at pkg/datapath/loader/tcx.go:44

	case netlink.HANDLE_MIN_INGRESS:
		return ebpf.AttachTCXIngress
	case netlink.HANDLE_MIN_EGRESS:
		return ebpf.AttachTCXEgress
	}
	panic(fmt.Sprintf("invalid tc direction: %d", parent))
}

// upsertTCXProgram updates or creates a new tcx attachment for prog to device.
// Returns [link.ErrNotSupported] if tcx is not supported on the node.
func upsertTCXProgram(logger *slog.Logger, device netlink.Link, prog *ebpf.Program, progName, bpffsDir string, parent uint32) error {
	err := updateTCX(logger, prog, progName, bpffsDir)
	if err == nil {
		// Link was updated, nothing left to do.
		return nil
	}
	if !errors.Is(err, os.ErrNotExist) {
		// Unrecoverable error, surface to the caller.
		return fmt.Errorf("updating tcx program: %w", err)
	}

	return attachTCX(logger, device, prog, progName, bpffsDir, parentToAttachType(parent))
}

// attachTCX creates a new tcx attachment for prog to device at the given attach
// type. It pins the resulting link object to progName in bpffsDir.
//
// progName is typically the Program's key in CollectionSpec.Programs.
func attachTCX(logger *slog.Logger, device netlink.Link, prog *ebpf.Program, progName, bpffsDir string, attach ebpf.AttachType) error {
	if err := bpf.MkdirBPF(bpffsDir); err != nil {
		return fmt.Errorf("creating bpffs link dir for tcx attachment to device %s: %w", device.Attrs().Name, err)
	}

	l, err := link.AttachTCX(link.TCXOptions{
		Program:   prog,
		Attach:    attach,
		Interface: device.Attrs().Index,

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Inspect the pin: bpftool link show pinned <bpffsDir>/<progName>; remove a corrupt pin and restart the agent.
  2. Verify bpffs is mounted correctly and writable, and the agent holds CAP_SYS_ADMIN.
  3. If the pin path changed (config/env), align the bpffs directory configuration with what is on disk.
  4. As a fallback, disable tcx (run with legacy tc) if the kernel cannot support tcx links.

Example fix

// before: corrupt pin breaks update
$ bpftool link show pinned /sys/fs/bpf/cilium/tcx_from_netdev
Error: can't get link info
// after
$ rm /sys/fs/bpf/cilium/tcx_from_netdev && cilium-agent restart
Defensive patterns

Strategy: try-catch

Validate before calling

// Probe the pin before upsert
if _, err := os.Stat(filepath.Join(bpffsDir, progName)); err == nil {
    if _, err := link.LoadPinnedLink(filepath.Join(bpffsDir, progName), nil); err != nil {
        os.Remove(filepath.Join(bpffsDir, progName)) // corrupt pin
    }
}

Try / catch

if err := upsertTCXProgram(logger, device, prog, progName, bpffsDir, parent); err != nil {
    if !errors.Is(err, link.ErrNotSupported) && !errors.Is(err, os.ErrNotExist) {
        logger.Error("tcx update failed", "pin", filepath.Join(bpffsDir, progName), "err", err)
        os.Remove(filepath.Join(bpffsDir, progName)) // drop corrupt pin and retry
    }
    return err
}

Prevention

When it happens

Trigger: attachSKBProgram with tcxEnabled calls upsertTCXProgram; bpf.UpdateLink on bpffsDir/progName fails with something other than ENOLINK or ErrNotExist — e.g. pin path is a corrupt file, bpffs I/O error, or permission denied opening the pinned link.

Common situations: Corrupted bpffs state after node crash, bpffs mount mismatch between restarts, permission/capability loss, wrong pin path due to config change of the bpffs directory.

Related errors


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