summaryrefslogtreecommitdiff
path: root/src/conf_mode
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode')
-rwxr-xr-xsrc/conf_mode/interfaces-l2tpv3.py6
-rwxr-xr-xsrc/conf_mode/interfaces-macsec.py8
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py12
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py6
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py6
-rwxr-xr-xsrc/conf_mode/interfaces-vxlan.py6
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py6
-rwxr-xr-xsrc/conf_mode/nat.py9
-rwxr-xr-xsrc/conf_mode/nat66.py171
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py71
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py163
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py123
-rwxr-xr-xsrc/conf_mode/ssh.py20
-rwxr-xr-xsrc/conf_mode/system-option.py14
14 files changed, 546 insertions, 75 deletions
diff --git a/src/conf_mode/interfaces-l2tpv3.py b/src/conf_mode/interfaces-l2tpv3.py
index 1118143e4..7b3afa058 100755
--- a/src/conf_mode/interfaces-l2tpv3.py
+++ b/src/conf_mode/interfaces-l2tpv3.py
@@ -47,12 +47,6 @@ def get_config(config=None):
base = ['interfaces', 'l2tpv3']
l2tpv3 = get_interface_dict(conf, base)
- # L2TPv3 is "special" the default MTU is 1488 - update accordingly
- # as the config_level is already st in get_interface_dict() - we can use []
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
- if 'mtu' not in tmp:
- l2tpv3['mtu'] = '1488'
-
# To delete an l2tpv3 interface we need the current tunnel and session-id
if 'deleted' in l2tpv3:
tmp = leaf_node_changed(conf, ['tunnel-id'])
diff --git a/src/conf_mode/interfaces-macsec.py b/src/conf_mode/interfaces-macsec.py
index 2c8367ff3..bfebed7e4 100755
--- a/src/conf_mode/interfaces-macsec.py
+++ b/src/conf_mode/interfaces-macsec.py
@@ -49,14 +49,6 @@ def get_config(config=None):
base = ['interfaces', 'macsec']
macsec = get_interface_dict(conf, base)
- # MACsec is "special" the default MTU is 1460 - update accordingly
- # as the config_level is already st in get_interface_dict() - we can use []
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
- if 'mtu' not in tmp:
- # base MTU for MACsec is 1468 bytes, but we leave room for 802.1ad and
- # 802.1q VLAN tags, thus the limit is 1460 bytes.
- macsec['mtu'] = '1460'
-
# Check if interface has been removed
if 'deleted' in macsec:
source_interface = conf.return_effective_value(['source-interface'])
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index e4a6a5ec1..ee6f05fcd 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -17,6 +17,7 @@
import os
import re
+from glob import glob
from sys import exit
from ipaddress import IPv4Address
from ipaddress import IPv4Network
@@ -488,14 +489,9 @@ def apply(openvpn):
# Do some cleanup when OpenVPN is disabled/deleted
if 'deleted' in openvpn or 'disable' in openvpn:
- # cleanup old configuration files
- cleanup = []
- cleanup.append(cfg_file.format(**openvpn))
- cleanup.append(openvpn['auth_user_pass_file'])
-
- for file in cleanup:
- if os.path.isfile(file):
- os.unlink(file)
+ for cleanup_file in glob(f'/run/openvpn/{interface}.*'):
+ if os.path.isfile(cleanup_file):
+ os.unlink(cleanup_file)
if interface in interfaces():
VTunIf(interface).remove()
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index c31e49574..f49792a7a 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -43,12 +43,6 @@ def get_config(config=None):
base = ['interfaces', 'pppoe']
pppoe = get_interface_dict(conf, base)
- # PPPoE is "special" the default MTU is 1492 - update accordingly
- # as the config_level is already st in get_interface_dict() - we can use []
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
- if 'mtu' not in tmp:
- pppoe['mtu'] = '1492'
-
return pppoe
def verify(pppoe):
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index d2fcf3121..f03bc9d5d 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -57,12 +57,6 @@ def get_config(config=None):
base = ['interfaces', 'tunnel']
tunnel = get_interface_dict(conf, base)
- # Wireguard is "special" the default MTU is 1420 - update accordingly
- # as the config_level is already st in get_interface_dict() - we can use []
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
- if 'mtu' not in tmp:
- tunnel['mtu'] = '1476'
-
tmp = leaf_node_changed(conf, ['encapsulation'])
if tmp: tunnel.update({'encapsulation_changed': {}})
diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py
index 04e258fcf..9a6d72772 100755
--- a/src/conf_mode/interfaces-vxlan.py
+++ b/src/conf_mode/interfaces-vxlan.py
@@ -42,12 +42,6 @@ def get_config(config=None):
base = ['interfaces', 'vxlan']
vxlan = get_interface_dict(conf, base)
- # VXLAN is "special" the default MTU is 1492 - update accordingly
- # as the config_level is already st in get_interface_dict() - we can use []
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
- if 'mtu' not in tmp:
- vxlan['mtu'] = '1450'
-
return vxlan
def verify(vxlan):
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 3e6320f02..024ab8f59 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -46,12 +46,6 @@ def get_config(config=None):
base = ['interfaces', 'wireguard']
wireguard = get_interface_dict(conf, base)
- # Wireguard is "special" the default MTU is 1420 - update accordingly
- # as the config_level is already st in get_interface_dict() - we can use []
- tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
- if 'mtu' not in tmp:
- wireguard['mtu'] = '1420'
-
# Mangle private key - it has a default so its always valid
wireguard['private_key'] = '/config/auth/wireguard/{private_key}/private.key'.format(**wireguard)
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 2d98cb11b..dae958774 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -26,6 +26,7 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.template import render
+from vyos.template import is_ip_network
from vyos.util import cmd
from vyos.util import check_kmod
from vyos.util import dict_search
@@ -68,9 +69,9 @@ def verify_rule(config, err_msg):
'ports can only be specified when protocol is '\
'either tcp, udp or tcp_udp!')
- if '/' in (dict_search('translation.address', config) or []):
+ if is_ip_network(dict_search('translation.address', config)):
raise ConfigError(f'{err_msg}\n' \
- 'Cannot use ports with an IPv4net type translation address as it\n' \
+ 'Cannot use ports with an IPv4 network as translation address as it\n' \
'statically maps a whole network of addresses onto another\n' \
'network of addresses')
@@ -147,7 +148,7 @@ def verify(nat):
addr = dict_search('translation.address', config)
if addr != None:
- if addr != 'masquerade':
+ if addr != 'masquerade' and not is_ip_network(addr):
for ip in addr.split('-'):
if not is_addr_assigned(ip):
print(f'WARNING: IP address {ip} does not exist on the system!')
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_bgp.py b/src/conf_mode/protocols_bgp.py
index de0148b2f..41d89e03b 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -14,6 +14,8 @@
# 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
@@ -28,12 +30,25 @@ from vyos import airbag
airbag.enable()
config_file = r'/tmp/bgp.frr'
-
-def get_config():
- conf = Config()
+frr_daemon = 'bgpd'
+
+DEBUG = os.path.exists('/tmp/bgp.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', 'bgp']
bgp = 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 bgp
@@ -78,8 +93,13 @@ def verify(bgp):
if neighbor == 'neighbor':
# remote-as must be either set explicitly for the neighbor
# or for the entire peer-group
- if 'remote_as' not in peer_config:
- if 'peer_group' not in peer_config or 'remote_as' not in asn_config['peer_group'][peer_config['peer_group']]:
+ if 'interface' in peer_config:
+ if 'remote_as' not in peer_config['interface']:
+ if 'peer_group' not in peer_config['interface'] or 'remote_as' not in asn_config['peer_group'][ peer_config['interface']['peer_group'] ]:
+ raise ConfigError('Remote AS must be set for neighbor or peer-group!')
+
+ elif 'remote_as' not in peer_config:
+ if 'peer_group' not in peer_config or 'remote_as' not in asn_config['peer_group'][ peer_config['peer_group'] ]:
raise ConfigError('Remote AS must be set for neighbor or peer-group!')
for afi in ['ipv4_unicast', 'ipv6_unicast']:
@@ -94,7 +114,7 @@ def verify(bgp):
if tmp not in afi_config['prefix_list']:
# bail out early
continue
- # get_config_dict() mangles all '-' characters to '_' this is legitim, thus all our
+ # get_config_dict() mangles all '-' characters to '_' this is legitimate, thus all our
# compares will run on '_' as also '_' is a valid name for a prefix-list
prefix_list = afi_config['prefix_list'][tmp].replace('-', '_')
if afi == 'ipv4_unicast':
@@ -113,6 +133,16 @@ def verify(bgp):
if dict_search(f'policy.route_map.{route_map}', asn_config) == None:
raise ConfigError(f'route-map "{route_map}" used for "{tmp}" does not exist!')
+ # Throw an error if a peer group is not configured for allow range
+ for prefix in dict_search('listen.range', asn_config) or []:
+ # we can not use dict_search() here as prefix contains dots ...
+ if 'peer_group' not in asn_config['listen']['range'][prefix]:
+ raise ConfigError(f'Listen range for prefix "{prefix}" has no peer group configured.')
+ else:
+ peer_group = asn_config['listen']['range'][prefix]['peer_group']
+ # the peer group must also exist
+ if not dict_search(f'peer_group.{peer_group}', asn_config):
+ raise ConfigError(f'Peer-group "{peer_group}" for listen range "{prefix}" does not exist!')
return None
@@ -135,25 +165,32 @@ def generate(bgp):
def apply(bgp):
# Save original configuration prior to starting any commit actions
frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(daemon='bgpd')
+ frr_cfg.load_configuration(frr_daemon)
frr_cfg.modify_section(f'router bgp \S+', '')
frr_cfg.add_before(r'(ip prefix-list .*|route-map .*|line vty)', bgp['new_frr_config'])
- frr_cfg.commit_configuration(daemon='bgpd')
+
+ # 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'{bgp["new_frr_config"]}')
+ print(f'Modified config:\n')
+ print(f'{frr_cfg}')
+
+ 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 bgp['new_frr_config'] == '':
for a in range(5):
- frr_cfg.commit_configuration(daemon='bgpd')
+ frr_cfg.commit_configuration(frr_daemon)
- # Debugging
- '''
- print('')
- print('--------- DEBUGGING ----------')
- print(f'Existing config:\n{frr_cfg["original_config"]}\n\n')
- print(f'Replacement config:\n{bgp["new_frr_config"]}\n\n')
- print(f'Modified config:\n{frr_cfg["modified_config"]}\n\n')
- '''
return None
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
new file mode 100755
index 000000000..7c0ffbd27
--- /dev/null
+++ b/src/conf_mode/protocols_ospf.py
@@ -0,0 +1,163 @@
+#!/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/ospf.frr'
+frr_daemon = 'ospfd'
+
+DEBUG = os.path.exists('/tmp/ospf.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', '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):
+ if not ospf:
+ ospf['new_frr_config'] = ''
+ return None
+
+ # render(config) not needed, its only for debug
+ render(config_file, 'frr/ospf.frr.tmpl', ospf)
+ ospf['new_frr_config'] = render_to_string('frr/ospf.frr.tmpl', ospf)
+
+ return None
+
+def apply(ospf):
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ 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:
+ 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'{ospf["new_frr_config"]}')
+ print(f'Modified config:\n')
+ print(f'{frr_cfg}')
+
+ 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(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/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/conf_mode/ssh.py b/src/conf_mode/ssh.py
index 8eeb0a7c1..67724b043 100755
--- a/src/conf_mode/ssh.py
+++ b/src/conf_mode/ssh.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -17,6 +17,8 @@
import os
from sys import exit
+from syslog import syslog
+from syslog import LOG_INFO
from vyos.config import Config
from vyos.configdict import dict_merge
@@ -31,6 +33,10 @@ airbag.enable()
config_file = r'/run/sshd/sshd_config'
systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf'
+key_rsa = '/etc/ssh/ssh_host_rsa_key'
+key_dsa = '/etc/ssh/ssh_host_dsa_key'
+key_ed25519 = '/etc/ssh/ssh_host_ed25519_key'
+
def get_config(config=None):
if config:
conf = config
@@ -66,6 +72,18 @@ def generate(ssh):
return None
+ # This usually happens only once on a fresh system, SSH keys need to be
+ # freshly generted, one per every system!
+ if not os.path.isfile(key_rsa):
+ syslog(LOG_INFO, 'SSH RSA host key not found, generating new key!')
+ call(f'ssh-keygen -q -N "" -t rsa -f {key_rsa}')
+ if not os.path.isfile(key_dsa):
+ syslog(LOG_INFO, 'SSH DSA host key not found, generating new key!')
+ call(f'ssh-keygen -q -N "" -t dsa -f {key_dsa}')
+ if not os.path.isfile(key_ed25519):
+ syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!')
+ call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}')
+
render(config_file, 'ssh/sshd_config.tmpl', ssh)
render(systemd_override, 'ssh/override.conf.tmpl', ssh)
# Reload systemd manager configuration
diff --git a/src/conf_mode/system-option.py b/src/conf_mode/system-option.py
index 910c14474..454611c55 100755
--- a/src/conf_mode/system-option.py
+++ b/src/conf_mode/system-option.py
@@ -87,10 +87,10 @@ def apply(options):
# Ctrl-Alt-Delete action
if os.path.exists(systemd_action_file):
os.unlink(systemd_action_file)
- if 'ctrl_alt_del' in options:
- if options['ctrl_alt_del'] == 'reboot':
+ if 'ctrl_alt_delete' in options:
+ if options['ctrl_alt_delete'] == 'reboot':
os.symlink('/lib/systemd/system/reboot.target', systemd_action_file)
- elif options['ctrl_alt_del'] == 'poweroff':
+ elif options['ctrl_alt_delete'] == 'poweroff':
os.symlink('/lib/systemd/system/poweroff.target', systemd_action_file)
# Configure HTTP client
@@ -104,11 +104,11 @@ def apply(options):
os.unlink(ssh_config)
# Reboot system on kernel panic
+ timeout = '0'
+ if 'reboot_on_panic' in options:
+ timeout = '60'
with open('/proc/sys/kernel/panic', 'w') as f:
- if 'reboot_on_panic' in options:
- f.write('60')
- else:
- f.write('0')
+ f.write(timeout)
# tuned - performance tuning
if 'performance' in options: