netbirdio/netbird · error

received error "%w" while removing peer %s from interface %s

Error message

received error "%w" while removing peer %s from interface %s

What it means

The kernel configurer failed to remove a peer from the named WireGuard interface via wgctrl/netlink. The %w chain holds the actual cause: ENOENT when the device (or on some wgctrl versions the peer) no longer exists, EACCES/EPERM without privileges, or a netlink/uapi transport error. The peer key and interface name are embedded to identify the exact removal that failed.

Source

Thrown at client/iface/configurer/kernel_unix.go:143

}

func (c *KernelConfigurer) RemovePeer(peerKey string) error {
	peerKeyParsed, err := wgtypes.ParseKey(peerKey)
	if err != nil {
		return err
	}

	peer := wgtypes.PeerConfig{
		PublicKey: peerKeyParsed,
		Remove:    true,
	}

	config := wgtypes.Config{
		Peers: []wgtypes.PeerConfig{peer},
	}
	err = c.configure(config)
	if err != nil {
		return fmt.Errorf(`received error "%w" while removing peer %s from interface %s`, err, peerKey, c.deviceName)
	}
	return nil
}

func (c *KernelConfigurer) AddAllowedIP(peerKey string, allowedIP netip.Prefix) error {
	ipNet := net.IPNet{
		IP:   allowedIP.Addr().AsSlice(),
		Mask: net.CIDRMask(allowedIP.Bits(), allowedIP.Addr().BitLen()),
	}

	peerKeyParsed, err := wgtypes.ParseKey(peerKey)
	if err != nil {
		return err
	}
	peer := wgtypes.PeerConfig{
		PublicKey:         peerKeyParsed,
		UpdateOnly:        true,
		ReplaceAllowedIPs: false,

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Classify the wrapped error: device/peer-missing is effectively success for a removal, so absorb it; permission errors need a privilege fix
  2. Make removal idempotent by checking getPeer first or tolerating the not-found cause in the caller
  3. Serialize peer removal with interface lifecycle events so removals never target a torn-down device
  4. Verify with wg show <if> that the peer actually remains if the error was a transport failure, then retry

Example fix

// before
if err := c.RemovePeer(peerKey); err != nil { return err }

// after
if err := c.RemovePeer(peerKey); err != nil {
    if errors.Is(err, syscall.ENOENT) {
        return nil // already gone; desired state reached
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

client, err := wgctrl.New()
if err != nil { return err }
device, err := client.Device(deviceName)
if err != nil { return err }
for _, p := range device.Peers {
    if p.PublicKey.String() == peerKey {
        // present: proceed with removal
    }
}

Type guard

func peerOnDevice(deviceName, peerKey string) (bool, error) {
    client, err := wgctrl.New()
    if err != nil {
        return false, err
    }
    defer client.Close()
    d, err := client.Device(deviceName)
    if err != nil {
        return false, err
    }
    for _, p := range d.Peers {
        if p.PublicKey.String() == peerKey {
            return true, nil
        }
    }
    return false, nil
}

Try / catch

if err := configurer.RemovePeer(peerKey); err != nil {
    if errors.Is(err, syscall.ENOENT) {
        return nil // device or peer already gone: desired state
    }
    return fmt.Errorf("remove peer %s: %w", peerKey, err)
}

Prevention

When it happens

Trigger: Removing a peer after the interface was deleted or fully resynced with ReplacePeers (peer already gone); concurrent teardown during engine shutdown; running without CAP_NET_ADMIN; uapi socket unavailable in restricted environments.

Common situations: Network map changes (peer left a group) racing engine restarts; duplicate peer-removal events processed twice; unclean previous shutdowns leaving the device state out of sync with what the configurer expects.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/957891e6d6ed1c43. Report an issue: GitHub.