netbirdio/netbird · error

block wg v6 net: %w

Error message

block wg v6 net: %w

What it means

Returned by blockInvalidRouted (client/firewall/uspfilter/filter.go:388) when the same drop rule cannot be installed for the IPv6 overlay prefix (v6Net.IsValid() was true, so the agent attempted the v6 leg). Per the function's contract, a v6 failure leaves the v4 rule installed and the returned slice still contains v4 - callers must persist it so DisableRouting can clean partial state. Failures are input-class: an interface-reported v6 prefix that is present but malformed (wrong bit length, non-canonical), or invalid v6 sources.

Source

Thrown at client/firewall/uspfilter/filter.go:388

	)
	if err != nil {
		return rules, fmt.Errorf("block wg v4 net: %w", err)
	}
	rules = append(rules, v4Rule)

	if v6Net.IsValid() {
		log.Debugf("blocking invalid routed traffic for %s", v6Net)
		v6Rule, err := m.addRouteFiltering(
			nil,
			sources,
			firewall.Network{Prefix: v6Net},
			firewall.ProtocolALL,
			nil,
			nil,
			firewall.ActionDrop,
		)
		if err != nil {
			return rules, fmt.Errorf("block wg v6 net: %w", err)
		}
		rules = append(rules, v6Rule)
	}

	// TODO: Block networks that we're a client of

	return rules, nil
}

func (m *Manager) determineRouting() error {
	var disableUspRouting, forceUserspaceRouter bool
	var err error
	if val := os.Getenv(EnvDisableUserspaceRouting); val != "" {
		disableUspRouting, err = strconv.ParseBool(val)
		if err != nil {
			log.Warnf("failed to parse %s: %v", EnvDisableUserspaceRouting, err)
		}
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Normalize the v6 prefix (Masked(), sane Bits bounds) before calling blockInvalidRouted; skip the v6 leg when normalization deems it unusable
  2. Treat v6 block failure as non-fatal per the IPv6-soft-feature policy: keep v4 protection, log a warning, and continue routing
  3. Always persist the returned partial rules slice so DisableRouting cleans whatever was installed
  4. If v6 is not needed, run the overlay IPv4-only so v6Net.IsValid() is false and the leg is skipped

Example fix

// before
v6Rule, err := m.addRouteFiltering(nil, sources, firewall.Network{Prefix: v6Net}, firewall.ProtocolALL, nil, nil, firewall.ActionDrop)
if err != nil {
    return rules, fmt.Errorf("block wg v6 net: %w", err)
}
// after - keep v4 protection, degrade the v6 leg to a warning
v6Rule, err := m.addRouteFiltering(nil, sources, firewall.Network{Prefix: v6Net}, firewall.ProtocolALL, nil, nil, firewall.ActionDrop)
if err != nil {
    log.Warnf("skip invalid routed v6 block for %s: %v", v6Net, err)
    return rules, nil
}
rules = append(rules, v6Rule)
Defensive patterns

Strategy: fallback

Validate before calling

v6 := iface.Address().IPv6Net
if v6.IsValid() && (v6.Bits() < 0 || v6.Bits() > 128 || !v6.Addr().Is6() || v6.Addr().Is4In6()) {
    // treat as 'no v6' rather than failing EnableRouting
    v6 = netip.Prefix{}
}

Type guard

func usableOverlayV6(p netip.Prefix) bool {
    return p.IsValid() && p.Addr().Is6() && !p.Addr().Is4In6() && p.Bits() >= 0 && p.Bits() <= 128
}

Try / catch

if err := fw.EnableRouting(); err != nil {
    if strings.Contains(err.Error(), "block wg v6 net") {
        log.Warnf("continuing v4-only routing; v6 default-drop failed: %v", err)
        err = nil
    }
    if err != nil {
        return err
    }
}

Prevention

When it happens

Trigger: Dual-stack overlay where iface.Address().IPv6Net reports a valid-but-unusable prefix (e.g. Bits() > 64 or host bits set non-canonically); v6 sources from the network map that fail validation mid-install.

Common situations: Management pushes an IPv6 range with an unusual prefix length; agent version mismatch in v6 address normalization (Unmap missing, v4-mapped entries in the v6 net); hosts with flapping v6 capability re-enabling the v6 leg mid-session.

Related errors


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