diff options
-rw-r--r-- | data/templates/firewall/nftables-nat66.j2 | 24 | ||||
-rw-r--r-- | interface-definitions/nat66.xml.in | 1 | ||||
-rw-r--r-- | python/vyos/nat.py | 10 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_nat66.py | 30 | ||||
-rwxr-xr-x | src/conf_mode/nat66.py | 28 |
5 files changed, 78 insertions, 15 deletions
diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2 index 67eb2c109..09b5b6ac2 100644 --- a/data/templates/firewall/nftables-nat66.j2 +++ b/data/templates/firewall/nftables-nat66.j2 @@ -1,8 +1,11 @@ #!/usr/sbin/nft -f +{% import 'firewall/nftables-defines.j2' as group_tmpl %} + {% if first_install is not vyos_defined %} delete table ip6 vyos_nat {% endif %} +{% if deleted is not vyos_defined %} table ip6 vyos_nat { # # Destination NAT66 rules build up here @@ -10,11 +13,11 @@ table ip6 vyos_nat { chain PREROUTING { type nat hook prerouting priority -100; policy accept; counter jump VYOS_DNPT_HOOK -{% if destination.rule is vyos_defined %} -{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %} - {{ config | nat_rule(rule, 'destination', ipv6=True) }} -{% endfor %} -{% endif %} +{% if destination.rule is vyos_defined %} +{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %} + {{ config | nat_rule(rule, 'destination', ipv6=True) }} +{% endfor %} +{% endif %} } # @@ -23,11 +26,11 @@ table ip6 vyos_nat { chain POSTROUTING { type nat hook postrouting priority 100; policy accept; counter jump VYOS_SNPT_HOOK -{% if source.rule is vyos_defined %} -{% for rule, config in source.rule.items() if config.disable is not vyos_defined %} +{% if source.rule is vyos_defined %} +{% for rule, config in source.rule.items() if config.disable is not vyos_defined %} {{ config | nat_rule(rule, 'source', ipv6=True) }} -{% endfor %} -{% endif %} +{% endfor %} +{% endif %} } chain VYOS_DNPT_HOOK { @@ -37,4 +40,7 @@ table ip6 vyos_nat { chain VYOS_SNPT_HOOK { return } + +{{ group_tmpl.groups(firewall_group, True, True) }} } +{% endif %} diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in index 32d501cce..c59725c53 100644 --- a/interface-definitions/nat66.xml.in +++ b/interface-definitions/nat66.xml.in @@ -179,6 +179,7 @@ </properties> </leafNode> #include <include/nat-port.xml.i> + #include <include/firewall/source-destination-group-ipv6.xml.i> </children> </node> <node name="source"> diff --git a/python/vyos/nat.py b/python/vyos/nat.py index e54548788..5fab3c2a1 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -199,7 +199,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): if group_name[0] == '!': operator = '!=' group_name = group_name[1:] - output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}') + if ipv6: + output.append(f'{ip_prefix} {prefix}addr {operator} @A6_{group_name}') + else: + output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}') # Generate firewall group domain-group elif 'domain_group' in group and not (ignore_type_addr and target == nat_type): group_name = group['domain_group'] @@ -214,7 +217,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False): if group_name[0] == '!': operator = '!=' group_name = group_name[1:] - output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}') + if ipv6: + output.append(f'{ip_prefix} {prefix}addr {operator} @N6_{group_name}') + else: + output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}') if 'mac_group' in group: group_name = group['mac_group'] operator = '' diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py index e8eeae26f..52ad8e3ef 100755 --- a/smoketest/scripts/cli/test_nat66.py +++ b/smoketest/scripts/cli/test_nat66.py @@ -141,6 +141,36 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip6 vyos_nat') + def test_destination_nat66_network_group(self): + address_group = 'smoketest_addr' + address_group_member = 'fc00::1' + network_group = 'smoketest_net' + network_group_member = 'fc00::/64' + translation_prefix = 'fc01::/64' + + self.cli_set(['firewall', 'group', 'ipv6-address-group', address_group, 'address', address_group_member]) + self.cli_set(['firewall', 'group', 'ipv6-network-group', network_group, 'network', network_group_member]) + + self.cli_set(dst_path + ['rule', '1', 'destination', 'group', 'address-group', address_group]) + self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_prefix]) + + self.cli_set(dst_path + ['rule', '2', 'destination', 'group', 'network-group', network_group]) + self.cli_set(dst_path + ['rule', '2', 'translation', 'address', translation_prefix]) + + self.cli_commit() + + nftables_search = [ + [f'set A6_{address_group}'], + [f'elements = {{ {address_group_member} }}'], + [f'set N6_{network_group}'], + [f'elements = {{ {network_group_member} }}'], + ['ip6 daddr', f'@A6_{address_group}', 'dnat prefix to fc01::/64'], + ['ip6 daddr', f'@N6_{network_group}', 'dnat prefix to fc01::/64'] + ] + + self.verify_nftables(nftables_search, 'ip6 vyos_nat') + + def test_destination_nat66_without_translation_address(self): self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1']) self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443']) diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index c44320f36..95dfae3a5 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -26,6 +26,7 @@ from vyos.utils.dict import dict_search from vyos.utils.kernel import check_kmod from vyos.utils.network import interface_exists from vyos.utils.process import cmd +from vyos.utils.process import run from vyos.template import is_ipv6 from vyos import ConfigError from vyos import airbag @@ -48,6 +49,14 @@ def get_config(config=None): if not conf.exists(base): nat['deleted'] = '' + return nat + + nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True, + no_tag_node_value_mangle=True) + + # Remove dynamic firewall groups if present: + if 'dynamic_group' in nat['firewall_group']: + del nat['firewall_group']['dynamic_group'] return nat @@ -99,22 +108,33 @@ def verify(nat): if not interface_exists(interface_name): Warning(f'Interface "{interface_name}" for destination NAT66 rule "{rule}" does not exist!') + if 'destination' in config and 'group' in config['destination']: + if len({'address_group', 'network_group', 'domain_group'} & set(config['destination']['group'])) > 1: + raise ConfigError('Only one address-group, network-group or domain-group can be specified') + return None def generate(nat): if not os.path.exists(nftables_nat66_config): nat['first_install'] = True - render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755) + render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat) + + # dry-run newly generated configuration + tmp = run(f'nft --check --file {nftables_nat66_config}') + if tmp > 0: + raise ConfigError('Configuration file errors encountered!') + return None def apply(nat): - if not nat: - return None - check_kmod(k_mod) cmd(f'nft --file {nftables_nat66_config}') + + if not nat or 'deleted' in nat: + os.unlink(nftables_nat66_config) + call_dependents() return None |