netbirdio/netbird · error

parse IP %s

Error message

parse IP %s

What it means

While deleting a peer rule, net.ParseIP of the Rule's stored ip string returned nil, so the string is not a valid IPv4 or IPv6 literal. In normal operation ip is produced by ip.String() and always parses; a failure therefore means the Rule was constructed outside the manager with a corrupted, empty, or malformed address (e.g. a CIDR like 10.0.0.1/32 instead of a bare IP).

Source

Thrown at client/firewall/iptables/acl_linux.go:202

	m.updateState()

	return []firewall.Rule{rule}, nil
}

// DeletePeerRule from the firewall by rule definition
func (m *aclManager) DeletePeerRule(rule firewall.Rule) error {
	r, ok := rule.(*Rule)
	if !ok {
		return fmt.Errorf("invalid rule type")
	}

	shouldDestroyIpset := false
	if ipsetList, ok := m.ipsetStore.ipset(r.ipsetName); ok {
		// delete IP from ruleset IPs list and ipset
		if _, ok := ipsetList.ips[r.ip]; ok {
			ip := net.ParseIP(r.ip)
			if ip == nil {
				return fmt.Errorf("parse IP %s", r.ip)
			}
			if err := m.delFromIPSet(r.ipsetName, ip); err != nil {
				return fmt.Errorf("delete ip from ipset: %w", err)
			}
			delete(ipsetList.ips, r.ip)
		}

		// if after delete, set still contains other IPs,
		// no need to delete firewall rule and we should exit here
		if len(ipsetList.ips) != 0 {
			return nil
		}

		// we delete last IP from the set, that means we need to delete
		// set itself and associated firewall rule too
		m.ipsetStore.deleteIpset(r.ipsetName)
		shouldDestroyIpset = true
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Always create rules through AddPeerFiltering so ip is set from net.IP.String().
  2. Validate the field at construction: reject anything net.ParseIP cannot parse, and store bare addresses, never CIDRs.
  3. If restoring rules from persisted state, re-parse with netip.ParsePrefix and pass .Addr().String() when the value carries a prefix.

Example fix

// before
ip := net.ParseIP(r.ip) // r.ip == "10.0.0.1/32" -> nil

// after (normalize at the boundary)
addr, err := netip.ParseAddr(r.ip)
if err != nil {
    if p, perr := netip.ParsePrefix(r.ip); perr == nil {
        addr = p.Addr()
    } else {
        return fmt.Errorf("parse IP %s: %w", r.ip, err)
    }
}
Defensive patterns

Strategy: validation

Validate before calling

if net.ParseIP(candidateIP) == nil {
    return fmt.Errorf("%q is not a valid IP literal; refusing to build a rule", candidateIP)
}

Type guard

func isValidRuleIP(s string) bool { return net.ParseIP(s) != nil }

Try / catch

if err := mgr.DeletePeerRule(rule); err != nil {
    if strings.Contains(err.Error(), "parse IP") {
        // corrupted rule record: drop it locally instead of retrying
        removeRuleFromState(rule)
    }
}

Prevention

When it happens

Trigger: Hand-built Rule values in tests or restored state that carry "10.0.0.1/32", "", a hostname, or whitespace; serialization round-trips that mangle the field; rules copied from the routemanager where prefixes are the native format.

Common situations: Test fixtures written by hand; persistence layers that store prefix notation; refactors that changed the field's expected format without updating all constructors.

Related errors


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