summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/conf_mode/nat66.py171
-rwxr-xr-x[-rw-r--r--]src/conf_mode/protocols_ospf.py72
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py123
-rwxr-xr-xsrc/migration-scripts/nat66/0-to-171
-rw-r--r--src/systemd/dropbear@.service5
-rw-r--r--src/systemd/ndppd.service15
6 files changed, 450 insertions, 7 deletions
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
new file mode 100755
index 000000000..dc3cd550c
--- /dev/null
+++ b/src/conf_mode/nat66.py
@@ -0,0 +1,171 @@
+#!/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
+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'])
+ if 'rule' in nat[direction]:
+ 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 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 generate(nat):
+ render(iptables_nat_config, 'firewall/nftables-nat66.tmpl', nat, permission=0o755)
+ render(ndppd_config, 'proxy-ndp/ndppd.conf.tmpl', nat, permission=0o755)
+ return None
+
+def apply(nat):
+ if not nat:
+ return None
+ cmd(f'{iptables_nat_config}')
+ if 'deleted' in nat or not dict_search('source.rule', nat):
+ cmd('systemctl stop ndppd')
+ if os.path.isfile(ndppd_config):
+ os.unlink(ndppd_config)
+ else:
+ 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)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 73c244571..7c0ffbd27 100644..100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -20,16 +20,19 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import dict_merge
+from vyos.configverify import verify_route_maps
from vyos.template import render
from vyos.template import render_to_string
from vyos.util import call
from vyos.util import dict_search
+from vyos.xml import defaults
from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
config_file = r'/tmp/ospf.frr'
+frr_daemon = 'ospfd'
DEBUG = os.path.exists('/tmp/ospf.debug')
if DEBUG:
@@ -39,16 +42,72 @@ if DEBUG:
ch = logging.StreamHandler()
lg.addHandler(ch)
-def get_config():
- conf = Config()
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
base = ['protocols', 'ospf']
ospf = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ return ospf
+
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+
+ # We have to cleanup the default dict, as default values could enable features
+ # which are not explicitly enabled on the CLI. Example: default-information
+ # originate comes with a default metric-type of 2, which will enable the
+ # entire default-information originate tree, even when not set via CLI so we
+ # need to check this first and probably drop that key.
+ if dict_search('default_information.originate', ospf) is None:
+ del default_values['default_information']
+ if dict_search('area.area_type.nssa', ospf) is None:
+ del default_values['area']['area_type']['nssa']
+ if 'mpls_te' not in ospf:
+ del default_values['mpls_te']
+ for protocol in ['bgp', 'connected', 'kernel', 'rip', 'static']:
+ if dict_search(f'redistribute.{protocol}', ospf) is None:
+ del default_values['redistribute'][protocol]
+ # XXX: T2665: we currently have no nice way for defaults under tag nodes,
+ # clean them out and add them manually :(
+ del default_values['neighbor']
+ del default_values['area']['virtual_link']
+
+ # merge in remaining default values
+ ospf = dict_merge(default_values, ospf)
+
+ if 'neighbor' in ospf:
+ default_values = defaults(base + ['neighbor'])
+ for neighbor in ospf['neighbor']:
+ ospf['neighbor'][neighbor] = dict_merge(default_values, ospf['neighbor'][neighbor])
+
+ if 'area' in ospf:
+ default_values = defaults(base + ['area', 'virtual-link'])
+ for area, area_config in ospf['area'].items():
+ if 'virtual_link' in area_config:
+ print(default_values)
+ for virtual_link in area_config['virtual_link']:
+ ospf['area'][area]['virtual_link'][virtual_link] = dict_merge(
+ default_values, ospf['area'][area]['virtual_link'][virtual_link])
+
+ # We also need some additional information from the config, prefix-lists
+ # and route-maps for instance. They will be used in verify()
+ base = ['policy']
+ tmp = conf.get_config_dict(base, key_mangling=('-', '_'))
+ # Merge policy dict into OSPF dict
+ ospf = dict_merge(tmp, ospf)
+
return ospf
def verify(ospf):
if not ospf:
return None
+ verify_route_maps(ospf)
return None
def generate(ospf):
@@ -65,8 +124,9 @@ def generate(ospf):
def apply(ospf):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(daemon='ospfd')
- frr_cfg.modify_section(f'router ospf', '')
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section('router ospf', '')
+ frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospf['new_frr_config'])
# Debugging
if DEBUG:
@@ -82,13 +142,13 @@ def apply(ospf):
print(f'Modified config:\n')
print(f'{frr_cfg}')
- frr_cfg.commit_configuration(daemon='ospfd')
+ frr_cfg.commit_configuration(frr_daemon)
# If FRR config is blank, rerun the blank commit x times due to frr-reload
# behavior/bug not properly clearing out on one commit.
if ospf['new_frr_config'] == '':
for a in range(5):
- frr_cfg.commit_configuration(daemon='ospfd')
+ frr_cfg.commit_configuration(frr_daemon)
return None
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
new file mode 100755
index 000000000..2fb600056
--- /dev/null
+++ b/src/conf_mode/protocols_ospfv3.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 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
+
+from sys import exit
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.configverify import verify_route_maps
+from vyos.template import render
+from vyos.template import render_to_string
+from vyos.util import call
+from vyos.util import dict_search
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+config_file = r'/tmp/ospfv3.frr'
+frr_daemon = 'ospf6d'
+
+DEBUG = os.path.exists('/tmp/ospfv3.debug')
+if DEBUG:
+ import logging
+ lg = logging.getLogger("vyos.frr")
+ lg.setLevel(logging.DEBUG)
+ ch = logging.StreamHandler()
+ lg.addHandler(ch)
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['protocols', 'ospfv3']
+ ospfv3 = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+
+ # Bail out early if configuration tree does not exist
+ if not conf.exists(base):
+ return ospfv3
+
+ # We also need some additional information from the config, prefix-lists
+ # and route-maps for instance. They will be used in verify()
+ base = ['policy']
+ tmp = conf.get_config_dict(base, key_mangling=('-', '_'))
+ # Merge policy dict into OSPF dict
+ ospfv3 = dict_merge(tmp, ospfv3)
+
+ return ospfv3
+
+def verify(ospfv3):
+ if not ospfv3:
+ return None
+
+ verify_route_maps(ospfv3)
+ return None
+
+def generate(ospfv3):
+ if not ospfv3:
+ ospfv3['new_frr_config'] = ''
+ return None
+
+ # render(config) not needed, its only for debug
+ render(config_file, 'frr/ospfv3.frr.tmpl', ospfv3)
+ ospfv3['new_frr_config'] = render_to_string('frr/ospfv3.frr.tmpl', ospfv3)
+
+ return None
+
+def apply(ospfv3):
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(frr_daemon)
+ frr_cfg.modify_section('router ospf6', '')
+ frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', ospfv3['new_frr_config'])
+
+ # Debugging
+ if DEBUG:
+ from pprint import pprint
+ print('')
+ print('--------- DEBUGGING ----------')
+ pprint(dir(frr_cfg))
+ print('Existing config:\n')
+ for line in frr_cfg.original_config:
+ print(line)
+ print(f'Replacement config:\n')
+ print(f'{ospfv3["new_frr_config"]}')
+ print(f'Modified config:\n')
+ print(f'{frr_cfg}')
+
+ frr_cfg.commit_configuration(frr_daemon)
+
+ # If FRR config is blank, re-run the blank commit x times due to frr-reload
+ # behavior/bug not properly clearing out on one commit.
+ if ospfv3['new_frr_config'] == '':
+ for a in range(5):
+ frr_cfg.commit_configuration(frr_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ 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..74d64c07b
--- /dev/null
+++ b/src/migration-scripts/nat66/0-to-1
@@ -0,0 +1,71 @@
+#!/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', 'nptv6']):
+ # Nothing to do
+ 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/dropbear@.service b/src/systemd/dropbear@.service
index 606a7ea6d..a3fde5708 100644
--- a/src/systemd/dropbear@.service
+++ b/src/systemd/dropbear@.service
@@ -4,6 +4,7 @@ Requires=dropbearkey.service
Wants=conserver-server.service
ConditionPathExists=/run/conserver/conserver.cf
After=dropbearkey.service vyos-router.service conserver-server.service
+StartLimitIntervalSec=0
[Service]
Type=forking
@@ -11,4 +12,6 @@ ExecStartPre=/usr/bin/bash -c '/usr/bin/systemctl set-environment PORT=$(cli-she
ExecStart=-/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -c "/usr/bin/console %I" -P /run/conserver/dropbear.%I.pid -p ${PORT}
PIDFile=/run/conserver/dropbear.%I.pid
KillMode=process
-Restart=on-failure
+Restart=always
+RestartSec=10
+RuntimeDirectoryPreserve=yes
diff --git a/src/systemd/ndppd.service b/src/systemd/ndppd.service
new file mode 100644
index 000000000..5790d37f1
--- /dev/null
+++ b/src/systemd/ndppd.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=NDP Proxy Daemon
+After=vyos-router.service
+ConditionPathExists=/run/ndppd/ndppd.conf
+StartLimitIntervalSec=0
+
+[Service]
+Type=forking
+ExecStart=/usr/sbin/ndppd -d -p /run/ndppd/ndppd.pid -c /run/ndppd/ndppd.conf
+PIDFile=/run/ndppd/ndppd.pid
+Restart=on-failure
+RestartSec=20
+
+[Install]
+WantedBy=multi-user.target