fix(migration): do not emit a NAT translation port for a port list

`set nat destination rule N translation port '16881,6881'` is rejected --
"16881,6881 is not a valid service name" -- because mapping a list of ports
onto a list is ambiguous. `destination port` accepts the same list happily,
which is why this only shows up on the translation side.

All four UniFi port forwards map a port to itself, so translation port was
redundant anyway: omitting it makes VyOS preserve the original port, which is
exactly the intent. It is now emitted only when the forwarded port genuinely
differs, and generation fails loudly rather than producing a config that will
not commit if a differing port LIST ever appears.

Found by loading vyos001's real running config onto the labsim router and
applying the full delta to the candidate config without committing. Worth
noting the delta had already passed a read-through: this one only surfaced by
running it against a real VyOS of the same version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DMVzWZgiKW2wquf5z8S1yH
This commit is contained in:
Michal
2026-08-16 00:02:21 +01:00
parent 7f5d3517a3
commit 01a923352f

View File

@@ -125,8 +125,20 @@ def build_delta(inv: dict, priority: int, wan_user: str) -> list[str]:
f"set nat destination rule {rule} protocol {proto}", f"set nat destination rule {rule} protocol {proto}",
f"set nat destination rule {rule} destination port '{p['dst_port']}'", f"set nat destination rule {rule} destination port '{p['dst_port']}'",
f"set nat destination rule {rule} translation address {p['fwd']}", f"set nat destination rule {rule} translation address {p['fwd']}",
f"set nat destination rule {rule} translation port '{p['fwd_port']}'",
] ]
# `destination port` accepts a comma list but `translation port` does
# NOT -- "16881,6881 is not a valid service name" -- because mapping a
# list onto a list is ambiguous. Every forward here maps a port to
# itself, and omitting translation port makes VyOS preserve the
# original, which is exactly right. Only emit it when it genuinely
# differs, and refuse rather than guess when a differing list appears.
if p["fwd_port"] != p["dst_port"]:
if "," in str(p["fwd_port"]) or "," in str(p["dst_port"]):
raise SystemExit(
f"port forward '{p['name']}' remaps a LIST of ports "
f"({p['dst_port']} -> {p['fwd_port']}). VyOS cannot express "
f"that in one rule; split it into one rule per port by hand.")
out.append(f"set nat destination rule {rule} translation port '{p['fwd_port']}'")
out += [ out += [
"", "",