Fix ensure_postup_dnat to strip-and-replace all DNAT rules idempotently

_get_dnat_container_ips() used a concatenating docker inspect format that
produced "invalid IP" when containers had multiple network attachments.
The old ensure_postup_dnat appended rather than replacing, so each update
call added a broken duplicate set of rules causing iptables to fail on
startup and tear down wg0 entirely.

Fix _get_dnat_container_ips to use a space separator in the format string
and validate each token as a real IP before accepting it.

Rewrite ensure_postup_dnat with _is_dnat_rule() helper: strips every
managed DNAT/FORWARD rule (any IP, port 53/80) on semicolon-split and
appends a single correct set — fully idempotent regardless of prior state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 06:54:20 -04:00
parent d36fe88e16
commit 28a193e430
2 changed files with 64 additions and 34 deletions
+7 -5
View File
@@ -565,11 +565,13 @@ class TestWireGuardSysctlAndPortCheck(unittest.TestCase):
def test_ensure_postup_dnat_idempotent_when_rules_present(self, mock_run):
mock_run.return_value.returncode = 0
mock_run.return_value.stdout = '172.20.0.3'
self._write_wg_conf_postup(
extra_postup='iptables -t nat -A PREROUTING -i %i -p udp --dport 53 -j DNAT --to-destination 172.20.0.3:53'
)
changed = self.wg.ensure_postup_dnat()
self.assertFalse(changed)
self._write_wg_conf_postup()
# First call: writes all 6 DNAT rules
first = self.wg.ensure_postup_dnat()
self.assertTrue(first)
# Second call: rules already correct, no change
second = self.wg.ensure_postup_dnat()
self.assertFalse(second)
def test_ensure_postup_dnat_returns_false_when_no_conf(self):
changed = self.wg.ensure_postup_dnat()