netbirdio/netbird · error

expected IPv6 address, got %s

Error message

expected IPv6 address, got %s

What it means

Thrown by wgaddr.Address.SetIPv6FromCompact when the compact byte blob decoded with netiputil.DecodePrefix yields a non-IPv6 address (prefix.Addr().Is6() is false, i.e. a plain v4 address). The method is the single entry point that fills the IPv6/IPv6Net overlay fields from serialized data (config file or network map from management), so the error means the sender put v4 data into the v6 slot. Note that Is6() accepts v4-mapped addresses like ::ffff:10.0.0.1, so this only fires for genuinely 4-byte/v4-encoded values.

Source

Thrown at client/iface/wgaddr/address.go:75

		return netip.Prefix{}
	}
	return netip.PrefixFrom(addr.IPv6, addr.IPv6Net.Bits())
}

// SetIPv6FromCompact decodes a compact prefix (5 or 17 bytes) and sets the IPv6 fields.
// Returns an error if the bytes are invalid. A nil or empty input is a no-op.
//
//nolint:recvcheck
func (addr *Address) SetIPv6FromCompact(raw []byte) error {
	if len(raw) == 0 {
		return nil
	}
	prefix, err := netiputil.DecodePrefix(raw)
	if err != nil {
		return fmt.Errorf("decode v6 overlay address: %w", err)
	}
	if !prefix.Addr().Is6() {
		return fmt.Errorf("expected IPv6 address, got %s", prefix.Addr())
	}
	addr.IPv6 = prefix.Addr()
	addr.IPv6Net = prefix.Masked()
	return nil
}

// ClearIPv6 removes the IPv6 overlay address, leaving only v4.
//
//nolint:recvcheck
func (addr *Address) ClearIPv6() {
	addr.IPv6 = netip.Addr{}
	addr.IPv6Net = netip.Prefix{}
}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Check agent and management versions match; the compact prefix encoding (5 vs 17 bytes) changed across releases, so resync by re-registering or updating the peer
  2. Log the offending peer/account and skip the v6 assignment (ClearIPv6) instead of propagating the error, keeping the v4 overlay working
  3. If you control the producer, fix it to only call the v4 compact encoding for the v4 field and the 17-byte v6 encoding for the v6 field
  4. Reproduce by decoding the raw bytes with netiputil.DecodePrefix and printing prefix.Addr() to confirm which field is malformed

Example fix

// before
if err := addr.SetIPv6FromCompact(raw); err != nil {
    return fmt.Errorf("set v6 overlay address: %w", err)
}

// after - v6 is soft, never break v4 for it
if err := addr.SetIPv6FromCompact(raw); err != nil {
    log.Warnf("ignoring invalid v6 overlay payload, continuing v4-only: %v", err)
    addr.ClearIPv6()
}
Defensive patterns

Strategy: validation

Validate before calling

// before assigning, confirm the compact blob really carries a v6 prefix
func hasIPv6CompactPayload(raw []byte) bool {
    if len(raw) == 0 {
        return false
    }
    p, err := netiputil.DecodePrefix(raw)
    return err == nil && p.Addr().Is6()
}

Type guard

func isV6Prefix(p netip.Prefix) bool {
    return p.IsValid() && p.Addr().Is6()
}

Try / catch

err := addr.SetIPv6FromCompact(raw)
if err != nil {
    if strings.Contains(err.Error(), "expected IPv6 address") {
        log.Warnf("peer sent v4 in v6 slot, continuing v4-only: %v", err)
        addr.ClearIPv6()
    } else {
        return err // decode failure is a different problem
    }
}

Prevention

When it happens

Trigger: Management (or a hand-edited serialized state/config) writes an IPv4 prefix into the IPv6 field of the compact network map or config that SetIPv6FromCompact decodes. Concretely: agent upgrade/downgrade across versions that changed the compact encoding, a management bug, or test harnesses crafting the raw []byte with the wrong family.

Common situations: Version skew between agent and management after a partial upgrade; restoring a state file produced by a different agent build; self-hosted management patched to send v4 in the v6 slot. Per the repo's 'IPv6 is a soft feature' rule, a bad v6 payload should degrade to v4-only, not fail startup.

Related errors


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