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
- Retry Setup — the race is transient and the next attempt takes a clean path.
- Check for other controllers/scripts deleting interfaces named dummy0..dummyN and stop them.
- Investigate netlink socket health (netns changes, socket timeouts) in the wrapped error.
- 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
- Allow only one controller to manage dummyN device names.
- Retry transient netlink races with backoff.
- Avoid switching network namespaces in other goroutines during Setup.
- Monitor for external scripts/plugins deleting host interfaces.
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
- failed to list interfaces: %w
- unable to lookup veth peer just created: %w
- unable to lookup route for node %s: %w
- unable to find local route for destination %s: %w
- failed to delete all direct routes: %w
AI-assisted analysis of cilium/cilium@ac7b90affa (2026-08-31).
Data as JSON: /api/errors/0f62ec54265c576c.
Report an issue: GitHub.