diff options
| author | Nataliia Solomko <natalirs1985@gmail.com> | 2026-10-30 13:34:13 +0200 |
|---|---|---|
| committer | Nataliia Solomko <natalirs1985@gmail.com> | 2026-10-30 13:34:13 +0200 |
| commit | d5e67f48a76dfd7b48b5ce84f08246d2a6c48489 (patch) | |
| tree | 182d179b37eb1604defb5f874bbfdd73ef5079c6 | |
| parent | fe7a0394f86591695b6f461d42a71c520d053dd9 (diff) | |
| download | vyos-1x-d5e67f48a76dfd7b48b5ce84f08246d2a6c48489.tar.gz vyos-1x-d5e67f48a76dfd7b48b5ce84f08246d2a6c48489.zip | |
nat64: T9536: Fix check for only one source rule
The verify step was meant to allow only one NAT64 netfilter instance, but
it looked for a "mode" node that does not exist in the CLI, so it never
matched. A second source rule was accepted and Jool created a second
netfilter instance without error, although the Jool documentation states
that only one is allowed per network namespace. Which instance translates
traffic is then undefined.
Every rule is created with the netfilter framework, so verify now counts
all configured source rules and rejects the commit when there is more than
one. A dead check that tried to recreate an instance on a "mode" change is
removed for the same reason.
| -rwxr-xr-x | smoketest/scripts/cli/test_nat64.py | 33 | ||||
| -rwxr-xr-x | src/conf_mode/nat64.py | 22 |
2 files changed, 42 insertions, 13 deletions
diff --git a/smoketest/scripts/cli/test_nat64.py b/smoketest/scripts/cli/test_nat64.py index dc0eb0abc..1a0172ccd 100755 --- a/smoketest/scripts/cli/test_nat64.py +++ b/smoketest/scripts/cli/test_nat64.py @@ -108,6 +108,39 @@ class TestNAT64(VyOSUnitTestSHIM.TestCase): else: self.fail(f'Unexpected protocol: {protocol}') + def test_snat64_single_instance(self): + # Pool ports must not overlap the kernel's local port range + tmp = sysctl_read(['net', 'ipv4', 'ip_local_port_range']) + _, local_port_max = map(int, tmp.split()) + pool_port = f'{local_port_max +1}-{local_port_max +1000}' + + rule_100_path = src_path + ['rule', '100'] + self.cli_set(rule_100_path + ['source', 'prefix', '64:ff9b::/96']) + self.cli_set( + rule_100_path + ['translation', 'pool', '10', 'address', '192.0.2.10'] + ) + self.cli_set(rule_100_path + ['translation', 'pool', '10', 'port', pool_port]) + + rule_200_path = src_path + ['rule', '200'] + self.cli_set(rule_200_path + ['source', 'prefix', '64:ff9c::/96']) + self.cli_set( + rule_200_path + ['translation', 'pool', '10', 'address', '192.0.2.20'] + ) + self.cli_set(rule_200_path + ['translation', 'pool', '10', 'port', pool_port]) + + # Jool supports only one netfilter NAT64 instance per network namespace, + # but does not refuse a second one - verify has to reject it + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # Removing the second rule must make the config valid + self.cli_delete(rule_200_path) + self.cli_commit() + + # The rejected commit must not have left an instance for rule 200 behind + self.assertTrue(os.path.exists('/run/jool/instance-100.json')) + self.assertFalse(os.path.exists('/run/jool/instance-200.json')) + if __name__ == '__main__': unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on()) diff --git a/src/conf_mode/nat64.py b/src/conf_mode/nat64.py index 8afeeafe4..7bec596ee 100755 --- a/src/conf_mode/nat64.py +++ b/src/conf_mode/nat64.py @@ -77,11 +77,6 @@ def verify(nat64) -> None: rules[num] = {'deleted': True} continue - # If the user changes the mode, recreate the instance else Jool fails with: - # Jool error: Sorry; you can't change an instance's framework for now. - if config_diff.is_node_changed(base_rule + [f'instance-{num}', 'mode']): - rules[num]['recreate'] = True - # If the user changes the pool6, recreate the instance else Jool fails with: # Jool error: Sorry; you can't change a NAT64 instance's pool6 for now. if dict_search('source.prefix', rules[num]) and config_diff.is_node_changed( @@ -100,15 +95,16 @@ def verify(nat64) -> None: ephemeral_port_min, ephemeral_port_max = map(int, tmp.split()) if dict_search('source.rule', nat64): - # Ensure only 1 netfilter instance per namespace - nf_rules = filter( - lambda i: 'deleted' not in i and i.get('mode') == 'netfilter', - nat64['source']['rule'].values(), - ) - next(nf_rules, None) # Discard the first element - if next(nf_rules, None) is not None: + # https://nicmx.github.io/Jool/en/intro-jool.html + # Every instance uses the netfilter framework, and Jool supports only + # one of those per network namespace. Jool does not refuse a second + # instance, so the limit has to be enforced here. + active_rules = [ + r for r, i in nat64['source']['rule'].items() if 'deleted' not in i + ] + if len(active_rules) > 1: raise ConfigError( - 'Jool permits only 1 NAT64 netfilter instance (per network namespace)' + 'Jool supports only 1 NAT64 netfilter instance (per network namespace)' ) for rule, instance in nat64['source']['rule'].items(): |
