netbirdio/netbird · error
invalid translated port: %v
Error message
invalid translated port: %v
What it means
handleTranslatedPort dispatches on rule.TranslatedPort: two values with IsRange map to a range, zero values to address-only DNAT, one value to a single port. Any other shape (three or more values, or IsRange with one value) falls to default and returns 'invalid translated port' printing the whole Port struct. It is a deterministic validation error raised while building expressions, before any netlink message is queued.
Source
Thrown at client/firewall/nftables/router_linux.go:1668
Exprs: dnatExprs,
UserData: []byte(ruleKey + dnatSuffix),
}
r.conn.AddRule(dnatRule)
r.rules[ruleKey+dnatSuffix] = dnatRule
return nil
}
func (r *router) handleTranslatedPort(rule firewall.ForwardRule) ([]expr.Any, uint32, uint32, error) {
switch {
case rule.TranslatedPort.IsRange && len(rule.TranslatedPort.Values) == 2:
return r.handlePortRange(rule)
case len(rule.TranslatedPort.Values) == 0:
return r.handleAddressOnly(rule)
case len(rule.TranslatedPort.Values) == 1:
return r.handleSinglePort(rule)
default:
return nil, 0, 0, fmt.Errorf("invalid translated port: %v", rule.TranslatedPort)
}
}
func (r *router) handlePortRange(rule firewall.ForwardRule) ([]expr.Any, uint32, uint32, error) {
exprs := []expr.Any{
&expr.Immediate{
Register: 1,
Data: rule.TranslatedAddress.AsSlice(),
},
&expr.Immediate{
Register: 2,
Data: binaryutil.BigEndian.PutUint16(rule.TranslatedPort.Values[0]),
},
&expr.Immediate{
Register: 3,
Data: binaryutil.BigEndian.PutUint16(rule.TranslatedPort.Values[1]),
},
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Validate the TranslatedPort shape before building rules: exactly 0, 1, or (IsRange with exactly 2) values.
- Map many ports to one translated port by emitting one ForwardRule per port, not one rule with 3+ values.
- Always set IsRange=true when supplying two boundary ports.
- Print the rule's Port struct (the %v payload shows IsRange/Values) to find the misbehaving producer.
Example fix
// before: two ports without IsRange -> default case -> error
rule.TranslatedPort = firewall.Port{Values: []uint16{8080, 8443}}
// after: either a proper range
rule.TranslatedPort = firewall.Port{IsRange: true, Values: []uint16{8080, 8443}}
// or one rule per port
ruleA.TranslatedPort = firewall.Port{Values: []uint16{8080}}
ruleB.TranslatedPort = firewall.Port{Values: []uint16{8443}} Defensive patterns
Strategy: validation
Validate before calling
func validTranslatedPort(p firewall.Port) bool {
switch {
case len(p.Values) == 0:
return true // address-only DNAT
case len(p.Values) == 1:
return true
case p.IsRange && len(p.Values) == 2:
return p.Values[0] <= p.Values[1]
}
return false
}
// use before creating forward rules
if !validTranslatedPort(rule.TranslatedPort) {
return fmt.Errorf("invalid translated port shape: %+v", rule.TranslatedPort)
} Type guard
func isRangePort(p firewall.Port) bool {
return p.IsRange && len(p.Values) == 2 && p.Values[0] <= p.Values[1]
} Try / catch
if _, _, _, err := r.handleTranslatedPort(rule); err != nil {
// deterministic config error: fix the producer, do not retry
return fmt.Errorf("rejecting forward rule with malformed translated port %+v: %w", rule.TranslatedPort, err)
} Prevention
- Constrain port-forwarding config schemas to 0, 1, or exactly 2 boundary ports with an explicit range flag.
- Emit one rule per port when mapping multiple ports to one target.
- Validate port shapes in management/CLI code so the firewall backend never sees them.
When it happens
Trigger: A ForwardRule whose TranslatedPort.Values has length 3 or more, or IsRange=true with a single value, reaches addDnatRedirect: hand-built rules or a management payload that filled several ports without setting IsRange.
Common situations: Port-forwarding APIs that forward a list of ports to one translated port; version skew introducing new port shapes; test fixtures writing Port{Values: []uint16{80, 8080}} and forgetting IsRange.
Related errors
- start port forwarding: %w
- local port forward %s: %w
- remote port forward %s: %w
- invalid remote address: %w
- invalid local address: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/43a881f018c5224d.
Report an issue: GitHub.