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 networksView on GitHub (pinned to dd8f660c0a)
Solutions
- Read the inner error after the backtick to get the real cause (e.g. inverted port range)
- 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
- 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
- Pre-write port ranges low-to-high in configs
- Validate the full firewall config before passing it to NewFirewallFromConfig
- Include the rule index/table in your own config linting
- Add a CI check that loads the config via the library before deployment
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
- start port was lower than end port
- only one of group or groups should be defined, both provided
- groups spec [%s] contains the group '"any". This rule will i
- groups specified as %s, but host=any will match any host, re
- cidr specified as %s, but host=any will match any host, rega
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/7792fbdcc04b885d.
Report an issue: GitHub.