From cbd2d71fc85f89f322f1d5c85052034b0b57b3b9 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 5 Jan 2021 21:15:02 +0100 Subject: smoketest: tunnel: fix setUp() ordering for reference base class members With commit ce809eee ("smoketest: mirror: T3169: re-add mirror / SPAN test case") the setUp() method has been re-organized not taking into account that there will already be a reference to self.session which will only be created by the base class. --- smoketest/scripts/cli/test_interfaces_tunnel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index f7b7f99ca..ca68cb8ba 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -67,9 +67,6 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self.local_v4 = '192.0.2.1' self.local_v6 = '2001:db8::1' - self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) - self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) - self._options = { 'tun10': ['encapsulation ipip', 'remote-ip 192.0.2.10', 'local-ip ' + self.local_v4], 'tun20': ['encapsulation gre', 'remote-ip 192.0.2.20', 'local-ip ' + self.local_v4], @@ -78,6 +75,9 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest): self._interfaces = list(self._options) super().setUp() + self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v4 + '/32']) + self.session.set(['interfaces', 'dummy', source_if, 'address', self.local_v6 + '/128']) + def tearDown(self): self.session.delete(['interfaces', 'dummy', source_if]) super().tearDown() -- cgit v1.2.3 From 216b4511329f96c2a9287e0caade0a4c76a9ba69 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 Jan 2021 12:43:11 +0100 Subject: smoketest: bgp: refactor verify part to be reusable --- smoketest/scripts/cli/test_protocols_bgp.py | 148 +++++++++++++++++----------- 1 file changed, 88 insertions(+), 60 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 941d7828f..540ef2ec4 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -28,6 +28,8 @@ base_path = ['protocols', 'bgp', ASN] neighbor_config = { '192.0.2.1' : { + 'cap_dynamic' : '', + 'cap_ext_next': '', 'remote_as' : '100', 'adv_interv' : '400', 'passive' : '', @@ -35,6 +37,7 @@ neighbor_config = { 'shutdown' : '', 'cap_over' : '', 'ttl_security': '5', + 'local_as' : '300', }, '192.0.2.2' : { 'remote_as' : '200', @@ -49,6 +52,7 @@ neighbor_config = { 'remote_as' : '200', 'passive' : '', 'multi_hop' : '5', + 'update_src' : 'lo', }, } @@ -63,18 +67,23 @@ peer_group_config = { # 'ttl_security': '5', }, 'bar' : { +# XXX: not available in current Perl backend +# 'description' : 'foo peer bar group', 'remote_as' : '200', 'shutdown' : '', 'no_cap_nego' : '', + 'local_as' : '300', }, 'baz' : { + 'cap_dynamic' : '', + 'cap_ext_next': '', 'remote_as' : '200', 'passive' : '', 'multi_hop' : '5', + 'update_src' : 'lo', }, } - def getFRRBGPconfig(): return cmd(f'vtysh -c "show run" | sed -n "/router bgp {ASN}/,/^!/p"') @@ -87,6 +96,35 @@ class TestProtocolsBGP(unittest.TestCase): self.session.commit() del self.session + def verify_frr_config(self, peer, peer_config, frrconfig): + # recurring patterns to verify for both a simple neighbor and a peer-group + if 'cap_dynamic' in peer_config: + self.assertIn(f' neighbor {peer} capability dynamic', frrconfig) + if 'cap_ext_next' in peer_config: + self.assertIn(f' neighbor {peer} capability extended-nexthop', frrconfig) + if 'description' in peer_config: + self.assertIn(f' neighbor {peer} description {peer_config["description"]}', frrconfig) + if 'no_cap_nego' in peer_config: + self.assertIn(f' neighbor {peer} dont-capability-negotiate', frrconfig) + if 'multi_hop' in peer_config: + self.assertIn(f' neighbor {peer} ebgp-multihop {peer_config["multi_hop"]}', frrconfig) + if 'local_as' in peer_config: + self.assertIn(f' neighbor {peer} local-as {peer_config["local_as"]}', frrconfig) + if 'cap_over' in peer_config: + self.assertIn(f' neighbor {peer} override-capability', frrconfig) + if 'passive' in peer_config: + self.assertIn(f' neighbor {peer} passive', frrconfig) + if 'password' in peer_config: + self.assertIn(f' neighbor {peer} password {peer_config["password"]}', frrconfig) + if 'remote_as' in peer_config: + self.assertIn(f' neighbor {peer} remote-as {peer_config["remote_as"]}', frrconfig) + if 'shutdown' in peer_config: + self.assertIn(f' neighbor {peer} shutdown', frrconfig) + if 'ttl_security' in peer_config: + self.assertIn(f' neighbor {peer} ttl-security hops {peer_config["ttl_security"]}', frrconfig) + if 'update_src' in peer_config: + self.assertIn(f' neighbor {peer} update-source {peer_config["update_src"]}', frrconfig) + def test_bgp_01_simple(self): router_id = '127.0.0.1' local_pref = '500' @@ -113,31 +151,41 @@ class TestProtocolsBGP(unittest.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) def test_bgp_02_neighbors(self): + # Test out individual neighbor configuration items, not all of them are + # also available to a peer-group! for neighbor, config in neighbor_config.items(): - if 'remote_as' in config: - self.session.set(base_path + ['neighbor', neighbor, 'remote-as', config["remote_as"]]) - if 'description' in config: - self.session.set(base_path + ['neighbor', neighbor, 'description', config["description"]]) - if 'passive' in config: - self.session.set(base_path + ['neighbor', neighbor, 'passive']) - if 'password' in config: - self.session.set(base_path + ['neighbor', neighbor, 'password', config["password"]]) - if 'shutdown' in config: - self.session.set(base_path + ['neighbor', neighbor, 'shutdown']) if 'adv_interv' in config: self.session.set(base_path + ['neighbor', neighbor, 'advertisement-interval', config["adv_interv"]]) + if 'cap_dynamic' in config: + self.session.set(base_path + ['neighbor', neighbor, 'capability', 'dynamic']) + if 'cap_ext_next' in config: + self.session.set(base_path + ['neighbor', neighbor, 'capability', 'extended-nexthop']) + if 'description' in config: + self.session.set(base_path + ['neighbor', neighbor, 'description', config["description"]]) if 'no_cap_nego' in config: self.session.set(base_path + ['neighbor', neighbor, 'disable-capability-negotiation']) - if 'port' in config: - self.session.set(base_path + ['neighbor', neighbor, 'port', config["port"]]) if 'multi_hop' in config: self.session.set(base_path + ['neighbor', neighbor, 'ebgp-multihop', config["multi_hop"]]) + if 'local_as' in config: + self.session.set(base_path + ['neighbor', neighbor, 'local-as', config["local_as"]]) if 'cap_over' in config: self.session.set(base_path + ['neighbor', neighbor, 'override-capability']) + if 'passive' in config: + self.session.set(base_path + ['neighbor', neighbor, 'passive']) + if 'password' in config: + self.session.set(base_path + ['neighbor', neighbor, 'password', config["password"]]) + if 'port' in config: + self.session.set(base_path + ['neighbor', neighbor, 'port', config["port"]]) + if 'remote_as' in config: + self.session.set(base_path + ['neighbor', neighbor, 'remote-as', config["remote_as"]]) if 'cap_strict' in config: self.session.set(base_path + ['neighbor', neighbor, 'strict-capability-match']) + if 'shutdown' in config: + self.session.set(base_path + ['neighbor', neighbor, 'shutdown']) if 'ttl_security' in config: self.session.set(base_path + ['neighbor', neighbor, 'ttl-security', 'hops', config["ttl_security"]]) + if 'update_src' in config: + self.session.set(base_path + ['neighbor', neighbor, 'update-source', config["update_src"]]) # commit changes self.session.commit() @@ -146,49 +194,45 @@ class TestProtocolsBGP(unittest.TestCase): frrconfig = getFRRBGPconfig() self.assertIn(f'router bgp {ASN}', frrconfig) - for neighbor, config in neighbor_config.items(): - if 'remote_as' in config: - self.assertIn(f' neighbor {neighbor} remote-as {config["remote_as"]}', frrconfig) - if 'description' in config: - self.assertIn(f' neighbor {neighbor} description {config["description"]}', frrconfig) - if 'passive' in config: - self.assertIn(f' neighbor {neighbor} passive', frrconfig) - if 'password' in config: - self.assertIn(f' neighbor {neighbor} password {config["password"]}', frrconfig) - if 'shutdown' in config: - self.assertIn(f' neighbor {neighbor} shutdown', frrconfig) + for peer, peer_config in neighbor_config.items(): if 'adv_interv' in config: - self.assertIn(f' neighbor {neighbor} advertisement-interval {config["adv_interv"]}', frrconfig) - if 'no_cap_nego' in config: - self.assertIn(f' neighbor {neighbor} dont-capability-negotiate', frrconfig) + self.assertIn(f' neighbor {peer} advertisement-interval {peer_config["adv_interv"]}', frrconfig) if 'port' in config: - self.assertIn(f' neighbor {neighbor} port {config["port"]}', frrconfig) - if 'multi_hop' in config: - self.assertIn(f' neighbor {neighbor} ebgp-multihop {config["multi_hop"]}', frrconfig) - if 'cap_over' in config: - self.assertIn(f' neighbor {neighbor} override-capability', frrconfig) + self.assertIn(f' neighbor {peer} port {peer_config["port"]}', frrconfig) if 'cap_strict' in config: - self.assertIn(f' neighbor {neighbor} strict-capability-match', frrconfig) - if 'ttl_security' in config: - self.assertIn(f' neighbor {neighbor} ttl-security hops {config["ttl_security"]}', frrconfig) + self.assertIn(f' neighbor {peer} strict-capability-match', frrconfig) + + self.verify_frr_config(peer, peer_config, frrconfig) def test_bgp_03_peer_groups(self): + # Test out individual peer-group configuration items for peer_group, config in peer_group_config.items(): - self.session.set(base_path + ['peer-group', peer_group, 'remote-as', config["remote_as"]]) - if 'passive' in config: - self.session.set(base_path + ['peer-group', peer_group, 'passive']) - if 'password' in config: - self.session.set(base_path + ['peer-group', peer_group, 'password', config["password"]]) - if 'shutdown' in config: - self.session.set(base_path + ['peer-group', peer_group, 'shutdown']) + if 'cap_dynamic' in config: + self.session.set(base_path + ['peer-group', peer_group, 'capability', 'dynamic']) + if 'cap_ext_next' in config: + self.session.set(base_path + ['peer-group', peer_group, 'capability', 'extended-nexthop']) + if 'description' in config: + self.session.set(base_path + ['peer-group', peer_group, 'description', config["description"]]) if 'no_cap_nego' in config: self.session.set(base_path + ['peer-group', peer_group, 'disable-capability-negotiation']) if 'multi_hop' in config: self.session.set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) + if 'local_as' in config: + self.session.set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"]]) if 'cap_over' in config: self.session.set(base_path + ['peer-group', peer_group, 'override-capability']) + if 'passive' in config: + self.session.set(base_path + ['peer-group', peer_group, 'passive']) + if 'password' in config: + self.session.set(base_path + ['peer-group', peer_group, 'password', config["password"]]) + if 'remote_as' in config: + self.session.set(base_path + ['peer-group', peer_group, 'remote-as', config["remote_as"]]) + if 'shutdown' in config: + self.session.set(base_path + ['peer-group', peer_group, 'shutdown']) if 'ttl_security' in config: self.session.set(base_path + ['peer-group', peer_group, 'ttl-security', 'hops', config["ttl_security"]]) + if 'update_src' in config: + self.session.set(base_path + ['peer-group', peer_group, 'update-source', config["update_src"]]) # commit changes self.session.commit() @@ -197,25 +241,9 @@ class TestProtocolsBGP(unittest.TestCase): frrconfig = getFRRBGPconfig() self.assertIn(f'router bgp {ASN}', frrconfig) - for peer_group, config in peer_group_config.items(): + for peer, peer_config in peer_group_config.items(): self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) - - if 'remote_as' in config: - self.assertIn(f' neighbor {peer_group} remote-as {config["remote_as"]}', frrconfig) - if 'passive' in config: - self.assertIn(f' neighbor {peer_group} passive', frrconfig) - if 'password' in config: - self.assertIn(f' neighbor {peer_group} password {config["password"]}', frrconfig) - if 'shutdown' in config: - self.assertIn(f' neighbor {peer_group} shutdown', frrconfig) - if 'no_cap_nego' in config: - self.assertIn(f' neighbor {peer_group} dont-capability-negotiate', frrconfig) - if 'multi_hop' in config: - self.assertIn(f' neighbor {peer_group} ebgp-multihop {config["multi_hop"]}', frrconfig) - if 'cap_over' in config: - self.assertIn(f' neighbor {peer_group} override-capability', frrconfig) - if 'ttl_security' in config: - self.assertIn(f' neighbor {peer_group} ttl-security hops {config["ttl_security"]}', frrconfig) + self.verify_frr_config(peer, peer_config, frrconfig) if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From 9f33a14ac2a2e85da16b3169465155ca6114d09e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 6 Jan 2021 13:10:01 +0100 Subject: smoketest: bgp: add ipv4/ipv6 address-family tests --- smoketest/scripts/cli/test_protocols_bgp.py | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index 540ef2ec4..1d93aeda4 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -245,5 +245,98 @@ class TestProtocolsBGP(unittest.TestCase): self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) self.verify_frr_config(peer, peer_config, frrconfig) + + def test_bgp_04_afi_ipv4(self): + networks = { + '10.0.0.0/8' : { + 'as_set' : '', + }, + '100.64.0.0/10' : { + 'as_set' : '', + }, + '192.168.0.0/16' : { + 'summary_only' : '', + }, + } + + # We want to redistribute ... + redistributes = ['connected', 'kernel', 'ospf', 'rip', 'static'] + for redistribute in redistributes: + self.session.set(base_path + ['address-family', 'ipv4-unicast', + 'redistribute', redistribute]) + + for network, network_config in networks.items(): + self.session.set(base_path + ['address-family', 'ipv4-unicast', + 'network', network]) + if 'as_set' in network_config: + self.session.set(base_path + ['address-family', 'ipv4-unicast', + 'aggregate-address', network, 'as-set']) + if 'summary_only' in network_config: + self.session.set(base_path + ['address-family', 'ipv4-unicast', + 'aggregate-address', network, 'summary-only']) + + # commit changes + self.session.commit() + + # Verify FRR bgpd configuration + frrconfig = getFRRBGPconfig() + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family ipv4 unicast', frrconfig) + + for redistribute in redistributes: + self.assertIn(f' redistribute {redistribute}', frrconfig) + + for network, network_config in networks.items(): + self.assertIn(f' network {network}', frrconfig) + if 'as_set' in network_config: + self.assertIn(f' aggregate-address {network} as-set', frrconfig) + if 'summary_only' in network_config: + self.assertIn(f' aggregate-address {network} summary-only', frrconfig) + + + def test_bgp_05_afi_ipv6(self): + networks = { + '2001:db8:100::/48' : { + }, + '2001:db8:200::/48' : { + }, + '2001:db8:300::/48' : { + 'summary_only' : '', + }, + } + + # We want to redistribute ... + redistributes = ['connected', 'kernel', 'ospfv3', 'ripng', 'static'] + for redistribute in redistributes: + self.session.set(base_path + ['address-family', 'ipv6-unicast', + 'redistribute', redistribute]) + + for network, network_config in networks.items(): + self.session.set(base_path + ['address-family', 'ipv6-unicast', + 'network', network]) + if 'summary_only' in network_config: + self.session.set(base_path + ['address-family', 'ipv6-unicast', + 'aggregate-address', network, 'summary-only']) + + # commit changes + self.session.commit() + + # Verify FRR bgpd configuration + frrconfig = getFRRBGPconfig() + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f' address-family ipv6 unicast', frrconfig) + + for redistribute in redistributes: + # FRR calls this OSPF6 + if redistribute == 'ospfv3': + redistribute = 'ospf6' + self.assertIn(f' redistribute {redistribute}', frrconfig) + + for network, network_config in networks.items(): + self.assertIn(f' network {network}', frrconfig) + if 'as_set' in network_config: + self.assertIn(f' aggregate-address {network} summary-only', frrconfig) + + if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3 From a8e4317c61b1253fa02044cffc7a588d45259a5e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 17:01:09 +0100 Subject: smoketest: interfaces: test dhcpv6 pd sla-id auto increment --- smoketest/scripts/cli/base_interfaces_test.py | 125 ++++++++++++++++++++------ 1 file changed, 96 insertions(+), 29 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 8ee5395d0..ad7886414 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-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 @@ -12,7 +12,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import re import os import unittest import json @@ -51,17 +50,6 @@ def is_mirrored_to(interface, mirror_if, qdisc): ret_val = True return ret_val - -dhcp6c_config_file = '/run/dhcp6c/dhcp6c.{}.conf' -def get_dhcp6c_config_value(interface, key): - tmp = read_file(dhcp6c_config_file.format(interface)) - tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) - - out = [] - for item in tmp: - out.append(item.replace(';','')) - return out - class BasicInterfaceTest: class BaseTest(unittest.TestCase): _test_ip = False @@ -378,13 +366,73 @@ class BasicInterfaceTest: self.assertEqual(dad_transmits, tmp) - def test_ipv6_dhcpv6_prefix_delegation(self): + def test_ipv6_dhcpv6_pd_auto_inc_sla_id(self): if not self._test_ipv6: return None + address = '1' + prefix_len = '56' + sla_len = str(64 - int(prefix_len)) + + delegatees = ['dum2340', 'dum2341', 'dum2342', 'dum2343', 'dum2344'] + + for interface in self._interfaces: + path = self._base_path + [interface] + for option in self._options.get(interface, []): + self.session.set(path + option.split()) + + # prefix delegation stuff + pd_base = path + ['dhcpv6-options', 'pd', '0'] + self.session.set(pd_base + ['length', prefix_len]) + + for delegatee in delegatees: + section = Section.section(delegatee) + self.session.set(['interfaces', section, delegatee]) + self.session.set(pd_base + ['interface', delegatee, 'address', address]) + # increment interface address + address = str(int(address) + 1) + + self.session.commit() + address = '1' sla_id = '0' - sla_len = '8' + for interface in self._interfaces: + dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + + # verify DHCPv6 prefix delegation + self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + + for delegatee in delegatees: + self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) + self.assertIn(f'ifid {address};', dhcpc6_config) + self.assertIn(f'sla-id {sla_id};', dhcpc6_config) + self.assertIn(f'sla-len {sla_len};', dhcpc6_config) + + # increment sla-id + sla_id = str(int(sla_id) + 1) + # increment interface address + address = str(int(address) + 1) + + # Check for running process + self.assertTrue(process_named_running('dhcp6c')) + + for delegatee in delegatees: + # we can already cleanup the test delegatee interface here + # as until commit() is called, nothing happens + section = Section.section(delegatee) + self.session.delete(['interfaces', section, delegatee]) + + def test_ipv6_dhcpv6_pd_manual_sla_id(self): + if not self._test_ipv6: + return None + + address = '1' + prefix_len = '56' + sla_len = str(64 - int(prefix_len)) + sla_id = '1' + + delegatees = ['dum3340', 'dum3341', 'dum3342', 'dum3343', 'dum3344'] + for interface in self._interfaces: path = self._base_path + [interface] for option in self._options.get(interface, []): @@ -392,25 +440,44 @@ class BasicInterfaceTest: # prefix delegation stuff pd_base = path + ['dhcpv6-options', 'pd', '0'] - self.session.set(pd_base + ['length', '56']) - self.session.set(pd_base + ['interface', interface, 'address', address]) - self.session.set(pd_base + ['interface', interface, 'sla-id', sla_id]) + self.session.set(pd_base + ['length', prefix_len]) + + for delegatee in delegatees: + section = Section.section(delegatee) + self.session.set(['interfaces', section, delegatee]) + self.session.set(pd_base + ['interface', delegatee, 'address', address]) + self.session.set(pd_base + ['interface', delegatee, 'sla-id', address]) + + # increment interface address + address = str(int(address) + 1) + sla_id = str(int(sla_id) + 1) self.session.commit() + address = '1' + sla_id = '1' for interface in self._interfaces: + dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + # verify DHCPv6 prefix delegation - # will return: ['delegation', '::/56 infinity;'] - tmp = get_dhcp6c_config_value(interface, 'prefix')[1].split()[0] # mind the whitespace - self.assertEqual(tmp, '::/56') - tmp = get_dhcp6c_config_value(interface, 'prefix-interface')[0].split()[0] - self.assertEqual(tmp, interface) - tmp = get_dhcp6c_config_value(interface, 'ifid')[0] - self.assertEqual(tmp, address) - tmp = get_dhcp6c_config_value(interface, 'sla-id')[0] - self.assertEqual(tmp, sla_id) - tmp = get_dhcp6c_config_value(interface, 'sla-len')[0] - self.assertEqual(tmp, sla_len) + self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + + for delegatee in delegatees: + self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) + self.assertIn(f'ifid {address};', dhcpc6_config) + self.assertIn(f'sla-id {sla_id};', dhcpc6_config) + self.assertIn(f'sla-len {sla_len};', dhcpc6_config) + + # increment sla-id + sla_id = str(int(sla_id) + 1) + # increment interface address + address = str(int(address) + 1) # Check for running process self.assertTrue(process_named_running('dhcp6c')) + + for delegatee in delegatees: + # we can already cleanup the test delegatee interface here + # as until commit() is called, nothing happens + section = Section.section(delegatee) + self.session.delete(['interfaces', section, delegatee]) -- cgit v1.2.3 From b9feaf0d6be3adf179df6f35fcd8416d128750f6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 18:33:23 +0100 Subject: login: radius: T3192: support IPv6 server(s) and source-address --- data/templates/login/pam_radius_auth.conf.tmpl | 33 +++++++++++ .../system-login/pam_radius_auth.conf.tmpl | 16 ------ .../include/radius-server-ipv4-ipv6.xml.i | 32 +++++++++++ .../include/source-address-ipv4-ipv6.xml.i | 1 + interface-definitions/system-login.xml.in | 2 +- smoketest/scripts/cli/test_system_login.py | 66 +++++++++++++++++++++- src/conf_mode/system-login.py | 47 ++++++++------- 7 files changed, 157 insertions(+), 40 deletions(-) create mode 100644 data/templates/login/pam_radius_auth.conf.tmpl delete mode 100644 data/templates/system-login/pam_radius_auth.conf.tmpl create mode 100644 interface-definitions/include/radius-server-ipv4-ipv6.xml.i (limited to 'smoketest/scripts') diff --git a/data/templates/login/pam_radius_auth.conf.tmpl b/data/templates/login/pam_radius_auth.conf.tmpl new file mode 100644 index 000000000..56a5e10ee --- /dev/null +++ b/data/templates/login/pam_radius_auth.conf.tmpl @@ -0,0 +1,33 @@ +# Automatically generated by system-login.py +# RADIUS configuration file + +{# RADIUS IPv6 source address must be specified in [] notation #} +{% set source_address = namespace() %} +{% if radius_source_address is defined and radius_source_address is not none %} +{% for address in radius_source_address %} +{% if address | is_ipv4 %} +{% set source_address.ipv4 = address %} +{% elif address | is_ipv6 %} +{% set source_address.ipv6 = "[" + address + "]" %} +{% endif %} +{% endfor %} +{% endif %} +{% if radius_server is defined and radius_server is not none %} +# server[:port] shared_secret timeout source_ip +{% for server in radius_server | sort(attribute='priority') if not server.disabled %} +{# RADIUS IPv6 servers must be specified in [] notation #} +{% if server.address | is_ipv4 %} +{{ server.address }}:{{ server.port }} {{ "%-25s" | format(server.key) }} {{ "%-10s" | format(server.timeout) }} {{ source_address.ipv4 if source_address.ipv4 is defined }} +{% else %} +[{{ server.address }}]:{{ server.port }} {{ "%-25s" | format(server.key) }} {{ "%-10s" | format(server.timeout) }} {{ source_address.ipv6 if source_address.ipv6 is defined }} +{% endif %} +{% endfor %} + +priv-lvl 15 +mapped_priv_user radius_priv_user + +{% if radius_vrf %} +vrf-name {{ radius_vrf }} +{% endif %} +{% endif %} + diff --git a/data/templates/system-login/pam_radius_auth.conf.tmpl b/data/templates/system-login/pam_radius_auth.conf.tmpl deleted file mode 100644 index ec2d6df95..000000000 --- a/data/templates/system-login/pam_radius_auth.conf.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -# Automatically generated by system-login.py -# RADIUS configuration file -{% if radius_server %} -# server[:port] shared_secret timeout source_ip -{% for s in radius_server|sort(attribute='priority') if not s.disabled %} -{% set addr_port = s.address + ":" + s.port %} -{{ "%-22s" | format(addr_port) }} {{ "%-25s" | format(s.key) }} {{ "%-10s" | format(s.timeout) }} {{ radius_source_address if radius_source_address }} -{% endfor %} - -priv-lvl 15 -mapped_priv_user radius_priv_user - -{% if radius_vrf %} -vrf-name {{ radius_vrf }} -{% endif %} -{% endif %} diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i new file mode 100644 index 000000000..e947c09e2 --- /dev/null +++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i @@ -0,0 +1,32 @@ + + + + RADIUS based user authentication + + + #include + + + RADIUS server configuration + + ipv4 + RADIUS server IPv4 address + + + ipv6 + RADIUS server IPv6 address + + + + + + + + #include + #include + #include + + + + + diff --git a/interface-definitions/include/source-address-ipv4-ipv6.xml.i b/interface-definitions/include/source-address-ipv4-ipv6.xml.i index 004e04f7b..4da4698c2 100644 --- a/interface-definitions/include/source-address-ipv4-ipv6.xml.i +++ b/interface-definitions/include/source-address-ipv4-ipv6.xml.i @@ -17,6 +17,7 @@ + diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index 0bea6a22d..6c573bf96 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -110,7 +110,7 @@ - #include + #include diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 6188cf38b..bb6f57fc2 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -24,8 +24,10 @@ from platform import release as kernel_version from subprocess import Popen, PIPE from vyos.configsession import ConfigSession +from vyos.configsession import ConfigSessionError from vyos.util import cmd from vyos.util import read_file +from vyos.template import inc_ip base_path = ['system', 'login'] users = ['vyos1', 'vyos2'] @@ -42,7 +44,7 @@ class TestSystemLogin(unittest.TestCase): self.session.commit() del self.session - def test_local_user(self): + def test_system_login_user(self): # Check if user can be created and we can SSH to localhost self.session.set(['service', 'ssh', 'port', '22']) @@ -82,7 +84,7 @@ class TestSystemLogin(unittest.TestCase): for option in options: self.assertIn(f'{option}=y', kernel_config) - def test_radius_config(self): + def test_system_login_radius_ipv4(self): # Verify generated RADIUS configuration files radius_key = 'VyOSsecretVyOS' @@ -95,6 +97,12 @@ class TestSystemLogin(unittest.TestCase): self.session.set(base_path + ['radius', 'server', radius_server, 'port', radius_port]) self.session.set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout]) self.session.set(base_path + ['radius', 'source-address', radius_source]) + self.session.set(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + # check validate() - Only one IPv4 source-address supported + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) self.session.commit() @@ -130,5 +138,59 @@ class TestSystemLogin(unittest.TestCase): tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf) self.assertTrue(tmp) + def test_system_login_radius_ipv6(self): + # Verify generated RADIUS configuration files + + radius_key = 'VyOS-VyOS' + radius_server = '2001:db8::1' + radius_source = '::1' + radius_port = '4000' + radius_timeout = '4' + + self.session.set(base_path + ['radius', 'server', radius_server, 'key', radius_key]) + self.session.set(base_path + ['radius', 'server', radius_server, 'port', radius_port]) + self.session.set(base_path + ['radius', 'server', radius_server, 'timeout', radius_timeout]) + self.session.set(base_path + ['radius', 'source-address', radius_source]) + self.session.set(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + # check validate() - Only one IPv4 source-address supported + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.delete(base_path + ['radius', 'source-address', inc_ip(radius_source, 1)]) + + self.session.commit() + + # this file must be read with higher permissions + pam_radius_auth_conf = cmd('sudo cat /etc/pam_radius_auth.conf') + tmp = re.findall(r'\n?\[{}\]:{}\s+{}\s+{}\s+\[{}\]'.format(radius_server, + radius_port, radius_key, radius_timeout, + radius_source), pam_radius_auth_conf) + self.assertTrue(tmp) + + # required, static options + self.assertIn('priv-lvl 15', pam_radius_auth_conf) + self.assertIn('mapped_priv_user radius_priv_user', pam_radius_auth_conf) + + # PAM + pam_common_account = read_file('/etc/pam.d/common-account') + self.assertIn('pam_radius_auth.so', pam_common_account) + + pam_common_auth = read_file('/etc/pam.d/common-auth') + self.assertIn('pam_radius_auth.so', pam_common_auth) + + pam_common_session = read_file('/etc/pam.d/common-session') + self.assertIn('pam_radius_auth.so', pam_common_session) + + pam_common_session_noninteractive = read_file('/etc/pam.d/common-session-noninteractive') + self.assertIn('pam_radius_auth.so', pam_common_session_noninteractive) + + # NSS + nsswitch_conf = read_file('/etc/nsswitch.conf') + tmp = re.findall(r'passwd:\s+mapuid\s+files\s+mapname', nsswitch_conf) + self.assertTrue(tmp) + + tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf) + self.assertTrue(tmp) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 39bad717d..92f717df8 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -25,6 +25,7 @@ from sys import exit from vyos.config import Config from vyos.template import render +from vyos.template import is_ipv4 from vyos.util import cmd, call, DEVNULL, chmod_600, chmod_755 from vyos import ConfigError @@ -38,7 +39,7 @@ default_config_data = { 'add_users': [], 'del_users': [], 'radius_server': [], - 'radius_source_address': '', + 'radius_source_address': [], 'radius_vrf': '' } @@ -134,7 +135,7 @@ def get_config(config=None): conf.set_level(base_level + ['radius']) if conf.exists(['source-address']): - login['radius_source_address'] = conf.return_value(['source-address']) + login['radius_source_address'] = conf.return_values(['source-address']) # retrieve VRF instance if conf.exists(['vrf']): @@ -214,6 +215,17 @@ def verify(login): if fail: raise ConfigError('At least one RADIUS server must be active.') + ipv4_count = 0 + ipv6_count = 0 + for address in login['radius_source_address']: + if is_ipv4(address): ipv4_count += 1 + else: ipv6_count += 1 + + if ipv4_count > 1: + raise ConfigError('Only one IPv4 source-address can be set!') + if ipv6_count > 1: + raise ConfigError('Only one IPv6 source-address can be set!') + vrf_name = login['radius_vrf'] if vrf_name and vrf_name not in interfaces(): raise ConfigError(f'VRF "{vrf_name}" does not exist') @@ -255,13 +267,8 @@ def generate(login): pass if len(login['radius_server']) > 0: - render(radius_config_file, 'system-login/pam_radius_auth.conf.tmpl', - login) - - uid = getpwnam('root').pw_uid - gid = getpwnam('root').pw_gid - os.chown(radius_config_file, uid, gid) - chmod_600(radius_config_file) + render(radius_config_file, 'login/pam_radius_auth.conf.tmpl', login, + permission=0o600, user='root', group='root') else: if os.path.isfile(radius_config_file): os.unlink(radius_config_file) @@ -284,16 +291,15 @@ def apply(login): # we need to use '' quotes when passing formatted data to the shell # else it will not work as some data parts are lost in translation if user['password_encrypted']: - command += " -p '{}'".format(user['password_encrypted']) + command += " -p '{password_encrypted}'".format(**user) if user['full_name']: - command += " -c '{}'".format(user['full_name']) + command += " -c '{full_name}'".format(**user) if user['home_dir']: - command += " -d '{}'".format(user['home_dir']) + command += " -d '{home_dir}'".format(**user) - command += " -G frrvty,vyattacfg,sudo,adm,dip,disk" - command += " {}".format(user['name']) + command += " -G frrvty,vyattacfg,sudo,adm,dip,disk {name}".format(**user) try: cmd(command) @@ -321,10 +327,9 @@ def apply(login): for id in user['public_keys']: line = '' if id['options']: - line = '{} '.format(id['options']) + line = '{options} '.format(**id) - line += '{} {} {}\n'.format(id['type'], - id['key'], id['name']) + line += '{type} {key} {name}\n'.format(**id) f.write(line) os.chown(ssh_key_file, uid, gid) @@ -339,8 +344,8 @@ def apply(login): try: # Logout user if he is logged in if user in list(set([tmp[0] for tmp in users()])): - print('{} is logged in, forcing logout'.format(user)) - call('pkill -HUP -u {}'.format(user)) + print(f'{user} is logged in, forcing logout') + call(f'pkill -HUP -u {user}') # Remove user account but leave home directory to be safe call(f'userdel -r {user}', stderr=DEVNULL) @@ -356,7 +361,7 @@ def apply(login): env = os.environ.copy() env['DEBIAN_FRONTEND'] = 'noninteractive' # Enable RADIUS in PAM - cmd("pam-auth-update --package --enable radius", env=env) + cmd('pam-auth-update --package --enable radius', env=env) # Make NSS system aware of RADIUS, too command = "sed -i -e \'/\smapname/b\' \ -- cgit v1.2.3 From 65ee3a66077c7708f366d9492033634024887545 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 21:30:00 +0100 Subject: ssh: T2635: change sshd_config path to /run/sshd --- data/templates/ssh/sshd_config.tmpl | 1 + smoketest/scripts/cli/test_service_ssh.py | 2 +- src/conf_mode/ssh.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'smoketest/scripts') diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl index 52d537aca..1dc700d38 100644 --- a/data/templates/ssh/sshd_config.tmpl +++ b/data/templates/ssh/sshd_config.tmpl @@ -27,6 +27,7 @@ Banner /etc/issue.net Subsystem sftp /usr/lib/openssh/sftp-server UsePAM yes PermitRootLogin no +PidFile /run/sshd/sshd.pid # # User configurable section diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 0bb907c3a..e9e4639fc 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -25,7 +25,7 @@ from vyos.util import process_named_running from vyos.util import read_file PROCESS_NAME = 'sshd' -SSHD_CONF = '/run/ssh/sshd_config' +SSHD_CONF = '/run/sshd/sshd_config' base_path = ['service', 'ssh'] vrf = 'ssh-test' diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 8f99053d2..07c057fd7 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -28,7 +28,7 @@ from vyos import ConfigError from vyos import airbag airbag.enable() -config_file = r'/run/ssh/sshd_config' +config_file = r'/run/sshd/sshd_config' systemd_override = r'/etc/systemd/system/ssh.service.d/override.conf' def get_config(config=None): -- cgit v1.2.3 From dcdc4f3ea27f1a26f8baa6b72b51c7911f21e6ba Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 23:22:58 +0100 Subject: ssh: T2635: harden Jinja2 template and daemon startup --- data/templates/ssh/sshd_config.tmpl | 30 +++++++++++++++--------------- smoketest/scripts/cli/test_service_ssh.py | 12 +++++------- src/conf_mode/ssh.py | 5 ++--- 3 files changed, 22 insertions(+), 25 deletions(-) (limited to 'smoketest/scripts') diff --git a/data/templates/ssh/sshd_config.tmpl b/data/templates/ssh/sshd_config.tmpl index 1dc700d38..7d7257cae 100644 --- a/data/templates/ssh/sshd_config.tmpl +++ b/data/templates/ssh/sshd_config.tmpl @@ -48,59 +48,59 @@ LogLevel {{ loglevel | upper }} # Specifies whether password authentication is allowed PasswordAuthentication {{ "no" if disable_password_authentication is defined else "yes" }} -{% if listen_address %} +{% if listen_address is defined and listen_address is not none %} # Specifies the local addresses sshd should listen on {% for address in listen_address %} ListenAddress {{ address }} {% endfor %} {% endif %} -{% if ciphers %} +{% if ciphers is defined and ciphers is not none %} # Specifies the ciphers allowed for protocol version 2 -{% set value = ciphers if ciphers is string else ciphers | join(',') %} +{% set value = ciphers if ciphers is string else ciphers | join(',') %} Ciphers {{ value }} {% endif %} -{% if mac %} +{% if mac is defined and mac is not none %} # Specifies the available MAC (message authentication code) algorithms -{% set value = mac if mac is string else mac | join(',') %} +{% set value = mac if mac is string else mac | join(',') %} MACs {{ value }} {% endif %} -{% if key_exchange %} +{% if key_exchange is defined and key_exchange is not none %} # Specifies the available Key Exchange algorithms -{% set value = key_exchange if key_exchange is string else key_exchange | join(',') %} +{% set value = key_exchange if key_exchange is string else key_exchange | join(',') %} KexAlgorithms {{ value }} {% endif %} -{% if access_control is defined %} -{% if access_control.allow is defined %} +{% if access_control is defined and access_control is not none %} +{% if access_control.allow is defined and access_control.allow is not none %} {% if access_control.allow.user is defined %} # If specified, login is allowed only for user names that match -{% set value = access_control.allow.user if access_control.allow.user is string else access_control.allow.user | join(' ') %} +{% set value = access_control.allow.user if access_control.allow.user is string else access_control.allow.user | join(' ') %} AllowUsers {{ value }} {% endif %} {% if access_control.allow.group is defined %} # If specified, login is allowed only for users whose primary group or supplementary group list matches -{% set value = access_control.allow.group if access_control.allow.group is string else access_control.allow.group | join(' ') %} +{% set value = access_control.allow.group if access_control.allow.group is string else access_control.allow.group | join(' ') %} AllowGroups {{ value }} {% endif %} {% endif %} -{% if access_control.deny is defined %} +{% if access_control.deny is defined and access_control.deny is not none %} {% if access_control.deny.user is defined %} # Login is disallowed for user names that match -{% set value = access_control.deny.user if access_control.deny.user is string else access_control.deny.user | join(' ') %} +{% set value = access_control.deny.user if access_control.deny.user is string else access_control.deny.user | join(' ') %} DenyUsers {{ value }} {% endif %} {% if access_control.deny.group is defined %} # Login is disallowed for users whose primary group or supplementary group list matches -{% set value = access_control.deny.group if access_control.deny.group is string else access_control.deny.group | join(' ') %} +{% set value = access_control.deny.group if access_control.deny.group is string else access_control.deny.group | join(' ') %} DenyGroups {{ value }} {% endif %} {% endif %} {% endif %} -{% if client_keepalive_interval %} +{% if client_keepalive_interval is defined and client_keepalive_interval is not none %} # Sets a timeout interval in seconds after which if no data has been received from the client, # sshd(8) will send a message through the encrypted channel to request a response from the client ClientAliveInterval {{ client_keepalive_interval }} diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index e9e4639fc..eede042de 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -44,11 +44,6 @@ class TestServiceSSH(unittest.TestCase): def tearDown(self): # delete testing SSH config self.session.delete(base_path) - # restore "plain" SSH access - self.session.set(base_path) - # delete VRF - self.session.delete(['vrf', 'name', vrf]) - self.session.commit() del self.session @@ -109,7 +104,7 @@ class TestServiceSSH(unittest.TestCase): def test_ssh_multiple_listen_addresses(self): # Check if SSH service can be configured and runs with multiple # listen ports and listen-addresses - ports = ['22', '2222'] + ports = ['22', '2222', '2223', '2224'] for port in ports: self.session.set(base_path + ['port', port]) @@ -143,7 +138,7 @@ class TestServiceSSH(unittest.TestCase): with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(['vrf', 'name', vrf, 'table', '1001']) + self.session.set(['vrf', 'name', vrf, 'table', '1338']) # commit changes self.session.commit() @@ -159,5 +154,8 @@ class TestServiceSSH(unittest.TestCase): tmp = cmd(f'ip vrf pids {vrf}') self.assertIn(PROCESS_NAME, tmp) + # delete VRF + self.session.delete(['vrf', 'name', vrf]) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/ssh.py b/src/conf_mode/ssh.py index 07c057fd7..28e606663 100755 --- a/src/conf_mode/ssh.py +++ b/src/conf_mode/ssh.py @@ -68,6 +68,8 @@ def generate(ssh): render(config_file, 'ssh/sshd_config.tmpl', ssh) render(systemd_override, 'ssh/override.conf.tmpl', ssh) + # Reload systemd manager configuration + call('systemctl daemon-reload') return None @@ -76,9 +78,6 @@ def apply(ssh): # SSH access is removed in the commit call('systemctl stop ssh.service') - # Reload systemd manager configuration - call('systemctl daemon-reload') - if ssh: call('systemctl restart ssh.service') -- cgit v1.2.3 From fde684497e9be82123cffefa3e4766689e4dabc6 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 21:41:12 +0100 Subject: smoketest: interfaces: fix dhcpv6 pd testcase when using multiple interfaces Commit a8e4317c ("smoketest: interfaces: test dhcpv6 pd sla-id auto increment") added a new test, but when executed on multiple interfaces, e.g.: TEST_ETH="eth1 eth2" /usr/libexec/vyos/tests/smoke/cli/test_interfaces_ethernet.py A variable was not properly reset --- smoketest/scripts/cli/base_interfaces_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index ad7886414..5efa39bd5 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -395,13 +395,13 @@ class BasicInterfaceTest: self.session.commit() address = '1' - sla_id = '0' for interface in self._interfaces: dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') # verify DHCPv6 prefix delegation self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + sla_id = '0' for delegatee in delegatees: self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) self.assertIn(f'ifid {address};', dhcpc6_config) @@ -446,7 +446,7 @@ class BasicInterfaceTest: section = Section.section(delegatee) self.session.set(['interfaces', section, delegatee]) self.session.set(pd_base + ['interface', delegatee, 'address', address]) - self.session.set(pd_base + ['interface', delegatee, 'sla-id', address]) + self.session.set(pd_base + ['interface', delegatee, 'sla-id', sla_id]) # increment interface address address = str(int(address) + 1) -- cgit v1.2.3 From 5cb935fc1d86c9a2ae61c7406425d0eed79dc87a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 23:01:29 +0100 Subject: smoketest: ethernet: check for error on non existing interface When performing a commit on an ethernet interface that does not exist, e.g. eth667, verify an exception is raised. --- smoketest/scripts/cli/test_interfaces_ethernet.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 3c4796283..971d965f5 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -123,6 +123,11 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}') + def test_non_existing_interface(self): + self.session.set(self._base_path + ['eth667']) + # check validate() - interface does not exist + with self.assertRaises(ConfigSessionError): + self.session.commit() def test_eapol_support(self): for interface in self._interfaces: -- cgit v1.2.3 From 27e1779dd431bb2144256fa4cab1466967ddf104 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 23:41:08 +0100 Subject: smoketest: ethernet: bugfixes for dhcpc6 and unknown interfaces --- smoketest/scripts/cli/base_interfaces_test.py | 13 +++++++------ smoketest/scripts/cli/test_interfaces_ethernet.py | 9 ++++++++- 2 files changed, 15 insertions(+), 7 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 5efa39bd5..5056eb190 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -366,7 +366,7 @@ class BasicInterfaceTest: self.assertEqual(dad_transmits, tmp) - def test_ipv6_dhcpv6_pd_auto_inc_sla_id(self): + def test_dhcpv6pd_auto_sla_id(self): if not self._test_ipv6: return None @@ -422,14 +422,12 @@ class BasicInterfaceTest: section = Section.section(delegatee) self.session.delete(['interfaces', section, delegatee]) - def test_ipv6_dhcpv6_pd_manual_sla_id(self): + def test_dhcpv6pd_manual_sla_id(self): if not self._test_ipv6: return None - address = '1' prefix_len = '56' sla_len = str(64 - int(prefix_len)) - sla_id = '1' delegatees = ['dum3340', 'dum3341', 'dum3342', 'dum3343', 'dum3344'] @@ -439,6 +437,8 @@ class BasicInterfaceTest: self.session.set(path + option.split()) # prefix delegation stuff + address = '1' + sla_id = '1' pd_base = path + ['dhcpv6-options', 'pd', '0'] self.session.set(pd_base + ['length', prefix_len]) @@ -454,9 +454,10 @@ class BasicInterfaceTest: self.session.commit() - address = '1' - sla_id = '1' + # Verify dhcpc6 client configuration for interface in self._interfaces: + address = '1' + sla_id = '1' dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') # verify DHCPv6 prefix delegation diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 971d965f5..0f3579c30 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -19,6 +19,7 @@ import re import unittest from base_interfaces_test import BasicInterfaceTest +from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.util import cmd from vyos.util import process_named_running @@ -124,11 +125,17 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}') def test_non_existing_interface(self): - self.session.set(self._base_path + ['eth667']) + unknonw_interface = self._base_path + ['eth667'] + self.session.set(unknonw_interface) + # check validate() - interface does not exist with self.assertRaises(ConfigSessionError): self.session.commit() + # we need to remove this wrong interface from the configuration + # manually, else tearDown() will have problem in commit() + self.session.delete(unknonw_interface) + def test_eapol_support(self): for interface in self._interfaces: # Enable EAPoL -- cgit v1.2.3 From 551ae04df7f6ce8a6cdbdf9712b4eea5d9e0c2f3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 23:41:10 +0100 Subject: smoketest: interfaces: report skipped tests --- smoketest/scripts/cli/base_interfaces_test.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 5056eb190..19be5a3a6 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -94,7 +94,7 @@ class BasicInterfaceTest: def test_span_mirror(self): if not self._mirror_interfaces: - return None + self.skipTest('testcase not enabled') # Check the two-way mirror rules of ingress and egress for mirror in self._mirror_interfaces: @@ -163,7 +163,7 @@ class BasicInterfaceTest: def test_ipv6_link_local_address(self): # Common function for IPv6 link-local address assignemnts if not self._test_ipv6: - return None + self.skipTest('testcase not enabled') for interface in self._interfaces: base = self._base_path + [interface] @@ -190,7 +190,7 @@ class BasicInterfaceTest: def test_interface_mtu(self): if not self._test_mtu: - return None + self.skipTest('testcase not enabled') for intf in self._interfaces: base = self._base_path + [intf] @@ -210,7 +210,7 @@ class BasicInterfaceTest: # Testcase if MTU can be changed to 1200 on non IPv6 # enabled interfaces if not self._test_mtu: - return None + self.skipTest('testcase not enabled') old_mtu = self._mtu self._mtu = '1200' @@ -235,7 +235,7 @@ class BasicInterfaceTest: def test_8021q_vlan_interfaces(self): if not self._test_vlan: - return None + self.skipTest('testcase not enabled') for interface in self._interfaces: base = self._base_path + [interface] @@ -262,7 +262,7 @@ class BasicInterfaceTest: def test_8021ad_qinq_vlan_interfaces(self): if not self._test_qinq: - return None + self.skipTest('testcase not enabled') for interface in self._interfaces: base = self._base_path + [interface] @@ -293,7 +293,7 @@ class BasicInterfaceTest: def test_interface_ip_options(self): if not self._test_ip: - return None + self.skipTest('testcase not enabled') for interface in self._interfaces: arp_tmo = '300' @@ -344,7 +344,7 @@ class BasicInterfaceTest: def test_interface_ipv6_options(self): if not self._test_ipv6: - return None + self.skipTest('testcase not enabled') for interface in self._interfaces: dad_transmits = '10' @@ -368,7 +368,7 @@ class BasicInterfaceTest: def test_dhcpv6pd_auto_sla_id(self): if not self._test_ipv6: - return None + self.skipTest('testcase not enabled') address = '1' prefix_len = '56' @@ -424,7 +424,7 @@ class BasicInterfaceTest: def test_dhcpv6pd_manual_sla_id(self): if not self._test_ipv6: - return None + self.skipTest('testcase not enabled') prefix_len = '56' sla_len = str(64 - int(prefix_len)) -- cgit v1.2.3 From 7a95aa238ba4d6743be645ffa3baef0e69251926 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 7 Jan 2021 23:41:33 +0100 Subject: smoketest: ethernet: verify() speed/duplex must both be auto or discrete --- smoketest/scripts/cli/test_interfaces_ethernet.py | 12 ++++++++++++ src/conf_mode/interfaces-ethernet.py | 11 ++++------- 2 files changed, 16 insertions(+), 7 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 0f3579c30..6b21853c3 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -136,6 +136,18 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): # manually, else tearDown() will have problem in commit() self.session.delete(unknonw_interface) + def test_speed_duplex_verify(self): + for interface in self._interfaces: + self.session.set(self._base_path + [interface, 'speed', '1000']) + + # check validate() - if either speed or duplex is not auto, the + # other one must be manually configured, too + with self.assertRaises(ConfigSessionError): + self.session.commit() + self.session.set(self._base_path + [interface, 'duplex', 'full']) + + self.session.commit() + def test_eapol_support(self): for interface in self._interfaces: # Enable EAPoL diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py index 0a904c592..e7f0cd6a5 100755 --- a/src/conf_mode/interfaces-ethernet.py +++ b/src/conf_mode/interfaces-ethernet.py @@ -62,13 +62,10 @@ def verify(ethernet): ifname = ethernet['ifname'] verify_interface_exists(ifname) - if ethernet.get('speed', None) == 'auto': - if ethernet.get('duplex', None) != 'auto': - raise ConfigError('If speed is hardcoded, duplex must be hardcoded, too') - - if ethernet.get('duplex', None) == 'auto': - if ethernet.get('speed', None) != 'auto': - raise ConfigError('If duplex is hardcoded, speed must be hardcoded, too') + # No need to check speed and duplex keys as both have default values. + if ((ethernet['speed'] == 'auto' and ethernet['duplex'] != 'auto') or + (ethernet['speed'] != 'auto' and ethernet['duplex'] == 'auto')): + raise ConfigError('Speed/Duplex missmatch. Must be both auto or manually configured') verify_mtu(ethernet) verify_mtu_ipv6(ethernet) -- cgit v1.2.3 From 7d5c1b7572bdc8a4819b04098b1b002cb855a461 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 8 Jan 2021 16:27:33 +0100 Subject: smoketest: ethernet: fix link-speed loop test --- smoketest/scripts/cli/test_interfaces_ethernet.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index 6b21853c3..6a0bdf150 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -144,9 +144,8 @@ class EthernetInterfaceTest(BasicInterfaceTest.BaseTest): # other one must be manually configured, too with self.assertRaises(ConfigSessionError): self.session.commit() - self.session.set(self._base_path + [interface, 'duplex', 'full']) - - self.session.commit() + self.session.set(self._base_path + [interface, 'speed', 'auto']) + self.session.commit() def test_eapol_support(self): for interface in self._interfaces: -- cgit v1.2.3 From b72e2a13e11d28a7b0345c2b496db46221e049f5 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 8 Jan 2021 16:27:54 +0100 Subject: smoketest: interfaces: dhcpv6pd final fix Previous fix somehow got lost during a rebase :( --- smoketest/scripts/cli/base_interfaces_test.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 19be5a3a6..8b04eb337 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -94,7 +94,7 @@ class BasicInterfaceTest: def test_span_mirror(self): if not self._mirror_interfaces: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') # Check the two-way mirror rules of ingress and egress for mirror in self._mirror_interfaces: @@ -163,7 +163,7 @@ class BasicInterfaceTest: def test_ipv6_link_local_address(self): # Common function for IPv6 link-local address assignemnts if not self._test_ipv6: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') for interface in self._interfaces: base = self._base_path + [interface] @@ -190,7 +190,7 @@ class BasicInterfaceTest: def test_interface_mtu(self): if not self._test_mtu: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') for intf in self._interfaces: base = self._base_path + [intf] @@ -210,7 +210,7 @@ class BasicInterfaceTest: # Testcase if MTU can be changed to 1200 on non IPv6 # enabled interfaces if not self._test_mtu: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') old_mtu = self._mtu self._mtu = '1200' @@ -235,7 +235,7 @@ class BasicInterfaceTest: def test_8021q_vlan_interfaces(self): if not self._test_vlan: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') for interface in self._interfaces: base = self._base_path + [interface] @@ -262,7 +262,7 @@ class BasicInterfaceTest: def test_8021ad_qinq_vlan_interfaces(self): if not self._test_qinq: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') for interface in self._interfaces: base = self._base_path + [interface] @@ -293,7 +293,7 @@ class BasicInterfaceTest: def test_interface_ip_options(self): if not self._test_ip: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') for interface in self._interfaces: arp_tmo = '300' @@ -344,7 +344,7 @@ class BasicInterfaceTest: def test_interface_ipv6_options(self): if not self._test_ipv6: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') for interface in self._interfaces: dad_transmits = '10' @@ -368,9 +368,8 @@ class BasicInterfaceTest: def test_dhcpv6pd_auto_sla_id(self): if not self._test_ipv6: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') - address = '1' prefix_len = '56' sla_len = str(64 - int(prefix_len)) @@ -381,6 +380,7 @@ class BasicInterfaceTest: for option in self._options.get(interface, []): self.session.set(path + option.split()) + address = '1' # prefix delegation stuff pd_base = path + ['dhcpv6-options', 'pd', '0'] self.session.set(pd_base + ['length', prefix_len]) @@ -394,13 +394,13 @@ class BasicInterfaceTest: self.session.commit() - address = '1' for interface in self._interfaces: dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') # verify DHCPv6 prefix delegation self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) + address = '1' sla_id = '0' for delegatee in delegatees: self.assertIn(f'prefix-interface {delegatee}' + r' {', dhcpc6_config) @@ -424,7 +424,7 @@ class BasicInterfaceTest: def test_dhcpv6pd_manual_sla_id(self): if not self._test_ipv6: - self.skipTest('testcase not enabled') + self.skipTest('not enabled') prefix_len = '56' sla_len = str(64 - int(prefix_len)) -- cgit v1.2.3 From b01fd3e053f20876f19dff6f0d2e58ed0e7394e3 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 8 Jan 2021 16:28:56 +0100 Subject: smoketest: bridge: bond: enable ip subsystem tests --- smoketest/scripts/cli/test_interfaces_bonding.py | 1 + smoketest/scripts/cli/test_interfaces_bridge.py | 1 + 2 files changed, 2 insertions(+) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index a35682b7c..d73ff09e9 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -26,6 +26,7 @@ from vyos.util import read_file class BondingInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): + self._test_ip = True self._test_mtu = True self._test_vlan = True self._test_qinq = True diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 7444701c1..d47d236d0 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -28,6 +28,7 @@ from vyos.util import read_file class BridgeInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): + self._test_ip = True self._test_ipv6 = True self._test_vlan = True self._test_qinq = True -- cgit v1.2.3 From 23f55c4bcbe5475ed98d57cf54b645ef0c2cc1a8 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 8 Jan 2021 18:15:06 +0100 Subject: smoketest: dummy: fix indent --- smoketest/scripts/cli/test_interfaces_dummy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'smoketest/scripts') diff --git a/smoketest/scripts/cli/test_interfaces_dummy.py b/smoketest/scripts/cli/test_interfaces_dummy.py index c482a6f0b..60465a1d5 100755 --- a/smoketest/scripts/cli/test_interfaces_dummy.py +++ b/smoketest/scripts/cli/test_interfaces_dummy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2021 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -20,9 +20,9 @@ from base_interfaces_test import BasicInterfaceTest class DummyInterfaceTest(BasicInterfaceTest.BaseTest): def setUp(self): - self._base_path = ['interfaces', 'dummy'] - self._interfaces = ['dum0', 'dum1', 'dum2'] - super().setUp() + self._base_path = ['interfaces', 'dummy'] + self._interfaces = ['dum435', 'dum8677', 'dum0931', 'dum089'] + super().setUp() if __name__ == '__main__': unittest.main(verbosity=2) -- cgit v1.2.3