slackhq/nebula · error

%s rule #%v; `%s`

Error message

%s rule #%v; `%s`

What it means

This is a wrapper error from AddFirewallRulesFromConfig. When AddRule (backed by firewallPort.addRule) fails while installing a rule parsed from the YAML config, the error is re-wrapped as '<table> rule #<index>; `<original error>`' so the operator knows exactly which rule in which firewall table (inbound/outbound) is broken. The inner message (e.g. 'start port was lower than end port') is the actual cause.

Source

Thrown at firewall.go:411

		if r.LocalCidr != "" && r.LocalCidr != "any" {
			_, err = netip.ParsePrefix(r.LocalCidr)
			if err != nil {
				return fmt.Errorf("%s rule #%v; local_cidr did not parse; %s", table, i, err)
			}
		}

		if warning := r.sanity(); warning != nil {
			l.Warn("firewall rule sanity check",
				"table", table,
				"rule", i,
				"warning", warning,
			)
		}

		err = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)
		if err != nil {
			return fmt.Errorf("%s rule #%v; `%s`", table, i, err)
		}
	}

	return nil
}

var ErrUnknownNetworkType = errors.New("unknown network type")
var ErrPeerRejected = errors.New("remote address is not within a network that we handle")
var ErrInvalidRemoteIP = errors.New("remote address is not in remote certificate networks")
var ErrInvalidLocalIP = errors.New("local address is not in list of handled local addresses")
var ErrNoMatchingRule = errors.New("no matching rule in firewall table")

// Drop returns an error if the packet should be dropped, explaining why. It
// returns nil if the packet should not be dropped.
func (f *Firewall) Drop(fp firewall.Packet, incoming bool, h *HostInfo, caPool *cert.CAPool, localCache firewall.ConntrackCache) error {
	// Make sure remote address matches nebula certificate, and determine how to treat it
	if h.networks == nil {
		// Simple case: Certificate has one address and no unsafe networks

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Read the inner error after the backtick to get the real cause (e.g. inverted port range)
  2. Fix the rule at the reported index in the firewall.inbound/outbound table so the port range is low-to-high or a single port
  3. Validate all port ranges before loading the config

Example fix

// before (config)
port: 8080-80
// after
port: 80-8080
Defensive patterns

Strategy: validation

Validate before calling

for i, r := range cfg.Firewall.Inbound {
    if p := strings.SplitN(r.Port, "-", 2); len(p) == 2 {
        start, err1 := strconv.Atoi(strings.TrimSpace(p[0]))
        end, err2 := strconv.Atoi(strings.TrimSpace(p[1]))
        if err1 == nil && err2 == nil && start > end {
            return fmt.Errorf("inbound rule #%d: port range %s is inverted", i, r.Port)
        }
    }
}

Type guard

func hasValidPortRange(port string) bool {
    if !strings.Contains(port, "-") { return true }
    parts := strings.SplitN(port, "-", 2)
    s, err1 := strconv.Atoi(strings.TrimSpace(parts[0]))
    e, err2 := strconv.Atoi(strings.TrimSpace(parts[1]))
    return err1 == nil && err2 == nil && s <= e
}

Try / catch

if err := fw.AddFirewallRulesFromConfig(l, inbound, outbound, config); err != nil {
    var wrapped string = err.Error()
    // inner cause is after the backtick: `table rule #N; <cause>`
    return fmt.Errorf("firewall config load failed: %w", err)
}

Prevention

When it happens

Trigger: Calling NewFirewallFromConfig (or TestAddFirewallRulesFromConfig) with a firewall config where a rule passes parsing/validation (addFirewallRulesFromConfig) but fails at install time — most commonly a rule whose port range has start > end (e.g. port: '100-50'), since firewallPort.addRule rejects startPort > endPort.

Common situations: Hand-written YAML where the range was accidentally written high-to-low ('8080-80'); generated configs producing inverted ranges; port 'any' rules mixing with ranges; typos in range separators.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/7792fbdcc04b885d. Report an issue: GitHub.