diff options
Diffstat (limited to 'smoketest')
96 files changed, 5110 insertions, 1102 deletions
diff --git a/smoketest/bin/vyos-configtest b/smoketest/bin/vyos-configtest index 3e42b0380..c1b602737 100755 --- a/smoketest/bin/vyos-configtest +++ b/smoketest/bin/vyos-configtest @@ -24,6 +24,7 @@ from vyos.configsession import ConfigSession, ConfigSessionError from vyos import ConfigError config_dir = '/usr/libexec/vyos/tests/config' +config_test_dir = '/usr/libexec/vyos/tests/config-tests' save_config = '/tmp/vyos-configtest-save' class DynamicClassBase(unittest.TestCase): @@ -42,7 +43,7 @@ class DynamicClassBase(unittest.TestCase): except OSError: pass -def make_test_function(filename): +def make_test_function(filename, test_path=None): def test_config_load(self): config_path = os.path.join(config_dir, filename) self.session.migrate_and_load_config(config_path) @@ -51,6 +52,16 @@ def make_test_function(filename): except (ConfigError, ConfigSessionError): self.session.discard() self.fail() + + if test_path: + config_commands = self.session.show(['configuration', 'commands']) + + with open(test_path, 'r') as f: + for line in f.readlines(): + if not line or line.startswith("#"): + continue + + self.assertIn(line, config_commands) return test_config_load def class_name_from_func_name(s): @@ -69,10 +80,18 @@ if __name__ == '__main__': config_list.sort() for config in config_list: - test_func = make_test_function(config) + test_path = os.path.join(config_test_dir, config) + + if not os.path.exists(test_path): + test_path = None + else: + log.info(f'Loaded migration result test for config "{config}"') + + test_func = make_test_function(config, test_path) func_name = config.replace('-', '_') klassname = f'TestConfig{class_name_from_func_name(func_name)}' + globals()[klassname] = type(klassname, (DynamicClassBase,), {f'test_{func_name}': test_func}) diff --git a/smoketest/bin/vyos-smoketest b/smoketest/bin/vyos-smoketest index cb039db42..135388afe 100755 --- a/smoketest/bin/vyos-smoketest +++ b/smoketest/bin/vyos-smoketest @@ -26,7 +26,7 @@ for root, dirs, files in os.walk('/usr/libexec/vyos/tests/smoke'): test_file = os.path.join(root, name) mode = os.stat(test_file).st_mode - if mode & S_IXOTH: + if name.startswith("test_") and mode & S_IXOTH: print('Running Testcase: ' + test_file) process = Popen([test_file], stdout=PIPE) (output, err) = process.communicate() diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos new file mode 100644 index 000000000..ef8bf374a --- /dev/null +++ b/smoketest/config-tests/basic-vyos @@ -0,0 +1,62 @@ +set interfaces ethernet eth0 address '192.168.0.1/24' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 speed 'auto' +set interfaces ethernet eth2 duplex 'auto' +set interfaces ethernet eth2 speed 'auto' +set interfaces ethernet eth2 vif 100 address '100.100.0.1/24' +set interfaces ethernet eth2 vif-s 200 address '100.64.200.254/24' +set interfaces ethernet eth2 vif-s 200 vif-c 201 address '100.64.201.254/24' +set interfaces ethernet eth2 vif-s 200 vif-c 202 address '100.64.202.254/24' +set interfaces loopback lo +set protocols static arp interface eth0 address 192.168.0.20 mac '00:50:00:00:00:20' +set protocols static arp interface eth0 address 192.168.0.30 mac '00:50:00:00:00:30' +set protocols static arp interface eth0 address 192.168.0.40 mac '00:50:00:00:00:40' +set protocols static arp interface eth2.100 address 100.100.0.2 mac '00:50:00:00:02:02' +set protocols static arp interface eth2.100 address 100.100.0.3 mac '00:50:00:00:02:03' +set protocols static arp interface eth2.100 address 100.100.0.4 mac '00:50:00:00:02:04' +set protocols static arp interface eth2.200 address 100.64.200.1 mac '00:50:00:00:00:01' +set protocols static arp interface eth2.200 address 100.64.200.2 mac '00:50:00:00:00:02' +set protocols static arp interface eth2.200.201 address 100.64.201.10 mac '00:50:00:00:00:10' +set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50:00:00:00:20' +set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30' +set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40' +set protocols static route 0.0.0.0/0 next-hop 100.64.0.1 +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.20' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240' +set service dns forwarding allow-from '192.168.0.0/16' +set service dns forwarding cache-size '10000' +set service dns forwarding dnssec 'off' +set service dns forwarding listen-address '192.168.0.1' +set service ssh ciphers 'aes128-ctr' +set service ssh ciphers 'aes192-ctr' +set service ssh ciphers 'aes256-ctr' +set service ssh ciphers 'chacha20-poly1305@openssh.com' +set service ssh ciphers 'rijndael-cbc@lysator.liu.se' +set service ssh key-exchange 'curve25519-sha256@libssh.org' +set service ssh key-exchange 'diffie-hellman-group1-sha1' +set service ssh key-exchange 'diffie-hellman-group-exchange-sha1' +set service ssh key-exchange 'diffie-hellman-group-exchange-sha256' +set service ssh listen-address '192.168.0.1' +set service ssh port '22' +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system name-server '192.168.0.1' +set system syslog console facility all level 'emerg' +set system syslog console facility mail level 'info' +set system syslog global facility all level 'info' +set system syslog global facility auth level 'info' +set system syslog global facility local7 level 'debug' +set system syslog global preserve-fqdn +set system syslog host syslog.vyos.net facility auth level 'warning' +set system syslog host syslog.vyos.net facility local7 level 'notice' +set system syslog host syslog.vyos.net format octet-counted +set system syslog host syslog.vyos.net port '8000' +set system time-zone 'Europe/Berlin' diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn new file mode 100644 index 000000000..37baee0fd --- /dev/null +++ b/smoketest/config-tests/dialup-router-medium-vpn @@ -0,0 +1,321 @@ +set firewall global-options all-ping 'enable' +set firewall global-options broadcast-ping 'disable' +set firewall global-options ip-src-route 'disable' +set firewall global-options ipv6-receive-redirects 'disable' +set firewall global-options ipv6-src-route 'disable' +set firewall global-options log-martians 'enable' +set firewall global-options receive-redirects 'disable' +set firewall global-options send-redirects 'enable' +set firewall global-options source-validation 'disable' +set firewall global-options syn-cookies 'disable' +set firewall global-options twa-hazards-protection 'enable' +set firewall ipv4 name test_tcp_flags rule 1 action 'drop' +set firewall ipv4 name test_tcp_flags rule 1 protocol 'tcp' +set firewall ipv4 name test_tcp_flags rule 1 tcp flags ack +set firewall ipv4 name test_tcp_flags rule 1 tcp flags not fin +set firewall ipv4 name test_tcp_flags rule 1 tcp flags not rst +set firewall ipv4 name test_tcp_flags rule 1 tcp flags syn +set high-availability vrrp group LAN address 192.168.0.1/24 +set high-availability vrrp group LAN hello-source-address '192.168.0.250' +set high-availability vrrp group LAN interface 'eth1' +set high-availability vrrp group LAN peer-address '192.168.0.251' +set high-availability vrrp group LAN priority '200' +set high-availability vrrp group LAN vrid '1' +set high-availability vrrp sync-group failover-group member 'LAN' +set interfaces ethernet eth0 duplex 'auto' +set interfaces ethernet eth0 mtu '9000' +set interfaces ethernet eth0 offload gro +set interfaces ethernet eth0 offload gso +set interfaces ethernet eth0 offload sg +set interfaces ethernet eth0 offload tso +set interfaces ethernet eth0 speed 'auto' +set interfaces ethernet eth1 address '192.168.0.250/24' +set interfaces ethernet eth1 duplex 'auto' +set interfaces ethernet eth1 ip source-validation 'strict' +set interfaces ethernet eth1 mtu '9000' +set interfaces ethernet eth1 offload gro +set interfaces ethernet eth1 offload gso +set interfaces ethernet eth1 offload sg +set interfaces ethernet eth1 offload tso +set interfaces ethernet eth1 speed 'auto' +set interfaces loopback lo +set interfaces openvpn vtun0 encryption cipher 'aes256' +set interfaces openvpn vtun0 hash 'sha512' +set interfaces openvpn vtun0 ip adjust-mss '1380' +set interfaces openvpn vtun0 ip source-validation 'strict' +set interfaces openvpn vtun0 keep-alive failure-count '3' +set interfaces openvpn vtun0 keep-alive interval '30' +set interfaces openvpn vtun0 mode 'client' +set interfaces openvpn vtun0 openvpn-option 'comp-lzo adaptive' +set interfaces openvpn vtun0 openvpn-option 'fast-io' +set interfaces openvpn vtun0 openvpn-option 'persist-key' +set interfaces openvpn vtun0 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun0 persistent-tunnel +set interfaces openvpn vtun0 remote-host '192.0.2.10' +set interfaces openvpn vtun0 tls auth-key 'openvpn_vtun0_auth' +set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_1' +set interfaces openvpn vtun0 tls ca-certificate 'openvpn_vtun0_2' +set interfaces openvpn vtun0 tls certificate 'openvpn_vtun0' +set interfaces openvpn vtun1 authentication password 'vyos1' +set interfaces openvpn vtun1 authentication username 'vyos1' +set interfaces openvpn vtun1 encryption cipher 'aes256' +set interfaces openvpn vtun1 hash 'sha1' +set interfaces openvpn vtun1 ip adjust-mss '1380' +set interfaces openvpn vtun1 keep-alive failure-count '3' +set interfaces openvpn vtun1 keep-alive interval '30' +set interfaces openvpn vtun1 mode 'client' +set interfaces openvpn vtun1 openvpn-option 'comp-lzo adaptive' +set interfaces openvpn vtun1 openvpn-option 'tun-mtu 1500' +set interfaces openvpn vtun1 openvpn-option 'tun-mtu-extra 32' +set interfaces openvpn vtun1 openvpn-option 'mssfix 1300' +set interfaces openvpn vtun1 openvpn-option 'persist-key' +set interfaces openvpn vtun1 openvpn-option 'mute 10' +set interfaces openvpn vtun1 openvpn-option 'route-nopull' +set interfaces openvpn vtun1 openvpn-option 'fast-io' +set interfaces openvpn vtun1 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun1 persistent-tunnel +set interfaces openvpn vtun1 protocol 'udp' +set interfaces openvpn vtun1 remote-host '01.foo.com' +set interfaces openvpn vtun1 remote-port '1194' +set interfaces openvpn vtun1 tls auth-key 'openvpn_vtun1_auth' +set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_1' +set interfaces openvpn vtun1 tls ca-certificate 'openvpn_vtun1_2' +set interfaces openvpn vtun2 authentication password 'vyos2' +set interfaces openvpn vtun2 authentication username 'vyos2' +set interfaces openvpn vtun2 disable +set interfaces openvpn vtun2 encryption cipher 'aes256' +set interfaces openvpn vtun2 hash 'sha512' +set interfaces openvpn vtun2 ip adjust-mss '1380' +set interfaces openvpn vtun2 keep-alive failure-count '3' +set interfaces openvpn vtun2 keep-alive interval '30' +set interfaces openvpn vtun2 mode 'client' +set interfaces openvpn vtun2 openvpn-option 'tun-mtu 1500' +set interfaces openvpn vtun2 openvpn-option 'tun-mtu-extra 32' +set interfaces openvpn vtun2 openvpn-option 'mssfix 1300' +set interfaces openvpn vtun2 openvpn-option 'persist-key' +set interfaces openvpn vtun2 openvpn-option 'mute 10' +set interfaces openvpn vtun2 openvpn-option 'route-nopull' +set interfaces openvpn vtun2 openvpn-option 'fast-io' +set interfaces openvpn vtun2 openvpn-option 'remote-random' +set interfaces openvpn vtun2 openvpn-option 'reneg-sec 86400' +set interfaces openvpn vtun2 persistent-tunnel +set interfaces openvpn vtun2 protocol 'udp' +set interfaces openvpn vtun2 remote-host '01.myvpn.com' +set interfaces openvpn vtun2 remote-host '02.myvpn.com' +set interfaces openvpn vtun2 remote-host '03.myvpn.com' +set interfaces openvpn vtun2 remote-port '1194' +set interfaces openvpn vtun2 tls auth-key 'openvpn_vtun2_auth' +set interfaces openvpn vtun2 tls ca-certificate 'openvpn_vtun2_1' +set interfaces pppoe pppoe0 authentication password 'password' +set interfaces pppoe pppoe0 authentication username 'vyos' +set interfaces pppoe pppoe0 mtu '1500' +set interfaces pppoe pppoe0 source-interface 'eth0' +set interfaces wireguard wg0 address '192.168.10.1/24' +set interfaces wireguard wg0 ip adjust-mss '1380' +set interfaces wireguard wg0 peer blue allowed-ips '192.168.10.3/32' +set interfaces wireguard wg0 peer blue persistent-keepalive '20' +set interfaces wireguard wg0 peer blue preshared-key 'ztFDOY9UyaDvn8N3X97SFMDwIfv7EEfuUIPP2yab6UI=' +set interfaces wireguard wg0 peer blue public-key 'G4pZishpMRrLmd96Kr6V7LIuNGdcUb81gWaYZ+FWkG0=' +set interfaces wireguard wg0 peer green allowed-ips '192.168.10.21/32' +set interfaces wireguard wg0 peer green persistent-keepalive '25' +set interfaces wireguard wg0 peer green preshared-key 'LQ9qmlTh9G4nZu4UgElxRUwg7JB/qoV799aADJOijnY=' +set interfaces wireguard wg0 peer green public-key '5iQUD3VoCDBTPXAPHOwUJ0p7xzKGHEY/wQmgvBVmaFI=' +set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.14/32' +set interfaces wireguard wg0 peer pink allowed-ips '192.168.10.16/32' +set interfaces wireguard wg0 peer pink persistent-keepalive '25' +set interfaces wireguard wg0 peer pink preshared-key 'Qi9Odyx0/5itLPN5C5bEy3uMX+tmdl15QbakxpKlWqQ=' +set interfaces wireguard wg0 peer pink public-key 'i4qNPmxyy9EETL4tIoZOLKJF4p7IlVmpAE15gglnAk4=' +set interfaces wireguard wg0 peer red allowed-ips '192.168.10.4/32' +set interfaces wireguard wg0 peer red persistent-keepalive '20' +set interfaces wireguard wg0 peer red preshared-key 'CumyXX7osvUT9AwnS+m2TEfCaL0Ptc2LfuZ78Sujuk8=' +set interfaces wireguard wg0 peer red public-key 'ALGWvMJCKpHF2tVH3hEIHqUe9iFfAmZATUUok/WQzks=' +set interfaces wireguard wg0 port '7777' +set interfaces wireguard wg1 address '10.89.90.2/30' +set interfaces wireguard wg1 ip adjust-mss '1380' +set interfaces wireguard wg1 peer sam address '192.0.2.45' +set interfaces wireguard wg1 peer sam allowed-ips '10.1.1.0/24' +set interfaces wireguard wg1 peer sam allowed-ips '10.89.90.1/32' +set interfaces wireguard wg1 peer sam persistent-keepalive '20' +set interfaces wireguard wg1 peer sam port '1200' +set interfaces wireguard wg1 peer sam preshared-key 'XpFtzx2Z+nR8pBv9/sSf7I94OkZkVYTz0AeU5Q/QQUE=' +set interfaces wireguard wg1 peer sam public-key 'v5zfKGvH6W/lfDXJ0en96lvKo1gfFxMUWxe02+Fj5BU=' +set interfaces wireguard wg1 port '7778' +set nat destination rule 50 destination port '49371' +set nat destination rule 50 inbound-interface 'pppoe0' +set nat destination rule 50 protocol 'tcp_udp' +set nat destination rule 50 translation address '192.168.0.5' +set nat destination rule 51 destination port '58050-58051' +set nat destination rule 51 inbound-interface 'pppoe0' +set nat destination rule 51 protocol 'tcp' +set nat destination rule 51 translation address '192.168.0.5' +set nat destination rule 52 destination port '22067-22070' +set nat destination rule 52 inbound-interface 'pppoe0' +set nat destination rule 52 protocol 'tcp' +set nat destination rule 52 translation address '192.168.0.5' +set nat destination rule 53 destination port '34342' +set nat destination rule 53 inbound-interface 'pppoe0' +set nat destination rule 53 protocol 'tcp_udp' +set nat destination rule 53 translation address '192.168.0.121' +set nat destination rule 54 destination port '45459' +set nat destination rule 54 inbound-interface 'pppoe0' +set nat destination rule 54 protocol 'tcp_udp' +set nat destination rule 54 translation address '192.168.0.120' +set nat destination rule 55 destination port '22' +set nat destination rule 55 inbound-interface 'pppoe0' +set nat destination rule 55 protocol 'tcp' +set nat destination rule 55 translation address '192.168.0.5' +set nat destination rule 56 destination port '8920' +set nat destination rule 56 inbound-interface 'pppoe0' +set nat destination rule 56 protocol 'tcp' +set nat destination rule 56 translation address '192.168.0.5' +set nat destination rule 60 destination port '80,443' +set nat destination rule 60 inbound-interface 'pppoe0' +set nat destination rule 60 protocol 'tcp' +set nat destination rule 60 translation address '192.168.0.5' +set nat destination rule 70 destination port '5001' +set nat destination rule 70 inbound-interface 'pppoe0' +set nat destination rule 70 protocol 'tcp' +set nat destination rule 70 translation address '192.168.0.5' +set nat destination rule 80 destination port '25' +set nat destination rule 80 inbound-interface 'pppoe0' +set nat destination rule 80 protocol 'tcp' +set nat destination rule 80 translation address '192.168.0.5' +set nat destination rule 90 destination port '8123' +set nat destination rule 90 inbound-interface 'pppoe0' +set nat destination rule 90 protocol 'tcp' +set nat destination rule 90 translation address '192.168.0.7' +set nat destination rule 91 destination port '1880' +set nat destination rule 91 inbound-interface 'pppoe0' +set nat destination rule 91 protocol 'tcp' +set nat destination rule 91 translation address '192.168.0.7' +set nat destination rule 500 destination address '!192.168.0.0/24' +set nat destination rule 500 destination port '53' +set nat destination rule 500 inbound-interface 'eth1' +set nat destination rule 500 protocol 'tcp_udp' +set nat destination rule 500 source address '!192.168.0.1-192.168.0.5' +set nat destination rule 500 translation address '192.168.0.1' +set nat source rule 1000 outbound-interface 'pppoe0' +set nat source rule 1000 translation address 'masquerade' +set nat source rule 2000 outbound-interface 'vtun0' +set nat source rule 2000 source address '192.168.0.0/16' +set nat source rule 2000 translation address 'masquerade' +set nat source rule 3000 outbound-interface 'vtun1' +set nat source rule 3000 translation address 'masquerade' +set policy prefix-list user1-routes rule 1 action 'permit' +set policy prefix-list user1-routes rule 1 prefix '192.168.0.0/24' +set policy prefix-list user2-routes rule 1 action 'permit' +set policy prefix-list user2-routes rule 1 prefix '10.1.1.0/24' +set policy route LAN-POLICY-BASED-ROUTING interface 'eth1' +set policy route LAN-POLICY-BASED-ROUTING rule 10 destination +set policy route LAN-POLICY-BASED-ROUTING rule 10 disable +set policy route LAN-POLICY-BASED-ROUTING rule 10 set table '10' +set policy route LAN-POLICY-BASED-ROUTING rule 10 source address '192.168.0.119/32' +set policy route LAN-POLICY-BASED-ROUTING rule 20 destination +set policy route LAN-POLICY-BASED-ROUTING rule 20 set table '100' +set policy route LAN-POLICY-BASED-ROUTING rule 20 source address '192.168.0.240' +set policy route-map rm-static-to-bgp rule 10 action 'permit' +set policy route-map rm-static-to-bgp rule 10 match ip address prefix-list 'user1-routes' +set policy route-map rm-static-to-bgp rule 100 action 'deny' +set policy route6 LAN6-POLICY-BASED-ROUTING interface 'eth1' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 destination +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 disable +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 set table '10' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 10 source address '2002::1' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 destination +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 set table '100' +set policy route6 LAN6-POLICY-BASED-ROUTING rule 20 source address '2008::f' +set protocols bgp address-family ipv4-unicast redistribute connected route-map 'rm-static-to-bgp' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast nexthop-self +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list export 'user1-routes' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast prefix-list import 'user2-routes' +set protocols bgp neighbor 10.89.90.1 address-family ipv4-unicast soft-reconfiguration inbound +set protocols bgp neighbor 10.89.90.1 password 'ericandre2020' +set protocols bgp neighbor 10.89.90.1 remote-as '64589' +set protocols bgp parameters log-neighbor-changes +set protocols bgp parameters router-id '10.89.90.2' +set protocols bgp system-as '64590' +set protocols static route 100.64.160.23/32 interface pppoe0 +set protocols static route 100.64.165.25/32 interface pppoe0 +set protocols static route 100.64.165.26/32 interface pppoe0 +set protocols static route 100.64.198.0/24 interface vtun0 +set protocols static table 10 route 0.0.0.0/0 interface vtun1 +set protocols static table 100 route 0.0.0.0/0 next-hop 192.168.10.5 +set service conntrack-sync accept-protocol 'tcp' +set service conntrack-sync accept-protocol 'udp' +set service conntrack-sync accept-protocol 'icmp' +set service conntrack-sync disable-external-cache +set service conntrack-sync event-listen-queue-size '8' +set service conntrack-sync expect-sync 'all' +set service conntrack-sync failover-mechanism vrrp sync-group 'failover-group' +set service conntrack-sync interface eth1 peer '192.168.0.251' +set service conntrack-sync sync-queue-size '8' +set service dhcp-server failover name 'DHCP02' +set service dhcp-server failover remote '192.168.0.251' +set service dhcp-server failover source-address '192.168.0.250' +set service dhcp-server failover status 'primary' +set service dhcp-server shared-network-name LAN authoritative +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 enable-failover +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 lease '86400' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.200' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio ip-address '192.168.0.107' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio mac-address '00:50:01:dc:91:14' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV ip-address '192.168.0.104' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV mac-address '00:50:01:31:b5:f6' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus ip-address '192.168.0.60' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus mac-address '00:50:01:58:ac:95' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 ip-address '192.168.0.109' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 mac-address '00:50:01:bc:ac:51' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 ip-address '192.168.0.11' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 mac-address '00:50:01:70:b9:4d' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 ip-address '192.168.0.12' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 mac-address '00:50:01:70:b7:4f' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 static-mapping-parameters 'option domain-name-servers 192.168.0.6,192.168.0.17;' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV ip-address '192.168.0.101' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV mac-address '00:50:01:ba:62:79' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand ip-address '192.168.0.110' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand mac-address '00:50:01:af:c5:d2' +set service dns forwarding allow-from '192.168.0.0/16' +set service dns forwarding cache-size '8192' +set service dns forwarding dnssec 'off' +set service dns forwarding listen-address '192.168.0.1' +set service dns forwarding name-server 100.64.0.1 +set service dns forwarding name-server 100.64.0.2 +set service ntp allow-client address '192.168.0.0/16' +set service ntp server nz.pool.ntp.org prefer +set service snmp community AwesomeCommunity authorization 'ro' +set service snmp community AwesomeCommunity client '127.0.0.1' +set service snmp community AwesomeCommunity network '192.168.0.0/24' +set service ssh access-control allow user 'vyos' +set service ssh client-keepalive-interval '60' +set service ssh listen-address '192.168.0.1' +set service ssh listen-address '192.168.10.1' +set service ssh listen-address '192.168.0.250' +set system config-management commit-revisions '100' +set system console device ttyS0 speed '115200' +set system host-name 'vyos' +set system ip arp table-size '1024' +set system name-server '192.168.0.1' +set system name-server 'pppoe0' +set system option ctrl-alt-delete 'ignore' +set system option reboot-on-panic +set system option startup-beep +set system static-host-mapping host-name host60.vyos.net inet '192.168.0.60' +set system static-host-mapping host-name host104.vyos.net inet '192.168.0.104' +set system static-host-mapping host-name host107.vyos.net inet '192.168.0.107' +set system static-host-mapping host-name host109.vyos.net inet '192.168.0.109' +set system sysctl parameter net.core.default_qdisc value 'fq' +set system sysctl parameter net.ipv4.tcp_congestion_control value 'bbr' +set system syslog global facility all level 'info' +set system syslog host 192.168.0.252 facility all level 'debug' +set system syslog host 192.168.0.252 protocol 'udp' +set system task-scheduler task Update-Blacklists executable path '/config/scripts/vyos-foo-update.script' +set system task-scheduler task Update-Blacklists interval '3h' +set system time-zone 'Pacific/Auckland' diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index 23186b9b8..78dba3ee2 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -116,6 +116,18 @@ system { speed 115200 } } + conntrack { + ignore { + rule 1 { + destination { + address 192.0.2.2 + } + source { + address 192.0.2.1 + } + } + } + } host-name vyos login { user vyos { @@ -127,14 +139,40 @@ system { } name-server 192.168.0.1 syslog { - global { - archive { - file 5 - size 512 + console { + facility all { + level emerg } + facility mail { + level info + } + } + global { facility all { level info } + facility protocols { + level debug + } + facility security { + level info + } + preserve-fqdn + } + host syslog.vyos.net { + facility local7 { + level notice + } + facility protocols { + level alert + } + facility security { + level warning + } + format { + octet-counted + } + port 8000 } } time-zone Europe/Berlin diff --git a/smoketest/configs/dialup-router-complex b/smoketest/configs/dialup-router-complex index 909e6d17b..aa9837fe9 100644 --- a/smoketest/configs/dialup-router-complex +++ b/smoketest/configs/dialup-router-complex @@ -1094,6 +1094,10 @@ firewall { adjust-mss 1452 adjust-mss6 1432 } + interface eth0.10 { + adjust-mss 1320 + adjust-mss6 1300 + } } receive-redirects disable send-redirects enable diff --git a/smoketest/configs/dialup-router-medium-vpn b/smoketest/configs/dialup-router-medium-vpn index 56722d222..503280017 100644 --- a/smoketest/configs/dialup-router-medium-vpn +++ b/smoketest/configs/dialup-router-medium-vpn @@ -68,9 +68,6 @@ interfaces { mtu 1500 name-server auto password password - traffic-policy { - out shape-17mbit - } user-id vyos password vyos } @@ -96,9 +93,6 @@ interfaces { } smp-affinity auto speed auto - traffic-policy { - out shape-94mbit - } } loopback lo { } @@ -719,24 +713,6 @@ system { } time-zone Pacific/Auckland } -traffic-policy { - shaper shape-17mbit { - bandwidth 17mbit - default { - bandwidth 100% - burst 15k - queue-type fq-codel - } - } - shaper shape-94mbit { - bandwidth 94mbit - default { - bandwidth 100% - burst 15k - queue-type fq-codel - } - } -} /* Warning: Do not remove the following line. */ /* === vyatta-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack-sync@1:conntrack@1:dhcp-relay@2:dhcp-server@5:dns-forwarding@1:firewall@5:ipsec@5:l2tp@1:mdns@1:nat@4:ntp@1:pptp@1:qos@1:quagga@6:snmp@1:ssh@1:system@9:vrrp@2:wanloadbalance@3:webgui@1:webproxy@1:webproxy@2:zone-policy@1" === */ /* Release version: 1.2.6 */ diff --git a/smoketest/configs/egb-igp-route-maps b/smoketest/configs/egb-igp-route-maps new file mode 100644 index 000000000..ca36691d4 --- /dev/null +++ b/smoketest/configs/egb-igp-route-maps @@ -0,0 +1,127 @@ +interfaces { + ethernet eth0 { + address 192.0.2.1/25 + duplex auto + smp-affinity auto + speed auto + } + ethernet eth1 { + address 192.0.2.129/25 + address 2001:db8::1234/64 + duplex auto + smp-affinity auto + speed auto + } + loopback lo { + } +} +policy { + route-map zebra-bgp { + rule 10 { + action permit + } + } + route-map zebra-isis { + rule 10 { + action permit + } + } + route-map zebra-ospf { + rule 10 { + action permit + } + } + route-map zebra-ospfv3 { + rule 10 { + action permit + } + } + route-map zebra-ripng { + rule 10 { + action permit + } + } + route-map zebra-static { + rule 10 { + action permit + } + } +} +protocols { + bgp 100 { + route-map zebra-bgp + } + isis { + interface eth0 { + } + net 49.0001.1921.6800.1002.00 + route-map zebra-isis + } + ospf { + area 0 { + network 192.0.2.0/25 + network 192.0.2.128/25 + } + log-adjacency-changes { + } + parameters { + abr-type cisco + router-id 1.1.1.1 + } + passive-interface default + passive-interface-exclude eth0 + passive-interface-exclude eth1 + route-map zebra-ospf + } + ospfv3 { + area 0 { + interface eth1 + } + parameters { + router-id 1.1.1.1 + } + route-map zebra-ospfv3 + } + ripng { + interface eth1 + route-map zebra-ripng + } + static { + route-map zebra-static + } +} +system { + config-management { + commit-revisions 100 + } + console { + device ttyS0 { + speed 115200 + } + } + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + syslog { + global { + archive { + file 5 + size 512 + } + facility all { + level info + } + } + } + time-zone Europe/Berlin +} +// Warning: Do not remove the following line. +// vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" +// Release version: 1.3.2 diff --git a/smoketest/configs/qos-basic b/smoketest/configs/qos-basic index f94a5650d..65a888d38 100644 --- a/smoketest/configs/qos-basic +++ b/smoketest/configs/qos-basic @@ -8,7 +8,7 @@ interfaces { ethernet eth1 { address 10.2.1.1/24 traffic-policy { - out M2 + out ISPC } } ethernet eth2 { @@ -16,19 +16,14 @@ interfaces { traffic-policy { out MY-HTB } - } - loopback lo { - } -} -protocols { - static { - route 0.0.0.0/0 { - next-hop 10.9.9.2 { - } - next-hop 10.1.1.1 { + vif 200 { + traffic-policy { + out foo-emulate } } } + loopback lo { + } } system { config-management { @@ -79,24 +74,15 @@ system { } } traffic-policy { - shaper M2 { - bandwidth auto - class 10 { - bandwidth 100% - burst 15k - match ADDRESS10 { - ip { - dscp CS4 - } - } - queue-type fair-queue - set-dscp CS5 - } + shaper ISPC { + bandwidth 600Mbit default { - bandwidth 10mbit - burst 15k - queue-type fair-queue + bandwidth 50% + burst 768k + ceiling 100% + queue-type fq-codel } + description "Outbound Traffic Shaper - ISPC" } shaper MY-HTB { bandwidth 10mbit @@ -120,7 +106,6 @@ traffic-policy { ceiling 100% match ADDRESS40 { ip { - dscp CS4 source { address 10.2.1.0/24 } @@ -133,12 +118,13 @@ traffic-policy { bandwidth 100% burst 15k match ADDRESS50 { - ip { - dscp CS5 + ipv6 { + source { + address "2001:db8::1/64" + } } } queue-type fair-queue - set-dscp CS7 } default { bandwidth 10% @@ -146,7 +132,6 @@ traffic-policy { ceiling 100% priority 7 queue-type fair-queue - set-dscp CS1 } } shaper FS { @@ -162,7 +147,6 @@ traffic-policy { } } queue-type fair-queue - set-dscp CS4 } class 20 { bandwidth 100% @@ -175,7 +159,6 @@ traffic-policy { } } queue-type fair-queue - set-dscp CS5 } class 30 { bandwidth 100% @@ -188,7 +171,6 @@ traffic-policy { } } queue-type fair-queue - set-dscp CS6 } default { bandwidth 10% @@ -198,6 +180,10 @@ traffic-policy { queue-type fair-queue } } + network-emulator foo-emulate { + bandwidth 300mbit + burst 20000 + } } // Warning: Do not remove the following line. // vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@22:ipoe-server@1:ipsec@5:isis@1:l2tp@3:lldp@1:mdns@1:nat@5:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@21:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" diff --git a/smoketest/configs/vrf-bgp-pppoe-underlay b/smoketest/configs/vrf-bgp-pppoe-underlay new file mode 100644 index 000000000..cba35eab1 --- /dev/null +++ b/smoketest/configs/vrf-bgp-pppoe-underlay @@ -0,0 +1,473 @@ +interfaces { + bridge br50 { + address 192.168.0.1/24 + member { + interface eth0.50 { + } + interface eth2 { + } + interface eth3 { + } + } + } + dummy dum0 { + address 100.64.51.252/32 + address 2001:db8:200:ffff::1/128 + vrf vyos-test-01 + } + ethernet eth0 { + offload { + gro + gso + rps + sg + tso + } + ring-buffer { + rx 256 + tx 256 + } + vif 5 { + address 2001:db8:200:f0::114/64 + address 100.64.50.121/28 + vrf vyos-test-01 + } + vif 10 { + address 2001:db8:200:10::ffff/64 + address 2001:db8:200::ffff/64 + address 100.64.50.62/26 + vrf vyos-test-01 + } + vif 15 { + address 100.64.50.78/28 + address 2001:db8:200:15::ffff/64 + vrf vyos-test-01 + } + vif 50 { + description "Member of bridge br50" + } + vif 110 { + address 100.64.51.190/27 + address 100.64.51.158/28 + address 2001:db8:200:101::ffff/64 + vrf vyos-test-01 + } + vif 410 { + address 100.64.51.206/28 + address 2001:db8:200:104::ffff/64 + vrf vyos-test-01 + } + vif 500 { + address 100.64.51.238/28 + address 2001:db8:200:50::ffff/64 + vrf vyos-test-01 + } + vif 520 { + address 100.64.50.190/28 + address 2001:db8:200:520::ffff/64 + vrf vyos-test-01 + } + vif 666 { + address 2001:db8:200:ff::101:1/112 + address 100.64.51.223/31 + vrf vyos-test-01 + } + vif 800 { + address 2001:db8:200:ff::104:1/112 + address 100.64.51.212/31 + vrf vyos-test-01 + } + vif 810 { + address 100.64.51.30/27 + address 2001:db8:200:102::ffff/64 + vrf vyos-test-01 + } + } + ethernet eth1 { + offload { + gro + gso + rps + sg + tso + } + ring-buffer { + rx 256 + tx 256 + } + } + ethernet eth2 { + offload { + gro + gso + sg + tso + } + } + ethernet eth3 { + offload { + gro + gso + sg + tso + } + } + loopback lo { + } + pppoe pppoe7 { + authentication { + password vyos + username vyos + } + dhcpv6-options { + pd 0 { + interface br50 { + address 1 + } + length 56 + } + } + ip { + adjust-mss 1452 + } + ipv6 { + address { + autoconf + } + adjust-mss 1432 + } + mtu 1492 + no-peer-dns + source-interface eth1 + } + virtual-ethernet veth0 { + address 100.64.51.220/31 + address 2001:db8:200:ff::105:1/112 + description "Core: connect vyos-test-01 and default VRF" + peer-name veth1 + } + virtual-ethernet veth1 { + address 100.64.51.221/31 + address 2001:db8:200:ff::105:2/112 + description "Core: connect vyos-test-01 and default VRF" + peer-name veth0 + vrf vyos-test-01 + } + wireguard wg500 { + address 100.64.51.209/31 + mtu 1500 + peer A { + address 192.0.2.1 + allowed-ips 0.0.0.0/0 + port 5500 + public-key KGSXF4QckzGe7f7CT+r6VZ5brOD/pVYk8yvrxOQ+X0Y= + } + port 5500 + private-key iLJh6Me6AdPJtNv3dgGhUbtyFxExxmNU4v0Fs6YE2Xc= + vrf vyos-test-01 + } + wireguard wg501 { + address 2001:db8:200:ff::102:2/112 + mtu 1500 + peer A { + address 2001:db8:300::1 + allowed-ips ::/0 + port 5501 + public-key OF+1OJ+VfQ0Yw1mgVtQ2ion4CnAdy8Bvx7yEiO4+Pn8= + } + port 5501 + private-key 0MP5X0PW58O4q2LDpuIXgZ0ySyAoWH8/kdpvQccCbUU= + vrf vyos-test-01 + } + wireguard wg666 { + address 172.29.0.0/31 + mtu 1500 + peer B { + allowed-ips 0.0.0.0/0 + public-key 2HT+RfwcqJMYNYzdmtmpem8Ht0dL37o31APHVwmh024= + } + port 50666 + private-key zvPnp2MLAoX7SotuHLFLDyy4sdlD7ttbD1xNEqA3mkU= + } +} +nat { + source { + rule 100 { + outbound-interface pppoe7 + source { + address 192.168.0.0/24 + } + translation { + address masquerade + } + } + } +} +policy { + prefix-list AS100-origin-v4 { + rule 10 { + action permit + prefix 100.64.0.0/12 + } + rule 100 { + action permit + prefix 0.0.0.0/0 + } + } + prefix-list AS200-origin-v4 { + rule 10 { + action permit + prefix 10.0.0.0/8 + } + rule 20 { + action permit + prefix 172.16.0.0/12 + } + + } + prefix-list6 AS100-origin-v6 { + rule 10 { + action permit + prefix 2001:db8:200::/40 + } + } + prefix-list6 AS200-origin-v6 { + rule 10 { + action permit + prefix 2001:db8:100::/40 + } + } +} +protocols { + static { + route 192.0.2.255/32 { + interface pppoe7 { + } + } + route 100.64.50.0/23 { + next-hop 100.64.51.221 { + } + } + route6 2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/128 { + interface pppoe7 { + } + } + } +} +qos { + interface pppoe7 { + egress isp-out + } + policy { + shaper isp-out { + bandwidth 38mbit + default { + bandwidth 100% + burst 15k + queue-limit 1000 + queue-type fq-codel + } + } + } +} +service { + router-advert { + interface br50 { + prefix ::/64 { + preferred-lifetime 2700 + valid-lifetime 5400 + } + } + interface eth0.500 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:50::/64 { + valid-lifetime infinity + } + } + interface eth0.520 { + default-preference high + name-server 2001:db8:200::1 + name-server 2001:db8:200::2 + prefix 2001:db8:200:520::/64 { + valid-lifetime infinity + } + } + } + ssh { + disable-host-validation + dynamic-protection { + allow-from 100.64.0.0/10 + allow-from 2001:db8:200::/40 + } + } +} +system { + config-management { + commit-revisions 100 + } + conntrack { + modules { + ftp + h323 + nfs + pptp + sip + sqlnet + tftp + } + } + console { + device ttyS0 { + speed 115200 + } + } + domain-name vyos.net + host-name vyos + login { + user vyos { + authentication { + encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 + plaintext-password "" + } + } + } + name-server 192.168.0.1 + syslog { + global { + facility all { + level info + } + facility protocols { + level debug + } + } + } + time-zone Europe/Berlin +} +vrf { + bind-to-all + name vyos-test-01 { + protocols { + bgp { + address-family { + ipv4-unicast { + network 100.64.50.0/23 { + } + } + ipv6-unicast { + network 2001:db8:200:ffff::1/128 { + } + } + } + neighbor 100.64.51.208 { + peer-group AS100v4 + } + neighbor 100.64.51.222 { + address-family { + ipv4-unicast { + default-originate { + } + maximum-prefix 10 + prefix-list { + export AS100-origin-v4 + import AS200-origin-v4 + } + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + remote-as 200 + } + neighbor 100.64.51.251 { + peer-group AS100v4 + shutdown + } + neighbor 100.64.51.254 { + peer-group AS100v4 + shutdown + } + neighbor 2001:db8:200:ffff::2 { + peer-group AS100v6 + shutdown + } + neighbor 2001:db8:200:ffff::a { + peer-group AS100v6 + } + neighbor 2001:db8:200:ff::101:2 { + address-family { + ipv6-unicast { + maximum-prefix 10 + prefix-list { + export AS100-origin-v6 + import AS200-origin-v6 + } + soft-reconfiguration { + inbound + } + } + } + capability { + dynamic + } + remote-as 200 + } + peer-group AS100v4 { + address-family { + ipv4-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as internal + update-source dum0 + } + peer-group AS100v6 { + address-family { + ipv6-unicast { + nexthop-self { + } + } + } + capability { + dynamic + } + remote-as internal + update-source dum0 + } + system-as 100 + } + static { + route 192.168.0.0/24 { + next-hop 100.64.51.220 { + } + } + route 100.64.50.0/23 { + blackhole { + } + } + route 100.64.51.32/27 { + next-hop 100.64.51.5 { + } + } + route6 2001:db8:2fe:ffff::/64 { + next-hop 2001:db8:200:102::5 { + } + } + } + } + table 1000 + } +} + +// Warning: Do not remove the following line. +// vyos-config-version: "bgp@3:broadcast-relay@1:cluster@1:config-management@1:conntrack@3:conntrack-sync@2:container@1:dhcp-relay@2:dhcp-server@6:dhcpv6-server@1:dns-forwarding@3:firewall@9:flow-accounting@1:https@4:ids@1:interfaces@28:ipoe-server@1:ipsec@12:isis@2:l2tp@4:lldp@1:mdns@1:monitoring@1:nat@5:nat66@1:ntp@2:openconnect@2:ospf@1:policy@5:pppoe-server@6:pptp@2:qos@2:quagga@10:rpki@1:salt@1:snmp@3:ssh@2:sstp@4:system@25:vrf@3:vrrp@3:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2" +// Release version: 1.4-rolling-202303160317 diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py index 471bdaffb..989028f64 100644 --- a/smoketest/scripts/cli/base_accel_ppp_test.py +++ b/smoketest/scripts/cli/base_accel_ppp_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -21,9 +21,9 @@ from configparser import ConfigParser from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.template import is_ipv4 -from vyos.util import cmd -from vyos.util import get_half_cpus -from vyos.util import process_named_running +from vyos.utils.system import get_half_cpus +from vyos.utils.process import process_named_running +from vyos.utils.process import cmd class BasicAccelPPPTest: class TestCase(VyOSUnitTestSHIM.TestCase): diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 55343b893..51ccbc9e6 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -1,4 +1,4 @@ -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -26,15 +26,23 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError +from vyos.defaults import directories from vyos.ifconfig import Interface from vyos.ifconfig import Section -from vyos.util import read_file -from vyos.util import cmd -from vyos.util import dict_search -from vyos.util import process_named_running -from vyos.util import get_interface_config -from vyos.validate import is_intf_addr_assigned -from vyos.validate import is_ipv6_link_local +from vyos.utils.file import read_file +from vyos.utils.dict import dict_search +from vyos.utils.process import process_named_running +from vyos.utils.network import get_interface_config +from vyos.utils.network import get_interface_vrf +from vyos.utils.process import cmd +from vyos.utils.network import is_intf_addr_assigned +from vyos.utils.network import is_ipv6_link_local +from vyos.xml_ref import cli_defined + +dhclient_base_dir = directories['isc_dhclient_dir'] +dhclient_process_name = 'dhclient' +dhcp6c_base_dir = directories['dhcp6_client_dir'] +dhcp6c_process_name = 'dhcp6c' def is_mirrored_to(interface, mirror_if, qdisc): """ @@ -65,6 +73,7 @@ class BasicInterfaceTest: _test_ipv6_pd = False _test_ipv6_dhcpc6 = False _test_mirror = False + _test_vrf = False _base_path = [] _options = {} @@ -82,6 +91,18 @@ class BasicInterfaceTest: def setUpClass(cls): super(BasicInterfaceTest.TestCase, cls).setUpClass() + # XXX the case of test_vif_8021q_mtu_limits, below, shows that + # we should extend cli_defined to support more complex queries + cls._test_vlan = cli_defined(cls._base_path, 'vif') + cls._test_qinq = cli_defined(cls._base_path, 'vif-s') + cls._test_dhcp = cli_defined(cls._base_path, 'dhcp-options') + cls._test_ip = cli_defined(cls._base_path, 'ip') + cls._test_ipv6 = cli_defined(cls._base_path, 'ipv6') + cls._test_ipv6_dhcpc6 = cli_defined(cls._base_path, 'dhcpv6-options') + cls._test_ipv6_pd = cli_defined(cls._base_path + ['dhcpv6-options'], 'pd') + cls._test_mtu = cli_defined(cls._base_path, 'mtu') + cls._test_vrf = cli_defined(cls._base_path, 'vrf') + # Setup mirror interfaces for SPAN (Switch Port Analyzer) for span in cls._mirror_interfaces: section = Section.section(span) @@ -104,9 +125,15 @@ class BasicInterfaceTest: for intf in self._interfaces: self.assertNotIn(intf, interfaces()) - # No daemon that was started during a test should remain running + # No daemon started during tests should remain running for daemon in ['dhcp6c', 'dhclient']: - self.assertFalse(process_named_running(daemon)) + # if _interface list is populated do a more fine grained search + # by also checking the cmd arguments passed to the daemon + if self._interfaces: + for tmp in self._interfaces: + self.assertFalse(process_named_running(daemon, tmp)) + else: + self.assertFalse(process_named_running(daemon)) def test_dhcp_disable_interface(self): if not self._test_dhcp: @@ -119,11 +146,9 @@ class BasicInterfaceTest: for option in self._options.get(interface, []): self.cli_set(self._base_path + [interface] + option.split()) - self.cli_set(self._base_path + [interface, 'disable']) - # Also enable DHCP (ISC DHCP always places interface in admin up # state so we check that we do not start DHCP client. - # https://phabricator.vyos.net/T2767 + # https://vyos.dev/T2767 self.cli_set(self._base_path + [interface, 'address', 'dhcp']) self.cli_commit() @@ -133,6 +158,99 @@ class BasicInterfaceTest: flags = read_file(f'/sys/class/net/{interface}/flags') self.assertEqual(int(flags, 16) & 1, 0) + def test_dhcp_client_options(self): + if not self._test_dhcp or not self._test_vrf: + self.skipTest('not supported') + + distance = '100' + + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'address', 'dhcp']) + self.cli_set(self._base_path + [interface, 'dhcp-options', 'default-route-distance', distance]) + + self.cli_commit() + + for interface in self._interfaces: + # Check if dhclient process runs + dhclient_pid = process_named_running(dhclient_process_name, cmdline=interface) + self.assertTrue(dhclient_pid) + + dhclient_config = read_file(f'{dhclient_base_dir}/dhclient_{interface}.conf') + self.assertIn('request subnet-mask, broadcast-address, routers, domain-name-servers', dhclient_config) + self.assertIn('require subnet-mask;', dhclient_config) + + # and the commandline has the appropriate options + cmdline = read_file(f'/proc/{dhclient_pid}/cmdline') + self.assertIn(f'-e\x00IF_METRIC={distance}', cmdline) + + def test_dhcp_vrf(self): + if not self._test_dhcp or not self._test_vrf: + self.skipTest('not supported') + + vrf_name = 'purple4' + self.cli_set(['vrf', 'name', vrf_name, 'table', '65000']) + + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'address', 'dhcp']) + self.cli_set(self._base_path + [interface, 'vrf', vrf_name]) + + self.cli_commit() + + # Validate interface state + for interface in self._interfaces: + tmp = get_interface_vrf(interface) + self.assertEqual(tmp, vrf_name) + + # Check if dhclient process runs + dhclient_pid = process_named_running(dhclient_process_name, cmdline=interface) + self.assertTrue(dhclient_pid) + # .. inside the appropriate VRF instance + vrf_pids = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(str(dhclient_pid), vrf_pids) + # and the commandline has the appropriate options + cmdline = read_file(f'/proc/{dhclient_pid}/cmdline') + self.assertIn('-e\x00IF_METRIC=210', cmdline) # 210 is the default value + + self.cli_delete(['vrf', 'name', vrf_name]) + + def test_dhcpv6_vrf(self): + if not self._test_ipv6_dhcpc6 or not self._test_vrf: + self.skipTest('not supported') + + vrf_name = 'purple6' + self.cli_set(['vrf', 'name', vrf_name, 'table', '65001']) + + # When interface is configured as admin down, it must be admin down + # even when dhcpc starts on the given interface + for interface in self._interfaces: + for option in self._options.get(interface, []): + self.cli_set(self._base_path + [interface] + option.split()) + + self.cli_set(self._base_path + [interface, 'address', 'dhcpv6']) + self.cli_set(self._base_path + [interface, 'vrf', vrf_name]) + + self.cli_commit() + + # Validate interface state + for interface in self._interfaces: + tmp = get_interface_vrf(interface) + self.assertEqual(tmp, vrf_name) + + # Check if dhclient process runs + tmp = process_named_running(dhcp6c_process_name, cmdline=interface) + self.assertTrue(tmp) + # .. inside the appropriate VRF instance + vrf_pids = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(str(tmp), vrf_pids) + + self.cli_delete(['vrf', 'name', vrf_name]) + def test_span_mirror(self): if not self._mirror_interfaces: self.skipTest('not supported') @@ -360,7 +478,7 @@ class BasicInterfaceTest: # is the Wireless test will first load the wifi kernel hwsim module # which creates a wlan0 and wlan1 interface which will fail the # tearDown() test in the end that no interface is allowed to survive! - if not self._test_vlan: + if not self._test_vlan or not self._test_mtu: self.skipTest('not supported') mtu_1500 = '1500' @@ -476,7 +594,7 @@ class BasicInterfaceTest: self.assertEqual(to_key, new_egress_qos_to) def test_vif_8021q_lower_up_down(self): - # Testcase for https://phabricator.vyos.net/T3349 + # Testcase for https://vyos.dev/T3349 if not self._test_vlan: self.skipTest('not supported') @@ -634,58 +752,94 @@ class BasicInterfaceTest: self.cli_set(path + option.split()) # Options - self.cli_set(path + ['ip', 'adjust-mss', mss]) - self.cli_set(path + ['ip', 'arp-cache-timeout', arp_tmo]) - self.cli_set(path + ['ip', 'disable-arp-filter']) - self.cli_set(path + ['ip', 'disable-forwarding']) - self.cli_set(path + ['ip', 'enable-directed-broadcast']) - self.cli_set(path + ['ip', 'enable-arp-accept']) - self.cli_set(path + ['ip', 'enable-arp-announce']) - self.cli_set(path + ['ip', 'enable-arp-ignore']) - self.cli_set(path + ['ip', 'enable-proxy-arp']) - self.cli_set(path + ['ip', 'proxy-arp-pvlan']) - self.cli_set(path + ['ip', 'source-validation', 'loose']) + if cli_defined(self._base_path + ['ip'], 'adjust-mss'): + self.cli_set(path + ['ip', 'adjust-mss', mss]) + + if cli_defined(self._base_path + ['ip'], 'arp-cache-timeout'): + self.cli_set(path + ['ip', 'arp-cache-timeout', arp_tmo]) + + if cli_defined(self._base_path + ['ip'], 'disable-arp-filter'): + self.cli_set(path + ['ip', 'disable-arp-filter']) + + if cli_defined(self._base_path + ['ip'], 'disable-forwarding'): + self.cli_set(path + ['ip', 'disable-forwarding']) + + if cli_defined(self._base_path + ['ip'], 'enable-directed-broadcast'): + self.cli_set(path + ['ip', 'enable-directed-broadcast']) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-accept'): + self.cli_set(path + ['ip', 'enable-arp-accept']) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-announce'): + self.cli_set(path + ['ip', 'enable-arp-announce']) + + if cli_defined(self._base_path + ['ip'], 'enable-arp-ignore'): + self.cli_set(path + ['ip', 'enable-arp-ignore']) + + if cli_defined(self._base_path + ['ip'], 'enable-proxy-arp'): + self.cli_set(path + ['ip', 'enable-proxy-arp']) + + if cli_defined(self._base_path + ['ip'], 'proxy-arp-pvlan'): + self.cli_set(path + ['ip', 'proxy-arp-pvlan']) + + if cli_defined(self._base_path + ['ip'], 'source-validation'): + self.cli_set(path + ['ip', 'source-validation', 'loose']) self.cli_commit() for interface in self._interfaces: - base_options = f'oifname "{interface}"' - out = cmd('sudo nft list chain raw VYOS_TCP_MSS') - for line in out.splitlines(): - if line.startswith(base_options): - self.assertIn(f'tcp option maxseg size set {mss}', line) + if cli_defined(self._base_path + ['ip'], 'adjust-mss'): + base_options = f'oifname "{interface}"' + out = cmd('sudo nft list chain raw VYOS_TCP_MSS') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'tcp option maxseg size set {mss}', line) - tmp = read_file(f'/proc/sys/net/ipv4/neigh/{interface}/base_reachable_time_ms') - self.assertEqual(tmp, str((int(arp_tmo) * 1000))) # tmo value is in milli seconds + if cli_defined(self._base_path + ['ip'], 'arp-cache-timeout'): + tmp = read_file(f'/proc/sys/net/ipv4/neigh/{interface}/base_reachable_time_ms') + self.assertEqual(tmp, str((int(arp_tmo) * 1000))) # tmo value is in milli seconds proc_base = f'/proc/sys/net/ipv4/conf/{interface}' - tmp = read_file(f'{proc_base}/arp_filter') - self.assertEqual('0', tmp) + if cli_defined(self._base_path + ['ip'], 'disable-arp-filter'): + tmp = read_file(f'{proc_base}/arp_filter') + self.assertEqual('0', tmp) - tmp = read_file(f'{proc_base}/arp_accept') - self.assertEqual('1', tmp) + if cli_defined(self._base_path + ['ip'], 'enable-arp-accept'): + tmp = read_file(f'{proc_base}/arp_accept') + self.assertEqual('1', tmp) - tmp = read_file(f'{proc_base}/arp_announce') - self.assertEqual('1', tmp) + if cli_defined(self._base_path + ['ip'], 'enable-arp-announce'): + tmp = read_file(f'{proc_base}/arp_announce') + self.assertEqual('1', tmp) - tmp = read_file(f'{proc_base}/arp_ignore') - self.assertEqual('1', tmp) + if cli_defined(self._base_path + ['ip'], 'enable-arp-ignore'): + tmp = read_file(f'{proc_base}/arp_ignore') + self.assertEqual('1', tmp) - tmp = read_file(f'{proc_base}/forwarding') - self.assertEqual('0', tmp) + if cli_defined(self._base_path + ['ip'], 'disable-forwarding'): + tmp = read_file(f'{proc_base}/forwarding') + self.assertEqual('0', tmp) - tmp = read_file(f'{proc_base}/bc_forwarding') - self.assertEqual('1', tmp) + if cli_defined(self._base_path + ['ip'], 'enable-directed-broadcast'): + tmp = read_file(f'{proc_base}/bc_forwarding') + self.assertEqual('1', tmp) - tmp = read_file(f'{proc_base}/proxy_arp') - self.assertEqual('1', tmp) + if cli_defined(self._base_path + ['ip'], 'enable-proxy-arp'): + tmp = read_file(f'{proc_base}/proxy_arp') + self.assertEqual('1', tmp) - tmp = read_file(f'{proc_base}/proxy_arp_pvlan') - self.assertEqual('1', tmp) + if cli_defined(self._base_path + ['ip'], 'proxy-arp-pvlan'): + tmp = read_file(f'{proc_base}/proxy_arp_pvlan') + self.assertEqual('1', tmp) - tmp = read_file(f'{proc_base}/rp_filter') - self.assertEqual('2', tmp) + if cli_defined(self._base_path + ['ip'], 'source-validation'): + base_options = f'iifname "{interface}"' + out = cmd('sudo nft list chain ip raw vyos_rpfilter') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn('fib saddr oif 0', line) + self.assertIn('drop', line) def test_interface_ipv6_options(self): if not self._test_ipv6: @@ -693,6 +847,8 @@ class BasicInterfaceTest: mss = '1400' dad_transmits = '10' + accept_dad = '0' + source_validation = 'strict' for interface in self._interfaces: path = self._base_path + [interface] @@ -700,26 +856,51 @@ class BasicInterfaceTest: self.cli_set(path + option.split()) # Options - self.cli_set(path + ['ipv6', 'adjust-mss', mss]) - self.cli_set(path + ['ipv6', 'disable-forwarding']) - self.cli_set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) + if cli_defined(self._base_path + ['ipv6'], 'adjust-mss'): + self.cli_set(path + ['ipv6', 'adjust-mss', mss]) - self.cli_commit() + if cli_defined(self._base_path + ['ipv6'], 'accept-dad'): + self.cli_set(path + ['ipv6', 'accept-dad', accept_dad]) - for interface in self._interfaces: - base_options = f'oifname "{interface}"' - out = cmd('sudo nft list chain ip6 raw VYOS_TCP_MSS') - for line in out.splitlines(): - if line.startswith(base_options): - self.assertIn(f'tcp option maxseg size set {mss}', line) + if cli_defined(self._base_path + ['ipv6'], 'dup-addr-detect-transmits'): + self.cli_set(path + ['ipv6', 'dup-addr-detect-transmits', dad_transmits]) - proc_base = f'/proc/sys/net/ipv6/conf/{interface}' + if cli_defined(self._base_path + ['ipv6'], 'disable-forwarding'): + self.cli_set(path + ['ipv6', 'disable-forwarding']) - tmp = read_file(f'{proc_base}/forwarding') - self.assertEqual('0', tmp) + if cli_defined(self._base_path + ['ipv6'], 'source-validation'): + self.cli_set(path + ['ipv6', 'source-validation', source_validation]) - tmp = read_file(f'{proc_base}/dad_transmits') - self.assertEqual(dad_transmits, tmp) + self.cli_commit() + + for interface in self._interfaces: + proc_base = f'/proc/sys/net/ipv6/conf/{interface}' + if cli_defined(self._base_path + ['ipv6'], 'adjust-mss'): + base_options = f'oifname "{interface}"' + out = cmd('sudo nft list chain ip6 raw VYOS_TCP_MSS') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn(f'tcp option maxseg size set {mss}', line) + + if cli_defined(self._base_path + ['ipv6'], 'accept-dad'): + tmp = read_file(f'{proc_base}/accept_dad') + self.assertEqual(accept_dad, tmp) + + if cli_defined(self._base_path + ['ipv6'], 'dup-addr-detect-transmits'): + tmp = read_file(f'{proc_base}/dad_transmits') + self.assertEqual(dad_transmits, tmp) + + if cli_defined(self._base_path + ['ipv6'], 'disable-forwarding'): + tmp = read_file(f'{proc_base}/forwarding') + self.assertEqual('0', tmp) + + if cli_defined(self._base_path + ['ipv6'], 'source-validation'): + base_options = f'iifname "{interface}"' + out = cmd('sudo nft list chain ip6 raw vyos_rpfilter') + for line in out.splitlines(): + if line.startswith(base_options): + self.assertIn('fib saddr . iif oif 0', line) + self.assertIn('drop', line) def test_dhcpv6_client_options(self): if not self._test_ipv6_dhcpc6: @@ -734,6 +915,7 @@ class BasicInterfaceTest: # Enable DHCPv6 client self.cli_set(path + ['address', 'dhcpv6']) + self.cli_set(path + ['dhcpv6-options', 'no-release']) self.cli_set(path + ['dhcpv6-options', 'rapid-commit']) self.cli_set(path + ['dhcpv6-options', 'parameters-only']) self.cli_set(path + ['dhcpv6-options', 'duid', duid]) @@ -744,7 +926,7 @@ class BasicInterfaceTest: duid_base = 10 for interface in self._interfaces: duid = '00:01:00:01:27:71:db:f0:00:50:00:00:00:{}'.format(duid_base) - dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + dhcpc6_config = read_file(f'{dhcp6c_base_dir}/dhcp6c.{interface}.conf') self.assertIn(f'interface {interface} ' + '{', dhcpc6_config) self.assertIn(f' request domain-name-servers;', dhcpc6_config) self.assertIn(f' request domain-name;', dhcpc6_config) @@ -755,8 +937,12 @@ class BasicInterfaceTest: self.assertIn('};', dhcpc6_config) duid_base += 1 - # Check for running process - self.assertTrue(process_named_running('dhcp6c')) + # Better ask the process about it's commandline in the future + pid = process_named_running(dhcp6c_process_name, cmdline=interface) + self.assertTrue(pid) + + dhcp6c_options = read_file(f'/proc/{pid}/cmdline') + self.assertIn('-n', dhcp6c_options) def test_dhcpv6pd_auto_sla_id(self): if not self._test_ipv6_pd: @@ -765,7 +951,14 @@ class BasicInterfaceTest: prefix_len = '56' sla_len = str(64 - int(prefix_len)) + # Create delegatee interfaces first to avoid any confusion by dhcpc6 + # this is mainly an "issue" with virtual-ethernet interfaces delegatees = ['dum2340', 'dum2341', 'dum2342', 'dum2343', 'dum2344'] + for delegatee in delegatees: + section = Section.section(delegatee) + self.cli_set(['interfaces', section, delegatee]) + + self.cli_commit() for interface in self._interfaces: path = self._base_path + [interface] @@ -778,8 +971,6 @@ class BasicInterfaceTest: self.cli_set(pd_base + ['length', prefix_len]) for delegatee in delegatees: - section = Section.section(delegatee) - self.cli_set(['interfaces', section, delegatee]) self.cli_set(pd_base + ['interface', delegatee, 'address', address]) # increment interface address address = str(int(address) + 1) @@ -787,7 +978,7 @@ class BasicInterfaceTest: self.cli_commit() for interface in self._interfaces: - dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + dhcpc6_config = read_file(f'{dhcp6c_base_dir}/dhcp6c.{interface}.conf') # verify DHCPv6 prefix delegation self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) @@ -805,8 +996,8 @@ class BasicInterfaceTest: # increment interface address address = str(int(address) + 1) - # Check for running process - self.assertTrue(process_named_running('dhcp6c')) + # Check for running process + self.assertTrue(process_named_running(dhcp6c_process_name, cmdline=interface)) for delegatee in delegatees: # we can already cleanup the test delegatee interface here @@ -821,7 +1012,14 @@ class BasicInterfaceTest: prefix_len = '56' sla_len = str(64 - int(prefix_len)) + # Create delegatee interfaces first to avoid any confusion by dhcpc6 + # this is mainly an "issue" with virtual-ethernet interfaces delegatees = ['dum3340', 'dum3341', 'dum3342', 'dum3343', 'dum3344'] + for delegatee in delegatees: + section = Section.section(delegatee) + self.cli_set(['interfaces', section, delegatee]) + + self.cli_commit() for interface in self._interfaces: path = self._base_path + [interface] @@ -835,8 +1033,6 @@ class BasicInterfaceTest: self.cli_set(pd_base + ['length', prefix_len]) for delegatee in delegatees: - section = Section.section(delegatee) - self.cli_set(['interfaces', section, delegatee]) self.cli_set(pd_base + ['interface', delegatee, 'address', address]) self.cli_set(pd_base + ['interface', delegatee, 'sla-id', sla_id]) @@ -850,7 +1046,7 @@ class BasicInterfaceTest: for interface in self._interfaces: address = '1' sla_id = '1' - dhcpc6_config = read_file(f'/run/dhcp6c/dhcp6c.{interface}.conf') + dhcpc6_config = read_file(f'{dhcp6c_base_dir}/dhcp6c.{interface}.conf') # verify DHCPv6 prefix delegation self.assertIn(f'prefix ::/{prefix_len} infinity;', dhcpc6_config) @@ -866,8 +1062,8 @@ class BasicInterfaceTest: # increment interface address address = str(int(address) + 1) - # Check for running process - self.assertTrue(process_named_running('dhcp6c')) + # Check for running process + self.assertTrue(process_named_running(dhcp6c_process_name, cmdline=interface)) for delegatee in delegatees: # we can already cleanup the test delegatee interface here diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py index 7cfb53045..f694f539d 100644 --- a/smoketest/scripts/cli/base_vyostest_shim.py +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -14,6 +14,7 @@ import os import unittest +import paramiko from time import sleep from typing import Type @@ -22,8 +23,8 @@ from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos import ConfigError from vyos.defaults import commit_lock -from vyos.util import cmd -from vyos.util import run +from vyos.utils.process import cmd +from vyos.utils.process import run save_config = '/tmp/vyos-smoketest-save' @@ -87,6 +88,19 @@ class VyOSUnitTestSHIM: pprint.pprint(out) return out + @staticmethod + def ssh_send_cmd(command, username, password, hostname='localhost'): + """ SSH command execution helper """ + # Try to login via SSH + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_client.connect(hostname=hostname, username=username, password=password) + _, stdout, stderr = ssh_client.exec_command(command) + output = stdout.read().decode().strip() + error = stderr.read().decode().strip() + ssh_client.close() + return output, error + # standard construction; typing suggestion: https://stackoverflow.com/a/70292317 def ignore_warning(warning: Type[Warning]): import warnings diff --git a/smoketest/scripts/cli/test_configd_init.py b/smoketest/scripts/cli/test_configd_init.py index 5dec89963..245c03824 100755 --- a/smoketest/scripts/cli/test_configd_init.py +++ b/smoketest/scripts/cli/test_configd_init.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,7 +17,8 @@ import unittest from time import sleep -from vyos.util import cmd, is_systemd_service_running +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import cmd class TestConfigdInit(unittest.TestCase): def setUp(self): diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py index b9d308ae1..b43c05fae 100755 --- a/smoketest/scripts/cli/test_container.py +++ b/smoketest/scripts/cli/test_container.py @@ -21,9 +21,9 @@ import json from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file base_path = ['container'] cont_image = 'busybox:stable' # busybox is included in vyos-build @@ -47,7 +47,10 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): super(TestContainer, cls).setUpClass() # Load image for smoketest provided in vyos-build - cmd(f'cat {busybox_image_path} | sudo podman load') + try: + cmd(f'cat {busybox_image_path} | sudo podman load') + except: + cls.skipTest(cls, reason='busybox image not available') @classmethod def tearDownClass(cls): diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 821925bcd..391ef03ff 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -17,11 +17,13 @@ import unittest from glob import glob +from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd +from vyos.utils.process import cmd +from vyos.utils.process import run sysfs_config = { 'all_ping': {'sysfs': '/proc/sys/net/ipv4/icmp_echo_ignore_all', 'default': '0', 'test_value': 'disable'}, @@ -76,20 +78,42 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): break self.assertTrue(not matched if inverse else matched, msg=search) + def verify_nftables_chain(self, nftables_search, table, chain, inverse=False, args=''): + nftables_output = cmd(f'sudo nft {args} list chain {table} {chain}') + + for search in nftables_search: + matched = False + for line in nftables_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + + def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): + # Resolver no longer blocks commit, need to wait for daemon to populate set + count = 0 + while count < max_wait: + code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') + if code == 0: + return True + count += 1 + sleep(1) + return False + def test_geoip(self): - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'drop']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'se']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'geoip', 'country-code', 'gb']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'de']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'country-code', 'fr']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '2', 'source', 'geoip', 'inverse-match']) self.cli_commit() nftables_search = [ - ['ip saddr @GEOIP_CC_smoketest_1', 'drop'], - ['ip saddr != @GEOIP_CC_smoketest_2', 'return'] + ['ip saddr @GEOIP_CC_name_smoketest_1', 'drop'], + ['ip saddr != @GEOIP_CC_name_smoketest_2', 'accept'] ] # -t prevents 1000+ GeoIP elements being returned @@ -111,31 +135,36 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '123']) self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.com']) self.cli_set(['firewall', 'group', 'domain-group', 'smoketest_domain', 'address', 'example.org']) + self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'eth0']) + self.cli_set(['firewall', 'group', 'interface-group', 'smoketest_interface', 'interface', 'vtun0']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'outbound-interface', 'interface-group', '!smoketest_interface']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'action', 'accept']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '2', 'source', 'group', 'mac-group', 'smoketest_mac']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'action', 'accept']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '3', 'source', 'group', 'domain-group', 'smoketest_domain']) + self.cli_commit() - self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) + self.wait_for_domain_resolver('ip vyos_filter', 'D_smoketest_domain', '192.0.2.5') - self.cli_commit() nftables_search = [ - ['iifname "eth0"', 'jump NAME_smoketest'], - ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'return'], + ['ip saddr @N_smoketest_network', 'ip daddr 172.16.10.10', 'th dport @P_smoketest_port', 'accept'], ['elements = { 172.16.99.0/24 }'], ['elements = { 53, 123 }'], - ['ether saddr @M_smoketest_mac', 'return'], + ['ether saddr @M_smoketest_mac', 'accept'], ['elements = { 00:01:02:03:04:05 }'], ['set D_smoketest_domain'], ['elements = { 192.0.2.5, 192.0.2.8,'], ['192.0.2.10, 192.0.2.11 }'], - ['ip saddr @D_smoketest_domain', 'return'] + ['ip saddr @D_smoketest_domain', 'accept'], + ['oifname != @I_smoketest_interface', 'accept'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') @@ -149,12 +178,10 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port', 'port', '53']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'port', '123']) self.cli_set(['firewall', 'group', 'port-group', 'smoketest_port1', 'include', 'smoketest_port']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'action', 'accept']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1']) - self.cli_set(['firewall', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) - - self.cli_set(['firewall', 'interface', 'eth0', 'in', 'name', 'smoketest']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network1']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'destination', 'group', 'port-group', 'smoketest_port1']) + self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'protocol', 'tcp_udp']) self.cli_commit() @@ -166,8 +193,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.cli_delete(['firewall', 'group', 'network-group', 'smoketest_network', 'include', 'smoketest_network1']) nftables_search = [ - ['iifname "eth0"', 'jump NAME_smoketest'], - ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'return'], + ['ip saddr @N_smoketest_network1', 'th dport @P_smoketest_port1', 'accept'], ['elements = { 172.16.99.0/24, 172.16.101.0/24 }'], ['elements = { 53, 123 }'] ] @@ -177,56 +203,80 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): def test_ipv4_basic_rules(self): name = 'smoketest' interface = 'eth0' + interface_inv = '!eth0' + interface_wc = 'l2tp*' mss_range = '501-1460' - - self.cli_set(['firewall', 'name', name, 'default-action', 'drop']) - self.cli_set(['firewall', 'name', name, 'enable-default-log']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'log', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'log-level', 'debug']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'ttl', 'eq', '15']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'protocol', 'tcp']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'destination', 'port', '8888']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'log', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'log-level', 'err']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'ttl', 'gt', '102']) - self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '3', 'protocol', 'tcp']) - self.cli_set(['firewall', 'name', name, 'rule', '3', 'destination', 'port', '22']) - self.cli_set(['firewall', 'name', name, 'rule', '3', 'limit', 'rate', '5/minute']) - self.cli_set(['firewall', 'name', name, 'rule', '3', 'log', 'disable']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'action', 'drop']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'protocol', 'tcp']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'destination', 'port', '22']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'count', '10']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'recent', 'time', 'minute']) - self.cli_set(['firewall', 'name', name, 'rule', '5', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '5', 'protocol', 'tcp']) - self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'flags', 'syn']) - self.cli_set(['firewall', 'name', name, 'rule', '5', 'tcp', 'mss', mss_range]) - self.cli_set(['firewall', 'name', name, 'rule', '5', 'inbound-interface', interface]) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'return']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'protocol', 'gre']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'outbound-interface', interface]) - - self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) + conn_mark = '555' + + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'source', 'address', '172.16.20.10']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'log-options', 'level', 'debug']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'ttl', 'eq', '15']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'destination', 'port', '8888']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'log-options', 'level', 'err']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'syn']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'tcp', 'flags', 'not', 'ack']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'ttl', 'gt', '102']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'destination', 'port', '22']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'limit', 'rate', '5/minute']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '3', 'log', 'disable']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'destination', 'port', '22']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'count', '10']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'recent', 'time', 'minute']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '4', 'packet-type', 'host']) + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'flags', 'syn']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'tcp', 'mss', mss_range]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'packet-type', 'broadcast']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '5', 'inbound-interface', 'interface-name', interface_wc]) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'action', 'return']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '6', 'connection-mark', conn_mark]) + + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'default-action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '5', 'outbound-interface', 'interface-name', interface_inv]) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'action', 'return']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'protocol', 'icmp']) + self.cli_set(['firewall', 'ipv4', 'output', 'filter', 'rule', '6', 'connection-mark', conn_mark]) self.cli_commit() + mark_hex = "{0:#010x}".format(int(conn_mark)) + nftables_search = [ - [f'iifname "{interface}"', f'jump NAME_{name}'], - ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[smoketest-1-A]" level debug', 'ip ttl 15', 'return'], - ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[smoketest-2-R]" level err', 'ip ttl > 102', 'reject'], - ['tcp dport 22', 'limit rate 5/minute', 'return'], - ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'], - ['tcp dport 22', 'add @RECENT_smoketest_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'drop'], - ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface}"'], - ['meta l4proto gre', f'oifname "{interface}"', 'return'] + ['chain VYOS_FORWARD_filter'], + ['type filter hook forward priority filter; policy drop;'], + ['tcp dport 22', 'limit rate 5/minute', 'accept'], + ['tcp dport 22', 'add @RECENT_FWD_filter_4 { ip saddr limit rate over 10/minute burst 10 packets }', 'meta pkttype host', 'drop'], + ['chain VYOS_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['tcp flags & syn == syn', f'tcp option maxseg size {mss_range}', f'iifname "{interface_wc}"', 'meta pkttype broadcast', 'accept'], + ['meta l4proto gre', f'ct mark {mark_hex}', 'return'], + ['chain VYOS_OUTPUT_filter'], + ['type filter hook output priority filter; policy accept;'], + ['meta l4proto gre', f'oifname != "{interface}"', 'drop'], + ['meta l4proto icmp', f'ct mark {mark_hex}', 'return'], + ['chain NAME_smoketest'], + ['saddr 172.16.20.10', 'daddr 172.16.10.10', 'log prefix "[ipv4-NAM-smoketest-1-A]" log level debug', 'ip ttl 15', 'accept'], + ['tcp flags syn / syn,ack', 'tcp dport 8888', 'log prefix "[ipv4-NAM-smoketest-2-R]" log level err', 'ip ttl > 102', 'reject'], + ['log prefix "[smoketest-default-D]"','smoketest default-action', 'drop'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') @@ -236,76 +286,134 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): name2 = 'smoketest-adv2' interface = 'eth0' - self.cli_set(['firewall', 'name', name, 'default-action', 'drop']) - self.cli_set(['firewall', 'name', name, 'enable-default-log']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '64']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '512']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'packet-length', '1024']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '17']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'dscp', '52']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'group', '66']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'snapshot-length', '6666']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '6', 'log-options', 'queue-threshold','32000']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length', '1-30000']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp', '3-11']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '7', 'dscp-exclude', '21-25']) + + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'source', 'address', '198.51.100.1']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv4', 'forward', 'filter', 'rule', '1', 'jump-target', name]) + + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'protocol', 'tcp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'action', 'queue']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '2', 'queue', '3']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'action', 'queue']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'fanout']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue-options', 'bypass']) + self.cli_set(['firewall', 'ipv4', 'input', 'filter', 'rule', '3', 'queue', '0-15']) + + self.cli_commit() + + nftables_search = [ + ['chain VYOS_FORWARD_filter'], + ['type filter hook forward priority filter; policy drop;'], + ['ip saddr 198.51.100.1', f'jump NAME_{name}'], + ['chain VYOS_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + [f'meta l4proto tcp','queue to 3'], + [f'meta l4proto udp','queue flags bypass,fanout to 0-15'], + [f'chain NAME_{name}'], + ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', f'log prefix "[ipv4-NAM-{name}-6-A]" log group 66 snaplen 6666 queue-threshold 32000', 'accept'], + ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'accept'], + [f'log prefix "[{name}-default-D]"', 'drop'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + + def test_ipv4_mask(self): + name = 'smoketest-mask' + interface = 'eth0' + + self.cli_set(['firewall', 'group', 'address-group', 'mask_group', 'address', '1.1.1.1']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '64']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '512']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'packet-length', '1024']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '17']) - self.cli_set(['firewall', 'name', name, 'rule', '6', 'dscp', '52']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'enable-default-log']) - self.cli_set(['firewall', 'name', name, 'rule', '7', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length', '1-30000']) - self.cli_set(['firewall', 'name', name, 'rule', '7', 'packet-length-exclude', '60000-65535']) - self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp', '3-11']) - self.cli_set(['firewall', 'name', name, 'rule', '7', 'dscp-exclude', '21-25']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address', '0.0.1.2']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'destination', 'address-mask', '0.0.255.255']) - self.cli_set(['firewall', 'name', name2, 'default-action', 'jump']) - self.cli_set(['firewall', 'name', name2, 'default-jump-target', name]) - self.cli_set(['firewall', 'name', name2, 'enable-default-log']) - self.cli_set(['firewall', 'name', name2, 'rule', '1', 'source', 'address', '198.51.100.1']) - self.cli_set(['firewall', 'name', name2, 'rule', '1', 'action', 'jump']) - self.cli_set(['firewall', 'name', name2, 'rule', '1', 'jump-target', name]) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address', '!0.0.3.4']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'source', 'address-mask', '0.0.255.255']) - self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'source', 'address-mask', '0.0.255.255']) self.cli_commit() nftables_search = [ - [f'iifname "{interface}"', f'jump NAME_{name}'], - ['ip length { 64, 512, 1024 }', 'ip dscp { 0x11, 0x34 }', 'return'], - ['ip length 1-30000', 'ip length != 60000-65535', 'ip dscp 0x03-0x0b', 'ip dscp != 0x15-0x19', 'return'], - [f'log prefix "[{name}-default-D]"', 'drop'], - ['ip saddr 198.51.100.1', f'jump NAME_{name}'], - [f'log prefix "[{name2}-default-J]"', f'jump NAME_{name}'] + [f'daddr & 0.0.255.255 == 0.0.1.2'], + [f'saddr & 0.0.255.255 != 0.0.3.4'], + [f'saddr & 0.0.255.255 == @A_mask_group'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') + def test_ipv6_basic_rules(self): name = 'v6-smoketest' interface = 'eth0' - self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop']) - self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'action', 'accept']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'source', 'address', '2002::1']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'destination', 'address', '2002::1:1']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log', 'enable']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '1', 'log-level', 'crit']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'source', 'address', '2002::1']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '2002::1:1']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log', 'enable']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'action', 'reject']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'protocol', 'tcp_udp']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'destination', 'port', '8888']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '2', 'inbound-interface', interface]) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'default-action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'protocol', 'tcp_udp']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'destination', 'port', '8888']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '2', 'inbound-interface', 'interface-name', interface]) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'return']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'protocol', 'gre']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'outbound-interface', interface]) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'action', 'return']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'protocol', 'gre']) + self.cli_set(['firewall', 'ipv6', 'output', 'filter', 'rule', '3', 'outbound-interface', 'interface-name', interface]) - self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'protocol', 'udp']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'source', 'address', '2002::1:2']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '3', 'inbound-interface', 'interface-name', interface]) self.cli_commit() nftables_search = [ - [f'iifname "{interface}"', f'jump NAME6_{name}'], - ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[v6-smoketest-1-A]" level crit', 'return'], + ['chain VYOS_IPV6_FORWARD_filter'], + ['type filter hook forward priority filter; policy accept;'], ['meta l4proto { tcp, udp }', 'th dport 8888', f'iifname "{interface}"', 'reject'], + ['chain VYOS_IPV6_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], + ['meta l4proto udp', 'ip6 saddr 2002::1:2', f'iifname "{interface}"', 'accept'], + ['chain VYOS_IPV6_OUTPUT_filter'], + ['type filter hook output priority filter; policy drop;'], ['meta l4proto gre', f'oifname "{interface}"', 'return'], - ['smoketest default-action', f'log prefix "[{name}-default-D]"', 'drop'] + [f'chain NAME6_{name}'], + ['saddr 2002::1', 'daddr 2002::1:1', 'log prefix "[ipv6-NAM-v6-smoketest-1-A]" log level crit', 'accept'], + [f'"{name} default-action drop"', f'log prefix "[{name}-default-D]"', 'drop'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') @@ -315,95 +423,169 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): name2 = 'v6-smoketest-adv2' interface = 'eth0' - self.cli_set(['firewall', 'ipv6-name', name, 'default-action', 'drop']) - self.cli_set(['firewall', 'ipv6-name', name, 'enable-default-log']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'action', 'accept']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '65']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '513']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'packet-length', '1025']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '18']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '3', 'dscp', '53']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '65']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '513']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'packet-length', '1025']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '18']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'dscp', '53']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'action', 'accept']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length', '1-1999']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'packet-length-exclude', '60000-65535']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp', '4-14']) - self.cli_set(['firewall', 'ipv6-name', name, 'rule', '4', 'dscp-exclude', '31-35']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length', '1-1999']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'packet-length-exclude', '60000-65535']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp', '4-14']) + self.cli_set(['firewall', 'ipv6', 'forward', 'filter', 'rule', '4', 'dscp-exclude', '31-35']) - self.cli_set(['firewall', 'ipv6-name', name2, 'default-action', 'jump']) - self.cli_set(['firewall', 'ipv6-name', name2, 'default-jump-target', name]) - self.cli_set(['firewall', 'ipv6-name', name2, 'enable-default-log']) - self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'source', 'address', '2001:db8::/64']) - self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'action', 'jump']) - self.cli_set(['firewall', 'ipv6-name', name2, 'rule', '1', 'jump-target', name]) - - self.cli_set(['firewall', 'interface', interface, 'in', 'ipv6-name', name]) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'default-action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'source', 'address', '2001:db8::/64']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'action', 'jump']) + self.cli_set(['firewall', 'ipv6', 'input', 'filter', 'rule', '1', 'jump-target', name]) self.cli_commit() nftables_search = [ - [f'iifname "{interface}"', f'jump NAME6_{name}'], - ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'return'], - ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'return'], - [f'log prefix "[{name}-default-D]"', 'drop'], + ['chain VYOS_IPV6_FORWARD_filter'], + ['type filter hook forward priority filter; policy accept;'], + ['ip6 length 1-1999', 'ip6 length != 60000-65535', 'ip6 dscp 0x04-0x0e', 'ip6 dscp != 0x1f-0x23', 'accept'], + ['chain VYOS_IPV6_INPUT_filter'], + ['type filter hook input priority filter; policy accept;'], ['ip6 saddr 2001:db8::/64', f'jump NAME6_{name}'], - [f'log prefix "[{name2}-default-J]"', f'jump NAME6_{name}'] + [f'chain NAME6_{name}'], + ['ip6 length { 65, 513, 1025 }', 'ip6 dscp { af21, 0x35 }', 'accept'], + [f'log prefix "[{name}-default-D]"', 'drop'] ] self.verify_nftables(nftables_search, 'ip6 vyos_filter') - def test_state_policy(self): - self.cli_set(['firewall', 'state-policy', 'established', 'action', 'accept']) - self.cli_set(['firewall', 'state-policy', 'related', 'action', 'accept']) - self.cli_set(['firewall', 'state-policy', 'invalid', 'action', 'drop']) + def test_ipv6_mask(self): + name = 'v6-smoketest-mask' + interface = 'eth0' + + self.cli_set(['firewall', 'group', 'ipv6-address-group', 'mask_group', 'address', '::beef']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'enable-default-log']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address', '::1111:2222:3333:4444']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '1', 'destination', 'address-mask', '::ffff:ffff:ffff:ffff']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'action', 'accept']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address', '!::aaaa:bbbb:cccc:dddd']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '2', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) + + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'action', 'drop']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'group', 'address-group', 'mask_group']) + self.cli_set(['firewall', 'ipv6', 'name', name, 'rule', '3', 'source', 'address-mask', '::ffff:ffff:ffff:ffff']) self.cli_commit() - chains = { - 'ip vyos_filter': ['VYOS_FW_FORWARD', 'VYOS_FW_OUTPUT', 'VYOS_FW_LOCAL'], - 'ip6 vyos_filter': ['VYOS_FW6_FORWARD', 'VYOS_FW6_OUTPUT', 'VYOS_FW6_LOCAL'] - } + nftables_search = [ + ['daddr & ::ffff:ffff:ffff:ffff == ::1111:2222:3333:4444'], + ['saddr & ::ffff:ffff:ffff:ffff != ::aaaa:bbbb:cccc:dddd'], + ['saddr & ::ffff:ffff:ffff:ffff == @A6_mask_group'] + ] - for table in ['ip vyos_filter', 'ip6 vyos_filter']: - for chain in chains[table]: - nftables_output = cmd(f'sudo nft list chain {table} {chain}') - self.assertTrue('jump VYOS_STATE_POLICY' in nftables_output) + self.verify_nftables(nftables_search, 'ip6 vyos_filter') def test_ipv4_state_and_status_rules(self): name = 'smoketest-state' interface = 'eth0' - self.cli_set(['firewall', 'name', name, 'default-action', 'drop']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'state', 'established', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '1', 'state', 'related', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'action', 'reject']) - self.cli_set(['firewall', 'name', name, 'rule', '2', 'state', 'invalid', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '3', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '3', 'state', 'new', 'enable']) - - self.cli_set(['firewall', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'action', 'accept']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'new', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'state', 'established', 'enable']) - self.cli_set(['firewall', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source']) - - self.cli_set(['firewall', 'interface', interface, 'in', 'name', name]) + self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'established', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'related', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'action', 'reject']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '2', 'state', 'invalid', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'state', 'new', 'enable']) + + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '3', 'connection-status', 'nat', 'destination']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'new', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'state', 'established', 'enable']) + self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '4', 'connection-status', 'nat', 'source']) self.cli_commit() nftables_search = [ - [f'iifname "{interface}"', f'jump NAME_{name}'], - ['ct state { established, related }', 'return'], + ['ct state { established, related }', 'accept'], ['ct state invalid', 'reject'], - ['ct state new', 'ct status dnat', 'return'], - ['ct state { established, new }', 'ct status snat', 'return'], + ['ct state new', 'ct status dnat', 'accept'], + ['ct state { established, new }', 'ct status snat', 'accept'], ['drop', f'comment "{name} default-action drop"'] ] self.verify_nftables(nftables_search, 'ip vyos_filter') + # Check conntrack + self.verify_nftables_chain([['accept']], 'raw', 'FW_CONNTRACK') + self.verify_nftables_chain([['return']], 'ip6 raw', 'FW_CONNTRACK') + + def test_bridge_basic_rules(self): + name = 'smoketest' + interface_in = 'eth0' + mac_address = '00:53:00:00:00:01' + vlan_id = '12' + vlan_prior = '3' + + self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept']) + self.cli_set(['firewall', 'bridge', 'name', name, 'enable-default-log']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'source', 'mac-address', mac_address]) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'inbound-interface', 'interface-name', interface_in]) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log', 'enable']) + self.cli_set(['firewall', 'bridge', 'name', name, 'rule', '1', 'log-options', 'level', 'crit']) + + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-action', 'drop']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id]) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump']) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name]) + self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior]) + + self.cli_commit() + + nftables_search = [ + ['chain VYOS_FORWARD_filter'], + ['type filter hook forward priority filter; policy drop;'], + [f'vlan id {vlan_id}', 'accept'], + [f'vlan pcp {vlan_prior}', f'jump NAME_{name}'], + [f'chain NAME_{name}'], + [f'ether saddr {mac_address}', f'iifname "{interface_in}"', f'log prefix "[bri-NAM-{name}-1-A]" log level crit', 'accept'] + ] + + self.verify_nftables(nftables_search, 'bridge vyos_filter') + + def test_source_validation(self): + # Strict + self.cli_set(['firewall', 'global-options', 'source-validation', 'strict']) + self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'strict']) + self.cli_commit() + + nftables_strict_search = [ + ['fib saddr . iif oif 0', 'drop'] + ] + + self.verify_nftables_chain(nftables_strict_search, 'ip raw', 'vyos_global_rpfilter') + self.verify_nftables_chain(nftables_strict_search, 'ip6 raw', 'vyos_global_rpfilter') + + # Loose + self.cli_set(['firewall', 'global-options', 'source-validation', 'loose']) + self.cli_set(['firewall', 'global-options', 'ipv6-source-validation', 'loose']) + self.cli_commit() + + nftables_loose_search = [ + ['fib saddr oif 0', 'drop'] + ] + + self.verify_nftables_chain(nftables_loose_search, 'ip raw', 'vyos_global_rpfilter') + self.verify_nftables_chain(nftables_loose_search, 'ip6 raw', 'vyos_global_rpfilter') + def test_sysfs(self): for name, conf in sysfs_config.items(): paths = glob(conf['sysfs']) @@ -411,7 +593,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): with open(path, 'r') as f: self.assertEqual(f.read().strip(), conf['default'], msg=path) - self.cli_set(['firewall', name.replace("_", "-"), conf['test_value']]) + self.cli_set(['firewall', 'global-options', name.replace("_", "-"), conf['test_value']]) self.cli_commit() @@ -421,35 +603,17 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): with open(path, 'r') as f: self.assertNotEqual(f.read().strip(), conf['default'], msg=path) - def test_zone_basic(self): - self.cli_set(['firewall', 'name', 'smoketest', 'default-action', 'drop']) - self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0']) - self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) - self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone']) - self.cli_set(['firewall', 'zone', 'smoketest-local', 'from', 'smoketest-eth0', 'firewall', 'name', 'smoketest']) - + def test_flow_offload_software(self): + self.cli_set(['firewall', 'global-options', 'flow-offload', 'software', 'interface', 'eth0']) self.cli_commit() - nftables_search = [ - ['chain VZONE_smoketest-eth0'], - ['chain VZONE_smoketest-local_IN'], - ['chain VZONE_smoketest-local_OUT'], - ['oifname "eth0"', 'jump VZONE_smoketest-eth0'], - ['jump VZONE_smoketest-local_IN'], - ['jump VZONE_smoketest-local_OUT'], - ['iifname "eth0"', 'jump NAME_smoketest'], - ['oifname "eth0"', 'jump NAME_smoketest'] + ['flowtable VYOS_FLOWTABLE_software'], + ['hook ingress priority filter - 1'], + ['devices = { eth0 }'], + ['flow add @VYOS_FLOWTABLE_software'], ] + self.verify_nftables(nftables_search, 'inet vyos_offload') - nftables_output = cmd('sudo nft list table ip vyos_filter') - - for search in nftables_search: - matched = False - for line in nftables_output.split("\n"): - if all(item in line for item in search): - matched = True - break - self.assertTrue(matched) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_ha_virtual_server.py b/smoketest/scripts/cli/test_ha_virtual_server.py index e3a91283e..51ccfa4df 100755 --- a/smoketest/scripts/cli/test_ha_virtual_server.py +++ b/smoketest/scripts/cli/test_ha_virtual_server.py @@ -20,9 +20,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig.vrrp import VRRP -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file from vyos.template import inc_ip PROCESS_NAME = 'keepalived' @@ -47,6 +47,7 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): delay = '10' method = 'nat' persistence_timeout = '600' + vs = 'serv-one' vip = '203.0.113.111' vport = '2222' rservers = ['192.0.2.21', '192.0.2.22', '192.0.2.23'] @@ -56,21 +57,23 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): vserver_base = base_path + ['virtual-server'] - self.cli_set(vserver_base + [vip, 'algorithm', algo]) - self.cli_set(vserver_base + [vip, 'delay-loop', delay]) - self.cli_set(vserver_base + [vip, 'forward-method', method]) - self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vip, 'port', vport]) - self.cli_set(vserver_base + [vip, 'protocol', proto]) + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) for rs in rservers: - self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) # commit changes self.cli_commit() config = read_file(KEEPALIVED_CONF) + self.assertIn(f'virtual_server {vip} {vport}', config) self.assertIn(f'delay_loop {delay}', config) self.assertIn(f'lb_algo lc', config) self.assertIn(f'lb_kind {method.upper()}', config) @@ -86,6 +89,7 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): delay = '15' method = 'nat' persistence_timeout = '300' + vs = 'serv-two' vip = '203.0.113.222' vport = '22322' rservers = ['192.0.2.11', '192.0.2.12'] @@ -107,15 +111,16 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): self.cli_set(vrrp_base + [group, 'vrid', vrid]) # Virtual-server config - self.cli_set(vserver_base + [vip, 'algorithm', algo]) - self.cli_set(vserver_base + [vip, 'delay-loop', delay]) - self.cli_set(vserver_base + [vip, 'forward-method', method]) - self.cli_set(vserver_base + [vip, 'persistence-timeout', persistence_timeout]) - self.cli_set(vserver_base + [vip, 'port', vport]) - self.cli_set(vserver_base + [vip, 'protocol', proto]) + self.cli_set(vserver_base + [vs, 'address', vip]) + self.cli_set(vserver_base + [vs, 'algorithm', algo]) + self.cli_set(vserver_base + [vs, 'delay-loop', delay]) + self.cli_set(vserver_base + [vs, 'forward-method', method]) + self.cli_set(vserver_base + [vs, 'persistence-timeout', persistence_timeout]) + self.cli_set(vserver_base + [vs, 'port', vport]) + self.cli_set(vserver_base + [vs, 'protocol', proto]) for rs in rservers: - self.cli_set(vserver_base + [vip, 'real-server', rs, 'connection-timeout', connection_timeout]) - self.cli_set(vserver_base + [vip, 'real-server', rs, 'port', rport]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'connection-timeout', connection_timeout]) + self.cli_set(vserver_base + [vs, 'real-server', rs, 'port', rport]) # commit changes self.cli_commit() @@ -131,6 +136,7 @@ class TestHAVirtualServer(VyOSUnitTestSHIM.TestCase): self.assertIn(f'preempt_delay 0', config) # default value # Keepalived virtual-server + self.assertIn(f'virtual_server {vip} {vport}', config) self.assertIn(f'delay_loop {delay}', config) self.assertIn(f'lb_algo lc', config) self.assertIn(f'lb_kind {method.upper()}', config) diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py index 68905e447..98259d830 100755 --- a/smoketest/scripts/cli/test_ha_vrrp.py +++ b/smoketest/scripts/cli/test_ha_vrrp.py @@ -20,9 +20,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig.vrrp import VRRP -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file from vyos.template import inc_ip PROCESS_NAME = 'keepalived' @@ -87,11 +87,22 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): advertise_interval = '77' priority = '123' preempt_delay = '400' + startup_delay = '120' + garp_master_delay = '2' + garp_master_repeat = '3' + garp_master_refresh = '4' + garp_master_refresh_repeat = '5' + garp_interval = '1.5' + group_garp_master_delay = '12' + group_garp_master_repeat = '13' + group_garp_master_refresh = '14' + vrrp_version = '3' for group in groups: vlan_id = group.lstrip('VLAN') vip = f'100.64.{vlan_id}.1/24' group_base = base_path + ['vrrp', 'group', group] + global_param_base = base_path + ['vrrp', 'global-parameters'] self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', inc_ip(vip, 1) + '/' + vip.split('/')[-1]]) @@ -110,9 +121,34 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.cli_set(group_base + ['authentication', 'type', 'plaintext-password']) self.cli_set(group_base + ['authentication', 'password', f'{group}']) + # GARP + self.cli_set(group_base + ['garp', 'master-delay', group_garp_master_delay]) + self.cli_set(group_base + ['garp', 'master-repeat', group_garp_master_repeat]) + self.cli_set(group_base + ['garp', 'master-refresh', group_garp_master_refresh]) + + # Global parameters + #config = getConfig(f'global_defs') + self.cli_set(global_param_base + ['startup-delay', f'{startup_delay}']) + self.cli_set(global_param_base + ['garp', 'interval', f'{garp_interval}']) + self.cli_set(global_param_base + ['garp', 'master-delay', f'{garp_master_delay}']) + self.cli_set(global_param_base + ['garp', 'master-repeat', f'{garp_master_repeat}']) + self.cli_set(global_param_base + ['garp', 'master-refresh', f'{garp_master_refresh}']) + self.cli_set(global_param_base + ['garp', 'master-refresh-repeat', f'{garp_master_refresh_repeat}']) + self.cli_set(global_param_base + ['version', vrrp_version]) + # commit changes self.cli_commit() + # Check Global parameters + config = getConfig(f'global_defs') + self.assertIn(f'vrrp_startup_delay {startup_delay}', config) + self.assertIn(f'vrrp_garp_interval {garp_interval}', config) + self.assertIn(f'vrrp_garp_master_delay {garp_master_delay}', config) + self.assertIn(f'vrrp_garp_master_repeat {garp_master_repeat}', config) + self.assertIn(f'vrrp_garp_master_refresh {garp_master_refresh}', config) + self.assertIn(f'vrrp_garp_master_refresh_repeat {garp_master_refresh_repeat}', config) + self.assertIn(f'vrrp_version {vrrp_version}', config) + for group in groups: vlan_id = group.lstrip('VLAN') vip = f'100.64.{vlan_id}.1/24' @@ -132,6 +168,11 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'auth_pass "{group}"', config) self.assertIn(f'auth_type PASS', config) + #GARP + self.assertIn(f'garp_master_delay {group_garp_master_delay}', config) + self.assertIn(f'garp_master_refresh {group_garp_master_refresh}', config) + self.assertIn(f'garp_master_repeat {group_garp_master_repeat}', config) + def test_03_sync_group(self): sync_group = 'VyOS' diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index cd3995ed9..8867cb427 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -22,20 +22,12 @@ from base_interfaces_test import BasicInterfaceTest from vyos.ifconfig import Section from vyos.ifconfig.interface import Interface from vyos.configsession import ConfigSessionError -from vyos.util import get_interface_config -from vyos.util import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.file import read_file class BondingInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_dhcp = True - cls._test_ip = True - cls._test_ipv6 = True - cls._test_ipv6_pd = True - cls._test_ipv6_dhcpc6 = True - cls._test_mtu = True - cls._test_vlan = True - cls._test_qinq = True cls._base_path = ['interfaces', 'bonding'] cls._mirror_interfaces = ['dum21354'] cls._members = [] @@ -45,9 +37,8 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): if 'TEST_ETH' in os.environ: cls._members = os.environ['TEST_ETH'].split() else: - for tmp in Section.interfaces('ethernet'): - if not '.' in tmp: - cls._members.append(tmp) + for tmp in Section.interfaces('ethernet', vlan=False): + cls._members.append(tmp) cls._options = {'bond0' : []} for member in cls._members: diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 6d7af78eb..cdff49f4b 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -24,20 +24,14 @@ from glob import glob from netifaces import interfaces from vyos.ifconfig import Section -from vyos.util import cmd -from vyos.util import read_file -from vyos.util import get_interface_config -from vyos.validate import is_intf_addr_assigned +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.network import is_intf_addr_assigned class BridgeInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_dhcp = True - cls._test_ip = True - cls._test_ipv6 = True - cls._test_ipv6_pd = True - cls._test_ipv6_dhcpc6 = True - cls._test_vlan = True cls._base_path = ['interfaces', 'bridge'] cls._mirror_interfaces = ['dum21354'] cls._members = [] @@ -47,9 +41,8 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): if 'TEST_ETH' in os.environ: cls._members = os.environ['TEST_ETH'].split() else: - for tmp in Section.interfaces('ethernet'): - if not '.' in tmp: - cls._members.append(tmp) + for tmp in Section.interfaces('ethernet', vlan=False): + cls._members.append(tmp) cls._options['br0'] = [] for member in cls._members: diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index ed611062a..a39b81348 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -28,10 +28,10 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.pki import CERT_BEGIN from vyos.template import is_ipv6 -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file -from vyos.validate import is_ipv6_link_local +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.network import is_ipv6_link_local server_ca_root_cert_data = """ MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw @@ -109,14 +109,6 @@ def get_certificate_count(interface, cert_type): class EthernetInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_dhcp = True - cls._test_ip = True - cls._test_ipv6 = True - cls._test_ipv6_pd = True - cls._test_ipv6_dhcpc6 = True - cls._test_mtu = True - cls._test_vlan = True - cls._test_qinq = True cls._base_path = ['interfaces', 'ethernet'] cls._mirror_interfaces = ['dum21354'] @@ -160,7 +152,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): self.assertFalse(is_intf_addr_assigned(intf, addr['addr'])) def test_offloading_rps(self): - # enable RPS on all available CPUs, RPS works woth a CPU bitmask, + # enable RPS on all available CPUs, RPS works with a CPU bitmask, # where each bit represents a CPU (core/thread). The formula below # expands to rps_cpus = 255 for a 8 core system rps_cpus = (1 << os.cpu_count()) -1 @@ -258,10 +250,19 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): for interface in self._interfaces: # Enable EAPoL self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate']) + self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name]) self.cli_commit() + # Test multiple CA chains + self.assertEqual(get_certificate_count(interface, 'ca'), 4) + + for interface in self._interfaces: + self.cli_delete(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate']) + + self.cli_commit() + # Check for running process self.assertTrue(process_named_running('wpa_supplicant')) diff --git a/smoketest/scripts/cli/test_interfaces_geneve.py b/smoketest/scripts/cli/test_interfaces_geneve.py index 0e5098aa7..5f8fae91e 100755 --- a/smoketest/scripts/cli/test_interfaces_geneve.py +++ b/smoketest/scripts/cli/test_interfaces_geneve.py @@ -17,15 +17,13 @@ import unittest from vyos.ifconfig import Interface -from vyos.util import get_interface_config +from vyos.utils.network import get_interface_config from base_interfaces_test import BasicInterfaceTest class GeneveInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_ip = True - cls._test_ipv6 = True cls._base_path = ['interfaces', 'geneve'] cls._options = { 'gnv0': ['vni 10', 'remote 127.0.1.1'], @@ -45,6 +43,7 @@ class GeneveInterfaceTest(BasicInterfaceTest.TestCase): self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'df', 'set']) self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'tos', tos]) + self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'innerproto']) self.cli_set(self._base_path + [intf, 'parameters', 'ip', 'ttl', str(ttl)]) ttl += 10 @@ -69,6 +68,11 @@ class GeneveInterfaceTest(BasicInterfaceTest.TestCase): label = options['linkinfo']['info_data']['label'] self.assertIn(f'parameters ipv6 flowlabel {label}', self._options[interface]) + if any('innerproto' in s for s in self._options[interface]): + inner = options['linkinfo']['info_data']['innerproto'] + self.assertIn(f'parameters ip {inner}', self._options[interface]) + + self.assertEqual('geneve', options['linkinfo']['info_kind']) self.assertEqual('set', options['linkinfo']['info_data']['df']) self.assertEqual(f'0x{tos}', options['linkinfo']['info_data']['tos']) diff --git a/smoketest/scripts/cli/test_interfaces_input.py b/smoketest/scripts/cli/test_interfaces_input.py new file mode 100755 index 000000000..3ddf86000 --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_input.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 vyos.utils.file import read_file +from vyos.ifconfig import Interface +from base_vyostest_shim import VyOSUnitTestSHIM + +base_path = ['interfaces', 'input'] + +# add a classmethod to setup a temporaray PPPoE server for "proper" validation +class InputInterfaceTest(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(InputInterfaceTest, cls).setUpClass() + cls._interfaces = ['ifb10', 'ifb20', 'ifb30'] + + def tearDown(self): + self.cli_delete(base_path) + self.cli_commit() + + def test_01_description(self): + # Check if PPPoE dialer can be configured and runs + for interface in self._interfaces: + self.cli_set(base_path + [interface, 'description', f'foo-{interface}']) + + # commit changes + self.cli_commit() + + # Validate remove interface description "empty" + for interface in self._interfaces: + tmp = read_file(f'/sys/class/net/{interface}/ifalias') + self.assertEqual(tmp, f'foo-{interface}') + self.assertEqual(Interface(interface).get_alias(), f'foo-{interface}') + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_l2tpv3.py b/smoketest/scripts/cli/test_interfaces_l2tpv3.py index aed8e6f15..af3d49f75 100755 --- a/smoketest/scripts/cli/test_interfaces_l2tpv3.py +++ b/smoketest/scripts/cli/test_interfaces_l2tpv3.py @@ -19,13 +19,11 @@ import json import unittest from base_interfaces_test import BasicInterfaceTest -from vyos.util import cmd +from vyos.utils.process import cmd class L2TPv3InterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_ip = True - cls._test_ipv6 = True cls._base_path = ['interfaces', 'l2tpv3'] cls._options = { 'l2tpeth10': ['source-address 127.0.0.1', 'remote 127.10.10.10', diff --git a/smoketest/scripts/cli/test_interfaces_loopback.py b/smoketest/scripts/cli/test_interfaces_loopback.py index 5ff9c250e..0454dc658 100755 --- a/smoketest/scripts/cli/test_interfaces_loopback.py +++ b/smoketest/scripts/cli/test_interfaces_loopback.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -19,7 +19,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest from netifaces import interfaces -from vyos.validate import is_intf_addr_assigned +from vyos.utils.network import is_intf_addr_assigned loopbacks = ['127.0.0.1', '::1'] @@ -31,6 +31,9 @@ class LoopbackInterfaceTest(BasicInterfaceTest.TestCase): # call base-classes classmethod super(LoopbackInterfaceTest, cls).setUpClass() + # we need to override tearDown() as loopback interfaces are ephemeral and + # will always be present on the system - the base class check will fail as + # the loopback interface will still exist. def tearDown(self): self.cli_delete(self._base_path) self.cli_commit() diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py index f141cc6d3..ea0f00071 100755 --- a/smoketest/scripts/cli/test_interfaces_macsec.py +++ b/smoketest/scripts/cli/test_interfaces_macsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -23,10 +23,10 @@ from netifaces import interfaces from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import cmd -from vyos.util import read_file -from vyos.util import get_interface_config -from vyos.util import process_named_running +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.process import process_named_running PROCESS_NAME = 'wpa_supplicant' @@ -42,9 +42,6 @@ def get_cipher(interface): class MACsecInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_dhcp = True - cls._test_ip = True - cls._test_ipv6 = True cls._base_path = ['interfaces', 'macsec'] cls._options = { 'macsec0': ['source-interface eth0', 'security cipher gcm-aes-128'] } @@ -142,15 +139,9 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): # final commit and verify self.cli_commit() self.assertIn(interface, interfaces()) - self.assertIn(interface, interfaces()) - self.assertEqual(cipher, get_cipher(interface)) - # check that we use the new macsec_csindex option (T4537) - tmp = get_config_value(src_interface, 'macsec_csindex') - self.assertIn("0", tmp) - - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) + # Verify proper cipher suite (T4537) + self.assertEqual(cipher, get_cipher(interface)) def test_macsec_gcm_aes_256(self): src_interface = 'eth0' @@ -171,18 +162,12 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): # final commit and verify self.cli_commit() self.assertIn(interface, interfaces()) - self.assertEqual(cipher, get_cipher(interface)) - # check that we use the new macsec_csindex option (T4537) - tmp = get_config_value(src_interface, 'macsec_csindex') - self.assertIn("1", tmp) - - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) + # Verify proper cipher suite (T4537) + self.assertEqual(cipher, get_cipher(interface)) def test_macsec_source_interface(self): # Ensure source-interface can bot be part of any other bond or bridge - base_bridge = ['interfaces', 'bridge', 'br200'] base_bond = ['interfaces', 'bonding', 'bond200'] @@ -208,9 +193,77 @@ class MACsecInterfaceTest(BasicInterfaceTest.TestCase): self.cli_commit() self.assertIn(interface, interfaces()) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) + def test_macsec_static_keys(self): + src_interface = 'eth0' + interface = 'macsec5' + cipher1 = 'gcm-aes-128' + cipher2 = 'gcm-aes-256' + tx_key_1 = '71a82a48eddfa12c08a19792ca20c4bb' + tx_key_2 = 'dd487b2958e855ea35a5d43a5ecb3dcfbe7889ffcb877770252feb13b734478d' + rx_key_1 = '0022d00f57e75241a230cdf7118dfcc5' + rx_key_2 = 'b7d6d7ad075e02323fdeb845217b884d3f93ff36b2cdaf6b07eeb189b877245f' + peer_mac = '00:11:22:33:44:55' + self.cli_set(self._base_path + [interface]) -if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + # Encrypt link + self.cli_set(self._base_path + [interface, 'security', 'encrypt']) + # check validate() - source interface is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'source-interface', src_interface]) + + # check validate() - cipher is mandatory + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher1]) + + # check validate() - only static or mka config is allowed + self.cli_set(self._base_path + [interface, 'security', 'static']) + self.cli_set(self._base_path + [interface, 'security', 'mka', 'cak', tx_key_1]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'security', 'mka']) + + # check validate() - tx-key required + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - tx-key length must match cipher + self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_1]) + + # check validate() - at least one peer must be defined + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + # check validate() - enabled peer must have both rx-key and MAC defined + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac']) + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_1]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'mac', peer_mac]) + + # check validate() - peer rx-key length must match cipher + self.cli_set(self._base_path + [interface, 'security', 'cipher', cipher2]) + self.cli_set(self._base_path + [interface, 'security', 'static', 'key', tx_key_2]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(self._base_path + [interface, 'security', 'static', 'peer', 'TESTPEER', 'key', rx_key_2]) + + # final commit and verify + self.cli_commit() + self.assertIn(interface, interfaces()) + self.assertEqual(cipher2, get_cipher(interface)) + self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}')) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py index b2143d16e..d1ece84d6 100755 --- a/smoketest/scripts/cli/test_interfaces_openvpn.py +++ b/smoketest/scripts/cli/test_interfaces_openvpn.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -24,9 +24,9 @@ from netifaces import interfaces from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file from vyos.template import address_from_cidr from vyos.template import dec_ip from vyos.template import inc_ip @@ -337,10 +337,6 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): self.cli_commit() self.cli_delete(path + ['protocol']) - - # check validate() - must specify "tls dh-params" when "tls role" is "passive" - with self.assertRaises(ConfigSessionError): - self.cli_commit() self.cli_set(path + ['tls', 'dh-params', 'ovpn_test']) self.cli_commit() @@ -368,6 +364,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): self.cli_set(path + ['hash', auth_hash]) self.cli_set(path + ['mode', 'server']) self.cli_set(path + ['local-port', port]) + self.cli_set(path + ['server', 'mfa', 'totp']) self.cli_set(path + ['server', 'subnet', subnet]) self.cli_set(path + ['server', 'topology', 'subnet']) self.cli_set(path + ['keep-alive', 'failure-count', '5']) @@ -388,6 +385,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): for ii in num_range: interface = f'vtun{ii}' + plugin = f'plugin "/usr/lib/openvpn/openvpn-otp.so" "otp_secrets=/config/auth/openvpn/{interface}-otp-secrets otp_slop=180 totp_t0=0 totp_step=30 totp_digits=6 password_is_cr=1"' subnet = f'192.0.{ii}.0/24' start_addr = inc_ip(subnet, '2') @@ -411,6 +409,7 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase): self.assertIn(f'topology subnet', config) self.assertIn(f'lport {port}', config) self.assertIn(f'push "redirect-gateway def1"', config) + self.assertIn(f'{plugin}', config) self.assertIn(f'keepalive 5 25', config) # TLS options diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py index 8927121a8..0ce5e2fe0 100755 --- a/smoketest/scripts/cli/test_interfaces_pppoe.py +++ b/smoketest/scripts/cli/test_interfaces_pppoe.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 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 @@ -14,7 +14,6 @@ # 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 re import unittest from psutil import process_iter @@ -57,11 +56,11 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): def test_01_pppoe_client(self): # Check if PPPoE dialer can be configured and runs for interface in self._interfaces: - user = 'VyOS-user-' + interface - passwd = 'VyOS-passwd-' + interface + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' mtu = '1400' - self.cli_set(base_path + [interface, 'authentication', 'user', user]) + self.cli_set(base_path + [interface, 'authentication', 'username', user]) self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) self.cli_set(base_path + [interface, 'mtu', mtu]) self.cli_set(base_path + [interface, 'no-peer-dns']) @@ -76,23 +75,26 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): # verify configuration file(s) for interface in self._interfaces: - user = 'VyOS-user-' + interface - password = 'VyOS-passwd-' + interface + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' tmp = get_config_value(interface, 'mtu')[1] self.assertEqual(tmp, mtu) tmp = get_config_value(interface, 'user')[1].replace('"', '') self.assertEqual(tmp, user) tmp = get_config_value(interface, 'password')[1].replace('"', '') - self.assertEqual(tmp, password) + self.assertEqual(tmp, passwd) tmp = get_config_value(interface, 'ifname')[1] self.assertEqual(tmp, interface) def test_02_pppoe_client_disabled_interface(self): # Check if PPPoE Client can be disabled for interface in self._interfaces: - self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) - self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) self.cli_set(base_path + [interface, 'disable']) @@ -117,7 +119,10 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): def test_03_pppoe_authentication(self): # When username or password is set - so must be the other for interface in self._interfaces: - self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf']) @@ -125,7 +130,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): with self.assertRaises(ConfigSessionError): self.cli_commit() - self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) self.cli_commit() @@ -136,8 +141,11 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): sla_len = '8' for interface in self._interfaces: - self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos']) - self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos']) + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) self.cli_set(base_path + [interface, 'no-default-route']) self.cli_set(base_path + [interface, 'no-peer-dns']) self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) @@ -149,18 +157,54 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'address', address]) self.cli_set(dhcpv6_pd_base + ['interface', self._source_interface, 'sla-id', sla_id]) - # commit changes - self.cli_commit() + # commit changes + self.cli_commit() + + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' # verify "normal" PPPoE value - 1492 is default MTU tmp = get_config_value(interface, 'mtu')[1] self.assertEqual(tmp, '1492') tmp = get_config_value(interface, 'user')[1].replace('"', '') - self.assertEqual(tmp, 'vyos') + self.assertEqual(tmp, user) tmp = get_config_value(interface, 'password')[1].replace('"', '') - self.assertEqual(tmp, 'vyos') + self.assertEqual(tmp, passwd) tmp = get_config_value(interface, '+ipv6 ipv6cp-use-ipaddr') self.assertListEqual(tmp, ['+ipv6', 'ipv6cp-use-ipaddr']) + def test_05_pppoe_options(self): + # Check if PPPoE dialer can be configured with DHCPv6-PD + for interface in self._interfaces: + user = f'VyOS-user-{interface}' + passwd = f'VyOS-passwd-{interface}' + ac_name = f'AC{interface}' + service_name = f'SRV{interface}' + host_uniq = 'cafebeefBABE123456' + + self.cli_set(base_path + [interface, 'authentication', 'username', user]) + self.cli_set(base_path + [interface, 'authentication', 'password', passwd]) + self.cli_set(base_path + [interface, 'source-interface', self._source_interface]) + + self.cli_set(base_path + [interface, 'access-concentrator', ac_name]) + self.cli_set(base_path + [interface, 'service-name', service_name]) + self.cli_set(base_path + [interface, 'host-uniq', host_uniq]) + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + ac_name = f'AC{interface}' + service_name = f'SRV{interface}' + host_uniq = 'cafebeefBABE123456' + + tmp = get_config_value(interface, 'pppoe-ac')[1] + self.assertEqual(tmp, f'"{ac_name}"') + tmp = get_config_value(interface, 'pppoe-service')[1] + self.assertEqual(tmp, f'"{service_name}"') + tmp = get_config_value(interface, 'pppoe-host-uniq')[1] + self.assertEqual(tmp, f'"{host_uniq}"') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py index a51b8d52c..0d6f5bc1f 100755 --- a/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_pseudo_ethernet.py @@ -23,14 +23,6 @@ from base_interfaces_test import BasicInterfaceTest class PEthInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_dhcp = True - cls._test_ip = True - cls._test_ipv6 = True - cls._test_ipv6_pd = True - cls._test_ipv6_dhcpc6 = True - cls._test_mtu = True - cls._test_vlan = True - cls._test_qinq = True cls._base_path = ['interfaces', 'pseudo-ethernet'] cls._options = {} diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py index 44bfbb5f0..2a7a519fd 100755 --- a/smoketest/scripts/cli/test_interfaces_tunnel.py +++ b/smoketest/scripts/cli/test_interfaces_tunnel.py @@ -19,7 +19,7 @@ import unittest from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError -from vyos.util import get_interface_config +from vyos.utils.network import get_interface_config from vyos.template import inc_ip remote_ip4 = '192.0.2.100' @@ -30,9 +30,6 @@ mtu = 1476 class TunnelInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_ip = True - cls._test_ipv6 = True - cls._test_mtu = True cls._base_path = ['interfaces', 'tunnel'] cls.local_v4 = '192.0.2.1' cls.local_v6 = '2001:db8::1' diff --git a/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py new file mode 100755 index 000000000..7874589ca --- /dev/null +++ b/smoketest/scripts/cli/test_interfaces_virtual_ethernet.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 netifaces import interfaces + +from vyos.ifconfig import Section +from vyos.utils.process import process_named_running +from base_interfaces_test import BasicInterfaceTest + +class VEthInterfaceTest(BasicInterfaceTest.TestCase): + @classmethod + def setUpClass(cls): + cls._base_path = ['interfaces', 'virtual-ethernet'] + cls._options = { + 'veth0': ['peer-name veth1'], + 'veth1': ['peer-name veth0'], + } + + cls._interfaces = list(cls._options) + # call base-classes classmethod + super(VEthInterfaceTest, cls).setUpClass() + + def test_vif_8021q_mtu_limits(self): + self.skipTest('not supported') + + # As we always need a pair of veth interfaces, we can not rely on the base + # class check to determine if there is a dhcp6c or dhclient instance running. + # This test will always fail as there is an instance running on the peer + # interface. + def tearDown(self): + self.cli_delete(self._base_path) + self.cli_commit() + + # Verify that no previously interface remained on the system + for intf in self._interfaces: + self.assertNotIn(intf, interfaces()) + + @classmethod + def tearDownClass(cls): + # No daemon started during tests should remain running + for daemon in ['dhcp6c', 'dhclient']: + cls.assertFalse(cls, process_named_running(daemon)) + + super(VEthInterfaceTest, cls).tearDownClass() + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_vti.py b/smoketest/scripts/cli/test_interfaces_vti.py index 9cbf104f0..7f13575a3 100755 --- a/smoketest/scripts/cli/test_interfaces_vti.py +++ b/smoketest/scripts/cli/test_interfaces_vti.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2023 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 @@ -21,9 +21,6 @@ from base_interfaces_test import BasicInterfaceTest class VTIInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_ip = True - cls._test_ipv6 = True - cls._test_mtu = True cls._base_path = ['interfaces', 'vti'] cls._interfaces = ['vti10', 'vti20', 'vti30'] diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py index 058f13721..e9c9e68fd 100755 --- a/smoketest/scripts/cli/test_interfaces_vxlan.py +++ b/smoketest/scripts/cli/test_interfaces_vxlan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -18,17 +18,16 @@ import unittest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface -from vyos.util import get_bridge_fdb -from vyos.util import get_interface_config +from vyos.utils.network import get_bridge_fdb +from vyos.utils.network import get_interface_config +from vyos.utils.network import interface_exists +from vyos.utils.network import get_vxlan_vlan_tunnels from vyos.template import is_ipv6 from base_interfaces_test import BasicInterfaceTest class VXLANInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_ip = True - cls._test_ipv6 = True - cls._test_mtu = True cls._base_path = ['interfaces', 'vxlan'] cls._options = { 'vxlan10': ['vni 10', 'remote 127.0.0.2'], @@ -136,5 +135,53 @@ class VXLANInterfaceTest(BasicInterfaceTest.TestCase): self.assertTrue(options['linkinfo']['info_data']['external']) self.assertEqual('vxlan', options['linkinfo']['info_kind']) + def test_vxlan_vlan_vni_mapping(self): + bridge = 'br0' + interface = 'vxlan0' + source_interface = 'eth0' + + vlan_to_vni = { + '10': '10010', + '11': '10011', + '12': '10012', + '13': '10013', + '20': '10020', + '30': '10030', + '31': '10031', + } + + self.cli_set(self._base_path + [interface, 'external']) + self.cli_set(self._base_path + [interface, 'source-interface', source_interface]) + + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # This must fail as this VXLAN interface is not associated with any bridge + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['interfaces', 'bridge', bridge, 'member', 'interface', interface]) + + # It is not allowed to use duplicate VNIs + self.cli_set(self._base_path + [interface, 'vlan-to-vni', '11', 'vni', vlan_to_vni['10']]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + # restore VLAN - VNI mappings + for vlan, vni in vlan_to_vni.items(): + self.cli_set(self._base_path + [interface, 'vlan-to-vni', vlan, 'vni', vni]) + + # commit configuration + self.cli_commit() + + self.assertTrue(interface_exists(bridge)) + self.assertTrue(interface_exists(interface)) + + tmp = get_interface_config(interface) + self.assertEqual(tmp['master'], bridge) + + tmp = get_vxlan_vlan_tunnels('vxlan0') + self.assertEqual(tmp, list(vlan_to_vni)) + + self.cli_delete(['interfaces', 'bridge', bridge]) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py index 14fc8d109..48c7cb6a1 100755 --- a/smoketest/scripts/cli/test_interfaces_wireguard.py +++ b/smoketest/scripts/cli/test_interfaces_wireguard.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -19,6 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file base_path = ['interfaces', 'wireguard'] @@ -35,7 +36,7 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - def test_wireguard_peer(self): + def test_01_wireguard_peer(self): # Create WireGuard interfaces with associated peers for intf in self._interfaces: peer = 'foo-' + intf @@ -62,7 +63,7 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase): self.assertTrue(os.path.isdir(f'/sys/class/net/{intf}')) - def test_wireguard_add_remove_peer(self): + def test_02_wireguard_add_remove_peer(self): # T2939: Create WireGuard interfaces with associated peers. # Remove one of the configured peers. # T4774: Test prevention of duplicate peer public keys @@ -100,5 +101,56 @@ class WireGuardInterfaceTest(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path + [interface, 'peer', 'PEER01']) self.cli_commit() + def test_03_wireguard_same_public_key(self): + # T5413: Test prevention of equality interface public key and peer's + # public key + interface = 'wg0' + port = '12345' + privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ=' + pubkey_fail = 'eiVeYKq66mqKLbrZLzlckSP9voaw8jSFyVNiNTdZDjU=' + pubkey_ok = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I=' + + self.cli_set(base_path + [interface, 'address', '172.16.0.1/24']) + self.cli_set(base_path + [interface, 'private-key', privkey]) + + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey_fail]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1']) + + # The same pubkey as the interface wg0 + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey_ok]) + + # Commit peers + self.cli_commit() + + self.assertTrue(os.path.isdir(f'/sys/class/net/{interface}')) + + def test_04_wireguard_threaded(self): + # T5409: Test adding threaded option on interface. + # Test prevention for adding threaded + # if no enabled peer is configured. + interface = 'wg0' + port = '12345' + privkey = 'OOjcXGfgQlAuM6q8Z9aAYduCua7pxf7UKYvIqoUPoGQ=' + pubkey = 'ebFx/1G0ti8tvuZd94sEIosAZZIznX+dBAKG/8DFm0I=' + + self.cli_set(base_path + [interface, 'address', '172.16.0.1/24']) + self.cli_set(base_path + [interface, 'private-key', privkey]) + + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'port', port]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'public-key', pubkey]) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'allowed-ips', '10.205.212.10/32']) + self.cli_set(base_path + [interface, 'peer', 'PEER01', 'address', '192.0.2.1']) + self.cli_set(base_path + [interface, 'per-client-thread']) + + # Commit peers + self.cli_commit() + tmp = read_file(f'/sys/class/net/{interface}/threaded') + self.assertTrue(tmp, "1") + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py index a24f37d8d..95246a7b9 100755 --- a/smoketest/scripts/cli/test_interfaces_wireless.py +++ b/smoketest/scripts/cli/test_interfaces_wireless.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2021 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -22,9 +22,9 @@ from base_interfaces_test import BasicInterfaceTest from glob import glob from vyos.configsession import ConfigSessionError -from vyos.util import process_named_running -from vyos.util import check_kmod -from vyos.util import read_file +from vyos.utils.process import process_named_running +from vyos.utils.kernel import check_kmod +from vyos.utils.file import read_file def get_config_value(interface, key): tmp = read_file(f'/run/hostapd/{interface}.conf') @@ -34,7 +34,6 @@ def get_config_value(interface, key): class WirelessInterfaceTest(BasicInterfaceTest.TestCase): @classmethod def setUpClass(cls): - cls._test_ip = True cls._base_path = ['interfaces', 'wireless'] cls._options = { 'wlan0': ['physical-device phy0', 'ssid VyOS-WIFI-0', @@ -50,6 +49,10 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): # call base-classes classmethod super(WirelessInterfaceTest, cls).setUpClass() + # T5245 - currently testcases are disabled + cls._test_ipv6 = False + cls._test_vlan = False + def test_wireless_add_single_ip_address(self): # derived method to check if member interfaces are enslaved properly super().test_add_single_ip_address() @@ -94,6 +97,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): vht_opt = { # VyOS CLI option hostapd - ht_capab setting + 'channel-set-width 3' : '[VHT160-80PLUS80]', 'stbc tx' : '[TX-STBC-2BY1]', 'stbc rx 12' : '[RX-STBC-12]', 'ldpc' : '[RXLDPC]', @@ -101,7 +105,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): 'vht-cf' : '[HTC-VHT]', 'antenna-pattern-fixed' : '[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]', 'max-mpdu 11454' : '[MAX-MPDU-11454]', - 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2][VHT160]', + 'max-mpdu-exp 2' : '[MAX-A-MPDU-LEN-EXP-2]', 'link-adaptation both' : '[VHT-LINK-ADAPT3]', 'short-gi 80' : '[SHORT-GI-80]', 'short-gi 160' : '[SHORT-GI-160]', @@ -231,9 +235,51 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase): self.assertIn(interface, bridge_members) self.cli_delete(bridge_path) - self.cli_delete(self._base_path) + + def test_wireless_security_station_address(self): + interface = 'wlan0' + ssid = 'VyOS-ACL' + + hostapd_accept_station_conf = f'/run/hostapd/{interface}_station_accept.conf' + hostapd_deny_station_conf = f'/run/hostapd/{interface}_station_deny.conf' + + accept_mac = ['00:00:00:00:ac:01', '00:00:00:00:ac:02', '00:00:00:00:ac:03', '00:00:00:00:ac:04'] + deny_mac = ['00:00:00:00:de:01', '00:00:00:00:de:02', '00:00:00:00:de:03', '00:00:00:00:de:04'] + + self.cli_set(self._base_path + [interface, 'ssid', ssid]) + self.cli_set(self._base_path + [interface, 'country-code', 'se']) + self.cli_set(self._base_path + [interface, 'type', 'access-point']) + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'accept']) + + for mac in accept_mac: + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'accept', 'mac', mac]) + for mac in deny_mac: + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'deny', 'mac', mac]) + self.cli_commit() + # in accept mode all addresses are allowed unless specified in the deny list + tmp = get_config_value(interface, 'macaddr_acl') + self.assertEqual(tmp, '0') + + accept_list = read_file(hostapd_accept_station_conf) + for mac in accept_mac: + self.assertIn(mac, accept_list) + + deny_list = read_file(hostapd_deny_station_conf) + for mac in deny_mac: + self.assertIn(mac, deny_list) + + # Switch mode accept -> deny + self.cli_set(self._base_path + [interface, 'security', 'station-address', 'mode', 'deny']) + self.cli_commit() + # In deny mode all addresses are denied unless specified in the allow list + tmp = get_config_value(interface, 'macaddr_acl') + self.assertEqual(tmp, '1') + + # Check for running process + self.assertTrue(process_named_running('hostapd')) + if __name__ == '__main__': check_kmod('mac80211_hwsim') unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py new file mode 100755 index 000000000..a33fd5c18 --- /dev/null +++ b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'haproxy' +HAPROXY_CONF = '/run/haproxy/haproxy.cfg' +base_path = ['load-balancing', 'reverse-proxy'] +proxy_interface = 'eth1' + + +class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address']) + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_lb_reverse_proxy_domain(self): + domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com'] + domain_bk_second = 'n5.example.com' + frontend = 'https_front' + front_port = '4433' + bk_server_first = '192.0.2.11' + bk_server_second = '192.0.2.12' + bk_first_name = 'bk-01' + bk_second_name = 'bk-02' + bk_server_port = '9090' + mode = 'http' + rule_ten = '10' + rule_twenty = '20' + send_proxy = 'send-proxy' + max_connections = '1000' + + back_base = base_path + ['backend'] + + self.cli_set(base_path + ['service', frontend, 'mode', mode]) + self.cli_set(base_path + ['service', frontend, 'port', front_port]) + for domain in domains_bk_first: + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second]) + self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name]) + + self.cli_set(back_base + [bk_first_name, 'mode', mode]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port]) + self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy]) + + self.cli_set(back_base + [bk_second_name, 'mode', mode]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second]) + self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port]) + + self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections]) + + # commit changes + self.cli_commit() + + config = read_file(HAPROXY_CONF) + + # Global + self.assertIn(f'maxconn {max_connections}', config) + + # Frontend + self.assertIn(f'frontend {frontend}', config) + self.assertIn(f'bind :::{front_port} v4v6', config) + self.assertIn(f'mode {mode}', config) + for domain in domains_bk_first: + self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config) + self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config) + self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config) + self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config) + + # Backend + self.assertIn(f'backend {bk_first_name}', config) + self.assertIn(f'balance roundrobin', config) + self.assertIn(f'option forwardfor', config) + self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config) + + self.assertIn(f'backend {bk_second_name}', config) + self.assertIn(f'mode {mode}', config) + self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_load_balancning_wan.py b/smoketest/scripts/cli/test_load_balancing_wan.py index 23020b9b1..47ca19b27 100755 --- a/smoketest/scripts/cli/test_load_balancning_wan.py +++ b/smoketest/scripts/cli/test_load_balancing_wan.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2023 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 @@ -21,8 +21,8 @@ import time from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import call -from vyos.util import cmd +from vyos.utils.process import call +from vyos.utils.process import cmd base_path = ['load-balancing'] @@ -46,7 +46,6 @@ def cmd_in_netns(netns, cmd): def delete_netns(name): return call(f'sudo ip netns del {name}') - class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -61,7 +60,6 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_table_routes(self): - ns1 = 'ns201' ns2 = 'ns202' ns3 = 'ns203' @@ -79,6 +77,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): create_veth_pair(iface1, container_iface1) create_veth_pair(iface2, container_iface2) create_veth_pair(iface3, container_iface3) + move_interface_to_netns(container_iface1, ns1) move_interface_to_netns(container_iface2, ns2) move_interface_to_netns(container_iface3, ns3) @@ -125,11 +124,12 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, original) # Delete veth interfaces and netns - for iface in [iface1, iface2]: + for iface in [iface1, iface2, iface3]: call(f'sudo ip link del dev {iface}') delete_netns(ns1) delete_netns(ns2) + delete_netns(ns3) def test_check_chains(self): @@ -144,15 +144,15 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): container_iface3 = 'ceth2' mangle_isp1 = """table ip mangle { chain ISP_veth1 { - counter ct mark set 0xc9 - counter meta mark set 0xc9 + counter ct mark set 0xc9 + counter meta mark set 0xc9 counter accept } }""" mangle_isp2 = """table ip mangle { chain ISP_veth2 { - counter ct mark set 0xca - counter meta mark set 0xca + counter ct mark set 0xca + counter meta mark set 0xca counter accept } }""" @@ -164,7 +164,7 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): }""" mangle_wanloadbalance_pre = """table ip mangle { chain WANLOADBALANCE_PRE { - iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth1 + iifname "veth3" ip saddr 198.51.100.0/24 ct state new meta random & 2147483647 < 1073741824 counter jump ISP_veth1 iifname "veth3" ip saddr 198.51.100.0/24 ct state new counter jump ISP_veth2 iifname "veth3" ip saddr 198.51.100.0/24 counter meta mark set ct mark } @@ -179,7 +179,6 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): chain VYOS_PRE_SNAT_HOOK { type nat hook postrouting priority srcnat - 1; policy accept; counter jump WANLOADBALANCE - return } }""" @@ -196,9 +195,10 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): call(f'sudo ip address add 203.0.113.10/24 dev {iface1}') call(f'sudo ip address add 192.0.2.10/24 dev {iface2}') call(f'sudo ip address add 198.51.100.10/24 dev {iface3}') - call(f'sudo ip link set dev {iface1} up') - call(f'sudo ip link set dev {iface2} up') - call(f'sudo ip link set dev {iface3} up') + + for iface in [iface1, iface2, iface3]: + call(f'sudo ip link set dev {iface} up') + cmd_in_netns(ns1, f'ip link set {container_iface1} name eth0') cmd_in_netns(ns2, f'ip link set {container_iface2} name eth0') cmd_in_netns(ns3, f'ip link set {container_iface3} name eth0') @@ -247,11 +247,12 @@ class TestLoadBalancingWan(VyOSUnitTestSHIM.TestCase): self.assertEqual(tmp, nat_vyos_pre_snat_hook) # Delete veth interfaces and netns - for iface in [iface1, iface2]: + for iface in [iface1, iface2, iface3]: call(f'sudo ip link del dev {iface}') delete_netns(ns1) delete_netns(ns2) + delete_netns(ns3) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py index 2ae90fcaf..31dfcef87 100755 --- a/smoketest/scripts/cli/test_nat.py +++ b/smoketest/scripts/cli/test_nat.py @@ -21,8 +21,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import dict_search +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search base_path = ['nat'] src_path = base_path + ['source'] @@ -58,6 +58,17 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): break self.assertTrue(not matched if inverse else matched, msg=search) + def wait_for_domain_resolver(self, table, set_name, element, max_wait=10): + # Resolver no longer blocks commit, need to wait for daemon to populate set + count = 0 + while count < max_wait: + code = run(f'sudo nft get element {table} {set_name} {{ {element} }}') + if code == 0: + return True + count += 1 + sleep(1) + return False + def test_snat(self): rules = ['100', '110', '120', '130', '200', '210', '220', '230'] outbound_iface_100 = 'eth0' @@ -84,6 +95,30 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_nat') + def test_snat_groups(self): + address_group = 'smoketest_addr' + address_group_member = '192.0.2.1' + rule = '100' + outbound_iface = 'eth0' + + self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member]) + + self.cli_set(src_path + ['rule', rule, 'source', 'group', 'address-group', address_group]) + self.cli_set(src_path + ['rule', rule, 'outbound-interface', outbound_iface]) + self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade']) + + self.cli_commit() + + nftables_search = [ + [f'set A_{address_group}'], + [f'elements = {{ {address_group_member} }}'], + [f'ip saddr @A_{address_group}', f'oifname "{outbound_iface}"', 'masquerade'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + self.cli_delete(['firewall']) + def test_dnat(self): rules = ['100', '110', '120', '130', '200', '210', '220', '230'] inbound_iface_100 = 'eth0' @@ -159,12 +194,13 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'eth1']) self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443']) self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) + self.cli_set(dst_path + ['rule', '1', 'packet-type', 'host']) self.cli_set(dst_path + ['rule', '1', 'translation', 'port', '443']) self.cli_commit() nftables_search = [ - ['iifname "eth1"', 'tcp dport 443', 'dnat to :443'] + ['iifname "eth1"', 'tcp dport 443', 'pkttype host', 'dnat to :443'] ] self.verify_nftables(nftables_search, 'ip vyos_nat') @@ -195,5 +231,69 @@ class TestNAT(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_static_nat') + def test_dnat_redirect(self): + dst_addr_1 = '10.0.1.1' + dest_port = '5122' + protocol = 'tcp' + redirected_port = '22' + ifname = 'eth0' + + self.cli_set(dst_path + ['rule', '10', 'destination', 'address', dst_addr_1]) + self.cli_set(dst_path + ['rule', '10', 'destination', 'port', dest_port]) + self.cli_set(dst_path + ['rule', '10', 'protocol', protocol]) + self.cli_set(dst_path + ['rule', '10', 'inbound-interface', ifname]) + self.cli_set(dst_path + ['rule', '10', 'translation', 'redirect', 'port', redirected_port]) + + self.cli_set(dst_path + ['rule', '20', 'destination', 'address', dst_addr_1]) + self.cli_set(dst_path + ['rule', '20', 'destination', 'port', dest_port]) + self.cli_set(dst_path + ['rule', '20', 'protocol', protocol]) + self.cli_set(dst_path + ['rule', '20', 'inbound-interface', ifname]) + self.cli_set(dst_path + ['rule', '20', 'translation', 'redirect']) + + self.cli_commit() + + nftables_search = [ + [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect to :{redirected_port}'], + [f'iifname "{ifname}"', f'ip daddr {dst_addr_1}', f'{protocol} dport {dest_port}', f'redirect'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + + def test_nat_balance(self): + ifname = 'eth0' + member_1 = '198.51.100.1' + weight_1 = '10' + member_2 = '198.51.100.2' + weight_2 = '90' + member_3 = '192.0.2.1' + weight_3 = '35' + member_4 = '192.0.2.2' + weight_4 = '65' + dst_port = '443' + + self.cli_set(dst_path + ['rule', '1', 'inbound-interface', ifname]) + self.cli_set(dst_path + ['rule', '1', 'protocol', 'tcp']) + self.cli_set(dst_path + ['rule', '1', 'destination', 'port', dst_port]) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-address']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'source-port']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-address']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'hash', 'destination-port']) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_1, 'weight', weight_1]) + self.cli_set(dst_path + ['rule', '1', 'load-balance', 'backend', member_2, 'weight', weight_2]) + + self.cli_set(src_path + ['rule', '1', 'outbound-interface', ifname]) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'hash', 'random']) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_3, 'weight', weight_3]) + self.cli_set(src_path + ['rule', '1', 'load-balance', 'backend', member_4, 'weight', weight_4]) + + self.cli_commit() + + nftables_search = [ + [f'iifname "{ifname}"', f'tcp dport {dst_port}', f'dnat to jhash ip saddr . tcp sport . ip daddr . tcp dport mod 100 map', f'0-9 : {member_1}, 10-99 : {member_2}'], + [f'oifname "{ifname}"', f'snat to numgen random mod 100 map', f'0-34 : {member_3}, 35-99 : {member_4}'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_nat') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py index 6cf7ca0a1..e062f28a6 100755 --- a/smoketest/scripts/cli/test_nat66.py +++ b/smoketest/scripts/cli/test_nat66.py @@ -22,8 +22,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import dict_search +from vyos.utils.process import cmd +from vyos.utils.dict import dict_search base_path = ['nat66'] src_path = base_path + ['source'] @@ -136,7 +136,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['iifname "eth1"', 'tcp dport 4545', 'ip6 saddr 2001:db8:2222::/64', 'tcp sport 8080', 'dnat to 2001:db8:1111::1:5555'] + ['iifname "eth1"', 'tcp dport 4545', 'ip6 saddr 2001:db8:2222::/64', 'tcp sport 8080', 'dnat to [2001:db8:1111::1]:5555'] ] self.verify_nftables(nftables_search, 'ip6 vyos_nat') @@ -208,7 +208,7 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase): self.cli_commit() nftables_search = [ - ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64', 'tcp dport 9999', 'tcp sport 8080', 'snat to 2001:db8:1111::1:80'] + ['oifname "eth1"', 'ip6 saddr 2001:db8:2222::/64', 'tcp dport 9999', 'tcp sport 8080', 'snat to [2001:db8:1111::1]:80'] ] self.verify_nftables(nftables_search, 'ip6 vyos_nat') diff --git a/smoketest/scripts/cli/test_interfaces_netns.py b/smoketest/scripts/cli/test_netns.py index 9975a6b09..fd04dd520 100755 --- a/smoketest/scripts/cli/test_interfaces_netns.py +++ b/smoketest/scripts/cli/test_netns.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -14,70 +14,70 @@ # 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 re -import os -import json import unittest -from netifaces import interfaces from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface from vyos.ifconfig import Section -from vyos.util import cmd +from vyos.utils.process import cmd +from vyos.utils.network import is_netns_interface +from vyos.utils.network import get_netns_all base_path = ['netns'] -namespaces = ['mgmt', 'front', 'back', 'ams-ix'] +interfaces = ['dum10', 'dum12', 'dum50'] -class NETNSTest(VyOSUnitTestSHIM.TestCase): +class NetNSTest(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + self.cli_delete(base_path) + # commit changes + self.cli_commit() - def setUp(self): - self._interfaces = ['dum10', 'dum12', 'dum50'] + # There should be no network namespace remaining + tmp = cmd('ip netns ls') + self.assertFalse(tmp) - def test_create_netns(self): + super(NetNSTest, self).tearDown() + + def test_netns_create(self): + namespaces = ['mgmt', 'front', 'back'] for netns in namespaces: - base = base_path + ['name', netns] - self.cli_set(base) + self.cli_set(base_path + ['name', netns]) # commit changes self.cli_commit() - netns_list = cmd('ip netns ls') - # Verify NETNS configuration for netns in namespaces: - self.assertTrue(netns in netns_list) + self.assertIn(netns, get_netns_all()) - - def test_netns_assign_interface(self): + def test_netns_interface(self): netns = 'foo' - self.cli_set(['netns', 'name', netns]) + self.cli_set(base_path + ['name', netns]) # Set - for iface in self._interfaces: + for iface in interfaces: self.cli_set(['interfaces', 'dummy', iface, 'netns', netns]) # commit changes self.cli_commit() - netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') - - for iface in self._interfaces: - self.assertTrue(iface in netns_iface_list) + for interface in interfaces: + self.assertTrue(is_netns_interface(interface, netns)) # Delete - for iface in self._interfaces: - self.cli_delete(['interfaces', 'dummy', iface, 'netns', netns]) + for interface in interfaces: + self.cli_delete(['interfaces', 'dummy', interface]) # commit changes self.cli_commit() netns_iface_list = cmd(f'sudo ip netns exec {netns} ip link show') - for iface in self._interfaces: - self.assertNotIn(iface, netns_iface_list) + for interface in interfaces: + self.assertFalse(is_netns_interface(interface, netns)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py index cba5ffdde..b18b0b039 100755 --- a/smoketest/scripts/cli/test_pki.py +++ b/smoketest/scripts/cli/test_pki.py @@ -246,5 +246,27 @@ class TestPKI(VyOSUnitTestSHIM.TestCase): self.cli_delete(['service', 'https', 'certificates', 'certificate']) + def test_certificate_eapol_update(self): + self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_ca_cert.replace('\n','')]) + self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_ca_private_key.replace('\n','')]) + self.cli_commit() + + self.cli_set(['interfaces', 'ethernet', 'eth1', 'eapol', 'certificate', 'smoketest']) + self.cli_commit() + + cert_data = None + + with open('/run/wpa_supplicant/eth1_cert.pem') as f: + cert_data = f.read() + + self.cli_set(base_path + ['certificate', 'smoketest', 'certificate', valid_update_cert.replace('\n','')]) + self.cli_set(base_path + ['certificate', 'smoketest', 'private', 'key', valid_update_private_key.replace('\n','')]) + self.cli_commit() + + with open('/run/wpa_supplicant/eth1_cert.pem') as f: + self.assertNotEqual(cert_data, f.read()) + + self.cli_delete(['interfaces', 'ethernet', 'eth1', 'eapol']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 2166e63ec..354f791bd 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -19,7 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd +from vyos.utils.process import cmd base_path = ['policy'] @@ -1030,6 +1030,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): 'metric' : '150', 'metric-type' : 'type-1', 'origin' : 'incomplete', + 'l3vpn' : '', 'originator-id' : '172.16.10.1', 'src' : '100.0.0.1', 'tag' : '65530', @@ -1070,6 +1071,22 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): }, }, }, + 'match-protocol' : { + 'rule' : { + '10' : { + 'action' : 'permit', + 'match' : { + 'protocol' : 'static', + }, + }, + '20' : { + 'action' : 'permit', + 'match' : { + 'protocol' : 'bgp', + }, + }, + }, + }, 'relative-metric' : { 'rule' : { '10' : { @@ -1201,6 +1218,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_set(path + ['rule', rule, 'match', 'rpki', 'notfound']) if 'rpki-valid' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'rpki', 'valid']) + if 'protocol' in rule_config['match']: + self.cli_set(path + ['rule', rule, 'match', 'protocol', rule_config['match']['protocol']]) if 'tag' in rule_config['match']: self.cli_set(path + ['rule', rule, 'match', 'tag', rule_config['match']['tag']]) @@ -1229,6 +1248,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): self.cli_set(path + ['rule', rule, 'set', 'ipv6-next-hop', 'local', rule_config['set']['ipv6-next-hop-local']]) if 'ip-next-hop' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'ip-next-hop', rule_config['set']['ip-next-hop']]) + if 'l3vpn' in rule_config['set']: + self.cli_set(path + ['rule', rule, 'set', 'l3vpn-nexthop', 'encapsulation', 'gre']) if 'local-preference' in rule_config['set']: self.cli_set(path + ['rule', rule, 'set', 'local-preference', rule_config['set']['local-preference']]) if 'metric' in rule_config['set']: @@ -1365,6 +1386,9 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): if 'peer' in rule_config['match']: tmp = f'match peer {rule_config["match"]["peer"]}' self.assertIn(tmp, config) + if 'protocol' in rule_config['match']: + tmp = f'match source-protocol {rule_config["match"]["protocol"]}' + self.assertIn(tmp, config) if 'rpki-invalid' in rule_config['match']: tmp = f'match rpki invalid' self.assertIn(tmp, config) @@ -1408,6 +1432,8 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase): tmp += 'ipv6 next-hop global ' + rule_config['set']['ipv6-next-hop-global'] elif 'ipv6-next-hop-local' in rule_config['set']: tmp += 'ipv6 next-hop local ' + rule_config['set']['ipv6-next-hop-local'] + elif 'l3vpn' in rule_config['set']: + tmp += 'l3vpn next-hop encapsulation gre' elif 'local-preference' in rule_config['set']: tmp += 'local-preference ' + rule_config['set']['local-preference'] elif 'metric' in rule_config['set']: diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index 046e385bb..118b1d3a2 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -18,12 +18,15 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import cmd +from vyos.utils.process import cmd mark = '100' +conn_mark = '555' +conn_mark_set = '111' table_mark_offset = 0x7fffffff table_id = '101' interface = 'eth0' +interface_wc = 'ppp*' interface_ip = '172.16.10.1/24' class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): @@ -42,18 +45,25 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): super(TestPolicyRoute, cls).tearDownClass() def tearDown(self): - self.cli_delete(['interfaces', 'ethernet', interface, 'policy']) self.cli_delete(['policy', 'route']) self.cli_delete(['policy', 'route6']) self.cli_commit() + # Verify nftables cleanup nftables_search = [ ['set N_smoketest_network'], ['set N_smoketest_network1'], ['chain VYOS_PBR_smoketest'] ] - self.verify_nftables(nftables_search, 'ip mangle', inverse=True) + self.verify_nftables(nftables_search, 'ip vyos_mangle', inverse=True) + + # Verify ip rule cleanup + ip_rule_search = [ + ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] + ] + + self.verify_rules(ip_rule_search, inverse=True) def verify_nftables(self, nftables_search, table, inverse=False): nftables_output = cmd(f'sudo nft list table {table}') @@ -66,6 +76,17 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): break self.assertTrue(not matched if inverse else matched, msg=search) + def verify_rules(self, rules_search, inverse=False): + rule_output = cmd('ip rule show') + + for search in rules_search: + matched = False + for line in rule_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + def test_pbr_group(self): self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network', 'network', '172.16.99.0/24']) self.cli_set(['firewall', 'group', 'network-group', 'smoketest_network1', 'network', '172.16.101.0/24']) @@ -74,17 +95,16 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'group', 'network-group', 'smoketest_network']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'group', 'network-group', 'smoketest_network1']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark]) - - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) self.cli_commit() nftables_search = [ - [f'iifname "{interface}"','jump VYOS_PBR_smoketest'], + [f'iifname "{interface}"','jump VYOS_PBR_UD_smoketest'], ['ip daddr @N_smoketest_network1', 'ip saddr @N_smoketest_network'], ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') self.cli_delete(['firewall']) @@ -92,19 +112,37 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'mark', mark]) - - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) self.cli_commit() mark_hex = "{0:#010x}".format(int(mark)) nftables_search = [ - [f'iifname "{interface}"','jump VYOS_PBR_smoketest'], + [f'iifname "{interface}"','jump VYOS_PBR_UD_smoketest'], ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'meta mark set ' + mark_hex], ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') + + def test_pbr_mark_connection(self): + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'source', 'address', '172.16.20.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'destination', 'address', '172.16.10.10']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'connection-mark', conn_mark]) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'set', 'connection-mark', conn_mark_set]) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + + self.cli_commit() + + mark_hex = "{0:#010x}".format(int(conn_mark)) + mark_hex_set = "{0:#010x}".format(int(conn_mark_set)) + + nftables_search = [ + [f'iifname "{interface}"','jump VYOS_PBR_UD_smoketest'], + ['ip daddr 172.16.10.10', 'ip saddr 172.16.20.10', 'ct mark ' + mark_hex, 'ct mark set ' + mark_hex_set], + ] + + self.verify_nftables(nftables_search, 'ip vyos_mangle') def test_pbr_table(self): self.cli_set(['policy', 'route', 'smoketest', 'rule', '1', 'protocol', 'tcp']) @@ -116,8 +154,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'destination', 'port', '8888']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '1', 'set', 'table', table_id]) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface]) self.cli_commit() @@ -126,20 +164,20 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): # IPv4 nftables_search = [ - [f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'], + [f'iifname "{interface}"', 'jump VYOS_PBR_UD_smoketest'], ['tcp flags syn / syn,ack', 'tcp dport 8888', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') # IPv6 nftables6_search = [ - [f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'], + [f'iifname "{interface}"', 'jump VYOS_PBR6_UD_smoketest'], ['meta l4proto { tcp, udp }', 'th dport 8888', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables6_search, 'ip6 mangle') + self.verify_nftables(nftables6_search, 'ip6 vyos_mangle') # IP rule fwmark -> table @@ -147,15 +185,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): ['fwmark ' + hex(table_mark_offset - int(table_id)), 'lookup ' + table_id] ] - ip_rule_output = cmd('ip rule show') - - for search in ip_rule_search: - matched = False - for line in ip_rule_output.split("\n"): - if all(item in line for item in search): - matched = True - break - self.assertTrue(matched) + self.verify_rules(ip_rule_search) def test_pbr_matching_criteria(self): @@ -175,6 +205,7 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'icmp', 'type-name', 'echo-request']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '128']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-length', '1024-2048']) + self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'packet-type', 'other']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'log', 'enable']) self.cli_set(['policy', 'route', 'smoketest', 'rule', '4', 'set', 'table', table_id]) self.cli_set(['policy', 'route', 'smoketest', 'rule', '5', 'dscp', '41']) @@ -197,14 +228,17 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'icmpv6', 'type', 'echo-request']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '128']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-length-exclude', '1024-2048']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'packet-type', 'multicast']) + self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'log', 'enable']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '4', 'set', 'table', table_id]) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '61']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'dscp-exclude', '14-19']) self.cli_set(['policy', 'route6', 'smoketest6', 'rule', '5', 'set', 'table', table_id]) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route', 'smoketest']) - self.cli_set(['interfaces', 'ethernet', interface, 'policy', 'route6', 'smoketest6']) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface]) + self.cli_set(['policy', 'route', 'smoketest', 'interface', interface_wc]) + self.cli_set(['policy', 'route6', 'smoketest6', 'interface', interface_wc]) self.cli_commit() @@ -212,27 +246,27 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): # IPv4 nftables_search = [ - [f'iifname "{interface}"', 'jump VYOS_PBR_smoketest'], + ['iifname { "' + interface + '", "' + interface_wc + '" }', 'jump VYOS_PBR_UD_smoketest'], ['meta l4proto udp', 'drop'], ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex], ['ct state new', 'tcp dport 22', 'ip saddr 198.51.100.0/24', 'ip ttl > 2', 'meta mark set ' + mark_hex], - ['meta l4proto icmp', 'log prefix "[smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta mark set ' + mark_hex], + ['meta l4proto icmp', 'log prefix "[ipv4-route-smoketest-4-A]"', 'icmp type echo-request', 'ip length { 128, 1024-2048 }', 'meta pkttype other', 'meta mark set ' + mark_hex], ['ip dscp { 0x29, 0x39-0x3b }', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables_search, 'ip mangle') + self.verify_nftables(nftables_search, 'ip vyos_mangle') # IPv6 nftables6_search = [ - [f'iifname "{interface}"', 'jump VYOS_PBR6_smoketest'], + [f'iifname "{interface_wc}"', 'jump VYOS_PBR6_UD_smoketest'], ['meta l4proto udp', 'drop'], ['tcp flags syn / syn,ack', 'meta mark set ' + mark_hex], ['ct state new', 'tcp dport 22', 'ip6 saddr 2001:db8::/64', 'ip6 hoplimit > 2', 'meta mark set ' + mark_hex], - ['meta l4proto ipv6-icmp', 'log prefix "[smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta mark set ' + mark_hex], + ['meta l4proto ipv6-icmp', 'log prefix "[ipv6-route6-smoketest6-4-A]"', 'icmpv6 type echo-request', 'ip6 length != { 128, 1024-2048 }', 'meta pkttype multicast', 'meta mark set ' + mark_hex], ['ip6 dscp != { 0x0e-0x13, 0x3d }', 'meta mark set ' + mark_hex] ] - self.verify_nftables(nftables6_search, 'ip6 mangle') + self.verify_nftables(nftables6_search, 'ip6 vyos_mangle') if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index fdc254a05..451565664 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -18,7 +18,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'bfdd' base_path = ['protocols', 'bfd'] diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index d2dad8c1a..77952d8d9 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -18,9 +18,10 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.ifconfig import Section from vyos.configsession import ConfigSessionError from vyos.template import is_ipv6 -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'bgpd' ASN = '64512' @@ -34,6 +35,10 @@ prefix_list_in6 = 'pfx-foo-in6' prefix_list_out6 = 'pfx-foo-out6' bfd_profile = 'foo-bar-baz' +import_afi = 'ipv4-unicast' +import_vrf = 'red' +import_rd = ASN + ':100' +import_vrf_base = ['vrf', 'name'] neighbor_config = { '192.0.2.1' : { 'bfd' : '', @@ -51,6 +56,7 @@ neighbor_config = { 'route_map_out' : route_map_out, 'no_send_comm_ext' : '', 'addpath_all' : '', + 'p_attr_discard' : '123', }, '192.0.2.2' : { 'bfd_profile' : bfd_profile, @@ -64,6 +70,7 @@ neighbor_config = { 'pfx_list_in' : prefix_list_in, 'pfx_list_out' : prefix_list_out, 'no_send_comm_std' : '', + 'local_role' : 'rs-client', }, '192.0.2.3' : { 'advertise_map' : route_map_in, @@ -94,6 +101,8 @@ neighbor_config = { 'no_send_comm_std' : '', 'addpath_per_as' : '', 'peer_group' : 'foo-bar', + 'local_role' : 'customer', + 'local_role_strict': '', }, '2001:db8::2' : { 'remote_as' : '456', @@ -122,10 +131,12 @@ peer_group_config = { 'cap_over' : '', 'ttl_security' : '5', 'disable_conn_chk' : '', + 'p_attr_discard' : '250', }, 'bar' : { 'remote_as' : '111', - 'graceful_rst_no' : '' + 'graceful_rst_no' : '', + 'port' : '667', }, 'foo-bar' : { 'advertise_map' : route_map_in, @@ -150,9 +161,10 @@ peer_group_config = { 'update_src' : 'lo', 'route_map_in' : route_map_in, 'route_map_out' : route_map_out, + 'local_role' : 'peer', + 'local_role_strict': '', }, } - class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -189,6 +201,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) + def create_bgp_instances_for_import_test(self): + table = '1000' + self.cli_set(import_vrf_base + [import_vrf, 'table', table]) + self.cli_set(import_vrf_base + [import_vrf, 'protocols', 'bgp', 'system-as', ASN]) + def verify_frr_config(self, peer, peer_config, frrconfig): # recurring patterns to verify for both a simple neighbor and a peer-group if 'bfd' in peer_config: @@ -208,12 +225,19 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): 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"]} no-prepend replace-as', frrconfig) + if 'local_role' in peer_config: + tmp = f' neighbor {peer} local-role {peer_config["local_role"]}' + if 'local_role_strict' in peer_config: + tmp += ' strict' + self.assertIn(tmp, 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 'port' in peer_config: + self.assertIn(f' neighbor {peer} port {peer_config["port"]}', frrconfig) if 'remote_as' in peer_config: self.assertIn(f' neighbor {peer} remote-as {peer_config["remote_as"]}', frrconfig) if 'solo' in peer_config: @@ -238,6 +262,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' no neighbor {peer} send-community extended', frrconfig) if 'addpath_all' in peer_config: self.assertIn(f' neighbor {peer} addpath-tx-all-paths', frrconfig) + if 'p_attr_discard' in peer_config: + self.assertIn(f' neighbor {peer} path-attribute discard {peer_config["p_attr_discard"]}', frrconfig) if 'addpath_per_as' in peer_config: self.assertIn(f' neighbor {peer} addpath-tx-bestpath-per-AS', frrconfig) if 'advertise_map' in peer_config: @@ -256,7 +282,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'disable_conn_chk' in peer_config: self.assertIn(f' neighbor {peer} disable-connected-check', frrconfig) - def test_bgp_01_simple(self): router_id = '127.0.0.1' local_pref = '500' @@ -267,6 +292,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): max_path_v6ibgp = '16' cond_adv_timer = '30' min_hold_time = '2' + tcp_keepalive_idle = '66' + tcp_keepalive_interval = '77' + tcp_keepalive_probes = '22' self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['parameters', 'log-neighbor-changes']) @@ -294,8 +322,12 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['parameters', 'minimum-holdtime', min_hold_time]) self.cli_set(base_path + ['parameters', 'no-suppress-duplicates']) self.cli_set(base_path + ['parameters', 'reject-as-sets']) + self.cli_set(base_path + ['parameters', 'route-reflector-allow-outbound-policy']) self.cli_set(base_path + ['parameters', 'shutdown']) self.cli_set(base_path + ['parameters', 'suppress-fib-pending']) + self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'idle', tcp_keepalive_idle]) + self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'interval', tcp_keepalive_interval]) + self.cli_set(base_path + ['parameters', 'tcp-keepalive', 'probes', tcp_keepalive_probes]) # AFI maximum path support self.cli_set(base_path + ['address-family', 'ipv4-unicast', 'maximum-paths', 'ebgp', max_path_v4]) @@ -322,8 +354,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp bestpath peer-type multipath-relax', frrconfig) self.assertIn(f' bgp minimum-holdtime {min_hold_time}', frrconfig) self.assertIn(f' bgp reject-as-sets', frrconfig) + self.assertIn(f' bgp route-reflector allow-outbound-policy', frrconfig) self.assertIn(f' bgp shutdown', frrconfig) self.assertIn(f' bgp suppress-fib-pending', frrconfig) + self.assertIn(f' bgp tcp-keepalive {tcp_keepalive_idle} {tcp_keepalive_interval} {tcp_keepalive_probes}', frrconfig) self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) self.assertIn(f' no bgp suppress-duplicates', frrconfig) @@ -335,7 +369,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' maximum-paths {max_path_v6}', afiv6_config) self.assertIn(f' maximum-paths ibgp {max_path_v6ibgp}', afiv6_config) - def test_bgp_02_neighbors(self): # Test out individual neighbor configuration items, not all of them are # also available to a peer-group! @@ -365,6 +398,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', peer, 'ebgp-multihop', peer_config["multi_hop"]]) if 'local_as' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'local-as', peer_config["local_as"], 'no-prepend', 'replace-as']) + if 'local_role' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'local-role', peer_config["local_role"]]) + if 'local_role_strict' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'local-role', peer_config["local_role"], 'strict']) if 'cap_over' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'override-capability']) if 'passive' in peer_config: @@ -385,6 +422,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', peer, 'ttl-security', 'hops', peer_config["ttl_security"]]) if 'update_src' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'update-source', peer_config["update_src"]]) + if 'p_attr_discard' in peer_config: + self.cli_set(base_path + ['neighbor', peer, 'path-attribute', 'discard', peer_config["p_attr_discard"]]) if 'route_map_in' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'address-family', afi, 'route-map', 'import', peer_config["route_map_in"]]) if 'route_map_out' in peer_config: @@ -434,8 +473,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): for peer, peer_config in neighbor_config.items(): if 'adv_interv' in peer_config: self.assertIn(f' neighbor {peer} advertisement-interval {peer_config["adv_interv"]}', frrconfig) - if 'port' in peer_config: - self.assertIn(f' neighbor {peer} port {peer_config["port"]}', frrconfig) if 'cap_strict' in peer_config: self.assertIn(f' neighbor {peer} strict-capability-match', frrconfig) @@ -461,12 +498,18 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer-group', peer_group, 'ebgp-multihop', config["multi_hop"]]) if 'local_as' in config: self.cli_set(base_path + ['peer-group', peer_group, 'local-as', config["local_as"], 'no-prepend', 'replace-as']) + if 'local_role' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'local-role', config["local_role"]]) + if 'local_role_strict' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'local-role', config["local_role"], 'strict']) if 'cap_over' in config: self.cli_set(base_path + ['peer-group', peer_group, 'override-capability']) if 'passive' in config: self.cli_set(base_path + ['peer-group', peer_group, 'passive']) if 'password' in config: self.cli_set(base_path + ['peer-group', peer_group, 'password', config["password"]]) + if 'port' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'port', config["port"]]) if 'remote_as' in config: self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', config["remote_as"]]) if 'shutdown' in config: @@ -499,6 +542,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer-group', peer_group, 'graceful-restart', 'restart-helper']) if 'disable_conn_chk' in config: self.cli_set(base_path + ['peer-group', peer_group, 'disable-connected-check']) + if 'p_attr_discard' in config: + self.cli_set(base_path + ['peer-group', peer_group, 'path-attribute', 'discard', config["p_attr_discard"]]) # Conditional advertisement if 'advertise_map' in config: @@ -518,7 +563,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'peer_group' in peer_config: self.cli_set(base_path + ['neighbor', peer, 'peer-group', peer_config['peer_group']]) - # commit changes self.cli_commit() @@ -534,7 +578,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'peer_group' in peer_config: self.assertIn(f' neighbor {peer} peer-group {peer_config["peer_group"]}', frrconfig) - def test_bgp_04_afi_ipv4(self): networks = { '10.0.0.0/8' : { @@ -582,7 +625,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): 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' : { @@ -629,7 +671,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): if 'as_set' in network_config: self.assertIn(f' aggregate-address {network} summary-only', frrconfig) - def test_bgp_06_listen_range(self): # Implemented via T1875 limit = '64' @@ -662,7 +703,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): for prefix in listen_ranges: self.assertIn(f' bgp listen range {prefix} peer-group {peer_group}', frrconfig) - def test_bgp_07_l2vpn_evpn(self): vnis = ['10010', '10020', '10030'] neighbors = ['192.0.2.10', '192.0.2.20', '192.0.2.30'] @@ -692,26 +732,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' advertise-default-gw', vniconfig) self.assertIn(f' advertise-svi-ip', vniconfig) - def test_bgp_08_zebra_route_map(self): - # Implemented because of T3328 - self.cli_set(base_path + ['route-map', route_map_in]) - # commit changes - self.cli_commit() - - # Verify FRR configuration - zebra_route_map = f'ip protocol bgp route-map {route_map_in}' - frrconfig = self.getFRRconfig(zebra_route_map) - self.assertIn(zebra_route_map, frrconfig) - - # Remove the route-map again - self.cli_delete(base_path + ['route-map']) - # commit changes - self.cli_commit() - - # Verify FRR configuration - frrconfig = self.getFRRconfig(zebra_route_map) - self.assertNotIn(zebra_route_map, frrconfig) - def test_bgp_09_distance_and_flowspec(self): distance_external = '25' distance_internal = '30' @@ -761,7 +781,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'{family}', frrconfig) self.assertIn(f'local-install {flowspec_int}', frrconfig) - def test_bgp_10_vrf_simple(self): router_id = '127.0.0.3' vrfs = ['red', 'green', 'blue'] @@ -771,15 +790,12 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # templates and Jinja2 FRR template. table = '1000' - self.cli_set(base_path + ['system-as', ASN]) # testing only one AFI is sufficient as it's generic code - for vrf in vrfs: vrf_base = ['vrf', 'name', vrf] self.cli_set(vrf_base + ['table', table]) self.cli_set(vrf_base + ['protocols', 'bgp', 'system-as', ASN]) self.cli_set(vrf_base + ['protocols', 'bgp', 'parameters', 'router-id', router_id]) - self.cli_set(vrf_base + ['protocols', 'bgp', 'route-map', route_map_in]) table = str(int(table) + 1000) # import VRF routes do main RIB @@ -792,7 +808,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv6 unicast', frrconfig) - for vrf in vrfs: self.assertIn(f' import vrf {vrf}', frrconfig) @@ -801,21 +816,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f'router bgp {ASN} vrf {vrf}', frr_vrf_config) self.assertIn(f' bgp router-id {router_id}', frr_vrf_config) - # XXX: Currently this is not working as FRR() class does not support - # route-maps for multiple vrfs because the modify_section() only works - # on lines and not text blocks. - # - # vrfconfig = self.getFRRconfig(f'vrf {vrf}') - # zebra_route_map = f' ip protocol bgp route-map {route_map_in}' - # self.assertIn(zebra_route_map, vrfconfig) - - def test_bgp_11_confederation(self): router_id = '127.10.10.2' confed_id = str(int(ASN) + 1) confed_asns = '10 20 30 40' - self.cli_set(base_path + ['system-as', ASN]) self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['parameters', 'confederation', 'identifier', confed_id]) for asn in confed_asns.split(): @@ -831,12 +836,10 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' bgp confederation identifier {confed_id}', frrconfig) self.assertIn(f' bgp confederation peers {confed_asns}', frrconfig) - def test_bgp_12_v6_link_local(self): remote_asn = str(int(ASN) + 10) interface = 'eth0' - self.cli_set(base_path + ['system-as', ASN]) self.cli_set(base_path + ['neighbor', interface, 'address-family', 'ipv6-unicast']) self.cli_set(base_path + ['neighbor', interface, 'interface', 'v6only', 'remote-as', remote_asn]) @@ -851,7 +854,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {interface} activate', frrconfig) self.assertIn(f' exit-address-family', frrconfig) - def test_bgp_13_vpn(self): remote_asn = str(int(ASN) + 150) neighbor = '192.0.2.55' @@ -861,12 +863,12 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): rt_export = f'{neighbor}:1002 1.2.3.4:567' rt_import = f'{neighbor}:1003 500:100' - self.cli_set(base_path + ['system-as', ASN]) # testing only one AFI is sufficient as it's generic code for afi in ['ipv4-unicast', 'ipv6-unicast']: self.cli_set(base_path + ['address-family', afi, 'export', 'vpn']) self.cli_set(base_path + ['address-family', afi, 'import', 'vpn']) self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'export', label]) + self.cli_set(base_path + ['address-family', afi, 'label', 'vpn', 'allocation-mode', 'per-nexthop']) self.cli_set(base_path + ['address-family', afi, 'rd', 'vpn', 'export', rd]) self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'export', route_map_out]) self.cli_set(base_path + ['address-family', afi, 'route-map', 'vpn', 'import', route_map_in]) @@ -886,6 +888,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' export vpn', afi_config) self.assertIn(f' import vpn', afi_config) self.assertIn(f' label vpn export {label}', afi_config) + self.assertIn(f' label vpn export allocation-mode per-nexthop', afi_config) self.assertIn(f' rd vpn export {rd}', afi_config) self.assertIn(f' route-map vpn export {route_map_out}', afi_config) self.assertIn(f' route-map vpn import {route_map_in}', afi_config) @@ -900,7 +903,6 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): peer_group = 'bar' interface = 'eth0' - self.cli_set(base_path + ['system-as', ASN]) self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn]) self.cli_set(base_path + ['neighbor', neighbor, 'peer-group', peer_group]) self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', remote_asn]) @@ -933,14 +935,13 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig) def test_bgp_15_local_as_ebgp(self): - # https://phabricator.vyos.net/T4560 + # https://vyos.dev/T4560 # local-as allowed only for ebgp peers neighbor = '192.0.2.99' remote_asn = '500' local_asn = '400' - self.cli_set(base_path + ['system-as', ASN]) self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', ASN]) self.cli_set(base_path + ['neighbor', neighbor, 'local-as', local_asn]) @@ -957,6 +958,128 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' neighbor {neighbor} remote-as {remote_asn}', frrconfig) self.assertIn(f' neighbor {neighbor} local-as {local_asn}', frrconfig) + def test_bgp_16_import_rd_rt_compatibility(self): + # Verify if import vrf and rd vpn export + # exist in the same address family + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_set( + base_path + ['address-family', import_afi, 'rd', 'vpn', 'export', + import_rd]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_17_import_rd_rt_compatibility(self): + # Verify if vrf that is in import vrf list contains rd vpn export + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'address-family ipv4 unicast', frrconfig) + self.assertIn(f' import vrf {import_vrf}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + + self.cli_set( + import_vrf_base + [import_vrf] + base_path + ['address-family', + import_afi, 'rd', + 'vpn', 'export', + import_rd]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_18_deleting_import_vrf(self): + # Verify deleting vrf that is in import vrf list + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'address-family ipv4 unicast', frrconfig) + self.assertIn(f' import vrf {import_vrf}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.cli_delete(import_vrf_base + [import_vrf]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_19_deleting_default_vrf(self): + # Verify deleting existent vrf default if other vrfs were created + self.create_bgp_instances_for_import_test() + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.cli_delete(base_path) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_20_import_rd_rt_compatibility(self): + # Verify if vrf that has rd vpn export is in import vrf of other vrfs + self.create_bgp_instances_for_import_test() + self.cli_set( + import_vrf_base + [import_vrf] + base_path + ['address-family', + import_afi, 'rd', + 'vpn', 'export', + import_rd]) + self.cli_commit() + frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + self.assertIn(f'router bgp {ASN}', frrconfig) + self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) + self.assertIn(f'address-family ipv4 unicast', frrconfig_vrf) + self.assertIn(f' rd vpn export {import_rd}', frrconfig_vrf) + + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + import_vrf]) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_21_import_unspecified_vrf(self): + # Verify if vrf that is in import is unspecified + self.create_bgp_instances_for_import_test() + self.cli_set( + base_path + ['address-family', import_afi, 'import', 'vrf', + 'test']) + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + def test_bgp_22_interface_mpls_forwarding(self): + interfaces = Section.interfaces('ethernet', vlan=False) + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'mpls', 'forwarding']) + + self.cli_commit() + + for interface in interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', frrconfig) + self.assertIn(f' mpls bgp forwarding', frrconfig) + + def test_bgp_23_vrf_interface_mpls_forwarding(self): + self.create_bgp_instances_for_import_test() + interfaces = Section.interfaces('ethernet', vlan=False) + for interface in interfaces: + self.cli_set(['interfaces', 'ethernet', interface, 'vrf', import_vrf]) + self.cli_set(import_vrf_base + [import_vrf] + base_path + ['interface', interface, 'mpls', 'forwarding']) + + self.cli_commit() + + for interface in interfaces: + frrconfig = self.getFRRconfig(f'interface {interface}') + self.assertIn(f'interface {interface}', frrconfig) + self.assertIn(f' mpls bgp forwarding', frrconfig) + self.cli_delete(['interfaces', 'ethernet', interface, 'vrf']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_igmp-proxy.py b/smoketest/scripts/cli/test_protocols_igmp-proxy.py index 079b5bee5..a75003b12 100755 --- a/smoketest/scripts/cli/test_protocols_igmp-proxy.py +++ b/smoketest/scripts/cli/test_protocols_igmp-proxy.py @@ -19,8 +19,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import read_file -from vyos.util import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running PROCESS_NAME = 'igmpproxy' IGMP_PROXY_CONF = '/etc/igmpproxy.conf' diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index d11d80a1f..5ab7fae14 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -19,7 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'isisd' base_path = ['protocols', 'isis'] @@ -119,39 +119,6 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_delete(['vrf', 'name', vrf]) self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) - def test_isis_03_zebra_route_map(self): - # Implemented because of T3328 - route_map = 'foo-isis-in' - - self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) - - self.isis_base_config() - self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map]) - self.cli_set(base_path + ['route-map', route_map]) - self.cli_set(base_path + ['level', 'level-2']) - - # commit changes - self.cli_commit() - - # Verify FRR configuration - zebra_route_map = f'ip protocol isis route-map {route_map}' - frrconfig = self.getFRRconfig(zebra_route_map, daemon='zebra') - self.assertIn(zebra_route_map, frrconfig) - - tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') - self.assertIn(' is-type level-2-only', tmp) - - # Remove the route-map again - self.cli_delete(base_path + ['route-map']) - # commit changes - self.cli_commit() - - # Verify FRR configuration - frrconfig = self.getFRRconfig(zebra_route_map, daemon='zebra') - self.assertNotIn(zebra_route_map, frrconfig) - - self.cli_delete(['policy', 'route-map', route_map]) - def test_isis_04_default_information(self): metric = '50' route_map = 'default-foo-' @@ -293,7 +260,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['segment-routing', 'prefix', prefix_three, 'absolute', 'explicit-null']) self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'value', prefix_four_value]) self.cli_set(base_path + ['segment-routing', 'prefix', prefix_four, 'absolute', 'no-php-flag']) - + # Commit all changes self.cli_commit() @@ -308,5 +275,50 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.assertIn(f' segment-routing prefix {prefix_three} absolute {prefix_three_value} explicit-null', tmp) self.assertIn(f' segment-routing prefix {prefix_four} absolute {prefix_four_value} no-php-flag', tmp) + def test_isis_08_ldp_sync(self): + holddown = "500" + interface = 'lo' + + self.cli_set(base_path + ['net', net]) + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['ldp-sync', 'holddown', holddown]) + + # Commit main ISIS changes + self.cli_commit() + + # Verify main ISIS changes + tmp = self.getFRRconfig(f'router isis {domain}', daemon='isisd') + self.assertIn(f' net {net}', tmp) + self.assertIn(f' mpls ldp-sync', tmp) + self.assertIn(f' mpls ldp-sync holddown {holddown}', tmp) + + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown]) + + # Commit interface changes for holddown + self.cli_commit() + + for interface in self._interfaces: + # Verify interface changes for holddown + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f'interface {interface}', tmp) + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + self.assertIn(f' isis mpls ldp-sync holddown {holddown}', tmp) + + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable']) + + # Commit interface changes for disable + self.cli_commit() + + for interface in self._interfaces: + # Verify interface changes for disable + tmp = self.getFRRconfig(f'interface {interface}', daemon='isisd') + self.assertIn(f'interface {interface}', tmp) + self.assertIn(f' ip router isis {domain}', tmp) + self.assertIn(f' ipv6 router isis {domain}', tmp) + self.assertIn(f' no isis mpls ldp-sync', tmp) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py index 76e6ca35a..06f21c6e1 100755 --- a/smoketest/scripts/cli/test_protocols_mpls.py +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -19,7 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'ldpd' base_path = ['protocols', 'mpls', 'ldp'] diff --git a/smoketest/scripts/cli/test_protocols_nhrp.py b/smoketest/scripts/cli/test_protocols_nhrp.py index 59252875b..45ef539f6 100755 --- a/smoketest/scripts/cli/test_protocols_nhrp.py +++ b/smoketest/scripts/cli/test_protocols_nhrp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -19,7 +19,9 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.firewall import find_nftables_rule -from vyos.util import call, process_named_running, read_file +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import call tunnel_path = ['interfaces', 'tunnel'] nhrp_path = ['protocols', 'nhrp'] @@ -54,7 +56,7 @@ class TestProtocolsNHRP(VyOSUnitTestSHIM.TestCase): self.cli_set(tunnel_path + [tunnel_if, "address", "172.16.253.134/29"]) self.cli_set(tunnel_path + [tunnel_if, "encapsulation", tunnel_encapsulation]) self.cli_set(tunnel_path + [tunnel_if, "source-address", tunnel_source]) - self.cli_set(tunnel_path + [tunnel_if, "multicast", "enable"]) + self.cli_set(tunnel_path + [tunnel_if, "enable-multicast"]) self.cli_set(tunnel_path + [tunnel_if, "parameters", "ip", "key", "1"]) # NHRP diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 51c947537..a6850db71 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -20,7 +20,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'ospfd' base_path = ['protocols', 'ospf'] @@ -56,7 +56,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults @@ -70,20 +70,35 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['auto-cost', 'reference-bandwidth', bandwidth]) self.cli_set(base_path + ['parameters', 'router-id', router_id]) self.cli_set(base_path + ['parameters', 'abr-type', abr_type]) + self.cli_set(base_path + ['parameters', 'opaque-lsa']) + self.cli_set(base_path + ['parameters', 'rfc1583-compatibility']) self.cli_set(base_path + ['log-adjacency-changes', 'detail']) self.cli_set(base_path + ['default-metric', metric]) + self.cli_set(base_path + ['passive-interface', 'default']) + self.cli_set(base_path + ['area', '10', 'area-type', 'stub']) + self.cli_set(base_path + ['area', '10', 'network', '10.0.0.0/16']) + self.cli_set(base_path + ['area', '10', 'range', '10.0.1.0/24']) + self.cli_set(base_path + ['area', '10', 'range', '10.0.2.0/24', 'not-advertise']) # commit changes self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' compatible rfc1583', frrconfig) self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig) self.assertIn(f' ospf router-id {router_id}', frrconfig) self.assertIn(f' ospf abr-type {abr_type}', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + self.assertIn(f' capability opaque', frrconfig) self.assertIn(f' default-metric {metric}', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + self.assertIn(f' area 10 stub', frrconfig) + self.assertIn(f' network 10.0.0.0/16 area 10', frrconfig) + self.assertIn(f' area 10 range 10.0.1.0/24', frrconfig) + self.assertNotIn(f' area 10 range 10.0.1.0/24 not-advertise', frrconfig) + self.assertIn(f' area 10 range 10.0.2.0/24 not-advertise', frrconfig) def test_ospf_03_access_list(self): @@ -101,7 +116,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults for ptotocol in protocols: @@ -122,7 +137,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -132,7 +147,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -144,6 +159,12 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): on_startup = '30' on_shutdown = '60' refresh = '50' + aggregation_timer = '100' + summary_nets = { + '10.0.1.0/24' : {}, + '10.0.2.0/24' : {'tag' : '50'}, + '10.0.3.0/24' : {'no_advertise' : {}}, + } self.cli_set(base_path + ['distance', 'global', global_distance]) self.cli_set(base_path + ['distance', 'ospf', 'external', external]) @@ -155,11 +176,20 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['mpls-te', 'enable']) self.cli_set(base_path + ['refresh', 'timers', refresh]) + self.cli_set(base_path + ['aggregation', 'timer', aggregation_timer]) + + for summary, summary_options in summary_nets.items(): + self.cli_set(base_path + ['summary-address', summary]) + if 'tag' in summary_options: + self.cli_set(base_path + ['summary-address', summary, 'tag', summary_options['tag']]) + if 'no_advertise' in summary_options: + self.cli_set(base_path + ['summary-address', summary, 'no-advertise']) + # commit changes self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' mpls-te on', frrconfig) self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default @@ -169,12 +199,20 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f' max-metric router-lsa on-shutdown {on_shutdown}', frrconfig) self.assertIn(f' refresh timer {refresh}', frrconfig) + self.assertIn(f' aggregation timer {aggregation_timer}', frrconfig) + for summary, summary_options in summary_nets.items(): + self.assertIn(f' summary-address {summary}', frrconfig) + if 'tag' in summary_options: + tag = summary_options['tag'] + self.assertIn(f' summary-address {summary} tag {tag}', frrconfig) + if 'no_advertise' in summary_options: + self.assertIn(f' summary-address {summary} no-advertise', frrconfig) # enable inter-area self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area]) self.cli_commit() - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) @@ -190,7 +228,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) for neighbor in neighbors: self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default @@ -209,7 +247,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) for protocol in redistribute: self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -236,7 +274,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} transmit-delay {transmit} dead-interval {dead}', frrconfig) @@ -268,7 +306,12 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' passive-interface default', frrconfig) + for interface in interfaces: + # Can not use daemon for getFRRconfig() as bandwidth parameter belongs to zebra process config = self.getFRRconfig(f'interface {interface}') self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf authentication-key {password}', config) @@ -281,25 +324,16 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertIn(f' no ip ospf passive', config) self.assertIn(f' bandwidth {bandwidth}', config) - def test_ospf_10_zebra_route_map(self): - # Implemented because of T3328 - self.cli_set(base_path + ['route-map', route_map]) - # commit changes - self.cli_commit() - - # Verify FRR configuration - zebra_route_map = f'ip protocol ospf route-map {route_map}' - frrconfig = self.getFRRconfig(zebra_route_map) - self.assertIn(zebra_route_map, frrconfig) - - # Remove the route-map again - self.cli_delete(base_path + ['route-map']) - # commit changes + # T5467: Remove interface from OSPF process and VRF + self.cli_delete(base_path + ['interface']) self.cli_commit() - # Verify FRR configuration - frrconfig = self.getFRRconfig(zebra_route_map) - self.assertNotIn(zebra_route_map, frrconfig) + for interface in interfaces: + # T5467: It must also be removed from FRR config + frrconfig = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertNotIn(f'interface {interface}', frrconfig) + # There should be no OSPF related command at all under the interface + self.assertNotIn(f' ip ospf', frrconfig) def test_ospf_11_interface_area(self): area = '0' @@ -317,11 +351,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}') + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf area {area}', config) @@ -333,8 +367,10 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): vrf = 'blue' vrf_base = ['vrf', 'name', vrf] vrf_iface = 'eth1' + area = '1' + self.cli_set(vrf_base + ['table', table]) - self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface]) + self.cli_set(vrf_base + ['protocols', 'ospf', 'interface', vrf_iface, 'area', area]) self.cli_set(['interfaces', 'ethernet', vrf_iface, 'vrf', vrf]) # Also set a default VRF OSPF config @@ -342,16 +378,31 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults - frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}') + frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=PROCESS_NAME) self.assertIn(f'router ospf vrf {vrf}', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {vrf_iface}', frrconfig) + self.assertIn(f' ip ospf area {area}', frrconfig) + + # T5467: Remove interface from OSPF process and VRF + self.cli_delete(vrf_base + ['protocols', 'ospf', 'interface']) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + self.cli_commit() + + # T5467: It must also be removed from FRR config + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + self.assertNotIn(f'interface {vrf_iface}', frrconfig) + # There should be no OSPF related command at all under the interface + self.assertNotIn(f' ip ospf', frrconfig) + # cleanup self.cli_delete(['vrf', 'name', vrf]) self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) @@ -363,7 +414,6 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): area = '0.0.0.10' network = '10.0.0.0/8' - self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'action', 'permit']) self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'source', 'any']) self.cli_set(['policy', 'access-list', acl, 'rule', seq, 'destination', 'any']) @@ -374,7 +424,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default self.assertIn(f' network {network} area {area}', frrconfig) @@ -408,13 +458,83 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - frrconfig = self.getFRRconfig('router ospf') + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) self.assertIn(f' segment-routing on', frrconfig) self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig) self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig) self.assertIn(f' segment-routing prefix {prefix_one} index {prefix_one_value} explicit-null', frrconfig) self.assertIn(f' segment-routing prefix {prefix_two} index {prefix_two_value} no-php-flag', frrconfig) + def test_ospf_15_ldp_sync(self): + holddown = "500" + interface = 'lo' + interfaces = Section.interfaces('ethernet') + + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['ldp-sync', 'holddown', holddown]) + + # Commit main OSPF changes + self.cli_commit() + + # Verify main OSPF changes + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) + self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig) + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'holddown', holddown]) + + # Commit interface changes for holddown + self.cli_commit() + + for interface in interfaces: + # Verify interface changes for holddown + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf dead-interval 40', config) + self.assertIn(f' ip ospf mpls ldp-sync', config) + self.assertIn(f' ip ospf mpls ldp-sync holddown {holddown}', config) + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, 'ldp-sync', 'disable']) + + # Commit interface changes for disable + self.cli_commit() + + for interface in interfaces: + # Verify interface changes for disable + config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ip ospf dead-interval 40', config) + self.assertIn(f' no ip ospf mpls ldp-sync', config) + + def test_ospf_16_graceful_restart(self): + period = '300' + supported_grace_time = '400' + router_ids = ['192.0.2.1', '192.0.2.2'] + + self.cli_set(base_path + ['capability', 'opaque']) + self.cli_set(base_path + ['graceful-restart', 'grace-period', period]) + self.cli_set(base_path + ['graceful-restart', 'helper', 'planned-only']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'no-strict-lsa-checking']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'supported-grace-time', supported_grace_time]) + for router_id in router_ids: + self.cli_set(base_path + ['graceful-restart', 'helper', 'enable', 'router-id', router_id]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME) + self.assertIn(f'router ospf', frrconfig) + self.assertIn(f' capability opaque', frrconfig) + self.assertIn(f' graceful-restart grace-period {period}', frrconfig) + self.assertIn(f' graceful-restart helper planned-only', frrconfig) + self.assertIn(f' no graceful-restart helper strict-lsa-checking', frrconfig) + self.assertIn(f' graceful-restart helper supported-grace-time {supported_grace_time}', frrconfig) + for router_id in router_ids: + self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index fa80ad555..0d6c6c691 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -20,7 +20,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'ospf6d' base_path = ['protocols', 'ospfv3'] @@ -74,7 +74,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {default_area} range {prefix}', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) @@ -82,7 +82,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig) for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon='ospf6d') + if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'ipv6 ospf6 area {default_area}', if_config) self.cli_delete(['policy', 'access-list6', acl_name]) @@ -103,7 +103,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' distance {dist_global}', frrconfig) self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig) @@ -123,7 +123,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) for protocol in redistribute: self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig) @@ -154,13 +154,13 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) cost = '100' priority = '10' for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon='ospf6d') + if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) self.assertIn(f'interface {interface}', if_config) self.assertIn(f' ipv6 ospf6 bfd', if_config) self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config) @@ -172,6 +172,15 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): cost = str(int(cost) + 10) priority = str(int(priority) + 5) + # Cleanup interfaces + self.cli_delete(base_path + ['interface']) + self.cli_commit() + + for interface in interfaces: + if_config = self.getFRRconfig(f'interface {interface}', daemon=PROCESS_NAME) + # There should be no OSPF6 configuration at all after interface removal + self.assertNotIn(f' ipv6 ospf6', if_config) + def test_ospfv3_05_area_stub(self): area_stub = '23' @@ -184,7 +193,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_stub} stub', frrconfig) self.assertIn(f' area {area_stub_nosum} stub no-summary', frrconfig) @@ -210,7 +219,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_nssa} nssa', frrconfig) self.assertIn(f' area {area_nssa_nosum} nssa default-information-originate no-summary', frrconfig) @@ -230,7 +239,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -239,7 +248,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -265,21 +274,58 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', daemon='ospf6d') + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon='ospf6d') + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ipv6 ospf6 bfd', frrconfig) - frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon='ospf6d') + frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=PROCESS_NAME) self.assertIn(f'router ospf6 vrf {vrf}', frrconfig) self.assertIn(f' ospf6 router-id {router_id_vrf}', frrconfig) + # T5467: Remove interface from OSPF process and VRF + self.cli_delete(vrf_base + ['protocols', 'ospfv3', 'interface']) + self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + self.cli_commit() + + # T5467: It must also be removed from FRR config + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=PROCESS_NAME) + self.assertNotIn(f'interface {vrf_iface}', frrconfig) + # There should be no OSPF related command at all under the interface + self.assertNotIn(f' ipv6 ospf6', frrconfig) + # cleanup self.cli_delete(['vrf', 'name', vrf]) self.cli_delete(['interfaces', 'ethernet', vrf_iface, 'vrf']) + + def test_ospfv3_09_graceful_restart(self): + period = '300' + supported_grace_time = '400' + router_ids = ['192.0.2.1', '192.0.2.2'] + + self.cli_set(base_path + ['graceful-restart', 'grace-period', period]) + self.cli_set(base_path + ['graceful-restart', 'helper', 'planned-only']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'lsa-check-disable']) + self.cli_set(base_path + ['graceful-restart', 'helper', 'supported-grace-time', supported_grace_time]) + for router_id in router_ids: + self.cli_set(base_path + ['graceful-restart', 'helper', 'enable', 'router-id', router_id]) + + # commit changes + self.cli_commit() + + # Verify FRR ospfd configuration + frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME) + self.assertIn(f'router ospf6', frrconfig) + self.assertIn(f' graceful-restart grace-period {period}', frrconfig) + self.assertIn(f' graceful-restart helper planned-only', frrconfig) + self.assertIn(f' graceful-restart helper lsa-check-disable', frrconfig) + self.assertIn(f' graceful-restart helper supported-grace-time {supported_grace_time}', frrconfig) + for router_id in router_ids: + self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py new file mode 100755 index 000000000..1be12836d --- /dev/null +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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.ifconfig import Section +from vyos.utils.process import process_named_running + +PROCESS_NAME = 'pim6d' +base_path = ['protocols', 'pim6'] + + +class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) + self.cli_commit() + + def test_pim6_01_mld_simple(self): + # commit changes + interfaces = Section.interfaces('ethernet') + + for interface in interfaces: + self.cli_set(base_path + ['interface', interface]) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld', config) + self.assertNotIn(f' ipv6 mld version 1', config) + + # Change to MLD version 1 + for interface in interfaces: + self.cli_set(base_path + ['interface', + interface, 'mld', 'version', '1']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld', config) + self.assertIn(f' ipv6 mld version 1', config) + + def test_pim6_02_mld_join(self): + # commit changes + interfaces = Section.interfaces('ethernet') + + # Use an invalid multiple group address + for interface in interfaces: + self.cli_set(base_path + ['interface', + interface, 'mld', 'join', 'fd00::1234']) + + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface']) + + # Use a valid multiple group address + for interface in interfaces: + self.cli_set(base_path + ['interface', + interface, 'mld', 'join', 'ff18::1234']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld join ff18::1234', config) + + # Join a source-specific multicast group + for interface in interfaces: + self.cli_set(base_path + ['interface', interface, + 'mld', 'join', 'ff38::5678', 'source', '2001:db8::5678']) + + self.cli_commit() + + # Verify FRR pim6d configuration + for interface in interfaces: + config = self.getFRRconfig( + f'interface {interface}', daemon=PROCESS_NAME) + self.assertIn(f'interface {interface}', config) + self.assertIn(f' ipv6 mld join ff38::5678 2001:db8::5678', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py index 11385adb5..925499fc8 100755 --- a/smoketest/scripts/cli/test_protocols_rip.py +++ b/smoketest/scripts/cli/test_protocols_rip.py @@ -19,7 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.ifconfig import Section -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'ripd' acl_in = '198' diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py index 53336a533..0a8ce7eef 100755 --- a/smoketest/scripts/cli/test_protocols_ripng.py +++ b/smoketest/scripts/cli/test_protocols_ripng.py @@ -19,7 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.ifconfig import Section -from vyos.util import process_named_running +from vyos.utils.process import process_named_running PROCESS_NAME = 'ripngd' acl_in = '198' diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py index e5e45565b..f4aedcbc3 100755 --- a/smoketest/scripts/cli/test_protocols_rpki.py +++ b/smoketest/scripts/cli/test_protocols_rpki.py @@ -20,8 +20,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import process_named_running +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running base_path = ['protocols', 'rpki'] PROCESS_NAME = 'bgpd' diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index 19efe7786..abf1080ab 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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,7 +20,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.template import is_ipv6 -from vyos.util import get_interface_config +from vyos.utils.network import get_interface_config base_path = ['protocols', 'static'] vrf_path = ['protocols', 'vrf'] @@ -31,6 +31,8 @@ routes = { '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.140' : { 'bfd_source' : '192.0.2.10' }, }, 'interface' : { 'eth0' : { 'distance' : '130' }, @@ -67,6 +69,8 @@ routes = { '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' }, @@ -95,6 +99,7 @@ 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 @@ -116,6 +121,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.cli_commit() def test_01_static(self): + bfd_profile = 'vyos-test' for route, route_config in routes.items(): route_type = 'route' if is_ipv6(route): @@ -132,6 +138,10 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): 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', 'profile', bfd_profile ]) + if 'bfd_source' in next_hop_config: + self.cli_set(base + ['next-hop', next_hop, 'bfd', 'multi-hop', 'source', next_hop_config['bfd_source'], 'profile', bfd_profile]) if 'interface' in route_config: @@ -186,6 +196,10 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): 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 profile ' + bfd_profile + if 'bfd_source' in next_hop_config: + tmp += ' bfd multi-hop source ' + next_hop_config['bfd_source'] + ' profile ' + bfd_profile if 'disable' in next_hop_config: self.assertNotIn(tmp, frrconfig) @@ -433,30 +447,5 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, frrconfig) - def test_04_static_zebra_route_map(self): - # Implemented because of T3328 - route_map = 'foo-static-in' - self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) - - self.cli_set(base_path + ['route-map', route_map]) - # commit changes - self.cli_commit() - - # Verify FRR configuration - zebra_route_map = f'ip protocol static route-map {route_map}' - frrconfig = self.getFRRconfig(zebra_route_map) - self.assertIn(zebra_route_map, frrconfig) - - # Remove the route-map again - self.cli_delete(base_path + ['route-map']) - # commit changes - self.cli_commit() - - # Verify FRR configuration - frrconfig = self.getFRRconfig(zebra_route_map) - self.assertNotIn(zebra_route_map, frrconfig) - - self.cli_delete(['policy', 'route-map', route_map]) - if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_static_arp.py b/smoketest/scripts/cli/test_protocols_static_arp.py index b61d8f854..7f8047249 100755 --- a/smoketest/scripts/cli/test_protocols_static_arp.py +++ b/smoketest/scripts/cli/test_protocols_static_arp.py @@ -19,7 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import cmd +from vyos.utils.process import cmd base_path = ['protocols', 'static', 'arp'] interface = 'eth0' diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py new file mode 100755 index 000000000..3743be788 --- /dev/null +++ b/smoketest/scripts/cli/test_qos.py @@ -0,0 +1,547 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from json import loads +from base_vyostest_shim import VyOSUnitTestSHIM + +from vyos.configsession import ConfigSessionError +from vyos.ifconfig import Section +from vyos.utils.process import cmd + +base_path = ['qos'] + +def get_tc_qdisc_json(interface) -> dict: + tmp = cmd(f'tc -detail -json qdisc show dev {interface}') + tmp = loads(tmp) + return next(iter(tmp)) + +def get_tc_filter_json(interface, direction) -> list: + if direction not in ['ingress', 'egress']: + raise ValueError() + tmp = cmd(f'tc -detail -json filter show dev {interface} {direction}') + tmp = loads(tmp) + return tmp + +class TestQoS(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestQoS, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + # We only test on physical interfaces and not VLAN (sub-)interfaces + cls._interfaces = [] + if 'TEST_ETH' in os.environ: + tmp = os.environ['TEST_ETH'].split() + cls._interfaces = tmp + else: + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) + + def tearDown(self): + # delete testing SSH config + self.cli_delete(base_path) + self.cli_commit() + + def test_01_cake(self): + bandwidth = 1000000 + rtt = 200 + + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'rtt', str(rtt)]) + self.cli_set(base_path + ['policy', 'cake', policy_name, 'flow-isolation', 'dual-src-host']) + + bandwidth += 1000000 + rtt += 20 + + # commit changes + self.cli_commit() + + bandwidth = 1000000 + rtt = 200 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('cake', tmp['kind']) + # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) + self.assertEqual(int(bandwidth *125), tmp['options']['bandwidth']) + # RTT internally is in us + self.assertEqual(int(rtt *1000), tmp['options']['rtt']) + self.assertEqual('dual-srchost', tmp['options']['flowmode']) + self.assertFalse(tmp['options']['ingress']) + self.assertFalse(tmp['options']['nat']) + self.assertTrue(tmp['options']['raw']) + + bandwidth += 1000000 + rtt += 20 + + def test_02_drop_tail(self): + queue_limit = 50 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'drop-tail', policy_name, 'queue-limit', str(queue_limit)]) + + queue_limit += 10 + + # commit changes + self.cli_commit() + + queue_limit = 50 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('pfifo', tmp['kind']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + queue_limit += 10 + + def test_03_fair_queue(self): + hash_interval = 10 + queue_limit = 5 + policy_type = 'fair-queue' + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'hash-interval', str(hash_interval)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + + hash_interval += 1 + queue_limit += 1 + + # commit changes + self.cli_commit() + + hash_interval = 10 + queue_limit = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('sfq', tmp['kind']) + self.assertEqual(hash_interval, tmp['options']['perturb']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + hash_interval += 1 + queue_limit += 1 + + def test_04_fq_codel(self): + policy_type = 'fq-codel' + codel_quantum = 1500 + flows = 512 + interval = 100 + queue_limit = 2048 + target = 5 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'codel-quantum', str(codel_quantum)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'flows', str(flows)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'interval', str(interval)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'target', str(target)]) + + codel_quantum += 10 + flows += 2 + interval += 10 + queue_limit += 512 + target += 1 + + # commit changes + self.cli_commit() + + codel_quantum = 1500 + flows = 512 + interval = 100 + queue_limit = 2048 + target = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('fq_codel', tmp['kind']) + self.assertEqual(codel_quantum, tmp['options']['quantum']) + self.assertEqual(flows, tmp['options']['flows']) + self.assertEqual(queue_limit, tmp['options']['limit']) + + # due to internal rounding we need to substract 1 from interval and target after converting to milliseconds + # configuration of: + # tc qdisc add dev eth0 root fq_codel quantum 1500 flows 512 interval 100ms limit 2048 target 5ms noecn + # results in: tc -j qdisc show dev eth0 + # [{"kind":"fq_codel","handle":"8046:","root":true,"refcnt":3,"options":{"limit":2048,"flows":512, + # "quantum":1500,"target":4999,"interval":99999,"memory_limit":33554432,"drop_batch":64}}] + self.assertAlmostEqual(tmp['options']['interval'], interval *1000, delta=1) + self.assertAlmostEqual(tmp['options']['target'], target *1000 -1, delta=1) + + codel_quantum += 10 + flows += 2 + interval += 10 + queue_limit += 512 + target += 1 + + def test_05_limiter(self): + qos_config = { + '1' : { + 'bandwidth' : '1000000', + 'match4' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + '2' : { + 'bandwidth' : '1000000', + 'match6' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + } + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'egress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # set default bandwidth parameter for all remaining connections + self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'bandwidth', '500000']) + + for qos_class, qos_class_config in qos_config.items(): + qos_class_base = base_path + ['policy', 'limiter', policy_name, 'class', qos_class] + + if 'match4' in qos_class_config: + for match, match_config in qos_class_config['match4'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) + + if 'match6' in qos_class_config: + for match, match_config in qos_class_config['match6'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) + + if 'bandwidth' in qos_class_config: + self.cli_set(qos_class_base + ['bandwidth', qos_class_config['bandwidth']]) + + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + for filter in get_tc_filter_json(interface, 'ingress'): + # bail out early if filter has no attached action + if 'options' not in filter or 'actions' not in filter['options']: + continue + + for qos_class, qos_class_config in qos_config.items(): + # Every flowid starts with ffff and we encopde the class number after the colon + if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': + continue + + ip_hdr_offset = 20 + if 'match6' in qos_class_config: + ip_hdr_offset = 40 + + self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) + if 'dport' in match_config: + dport = int(match_config['dport']) + self.assertEqual(f'{dport:x}', filter['options']['match']['value']) + + def test_06_network_emulator(self): + policy_type = 'network-emulator' + + bandwidth = 1000000 + corruption = 1 + delay = 2 + duplicate = 3 + loss = 4 + queue_limit = 5 + reordering = 6 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + + self.cli_set(base_path + ['policy', policy_type, policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'corruption', str(corruption)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'delay', str(delay)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'duplicate', str(duplicate)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'loss', str(loss)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'queue-limit', str(queue_limit)]) + self.cli_set(base_path + ['policy', policy_type, policy_name, 'reordering', str(reordering)]) + + bandwidth += 1000000 + corruption += 1 + delay += 1 + duplicate +=1 + loss += 1 + queue_limit += 1 + reordering += 1 + + # commit changes + self.cli_commit() + + bandwidth = 1000000 + corruption = 1 + delay = 2 + duplicate = 3 + loss = 4 + queue_limit = 5 + reordering = 6 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + self.assertEqual('netem', tmp['kind']) + + self.assertEqual(int(bandwidth *125), tmp['options']['rate']['rate']) + # values are in % + self.assertEqual(corruption/100, tmp['options']['corrupt']['corrupt']) + self.assertEqual(duplicate/100, tmp['options']['duplicate']['duplicate']) + self.assertEqual(loss/100, tmp['options']['loss-random']['loss']) + self.assertEqual(reordering/100, tmp['options']['reorder']['reorder']) + self.assertEqual(delay/1000, tmp['options']['delay']['delay']) + + self.assertEqual(queue_limit, tmp['options']['limit']) + + bandwidth += 1000000 + corruption += 1 + delay += 1 + duplicate += 1 + loss += 1 + queue_limit += 1 + reordering += 1 + + def test_07_priority_queue(self): + priorities = ['1', '2', '3', '4', '5'] + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'priority-queue', policy_name, 'default', 'queue-limit', '10']) + + for priority in priorities: + prio_base = base_path + ['policy', 'priority-queue', policy_name, 'class', priority] + self.cli_set(prio_base + ['match', f'prio-{priority}', 'ip', 'destination', 'port', str(1000 + int(priority))]) + + # commit changes + self.cli_commit() + + def test_08_random_detect(self): + self.skipTest('tc returns invalid JSON here - needs iproute2 fix') + bandwidth = 5000 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'random-detect', policy_name, 'bandwidth', str(bandwidth)]) + + bandwidth += 1000 + + # commit changes + self.cli_commit() + + bandwidth = 5000 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + import pprint + pprint.pprint(tmp) + + def test_09_rate_control(self): + bandwidth = 5000 + burst = 20 + latency = 5 + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'bandwidth', str(bandwidth)]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'burst', str(burst)]) + self.cli_set(base_path + ['policy', 'rate-control', policy_name, 'latency', str(latency)]) + + bandwidth += 1000 + burst += 5 + latency += 1 + # commit changes + self.cli_commit() + + bandwidth = 5000 + burst = 20 + latency = 5 + for interface in self._interfaces: + tmp = get_tc_qdisc_json(interface) + + self.assertEqual('tbf', tmp['kind']) + self.assertEqual(0, tmp['options']['mpu']) + # TC store rates as a 32-bit unsigned integer in bps (Bytes per second) + self.assertEqual(int(bandwidth * 125), tmp['options']['rate']) + + bandwidth += 1000 + burst += 5 + latency += 1 + + def test_10_round_robin(self): + qos_config = { + '1' : { + 'match4' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + '2' : { + 'match6' : { + 'ssh' : { 'dport' : '22', }, + }, + }, + } + + first = True + for interface in self._interfaces: + policy_name = f'qos-policy-{interface}' + + if first: + self.cli_set(base_path + ['interface', interface, 'ingress', policy_name]) + # verify() - selected QoS policy on interface only supports egress + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface', interface, 'ingress', policy_name]) + first = False + + self.cli_set(base_path + ['interface', interface, 'egress', policy_name]) + + for qos_class, qos_class_config in qos_config.items(): + qos_class_base = base_path + ['policy', 'round-robin', policy_name, 'class', qos_class] + + if 'match4' in qos_class_config: + for match, match_config in qos_class_config['match4'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ip', 'destination', 'port', match_config['dport']]) + + if 'match6' in qos_class_config: + for match, match_config in qos_class_config['match6'].items(): + if 'dport' in match_config: + self.cli_set(qos_class_base + ['match', match, 'ipv6', 'destination', 'port', match_config['dport']]) + + + # commit changes + self.cli_commit() + + for interface in self._interfaces: + import pprint + tmp = get_tc_qdisc_json(interface) + self.assertEqual('drr', tmp['kind']) + + for filter in get_tc_filter_json(interface, 'ingress'): + # bail out early if filter has no attached action + if 'options' not in filter or 'actions' not in filter['options']: + continue + + for qos_class, qos_class_config in qos_config.items(): + # Every flowid starts with ffff and we encopde the class number after the colon + if 'flowid' not in filter['options'] or filter['options']['flowid'] != f'ffff:{qos_class}': + continue + + ip_hdr_offset = 20 + if 'match6' in qos_class_config: + ip_hdr_offset = 40 + + self.assertEqual(ip_hdr_offset, filter['options']['match']['off']) + if 'dport' in match_config: + dport = int(match_config['dport']) + self.assertEqual(f'{dport:x}', filter['options']['match']['value']) + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcp-relay.py b/smoketest/scripts/cli/test_service_dhcp-relay.py index bbfd9e032..59c4b59a9 100755 --- a/smoketest/scripts/cli/test_service_dhcp-relay.py +++ b/smoketest/scripts/cli/test_service_dhcp-relay.py @@ -20,8 +20,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'dhcrelay' RELAY_CONF = '/run/dhcp-relay/dhcrelay.conf' @@ -82,6 +82,43 @@ class TestServiceDHCPRelay(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) + def test_relay_interfaces(self): + max_size = '800' + hop_count = '20' + agents_packets = 'append' + servers = ['192.0.2.1', '192.0.2.2'] + listen_iface = 'eth0' + up_iface = 'eth1' + + self.cli_set(base_path + ['interface', up_iface]) + self.cli_set(base_path + ['listen-interface', listen_iface]) + # check validate() - backward interface plus listen_interface + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_delete(base_path + ['interface']) + + self.cli_set(base_path + ['upstream-interface', up_iface]) + + for server in servers: + self.cli_set(base_path + ['server', server]) + + # commit changes + self.cli_commit() + + # Check configured port + config = read_file(RELAY_CONF) + + # Test configured relay interfaces + self.assertIn(f'-id {listen_iface}', config) + self.assertIn(f'-iu {up_iface}', config) + + # Test relay servers + for server in servers: + self.assertIn(f' {server}', config) + + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py index 9c9d6d9f1..093e43494 100755 --- a/smoketest/scripts/cli/test_service_dhcp-server.py +++ b/smoketest/scripts/cli/test_service_dhcp-server.py @@ -19,8 +19,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file from vyos.template import address_from_cidr from vyos.template import inc_ip from vyos.template import dec_ip @@ -102,16 +102,17 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): def test_dhcp_single_pool_options(self): shared_net_name = 'SMOKE-0815' - range_0_start = inc_ip(subnet, 10) - range_0_stop = inc_ip(subnet, 20) - smtp_server = '1.2.3.4' - time_server = '4.3.2.1' - tftp_server = 'tftp.vyos.io' - search_domains = ['foo.vyos.net', 'bar.vyos.net'] - bootfile_name = 'vyos' - bootfile_server = '192.0.2.1' - wpad = 'http://wpad.vyos.io/foo/bar' - server_identifier = bootfile_server + range_0_start = inc_ip(subnet, 10) + range_0_stop = inc_ip(subnet, 20) + smtp_server = '1.2.3.4' + time_server = '4.3.2.1' + tftp_server = 'tftp.vyos.io' + search_domains = ['foo.vyos.net', 'bar.vyos.net'] + bootfile_name = 'vyos' + bootfile_server = '192.0.2.1' + wpad = 'http://wpad.vyos.io/foo/bar' + server_identifier = bootfile_server + ipv6_only_preferred = '300' pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] # we use the first subnet IP address as default gateway @@ -132,6 +133,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.cli_set(pool + ['server-identifier', server_identifier]) self.cli_set(pool + ['static-route', '10.0.0.0/24', 'next-hop', '192.0.2.1']) + self.cli_set(pool + ['ipv6-only-preferred', ipv6_only_preferred]) # check validate() - No DHCP address range or active static-mapping set with self.assertRaises(ConfigSessionError): @@ -169,6 +171,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase): self.assertIn(f'max-lease-time 86400;', config) self.assertIn(f'range {range_0_start} {range_0_stop};', config) self.assertIn(f'set shared-networkname = "{shared_net_name}";', config) + self.assertIn(f'option rfc8925-ipv6-only-preferred {ipv6_only_preferred};', config) # weird syntax for those static routes self.assertIn(f'option rfc3442-static-route 24,10,0,0,192,0,2,1, 0,192,0,2,1;', config) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-relay.py b/smoketest/scripts/cli/test_service_dhcpv6-relay.py index fc206435b..4487f4b0f 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-relay.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-relay.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -21,8 +21,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.template import address_from_cidr -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'dhcrelay' RELAY_CONF = '/run/dhcp-relay/dhcrelay6.conf' @@ -34,22 +34,30 @@ listen_addr = '2001:db8:ffff::1/64' interfaces = [] class TestServiceDHCPv6Relay(VyOSUnitTestSHIM.TestCase): - def setUp(self): - for tmp in interfaces: + @classmethod + def setUpClass(cls): + super(TestServiceDHCPv6Relay, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + for tmp in Section.interfaces('ethernet', vlan=False): + interfaces.append(tmp) listen = listen_addr if tmp == upstream_if: listen = upstream_if_addr - self.cli_set(['interfaces', 'ethernet', tmp, 'address', listen]) + cls.cli_set(cls, ['interfaces', 'ethernet', tmp, 'address', listen]) - def tearDown(self): - self.cli_delete(base_path) + @classmethod + def tearDownClass(cls): for tmp in interfaces: listen = listen_addr if tmp == upstream_if: listen = upstream_if_addr - self.cli_delete(['interfaces', 'ethernet', tmp, 'address', listen]) + cls.cli_delete(cls, ['interfaces', 'ethernet', tmp, 'address', listen]) - self.cli_commit() + super(TestServiceDHCPv6Relay, cls).tearDownClass() def test_relay_default(self): dhcpv6_server = '2001:db8::ffff' @@ -100,9 +108,5 @@ class TestServiceDHCPv6Relay(VyOSUnitTestSHIM.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) if __name__ == '__main__': - for tmp in Section.interfaces('ethernet'): - if '.' not in tmp: - interfaces.append(tmp) - unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py index f83453323..4d9dabc3f 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-server.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -20,8 +20,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.template import inc_ip -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'dhcpd' DHCPD_CONF = '/run/dhcp-server/dhcpdv6.conf' diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index 90d10d40b..ee8a07b37 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2023 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 @@ -14,148 +14,179 @@ # 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 re import os import unittest +import tempfile from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_running -PROCESS_NAME = 'ddclient' DDCLIENT_CONF = '/run/ddclient/ddclient.conf' +DDCLIENT_PID = '/run/ddclient/ddclient.pid' base_path = ['service', 'dns', 'dynamic'] hostname = 'test.ddns.vyos.io' +zone = 'vyos.io' +password = 'paSS_@4ord' interface = 'eth0' -def get_config_value(key): - tmp = cmd(f'sudo cat {DDCLIENT_CONF}') - tmp = re.findall(r'\n?{}=+(.*)'.format(key), tmp) - tmp = tmp[0].rstrip(',') - return tmp - class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): def tearDown(self): + # Check for running process + self.assertTrue(process_running(DDCLIENT_PID)) + # Delete DDNS configuration self.cli_delete(base_path) self.cli_commit() - def test_dyndns_service(self): - ddns = ['interface', interface, 'service'] - services = ['cloudflare', 'afraid', 'dyndns', 'zoneedit'] + # PID file must no londer exist after process exited + self.assertFalse(os.path.exists(DDCLIENT_PID)) - for service in services: - user = 'vyos_user' - password = 'vyos_pass' - zone = 'vyos.io' + # IPv4 standard DDNS service configuration + def test_01_dyndns_service_standard(self): + ddns = ['address', interface, 'service'] + services = {'cloudflare': {'protocol': 'cloudflare'}, + 'freedns': {'protocol': 'freedns', 'username': 'vyos_user'}, + 'zoneedit': {'protocol': 'zoneedit1', 'username': 'vyos_user'}} + + for svc, details in services.items(): + # Always start with a clean CLI instance self.cli_delete(base_path) - self.cli_set(base_path + ddns + [service, 'host-name', hostname]) - self.cli_set(base_path + ddns + [service, 'login', user]) - self.cli_set(base_path + ddns + [service, 'password', password]) - self.cli_set(base_path + ddns + [service, 'zone', zone]) + + self.cli_set(base_path + ddns + [svc, 'host-name', hostname]) + self.cli_set(base_path + ddns + [svc, 'password', password]) + self.cli_set(base_path + ddns + [svc, 'zone', zone]) + for opt, value in details.items(): + self.cli_set(base_path + ddns + [svc, opt, value]) # commit changes - if service == 'cloudflare': - self.cli_commit() + if details['protocol'] == 'cloudflare': + pass else: - # zone option only works on cloudflare, an exception is raised - # for all others + # zone option does not work on all protocols, an exception is + # raised for all others with self.assertRaises(ConfigSessionError): self.cli_commit() - self.cli_delete(base_path + ddns + [service, 'zone', 'vyos.io']) - # commit changes again - now it should work - self.cli_commit() - - # we can only read the configuration file when we operate as 'root' - protocol = get_config_value('protocol') - login = get_config_value('login') - pwd = get_config_value('password') - - # some services need special treatment - protoname = service - if service == 'cloudflare': - tmp = get_config_value('zone') - self.assertTrue(tmp == zone) - elif service == 'afraid': - protoname = 'freedns' - elif service == 'dyndns': - protoname = 'dyndns2' - elif service == 'zoneedit': - protoname = 'zoneedit1' - - self.assertTrue(protocol == protoname) - self.assertTrue(login == user) - self.assertTrue(pwd == "'" + password + "'") - - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - def test_dyndns_rfc2136(self): - # Check if DDNS service can be configured and runs - ddns = ['interface', interface, 'rfc2136', 'vyos'] - ddns_key_file = '/config/auth/my.key' - - self.cli_set(base_path + ddns + ['key', ddns_key_file]) - self.cli_set(base_path + ddns + ['record', 'test.ddns.vyos.io']) - self.cli_set(base_path + ddns + ['server', 'ns1.vyos.io']) - self.cli_set(base_path + ddns + ['ttl', '300']) - self.cli_set(base_path + ddns + ['zone', 'vyos.io']) + self.cli_delete(base_path + ddns + [svc, 'zone', zone]) - # ensure an exception will be raised as no key is present - if os.path.exists(ddns_key_file): - os.unlink(ddns_key_file) - - # check validate() - the key file does not exist yet - with self.assertRaises(ConfigSessionError): + # commit changes self.cli_commit() - with open(ddns_key_file, 'w') as f: - f.write('S3cretKey') - - # commit changes - self.cli_commit() - - # TODO: inspect generated configuration file - - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - def test_dyndns_ipv6(self): - ddns = ['interface', interface, 'service', 'dynv6'] + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + # default value 300 seconds + self.assertIn(f'daemon=300', ddclient_conf) + self.assertIn(f'use=if', ddclient_conf) + self.assertIn(f'if={interface}', ddclient_conf) + self.assertIn(f'password={password}', ddclient_conf) + + for opt in details.keys(): + if opt == 'username': + login = details[opt] + self.assertIn(f'login={login}', ddclient_conf) + else: + tmp = details[opt] + self.assertIn(f'{opt}={tmp}', ddclient_conf) + + # IPv6 only DDNS service configuration + def test_02_dyndns_service_ipv6(self): + timeout = '60' + ddns = ['address', interface, 'service', 'dynv6'] proto = 'dyndns2' user = 'none' password = 'paSS_4ord' srv = 'ddns.vyos.io' + ip_version = 'ipv6' - self.cli_set(base_path + ['interface', interface, 'ipv6-enable']) - self.cli_set(base_path + ddns + ['host-name', hostname]) - self.cli_set(base_path + ddns + ['login', user]) - self.cli_set(base_path + ddns + ['password', password]) + self.cli_set(base_path + ['timeout', timeout]) + self.cli_set(base_path + ddns + ['ip-version', ip_version]) self.cli_set(base_path + ddns + ['protocol', proto]) self.cli_set(base_path + ddns + ['server', srv]) + self.cli_set(base_path + ddns + ['username', user]) + self.cli_set(base_path + ddns + ['password', password]) + self.cli_set(base_path + ddns + ['host-name', hostname]) # commit changes self.cli_commit() - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - - protocol = get_config_value('protocol') - login = get_config_value('login') - pwd = get_config_value('password') - server = get_config_value('server') - usev6 = get_config_value('usev6') - - # Check some generating config parameters - self.assertEqual(protocol, proto) - self.assertEqual(login, user) - self.assertEqual(pwd, f"'{password}'") - self.assertEqual(server, srv) - self.assertEqual(usev6, f"if, if={interface}") + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'daemon={timeout}', ddclient_conf) + self.assertIn(f'usev6=ifv6', ddclient_conf) + self.assertIn(f'ifv6={interface}', ddclient_conf) + self.assertIn(f'protocol={proto}', ddclient_conf) + self.assertIn(f'server={srv}', ddclient_conf) + self.assertIn(f'login={user}', ddclient_conf) + self.assertIn(f'password={password}', ddclient_conf) + + # IPv4+IPv6 dual DDNS service configuration + def test_03_dyndns_service_dual_stack(self): + ddns = ['address', interface, 'service'] + services = {'cloudflare': {'protocol': 'cloudflare', 'zone': 'vyos.io'}, + 'freedns': {'protocol': 'freedns', 'username': 'vyos_user'}} + password = 'vyos_pass' + ip_version = 'both' + + for svc, details in services.items(): + # Always start with a clean CLI instance + self.cli_delete(base_path) + + self.cli_set(base_path + ddns + [svc, 'host-name', hostname]) + self.cli_set(base_path + ddns + [svc, 'password', password]) + self.cli_set(base_path + ddns + [svc, 'ip-version', ip_version]) + for opt, value in details.items(): + self.cli_set(base_path + ddns + [svc, opt, value]) + + # commit changes + self.cli_commit() + + # Check the generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'usev4=ifv4', ddclient_conf) + self.assertIn(f'usev6=ifv6', ddclient_conf) + self.assertIn(f'ifv4={interface}', ddclient_conf) + self.assertIn(f'ifv6={interface}', ddclient_conf) + self.assertIn(f'password={password}', ddclient_conf) + + for opt in details.keys(): + if opt == 'username': + login = details[opt] + self.assertIn(f'login={login}', ddclient_conf) + else: + tmp = details[opt] + self.assertIn(f'{opt}={tmp}', ddclient_conf) + + def test_04_dyndns_rfc2136(self): + # Check if DDNS service can be configured and runs + ddns = ['address', interface, 'rfc2136', 'vyos'] + srv = 'ns1.vyos.io' + zone = 'vyos.io' + ttl = '300' + + with tempfile.NamedTemporaryFile(prefix='/config/auth/') as key_file: + key_file.write(b'S3cretKey') + + self.cli_set(base_path + ddns + ['server', srv]) + self.cli_set(base_path + ddns + ['zone', zone]) + self.cli_set(base_path + ddns + ['key', key_file.name]) + self.cli_set(base_path + ddns + ['ttl', ttl]) + self.cli_set(base_path + ddns + ['host-name', hostname]) + + # commit changes + self.cli_commit() + + # Check some generating config parameters + ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}') + self.assertIn(f'use=if', ddclient_conf) + self.assertIn(f'if={interface}', ddclient_conf) + self.assertIn(f'protocol=nsupdate', ddclient_conf) + self.assertIn(f'server={srv}', ddclient_conf) + self.assertIn(f'zone={zone}', ddclient_conf) + self.assertIn(f'password={key_file.name}', ddclient_conf) + self.assertIn(f'ttl={ttl}', ddclient_conf) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py index fe2682d50..bc50a4ffe 100755 --- a/smoketest/scripts/cli/test_service_dns_forwarding.py +++ b/smoketest/scripts/cli/test_service_dns_forwarding.py @@ -20,8 +20,9 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import read_file -from vyos.util import process_named_running +from vyos.template import bracketize_ipv6 +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running CONFIG_FILE = '/run/powerdns/recursor.conf' FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf' @@ -111,6 +112,10 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): tmp = get_config_value('serve-rfc1918') self.assertEqual(tmp, 'yes') + # verify default port configuration + tmp = get_config_value('local-port') + self.assertEqual(tmp, '53') + def test_dnssec(self): # DNSSEC option testing @@ -137,15 +142,20 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): for address in listen_adress: self.cli_set(base_path + ['listen-address', address]) - nameservers = ['192.0.2.1', '192.0.2.2'] - for nameserver in nameservers: - self.cli_set(base_path + ['name-server', nameserver]) + nameservers = {'192.0.2.1': {}, '192.0.2.2': {'port': '53'}, '2001:db8::1': {'port': '853'}} + for h,p in nameservers.items(): + if 'port' in p: + self.cli_set(base_path + ['name-server', h, 'port', p['port']]) + else: + self.cli_set(base_path + ['name-server', h]) # commit changes self.cli_commit() tmp = get_config_value(r'\+.', file=FORWARD_FILE) - self.assertEqual(tmp, ', '.join(nameservers)) + canonical_entries = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port'] if 'port' in p else 53}")(h, p) + for (h, p) in nameservers.items()] + self.assertEqual(tmp, ', '.join(canonical_entries)) # Do not use local /etc/hosts file in name resolution # default: yes @@ -159,10 +169,13 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['listen-address', address]) domains = ['vyos.io', 'vyos.net', 'vyos.com'] - nameservers = ['192.0.2.1', '192.0.2.2'] + nameservers = {'192.0.2.1': {}, '192.0.2.2': {'port': '53'}, '2001:db8::1': {'port': '853'}} for domain in domains: - for nameserver in nameservers: - self.cli_set(base_path + ['domain', domain, 'server', nameserver]) + for h,p in nameservers.items(): + if 'port' in p: + self.cli_set(base_path + ['domain', domain, 'name-server', h, 'port', p['port']]) + else: + self.cli_set(base_path + ['domain', domain, 'name-server', h]) # Test 'recursion-desired' flag for only one domain if domain == domains[0]: @@ -182,7 +195,9 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): if domain == domains[0]: key =f'\+{domain}' else: key =f'{domain}' tmp = get_config_value(key, file=FORWARD_FILE) - self.assertEqual(tmp, ', '.join(nameservers)) + canonical_entries = [(lambda h, p: f"{bracketize_ipv6(h)}:{p['port'] if 'port' in p else 53}")(h, p) + for (h, p) in nameservers.items()] + self.assertEqual(tmp, ', '.join(canonical_entries)) # Test 'negative trust anchor' flag for the second domain only if domain == domains[1]: @@ -224,5 +239,21 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase): tmp = get_config_value('dns64-prefix') self.assertEqual(tmp, dns_prefix) + def test_listening_port(self): + # We can listen on a different port compared to '53' but only one at a time + for port in ['1053', '5353']: + self.cli_set(base_path + ['port', port]) + for network in allow_from: + self.cli_set(base_path + ['allow-from', network]) + for address in listen_adress: + self.cli_set(base_path + ['listen-address', address]) + + # commit changes + self.cli_commit() + + # verify local-port configuration + tmp = get_config_value('local-port') + self.assertEqual(tmp, port) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py index 0f4b1393c..1ae5c104c 100755 --- a/smoketest/scripts/cli/test_service_https.py +++ b/smoketest/scripts/cli/test_service_https.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 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 @@ -21,8 +21,8 @@ from urllib3.exceptions import InsecureRequestWarning from base_vyostest_shim import VyOSUnitTestSHIM from base_vyostest_shim import ignore_warning -from vyos.util import read_file -from vyos.util import run +from vyos.utils.file import read_file +from vyos.utils.process import run base_path = ['service', 'https'] pki_base = ['pki'] @@ -193,7 +193,8 @@ class TestHTTPSService(VyOSUnitTestSHIM.TestCase): """ r = request('POST', graphql_url, verify=False, headers=headers, json={'query': query_no_key}) - self.assertEqual(r.status_code, 400) + success = r.json()['data']['SystemStatus']['success'] + self.assertFalse(success) # GraphQL token authentication test: request token; pass in header # of query. diff --git a/smoketest/scripts/cli/test_service_ids.py b/smoketest/scripts/cli/test_service_ids.py index dcf2bcefe..91b056eea 100755 --- a/smoketest/scripts/cli/test_service_ids.py +++ b/smoketest/scripts/cli/test_service_ids.py @@ -20,8 +20,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'fastnetmon' FASTNETMON_CONF = '/run/fastnetmon/fastnetmon.conf' diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py index bdab35834..4dd3e761c 100755 --- a/smoketest/scripts/cli/test_service_ipoe-server.py +++ b/smoketest/scripts/cli/test_service_ipoe-server.py @@ -19,13 +19,20 @@ import unittest from base_accel_ppp_test import BasicAccelPPPTest from vyos.configsession import ConfigSessionError -from vyos.util import cmd +from vyos.utils.process import cmd from configparser import ConfigParser ac_name = 'ACN' interface = 'eth0' + +def getConfig(string, end='cli'): + command = f'cat /run/accel-pppd/ipoe.conf | sed -n "/^{string}/,/^{end}/p"' + out = cmd(command) + return out + + class TestServiceIPoEServer(BasicAccelPPPTest.TestCase): @classmethod def setUpClass(cls): @@ -86,6 +93,92 @@ class TestServiceIPoEServer(BasicAccelPPPTest.TestCase): tmp = re.findall(regex, tmp) self.assertTrue(tmp) + def test_accel_named_pool(self): + first_pool = 'VyOS-pool1' + first_subnet = '192.0.2.0/25' + first_gateway = '192.0.2.1' + second_pool = 'Vyos-pool2' + second_subnet = '203.0.113.0/25' + second_gateway = '203.0.113.1' + + self.set(['authentication', 'mode', 'noauth']) + self.set(['client-ip-pool', 'name', first_pool, 'gateway-address', first_gateway]) + self.set(['client-ip-pool', 'name', first_pool, 'subnet', first_subnet]) + self.set(['client-ip-pool', 'name', second_pool, 'gateway-address', second_gateway]) + self.set(['client-ip-pool', 'name', second_pool, 'subnet', second_subnet]) + self.set(['interface', interface]) + + # commit changes + self.cli_commit() + + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False) + conf.read(self._config_file) + + self.assertTrue(conf['ipoe']['interface'], f'{interface},shared=1,mode=L2,ifcfg=1,start=dhcpv4,ipv6=1') + self.assertTrue(conf['ipoe']['noauth'], '1') + self.assertTrue(conf['ipoe']['ip-pool'], first_pool) + self.assertTrue(conf['ipoe']['ip-pool'], second_pool) + self.assertTrue(conf['ipoe']['gw-ip-address'], f'{first_gateway}/25') + self.assertTrue(conf['ipoe']['gw-ip-address'], f'{second_gateway}/25') + + config = getConfig('[ip-pool]') + pool_config = f'''{second_subnet},name={second_pool} +{first_subnet},name={first_pool} +gw-ip-address={second_gateway}/25 +gw-ip-address={first_gateway}/25''' + self.assertIn(pool_config, config) + + + def test_accel_next_pool(self): + first_pool = 'VyOS-pool1' + first_subnet = '192.0.2.0/25' + first_gateway = '192.0.2.1' + second_pool = 'Vyos-pool2' + second_subnet = '203.0.113.0/25' + second_gateway = '203.0.113.1' + third_pool = 'Vyos-pool3' + third_subnet = '198.51.100.0/24' + third_gateway = '198.51.100.1' + + self.set(['authentication', 'mode', 'noauth']) + self.set(['client-ip-pool', 'name', first_pool, 'gateway-address', first_gateway]) + self.set(['client-ip-pool', 'name', first_pool, 'subnet', first_subnet]) + self.set(['client-ip-pool', 'name', first_pool, 'next-pool', second_pool]) + self.set(['client-ip-pool', 'name', second_pool, 'gateway-address', second_gateway]) + self.set(['client-ip-pool', 'name', second_pool, 'subnet', second_subnet]) + self.set(['client-ip-pool', 'name', second_pool, 'next-pool', third_pool]) + self.set(['client-ip-pool', 'name', third_pool, 'gateway-address', third_gateway]) + self.set(['client-ip-pool', 'name', third_pool, 'subnet', third_subnet]) + self.set(['interface', interface]) + + # commit changes + self.cli_commit() + + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False) + conf.read(self._config_file) + + self.assertTrue(conf['ipoe']['interface'], f'{interface},shared=1,mode=L2,ifcfg=1,start=dhcpv4,ipv6=1') + self.assertTrue(conf['ipoe']['noauth'], '1') + self.assertTrue(conf['ipoe']['ip-pool'], first_pool) + self.assertTrue(conf['ipoe']['gw-ip-address'], f'{first_gateway}/25') + self.assertTrue(conf['ipoe']['gw-ip-address'], f'{second_gateway}/25') + self.assertTrue(conf['ipoe']['gw-ip-address'], f'{third_gateway}/24') + + config = getConfig('[ip-pool]') + # T5099 required specific order + pool_config = f'''{third_subnet},name={third_pool} +{second_subnet},name={second_pool},next={third_pool} +{first_subnet},name={first_pool},next={second_pool} +gw-ip-address={third_gateway}/24 +gw-ip-address={second_gateway}/25 +gw-ip-address={first_gateway}/25''' + self.assertIn(pool_config, config) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_lldp.py b/smoketest/scripts/cli/test_service_lldp.py index 439c96c33..ee26844ab 100755 --- a/smoketest/scripts/cli/test_service_lldp.py +++ b/smoketest/scripts/cli/test_service_lldp.py @@ -22,9 +22,9 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file from vyos.version import get_version_data PROCESS_NAME = 'lldpd' diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py index f99a98da1..9a9839025 100755 --- a/smoketest/scripts/cli/test_service_mdns-repeater.py +++ b/smoketest/scripts/cli/test_service_mdns-repeater.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -18,30 +18,57 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import process_named_running +from configparser import ConfigParser +from vyos.utils.process import process_named_running base_path = ['service', 'mdns', 'repeater'] intf_base = ['interfaces', 'dummy'] +config_file = '/run/avahi-daemon/avahi-daemon.conf' + class TestServiceMDNSrepeater(VyOSUnitTestSHIM.TestCase): def tearDown(self): + # Check for running process + self.assertTrue(process_named_running('avahi-daemon')) + self.cli_delete(base_path) self.cli_delete(intf_base + ['dum10']) self.cli_delete(intf_base + ['dum20']) self.cli_commit() + # Check that there is no longer a running process + self.assertFalse(process_named_running('avahi-daemon')) + def test_service(self): - # Service required a configured IP address on the interface + # mDNS browsing domains in addition to the default one (local) + domains = ['dom1.home.arpa', 'dom2.home.arpa'] + + # mDNS services to be repeated + services = ['_ipp._tcp', '_smb._tcp', '_ssh._tcp'] + # Service required a configured IP address on the interface self.cli_set(intf_base + ['dum10', 'address', '192.0.2.1/30']) self.cli_set(intf_base + ['dum20', 'address', '192.0.2.5/30']) self.cli_set(base_path + ['interface', 'dum10']) self.cli_set(base_path + ['interface', 'dum20']) + + for domain in domains: + self.cli_set(base_path + ['browse-domain', domain]) + + for service in services: + self.cli_set(base_path + ['allow-service', service]) + self.cli_commit() - # Check for running process - self.assertTrue(process_named_running('avahi-daemon')) + # Validate configuration values + conf = ConfigParser(delimiters='=') + conf.read(config_file) + + self.assertEqual(conf['server']['allow-interfaces'], 'dum10, dum20') + self.assertEqual(conf['server']['browse-domains'], ', '.join(domains)) + self.assertEqual(conf['reflector']['enable-reflector'], 'yes') + self.assertEqual(conf['reflector']['reflect-filters'], ', '.join(services)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_monitoring_telegraf.py b/smoketest/scripts/cli/test_service_monitoring_telegraf.py index ed486c3b9..f3355b735 100755 --- a/smoketest/scripts/cli/test_service_monitoring_telegraf.py +++ b/smoketest/scripts/cli/test_service_monitoring_telegraf.py @@ -20,8 +20,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'telegraf' TELEGRAF_CONF = '/run/telegraf/telegraf.conf' diff --git a/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py new file mode 100755 index 000000000..cb5f84406 --- /dev/null +++ b/smoketest/scripts/cli/test_service_monitoring_zabbix-agent.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import unittest + +from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + + +PROCESS_NAME = 'zabbix_agent2' +ZABBIX_AGENT_CONF = '/run/zabbix/zabbix-agent2.conf' +base_path = ['service', 'monitoring', 'zabbix-agent'] + + +class TestZabbixAgent(VyOSUnitTestSHIM.TestCase): + def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # Process must be terminated after deleting the config + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_01_zabbix_agent(self): + directory = '/tmp' + buffer_send = '8' + buffer_size = '120' + log_level = {'warning': '3'} + log_size = '1' + servers = ['192.0.2.1', '2001:db8::1'] + servers_active = {'192.0.2.5': {'port': '10051'}, '2001:db8::123': {'port': '10052'}} + port = '10050' + timeout = '5' + listen_ip = '0.0.0.0' + hostname = 'r-vyos' + + self.cli_set(base_path + ['directory', directory]) + self.cli_set(base_path + ['limits', 'buffer-flush-interval', buffer_send]) + self.cli_set(base_path + ['limits', 'buffer-size', buffer_size]) + self.cli_set(base_path + ['log', 'debug-level', next(iter(log_level))]) + self.cli_set(base_path + ['log', 'size', log_size]) + for server in servers: + self.cli_set(base_path + ['server', server]) + for server_active, server_config in servers_active.items(): + self.cli_set(base_path + ['server-active', server_active, 'port', server_config['port']]) + self.cli_set(base_path + ['timeout', timeout]) + self.cli_set(base_path + ['host-name', hostname]) + + # commit changes + self.cli_commit() + + config = read_file(ZABBIX_AGENT_CONF) + + self.assertIn(f'LogFileSize={log_size}', config) + self.assertIn(f'DebugLevel={log_level.get("warning")}', config) + + self.assertIn(f'Server={",".join(sorted(servers))}', config) + tmp = 'ServerActive=192.0.2.5:10051,[2001:db8::123]:10052' + self.assertIn(tmp, config) + + self.assertIn(f'ListenPort={port}', config) + self.assertIn(f'ListenIP={listen_ip}', config) + self.assertIn(f'BufferSend={buffer_send}', config) + self.assertIn(f'BufferSize={buffer_size}', config) + self.assertIn(f'Include={directory}/*.conf', config) + self.assertIn(f'Timeout={timeout}', config) + self.assertIn(f'Hostname={hostname}', config) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_service_ntp.py index a0806acf0..5e385d5ad 100755 --- a/smoketest/scripts/cli/test_system_ntp.py +++ b/smoketest/scripts/cli/test_service_ntp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 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 @@ -19,14 +19,12 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.template import address_from_cidr -from vyos.template import netmask_from_cidr -from vyos.util import read_file -from vyos.util import process_named_running +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running -PROCESS_NAME = 'ntpd' -NTP_CONF = '/run/ntpd/ntpd.conf' -base_path = ['system', 'ntp'] +PROCESS_NAME = 'chronyd' +NTP_CONF = '/run/chrony/chrony.conf' +base_path = ['service', 'ntp'] class TestSystemNTP(VyOSUnitTestSHIM.TestCase): @classmethod @@ -38,6 +36,8 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): cls.cli_delete(cls, base_path) def tearDown(self): + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) self.cli_commit() @@ -46,7 +46,7 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): def test_01_ntp_options(self): # Test basic NTP support with multiple servers and their options servers = ['192.0.2.1', '192.0.2.2'] - options = ['noselect', 'preempt', 'prefer'] + options = ['nts', 'noselect', 'prefer'] pools = ['pool.vyos.io'] for server in servers: @@ -61,12 +61,15 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Check generated configuration - config = read_file(NTP_CONF) - self.assertIn('driftfile /var/lib/ntp/ntp.drift', config) - self.assertIn('restrict default noquery nopeer notrap nomodify', config) - self.assertIn('restrict source nomodify notrap noquery', config) - self.assertIn('restrict 127.0.0.1', config) - self.assertIn('restrict -6 ::1', config) + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') + self.assertIn('driftfile /run/chrony/drift', config) + self.assertIn('dumpdir /run/chrony', config) + self.assertIn('ntsdumpdir /run/chrony', config) + self.assertIn('clientloglimit 1048576', config) + self.assertIn('rtcsync', config) + self.assertIn('makestep 1.0 3', config) + self.assertIn('leapsectz right/UTC', config) for server in servers: self.assertIn(f'server {server} iburst ' + ' '.join(options), config) @@ -80,9 +83,9 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): for listen in listen_address: self.cli_set(base_path + ['listen-address', listen]) - networks = ['192.0.2.0/24', '2001:db8:1000::/64'] + networks = ['192.0.2.0/24', '2001:db8:1000::/64', '100.64.0.0', '2001:db8::ffff'] for network in networks: - self.cli_set(base_path + ['allow-clients', 'address', network]) + self.cli_set(base_path + ['allow-client', 'address', network]) # Verify "NTP server not configured" verify() statement with self.assertRaises(ConfigSessionError): @@ -95,21 +98,17 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Check generated client address configuration - config = read_file(NTP_CONF) - self.assertIn('restrict default ignore', config) - + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') for network in networks: - network_address = address_from_cidr(network) - network_netmask = netmask_from_cidr(network) - self.assertIn(f'restrict {network_address} mask {network_netmask} nomodify notrap nopeer', config) + self.assertIn(f'allow {network}', config) # Check listen address - self.assertIn('interface ignore wildcard', config) for listen in listen_address: - self.assertIn(f'interface listen {listen}', config) + self.assertIn(f'bindaddress {listen}', config) def test_03_ntp_interface(self): - interfaces = ['eth0', 'eth1'] + interfaces = ['eth0'] for interface in interfaces: self.cli_set(base_path + ['interface', interface]) @@ -120,10 +119,28 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Check generated client address configuration - config = read_file(NTP_CONF) - self.assertIn('interface ignore wildcard', config) + # this file must be read with higher permissions + config = cmd(f'sudo cat {NTP_CONF}') for interface in interfaces: - self.assertIn(f'interface listen {interface}', config) + self.assertIn(f'binddevice {interface}', config) + + def test_04_ntp_vrf(self): + vrf_name = 'vyos-mgmt' + + self.cli_set(['vrf', 'name', vrf_name, 'table', '12345']) + self.cli_set(base_path + ['vrf', vrf_name]) + + servers = ['time1.vyos.net', 'time2.vyos.net'] + for server in servers: + self.cli_set(base_path + ['server', server]) + + self.cli_commit() + + # Check for process in VRF + tmp = cmd(f'ip vrf pids {vrf_name}') + self.assertIn(PROCESS_NAME, tmp) + + self.cli_delete(['vrf', 'name', vrf_name]) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index 7546c2e3d..963784f0a 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2023 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 @@ -19,7 +19,7 @@ import unittest from base_accel_ppp_test import BasicAccelPPPTest from configparser import ConfigParser -from vyos.util import read_file +from vyos.utils.file import read_file from vyos.template import range_to_regex local_if = ['interfaces', 'dummy', 'dum667'] @@ -143,6 +143,9 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): self.basic_config() subnet = '172.18.0.0/24' + fwmark = '223' + limiter = 'htb' + self.set(['client-ip-pool', 'subnet', subnet]) start = '192.0.2.10' @@ -151,6 +154,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): start_stop = f'{start}-{stop_octet}' self.set(['client-ip-pool', 'start', start]) self.set(['client-ip-pool', 'stop', stop]) + self.set(['shaper', 'fwmark', fwmark]) # commit changes self.cli_commit() @@ -163,6 +167,37 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): self.assertEqual(conf['ip-pool'][subnet], None) self.assertEqual(conf['ip-pool'][start_stop], None) self.assertEqual(conf['ip-pool']['gw-ip-address'], self._gateway) + self.assertEqual(conf['shaper']['fwmark'], fwmark) + self.assertEqual(conf['shaper']['down-limiter'], limiter) + + + def test_pppoe_server_client_ip_pool_name(self): + # Test configuration of named client pools + self.basic_config() + + subnet = '192.0.2.0/24' + gateway = '192.0.2.1' + pool = 'VYOS' + + subnet_name = f'{subnet},name' + gw_ip_prefix = f'{gateway}/24' + + self.set(['client-ip-pool', 'name', pool, 'subnet', subnet]) + self.set(['client-ip-pool', 'name', pool, 'gateway-address', gateway]) + self.cli_delete(self._base_path + ['gateway-address']) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + + # Validate configuration + self.assertEqual(conf['ip-pool'][subnet_name], pool) + self.assertEqual(conf['ip-pool']['gw-ip-address'], gateway) + self.assertEqual(conf['pppoe']['ip-pool'], pool) + self.assertEqual(conf['pppoe']['gw-ip-address'], gw_ip_prefix) def test_pppoe_server_client_ipv6_pool(self): @@ -208,9 +243,11 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): def test_accel_radius_authentication(self): radius_called_sid = 'ifname:mac' radius_acct_interim_jitter = '9' + radius_acct_interim_interval = '60' self.set(['authentication', 'radius', 'called-sid-format', radius_called_sid]) self.set(['authentication', 'radius', 'acct-interim-jitter', radius_acct_interim_jitter]) + self.set(['authentication', 'radius', 'accounting-interim-interval', radius_acct_interim_interval]) # run common tests super().test_accel_radius_authentication() @@ -222,6 +259,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): # Validate configuration self.assertEqual(conf['pppoe']['called-sid'], radius_called_sid) self.assertEqual(conf['radius']['acct-interim-jitter'], radius_acct_interim_jitter) + self.assertEqual(conf['radius']['acct-interim-interval'], radius_acct_interim_interval) def test_pppoe_server_vlan(self): diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py index 873be7df0..5fc2019fd 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -20,8 +20,8 @@ import unittest from vyos.configsession import ConfigSessionError from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import read_file -from vyos.util import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running PROCESS_NAME = 'radvd' RADVD_CONF = '/run/radvd/radvd.conf' @@ -37,7 +37,6 @@ def get_config_value(key): return tmp[0].split()[0].replace(';','') class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): - @classmethod def setUpClass(cls): super(TestServiceRADVD, cls).setUpClass() @@ -114,7 +113,6 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): tmp = get_config_value('DecrementLifetimes') self.assertEqual(tmp, 'off') - def test_dns(self): nameserver = ['2001:db8::1', '2001:db8::2'] dnssl = ['vyos.net', 'vyos.io'] @@ -150,7 +148,6 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): tmp = 'DNSSL ' + ' '.join(dnssl) + ' {' self.assertIn(tmp, config) - def test_deprecate_prefix(self): self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) self.cli_set(base_path + ['prefix', prefix, 'deprecate-prefix']) @@ -159,13 +156,45 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - config = read_file(RADVD_CONF) - tmp = get_config_value('DeprecatePrefix') self.assertEqual(tmp, 'on') tmp = get_config_value('DecrementLifetimes') self.assertEqual(tmp, 'on') + def test_route(self): + route = '2001:db8:1000::/64' + + self.cli_set(base_path + ['prefix', prefix]) + self.cli_set(base_path + ['route', route]) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + + tmp = f'route {route}' + ' {' + self.assertIn(tmp, config) + + self.assertIn('AdvRouteLifetime 1800;', config) + self.assertIn('AdvRoutePreference medium;', config) + self.assertIn('RemoveRoute on;', config) + + def test_rasrcaddress(self): + ra_src = ['fe80::1', 'fe80::2'] + + self.cli_set(base_path + ['prefix', prefix]) + for src in ra_src: + self.cli_set(base_path + ['source-address', src]) + + # commit changes + self.cli_commit() + + config = read_file(RADVD_CONF) + self.assertIn('AdvRASrcAddress {', config) + for src in ra_src: + self.assertIn(f' {src};', config) + + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_salt.py b/smoketest/scripts/cli/test_service_salt.py index 00a4f2020..48a588b72 100755 --- a/smoketest/scripts/cli/test_service_salt.py +++ b/smoketest/scripts/cli/test_service_salt.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2022 VyOS maintainers and contributors +# Copyright (C) 2022-2023 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 @@ -19,9 +19,9 @@ import unittest from socket import gethostname from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import process_named_running -from vyos.util import read_file -from vyos.util import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import cmd PROCESS_NAME = 'salt-minion' SALT_CONF = '/etc/salt/minion' diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py index e80c689cc..52a72ec4f 100755 --- a/smoketest/scripts/cli/test_service_snmp.py +++ b/smoketest/scripts/cli/test_service_snmp.py @@ -22,10 +22,10 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.template import is_ipv4 from vyos.template import address_from_cidr -from vyos.util import call -from vyos.util import DEVNULL -from vyos.util import read_file -from vyos.util import process_named_running +from vyos.utils.process import call +from vyos.utils.process import DEVNULL +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running from vyos.version import get_version_data PROCESS_NAME = 'snmpd' @@ -123,6 +123,28 @@ class TestSNMPService(VyOSUnitTestSHIM.TestCase): self.assertTrue(process_named_running(PROCESS_NAME)) self.cli_delete(['interfaces', 'dummy', dummy_if]) + ## Check communities and default view RESTRICTED + for auth in ['ro', 'rw']: + community = 'VyOS' + auth + for addr in clients: + if is_ipv4(addr): + entry = auth + 'community ' + community + ' ' + addr + ' -V' + else: + entry = auth + 'community6 ' + community + ' ' + addr + ' -V' + config = get_config_value(entry) + expected = 'RESTRICTED' + self.assertIn(expected, config) + for addr in networks: + if is_ipv4(addr): + entry = auth + 'community ' + community + ' ' + addr + ' -V' + else: + entry = auth + 'community6 ' + community + ' ' + addr + ' -V' + config = get_config_value(entry) + expected = 'RESTRICTED' + self.assertIn(expected, config) + # And finally check global entry for RESTRICTED view + config = get_config_value('view RESTRICTED included .1') + self.assertIn('80', config) def test_snmpv3_sha(self): # Check if SNMPv3 can be configured with SHA authentication diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index 8de98f34f..947d7d568 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -24,10 +24,10 @@ from pwd import getpwall from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import is_systemd_service_running -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import is_systemd_service_running +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'sshd' SSHD_CONF = '/run/sshd/sshd_config' @@ -174,18 +174,6 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # # We also try to login as an invalid user - this is not allowed to work. - def ssh_send_cmd(command, username, password, host='localhost'): - """ SSH command execution helper """ - # Try to login via SSH - ssh_client = paramiko.SSHClient() - ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh_client.connect(hostname='localhost', username=username, password=password) - _, stdout, stderr = ssh_client.exec_command(command) - output = stdout.read().decode().strip() - error = stderr.read().decode().strip() - ssh_client.close() - return output, error - test_user = 'ssh_test' test_pass = 'v2i57DZs8idUwMN3VC92' test_command = 'uname -a' @@ -197,14 +185,14 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Login with proper credentials - output, error = ssh_send_cmd(test_command, test_user, test_pass) + output, error = self.ssh_send_cmd(test_command, test_user, test_pass) # verify login self.assertFalse(error) self.assertEqual(output, cmd(test_command)) # Login with invalid credentials with self.assertRaises(paramiko.ssh_exception.AuthenticationException): - output, error = ssh_send_cmd(test_command, 'invalid_user', 'invalid_password') + output, error = self.ssh_send_cmd(test_command, 'invalid_user', 'invalid_password') self.cli_delete(['system', 'login', 'user', test_user]) self.cli_commit() diff --git a/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py index b57c33f26..d60794980 100755 --- a/smoketest/scripts/cli/test_service_tftp-server.py +++ b/smoketest/scripts/cli/test_service_tftp-server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2023 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 psutil import process_iter from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import read_file -from vyos.util import process_named_running +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running from vyos.template import is_ipv6 PROCESS_NAME = 'in.tftpd' @@ -33,15 +33,32 @@ address_ipv6 = '2001:db8::1' vrf = 'mgmt' class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): - def setUp(self): - self.cli_set(dummy_if_path + ['address', address_ipv4 + '/32']) - self.cli_set(dummy_if_path + ['address', address_ipv6 + '/128']) + @classmethod + def setUpClass(cls): + super(TestServiceTFTPD, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + cls.cli_set(cls, dummy_if_path + ['address', address_ipv4 + '/32']) + cls.cli_set(cls, dummy_if_path + ['address', address_ipv6 + '/128']) + + @classmethod + def tearDownClass(cls): + cls.cli_delete(cls, dummy_if_path) + super(TestServiceTFTPD, cls).tearDownClass() def tearDown(self): + # Check for running process + self.assertTrue(process_named_running(PROCESS_NAME)) + self.cli_delete(base_path) - self.cli_delete(dummy_if_path) self.cli_commit() + # Check for no longer running process + self.assertFalse(process_named_running(PROCESS_NAME)) + def test_01_tftpd_single(self): directory = '/tmp' port = '69' # default port @@ -61,9 +78,6 @@ class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): # verify upload self.assertIn('--create --umask 000', config) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - def test_02_tftpd_multi(self): directory = '/tmp' address = [address_ipv4, address_ipv6] @@ -125,9 +139,6 @@ class TestServiceTFTPD(VyOSUnitTestSHIM.TestCase): # verify upload self.assertIn('--create --umask 000', config) - # Check for running process - self.assertTrue(process_named_running(PROCESS_NAME)) - # Check for process in VRF tmp = cmd(f'ip vrf pids {vrf}') self.assertIn(PROCESS_NAME, tmp) diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py index e4df88c1e..c3fb0ec9d 100755 --- a/smoketest/scripts/cli/test_service_upnp.py +++ b/smoketest/scripts/cli/test_service_upnp.py @@ -22,8 +22,8 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSession from vyos.configsession import ConfigSessionError from vyos.template import ip_from_cidr -from vyos.util import read_file -from vyos.util import process_named_running +from vyos.utils.file import read_file +from vyos.utils.process import process_named_running UPNP_CONF = '/run/upnp/miniupnp.conf' DAEMON = 'miniupnpd' diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py index fb9b46a06..2b3f6d21c 100755 --- a/smoketest/scripts/cli/test_service_webproxy.py +++ b/smoketest/scripts/cli/test_service_webproxy.py @@ -19,9 +19,9 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'squid' PROXY_CONF = '/etc/squid/squid.conf' diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py index fd16146b1..ea304783d 100755 --- a/smoketest/scripts/cli/test_system_conntrack.py +++ b/smoketest/scripts/cli/test_system_conntrack.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -21,8 +21,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from vyos.firewall import find_nftables_rule -from vyos.util import cmd -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.file import read_file base_path = ['system', 'conntrack'] @@ -35,6 +35,17 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() + def verify_nftables(self, nftables_search, table, inverse=False, args=''): + nftables_output = cmd(f'sudo nft {args} list table {table}') + + for search in nftables_search: + matched = False + for line in nftables_output.split("\n"): + if all(item in line for item in search): + matched = True + break + self.assertTrue(not matched if inverse else matched, msg=search) + def test_conntrack_options(self): conntrack_config = { 'net.netfilter.nf_conntrack_expect_max' : { @@ -232,5 +243,51 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase): tmp = read_file('/etc/modprobe.d/vyatta_nf_conntrack.conf') self.assertIn(hash_size_default, tmp) + def test_conntrack_ignore(self): + address_group = 'conntracktest' + address_group_member = '192.168.0.1' + ipv6_address_group = 'conntracktest6' + ipv6_address_group_member = 'dead:beef::1' + + self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member]) + self.cli_set(['firewall', 'group', 'ipv6-address-group', ipv6_address_group, 'address', ipv6_address_group_member]) + + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'address', '192.0.2.2']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'destination', 'port', '22']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '1', 'protocol', 'tcp']) + + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'source', 'address', '192.0.2.1']) + self.cli_set(base_path + ['ignore', 'ipv4', 'rule', '2', 'destination', 'group', 'address-group', address_group]) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'address', 'fe80::2']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'destination', 'port', '22']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '11', 'protocol', 'tcp']) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '12', 'destination', 'group', 'address-group', ipv6_address_group]) + + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'source', 'address', 'fe80::1']) + self.cli_set(base_path + ['ignore', 'ipv6', 'rule', '13', 'destination', 'address', '!fe80::3']) + + self.cli_commit() + + nftables_search = [ + ['ip saddr 192.0.2.1', 'ip daddr 192.0.2.2', 'tcp dport 22', 'notrack'], + ['ip saddr 192.0.2.1', 'ip daddr @A_conntracktest', 'notrack'] + ] + + nftables6_search = [ + ['ip6 saddr fe80::1', 'ip6 daddr fe80::2', 'tcp dport 22', 'notrack'], + ['ip6 saddr fe80::1', 'ip6 daddr @A6_conntracktest6', 'notrack'], + ['ip6 saddr fe80::1', 'ip6 daddr != fe80::3', 'notrack'] + ] + + self.verify_nftables(nftables_search, 'raw') + self.verify_nftables(nftables6_search, 'ip6 raw') + + self.cli_delete(['firewall']) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py index df60b9613..d55ea616e 100755 --- a/smoketest/scripts/cli/test_system_flow-accounting.py +++ b/smoketest/scripts/cli/test_system_flow-accounting.py @@ -22,9 +22,9 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.template import bracketize_ipv6 from vyos.template import is_ipv6 -from vyos.util import cmd -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file PROCESS_NAME = 'uacctd' base_path = ['system', 'flow-accounting'] diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py index 331133ed4..3eb0cd0ab 100755 --- a/smoketest/scripts/cli/test_system_frr.py +++ b/smoketest/scripts/cli/test_system_frr.py @@ -17,7 +17,7 @@ import re import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import read_file +from vyos.utils.file import read_file config_file = '/etc/frr/daemons' base_path = ['system', 'frr'] @@ -143,4 +143,4 @@ class TestSystemFRR(VyOSUnitTestSHIM.TestCase): if __name__ == '__main__': - unittest.main(verbosity=2, failfast=True) + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py index f71ef5b3f..567416774 100755 --- a/smoketest/scripts/cli/test_system_ip.py +++ b/smoketest/scripts/cli/test_system_ip.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,7 +17,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import read_file +from vyos.configsession import ConfigSessionError +from vyos.utils.file import read_file base_path = ['system', 'ip'] @@ -82,5 +83,42 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase): self.assertEqual(read_file(gc_thresh2), str(size // 2)) self.assertEqual(read_file(gc_thresh1), str(size // 8)) + def test_system_ip_protocol_route_map(self): + protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', + 'kernel', 'ospf', 'rip', 'static', 'table'] + + for protocol in protocols: + self.cli_set(['policy', 'route-map', f'route-map-{protocol}', 'rule', '10', 'action', 'permit']) + self.cli_set(base_path + ['protocol', protocol, 'route-map', f'route-map-{protocol}']) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra') + for protocol in protocols: + self.assertIn(f'ip protocol {protocol} route-map route-map-{protocol}', frrconfig) + + # Delete route-maps + self.cli_delete(['policy', 'route-map']) + self.cli_delete(base_path + ['protocol']) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ip protocol', end='', daemon='zebra') + self.assertNotIn(f'ip protocol', frrconfig) + + def test_system_ip_protocol_non_existing_route_map(self): + non_existing = 'non-existing' + self.cli_set(base_path + ['protocol', 'static', 'route-map', non_existing]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', non_existing, 'rule', '10', 'action', 'deny']) + + # Commit again + self.cli_commit() + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index c8aea9100..225c2d666 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -18,10 +18,11 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from vyos.configsession import ConfigSessionError from vyos.template import is_ipv4 -from vyos.util import read_file -from vyos.util import get_interface_config -from vyos.validate import is_intf_addr_assigned +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.network import is_intf_addr_assigned base_path = ['system', 'ipv6'] @@ -88,5 +89,47 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.assertEqual(read_file(gc_thresh2), str(size // 2)) self.assertEqual(read_file(gc_thresh1), str(size // 8)) + def test_system_ipv6_protocol_route_map(self): + protocols = ['any', 'babel', 'bgp', 'connected', 'isis', + 'kernel', 'ospfv3', 'ripng', 'static', 'table'] + + for protocol in protocols: + route_map = 'route-map-' + protocol.replace('ospfv3', 'ospf6') + + self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + self.cli_set(base_path + ['protocol', protocol, 'route-map', route_map]) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra') + for protocol in protocols: + # VyOS and FRR use a different name for OSPFv3 (IPv6) + if protocol == 'ospfv3': + protocol = 'ospf6' + self.assertIn(f'ipv6 protocol {protocol} route-map route-map-{protocol}', frrconfig) + + # Delete route-maps + self.cli_delete(['policy', 'route-map']) + self.cli_delete(base_path + ['protocol']) + + self.cli_commit() + + # Verify route-map properly applied to FRR + frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon='zebra') + self.assertNotIn(f'ipv6 protocol', frrconfig) + + def test_system_ipv6_protocol_non_existing_route_map(self): + non_existing = 'non-existing6' + self.cli_set(base_path + ['protocol', 'static', 'route-map', non_existing]) + + # VRF does yet not exist - an error must be thrown + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', non_existing, 'rule', '10', 'action', 'deny']) + + # Commit again + self.cli_commit() + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_lcd.py b/smoketest/scripts/cli/test_system_lcd.py index 831fba979..fc440ca8a 100755 --- a/smoketest/scripts/cli/test_system_lcd.py +++ b/smoketest/scripts/cli/test_system_lcd.py @@ -19,7 +19,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM from configparser import ConfigParser -from vyos.util import process_named_running +from vyos.utils.process import process_named_running config_file = '/run/LCDd/LCDd.conf' base_path = ['system', 'lcd'] diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 6006fe0f6..195b127a4 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2022 VyOS maintainers and contributors +# Copyright (C) 2019-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,17 +17,17 @@ import re import platform import unittest +import paramiko from base_vyostest_shim import VyOSUnitTestSHIM -from distutils.version import LooseVersion -from platform import release as kernel_version from subprocess import Popen, PIPE from pwd import getpwall +from time import sleep from vyos.configsession import ConfigSessionError -from vyos.util import cmd -from vyos.util import read_file +from vyos.utils.process import cmd +from vyos.utils.file import read_file from vyos.template import inc_ip base_path = ['system', 'login'] @@ -53,12 +53,16 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration which will break this test cls.cli_delete(cls, base_path + ['radius']) + cls.cli_delete(cls, base_path + ['tacacs']) def tearDown(self): # Delete individual users from configuration for user in users: self.cli_delete(base_path + ['user', user]) + self.cli_delete(base_path + ['radius']) + self.cli_delete(base_path + ['tacacs']) + self.cli_commit() # After deletion, a user is not allowed to remain in /etc/passwd @@ -149,9 +153,6 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): # T2886 - RADIUS authentication - check for statically compiled options options = ['CONFIG_AUDIT', 'CONFIG_AUDITSYSCALL', 'CONFIG_AUDIT_ARCH'] - if LooseVersion(kernel_version()) < LooseVersion('5.0'): - options.append('CONFIG_AUDIT_WATCH') - options.append('CONFIG_AUDIT_TREE') for option in options: self.assertIn(f'{option}=y', kernel_config) @@ -264,5 +265,61 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): tmp = re.findall(r'group:\s+mapname\s+files', nsswitch_conf) self.assertTrue(tmp) + def test_system_login_max_login_session(self): + max_logins = '2' + timeout = '600' + + self.cli_set(base_path + ['max-login-session', max_logins]) + + # 'max-login-session' must be only with 'timeout' option + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + self.cli_set(base_path + ['timeout', timeout]) + + self.cli_commit() + + security_limits = read_file('/etc/security/limits.d/10-vyos.conf') + self.assertIn(f'* - maxsyslogins {max_logins}', security_limits) + + self.cli_delete(base_path + ['timeout']) + self.cli_delete(base_path + ['max-login-session']) + + def test_system_login_tacacs(self): + tacacs_secret = 'tac_plus_key' + tacacs_servers = ['100.64.0.11', '100.64.0.12'] + + # Enable TACACS + for server in tacacs_servers: + self.cli_set(base_path + ['tacacs', 'server', server, 'key', tacacs_secret]) + + self.cli_commit() + + # NSS + nsswitch_conf = read_file('/etc/nsswitch.conf') + tmp = re.findall(r'passwd:\s+tacplus\s+files', nsswitch_conf) + self.assertTrue(tmp) + + tmp = re.findall(r'group:\s+tacplus\s+files', nsswitch_conf) + self.assertTrue(tmp) + + # PAM TACACS configuration + pam_tacacs_conf = read_file('/etc/tacplus_servers') + # NSS TACACS configuration + nss_tacacs_conf = read_file('/etc/tacplus_nss.conf') + # Users have individual home directories + self.assertIn('user_homedir=1', pam_tacacs_conf) + + # specify services + self.assertIn('service=shell', pam_tacacs_conf) + self.assertIn('protocol=ssh', pam_tacacs_conf) + + for server in tacacs_servers: + self.assertIn(f'secret={tacacs_secret}', pam_tacacs_conf) + self.assertIn(f'server={server}', pam_tacacs_conf) + + self.assertIn(f'secret={tacacs_secret}', nss_tacacs_conf) + self.assertIn(f'server={server}', nss_tacacs_conf) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_logs.py b/smoketest/scripts/cli/test_system_logs.py index 92fa9c3d9..17cce5ca1 100755 --- a/smoketest/scripts/cli/test_system_logs.py +++ b/smoketest/scripts/cli/test_system_logs.py @@ -17,7 +17,7 @@ import re import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import read_file +from vyos.utils.file import read_file # path to logrotate configs logrotate_atop_file = '/etc/logrotate.d/vyos-atop' diff --git a/smoketest/scripts/cli/test_system_nameserver.py b/smoketest/scripts/cli/test_system_nameserver.py index 58c84988e..4979a7c72 100755 --- a/smoketest/scripts/cli/test_system_nameserver.py +++ b/smoketest/scripts/cli/test_system_nameserver.py @@ -21,7 +21,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError -from vyos.util import read_file +from vyos.utils.file import read_file RESOLV_CONF = '/etc/resolv.conf' diff --git a/smoketest/scripts/cli/test_system_sflow.py b/smoketest/scripts/cli/test_system_sflow.py new file mode 100755 index 000000000..63262db69 --- /dev/null +++ b/smoketest/scripts/cli/test_system_sflow.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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.ifconfig import Section +from vyos.utils.process import cmd +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file + +PROCESS_NAME = 'hsflowd' +base_path = ['system', 'sflow'] + +hsflowd_conf = '/run/sflow/hsflowd.conf' + + +class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase): + + @classmethod + def setUpClass(cls): + super(TestSystemFlowAccounting, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + + def tearDown(self): + # after service removal process must no longer run + self.assertTrue(process_named_running(PROCESS_NAME)) + + self.cli_delete(base_path) + self.cli_commit() + + # after service removal process must no longer run + self.assertFalse(process_named_running(PROCESS_NAME)) + + def test_sflow(self): + agent_address = '192.0.2.5' + agent_interface = 'eth0' + polling = '24' + sampling_rate = '128' + server = '192.0.2.254' + local_server = '127.0.0.1' + port = '8192' + default_port = '6343' + mon_limit = '50' + + self.cli_set( + ['interfaces', 'dummy', 'dum0', 'address', f'{agent_address}/24']) + self.cli_set(base_path + ['agent-address', agent_address]) + self.cli_set(base_path + ['agent-interface', agent_interface]) + + # You need to configure at least one interface for sflow + with self.assertRaises(ConfigSessionError): + self.cli_commit() + for interface in Section.interfaces('ethernet'): + self.cli_set(base_path + ['interface', interface]) + + self.cli_set(base_path + ['polling', polling]) + self.cli_set(base_path + ['sampling-rate', sampling_rate]) + self.cli_set(base_path + ['server', server, 'port', port]) + self.cli_set(base_path + ['server', local_server]) + self.cli_set(base_path + ['drop-monitor-limit', mon_limit]) + + # commit changes + self.cli_commit() + + # verify configuration + hsflowd = read_file(hsflowd_conf) + + self.assertIn(f'polling={polling}', hsflowd) + self.assertIn(f'sampling={sampling_rate}', hsflowd) + self.assertIn(f'agentIP={agent_address}', hsflowd) + self.assertIn(f'agent={agent_interface}', hsflowd) + self.assertIn(f'collector {{ ip = {server} udpport = {port} }}', hsflowd) + self.assertIn(f'collector {{ ip = {local_server} udpport = {default_port} }}', hsflowd) + self.assertIn(f'dropmon {{ limit={mon_limit} start=on sw=on hw=off }}', hsflowd) + self.assertIn('dbus { }', hsflowd) + + for interface in Section.interfaces('ethernet'): + self.assertIn(f'pcap {{ dev={interface} }}', hsflowd) + + +if __name__ == '__main__': + unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py index bd242104f..01b0406bf 100755 --- a/smoketest/scripts/cli/test_vpn_ipsec.py +++ b/smoketest/scripts/cli/test_vpn_ipsec.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2022 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -18,9 +18,9 @@ import os import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import call -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.utils.process import call +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file ethernet_path = ['interfaces', 'ethernet'] tunnel_path = ['interfaces', 'tunnel'] @@ -34,11 +34,15 @@ swanctl_file = '/etc/swanctl/swanctl.conf' peer_ip = '203.0.113.45' connection_name = 'main-branch' +local_id = 'left' +remote_id = 'right' interface = 'eth1' vif = '100' esp_group = 'MyESPGroup' ike_group = 'MyIKEGroup' secret = 'MYSECRETKEY' +PROCESS_NAME = 'charon-systemd' +regex_uuid4 = '[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}' ca_pem = """ MIIDSzCCAjOgAwIBAgIUQHK+ZgTUYZksvXY2/MyW+Jiels4wDQYJKoZIhvcNAQEL @@ -113,6 +117,8 @@ rgiyCHemtMepq57Pl1Nmj49eEA== """ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): + skip_process_check = False + @classmethod def setUpClass(cls): super(TestVPNIPsec, cls).setUpClass() @@ -137,23 +143,34 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): def tearDown(self): # Check for running process - self.assertTrue(process_named_running('charon')) + if not self.skip_process_check: + self.assertTrue(process_named_running(PROCESS_NAME)) + else: + self.skip_process_check = False # Reset self.cli_delete(base_path) self.cli_delete(tunnel_path) self.cli_commit() # Check for no longer running process - self.assertFalse(process_named_running('charon')) + self.assertFalse(process_named_running(PROCESS_NAME)) def test_01_dhcp_fail_handling(self): + # Skip process check - connection is not created for this test + self.skip_process_check = True + # Interface for dhcp-interface self.cli_set(ethernet_path + [interface, 'vif', vif, 'address', 'dhcp']) # Use VLAN to avoid getting IP from qemu dhcp server + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + # Site to site peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) - self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret]) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['default-esp-group', esp_group]) self.cli_set(peer_base_path + ['dhcp-interface', f'{interface}.{vif}']) @@ -166,21 +183,30 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): dhcp_waiting = read_file(dhcp_waiting_file) self.assertIn(f'{interface}.{vif}', dhcp_waiting) # Ensure dhcp-failed interface was added for dhclient hook + self.cli_delete(ethernet_path + [interface, 'vif', vif, 'address']) + def test_02_site_to_site(self): self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) - # Site to site local_address = '192.0.2.10' priority = '20' life_bytes = '100000' life_packets = '2000000' + + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + + # Site to site peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(base_path + ['esp-group', esp_group, 'life-bytes', life_bytes]) self.cli_set(base_path + ['esp-group', esp_group, 'life-packets', life_packets]) self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) - self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret]) self.cli_set(peer_base_path + ['ike-group', ike_group]) self.cli_set(peer_base_path + ['default-esp-group', esp_group]) self.cli_set(peer_base_path + ['local-address', local_address]) @@ -227,12 +253,14 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.assertIn(line, swanctl_conf) swanctl_secrets_lines = [ - f'id-local = {local_address} # dhcp:no', - f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}', + f'id-{regex_uuid4} = "{local_id}"', + f'id-{regex_uuid4} = "{remote_id}"', + f'id-{regex_uuid4} = "{local_address}"', + f'id-{regex_uuid4} = "{peer_ip}"', f'secret = "{secret}"' ] for line in swanctl_secrets_lines: - self.assertIn(line, swanctl_conf) + self.assertRegex(swanctl_conf, fr'{line}') def test_03_site_to_site_vti(self): @@ -246,10 +274,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): # VTI interface self.cli_set(vti_path + [vti, 'address', '10.1.1.1/24']) + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + # Site to site peer_base_path = base_path + ['site-to-site', 'peer', connection_name] self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) - self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret]) self.cli_set(peer_base_path + ['connection-type', 'none']) self.cli_set(peer_base_path + ['force-udp-encapsulation']) self.cli_set(peer_base_path + ['ike-group', ike_group]) @@ -292,12 +325,12 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.assertIn(line, swanctl_conf) swanctl_secrets_lines = [ - f'id-local = {local_address} # dhcp:no', - f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}', + f'id-{regex_uuid4} = "{local_id}"', + f'id-{regex_uuid4} = "{remote_id}"', f'secret = "{secret}"' ] for line in swanctl_secrets_lines: - self.assertIn(line, swanctl_conf) + self.assertRegex(swanctl_conf, fr'{line}') def test_04_dmvpn(self): @@ -310,7 +343,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.cli_set(tunnel_path + [tunnel_if, 'address', '172.16.253.134/29']) self.cli_set(tunnel_path + [tunnel_if, 'encapsulation', 'gre']) self.cli_set(tunnel_path + [tunnel_if, 'source-address', '192.0.2.1']) - self.cli_set(tunnel_path + [tunnel_if, 'multicast', 'enable']) + self.cli_set(tunnel_path + [tunnel_if, 'enable-multicast']) self.cli_set(tunnel_path + [tunnel_if, 'parameters', 'ip', 'key', '1']) # NHRP @@ -334,6 +367,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'dh-group', '2']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'encryption', 'aes256']) self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'hash', 'sha1']) + self.cli_set(base_path + ['ike-group', ike_group, 'proposal', '2', 'prf', 'prfsha1']) # Profile self.cli_set(base_path + ['profile', 'NHRPVPN', 'authentication', 'mode', 'pre-shared-secret']) @@ -346,7 +380,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): swanctl_conf = read_file(swanctl_file) swanctl_lines = [ - f'proposals = aes128-sha1-modp1024,aes256-sha1-modp1024', + f'proposals = aes128-sha1-modp1024,aes256-sha1-prfsha1-modp1024', f'version = 1', f'rekey_time = {ike_lifetime}s', f'rekey_time = {esp_lifetime}s', @@ -450,9 +484,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['options', 'interface', 'tun1']) self.cli_set(base_path + ['ike-group', ike_group, 'key-exchange', 'ikev2']) + # vpn ipsec auth psk <tag> id <x.x.x.x> + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', remote_id]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', local_address]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'id', peer_ip]) + self.cli_set(base_path + ['authentication', 'psk', connection_name, 'secret', secret]) + self.cli_set(peer_base_path + ['authentication', 'local-id', local_id]) self.cli_set(peer_base_path + ['authentication', 'mode', 'pre-shared-secret']) - self.cli_set(peer_base_path + ['authentication', 'pre-shared-secret', secret]) self.cli_set(peer_base_path + ['authentication', 'remote-id', remote_id]) self.cli_set(peer_base_path + ['connection-type', 'initiate']) self.cli_set(peer_base_path + ['ike-group', ike_group]) @@ -482,15 +522,15 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase): self.assertIn(line, swanctl_conf) swanctl_secrets_lines = [ - f'id-local = {local_address} # dhcp:no', - f'id-remote_{peer_ip.replace(".","-")} = {peer_ip}', - f'id-localid = {local_id}', - f'id-remoteid = {remote_id}', + f'id-{regex_uuid4} = "{local_id}"', + f'id-{regex_uuid4} = "{remote_id}"', + f'id-{regex_uuid4} = "{peer_ip}"', + f'id-{regex_uuid4} = "{local_address}"', f'secret = "{secret}"', ] for line in swanctl_secrets_lines: - self.assertIn(line, swanctl_conf) + self.assertRegex(swanctl_conf, fr'{line}') # Verify charon configuration charon_conf = read_file(charon_file) diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py index 8572d6d66..04abeb1aa 100755 --- a/smoketest/scripts/cli/test_vpn_openconnect.py +++ b/smoketest/scripts/cli/test_vpn_openconnect.py @@ -18,8 +18,9 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM -from vyos.util import process_named_running -from vyos.util import read_file +from vyos.template import ip_from_cidr +from vyos.utils.process import process_named_running +from vyos.utils.file import read_file OCSERV_CONF = '/run/ocserv/ocserv.conf' base_path = ['vpn', 'openconnect'] @@ -52,6 +53,9 @@ config_file = '/run/ocserv/ocserv.conf' auth_file = '/run/ocserv/ocpasswd' otp_file = '/run/ocserv/users.oath' +listen_if = 'dum116' +listen_address = '100.64.0.1/32' + class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -61,6 +65,8 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): # out the current configuration :) cls.cli_delete(cls, base_path) + cls.cli_set(cls, ['interfaces', 'dummy', listen_if, 'address', listen_address]) + cls.cli_set(cls, pki_path + ['ca', 'openconnect', 'certificate', cert_data.replace('\n','')]) cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'certificate', cert_data.replace('\n','')]) cls.cli_set(cls, pki_path + ['certificate', 'openconnect', 'private', 'key', key_data.replace('\n','')]) @@ -68,6 +74,7 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): @classmethod def tearDownClass(cls): cls.cli_delete(cls, pki_path) + cls.cli_delete(cls, ['interfaces', 'dummy', listen_if]) super(TestVPNOpenConnect, cls).tearDownClass() def tearDown(self): @@ -104,6 +111,9 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['ssl', 'ca-certificate', 'openconnect']) self.cli_set(base_path + ['ssl', 'certificate', 'openconnect']) + listen_ip_no_cidr = ip_from_cidr(listen_address) + self.cli_set(base_path + ['listen-address', listen_ip_no_cidr]) + self.cli_commit() # Verify configuration @@ -111,10 +121,15 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase): # authentication mode local password-otp self.assertIn(f'auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"', daemon_config) + self.assertIn(f'listen-host = {listen_ip_no_cidr}', daemon_config) self.assertIn(f'ipv4-network = {v4_subnet}', daemon_config) self.assertIn(f'ipv6-network = {v6_prefix}', daemon_config) self.assertIn(f'ipv6-subnet-prefix = {v6_len}', daemon_config) + # defaults + self.assertIn(f'tcp-port = 443', daemon_config) + self.assertIn(f'udp-port = 443', daemon_config) + for ns in name_server: self.assertIn(f'dns = {ns}', daemon_config) for domain in split_dns: diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py index 434e3aa05..232eafcf2 100755 --- a/smoketest/scripts/cli/test_vpn_sstp.py +++ b/smoketest/scripts/cli/test_vpn_sstp.py @@ -17,7 +17,7 @@ import unittest from base_accel_ppp_test import BasicAccelPPPTest -from vyos.util import read_file +from vyos.utils.file import read_file pki_path = ['pki'] diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index 176c095fb..0f658f366 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2022 VyOS maintainers and contributors +# Copyright (C) 2020-2023 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -26,13 +26,15 @@ from vyos.configsession import ConfigSessionError from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.template import is_ipv4 -from vyos.util import cmd -from vyos.util import read_file -from vyos.util import get_interface_config -from vyos.validate import is_intf_addr_assigned +from vyos.utils.process import cmd +from vyos.utils.file import read_file +from vyos.utils.network import get_interface_config +from vyos.utils.network import is_intf_addr_assigned base_path = ['vrf'] vrfs = ['red', 'green', 'blue', 'foo-bar', 'baz_foo'] +v4_protocols = ['any', 'babel', 'bgp', 'connected', 'eigrp', 'isis', 'kernel', 'ospf', 'rip', 'static', 'table'] +v6_protocols = ['any', 'babel', 'bgp', 'connected', 'isis', 'kernel', 'ospfv3', 'ripng', 'static', 'table'] class VRFTest(VyOSUnitTestSHIM.TestCase): _interfaces = [] @@ -45,9 +47,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): tmp = os.environ['TEST_ETH'].split() cls._interfaces = tmp else: - for tmp in Section.interfaces('ethernet'): - if not '.' in tmp: - cls._interfaces.append(tmp) + for tmp in Section.interfaces('ethernet', vlan=False): + cls._interfaces.append(tmp) # call base-classes classmethod super(VRFTest, cls).setUpClass() @@ -59,7 +60,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.assertNotIn(vrf, interfaces()) def test_vrf_vni_and_table_id(self): - table = '1000' + base_table = '1000' + table = base_table for vrf in vrfs: base = base_path + ['name', vrf] description = f'VyOS-VRF-{vrf}' @@ -80,7 +82,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify VRF configuration - table = '1000' + table = base_table iproute2_config = read_file('/etc/iproute2/rt_tables.d/vyos-vrf.conf') for vrf in vrfs: description = f'VyOS-VRF-{vrf}' @@ -194,7 +196,8 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.cli_delete(['interfaces', section, interface, 'vrf']) def test_vrf_static_route(self): - table = '100' + base_table = '100' + table = base_table for vrf in vrfs: next_hop = f'192.0.{table}.1' prefix = f'10.0.{table}.0/24' @@ -215,13 +218,12 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify VRF configuration - table = '100' + table = base_table for vrf in vrfs: next_hop = f'192.0.{table}.1' prefix = f'10.0.{table}.0/24' self.assertTrue(vrf in interfaces()) - vrf_if = Interface(vrf) frrconfig = self.getFRRconfig(f'vrf {vrf}') self.assertIn(f' vni {table}', frrconfig) @@ -291,5 +293,200 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.assertEqual(read_file(f'/proc/sys/net/ipv4/conf/{vrf}/forwarding'), '0') self.assertEqual(read_file(f'/proc/sys/net/ipv6/conf/{vrf}/forwarding'), '0') + def test_vrf_ip_protocol_route_map(self): + table = '6000' + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + + for protocol in v4_protocols: + self.cli_set(['policy', 'route-map', f'route-map-{vrf}-{protocol}', 'rule', '10', 'action', 'permit']) + self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'route-map-{vrf}-{protocol}']) + + table = str(int(table) + 1) + + self.cli_commit() + + # Verify route-map properly applied to FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertIn(f'vrf {vrf}', frrconfig) + for protocol in v4_protocols: + self.assertIn(f' ip protocol {protocol} route-map route-map-{vrf}-{protocol}', frrconfig) + + # Delete route-maps + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_delete(['policy', 'route-map', f'route-map-{vrf}-{protocol}']) + self.cli_delete(base + ['ip', 'protocol']) + + self.cli_commit() + + # Verify route-map properly is removed from FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertNotIn(f'vrf {vrf}', frrconfig) + + def test_vrf_ip_ipv6_protocol_non_existing_route_map(self): + table = '6100' + non_existing = 'non-existing' + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + for protocol in v4_protocols: + self.cli_set(base + ['ip', 'protocol', protocol, 'route-map', f'v4-{non_existing}']) + for protocol in v6_protocols: + self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', f'v6-{non_existing}']) + + table = str(int(table) + 1) + + # Both v4 and v6 route-maps do not exist yet + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', f'v4-{non_existing}', 'rule', '10', 'action', 'deny']) + + # v6 route-map does not exist yet + with self.assertRaises(ConfigSessionError): + self.cli_commit() + self.cli_set(['policy', 'route-map', f'v6-{non_existing}', 'rule', '10', 'action', 'deny']) + + # Commit again + self.cli_commit() + + def test_vrf_ipv6_protocol_route_map(self): + table = '6200' + + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', table]) + + for protocol in v6_protocols: + route_map = f'route-map-{vrf}-{protocol.replace("ospfv3", "ospf6")}' + self.cli_set(['policy', 'route-map', route_map, 'rule', '10', 'action', 'permit']) + self.cli_set(base + ['ipv6', 'protocol', protocol, 'route-map', route_map]) + + table = str(int(table) + 1) + + self.cli_commit() + + # Verify route-map properly applied to FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertIn(f'vrf {vrf}', frrconfig) + for protocol in v6_protocols: + # VyOS and FRR use a different name for OSPFv3 (IPv6) + if protocol == 'ospfv3': + protocol = 'ospf6' + route_map = f'route-map-{vrf}-{protocol}' + self.assertIn(f' ipv6 protocol {protocol} route-map {route_map}', frrconfig) + + # Delete route-maps + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_delete(['policy', 'route-map', f'route-map-{vrf}-{protocol}']) + self.cli_delete(base + ['ipv6', 'protocol']) + + self.cli_commit() + + # Verify route-map properly is removed from FRR + for vrf in vrfs: + frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon='zebra') + self.assertNotIn(f'vrf {vrf}', frrconfig) + + def test_vrf_vni_duplicates(self): + base_table = '6300' + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + self.cli_set(base + ['vni', '100']) + table = str(int(table) + 1) + + # L3VNIs can only be used once + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['vni', str(table)]) + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + self.assertTrue(vrf in interfaces()) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + # Increment table ID for the next run + table = str(int(table) + 1) + + def test_vrf_vni_add_change_remove(self): + base_table = '6300' + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['table', str(table)]) + self.cli_set(base + ['vni', str(table)]) + table = str(int(table) + 1) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + self.assertTrue(vrf in interfaces()) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + # Increment table ID for the next run + table = str(int(table) + 1) + + # Now change all L3VNIs (increment 2) + # We must also change the base_table number as we probably could get + # duplicate VNI's during the test as VNIs are applied 1:1 to FRR + base_table = '5000' + table = base_table + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_set(base + ['vni', str(table)]) + table = str(int(table) + 2) + + # commit changes + self.cli_commit() + + # Verify VRF configuration + table = base_table + for vrf in vrfs: + self.assertTrue(vrf in interfaces()) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertIn(f' vni {table}', frrconfig) + # Increment table ID for the next run + table = str(int(table) + 2) + + # Now delete all the VNIs + for vrf in vrfs: + base = base_path + ['name', vrf] + self.cli_delete(base + ['vni']) + + # commit changes + self.cli_commit() + + # Verify no VNI is defined + for vrf in vrfs: + self.assertTrue(vrf in interfaces()) + + frrconfig = self.getFRRconfig(f'vrf {vrf}') + self.assertNotIn('vni', frrconfig) + + if __name__ == '__main__': - unittest.main(verbosity=2) + unittest.main(verbosity=2, failfast=True) diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py index 4d9cbacbe..a39ae50bc 100755 --- a/smoketest/scripts/system/test_kernel_options.py +++ b/smoketest/scripts/system/test_kernel_options.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-2023 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 @@ -14,14 +14,18 @@ # 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 gzip import re +import os import platform import unittest -from vyos.util import read_file +from vyos.utils.process import call +from vyos.utils.file import read_file kernel = platform.release() config = read_file(f'/boot/config-{kernel}') +CONFIG = '/proc/config.gz' class TestKernelModules(unittest.TestCase): """ VyOS makes use of a lot of Kernel drivers, modules and features. The @@ -42,6 +46,35 @@ class TestKernelModules(unittest.TestCase): tmp = re.findall(f'{option}=(y|m)', config) self.assertTrue(tmp) + def test_dropmon_enabled(self): + options_to_check = [ + 'CONFIG_NET_DROP_MONITOR=y', + 'CONFIG_UPROBE_EVENTS=y', + 'CONFIG_BPF_EVENTS=y', + 'CONFIG_TRACEPOINTS=y' + ] + if not os.path.isfile(CONFIG): + call('sudo modprobe configs') + + with gzip.open(CONFIG, 'rt') as f: + config_data = f.read() + for option in options_to_check: + self.assertIn(option, config_data, + f"Option {option} is not present in /proc/config.gz") + + def test_synproxy_enabled(self): + options_to_check = [ + 'CONFIG_NFT_SYNPROXY', + 'CONFIG_IP_NF_TARGET_SYNPROXY' + ] + if not os.path.isfile(CONFIG): + call('sudo modprobe configs') + with gzip.open(CONFIG, 'rt') as f: + config_data = f.read() + for option in options_to_check: + tmp = re.findall(f'{option}=(y|m)', config_data) + self.assertTrue(tmp) + def test_qemu_support(self): # The bond/lacp interface must be enabled in the OS Kernel for option in ['CONFIG_VIRTIO_BLK', 'CONFIG_SCSI_VIRTIO', @@ -58,6 +91,7 @@ class TestKernelModules(unittest.TestCase): tmp = re.findall(f'{option}=(y|m)', config) self.assertTrue(tmp) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/system/test_module_load.py b/smoketest/scripts/system/test_module_load.py index 76a41ac4d..9d94f01e6 100755 --- a/smoketest/scripts/system/test_module_load.py +++ b/smoketest/scripts/system/test_module_load.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2020 VyOS maintainers and contributors +# Copyright (C) 2019-2023 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 @@ -15,7 +15,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. import unittest -from vyos.util import cmd +from vyos.utils.process import cmd modules = { "intel": ["e1000", "e1000e", "igb", "ixgb", "ixgbe", "ixgbevf", "i40e", @@ -24,7 +24,7 @@ modules = { "qat_c62x", "qat_c62xvf", "qat_d15xx", "qat_d15xxvf", "qat_dh895xcc", "qat_dh895xccvf"], "accel_ppp": ["ipoe", "vlan_mon"], - "misc": ["wireguard"] + "openvpn": ["ovpn-dco-v2"] } class TestKernelModules(unittest.TestCase): |