Reject duplicate local_port among local forwards; document the two meanings
local_port means different things per direction, and conflating them is easy: a `direction: local` tunnel binds it here (-L local_port:remote_host:remote_port), while a reverse tunnel — the default — uses it as the *destination* on the workstation (-R remote_port:remote_host:local_port) and binds on the remote. Two local forwards on one port do not fail loudly: whichever binds first wins and the losers reconnect forever, so the map reads healthy while the port answers from a different machine than the one asked for. Reject that at config load. Reverse tunnels stay exempt by design — every state-hub-* reverse tunnel targets 8000 on purpose, so each remote box reaches this hub at its own 18000. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
701cb80d89
commit
2461ac3c70
2 changed files with 82 additions and 1 deletions
|
|
@ -297,3 +297,48 @@ class TestCertCommandConfig:
|
|||
monkeypatch.setenv("BRIDGE_CONFIG", str(config_file))
|
||||
cfg = load_config()
|
||||
assert cfg.tunnels["state-hub-coulombcore"].cert_command is None
|
||||
|
||||
|
||||
def test_duplicate_local_ports_are_rejected():
|
||||
"""A local-forward port collision is silent at runtime; fail at load."""
|
||||
from bridge.config import ConfigError, _parse_tunnels
|
||||
|
||||
base = {
|
||||
"host": "192.0.2.1",
|
||||
"remote_port": 18000,
|
||||
"ssh_user": "tegwick",
|
||||
"ssh_key": "~/.ssh/id_ops",
|
||||
"actor": "agt-claude-railiance01",
|
||||
"direction": "local",
|
||||
}
|
||||
with pytest.raises(ConfigError, match="Duplicate local_port"):
|
||||
_parse_tunnels(
|
||||
{
|
||||
"k3s-a": {**base, "local_port": 16443},
|
||||
"k3s-b": {**base, "local_port": 16443},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def test_reverse_tunnels_may_share_a_local_port():
|
||||
"""local_port is the destination on this workstation for a reverse tunnel.
|
||||
|
||||
Every state-hub-* reverse tunnel targets 8000 on purpose, so each remote box
|
||||
reaches this hub at its own 18000. Rejecting that would break the tool.
|
||||
"""
|
||||
from bridge.config import _parse_tunnels
|
||||
|
||||
base = {
|
||||
"host": "192.0.2.1",
|
||||
"remote_port": 18000,
|
||||
"ssh_user": "tegwick",
|
||||
"ssh_key": "~/.ssh/id_ops",
|
||||
"actor": "agt-claude-railiance01",
|
||||
}
|
||||
tunnels = _parse_tunnels(
|
||||
{
|
||||
"state-hub-a": {**base, "local_port": 8000},
|
||||
"state-hub-b": {**base, "local_port": 8000},
|
||||
}
|
||||
)
|
||||
assert {t.local_port for t in tunnels.values()} == {8000}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue