diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2 index c662b7650..227807c62 100644 --- a/data/templates/frr/staticd.frr.j2 +++ b/data/templates/frr/staticd.frr.j2 @@ -1,145 +1,143 @@ {# Common macro for recurroiing options for a static route #} {% macro route_options(route, interface_or_next_hop, config, table) %} {# j2lint: disable=jinja-statements-delimeter #} {% set ip_route = route ~ ' ' ~ interface_or_next_hop %} {% if config.interface is vyos_defined %} {% set ip_route = ip_route ~ ' ' ~ config.interface %} {% endif %} {% if config.tag is vyos_defined %} {% set ip_route = ip_route ~ ' tag ' ~ config.tag %} {% endif %} {% if config.distance is vyos_defined %} {% set ip_route = ip_route ~ ' ' ~ config.distance %} {% endif %} {% if config.bfd is vyos_defined %} {% set ip_route = ip_route ~ ' bfd' %} {% if config.bfd.multi_hop is vyos_defined %} {% set ip_route = ip_route ~ ' multi-hop' %} {% if config.bfd.source_address is vyos_defined %} {% set ip_route = ip_route ~ ' source ' ~ config.bfd.source_address %} {% endif %} {% endif %} {% if config.bfd.profile is vyos_defined %} {% set ip_route = ip_route ~ ' profile ' ~ config.bfd.profile %} {% endif %} {% endif %} {% if config.vrf is vyos_defined %} {% set ip_route = ip_route ~ ' nexthop-vrf ' ~ config.vrf %} {% endif %} {% if config.segments is vyos_defined %} {# Segments used in/for SRv6 #} {% set ip_route = ip_route ~ ' segments ' ~ config.segments %} {% endif %} {# Routing table to configure #} {% if table is vyos_defined %} {% set ip_route = ip_route ~ ' table ' ~ table %} {% endif %} {{ ip_route }} {%- endmacro -%} {# Build static IPv4/IPv6 route #} {% macro static_routes(ip_ipv6, prefix, prefix_config, table=None) %} {% set route = ip_ipv6 ~ 'route ' ~ prefix %} {% if prefix_config.interface is vyos_defined %} {% for interface, interface_config in prefix_config.interface.items() if interface_config.disable is not defined %} {{ route_options(route, interface, interface_config, table) }} {% endfor %} {% endif %} {% if prefix_config.next_hop is vyos_defined and prefix_config.next_hop is not none %} {% for next_hop, next_hop_config in prefix_config.next_hop.items() if next_hop_config.disable is not defined %} {{ route_options(route, next_hop, next_hop_config, table) }} {% endfor %} {% endif %} {% if prefix_config.dhcp_interface is vyos_defined %} {% for dhcp_interface in prefix_config.dhcp_interface %} {% set next_hop = dhcp_interface | get_dhcp_router %} {% if next_hop is vyos_defined %} {{ ip_ipv6 }} route {{ prefix }} {{ next_hop }} {{ dhcp_interface }} {{ 'table ' ~ table if table is vyos_defined }} {% endif %} {% endfor %} {% endif %} {% if prefix_config.blackhole is vyos_defined %} {{ route_options(route, 'blackhole', prefix_config.blackhole, table) }} {% elif prefix_config.reject is vyos_defined %} {{ route_options(route, 'reject', prefix_config.reject, table) }} {% endif %} {# j2lint: disable=jinja-statements-delimeter #} {%- endmacro -%} ! {% set ip_prefix = 'ip ' %} {% set ipv6_prefix = 'ipv6 ' %} {% if vrf is vyos_defined %} {# We need to add an additional whitespace in front of the prefix #} {# when VRFs are in use, thus we use a variable for prefix handling #} {% set ip_prefix = ' ip ' %} {% set ipv6_prefix = ' ipv6 ' %} vrf {{ vrf }} {% endif %} {# IPv4 routing #} {% if route is vyos_defined %} {% for prefix, prefix_config in route.items() %} {{ static_routes(ip_prefix, prefix, prefix_config) }} {# j2lint: disable=jinja-statements-delimeter #} {%- endfor %} {% endif %} {# IPv4 default routes from DHCP interfaces #} {% if dhcp is vyos_defined %} {% for interface, interface_config in dhcp.items() if interface_config.dhcp_options.no_default_route is not vyos_defined %} {% set next_hop = interface | get_dhcp_router %} {% if next_hop is vyos_defined %} {{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.dhcp_options.default_route_distance if interface_config.dhcp_options.default_route_distance is vyos_defined }} {% endif %} {% endfor %} {% endif %} {# IPv4 default routes from PPPoE interfaces #} {% if pppoe is vyos_defined %} {% for interface, interface_config in pppoe.items() if interface_config.no_default_route is not vyos_defined %} {{ ip_prefix }} route 0.0.0.0/0 {{ interface }} tag 210 {{ interface_config.default_route_distance if interface_config.default_route_distance is vyos_defined }} {%- endfor %} {% endif %} {# IPv6 routing #} {% if route6 is vyos_defined %} {% for prefix, prefix_config in route6.items() %} {{ static_routes(ipv6_prefix, prefix, prefix_config) }} {# j2lint: disable=jinja-statements-delimeter #} {%- endfor %} {% endif %} {% if vrf is vyos_defined %} exit-vrf {% endif %} ! {# Policy route tables #} {% if table is vyos_defined %} {% for table_id, table_config in table.items() %} {% if table_config.route is vyos_defined %} {% for prefix, prefix_config in table_config.route.items() %} {{ static_routes('ip ', prefix, prefix_config, table_id) }} {# j2lint: disable=jinja-statements-delimeter #} {%- endfor %} {% endif %} ! {% if table_config.route6 is vyos_defined %} {% for prefix, prefix_config in table_config.route6.items() %} {{ static_routes('ipv6 ', prefix, prefix_config, table_id) }} {# j2lint: disable=jinja-statements-delimeter #} {%- endfor %} {% endif %} ! {% endfor %} {% endif %} ! {# Multicast route #} -{% if multicast is vyos_defined %} +{% if mroute is vyos_defined %} {% set ip_prefix = 'ip m' %} {# IPv4 multicast routing #} -{% if multicast.route is vyos_defined %} -{% for prefix, prefix_config in multicast.route.items() %} +{% for prefix, prefix_config in mroute.items() %} {{ static_routes(ip_prefix, prefix, prefix_config) }} {# j2lint: disable=jinja-statements-delimeter #} -{%- endfor %} -{% endif %} +{%- endfor %} {% endif %} ! {% if route_map is vyos_defined %} ip protocol static route-map {{ route_map }} ! {% endif %} diff --git a/interface-definitions/protocols_static.xml.in b/interface-definitions/protocols_static.xml.in index 407e56553..d8e0ee56b 100644 --- a/interface-definitions/protocols_static.xml.in +++ b/interface-definitions/protocols_static.xml.in @@ -1,100 +1,93 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="protocols"> <properties> <help>Routing protocols</help> </properties> <children> <node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py"> <properties> <help>Static Routing</help> <priority>480</priority> </properties> <children> - <node name="multicast"> + <tagNode name="mroute"> <properties> - <help>Multicast static route</help> + <help>Static IPv4 route for Multicast RIB</help> + <valueHelp> + <format>ipv4net</format> + <description>Network</description> + </valueHelp> + <constraint> + <validator name="ipv4-prefix"/> + </constraint> </properties> <children> - <tagNode name="route"> + <tagNode name="next-hop"> + <properties> + <help>Next-hop IPv4 router address</help> + <valueHelp> + <format>ipv4</format> + <description>Next-hop router address</description> + </valueHelp> + <constraint> + <validator name="ipv4-address"/> + </constraint> + </properties> + <children> + #include <include/generic-disable-node.xml.i> + #include <include/static/static-route-distance.xml.i> + </children> + </tagNode> + <tagNode name="interface"> <properties> - <help>Configure static unicast route into MRIB for multicast RPF lookup</help> + <help>Next-hop IPv4 router interface</help> + <completionHelp> + <script>${vyos_completion_dir}/list_interfaces</script> + </completionHelp> <valueHelp> - <format>ipv4net</format> - <description>Network</description> + <format>txt</format> + <description>Gateway interface name</description> </valueHelp> <constraint> - <validator name="ip-prefix"/> + #include <include/constraint/interface-name.xml.i> </constraint> </properties> <children> - <tagNode name="next-hop"> - <properties> - <help>Next-hop IPv4 router address</help> - <valueHelp> - <format>ipv4</format> - <description>Next-hop router address</description> - </valueHelp> - <constraint> - <validator name="ipv4-address"/> - </constraint> - </properties> - <children> - #include <include/generic-disable-node.xml.i> - #include <include/static/static-route-distance.xml.i> - </children> - </tagNode> - <tagNode name="interface"> - <properties> - <help>Next-hop IPv4 router interface</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>Gateway interface name</description> - </valueHelp> - <constraint> - #include <include/constraint/interface-name.xml.i> - </constraint> - </properties> - <children> - #include <include/generic-disable-node.xml.i> - #include <include/static/static-route-distance.xml.i> - </children> - </tagNode> + #include <include/generic-disable-node.xml.i> + #include <include/static/static-route-distance.xml.i> </children> </tagNode> </children> - </node> + </tagNode> #include <include/route-map.xml.i> #include <include/static/static-route.xml.i> #include <include/static/static-route6.xml.i> <tagNode name="table"> <properties> <help>Policy route table number</help> <valueHelp> <format>u32:1-200</format> <description>Policy route table number</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-200"/> </constraint> </properties> <children> <!-- iproute2 only considers the first "word" until whitespace in the name field but does not complain about special characters. We put an artificial limit here to make table descriptions potentially valid node names to avoid quoting and simplify future syntax changes if we decide to make any. --> #include <include/generic-description.xml.i> #include <include/static/static-route.xml.i> #include <include/static/static-route6.xml.i> </children> </tagNode> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 980bc6e7f..086235086 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -1,572 +1,572 @@ #!/usr/bin/env python3 # # Copyright (C) 2021-2024 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.template import is_ipv6 from vyos.utils.network import get_interface_config from vyos.utils.network import get_vrf_tableid base_path = ['protocols', 'static'] vrf_path = ['protocols', 'vrf'] routes = { '10.0.0.0/8' : { 'next_hop' : { '192.0.2.100' : { 'distance' : '100' }, '192.0.2.110' : { 'distance' : '110', 'interface' : 'eth0' }, '192.0.2.120' : { 'distance' : '120', 'disable' : '' }, '192.0.2.130' : { 'bfd' : '' }, '192.0.2.131' : { 'bfd' : '', 'bfd_profile' : 'vyos1' }, '192.0.2.140' : { 'bfd' : '', 'bfd_source' : '192.0.2.10', 'bfd_profile' : 'vyos2' }, }, 'interface' : { 'eth0' : { 'distance' : '130' }, 'eth1' : { 'distance' : '140' }, }, 'blackhole' : { 'distance' : '250', 'tag' : '500' }, }, '172.16.0.0/12' : { 'interface' : { 'eth0' : { 'distance' : '50', 'vrf' : 'black' }, 'eth1' : { 'distance' : '60', 'vrf' : 'black' }, }, 'blackhole' : { 'distance' : '90' }, }, '192.0.2.0/24' : { 'interface' : { 'eth0' : { 'distance' : '50', 'vrf' : 'black' }, 'eth1' : { 'disable' : '' }, }, 'blackhole' : { 'distance' : '90' }, }, '100.64.0.0/16' : { 'blackhole' : {}, }, '100.65.0.0/16' : { 'reject' : { 'distance' : '10', 'tag' : '200' }, }, '100.66.0.0/16' : { 'blackhole' : {}, 'reject' : { 'distance' : '10', 'tag' : '200' }, }, '2001:db8:100::/40' : { 'next_hop' : { '2001:db8::1' : { 'distance' : '10' }, '2001:db8::2' : { 'distance' : '20', 'interface' : 'eth0' }, '2001:db8::3' : { 'distance' : '30', 'disable' : '' }, '2001:db8::4' : { 'bfd' : '' }, '2001:db8::5' : { 'bfd_source' : '2001:db8::ffff' }, }, 'interface' : { 'eth0' : { 'distance' : '40', 'vrf' : 'black' }, 'eth1' : { 'distance' : '50', 'disable' : '' }, }, 'blackhole' : { 'distance' : '250', 'tag' : '500' }, }, '2001:db8:200::/40' : { 'interface' : { 'eth0' : { 'distance' : '40' }, 'eth1' : { 'distance' : '50', 'disable' : '' }, }, 'blackhole' : { 'distance' : '250', 'tag' : '500' }, }, '2001:db8:300::/40' : { 'reject' : { 'distance' : '250', 'tag' : '500' }, }, '2001:db8:400::/40' : { 'next_hop' : { '2001:db8::400' : { 'segments' : '2001:db8:aaaa::400/2002::400/2003::400/2004::400' }, }, }, '2001:db8:500::/40' : { 'next_hop' : { '2001:db8::500' : { 'segments' : '2001:db8:aaaa::500/2002::500/2003::500/2004::500' }, }, }, '2001:db8:600::/40' : { 'interface' : { 'eth0' : { 'segments' : '2001:db8:aaaa::600/2002::600' }, }, }, '2001:db8:700::/40' : { 'interface' : { 'eth1' : { 'segments' : '2001:db8:aaaa::700' }, }, }, '2001:db8::/32' : { 'blackhole' : { 'distance' : '200', 'tag' : '600' } }, } multicast_routes = { '224.0.0.0/24' : { 'next_hop' : { '224.203.0.1' : { }, '224.203.0.2' : { 'distance' : '110'}, }, }, '224.1.0.0/24' : { 'next_hop' : { '224.205.0.1' : { 'disable' : {} }, '224.205.0.2' : { 'distance' : '110'}, }, }, '224.2.0.0/24' : { 'next_hop' : { '1.2.3.0' : { }, '1.2.3.1' : { 'distance' : '110'}, }, }, '224.10.0.0/24' : { 'interface' : { 'eth1' : { 'disable' : {} }, 'eth2' : { 'distance' : '110'}, }, }, '224.11.0.0/24' : { 'interface' : { 'eth0' : { }, 'eth1' : { 'distance' : '10'}, }, }, '224.12.0.0/24' : { 'interface' : { 'eth0' : { }, 'eth1' : { 'distance' : '200'}, }, }, } tables = ['80', '81', '82'] class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestProtocolsStatic, cls).setUpClass() cls.cli_delete(cls, ['vrf']) cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210']) @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['vrf']) super(TestProtocolsStatic, cls).tearDownClass() def tearDown(self): self.cli_delete(base_path) self.cli_commit() v4route = self.getFRRconfig('ip route', end='') self.assertFalse(v4route) v6route = self.getFRRconfig('ipv6 route', end='') self.assertFalse(v6route) def test_01_static(self): for route, route_config in routes.items(): route_type = 'route' if is_ipv6(route): route_type = 'route6' base = base_path + [route_type, route] if 'next_hop' in route_config: for next_hop, next_hop_config in route_config['next_hop'].items(): self.cli_set(base + ['next-hop', next_hop]) if 'disable' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'disable']) if 'distance' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) if 'interface' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) if 'vrf' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) if 'bfd' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'bfd']) if 'bfd_profile' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'bfd', 'profile', next_hop_config['bfd_profile']]) if 'bfd_source' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop']) self.cli_set(base + ['next-hop', next_hop, 'bfd', 'source-address', next_hop_config['bfd_source']]) if 'segments' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'segments', next_hop_config['segments']]) if 'interface' in route_config: for interface, interface_config in route_config['interface'].items(): self.cli_set(base + ['interface', interface]) if 'disable' in interface_config: self.cli_set(base + ['interface', interface, 'disable']) if 'distance' in interface_config: self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']]) if 'vrf' in interface_config: self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']]) if 'segments' in interface_config: self.cli_set(base + ['interface', interface, 'segments', interface_config['segments']]) if 'blackhole' in route_config: self.cli_set(base + ['blackhole']) if 'distance' in route_config['blackhole']: self.cli_set(base + ['blackhole', 'distance', route_config['blackhole']['distance']]) if 'tag' in route_config['blackhole']: self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) if 'reject' in route_config: self.cli_set(base + ['reject']) if 'distance' in route_config['reject']: self.cli_set(base + ['reject', 'distance', route_config['reject']['distance']]) if 'tag' in route_config['reject']: self.cli_set(base + ['reject', 'tag', route_config['reject']['tag']]) if {'blackhole', 'reject'} <= set(route_config): # Can not use blackhole and reject at the same time with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base + ['blackhole']) self.cli_delete(base + ['reject']) # commit changes self.cli_commit() # Verify FRR bgpd configuration frrconfig = self.getFRRconfig('ip route', end='') # Verify routes for route, route_config in routes.items(): ip_ipv6 = 'ip' if is_ipv6(route): ip_ipv6 = 'ipv6' if 'next_hop' in route_config: for next_hop, next_hop_config in route_config['next_hop'].items(): tmp = f'{ip_ipv6} route {route} {next_hop}' if 'interface' in next_hop_config: tmp += ' ' + next_hop_config['interface'] if 'distance' in next_hop_config: tmp += ' ' + next_hop_config['distance'] if 'vrf' in next_hop_config: tmp += ' nexthop-vrf ' + next_hop_config['vrf'] if 'bfd' in next_hop_config: tmp += ' bfd' if 'bfd_source' in next_hop_config: tmp += ' multi-hop source ' + next_hop_config['bfd_source'] if 'bfd_profile' in next_hop_config: tmp += ' profile ' + next_hop_config['bfd_profile'] if 'segments' in next_hop_config: tmp += ' segments ' + next_hop_config['segments'] if 'disable' in next_hop_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if 'interface' in route_config: for interface, interface_config in route_config['interface'].items(): tmp = f'{ip_ipv6} route {route} {interface}' if 'interface' in interface_config: tmp += ' ' + interface_config['interface'] if 'distance' in interface_config: tmp += ' ' + interface_config['distance'] if 'vrf' in interface_config: tmp += ' nexthop-vrf ' + interface_config['vrf'] if 'segments' in interface_config: tmp += ' segments ' + interface_config['segments'] if 'disable' in interface_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if {'blackhole', 'reject'} <= set(route_config): # Can not use blackhole and reject at the same time # Config error validated above - skip this route continue if 'blackhole' in route_config: tmp = f'{ip_ipv6} route {route} blackhole' if 'tag' in route_config['blackhole']: tmp += ' tag ' + route_config['blackhole']['tag'] if 'distance' in route_config['blackhole']: tmp += ' ' + route_config['blackhole']['distance'] self.assertIn(tmp, frrconfig) if 'reject' in route_config: tmp = f'{ip_ipv6} route {route} reject' if 'tag' in route_config['reject']: tmp += ' tag ' + route_config['reject']['tag'] if 'distance' in route_config['reject']: tmp += ' ' + route_config['reject']['distance'] self.assertIn(tmp, frrconfig) def test_02_static_table(self): for table in tables: for route, route_config in routes.items(): route_type = 'route' if is_ipv6(route): route_type = 'route6' base = base_path + ['table', table, route_type, route] if 'next_hop' in route_config: for next_hop, next_hop_config in route_config['next_hop'].items(): self.cli_set(base + ['next-hop', next_hop]) if 'disable' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'disable']) if 'distance' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) if 'interface' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) if 'vrf' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) if 'interface' in route_config: for interface, interface_config in route_config['interface'].items(): self.cli_set(base + ['interface', interface]) if 'disable' in interface_config: self.cli_set(base + ['interface', interface, 'disable']) if 'distance' in interface_config: self.cli_set(base + ['interface', interface, 'distance', interface_config['distance']]) if 'vrf' in interface_config: self.cli_set(base + ['interface', interface, 'vrf', interface_config['vrf']]) if 'blackhole' in route_config: self.cli_set(base + ['blackhole']) if 'distance' in route_config['blackhole']: self.cli_set(base + ['blackhole', 'distance', route_config['blackhole']['distance']]) if 'tag' in route_config['blackhole']: self.cli_set(base + ['blackhole', 'tag', route_config['blackhole']['tag']]) # commit changes self.cli_commit() # Verify FRR bgpd configuration frrconfig = self.getFRRconfig('ip route', end='') for table in tables: # Verify routes for route, route_config in routes.items(): ip_ipv6 = 'ip' if is_ipv6(route): ip_ipv6 = 'ipv6' if 'next_hop' in route_config: for next_hop, next_hop_config in route_config['next_hop'].items(): tmp = f'{ip_ipv6} route {route} {next_hop}' if 'interface' in next_hop_config: tmp += ' ' + next_hop_config['interface'] if 'distance' in next_hop_config: tmp += ' ' + next_hop_config['distance'] if 'vrf' in next_hop_config: tmp += ' nexthop-vrf ' + next_hop_config['vrf'] tmp += ' table ' + table if 'disable' in next_hop_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if 'interface' in route_config: for interface, interface_config in route_config['interface'].items(): tmp = f'{ip_ipv6} route {route} {interface}' if 'interface' in interface_config: tmp += ' ' + interface_config['interface'] if 'distance' in interface_config: tmp += ' ' + interface_config['distance'] if 'vrf' in interface_config: tmp += ' nexthop-vrf ' + interface_config['vrf'] tmp += ' table ' + table if 'disable' in interface_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if 'blackhole' in route_config: tmp = f'{ip_ipv6} route {route} blackhole' if 'tag' in route_config['blackhole']: tmp += ' tag ' + route_config['blackhole']['tag'] if 'distance' in route_config['blackhole']: tmp += ' ' + route_config['blackhole']['distance'] tmp += ' table ' + table self.assertIn(tmp, frrconfig) def test_03_static_vrf(self): # Create VRF instances and apply the static routes from above to FRR. # Re-read the configured routes and match them if they are programmed # properly. This also includes VRF leaking vrfs = { 'red' : { 'table' : '1000' }, 'green' : { 'table' : '2000' }, 'blue' : { 'table' : '3000' }, } for vrf, vrf_config in vrfs.items(): vrf_base_path = ['vrf', 'name', vrf] self.cli_set(vrf_base_path + ['table', vrf_config['table']]) for route, route_config in routes.items(): route_type = 'route' if is_ipv6(route): route_type = 'route6' route_base_path = vrf_base_path + ['protocols', 'static', route_type, route] if 'next_hop' in route_config: for next_hop, next_hop_config in route_config['next_hop'].items(): self.cli_set(route_base_path + ['next-hop', next_hop]) if 'disable' in next_hop_config: self.cli_set(route_base_path + ['next-hop', next_hop, 'disable']) if 'distance' in next_hop_config: self.cli_set(route_base_path + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) if 'interface' in next_hop_config: self.cli_set(route_base_path + ['next-hop', next_hop, 'interface', next_hop_config['interface']]) if 'vrf' in next_hop_config: self.cli_set(route_base_path + ['next-hop', next_hop, 'vrf', next_hop_config['vrf']]) if 'segments' in next_hop_config: self.cli_set(route_base_path + ['next-hop', next_hop, 'segments', next_hop_config['segments']]) if 'interface' in route_config: for interface, interface_config in route_config['interface'].items(): self.cli_set(route_base_path + ['interface', interface]) if 'disable' in interface_config: self.cli_set(route_base_path + ['interface', interface, 'disable']) if 'distance' in interface_config: self.cli_set(route_base_path + ['interface', interface, 'distance', interface_config['distance']]) if 'vrf' in interface_config: self.cli_set(route_base_path + ['interface', interface, 'vrf', interface_config['vrf']]) if 'segments' in interface_config: self.cli_set(route_base_path + ['interface', interface, 'segments', interface_config['segments']]) if 'blackhole' in route_config: self.cli_set(route_base_path + ['blackhole']) if 'distance' in route_config['blackhole']: self.cli_set(route_base_path + ['blackhole', 'distance', route_config['blackhole']['distance']]) if 'tag' in route_config['blackhole']: self.cli_set(route_base_path + ['blackhole', 'tag', route_config['blackhole']['tag']]) # commit changes self.cli_commit() for vrf, vrf_config in vrfs.items(): tmp = get_interface_config(vrf) # Compare VRF table ID self.assertEqual(get_vrf_tableid(vrf), int(vrf_config['table'])) self.assertEqual(tmp['linkinfo']['info_kind'], 'vrf') # Verify FRR bgpd configuration frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f'vrf {vrf}', frrconfig) # Verify routes for route, route_config in routes.items(): ip_ipv6 = 'ip' if is_ipv6(route): ip_ipv6 = 'ipv6' if 'next_hop' in route_config: for next_hop, next_hop_config in route_config['next_hop'].items(): tmp = f'{ip_ipv6} route {route} {next_hop}' if 'interface' in next_hop_config: tmp += ' ' + next_hop_config['interface'] if 'distance' in next_hop_config: tmp += ' ' + next_hop_config['distance'] if 'vrf' in next_hop_config: tmp += ' nexthop-vrf ' + next_hop_config['vrf'] if 'segments' in next_hop_config: tmp += ' segments ' + next_hop_config['segments'] if 'disable' in next_hop_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if 'interface' in route_config: for interface, interface_config in route_config['interface'].items(): tmp = f'{ip_ipv6} route {route} {interface}' if 'interface' in interface_config: tmp += ' ' + interface_config['interface'] if 'distance' in interface_config: tmp += ' ' + interface_config['distance'] if 'vrf' in interface_config: tmp += ' nexthop-vrf ' + interface_config['vrf'] if 'segments' in interface_config: tmp += ' segments ' + interface_config['segments'] if 'disable' in interface_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if 'blackhole' in route_config: tmp = f'{ip_ipv6} route {route} blackhole' if 'tag' in route_config['blackhole']: tmp += ' tag ' + route_config['blackhole']['tag'] if 'distance' in route_config['blackhole']: tmp += ' ' + route_config['blackhole']['distance'] self.assertIn(tmp, frrconfig) def test_04_static_multicast(self): for route, route_config in multicast_routes.items(): if 'next_hop' in route_config: - base = base_path + ['multicast', 'route', route] + base = base_path + ['mroute', route] for next_hop, next_hop_config in route_config['next_hop'].items(): self.cli_set(base + ['next-hop', next_hop]) if 'distance' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'distance', next_hop_config['distance']]) if 'disable' in next_hop_config: self.cli_set(base + ['next-hop', next_hop, 'disable']) if 'interface' in route_config: - base = base_path + ['multicast', 'route', route] + base = base_path + ['mroute', route] for next_hop, next_hop_config in route_config['interface'].items(): self.cli_set(base + ['interface', next_hop]) if 'distance' in next_hop_config: self.cli_set(base + ['interface', next_hop, 'distance', next_hop_config['distance']]) self.cli_commit() # Verify FRR configuration frrconfig = self.getFRRconfig('ip mroute', end='') for route, route_config in multicast_routes.items(): if 'next_hop' in route_config: for next_hop, next_hop_config in route_config['next_hop'].items(): tmp = f'ip mroute {route} {next_hop}' if 'distance' in next_hop_config: tmp += ' ' + next_hop_config['distance'] if 'disable' in next_hop_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if 'next_hop_interface' in route_config: for next_hop, next_hop_config in route_config['next_hop_interface'].items(): tmp = f'ip mroute {route} {next_hop}' if 'distance' in next_hop_config: tmp += ' ' + next_hop_config['distance'] if 'disable' in next_hop_config: self.assertNotIn(tmp, frrconfig) else: self.assertIn(tmp, frrconfig) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py index 4cd5fb169..7737f9df5 100644 --- a/src/tests/test_initial_setup.py +++ b/src/tests/test_initial_setup.py @@ -1,99 +1,99 @@ # Copyright (C) 2018-2024 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 import vyos.configtree import vyos.initialsetup as vis from unittest import TestCase from vyos.xml_ref import definition from vyos.xml_ref.pkg_cache.vyos_1x_cache import reference class TestInitialSetup(TestCase): def setUp(self): with open('tests/data/config.boot.default', 'r') as f: config_string = f.read() self.config = vyos.configtree.ConfigTree(config_string) self.xml = definition.Xml() self.xml.define(reference) def test_set_user_password(self): vis.set_user_password(self.config, 'vyos', 'vyosvyos') # Old password hash from the default config old_pw = '$6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/' new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"]) # Just check it changed the hash, don't try to check if hash is good self.assertNotEqual(old_pw, new_pw) def test_disable_user_password(self): vis.disable_user_password(self.config, 'vyos') new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"]) self.assertEqual(new_pw, '!') def test_set_ssh_key_with_name(self): test_ssh_key = " ssh-rsa fakedata vyos@vyos " vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key) key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "type"]) key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "key"]) self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_set_ssh_key_without_name(self): # If key file doesn't include a name, the function will use user name for the key name test_ssh_key = " ssh-rsa fakedata " vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key) key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "type"]) key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "key"]) self.assertEqual(key_type, 'ssh-rsa') self.assertEqual(key_data, 'fakedata') self.assertTrue(self.xml.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"])) def test_create_user(self): vis.create_user(self.config, 'jrandomhacker', password='qwerty', key=" ssh-rsa fakedata jrandomhacker@foovax ") self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker"])) self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "public-keys", "jrandomhacker@foovax"])) self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "encrypted-password"])) self.assertEqual(self.config.return_value(["system", "login", "user", "jrandomhacker", "level"]), "admin") def test_set_hostname(self): vis.set_host_name(self.config, "vyos-test") self.assertEqual(self.config.return_value(["system", "host-name"]), "vyos-test") def test_set_name_servers(self): vis.set_name_servers(self.config, ["192.0.2.10", "203.0.113.20"]) servers = self.config.return_values(["system", "name-server"]) self.assertIn("192.0.2.10", servers) self.assertIn("203.0.113.20", servers) def test_set_gateway(self): vis.set_default_gateway(self.config, '192.0.2.1') self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route', '0.0.0.0/0', 'next-hop'])) - self.assertTrue(self.xml.is_tag(['protocols', 'static', 'multicast', 'route'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute', '0.0.0.0/0', 'next-hop'])) + self.assertTrue(self.xml.is_tag(['protocols', 'static', 'mroute'])) if __name__ == "__main__": unittest.main()