summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/firewall/nftables-nat.tmpl2
-rw-r--r--data/templates/firewall/nftables-nat66.tmpl101
-rw-r--r--data/templates/proxy-ndp/ndppd.conf.tmpl41
-rw-r--r--debian/control3
-rw-r--r--interface-definitions/nat.xml.in67
-rw-r--r--interface-definitions/nat66.xml.in181
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py62
-rwxr-xr-xsrc/conf_mode/nat66.py221
-rwxr-xr-xsrc/migration-scripts/nat66/0-to-176
-rw-r--r--src/systemd/ndppd.service15
11 files changed, 701 insertions, 69 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index 318ab0e14..345460700 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -28,6 +28,7 @@
"ipsec-settings.py",
"lldp.py",
"nat.py",
+"nat66.py",
"ntp.py",
"policy-local-route.py",
"protocols_bgp.py",
diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl
index 5480447f2..499733225 100644
--- a/data/templates/firewall/nftables-nat.tmpl
+++ b/data/templates/firewall/nftables-nat.tmpl
@@ -118,7 +118,7 @@
{% endmacro %}
# Start with clean NAT table
-flush table nat
+flush table ip nat
{% if helper_functions == 'remove' %}
{# NAT if going to be disabled - remove rules and targets from nftables #}
{% set base_command = 'delete rule ip raw' %}
diff --git a/data/templates/firewall/nftables-nat66.tmpl b/data/templates/firewall/nftables-nat66.tmpl
new file mode 100644
index 000000000..af533812e
--- /dev/null
+++ b/data/templates/firewall/nftables-nat66.tmpl
@@ -0,0 +1,101 @@
+#!/usr/sbin/nft -f
+
+{% macro nptv6_rule(rule,config, chain) %}
+
+{% set src_prefix = "ip6 saddr " + config.source.prefix if config.source is defined and config.source.prefix is defined and config.source.prefix is not none %}
+{% set dest_address = "ip6 daddr " + config.destination.address if config.destination is defined and config.destination.address is defined and config.destination.address is not none %}
+
+{% if chain == "PREROUTING" %}
+{% set interface = " iifname \"" + config.inbound_interface + "\"" if config.inbound_interface is defined and config.inbound_interface != 'any' else '' %}
+{% set trns_address = "dnat to " + config.translation.address if config.translation is defined and config.translation.address is defined and config.translation.address is not none %}
+{% elif chain == "POSTROUTING" %}
+{% set interface = " oifname \"" + config.outbound_interface + "\"" if config.outbound_interface is defined and config.outbound_interface != 'any' else '' %}
+{% set trns_prefix = "snat prefix to " + config.translation.prefix if config.translation is defined and config.translation.prefix is defined and config.translation.prefix is not none %}
+{% endif %}
+{% set comment = "NPT-NAT-" + rule %}
+{% if rule.log %}
+{% set base_log = "[NPT-DST-" + rule %}
+{% set log = base_log + "]" %}
+{% endif %}
+{% set output = "add rule ip6 nat " + chain + interface %}
+{# Count packets #}
+{% set output = output + " counter" %}
+
+{# Special handling of log option, we must repeat the entire rule before the #}
+{# NAT translation options are added, this is essential #}
+{% if log %}
+{% set log_output = output + " log prefix \"" + log + "\" comment \"" + comment + "\"" %}
+{% endif %}
+
+{% if src_prefix %}
+{% set output = output + " " + src_prefix %}
+{% endif %}
+
+
+{% if dest_address %}
+{% set output = output + " " + dest_address %}
+{% endif %}
+
+{% if trns_prefix %}
+{% set output = output + " " + trns_prefix %}
+{% endif %}
+
+{% if trns_address %}
+{% set output = output + " " + trns_address %}
+{% endif %}
+
+
+{% if comment %}
+{% set output = output + " comment \"" + comment + "\"" %}
+{% endif %}
+
+{{ log_output if log_output }}
+{{ output }}
+{% endmacro %}
+
+# Start with clean NAT table
+flush table ip6 nat
+
+{% if helper_functions == 'remove' %}
+{# NAT if going to be disabled - remove rules and targets from nftables #}
+
+
+
+{% set base_command = "delete rule ip6 raw" %}
+
+{{base_command}} PREROUTING handle {{ pre_ct_conntrack }}
+{{base_command}} OUTPUT handle {{ out_ct_conntrack }}
+
+delete chain ip6 raw NAT_CONNTRACK
+
+{% elif helper_functions == 'add' %}
+{# NAT if enabled - add targets to nftables #}
+add chain ip6 raw NAT_CONNTRACK
+add rule ip6 raw NAT_CONNTRACK counter accept
+
+{% set base_command = "add rule ip6 raw" %}
+
+
+{{ base_command }} PREROUTING position {{ pre_ct_conntrack }} counter jump NAT_CONNTRACK
+{{ base_command }} OUTPUT position {{ out_ct_conntrack }} counter jump NAT_CONNTRACK
+
+{% endif %}
+
+#
+# Destination NAT66 rules build up here
+#
+{% if destination is defined and destination.rule is defined and destination.rule is not none %}
+{% for rule, config in destination.rule.items() if config.disable is not defined %}
+{{ nptv6_rule(rule, config, 'PREROUTING') }}
+{% endfor %}
+{% endif %}
+
+#
+# Source NAT66 rules build up here
+#
+{% if source is defined and source.rule is defined and source.rule is not none %}
+{% for rule, config in source.rule.items() if config.disable is not defined %}
+{{ nptv6_rule(rule, config, 'POSTROUTING') }}
+{% endfor %}
+{% endif %}
+
diff --git a/data/templates/proxy-ndp/ndppd.conf.tmpl b/data/templates/proxy-ndp/ndppd.conf.tmpl
new file mode 100644
index 000000000..9bf120b3a
--- /dev/null
+++ b/data/templates/proxy-ndp/ndppd.conf.tmpl
@@ -0,0 +1,41 @@
+########################################################
+#
+# autogenerated by nat66.py
+#
+# The configuration file must define one upstream
+# interface.
+#
+# For some services, such as nat66, because it runs
+# stateless, it needs to rely on NDP Proxy to respond
+# to NDP requests.
+#
+# When using nat66 source rules, NDP Proxy needs
+# to be enabled
+#
+########################################################
+
+
+{% for i in interface %}
+
+{%- if not interface[i].disable %}
+
+proxy {{ i }} {
+ router {{ interface[i].router }}
+ timeout {{ interface[i].timeout }}
+ ttl {{ interface[i].ttl }}
+{% for p in interface[i].prefix %}
+ rule {{ p }} {
+{% if interface[i].prefix[p].mode == 'auto' %}
+ auto
+{% elif interface[i].prefix[p].mode == 'static' %}
+ static
+{% elif interface[i].prefix[p].mode == 'iface' and interface[i].prefix[p].iface %}
+ iface {{ interface[i].prefix[p].iface }}
+{% endif %}
+ }
+{%- endfor %}
+}
+
+{%- endif %}
+
+{% endfor %}
diff --git a/debian/control b/debian/control
index d0ba72bcf..2c8ee3d43 100644
--- a/debian/control
+++ b/debian/control
@@ -129,7 +129,8 @@ Depends:
wide-dhcpv6-client,
wireguard-tools,
wireless-regdb,
- wpasupplicant (>= 0.6.7)
+ wpasupplicant (>= 0.6.7),
+ ndppd
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in
index d6bed5b27..3cff8abc9 100644
--- a/interface-definitions/nat.xml.in
+++ b/interface-definitions/nat.xml.in
@@ -56,73 +56,6 @@
</tagNode>
</children>
</node>
- <node name="nptv6">
- <properties>
- <help>IPv6-to-IPv6 Network Prefix Translation Settings</help>
- </properties>
- <children>
- <tagNode name="rule">
- <properties>
- <help>NPTv6 rule number</help>
- <valueHelp>
- <format>1-999999</format>
- <description>Number for this rule</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-999999"/>
- </constraint>
- <constraintErrorMessage>NAT rule number must be between 1 and 999999</constraintErrorMessage>
- </properties>
- <children>
- <leafNode name="description">
- <properties>
- <help>Rule description</help>
- </properties>
- </leafNode>
- #include <include/generic-disable-node.xml.i>
- #include <include/nat-interface.xml.i>
- <node name="source">
- <properties>
- <help>IPv6 source prefix options</help>
- </properties>
- <children>
- <leafNode name="prefix">
- <properties>
- <help>IPv6 prefix to be translated</help>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 prefix</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-prefix"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
- <node name="translation">
- <properties>
- <help>Translated IPv6 prefix options</help>
- </properties>
- <children>
- <leafNode name="prefix">
- <properties>
- <help>IPv6 prefix to translate to</help>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 prefix</description>
- </valueHelp>
- <constraint>
- <validator name="ipv6-prefix"/>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
- </children>
- </tagNode>
- </children>
- </node>
<node name="source">
<properties>
<help>Source NAT settings</help>
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
new file mode 100644
index 000000000..b8e8a8859
--- /dev/null
+++ b/interface-definitions/nat66.xml.in
@@ -0,0 +1,181 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="nat66" owner="${vyos_conf_scripts_dir}/nat66.py">
+ <properties>
+ <help>IPv6-to-IPv6 Network Prefix Translation (NAT66/NPT) Settings</help>
+ <priority>220</priority>
+ </properties>
+ <children>
+ <node name="source">
+ <properties>
+ <help>Prefix mapping of IPv6 source address translation</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>SNPTv6 rule number</help>
+ <valueHelp>
+ <format>1-999999</format>
+ <description>Number for this rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="description">
+ <properties>
+ <help>Rule description</help>
+ </properties>
+ </leafNode>
+ <leafNode name="disable">
+ <properties>
+ <help>Disable NAT66 rule</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="log">
+ <properties>
+ <help>NAT66 rule logging</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="outbound-interface">
+ <properties>
+ <help>Outbound interface of NAT traffic</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <node name="source">
+ <properties>
+ <help>IPv6 source prefix options</help>
+ </properties>
+ <children>
+ <leafNode name="prefix">
+ <properties>
+ <help>IPv6 prefix to be translated</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="translation">
+ <properties>
+ <help>Translated IPv6 prefix options</help>
+ </properties>
+ <children>
+ <leafNode name="prefix">
+ <properties>
+ <help>IPv6 prefix to translate to</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <node name="destination">
+ <properties>
+ <help>Prefix mapping for IPv6 destination address translation</help>
+ </properties>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>DNPTv6 rule number</help>
+ <valueHelp>
+ <format>1-999999</format>
+ <description>Number for this rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-999999"/>
+ </constraint>
+ <constraintErrorMessage>NAT66 rule number must be between 1 and 999999</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="description">
+ <properties>
+ <help>Rule description</help>
+ </properties>
+ </leafNode>
+ <leafNode name="disable">
+ <properties>
+ <help>Disable DNPT rule</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="log">
+ <properties>
+ <help>NAT66 rule logging</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="inbound-interface">
+ <properties>
+ <help>Inbound interface of NAT traffic</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <node name="destination">
+ <properties>
+ <help>IPv6 destination prefix options</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IPv6 address to be translated</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="translation">
+ <properties>
+ <help>Translated IPv6 address options</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>IPv6 address to translate to</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
new file mode 100755
index 000000000..c1514270d
--- /dev/null
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import jmespath
+import json
+import unittest
+
+from vyos.configsession import ConfigSession
+from vyos.util import cmd
+
+base_path = ['nat66']
+snat_pattern = 'nftables[?rule].rule[?chain].{chain: chain, comment: comment, prefix: { network: expr[].match.right.prefix.addr | [0], prefix: expr[].match.right.prefix.len | [0]},saddr: {network: expr[].snat.addr.prefix.addr | [0], prefix: expr[].snat.addr.prefix.len | [0]}}'
+
+class TestNAT66(unittest.TestCase):
+ def setUp(self):
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ self.session = ConfigSession(os.getpid())
+ self.session.delete(base_path)
+
+ def tearDown(self):
+ self.session.delete(base_path)
+ self.session.commit()
+
+ def test_source_nat66(self):
+
+ path = base_path + ['source']
+ source_prefix = 'fc00::/64'
+ translation_prefix = 'fc00:2::/64'
+ self.session.set(path + ['rule', '1', 'outbound-interface', 'eth1'])
+ self.session.set(path + ['rule', '1', 'source', 'prefix', source_prefix])
+ self.session.set(path + ['rule', '1', 'translation', 'prefix', translation_prefix])
+
+ # check validate() - outbound-interface must be defined
+ self.session.commit()
+
+ tmp = cmd('sudo nft -j list table ip6 nat')
+ nftable_json = json.loads(tmp)
+ condensed_json = jmespath.search(snat_pattern, nftable_json)[0]
+
+ self.assertEqual(condensed_json['comment'], 'NPT-NAT-1')
+ self.assertEqual(condensed_json['prefix']['network'], source_prefix.split('/')[0])
+ self.assertEqual(str(condensed_json['prefix']['prefix']), source_prefix.split('/')[1])
+ self.assertEqual(condensed_json['saddr']['network'], translation_prefix.split('/')[0])
+ self.assertEqual(str(condensed_json['saddr']['prefix']), translation_prefix.split('/')[1])
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
new file mode 100755
index 000000000..fdfffe335
--- /dev/null
+++ b/src/conf_mode/nat66.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import jmespath
+import json
+import os
+
+from sys import exit
+from netifaces import interfaces
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.template import render
+from vyos.util import cmd
+from vyos.util import check_kmod
+from vyos.util import dict_search
+from vyos.template import is_ipv6
+from vyos.xml import defaults
+from vyos import ConfigError
+import platform
+from vyos import airbag
+airbag.enable()
+
+k_mod = ['nft_nat', 'nft_chain_nat']
+
+iptables_nat_config = '/tmp/vyos-nat66-rules.nft'
+ndppd_config = '/run/ndppd/ndppd.conf'
+
+def get_handler(json, chain, target):
+ """ Get nftable rule handler number of given chain/target combination.
+ Handler is required when adding NAT66/Conntrack helper targets """
+ for x in json:
+ if x['chain'] != chain:
+ continue
+ if x['target'] != target:
+ continue
+ return x['handle']
+
+ return None
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['nat66']
+ nat = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # T2665: we must add the tagNode defaults individually until this is
+ # moved to the base class
+ for direction in ['source', 'destination']:
+ if direction in nat:
+ default_values = defaults(base + [direction, 'rule'])
+ for rule in nat[direction]['rule']:
+ nat[direction]['rule'][rule] = dict_merge(default_values,
+ nat[direction]['rule'][rule])
+
+ # read in current nftable (once) for further processing
+ tmp = cmd('nft -j list table ip6 raw')
+ nftable_json = json.loads(tmp)
+
+ # condense the full JSON table into a list with only relevand informations
+ pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}'
+ condensed_json = jmespath.search(pattern, nftable_json)
+
+ if not conf.exists(base):
+ nat['helper_functions'] = 'remove'
+
+ nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK')
+ nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT','NAT_CONNTRACK')
+
+ nat['deleted'] = ''
+
+ return nat
+
+ # check if NAT66 connection tracking helpers need to be set up - this has to
+ # be done only once
+ if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'):
+ nat['helper_functions'] = 'add'
+
+ # Retrieve current table handler positions
+ nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK')
+ nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT','VYATTA_CT_OUTPUT_HOOK')
+ else:
+ nat['helper_functions'] = 'has'
+
+ return nat
+
+def verify(nat):
+ if not nat or 'deleted' in nat:
+ # no need to verify the CLI as NAT66 is going to be deactivated
+ return None
+
+ if 'helper_functions' in nat and nat['helper_functions'] != 'has':
+ if not (nat['pre_ct_conntrack'] or nat['out_ct_conntrack']):
+ raise Exception('could not determine nftable ruleset handlers')
+
+ if dict_search('source.rule', nat):
+ for rule, config in dict_search('source.rule', nat).items():
+ err_msg = f'Source NAT66 configuration error in rule {rule}:'
+ if 'outbound_interface' not in config:
+ raise ConfigError(f'{err_msg}\n' \
+ 'outbound-interface not specified')
+ else:
+ if config['outbound_interface'] not in 'any' and config['outbound_interface'] not in interfaces():
+ print(f'WARNING: rule "{rule}" interface "{config["outbound_interface"]}" does not exist on this system')
+
+
+ prefix = dict_search('translation.prefix', config)
+ if prefix != None:
+ if not is_ipv6(prefix):
+ raise ConfigError(f'Warning: IPv6 prefix {prefix} is not a valid address prefix')
+
+ prefix = dict_search('source.prefix', config)
+ if prefix != None:
+ if not is_ipv6(prefix):
+ raise ConfigError(f'{err_msg} source-prefix not specified')
+
+
+ if dict_search('destination.rule', nat):
+ for rule, config in dict_search('destination.rule', nat).items():
+ err_msg = f'Destination NAT66 configuration error in rule {rule}:'
+
+ if 'inbound_interface' not in config:
+ raise ConfigError(f'{err_msg}\n' \
+ 'inbound-interface not specified')
+ else:
+ if config['inbound_interface'] not in 'any' and config['inbound_interface'] not in interfaces():
+ print(f'WARNING: rule "{rule}" interface "{config["inbound_interface"]}" does not exist on this system')
+
+ return None
+
+def nat66_conf_to_ndp_proxy_conf(nat):
+ ndpproxy = {
+ 'interface': {}
+ }
+ if not nat or 'deleted' in nat:
+ # no need to verify the CLI as NAT66 is going to be deactivated
+ return None
+ # Detect and convert the default configuration of the configured interface
+ source_rule = dict_search('source.rule', nat)
+ if source_rule:
+ for rule,config in source_rule.items():
+ interface_ndp = {
+ 'router': 'yes',
+ 'timeout': 500,
+ 'ttl': 30000,
+ 'prefix': {}
+ }
+ if config['outbound_interface'] not in ndpproxy['interface']:
+ ndpproxy['interface'].update({config['outbound_interface']: interface_ndp})
+ for rule,config in source_rule.items():
+ if config['outbound_interface'] in ndpproxy['interface']:
+ prefix = dict_search('translation.prefix', config)
+ if prefix:
+ nat66_prefix = config['translation']['prefix']
+ if nat66_prefix not in ndpproxy['interface'][config['outbound_interface']]['prefix']:
+ ndpproxy['interface'][config['outbound_interface']]['prefix'].update({nat66_prefix: {'mode':'auto'}})
+
+ # Detect and convert the default configuration of the configured interface
+ destination_rule = dict_search('destination.rule', nat)
+ if destination_rule:
+ for rule,config in destination_rule.items():
+ interface_ndp = {
+ 'router': 'yes',
+ 'timeout': 500,
+ 'ttl': 30000,
+ 'prefix': {}
+ }
+ if config['inbound_interface'] not in ndpproxy['interface']:
+ ndpproxy['interface'].update({config['inbound_interface']: interface_ndp})
+ for rule,config in destination_rule.items():
+ if rule['interface'] in ndpproxy['interface']:
+ prefix = dict_search('destination.address', config)
+ if prefix:
+ nat66_address = config['destination']['address']
+ if nat66_prefix not in ndpproxy['interface'][config['inbound_interface']]['prefix']:
+ ndpproxy['interface'][config['inbound_interface']]['prefix'].update({nat66_prefix: {'mode':'auto'}})
+
+ return ndpproxy
+
+def generate(nat,ndp_proxy):
+ render(iptables_nat_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755)
+ if ndp_proxy == None:
+ return None
+ render(ndppd_config, 'proxy-ndp/ndppd.conf.tmpl', ndp_proxy, permission=0o755)
+ return None
+
+def apply(nat):
+ cmd(f'{iptables_nat_config}')
+ cmd('systemctl restart ndppd')
+ if os.path.isfile(iptables_nat_config):
+ os.unlink(iptables_nat_config)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ check_kmod(k_mod)
+ c = get_config()
+ verify(c)
+ ndp_proxy = nat66_conf_to_ndp_proxy_conf(c)
+ generate(c,ndp_proxy)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/migration-scripts/nat66/0-to-1 b/src/migration-scripts/nat66/0-to-1
new file mode 100755
index 000000000..2bc22061d
--- /dev/null
+++ b/src/migration-scripts/nat66/0-to-1
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+def merge_npt(config,base,rule):
+ merge_base = ['nat66','source','rule',rule]
+ # Configure migration functions
+ if config.exists(base + ['description']):
+ tmp = config.return_value(base + ['description'])
+ config.set(merge_base + ['description'],value=tmp)
+
+ if config.exists(base + ['disable']):
+ tmp = config.return_value(base + ['disable'])
+ config.set(merge_base + ['disable'],value=tmp)
+
+ if config.exists(base + ['outbound-interface']):
+ tmp = config.return_value(base + ['outbound-interface'])
+ config.set(merge_base + ['outbound-interface'],value=tmp)
+
+ if config.exists(base + ['source','prefix']):
+ tmp = config.return_value(base + ['source','prefix'])
+ config.set(merge_base + ['source','prefix'],value=tmp)
+
+ if config.exists(base + ['translation','prefix']):
+ tmp = config.return_value(base + ['translation','prefix'])
+ config.set(merge_base + ['translation','prefix'],value=tmp)
+
+
+if not config.exists(['nat']):
+ # Nothing to do
+ exit(0)
+else:
+ if not config.exists(['nat', 'nptv6']):
+ exit(0)
+
+ for rule in config.list_nodes(['nat', 'nptv6', 'rule']):
+ base = ['nat', 'nptv6', 'rule', rule]
+ # Merge 'nat nptv6' to 'nat66 source'
+ merge_npt(config,base,rule)
+
+ # Delete the original NPT configuration
+ config.delete(['nat','nptv6']);
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
+
diff --git a/src/systemd/ndppd.service b/src/systemd/ndppd.service
new file mode 100644
index 000000000..566172fb0
--- /dev/null
+++ b/src/systemd/ndppd.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=NDP Proxy Daemon
+After=network.target vyos-router.service
+ConditionPathExists=/run/ndppd/ndppd.conf
+StartLimitIntervalSec=0
+
+[Service]
+ExecStart=/usr/sbin/ndppd -d -p /var/run/ndppd/ndppd.pid -c /var/run/ndppd/ndppd.conf
+Type=forking
+PIDFile=/var/run/ndppd/ndppd.pid
+Restart=on-failure
+RestartSec=20
+
+[Install]
+WantedBy=multi-user.target