diff options
-rw-r--r-- | data/templates/dhcp-server/dhcpd.conf.tmpl | 2 | ||||
-rw-r--r-- | interface-definitions/policy-local-route.xml.in | 109 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_policy.py | 258 | ||||
-rwxr-xr-x | src/conf_mode/policy-local-route.py | 150 | ||||
-rwxr-xr-x | src/helpers/vyos_net_name | 21 |
5 files changed, 463 insertions, 77 deletions
diff --git a/data/templates/dhcp-server/dhcpd.conf.tmpl b/data/templates/dhcp-server/dhcpd.conf.tmpl index 233e2cc53..da2f28ced 100644 --- a/data/templates/dhcp-server/dhcpd.conf.tmpl +++ b/data/templates/dhcp-server/dhcpd.conf.tmpl @@ -165,7 +165,7 @@ shared-network {{ network | replace('_','-') }} { option wpad-url "{{ subnet_config.wpad_url }}"; {% endif %} {% if subnet_config.client_prefix_length is defined and subnet_config.client_prefix_length is not none %} - option subnet-mask {{ subnet_config.client_prefix_length }}; + option subnet-mask {{ ('0.0.0.0/' ~ subnet_config.client_prefix_length) | netmask_from_cidr }}; {% endif %} {% if subnet_config.lease is defined and subnet_config.lease is not none %} default-lease-time {{ subnet_config.lease }}; diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index 86445b65d..11b1e04d9 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -14,7 +14,7 @@ <valueHelp> <!-- table main with prio 32766 --> <format>u32:1-32765</format> - <description>Local-route rule number (1-219)</description> + <description>Local-route rule number (1-32765)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-32765"/> @@ -70,6 +70,113 @@ <multi/> </properties> </leafNode> + <leafNode name="destination"> + <properties> + <help>Destination address or prefix</help> + <valueHelp> + <format>ipv4</format> + <description>Address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>Prefix to match against</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + <validator name="ip-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </tagNode> + </children> + </node> + <node name="local-route6" owner="${vyos_conf_scripts_dir}/policy-local-route.py"> + <properties> + <help>IPv6 policy route of local traffic</help> + </properties> + <children> + <tagNode name="rule"> + <properties> + <help>IPv6 policy local-route rule set number</help> + <valueHelp> + <!-- table main with prio 32766 --> + <format>u32:1-32765</format> + <description>Local-route rule number (1-32765)</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-32765"/> + </constraint> + </properties> + <children> + <node name="set"> + <properties> + <help>Packet modifications</help> + </properties> + <children> + <leafNode name="table"> + <properties> + <help>Routing table to forward packet with</help> + <valueHelp> + <format>u32:1-200</format> + <description>Table number</description> + </valueHelp> + <completionHelp> + <list>main</list> + </completionHelp> + </properties> + </leafNode> + </children> + </node> + <leafNode name="fwmark"> + <properties> + <help>Match fwmark value</help> + <valueHelp> + <format>u32:1-2147483647</format> + <description>Address to match against</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-2147483647"/> + </constraint> + </properties> + </leafNode> + <leafNode name="source"> + <properties> + <help>Source address or prefix</help> + <valueHelp> + <format>ipv4</format> + <description>Address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv4net</format> + <description>Prefix to match against</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="destination"> + <properties> + <help>Destination address or prefix</help> + <valueHelp> + <format>ipv6</format> + <description>Address to match against</description> + </valueHelp> + <valueHelp> + <format>ipv6net</format> + <description>Prefix to match against</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="ipv6-prefix"/> + </constraint> + <multi/> + </properties> + </leafNode> </children> </tagNode> </children> diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 5844e1ec1..d055762f4 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1143,10 +1143,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 50: from 203.0.113.2 lookup 23 """ tmp = cmd('ip rule show prio 50') - original = original.split() - tmp = tmp.split() - self.assertEqual(tmp, original) + self.assertEqual(sort_ip(tmp), sort_ip(original)) # Test set table for fwmark def test_fwmark_table_id(self): @@ -1168,10 +1166,31 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 101: from all fwmark 0x18 lookup 154 """ tmp = cmd('ip rule show prio 101') - original = original.split() - tmp = tmp.split() - self.assertEqual(tmp, original) + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination + def test_destination_table_id(self): + path = base_path + ['local-route'] + + dst = '203.0.113.1' + rule = '102' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 102: from all to 203.0.113.1 lookup 154 + """ + tmp = cmd('ip rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) # Test set table for sources with fwmark def test_fwmark_sources_table_id(self): @@ -1196,10 +1215,231 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 100: from 203.0.113.12 fwmark 0x17 lookup 150 """ tmp = cmd('ip rule show prio 100') - original = original.split() - tmp = tmp.split() - self.assertEqual(tmp, original) + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources and destinations with fwmark + def test_fwmark_sources_destination_table_id(self): + path = base_path + ['local-route'] + + sources = ['203.0.113.11', '203.0.113.12'] + destinations = ['203.0.113.13', '203.0.113.15'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 103: from 203.0.113.11 to 203.0.113.13 fwmark 0x17 lookup 150 + 103: from 203.0.113.11 to 203.0.113.15 fwmark 0x17 lookup 150 + 103: from 203.0.113.12 to 203.0.113.13 fwmark 0x17 lookup 150 + 103: from 203.0.113.12 to 203.0.113.15 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table ipv6 for some sources ipv6 + def test_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:123::/48', '2001:db8:126::/48'] + rule = '50' + table = '23' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 50: from 2001:db8:123::/48 lookup 23 + 50: from 2001:db8:126::/48 lookup 23 + """ + tmp = cmd('ip -6 rule show prio 50') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for fwmark ipv6 + def test_fwmark_ipv6_table_id(self): + path = base_path + ['local-route6'] + + fwmk = '24' + rule = '100' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 100: from all fwmark 0x18 lookup 154 + """ + tmp = cmd('ip -6 rule show prio 100') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for destination ipv6 + def test_destination_ipv6_table_id(self): + path = base_path + ['local-route6'] + + dst = '2001:db8:1337::/126' + rule = '101' + table = '154' + + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 101: from all to 2001:db8:1337::/126 lookup 154 + """ + tmp = cmd('ip -6 rule show prio 101') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources with fwmark ipv6 + def test_fwmark_sources_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/126'] + fwmk = '23' + rule = '102' + table = '150' + for src in sources: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 102: from 2001:db8:1338::/126 fwmark 0x17 lookup 150 + 102: from 2001:db8:1339::/126 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip -6 rule show prio 102') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test set table for sources and destinations with fwmark ipv6 + def test_fwmark_sources_destination_ipv6_table_id(self): + path = base_path + ['local-route6'] + + sources = ['2001:db8:1338::/126', '2001:db8:1339::/56'] + destinations = ['2001:db8:13::/48', '2001:db8:16::/48'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 103: from 2001:db8:1338::/126 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip -6 rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + + # Test delete table for sources and destination with fwmark ipv4/ipv6 + def test_delete_ipv4_ipv6_table_id(self): + path = base_path + ['local-route'] + path_v6 = base_path + ['local-route6'] + + sources = ['203.0.113.0/24', '203.0.114.5'] + destinations = ['203.0.112.0/24', '203.0.116.5'] + sources_v6 = ['2001:db8:1338::/126', '2001:db8:1339::/56'] + destinations_v6 = ['2001:db8:13::/48', '2001:db8:16::/48'] + fwmk = '23' + rule = '103' + table = '150' + for src in sources: + for dst in destinations: + self.cli_set(path + ['rule', rule, 'set', 'table', table]) + self.cli_set(path + ['rule', rule, 'source', src]) + self.cli_set(path + ['rule', rule, 'destination', dst]) + self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) + + for src in sources_v6: + for dst in destinations_v6: + self.cli_set(path_v6 + ['rule', rule, 'set', 'table', table]) + self.cli_set(path_v6 + ['rule', rule, 'source', src]) + self.cli_set(path_v6 + ['rule', rule, 'destination', dst]) + self.cli_set(path_v6 + ['rule', rule, 'fwmark', fwmk]) + + self.cli_commit() + + # Check generated configuration + + # Expected values + original = """ + 103: from 203.0.113.1/24 to 203.0.112.1/24 fwmark 0x17 lookup 150 + 103: from 203.0.113.1/24 to 203.0.116.5 fwmark 0x17 lookup 150 + 103: from 203.0.114.5 to 203.0.112.1/24 fwmark 0x17 lookup 150 + 103: from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150 + """ + original_v6 = """ + 103: from 20016 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1338::/126 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:13::/48 fwmark 0x17 lookup 150 + 103: from 2001:db8:1339::/56 to 2001:db8:16::/48 fwmark 0x17 lookup 150 + """ + tmp = cmd('ip rule show prio 103') + tmp_v6 = cmd('ip -6 rule show prio 103') + + self.assertEqual(sort_ip(tmp), sort_ip(original)) + self.assertEqual(sort_ip(tmp_v6), sort_ip(original_v6)) + + self.cli_delete(path) + self.cli_delete(path_v6) + self.cli_commit() + + tmp = cmd('ip rule show prio 103') + tmp_v6 = cmd('ip -6 rule show prio 103') + + original = None + original_v6 = None + + self.assertEqual(sort_ip(tmp), original) + self.assertEqual(sort_ip(tmp_v6), original_v6) + +def sort_ip(output): + return output.splitlines().sort() if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/policy-local-route.py b/src/conf_mode/policy-local-route.py index 539189442..6dabb37ae 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -35,34 +35,53 @@ def get_config(config=None): conf = config else: conf = Config() - base = ['policy', 'local-route'] + base = ['policy'] + pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - # delete policy local-route - dict = {} - tmp = node_changed(conf, ['policy', 'local-route', 'rule'], key_mangling=('-', '_')) - if tmp: - for rule in (tmp or []): - src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) - fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) - if src: - dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) - pbr.update(dict) - if fwmk: - dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + for route in ['local_route', 'local_route6']: + dict_id = 'rule_remove' if route == 'local_route' else 'rule6_remove' + route_key = 'local-route' if route == 'local_route' else 'local-route6' + base_rule = base + [route_key, 'rule'] + + # delete policy local-route + dict = {} + tmp = node_changed(conf, base_rule, key_mangling=('-', '_')) + if tmp: + for rule in (tmp or []): + src = leaf_node_changed(conf, base_rule + [rule, 'source']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) + rule_def = {} + if src: + rule_def = dict_merge({'source' : src}, rule_def) + if fwmk: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if dst: + rule_def = dict_merge({'destination' : dst}, rule_def) + dict = dict_merge({dict_id : {rule : rule_def}}, dict) pbr.update(dict) - # delete policy local-route rule x source x.x.x.x - # delete policy local-route rule x fwmark x - if 'rule' in pbr: - for rule in pbr['rule']: - src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) - fwmk = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'fwmark']) - if src: - dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) - pbr.update(dict) - if fwmk: - dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + if not route in pbr: + continue + + # delete policy local-route rule x source x.x.x.x + # delete policy local-route rule x fwmark x + # delete policy local-route rule x destination x.x.x.x + if 'rule' in pbr[route]: + for rule in pbr[route]['rule']: + src = leaf_node_changed(conf, base_rule + [rule, 'source']) + fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) + dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) + + rule_def = {} + if src: + rule_def = dict_merge({'source' : src}, rule_def) + if fwmk: + rule_def = dict_merge({'fwmark' : fwmk}, rule_def) + if dst: + rule_def = dict_merge({'destination' : dst}, rule_def) + dict = dict_merge({dict_id : {rule : rule_def}}, dict) pbr.update(dict) return pbr @@ -72,13 +91,18 @@ def verify(pbr): if not pbr: return None - if 'rule' in pbr: - for rule in pbr['rule']: - if 'source' not in pbr['rule'][rule] and 'fwmark' not in pbr['rule'][rule]: - raise ConfigError('Source address or fwmark is required!') - else: - if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']: - raise ConfigError('Table set is required!') + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + pbr_route = pbr[route] + if 'rule' in pbr_route: + for rule in pbr_route['rule']: + if 'source' not in pbr_route['rule'][rule] and 'destination' not in pbr_route['rule'][rule] and 'fwmark' not in pbr_route['rule'][rule]: + raise ConfigError('Source or destination address or fwmark is required!') + else: + if 'set' not in pbr_route['rule'][rule] or 'table' not in pbr_route['rule'][rule]['set']: + raise ConfigError('Table set is required!') return None @@ -93,36 +117,44 @@ def apply(pbr): return None # Delete old rule if needed - if 'rule_remove' in pbr: - for rule in pbr['rule_remove']: - if 'source' in pbr['rule_remove'][rule]: - for src in pbr['rule_remove'][rule]['source']: - call(f'ip rule del prio {rule} from {src}') - if 'fwmark' in pbr['rule_remove'][rule]: - for fwmk in pbr['rule_remove'][rule]['fwmark']: - call(f'ip rule del prio {rule} from all fwmark {fwmk}') + for rule_rm in ['rule_remove', 'rule6_remove']: + if rule_rm in pbr: + v6 = " -6" if rule_rm == 'rule6_remove' else "" + for rule, rule_config in pbr[rule_rm].items(): + rule_config['source'] = rule_config['source'] if 'source' in rule_config else [''] + for src in rule_config['source']: + f_src = '' if src == '' else f' from {src} ' + rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else [''] + for dst in rule_config['destination']: + f_dst = '' if dst == '' else f' to {dst} ' + rule_config['fwmark'] = rule_config['fwmark'] if 'fwmark' in rule_config else [''] + for fwmk in rule_config['fwmark']: + f_fwmk = '' if fwmk == '' else f' fwmark {fwmk} ' + call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}') # Generate new config - if 'rule' in pbr: - for rule in pbr['rule']: - table = pbr['rule'][rule]['set']['table'] - # Only source in the rule - # set policy local-route rule 100 source '203.0.113.1' - if 'source' in pbr['rule'][rule] and not 'fwmark' in pbr['rule'][rule]: - for src in pbr['rule'][rule]['source']: - call(f'ip rule add prio {rule} from {src} lookup {table}') - # Only fwmark in the rule - # set policy local-route rule 101 fwmark '23' - if 'fwmark' in pbr['rule'][rule] and not 'source' in pbr['rule'][rule]: - fwmk = pbr['rule'][rule]['fwmark'] - call(f'ip rule add prio {rule} from all fwmark {fwmk} lookup {table}') - # Source and fwmark in the rule - # set policy local-route rule 100 source '203.0.113.1' - # set policy local-route rule 100 fwmark '23' - if 'source' in pbr['rule'][rule] and 'fwmark' in pbr['rule'][rule]: - fwmk = pbr['rule'][rule]['fwmark'] - for src in pbr['rule'][rule]['source']: - call(f'ip rule add prio {rule} from {src} fwmark {fwmk} lookup {table}') + for route in ['local_route', 'local_route6']: + if not route in pbr: + continue + + v6 = " -6" if route == 'local_route6' else "" + + pbr_route = pbr[route] + if 'rule' in pbr_route: + for rule, rule_config in pbr_route['rule'].items(): + table = rule_config['set']['table'] + + rule_config['source'] = rule_config['source'] if 'source' in rule_config else ['all'] + for src in rule_config['source'] or ['all']: + f_src = '' if src == '' else f' from {src} ' + rule_config['destination'] = rule_config['destination'] if 'destination' in rule_config else ['all'] + for dst in rule_config['destination']: + f_dst = '' if dst == '' else f' to {dst} ' + f_fwmk = '' + if 'fwmark' in rule_config: + fwmk = rule_config['fwmark'] + f_fwmk = f' fwmark {fwmk} ' + call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk} lookup {table}') return None diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name index afeef8f2d..1798e92db 100755 --- a/src/helpers/vyos_net_name +++ b/src/helpers/vyos_net_name @@ -20,12 +20,14 @@ import os import re import time import logging +import tempfile import threading from sys import argv from vyos.configtree import ConfigTree from vyos.defaults import directories from vyos.util import cmd, boot_configuration_complete +from vyos.migrator import VirtualMigrator vyos_udev_dir = directories['vyos_udev_dir'] vyos_log_dir = '/run/udev/log' @@ -139,14 +141,20 @@ def get_configfile_interfaces() -> dict: try: config = ConfigTree(config_file) except Exception: - logging.debug(f"updating component version string syntax") try: - # this will update the component version string in place, for - # updates 1.2 --> 1.3/1.4 - os.system(f'/usr/libexec/vyos/run-config-migration.py {config_path} --virtual --set-vintage=vyos') - with open(config_path) as f: - config_file = f.read() + logging.debug(f"updating component version string syntax") + # this will update the component version string syntax, + # required for updates 1.2 --> 1.3/1.4 + with tempfile.NamedTemporaryFile() as fp: + with open(fp.name, 'w') as fd: + fd.write(config_file) + virtual_migration = VirtualMigrator(fp.name) + virtual_migration.run() + with open(fp.name) as fd: + config_file = fd.read() + config = ConfigTree(config_file) + except Exception as e: logging.critical(f"ConfigTree error: {e}") @@ -246,4 +254,3 @@ if not boot_configuration_complete(): else: logging.debug("boot configuration complete") lock.release() - |