summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
-rw-r--r--src/etc/sysctl.d/30-vyos-router.conf98
-rw-r--r--src/etc/udev/rules.d/42-qemu-usb.rules14
-rw-r--r--src/etc/udev/rules.d/63-hyperv-vf-net.rules5
-rw-r--r--src/etc/udev/rules.d/64-vyos-vmware-net.rules14
-rw-r--r--src/etc/udev/rules.d/65-vyatta-net.rules26
-rwxr-xr-xsrc/migration-scripts/nat66/0-to-171
-rw-r--r--src/systemd/dropbear@.service5
-rw-r--r--src/systemd/ndppd.service15
-rw-r--r--src/tests/test_template.py19
23 files changed, 812 insertions, 76 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:
diff --git a/src/etc/sysctl.d/30-vyos-router.conf b/src/etc/sysctl.d/30-vyos-router.conf
new file mode 100644
index 000000000..8265e12dc
--- /dev/null
+++ b/src/etc/sysctl.d/30-vyos-router.conf
@@ -0,0 +1,98 @@
+#
+# VyOS specific sysctl settings, see sysctl.conf (5) for information.
+#
+
+# Panic on OOPS
+kernel.panic_on_oops=1
+
+# Timeout before rebooting on panic
+kernel.panic=60
+
+# Send all core files to /var/core/core.program.pid.time
+kernel.core_pattern=/var/core/core-%e-%p-%t
+
+# ARP configuration
+# arp_filter - allow multiple network interfaces on same subnet
+# arp_announce - avoid local addresses no on target's subnet
+# arp_ignore - reply only if target IP is local_address on the interface
+
+# arp_filter defaults to 1 so set all to 0 so vrrp interfaces can override it.
+net.ipv4.conf.all.arp_filter=0
+
+# https://phabricator.vyos.net/T300
+net.ipv4.conf.all.arp_ignore=0
+
+net.ipv4.conf.all.arp_announce=2
+
+# Enable packet forwarding for IPv4
+net.ipv4.ip_forward=1
+
+# if a primary address is removed from an interface promote the
+# secondary address if available
+net.ipv4.conf.all.promote_secondaries=1
+
+# Ignore ICMP broadcasts sent to broadcast/multicast
+net.ipv4.icmp_echo_ignore_broadcasts=1
+
+# Ignore bogus ICMP errors
+net.ipv4.icmp_ignore_bogus_error_responses=1
+
+# Send ICMP responses with primary address of exiting interface
+net.ipv4.icmp_errors_use_inbound_ifaddr=1
+
+# Log packets with impossible addresses to kernel log
+net.ipv4.conf.all.log_martians=1
+
+# Do not ignore all ICMP ECHO requests by default
+net.ipv4.icmp_echo_ignore_all=0
+
+# Disable source validation by default
+net.ipv4.conf.all.rp_filter=0
+net.ipv4.conf.default.rp_filter=0
+
+# Enable tcp syn-cookies by default
+net.ipv4.tcp_syncookies=1
+
+# Disable accept_redirects by default for any interface
+net.ipv4.conf.all.accept_redirects=0
+net.ipv4.conf.default.accept_redirects=0
+net.ipv6.conf.all.accept_redirects=0
+net.ipv6.conf.default.accept_redirects=0
+
+# Disable accept_source_route by default
+net.ipv4.conf.all.accept_source_route=0
+net.ipv4.conf.default.accept_source_route=0
+net.ipv6.conf.all.accept_source_route=0
+net.ipv6.conf.default.accept_source_route=0
+
+# Enable send_redirects by default
+net.ipv4.conf.all.send_redirects=1
+net.ipv4.conf.default.send_redirects=1
+
+# Increase size of buffer for netlink
+net.core.rmem_max=2097152
+
+# Enable packet forwarding for IPv6
+net.ipv6.conf.all.forwarding=1
+
+# Increase route table limit
+net.ipv6.route.max_size = 262144
+
+# Do not forget IPv6 addresses when a link goes down
+net.ipv6.conf.default.keep_addr_on_down=1
+net.ipv6.conf.all.keep_addr_on_down=1
+
+# Default value of 20 seems to interfere with larger OSPF and VRRP setups
+net.ipv4.igmp_max_memberships = 512
+
+# Enable conntrack helper by default
+net.netfilter.nf_conntrack_helper=1
+
+# Increase default garbage collection thresholds
+net.ipv4.neigh.default.gc_thresh1 = 1024
+net.ipv4.neigh.default.gc_thresh2 = 4096
+net.ipv4.neigh.default.gc_thresh3 = 8192
+#
+net.ipv6.neigh.default.gc_thresh1 = 1024
+net.ipv6.neigh.default.gc_thresh2 = 4096
+net.ipv6.neigh.default.gc_thresh3 = 8192
diff --git a/src/etc/udev/rules.d/42-qemu-usb.rules b/src/etc/udev/rules.d/42-qemu-usb.rules
new file mode 100644
index 000000000..a79543df7
--- /dev/null
+++ b/src/etc/udev/rules.d/42-qemu-usb.rules
@@ -0,0 +1,14 @@
+#
+# Enable autosuspend for qemu emulated usb hid devices.
+#
+# Note that there are buggy qemu versions which advertise remote
+# wakeup support but don't actually implement it correctly. This
+# is the reason why we need a match for the serial number here.
+# The serial number "42" is used to tag the implementations where
+# remote wakeup is working.
+#
+# Gerd Hoffmann <kraxel@xxxxxxxxxx>
+
+ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Mouse", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
+ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Tablet", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
+ACTION=="add", SUBSYSTEM=="usb", ATTR{product}=="QEMU USB Keyboard", ATTR{serial}=="42", TEST=="power/control", ATTR{power/control}="auto"
diff --git a/src/etc/udev/rules.d/63-hyperv-vf-net.rules b/src/etc/udev/rules.d/63-hyperv-vf-net.rules
new file mode 100644
index 000000000..b4dcb5a39
--- /dev/null
+++ b/src/etc/udev/rules.d/63-hyperv-vf-net.rules
@@ -0,0 +1,5 @@
+ATTR{[dmi/id]sys_vendor}!="Microsoft Corporation", GOTO="end_hyperv_nic"
+
+ACTION=="add", SUBSYSTEM=="net", DRIVERS=="hv_pci", NAME="vf_%k"
+
+LABEL="end_hyperv_nic"
diff --git a/src/etc/udev/rules.d/64-vyos-vmware-net.rules b/src/etc/udev/rules.d/64-vyos-vmware-net.rules
new file mode 100644
index 000000000..66a4a069b
--- /dev/null
+++ b/src/etc/udev/rules.d/64-vyos-vmware-net.rules
@@ -0,0 +1,14 @@
+ATTR{[dmi/id]sys_vendor}!="VMware, Inc.", GOTO="end_vmware_nic"
+
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet0", ENV{VYOS_IFNAME}="eth0"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet1", ENV{VYOS_IFNAME}="eth1"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet2", ENV{VYOS_IFNAME}="eth2"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet3", ENV{VYOS_IFNAME}="eth3"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet4", ENV{VYOS_IFNAME}="eth4"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet5", ENV{VYOS_IFNAME}="eth5"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet6", ENV{VYOS_IFNAME}="eth6"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet7", ENV{VYOS_IFNAME}="eth7"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet8", ENV{VYOS_IFNAME}="eth8"
+ACTION=="add", SUBSYSTEM=="net", ATTRS{label}=="Ethernet9", ENV{VYOS_IFNAME}="eth9"
+
+LABEL="end_vmware_nic"
diff --git a/src/etc/udev/rules.d/65-vyatta-net.rules b/src/etc/udev/rules.d/65-vyatta-net.rules
new file mode 100644
index 000000000..2b48c1213
--- /dev/null
+++ b/src/etc/udev/rules.d/65-vyatta-net.rules
@@ -0,0 +1,26 @@
+# These rules use vyatta_net_name to persistently name network interfaces
+# per "hwid" association in the Vyatta configuration file.
+
+ACTION!="add", GOTO="vyatta_net_end"
+SUBSYSTEM!="net", GOTO="vyatta_net_end"
+
+# ignore the interface if a name has already been set
+NAME=="?*", GOTO="vyatta_net_end"
+
+# Do name change for ethernet and wireless devices only
+KERNEL!="eth*|wlan*", GOTO="vyatta_net_end"
+
+# ignore "secondary" monitor interfaces of mac80211 drivers
+KERNEL=="wlan*", ATTRS{type}=="803", GOTO="vyatta_net_end"
+
+# If using VyOS predefined names
+ENV{VYOS_IFNAME}!="eth*", GOTO="end_vyos_predef_names"
+
+DRIVERS=="?*", PROGRAM="vyatta_net_name %k $attr{address} $env{VYOS_IFNAME}", NAME="%c", GOTO="vyatta_net_end"
+
+LABEL="end_vyos_predef_names"
+
+# ignore interfaces without a driver link like bridges and VLANs
+DRIVERS=="?*", PROGRAM="vyatta_net_name %k $attr{address}", NAME="%c"
+
+LABEL="vyatta_net_end"
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
diff --git a/src/tests/test_template.py b/src/tests/test_template.py
index 544755692..7800d007f 100644
--- a/src/tests/test_template.py
+++ b/src/tests/test_template.py
@@ -93,3 +93,22 @@ class TestVyOSTemplate(TestCase):
self.assertEqual(vyos.template.dec_ip('2001:db8::b/64', '10'), '2001:db8::1')
self.assertEqual(vyos.template.dec_ip('2001:db8::f', '5'), '2001:db8::a')
+ def test_is_network(self):
+ self.assertFalse(vyos.template.is_ip_network('192.0.2.0'))
+ self.assertFalse(vyos.template.is_ip_network('192.0.2.1/24'))
+ self.assertTrue(vyos.template.is_ip_network('192.0.2.0/24'))
+
+ self.assertFalse(vyos.template.is_ip_network('2001:db8::'))
+ self.assertFalse(vyos.template.is_ip_network('2001:db8::ffff'))
+ self.assertTrue(vyos.template.is_ip_network('2001:db8::/48'))
+ self.assertTrue(vyos.template.is_ip_network('2001:db8:1000::/64'))
+
+ def test_is_network(self):
+ self.assertTrue(vyos.template.compare_netmask('10.0.0.0/8', '20.0.0.0/8'))
+ self.assertTrue(vyos.template.compare_netmask('10.0.0.0/16', '20.0.0.0/16'))
+ self.assertFalse(vyos.template.compare_netmask('10.0.0.0/8', '20.0.0.0/16'))
+ self.assertFalse(vyos.template.compare_netmask('10.0.0.1', '20.0.0.0/16'))
+
+ self.assertTrue(vyos.template.compare_netmask('2001:db8:1000::/48', '2001:db8:2000::/48'))
+ self.assertTrue(vyos.template.compare_netmask('2001:db8:1000::/64', '2001:db8:2000::/64'))
+ self.assertFalse(vyos.template.compare_netmask('2001:db8:1000::/48', '2001:db8:2000::/64'))