netbirdio/netbird · error

invalid rule format: expected protocol/port or protocol/port

Error message

invalid rule format: expected protocol/port or protocol/port-range

What it means

ParseRuleString only accepts the bare keywords 'all' and 'icmp' or strings of the form protocol/port with exactly one '/' separator. It splits on '/' and requires exactly two segments; anything else fails here before protocol or port parsing begins.

Source

Thrown at shared/management/types/policy.go:199

	for groupID := range groups {
		groupIDs = append(groupIDs, groupID)
	}

	return groupIDs
}

func ParseRuleString(rule string) (PolicyRuleProtocolType, RulePortRange, error) {
	rule = strings.TrimSpace(strings.ToLower(rule))
	if rule == "all" {
		return PolicyRuleProtocolALL, RulePortRange{}, nil
	}
	if rule == "icmp" {
		return PolicyRuleProtocolICMP, RulePortRange{}, nil
	}

	split := strings.Split(rule, "/")
	if len(split) != 2 {
		return "", RulePortRange{}, errors.New("invalid rule format: expected protocol/port or protocol/port-range")
	}

	protoStr := strings.TrimSpace(split[0])
	portStr := strings.TrimSpace(split[1])

	var protocol PolicyRuleProtocolType
	switch protoStr {
	case "tcp":
		protocol = PolicyRuleProtocolTCP
	case "udp":
		protocol = PolicyRuleProtocolUDP
	case "icmp":
		return "", RulePortRange{}, errors.New("icmp does not accept ports; use 'icmp' without '/…'")
	case "netbird-ssh":
		return PolicyRuleProtocolNetbirdSSH, RulePortRange{Start: nativeSSHPortNumber, End: nativeSSHPortNumber}, nil
	default:
		return "", RulePortRange{}, fmt.Errorf("invalid protocol: %q", protoStr)
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Format rules as protocol/port, e.g. tcp/22, or a range like udp/5000-5010
  2. Use the bare keywords 'all' or 'icmp' when no port applies
  3. Normalize separators and trim whitespace before submitting rules

Example fix

// before
proto, ports, err := types.ParseRuleString("tcp 80")

// after
proto, ports, err := types.ParseRuleString("tcp/80")
Defensive patterns

Strategy: validation

Validate before calling

var ruleRe = regexp.MustCompile(`^(all|icmp|[a-z-]+/[0-9]+(-[0-9]+)?)$`)
if !ruleRe.MatchString(strings.ToLower(strings.TrimSpace(rule))) {
	return fmt.Errorf("rule %q must be 'all', 'icmp', or protocol/port[-range]", rule)
}

Type guard

func isParsableRule(rule string) bool {
	r := strings.ToLower(strings.TrimSpace(rule))
	if r == "all" || r == "icmp" {
		return true
	}
	parts := strings.Split(r, "/")
	return len(parts) == 2 && parts[0] != "" && parts[1] != ""
}

Try / catch

if _, _, err := types.ParseRuleString(rule); err != nil {
	return fmt.Errorf("rejecting rule %q from import: %w", rule, err)
}

Prevention

When it happens

Trigger: Inputs like "tcp" (no port), "tcp/80/443" (two slashes), "80" (port only), "tcp:80" (wrong separator), or an empty/whitespace-only string.

Common situations: Hand-written access-control rule strings in policy payloads; CSV/YAML imports with malformed cells; free-text UI fields without pre-validation.

Related errors


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