netbirdio/netbird · error

failed to add protocol address: %s

Error message

failed to add protocol address: %s

What it means

Returned by forwarder.New (client/firewall/uspfilter/forwarder/forwarder.go:96) when s.AddProtocolAddress on NIC 1 with the overlay's IPv4 address fails. gVisor rejects protocol-address additions for a non-existent NIC, a duplicate address, or an invalid prefix length; since the address comes from iface.Address().IP.As4() with Bits() from the network, a zero/invalid interface address or out-of-range prefix length produces this. The message uses %s on a tcpip error (not wrapped) and duplicates the 'failed to' wording - the underlying error type is lost to callers.

Source

Thrown at client/firewall/uspfilter/forwarder/forwarder.go:96

		logger: logger,
		device: iface.GetWGDevice(),
	}
	endpoint.mtu.Store(uint32(mtu))

	if err := s.CreateNIC(nicID, endpoint); err != nil {
		return nil, fmt.Errorf("create NIC: %v", err)
	}

	protoAddr := tcpip.ProtocolAddress{
		Protocol: ipv4.ProtocolNumber,
		AddressWithPrefix: tcpip.AddressWithPrefix{
			Address:   tcpip.AddrFrom4(iface.Address().IP.As4()),
			PrefixLen: iface.Address().Network.Bits(),
		},
	}

	if err := s.AddProtocolAddress(nicID, protoAddr, stack.AddressProperties{}); err != nil {
		return nil, fmt.Errorf("failed to add protocol address: %s", err)
	}

	if v6 := iface.Address().IPv6; v6.IsValid() {
		v6Addr := tcpip.ProtocolAddress{
			Protocol: ipv6.ProtocolNumber,
			AddressWithPrefix: tcpip.AddressWithPrefix{
				Address:   tcpip.AddrFrom16(v6.As16()),
				PrefixLen: iface.Address().IPv6Net.Bits(),
			},
		}
		if err := s.AddProtocolAddress(nicID, v6Addr, stack.AddressProperties{}); err != nil {
			return nil, fmt.Errorf("add IPv6 protocol address: %s", err)
		}
	}

	defaultSubnet, err := tcpip.NewSubnet(
		tcpip.AddrFrom4([4]byte{0, 0, 0, 0}),
		tcpip.MaskFromBytes([]byte{0, 0, 0, 0}),

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Require iface.Address().IP.IsValid() and 0 <= Network.Bits() <= 32 (and v6 bits <= 128) before constructing the forwarder
  2. Retry EnableRouting after the interface is configured; construction then receives a valid address
  3. When adding the optional v6 leg, keep the existing IsValid() guard so an absent v6 skips instead of failing
  4. Wrap the error with %w so callers can match tcpip error types

Example fix

// before
if err := s.AddProtocolAddress(nicID, protoAddr); err != nil {
    return nil, fmt.Errorf("failed to add protocol address: %s", err)
}
// after - validate inputs, preserve the error chain
addr := iface.Address()
if !addr.IP.IsValid() || addr.Network.Bits() > 32 {
    return nil, fmt.Errorf("invalid overlay address %s/%d", addr.IP, addr.Network.Bits())
}
if err := s.AddProtocolAddress(nicID, protoAddr); err != nil {
    return nil, fmt.Errorf("add protocol address %s: %w", protoAddr.AddressWithPrefix, err)
}
Defensive patterns

Strategy: validation

Validate before calling

a := iface.Address()
if !a.IP.IsValid() || !a.IP.Is4() || a.Network.Bits() > 32 {
    return fmt.Errorf("forwarder needs a valid IPv4 overlay address, got %s/%d", a.IP, a.Network.Bits())
}
_ = fw.EnableRouting()

Type guard

func validOverlayAddress(a common.WGAddress) bool {
    return a.IP.IsValid() && a.IP.Is4() && a.Network.Bits() <= 32 &&
        (!a.IPv6.IsValid() || a.IPv6Net.Bits() <= 128)
}

Try / catch

if err := fw.EnableRouting(); err != nil {
    msg := err.Error()
    if strings.Contains(msg, "protocol address") {
        // interface not addressed yet; retry once after bring-up
        time.Sleep(settleDelay)
        return fw.EnableRouting()
    }
    return err
}

Prevention

When it happens

Trigger: forwarder.New called before the interface address was assigned, so Address().IP is the zero value and/or Network.Bits() is invalid; duplicate AddProtocolAddress after a double init; prefix length > 32 reaching tcpip.

Common situations: Routing enabled in a bring-up race before address configuration completes; management delivering a network address whose prefix length the agent did not sanitize; embedded/wasm paths where Address() is populated asynchronously.

Related errors


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