Skip to content

Commit

Permalink
T6679: add destination groups
Browse files Browse the repository at this point in the history
  • Loading branch information
nvollmar committed Sep 2, 2024
1 parent c78c5bd commit f96733d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 15 deletions.
24 changes: 15 additions & 9 deletions data/templates/firewall/nftables-nat66.j2
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
#!/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
#
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 %}
}

#
Expand All @@ -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 {
Expand All @@ -37,4 +40,7 @@ table ip6 vyos_nat {
chain VYOS_SNPT_HOOK {
return
}

{{ group_tmpl.groups(firewall_group, True, True) }}
}
{% endif %}
1 change: 1 addition & 0 deletions interface-definitions/nat66.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
10 changes: 8 additions & 2 deletions python/vyos/nat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand All @@ -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 = ''
Expand Down
30 changes: 30 additions & 0 deletions smoketest/scripts/cli/test_nat66.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,36 @@ def test_destination_nat66_prefix(self):

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'])
Expand Down
28 changes: 24 additions & 4 deletions src/conf_mode/nat66.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f96733d

Please sign in to comment.