diff --git a/interface-definitions/include/interface/inbound-interface.xml.i b/interface-definitions/include/interface/inbound-interface.xml.i deleted file mode 100644 index 5a8d47280..000000000 --- a/interface-definitions/include/interface/inbound-interface.xml.i +++ /dev/null @@ -1,10 +0,0 @@ -<!-- include start from interface/inbound-interface.xml.i --> -<leafNode name="inbound-interface"> - <properties> - <help>Inbound Interface</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces.py</script> - </completionHelp> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/policy-local-route.xml.in b/interface-definitions/policy-local-route.xml.in index 573a7963f..3769c3748 100644 --- a/interface-definitions/policy-local-route.xml.in +++ b/interface-definitions/policy-local-route.xml.in @@ -1,188 +1,67 @@ <?xml version="1.0"?> <!-- Policy local-route --> <interfaceDefinition> <node name="policy"> <children> <node name="local-route" owner="${vyos_conf_scripts_dir}/policy-local-route.py"> <properties> <help>IPv4 policy route of local traffic</help> </properties> <children> <tagNode name="rule"> <properties> <help>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> + <description>Local-route rule number (1-219)</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="ipv4-address"/> <validator name="ip-prefix"/> </constraint> <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> - #include <include/interface/inbound-interface.xml.i> - </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> - #include <include/interface/inbound-interface.xml.i> </children> </tagNode> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 50f2d7b43..f1d195381 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1,1094 +1,695 @@ #!/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 unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.util import cmd base_path = ['policy'] class TestPolicy(VyOSUnitTestSHIM.TestCase): def tearDown(self): self.cli_delete(base_path) self.cli_commit() def test_access_list(self): acls = { '50' : { 'rule' : { '5' : { 'action' : 'permit', 'source' : { 'any' : '' }, }, '10' : { 'action' : 'deny', 'source' : { 'host' : '1.2.3.4' }, }, }, }, '150' : { 'rule' : { '10' : { 'action' : 'permit', 'source' : { 'any' : '' }, 'destination' : { 'host' : '2.2.2.2' }, }, '10' : { 'action' : 'deny', 'source' : { 'any' : '' }, 'destination' : { 'any' : '' }, }, }, }, '2000' : { 'rule' : { '10' : { 'action' : 'permit', 'destination' : { 'any' : '' }, 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, }, '20' : { 'action' : 'permit', 'destination' : { 'any' : '' }, 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, }, '30' : { 'action' : 'permit', 'destination' : { 'any' : '' }, 'source' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, }, '50' : { 'action' : 'permit', 'destination' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, 'source' : { 'network' : '10.0.0.0', 'inverse-mask' : '0.255.255.255' }, }, '60' : { 'action' : 'deny', 'destination' : { 'network' : '192.168.0.0', 'inverse-mask' : '0.0.255.255' }, 'source' : { 'network' : '172.16.0.0', 'inverse-mask' : '0.15.255.255' }, }, '70' : { 'action' : 'deny', 'destination' : { 'any' : '' }, 'source' : { 'any' : '' }, }, }, }, } for acl, acl_config in acls.items(): path = base_path + ['access-list', acl] self.cli_set(path + ['description', f'VyOS-ACL-{acl}']) if 'rule' not in acl_config: continue for rule, rule_config in acl_config['rule'].items(): self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) for direction in ['source', 'destination']: if direction in rule_config: if 'any' in rule_config[direction]: self.cli_set(path + ['rule', rule, direction, 'any']) if 'host' in rule_config[direction]: self.cli_set(path + ['rule', rule, direction, 'host', rule_config[direction]['host']]) if 'network' in rule_config[direction]: self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) self.cli_set(path + ['rule', rule, direction, 'inverse-mask', rule_config[direction]['inverse-mask']]) self.cli_commit() config = self.getFRRconfig('access-list', end='') for acl, acl_config in acls.items(): seq = '5' for rule, rule_config in acl_config['rule'].items(): tmp = f'access-list {acl} seq {seq}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' if {'source', 'destination'} <= set(rule_config): tmp += ' ip' for direction in ['source', 'destination']: if direction in rule_config: if 'any' in rule_config[direction]: tmp += ' any' if 'host' in rule_config[direction]: tmp += ' ' + rule_config[direction]['host'] if 'network' in rule_config[direction]: tmp += ' ' + rule_config[direction]['network'] + ' ' + rule_config[direction]['inverse-mask'] self.assertIn(tmp, config) seq = int(seq) + 5 def test_access_list6(self): acls = { '50' : { 'rule' : { '5' : { 'action' : 'permit', 'source' : { 'any' : '' }, }, '10' : { 'action' : 'deny', 'source' : { 'network' : '2001:db8:10::/48', 'exact-match' : '' }, }, '10' : { 'action' : 'deny', 'source' : { 'network' : '2001:db8:20::/48' }, }, }, }, '100' : { 'rule' : { '5' : { 'action' : 'deny', 'source' : { 'network' : '2001:db8:10::/64', 'exact-match' : '' }, }, '10' : { 'action' : 'deny', 'source' : { 'network' : '2001:db8:20::/64', }, }, '15' : { 'action' : 'deny', 'source' : { 'network' : '2001:db8:30::/64', 'exact-match' : '' }, }, '20' : { 'action' : 'deny', 'source' : { 'network' : '2001:db8:40::/64', 'exact-match' : '' }, }, '100' : { 'action' : 'deny', 'source' : { 'any' : '' }, }, }, }, } for acl, acl_config in acls.items(): path = base_path + ['access-list6', acl] self.cli_set(path + ['description', f'VyOS-ACL-{acl}']) if 'rule' not in acl_config: continue for rule, rule_config in acl_config['rule'].items(): self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) for direction in ['source', 'destination']: if direction in rule_config: if 'any' in rule_config[direction]: self.cli_set(path + ['rule', rule, direction, 'any']) if 'network' in rule_config[direction]: self.cli_set(path + ['rule', rule, direction, 'network', rule_config[direction]['network']]) if 'exact-match' in rule_config[direction]: self.cli_set(path + ['rule', rule, direction, 'exact-match']) self.cli_commit() config = self.getFRRconfig('ipv6 access-list', end='') for acl, acl_config in acls.items(): seq = '5' for rule, rule_config in acl_config['rule'].items(): tmp = f'ipv6 access-list {acl} seq {seq}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' if {'source', 'destination'} <= set(rule_config): tmp += ' ip' for direction in ['source', 'destination']: if direction in rule_config: if 'any' in rule_config[direction]: tmp += ' any' if 'network' in rule_config[direction]: tmp += ' ' + rule_config[direction]['network'] if 'exact-match' in rule_config[direction]: tmp += ' exact-match' self.assertIn(tmp, config) seq = int(seq) + 5 def test_as_path_list(self): test_data = { 'VyOS' : { 'rule' : { '10' : { 'action' : 'permit', 'regex' : '^44501 64502$', }, '20' : { 'action' : 'permit', 'regex' : '44501|44502|44503', }, '30' : { 'action' : 'permit', 'regex' : '^44501_([0-9]+_)+', }, }, }, 'Customers' : { 'rule' : { '10' : { 'action' : 'permit', 'regex' : '_10_', }, '20' : { 'action' : 'permit', 'regex' : '_20_', }, '30' : { 'action' : 'permit', 'regex' : '_30_', }, '30' : { 'action' : 'deny', 'regex' : '_40_', }, }, }, 'bogons' : { 'rule' : { '10' : { 'action' : 'permit', 'regex' : '_0_', }, '20' : { 'action' : 'permit', 'regex' : '_23456_', }, '30' : { 'action' : 'permit', 'regex' : '_6449[6-9]_|_65[0-4][0-9][0-9]_|_655[0-4][0-9]_|_6555[0-1]_', }, '30' : { 'action' : 'permit', 'regex' : '_6555[2-9]_|_655[6-9][0-9]_|_65[6-9][0-9][0-9]_|_6[6-9][0-9][0-9][0-]_|_[7-9][0-9][0-9][0-9][0-9]_|_1[0-2][0-9][0-9][0-9][0-9]_|_130[0-9][0-9][0-9]_|_1310[0-6][0-9]_|_13107[01]_', }, }, }, } for as_path, as_path_config in test_data.items(): path = base_path + ['as-path-list', as_path] self.cli_set(path + ['description', f'VyOS-ASPATH-{as_path}']) if 'rule' not in as_path_config: continue for rule, rule_config in as_path_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'regex' in rule_config: self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) self.cli_commit() config = self.getFRRconfig('bgp as-path access-list', end='') for as_path, as_path_config in test_data.items(): if 'rule' not in as_path_config: continue for rule, rule_config in as_path_config['rule'].items(): tmp = f'bgp as-path access-list {as_path}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' tmp += ' ' + rule_config['regex'] self.assertIn(tmp, config) def test_community_list(self): test_data = { '100' : { 'rule' : { '4' : { 'action' : 'permit', 'regex' : '.*', }, }, }, '200' : { 'rule' : { '1' : { 'action' : 'deny', 'regex' : '^1:201$', }, '2' : { 'action' : 'deny', 'regex' : '1:101$', }, '3' : { 'action' : 'deny', 'regex' : '^1:100$', }, }, }, } for comm_list, comm_list_config in test_data.items(): path = base_path + ['community-list', comm_list] self.cli_set(path + ['description', f'VyOS-COMM-{comm_list}']) if 'rule' not in comm_list_config: continue for rule, rule_config in comm_list_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'regex' in rule_config: self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) self.cli_commit() config = self.getFRRconfig('bgp community-list', end='') for comm_list, comm_list_config in test_data.items(): if 'rule' not in comm_list_config: continue for rule, rule_config in comm_list_config['rule'].items(): tmp = f'bgp community-list {comm_list} seq {rule}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' tmp += ' ' + rule_config['regex'] self.assertIn(tmp, config) def test_extended_community_list(self): test_data = { 'foo' : { 'rule' : { '4' : { 'action' : 'permit', 'regex' : '.*', }, }, }, '200' : { 'rule' : { '1' : { 'action' : 'deny', 'regex' : '^1:201$', }, '2' : { 'action' : 'deny', 'regex' : '1:101$', }, '3' : { 'action' : 'deny', 'regex' : '^1:100$', }, }, }, } for comm_list, comm_list_config in test_data.items(): path = base_path + ['extcommunity-list', comm_list] self.cli_set(path + ['description', f'VyOS-EXTCOMM-{comm_list}']) if 'rule' not in comm_list_config: continue for rule, rule_config in comm_list_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'regex' in rule_config: self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) self.cli_commit() config = self.getFRRconfig('bgp extcommunity-list', end='') for comm_list, comm_list_config in test_data.items(): if 'rule' not in comm_list_config: continue for rule, rule_config in comm_list_config['rule'].items(): # if the community is not a number but a name, the expanded # keyword is used expanded = '' if not comm_list.isnumeric(): expanded = ' expanded' tmp = f'bgp extcommunity-list{expanded} {comm_list} seq {rule}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' tmp += ' ' + rule_config['regex'] self.assertIn(tmp, config) def test_large_community_list(self): test_data = { 'foo' : { 'rule' : { '10' : { 'action' : 'permit', 'regex' : '667:123:100', }, }, }, 'bar' : { 'rule' : { '10' : { 'action' : 'permit', 'regex' : '65000:120:10', }, '20' : { 'action' : 'permit', 'regex' : '65000:120:20', }, '30' : { 'action' : 'permit', 'regex' : '65000:120:30', }, }, }, } for comm_list, comm_list_config in test_data.items(): path = base_path + ['large-community-list', comm_list] self.cli_set(path + ['description', f'VyOS-LARGECOMM-{comm_list}']) if 'rule' not in comm_list_config: continue for rule, rule_config in comm_list_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'regex' in rule_config: self.cli_set(path + ['rule', rule, 'regex', rule_config['regex']]) self.cli_commit() config = self.getFRRconfig('bgp large-community-list', end='') for comm_list, comm_list_config in test_data.items(): if 'rule' not in comm_list_config: continue for rule, rule_config in comm_list_config['rule'].items(): tmp = f'bgp large-community-list expanded {comm_list} seq {rule}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' tmp += ' ' + rule_config['regex'] self.assertIn(tmp, config) def test_prefix_list(self): test_data = { 'foo' : { 'rule' : { '10' : { 'action' : 'permit', 'prefix' : '10.0.0.0/8', 'ge' : '16', 'le' : '24', }, '20' : { 'action' : 'deny', 'prefix' : '172.16.0.0/12', 'ge' : '16', }, '30' : { 'action' : 'permit', 'prefix' : '192.168.0.0/16', }, }, }, 'bar' : { 'rule' : { '10' : { 'action' : 'permit', 'prefix' : '10.0.10.0/24', 'ge' : '25', 'le' : '26', }, '20' : { 'action' : 'deny', 'prefix' : '10.0.20.0/24', 'le' : '25', }, '25' : { 'action' : 'permit', 'prefix' : '10.0.25.0/24', }, }, }, } for prefix_list, prefix_list_config in test_data.items(): path = base_path + ['prefix-list', prefix_list] self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) if 'rule' not in prefix_list_config: continue for rule, rule_config in prefix_list_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'prefix' in rule_config: self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']]) if 'ge' in rule_config: self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']]) if 'le' in rule_config: self.cli_set(path + ['rule', rule, 'le', rule_config['le']]) self.cli_commit() config = self.getFRRconfig('ip prefix-list', end='') for prefix_list, prefix_list_config in test_data.items(): if 'rule' not in prefix_list_config: continue for rule, rule_config in prefix_list_config['rule'].items(): tmp = f'ip prefix-list {prefix_list} seq {rule}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' tmp += ' ' + rule_config['prefix'] if 'ge' in rule_config: tmp += ' ge ' + rule_config['ge'] if 'le' in rule_config: tmp += ' le ' + rule_config['le'] self.assertIn(tmp, config) def test_prefix_list6(self): test_data = { 'foo' : { 'rule' : { '10' : { 'action' : 'permit', 'prefix' : '2001:db8::/32', 'ge' : '40', 'le' : '48', }, '20' : { 'action' : 'deny', 'prefix' : '2001:db8::/32', 'ge' : '48', }, '30' : { 'action' : 'permit', 'prefix' : '2001:db8:1000::/64', }, }, }, 'bar' : { 'rule' : { '10' : { 'action' : 'permit', 'prefix' : '2001:db8:100::/40', 'ge' : '48', }, '20' : { 'action' : 'permit', 'prefix' : '2001:db8:200::/40', 'ge' : '48', }, '25' : { 'action' : 'deny', 'prefix' : '2001:db8:300::/40', 'le' : '64', }, }, }, } for prefix_list, prefix_list_config in test_data.items(): path = base_path + ['prefix-list6', prefix_list] self.cli_set(path + ['description', f'VyOS-PFX-LIST-{prefix_list}']) if 'rule' not in prefix_list_config: continue for rule, rule_config in prefix_list_config['rule'].items(): if 'action' in rule_config: self.cli_set(path + ['rule', rule, 'action', rule_config['action']]) if 'prefix' in rule_config: self.cli_set(path + ['rule', rule, 'prefix', rule_config['prefix']]) if 'ge' in rule_config: self.cli_set(path + ['rule', rule, 'ge', rule_config['ge']]) if 'le' in rule_config: self.cli_set(path + ['rule', rule, 'le', rule_config['le']]) self.cli_commit() config = self.getFRRconfig('ipv6 prefix-list', end='') for prefix_list, prefix_list_config in test_data.items(): if 'rule' not in prefix_list_config: continue for rule, rule_config in prefix_list_config['rule'].items(): tmp = f'ipv6 prefix-list {prefix_list} seq {rule}' if rule_config['action'] == 'permit': tmp += ' permit' else: tmp += ' deny' tmp += ' ' + rule_config['prefix'] if 'ge' in rule_config: tmp += ' ge ' + rule_config['ge'] if 'le' in rule_config: tmp += ' le ' + rule_config['le'] self.assertIn(tmp, config) # Test set table for some sources def test_table_id(self): path = base_path + ['local-route'] sources = ['203.0.113.1', '203.0.113.2'] 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() - original = """ - 50: from 203.0.113.1 lookup 23 - 50: from 203.0.113.2 lookup 23 - """ - tmp = cmd('ip rule show prio 50') - - self.assertEqual(sort_ip(tmp), sort_ip(original)) - - # Test set table for fwmark - def test_fwmark_table_id(self): - path = base_path + ['local-route'] - - fwmk = '24' - rule = '101' - table = '154' - - self.cli_set(path + ['rule', rule, 'set', 'table', table]) - self.cli_set(path + ['rule', rule, 'fwmark', fwmk]) - - self.cli_commit() - - original = """ - 101: from all fwmark 0x18 lookup 154 - """ - tmp = cmd('ip rule show prio 101') - - 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() - - 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): - path = base_path + ['local-route'] - - sources = ['203.0.113.11', '203.0.113.12'] - fwmk = '23' - rule = '100' - 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() - - original = """ - 100: from 203.0.113.11 fwmark 0x17 lookup 150 - 100: from 203.0.113.12 fwmark 0x17 lookup 150 - """ - tmp = cmd('ip rule show prio 100') - - self.assertEqual(sort_ip(tmp), sort_ip(original)) - - # Test set table for sources with iif - def test_iif_sources_table_id(self): - path = base_path + ['local-route'] - - sources = ['203.0.113.11', '203.0.113.12'] - iif = 'lo' - rule = '100' - table = '150' - - self.cli_set(path + ['rule', rule, 'set', 'table', table]) - self.cli_set(path + ['rule', rule, 'inbound-interface', iif]) - for src in sources: - self.cli_set(path + ['rule', rule, 'source', src]) - - self.cli_commit() - - # Check generated configuration - # Expected values - original = """ - 100: from 203.0.113.11 iif lo lookup 150 - 100: from 203.0.113.12 iif lo lookup 150 - """ - tmp = cmd('ip rule show prio 100') - - 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() - - 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() - - 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() - - 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() - - 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() - - 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 with iif ipv6 - def test_iif_sources_ipv6_table_id(self): - path = base_path + ['local-route6'] - - sources = ['2001:db8:1338::/126', '2001:db8:1339::/126'] - iif = 'lo' - 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, 'inbound-interface', iif]) - - self.cli_commit() - - # Check generated configuration - # Expected values - original = """ - 102: from 2001:db8:1338::/126 iif lo lookup 150 - 102: from 2001:db8:1339::/126 iif lo 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() - - 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() - - original = """ - 103: from 203.0.113.0/24 to 203.0.116.5 fwmark 0x17 lookup 150 - 103: from 203.0.114.5 to 203.0.112.0/24 fwmark 0x17 lookup 150 - 103: from 203.0.114.5 to 203.0.116.5 fwmark 0x17 lookup 150 - 103: from 203.0.113.0/24 to 203.0.112.0/24 fwmark 0x17 lookup 150 - """ - original_v6 = """ - 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 - 103: from 2001:db8:1338::/126 to 2001:db8:13::/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') - - self.assertEqual(sort_ip(tmp), []) - self.assertEqual(sort_ip(tmp_v6), []) - - # Test multiple commits ipv4 - def test_multiple_commit_ipv4_table_id(self): - path = base_path + ['local-route'] - - sources = ['192.0.2.1', '192.0.2.2'] - destination = '203.0.113.25' - rule = '105' - table = '151' - self.cli_set(path + ['rule', rule, 'set', 'table', table]) - for src in sources: - self.cli_set(path + ['rule', rule, 'source', src]) - - self.cli_commit() - - original_first = """ - 105: from 192.0.2.1 lookup 151 - 105: from 192.0.2.2 lookup 151 - """ - tmp = cmd('ip rule show prio 105') - - self.assertEqual(sort_ip(tmp), sort_ip(original_first)) - - # Create second commit with added destination - self.cli_set(path + ['rule', rule, 'destination', destination]) - self.cli_commit() - - original_second = """ - 105: from 192.0.2.1 to 203.0.113.25 lookup 151 - 105: from 192.0.2.2 to 203.0.113.25 lookup 151 - """ - tmp = cmd('ip rule show prio 105') - - self.assertEqual(sort_ip(tmp), sort_ip(original_second)) - - -def sort_ip(output): - o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()]) - o = o.splitlines() - o.sort() - return o - - # Test set table for fwmark - def test_fwmark_table_id(self): - path = base_path + ['local-route'] - - fwmk = '24' - rule = '101' - 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 = """ - 101: from all fwmark 0x18 lookup 154 - """ - tmp = cmd('ip rule show prio 101') - original = original.split() - tmp = tmp.split() - - self.assertEqual(tmp, original) - - # Test set table for sources with fwmark - def test_fwmark_sources_table_id(self): - path = base_path + ['local-route'] - - sources = ['203.0.113.11', '203.0.113.12'] - fwmk = '23' - rule = '100' - 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 = """ - 100: from 203.0.113.11 fwmark 0x17 lookup 150 - 100: from 203.0.113.12 fwmark 0x17 lookup 150 + 50: from 203.0.113.1 lookup 23 + 50: from 203.0.113.2 lookup 23 """ - tmp = cmd('ip rule show prio 100') + tmp = cmd('ip rule show prio 50') original = original.split() tmp = tmp.split() self.assertEqual(tmp, original) 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 0a4597869..013f22665 100755 --- a/src/conf_mode/policy-local-route.py +++ b/src/conf_mode/policy-local-route.py @@ -1,225 +1,110 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import os from sys import exit -from netifaces import interfaces from vyos.config import Config from vyos.configdict import dict_merge from vyos.configdict import node_changed from vyos.configdict import leaf_node_changed from vyos.template import render from vyos.util import call from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(config=None): if config: conf = config else: conf = Config() - base = ['policy'] - + base = ['policy', 'local-route'] pbr = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) - 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']) - iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) - 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 iif: - rule_def = dict_merge({'inbound_interface' : iif}, rule_def) - if dst: - rule_def = dict_merge({'destination' : dst}, rule_def) - dict = dict_merge({dict_id : {rule : rule_def}}, dict) - pbr.update(dict) - if fwmk: - dict = dict_merge({'rule_remove' : {rule : {'fwmark' : fwmk}}}, dict) + # 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']) + if src: + dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) pbr.update(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, rule_config in pbr[route]['rule'].items(): - src = leaf_node_changed(conf, base_rule + [rule, 'source']) - fwmk = leaf_node_changed(conf, base_rule + [rule, 'fwmark']) - iif = leaf_node_changed(conf, base_rule + [rule, 'inbound-interface']) - dst = leaf_node_changed(conf, base_rule + [rule, 'destination']) - # keep track of changes in configuration - # otherwise we might remove an existing node although nothing else has changed - changed = False - - rule_def = {} - # src is None if there are no changes to src - if src is None: - # if src hasn't changed, include it in the removal selector - # if a new selector is added, we have to remove all previous rules without this selector - # to make sure we remove all previous rules with this source(s), it will be included - if 'source' in rule_config: - rule_def = dict_merge({'source': rule_config['source']}, rule_def) - else: - # if src is not None, it's previous content will be returned - # this can be an empty array if it's just being set, or the previous value - # either way, something has to be changed and we only want to remove previous values - changed = True - # set the old value for removal if it's not empty - if len(src) > 0: - rule_def = dict_merge({'source' : src}, rule_def) - if fwmk is None: - if 'fwmark' in rule_config: - rule_def = dict_merge({'fwmark': rule_config['fwmark']}, rule_def) - else: - changed = True - if len(fwmk) > 0: - rule_def = dict_merge({'fwmark' : fwmk}, rule_def) - if iif is None: - if 'inbound_interface' in rule_config: - rule_def = dict_merge({'inbound_interface': rule_config['inbound_interface']}, rule_def) - else: - changed = True - if len(iif) > 0: - rule_def = dict_merge({'inbound_interface' : iif}, rule_def) - if dst is None: - if 'destination' in rule_config: - rule_def = dict_merge({'destination': rule_config['destination']}, rule_def) - else: - changed = True - if len(dst) > 0: - rule_def = dict_merge({'destination' : dst}, rule_def) - if changed: - dict = dict_merge({dict_id : {rule : rule_def}}, dict) - pbr.update(dict) + # delete policy local-route rule x source x.x.x.x + if 'rule' in pbr: + for rule in pbr['rule']: + src = leaf_node_changed(conf, ['policy', 'local-route', 'rule', rule, 'source']) + if src: + dict = dict_merge({'rule_remove' : {rule : {'source' : src}}}, dict) + pbr.update(dict) return pbr def verify(pbr): # bail out early - looks like removal from running config if not pbr: return None - 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] \ - and 'inbound_interface' not in pbr_route['rule'][rule]: - raise ConfigError('Source or destination address or fwmark or inbound-interface 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!') - if 'inbound_interface' in pbr_route['rule'][rule]: - interface = pbr_route['rule'][rule]['inbound_interface'] - if interface not in interfaces(): - raise ConfigError(f'Interface "{interface}" does not exist') + if 'rule' in pbr: + for rule in pbr['rule']: + if 'source' not in pbr['rule'][rule]: + raise ConfigError('Source address required!') + else: + if 'set' not in pbr['rule'][rule] or 'table' not in pbr['rule'][rule]['set']: + raise ConfigError('Table set is required!') return None def generate(pbr): if not pbr: return None return None def apply(pbr): if not pbr: return None # Delete old rule if needed - 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} ' - rule_config['inbound_interface'] = rule_config['inbound_interface'] if 'inbound_interface' in rule_config else [''] - for iif in rule_config['inbound_interface']: - f_iif = '' if iif == '' else f' iif {iif} ' - call(f'ip{v6} rule del prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif}') + if 'rule_remove' in pbr: + for rule in pbr['rule_remove']: + for src in pbr['rule_remove'][rule]['source']: + call(f'ip rule del prio {rule} from {src}') # Generate new config - 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} ' - f_iif = '' - if 'inbound_interface' in rule_config: - iif = rule_config['inbound_interface'] - f_iif = f' iif {iif} ' - call(f'ip{v6} rule add prio {rule} {f_src}{f_dst}{f_fwmk}{f_iif} lookup {table}') + if 'rule' in pbr: + for rule in pbr['rule']: + table = pbr['rule'][rule]['set']['table'] + if pbr['rule'][rule]['source']: + for src in pbr['rule'][rule]['source']: + call(f'ip rule add prio {rule} from {src} lookup {table}') return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)