cilium/cilium · error

dummy interface %s exists but could not be read: %w

Error message

dummy interface %s exists but could not be read: %w

What it means

Setup observed EEXIST from LinkAdd, meaning the interface name already exists, but the subsequent netlink.LinkByName lookup of that same interface failed. This is a race/anomaly: the kernel reported the device exists yet it cannot be read back. Setup aborts rather than adopting an unreadable device.

Source

Thrown at pkg/networkdriver/dummy/dummy.go:132

// device of the wrong type.
func (d DummyDevice) Setup(cfg types.DeviceConfig) error {
	dummy := &netlink.Dummy{
		LinkAttrs: netlink.LinkAttrs{Name: d.Name},
	}

	err := netlinkLinkAdd(dummy)
	if err == nil {
		return nil
	}
	if !errors.Is(err, unix.EEXIST) {
		return fmt.Errorf("failed to create dummy interface %s: %w", d.Name, err)
	}

	// The interface already exists. Adopt it if it is a dummy, otherwise
	// replace it.
	existing, lookupErr := netlinkLinkByName(d.Name)
	if lookupErr != nil {
		return fmt.Errorf("dummy interface %s exists but could not be read: %w", d.Name, lookupErr)
	}

	if _, ok := existing.(*netlink.Dummy); ok {
		// Same type; adopt the existing device.
		return nil
	}

	// Stale or mismatched device. Delete and recreate it.
	if delErr := netlinkLinkDel(existing); delErr != nil {
		return fmt.Errorf("failed to delete stale dummy interface %s: %w", d.Name, delErr)
	}
	if addErr := netlinkLinkAdd(dummy); addErr != nil {
		return fmt.Errorf("failed to recreate dummy interface %s: %w", d.Name, addErr)
	}

	return nil
}

View on GitHub (pinned to ac7b90affa)

Solutions

  1. Retry Setup — the race is transient and the next attempt takes a clean path.
  2. Check for other controllers/scripts deleting interfaces named dummy0..dummyN and stop them.
  3. Investigate netlink socket health (netns changes, socket timeouts) in the wrapped error.
  4. Ensure only one actor (the device manager) manages these device names.

Example fix

// before
if err := dev.Setup(cfg); err != nil { return err }
// after: tolerate transient races with a bounded retry
var err error
for i := 0; i < 3; i++ {
    if err = dev.Setup(cfg); err == nil || !isTransientNetlinkErr(err) { break }
    time.Sleep(100 * time.Millisecond)
}
Defensive patterns

Strategy: retry

Validate before calling

// Pre-check the name is not contended before Setup
if _, err := safenetlink.LinkByName(dev.Name); err == nil {
    log.Info("interface already exists; Setup will adopt or replace it")
}

Try / catch

if err := dev.Setup(cfg); err != nil {
    if strings.Contains(err.Error(), "exists but could not be read") {
        time.Sleep(100 * time.Millisecond)
        return dev.Setup(cfg) // transient race; retry once
    }
    return err
}

Prevention

When it happens

Trigger: netlinkLinkAdd returns unix.EEXIST, then netlinkLinkByName(d.Name) fails (typically netlink.LinkNotFoundError or a netlink communication error) within the same Setup call.

Common situations: A concurrent process deleted the interface between LinkAdd and LinkByName; flaky netlink socket (process netns changed mid-call); rapid pod churn racing two Setups for the same dummyN name.

Related errors


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