fix: stop deriving NAT translations from IPv6 pinholes
Closes netork#116. `get_port_forwards` reports IPv4 mappings and IPv6 pinholes together, which is right — both are inbound rules someone configured. `get_nat_translations` then iterated all of them, and that is not: a pinhole is a firewall hole, and IPv6 does not do NAT at all. The entries it produced were nonsense in two ways. They paired an IPv6 host with the IPv4 WAN address, and `inside_local` came out as "2001:db8::10:22", where the last colon is a port separator and everything before it is an address that also contains colons. The two failing tests were pulling in opposite directions. `TestGetNatTranslations` was right and the implementation had drifted past it when pinhole support landed. `TestGetPortForwards::test_returns_all_rules` was the opposite: it still expected one rule from before pinholes existed, which put it in direct contradiction with TestIPv6Pinholes further down the same file.
This commit is contained in:
@@ -618,12 +618,21 @@ class FritzBoxDriver(ResidentialGatewayDriver):
|
|||||||
TR-064 does not expose a live connection-tracking table; this
|
TR-064 does not expose a live connection-tracking table; this
|
||||||
derives static NAT-PT entries from the enabled port forwarding
|
derives static NAT-PT entries from the enabled port forwarding
|
||||||
rules, which is the closest equivalent FritzBox provides.
|
rules, which is the closest equivalent FritzBox provides.
|
||||||
|
|
||||||
|
IPv6 pinholes are skipped. ``get_port_forwards`` reports them alongside
|
||||||
|
the IPv4 mappings because both are inbound rules a user configured, but
|
||||||
|
a pinhole is a firewall hole, not a translation — IPv6 does not do NAT.
|
||||||
|
Including them produced entries pairing an IPv6 host with the IPv4 WAN
|
||||||
|
address, and an ``inside_local`` of ``2001:db8::10:22`` where the last
|
||||||
|
colon is a port separator and the rest is an address.
|
||||||
"""
|
"""
|
||||||
ext_ip = self._wan_external_ip()
|
ext_ip = self._wan_external_ip()
|
||||||
translations: list[NATTranslationDict] = []
|
translations: list[NATTranslationDict] = []
|
||||||
for forward in self.get_port_forwards():
|
for forward in self.get_port_forwards():
|
||||||
if not forward["enabled"]:
|
if not forward["enabled"]:
|
||||||
continue
|
continue
|
||||||
|
if ":" in forward["internal_ip"]:
|
||||||
|
continue
|
||||||
remote = forward.get("remote_host") or "0.0.0.0"
|
remote = forward.get("remote_host") or "0.0.0.0"
|
||||||
translations.append(
|
translations.append(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -480,8 +480,15 @@ class TestGetWanStatus:
|
|||||||
|
|
||||||
|
|
||||||
class TestGetPortForwards:
|
class TestGetPortForwards:
|
||||||
def test_returns_all_rules(self, driver):
|
def test_returns_ipv4_mappings_and_ipv6_pinholes(self, driver):
|
||||||
assert len(driver.get_port_forwards()) == 1
|
"""One IPv4 mapping plus one IPv6 pinhole.
|
||||||
|
|
||||||
|
This asserted 1 from before pinhole support existed, which put it in
|
||||||
|
direct contradiction with TestIPv6Pinholes further down the same file.
|
||||||
|
"""
|
||||||
|
forwards = driver.get_port_forwards()
|
||||||
|
assert len(forwards) == 2
|
||||||
|
assert {f["protocol"] for f in forwards} == {"TCP", "TCP6"}
|
||||||
|
|
||||||
def test_rule_fields(self, driver):
|
def test_rule_fields(self, driver):
|
||||||
forward = driver.get_port_forwards()[0]
|
forward = driver.get_port_forwards()[0]
|
||||||
@@ -497,7 +504,10 @@ class TestGetPortForwards:
|
|||||||
|
|
||||||
|
|
||||||
class TestGetNatTranslations:
|
class TestGetNatTranslations:
|
||||||
def test_derived_from_enabled_port_forwards(self, driver):
|
def test_derived_from_enabled_ipv4_port_forwards(self, driver):
|
||||||
|
"""Only the IPv4 mapping. IPv6 pinholes are firewall holes, not
|
||||||
|
translations, and IPv6 does not do NAT — including them paired an IPv6
|
||||||
|
host with the IPv4 WAN address."""
|
||||||
translations = driver.get_nat_translations()
|
translations = driver.get_nat_translations()
|
||||||
assert len(translations) == 1
|
assert len(translations) == 1
|
||||||
assert translations[0]["protocol"] == "tcp"
|
assert translations[0]["protocol"] == "tcp"
|
||||||
@@ -740,6 +750,12 @@ class TestIPv6Pinholes:
|
|||||||
assert ssh6["internal_port"] == 22
|
assert ssh6["internal_port"] == 22
|
||||||
assert ssh6["enabled"] is True
|
assert ssh6["enabled"] is True
|
||||||
|
|
||||||
|
def test_pinholes_are_not_reported_as_nat_translations(self, driver):
|
||||||
|
"""A pinhole opens the firewall for a globally routable address; there
|
||||||
|
is nothing to translate."""
|
||||||
|
assert all(":" not in t["inside_local"].rsplit(":", 1)[0]
|
||||||
|
for t in driver.get_nat_translations())
|
||||||
|
|
||||||
def test_no_pinholes_without_ipv6_service(self, driver):
|
def test_no_pinholes_without_ipv6_service(self, driver):
|
||||||
del driver.fc.services["WANIPv6Firewall1"]
|
del driver.fc.services["WANIPv6Firewall1"]
|
||||||
forwards = driver.get_port_forwards()
|
forwards = driver.get_port_forwards()
|
||||||
|
|||||||
Reference in New Issue
Block a user