From e64ab9ec34ce8cb221f3c82787d3641efa30aac0 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sun, 20 Sep 2020 13:12:16 +0200
Subject: smoketest: T2903: test 802.1ad (Q-in-Q) ethertype

---
 smoketest/scripts/cli/base_interfaces_test.py | 7 +++++++
 1 file changed, 7 insertions(+)

(limited to 'smoketest')

diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index 14ec7e137..047c19dd0 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -14,12 +14,15 @@
 
 import os
 import unittest
+import json
 
 from netifaces import ifaddresses, AF_INET, AF_INET6
 
 from vyos.configsession import ConfigSession
 from vyos.ifconfig import Interface
 from vyos.util import read_file
+from vyos.util import cmd
+from vyos.util import vyos_dict_search
 from vyos.validate import is_intf_addr_assigned, is_ipv6_link_local
 
 class BasicInterfaceTest:
@@ -212,8 +215,12 @@ class BasicInterfaceTest:
                             self.session.set(base + ['address', address])
 
             self.session.commit()
+
             for interface in self._interfaces:
                 for vif_s in self._qinq_range:
+                    tmp = json.loads(cmd(f'ip -d -j link show dev {interface}.{vif_s}'))[0]
+                    self.assertEqual(vyos_dict_search('linkinfo.info_data.protocol', tmp), '802.1ad')
+
                     for vif_c in self._vlan_range:
                         vif = f'{interface}.{vif_s}.{vif_c}'
                         for address in self._test_addr:
-- 
cgit v1.2.3


From 993f6873c02f3f79013acedfe61ce705bdb3a4d0 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sun, 20 Sep 2020 13:53:55 +0200
Subject: wwan: ifconfig: T2905: sync CLI nodes in dialup interfaces

Both PPPoE and WWAN interfaces are dialer interfaces handled by ppp, but use
different CLI nodes for the same functionality. PPPoE has "connect-on-demand"
to initiate an "on-demand" dialing and WWAN uses "ondemand" for this purpose.

Rename WWAN "ondemand" node to "connect-on-demand".
---
 data/templates/wwan/peer.tmpl                              |  2 +-
 .../include/interface-dial-on-demand.xml.i                 |  6 ++++++
 interface-definitions/interfaces-pppoe.xml.in              |  7 +------
 interface-definitions/interfaces-wirelessmodem.xml.in      |  7 +------
 smoketest/scripts/cli/test_interfaces_wirelessmodem.py     |  2 +-
 src/migration-scripts/interfaces/12-to-13                  | 14 ++++++++++++++
 6 files changed, 24 insertions(+), 14 deletions(-)
 create mode 100644 interface-definitions/include/interface-dial-on-demand.xml.i

(limited to 'smoketest')

diff --git a/data/templates/wwan/peer.tmpl b/data/templates/wwan/peer.tmpl
index aa759f741..e23881bf8 100644
--- a/data/templates/wwan/peer.tmpl
+++ b/data/templates/wwan/peer.tmpl
@@ -21,7 +21,7 @@ noauth
 crtscts
 lock
 persist
-{{ "demand" if ondemand is defined }}
+{{ "demand" if connect_on_demand is defined }}
 
 connect '/usr/sbin/chat -v -t6 -f /etc/ppp/peers/chat.{{ ifname }}'
 
diff --git a/interface-definitions/include/interface-dial-on-demand.xml.i b/interface-definitions/include/interface-dial-on-demand.xml.i
new file mode 100644
index 000000000..c14ddf6f5
--- /dev/null
+++ b/interface-definitions/include/interface-dial-on-demand.xml.i
@@ -0,0 +1,6 @@
+<leafNode name="connect-on-demand">
+  <properties>
+    <help>Establishment connection automatically when traffic is sent</help>
+    <valueless/>
+  </properties>
+</leafNode>
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 8a6c61312..b6208e0b9 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -42,12 +42,7 @@
               </leafNode>
             </children>
           </node>
-          <leafNode name="connect-on-demand">
-            <properties>
-              <help>Automatic establishment of PPPOE connection when traffic is sent</help>
-              <valueless/>
-            </properties>
-          </leafNode>
+          #include <include/interface-dial-on-demand.xml.i>
           <leafNode name="default-route">
             <properties>
               <help>Default route insertion behaviour (default: auto)</help>
diff --git a/interface-definitions/interfaces-wirelessmodem.xml.in b/interface-definitions/interfaces-wirelessmodem.xml.in
index d375b808d..96604ff00 100644
--- a/interface-definitions/interfaces-wirelessmodem.xml.in
+++ b/interface-definitions/interfaces-wirelessmodem.xml.in
@@ -80,12 +80,7 @@
               <valueless/>
             </properties>
           </leafNode>
-          <leafNode name="ondemand">
-            <properties>
-              <help>Only dial when traffic is available</help>
-              <valueless/>
-            </properties>
-          </leafNode>
+          #include <include/interface-dial-on-demand.xml.i>
         </children>
       </tagNode>
     </children>
diff --git a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py b/smoketest/scripts/cli/test_interfaces_wirelessmodem.py
index 40cd03b93..efc9c0e98 100755
--- a/smoketest/scripts/cli/test_interfaces_wirelessmodem.py
+++ b/smoketest/scripts/cli/test_interfaces_wirelessmodem.py
@@ -43,7 +43,7 @@ class WWANInterfaceTest(unittest.TestCase):
     def test_wlm_1(self):
         for interface in self._interfaces:
             self.session.set(base_path + [interface, 'no-peer-dns'])
-            self.session.set(base_path + [interface, 'ondemand'])
+            self.session.set(base_path + [interface, 'connect-on-demand'])
 
             # check validate() - APN must be configure
             with self.assertRaises(ConfigSessionError):
diff --git a/src/migration-scripts/interfaces/12-to-13 b/src/migration-scripts/interfaces/12-to-13
index 17d1d0b0a..f866ca9a6 100755
--- a/src/migration-scripts/interfaces/12-to-13
+++ b/src/migration-scripts/interfaces/12-to-13
@@ -17,6 +17,8 @@
 # - T2903: Change vif-s ethertype from numeric number to literal
 #   - 0x88a8 -> 802.1ad
 #   - 0x8100 -> 802.1q
+# - T2905: Change WWAN "ondemand" node to "connect-on-demand" to have identical
+#   CLI nodes for both types of dialer interfaces
 
 from sys import exit, argv
 from vyos.configtree import ConfigTree
@@ -32,6 +34,9 @@ if __name__ == '__main__':
 
     config = ConfigTree(config_file)
 
+    #
+    # T2903
+    #
     for type in config.list_nodes(['interfaces']):
         for interface in config.list_nodes(['interfaces', type]):
             if not config.exists(['interfaces', type, interface, 'vif-s']):
@@ -48,6 +53,15 @@ if __name__ == '__main__':
                     config.set(base_path + ['protocol'], value=protocol)
                     config.delete(base_path + ['ethertype'])
 
+    #
+    # T2905
+    #
+    wwan_base = ['interfaces', 'wirelessmodem']
+    if config.exists(wwan_base):
+        for interface in config.list_nodes(wwan_base):
+            if config.exists(wwan_base + [interface, 'ondemand']):
+                config.rename(wwan_base + [interface, 'ondemand'], 'connect-on-demand')
+
     try:
         with open(file_name, 'w') as f:
             f.write(config.to_string())
-- 
cgit v1.2.3


From 7ea3802aa3de99ec78fbe0cf24a8527b80c927db Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Mon, 21 Sep 2020 17:30:27 +0200
Subject: smoketest: macsec: T2023: test MTU setting

---
 data/templates/macsec/wpa_supplicant.conf.tmpl  |  2 +-
 smoketest/scripts/cli/test_interfaces_macsec.py | 39 +++++++++++++++----------
 2 files changed, 25 insertions(+), 16 deletions(-)

(limited to 'smoketest')

diff --git a/data/templates/macsec/wpa_supplicant.conf.tmpl b/data/templates/macsec/wpa_supplicant.conf.tmpl
index 1731bf160..5b353def8 100644
--- a/data/templates/macsec/wpa_supplicant.conf.tmpl
+++ b/data/templates/macsec/wpa_supplicant.conf.tmpl
@@ -1,4 +1,4 @@
-# autogenerated by interfaces-macsec.py
+### Autogenerated by interfaces-macsec.py ###
 
 # see full documentation:
 # https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf
diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index 0f1b6486d..30b040b97 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -23,8 +23,8 @@ from base_interfaces_test import BasicInterfaceTest
 from vyos.configsession import ConfigSessionError
 from vyos.util import read_file
 
-def get_config_value(intf, key):
-    tmp = read_file(f'/run/wpa_supplicant/{intf}.conf')
+def get_config_value(interface, key):
+    tmp = read_file(f'/run/wpa_supplicant/{interface}.conf')
     tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp)
     return tmp[0]
 
@@ -49,52 +49,61 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest):
         mode - both using different mandatory settings, lets test
         encryption as the basic authentication test has been performed
         using the base class tests """
-        intf = 'macsec0'
-        src_intf = 'eth0'
+        interface = 'macsec0'
+        src_interface = 'eth0'
         mak_cak = '232e44b7fda6f8e2d88a07bf78a7aff4'
         mak_ckn = '40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836'
         replay_window = '64'
-        self.session.set(self._base_path + [intf, 'security', 'encrypt'])
+        self.session.set(self._base_path + [interface, 'security', 'encrypt'])
 
         # check validate() - Cipher suite must be set for MACsec
         with self.assertRaises(ConfigSessionError):
             self.session.commit()
-        self.session.set(self._base_path + [intf, 'security', 'cipher', 'gcm-aes-128'])
+        self.session.set(self._base_path + [interface, 'security', 'cipher', 'gcm-aes-128'])
 
         # check validate() - Physical source interface must be set for MACsec
         with self.assertRaises(ConfigSessionError):
             self.session.commit()
-        self.session.set(self._base_path + [intf, 'source-interface', src_intf])
+        self.session.set(self._base_path + [interface, 'source-interface', src_interface])
+
+        # check validate() - Physical source interface MTU must be higher then our MTU
+        self.session.set(self._base_path + [interface, 'mtu', '1500'])
+        with self.assertRaises(ConfigSessionError):
+            self.session.commit()
+        self.session.delete(self._base_path + [interface, 'mtu'])
 
         # check validate() - MACsec security keys mandartory when encryption is enabled
         with self.assertRaises(ConfigSessionError):
             self.session.commit()
-        self.session.set(self._base_path + [intf, 'security', 'mka', 'cak', mak_cak])
+        self.session.set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak])
 
         # check validate() - MACsec security keys mandartory when encryption is enabled
         with self.assertRaises(ConfigSessionError):
             self.session.commit()
-        self.session.set(self._base_path + [intf, 'security', 'mka', 'ckn', mak_ckn])
+        self.session.set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn])
 
-        self.session.set(self._base_path + [intf, 'security', 'replay-window', replay_window])
+        self.session.set(self._base_path + [interface, 'security', 'replay-window', replay_window])
         self.session.commit()
 
-        tmp = get_config_value(src_intf, 'macsec_integ_only')
+        tmp = get_config_value(src_interface, 'macsec_integ_only')
         self.assertTrue("0" in tmp)
 
-        tmp = get_config_value(src_intf, 'mka_cak')
+        tmp = get_config_value(src_interface, 'mka_cak')
         self.assertTrue(mak_cak in tmp)
 
-        tmp = get_config_value(src_intf, 'mka_ckn')
+        tmp = get_config_value(src_interface, 'mka_ckn')
         self.assertTrue(mak_ckn in tmp)
 
         # check that the default priority of 255 is programmed
-        tmp = get_config_value(src_intf, 'mka_priority')
+        tmp = get_config_value(src_interface, 'mka_priority')
         self.assertTrue("255" in tmp)
 
-        tmp = get_config_value(src_intf, 'macsec_replay_window')
+        tmp = get_config_value(src_interface, 'macsec_replay_window')
         self.assertTrue(replay_window in tmp)
 
+        tmp = read_file(f'/sys/class/net/{interface}/mtu')
+        self.assertEqual(tmp, '1460')
+
         # Check for running process
         self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter()))
 
-- 
cgit v1.2.3


From 4ce3720109cdd6648e5251da98bf11902c443e2e Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Mon, 21 Sep 2020 17:30:53 +0200
Subject: smoketest: kernel: check for mandatory bond/lacp and bridge options

---
 smoketest/scripts/system/test_kernel_options.py | 47 +++++++++++++++++++++++++
 1 file changed, 47 insertions(+)
 create mode 100755 smoketest/scripts/system/test_kernel_options.py

(limited to 'smoketest')

diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
new file mode 100755
index 000000000..6316521f6
--- /dev/null
+++ b/smoketest/scripts/system/test_kernel_options.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import platform
+import unittest
+
+from vyos.util import read_file
+
+kernel = platform.release()
+config = read_file(f'/boot/config-{kernel}')
+
+class TestKernelModules(unittest.TestCase):
+    """ VyOS makes use of a lot of Kernel drivers, modules and features. The
+    required modules which are essential for VyOS should be tested that they are
+    available in the Kernel that is run. """
+
+    def test_bond_interface(self):
+        """ The bond/lacp interface must be enabled in the OS Kernel """
+        for option in ['CONFIG_BONDING']:
+            tmp = re.findall(f'{option}=(y|m)', config)
+            self.assertTrue(tmp)
+
+    def test_bridge_interface(self):
+        """ The bridge interface must be enabled in the OS Kernel """
+        for option in ['CONFIG_BRIDGE',
+                       'CONFIG_BRIDGE_IGMP_SNOOPING',
+                       'CONFIG_BRIDGE_VLAN_FILTERING']:
+            tmp = re.findall(f'{option}=(y|m)', config)
+            self.assertTrue(tmp)
+
+if __name__ == '__main__':
+    unittest.main()
+
-- 
cgit v1.2.3


From feb491584d84636883000634bd4bba9b014b2c83 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Tue, 22 Sep 2020 18:43:34 +0200
Subject: smoketest: macsec: T2023: check that source-interface is not used by
 any other interface

---
 smoketest/scripts/cli/test_interfaces_macsec.py | 137 ++++++++++++++++--------
 1 file changed, 91 insertions(+), 46 deletions(-)

(limited to 'smoketest')

diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index 30b040b97..fab5433de 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -16,10 +16,12 @@
 
 import re
 import unittest
+
 from psutil import process_iter
+from base_interfaces_test import BasicInterfaceTest
+from netifaces import interfaces
 
 from vyos.ifconfig import Section
-from base_interfaces_test import BasicInterfaceTest
 from vyos.configsession import ConfigSessionError
 from vyos.util import read_file
 
@@ -32,80 +34,123 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest):
     def setUp(self):
          super().setUp()
          self._base_path = ['interfaces', 'macsec']
-         self._options = {
-             'macsec0': ['source-interface eth0',
-                         'security cipher gcm-aes-128']
-         }
+         self._options = { 'macsec0': ['source-interface eth0', 'security cipher gcm-aes-128'] }
 
          # if we have a physical eth1 interface, add a second macsec instance
          if 'eth1' in Section.interfaces("ethernet"):
-             macsec = { 'macsec1': ['source-interface eth1', 'security cipher gcm-aes-128'] }
+             macsec = { 'macsec1': [f'source-interface eth1', 'security cipher gcm-aes-128'] }
              self._options.update(macsec)
 
          self._interfaces = list(self._options)
 
     def test_encryption(self):
-        """ MACsec can be operating in authentication and encryption
-        mode - both using different mandatory settings, lets test
-        encryption as the basic authentication test has been performed
-        using the base class tests """
-        interface = 'macsec0'
-        src_interface = 'eth0'
+        """ MACsec can be operating in authentication and encryption mode - both
+        using different mandatory settings, lets test encryption as the basic
+        authentication test has been performed using the base class tests """
+
         mak_cak = '232e44b7fda6f8e2d88a07bf78a7aff4'
         mak_ckn = '40916f4b23e3d548ad27eedd2d10c6f98c2d21684699647d63d41b500dfe8836'
         replay_window = '64'
-        self.session.set(self._base_path + [interface, 'security', 'encrypt'])
 
-        # check validate() - Cipher suite must be set for MACsec
-        with self.assertRaises(ConfigSessionError):
-            self.session.commit()
-        self.session.set(self._base_path + [interface, 'security', 'cipher', 'gcm-aes-128'])
+        for interface, option_value in self._options.items():
+            for option in option_value:
+                if option.split()[0] == 'source-interface':
+                    src_interface = option.split()[1]
 
-        # check validate() - Physical source interface must be set for MACsec
-        with self.assertRaises(ConfigSessionError):
-            self.session.commit()
-        self.session.set(self._base_path + [interface, 'source-interface', src_interface])
+                self.session.set(self._base_path + [interface] + option.split())
 
-        # check validate() - Physical source interface MTU must be higher then our MTU
-        self.session.set(self._base_path + [interface, 'mtu', '1500'])
-        with self.assertRaises(ConfigSessionError):
+            # Encrypt link
+            self.session.set(self._base_path + [interface, 'security', 'encrypt'])
+
+            # check validate() - Physical source interface MTU must be higher then our MTU
+            self.session.set(self._base_path + [interface, 'mtu', '1500'])
+            with self.assertRaises(ConfigSessionError):
+                self.session.commit()
+            self.session.delete(self._base_path + [interface, 'mtu'])
+
+            # check validate() - MACsec security keys mandartory when encryption is enabled
+            with self.assertRaises(ConfigSessionError):
+                self.session.commit()
+            self.session.set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak])
+
+            # check validate() - MACsec security keys mandartory when encryption is enabled
+            with self.assertRaises(ConfigSessionError):
+                self.session.commit()
+            self.session.set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn])
+
+            self.session.set(self._base_path + [interface, 'security', 'replay-window', replay_window])
+
+            # final commit of settings
             self.session.commit()
-        self.session.delete(self._base_path + [interface, 'mtu'])
 
-        # check validate() - MACsec security keys mandartory when encryption is enabled
+            tmp = get_config_value(src_interface, 'macsec_integ_only')
+            self.assertTrue("0" in tmp)
+
+            tmp = get_config_value(src_interface, 'mka_cak')
+            self.assertTrue(mak_cak in tmp)
+
+            tmp = get_config_value(src_interface, 'mka_ckn')
+            self.assertTrue(mak_ckn in tmp)
+
+            # check that the default priority of 255 is programmed
+            tmp = get_config_value(src_interface, 'mka_priority')
+            self.assertTrue("255" in tmp)
+
+            tmp = get_config_value(src_interface, 'macsec_replay_window')
+            self.assertTrue(replay_window in tmp)
+
+            tmp = read_file(f'/sys/class/net/{interface}/mtu')
+            self.assertEqual(tmp, '1460')
+
+            # Check for running process
+            self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter()))
+
+    def test_mandatory_toptions(self):
+        interface = 'macsec1'
+        self.session.set(self._base_path + [interface])
+
+        # check validate() - source interface is mandatory
         with self.assertRaises(ConfigSessionError):
             self.session.commit()
-        self.session.set(self._base_path + [interface, 'security', 'mka', 'cak', mak_cak])
+        self.session.set(self._base_path + [interface, 'source-interface', 'eth0'])
 
-        # check validate() - MACsec security keys mandartory when encryption is enabled
+        # check validate() - cipher is mandatory
         with self.assertRaises(ConfigSessionError):
             self.session.commit()
-        self.session.set(self._base_path + [interface, 'security', 'mka', 'ckn', mak_ckn])
+        self.session.set(self._base_path + [interface, 'security', 'cipher', 'gcm-aes-128'])
 
-        self.session.set(self._base_path + [interface, 'security', 'replay-window', replay_window])
+        # final commit and verify
         self.session.commit()
+        self.assertIn(interface, interfaces())
 
-        tmp = get_config_value(src_interface, 'macsec_integ_only')
-        self.assertTrue("0" in tmp)
+    def test_source_interface(self):
+        """ Ensure source-interface can bot be part of any other bond or bridge """
 
-        tmp = get_config_value(src_interface, 'mka_cak')
-        self.assertTrue(mak_cak in tmp)
+        base_bridge = ['interfaces', 'bridge', 'br200']
+        base_bond = ['interfaces', 'bonding', 'bond200']
 
-        tmp = get_config_value(src_interface, 'mka_ckn')
-        self.assertTrue(mak_ckn in tmp)
+        for interface, option_value in self._options.items():
+            for option in option_value:
+                self.session.set(self._base_path + [interface] + option.split())
+                if option.split()[0] == 'source-interface':
+                    src_interface = option.split()[1]
 
-        # check that the default priority of 255 is programmed
-        tmp = get_config_value(src_interface, 'mka_priority')
-        self.assertTrue("255" in tmp)
+            self.session.set(base_bridge + ['member', 'interface', src_interface])
+            # check validate() - Source interface must not already be a member of a bridge
+            with self.assertRaises(ConfigSessionError):
+                self.session.commit()
+            self.session.delete(base_bridge)
 
-        tmp = get_config_value(src_interface, 'macsec_replay_window')
-        self.assertTrue(replay_window in tmp)
+            self.session.set(base_bond + ['member', 'interface', src_interface])
+            # check validate() - Source interface must not already be a member of a bridge
+            with self.assertRaises(ConfigSessionError):
+                self.session.commit()
+            self.session.delete(base_bond)
 
-        tmp = read_file(f'/sys/class/net/{interface}/mtu')
-        self.assertEqual(tmp, '1460')
-
-        # Check for running process
-        self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter()))
+            # final commit and verify
+            self.session.commit()
+            self.assertIn(interface, interfaces())
 
 if __name__ == '__main__':
     unittest.main()
+
-- 
cgit v1.2.3


From 4db00f1cd820f4fc462ce3537d692694224e02a4 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Thu, 24 Sep 2020 18:23:08 +0200
Subject: smoketest: dns: forwarding: T2921: add initial testcases

---
 .../scripts/cli/test_service_dns_forwarding.py     | 163 +++++++++++++++++++++
 1 file changed, 163 insertions(+)
 create mode 100755 smoketest/scripts/cli/test_service_dns_forwarding.py

(limited to 'smoketest')

diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
new file mode 100755
index 000000000..0ae27a4d4
--- /dev/null
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -0,0 +1,163 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2020 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import os
+import unittest
+
+from psutil import process_iter
+
+from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.util import read_file
+from vyos.util import process_named_running
+
+CONFIG_FILE = '/run/powerdns/recursor.conf'
+FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf'
+PROCESS_NAME= 'pdns-r/worker'
+
+base_path = ['service', 'dns', 'forwarding']
+
+allow_from = ['192.0.2.0/24', '2001:db8::/32']
+listen_adress = ['127.0.0.1', '::1']
+
+def get_config_value(key, file=CONFIG_FILE):
+    tmp = read_file(file)
+    tmp = re.findall(r'\n{}=+(.*)'.format(key), tmp)
+    return tmp[0]
+
+class TestServicePowerDNS(unittest.TestCase):
+    def setUp(self):
+        self.session = ConfigSession(os.getpid())
+
+    def tearDown(self):
+        # Delete DNS forwarding configuration
+        self.session.delete(base_path)
+        self.session.commit()
+        del self.session
+
+    def test_basic_forwarding(self):
+        """ Check basic DNS forwarding settings """
+        cache_size = '20'
+        negative_ttl = '120'
+
+        self.session.set(base_path + ['cache-size', cache_size])
+        self.session.set(base_path + ['negative-ttl', negative_ttl])
+
+        # check validate() - allow from must be defined
+        with self.assertRaises(ConfigSessionError):
+            self.session.commit()
+        for network in allow_from:
+            self.session.set(base_path + ['allow-from', network])
+
+        # check validate() - listen-address must be defined
+        with self.assertRaises(ConfigSessionError):
+            self.session.commit()
+        for address in listen_adress:
+            self.session.set(base_path + ['listen-address', address])
+
+        # configure DNSSEC
+        self.session.set(base_path + ['dnssec', 'validate'])
+
+        # commit changes
+        self.session.commit()
+
+        # Check configured cache-size
+        tmp = get_config_value('max-cache-entries')
+        self.assertEqual(tmp, cache_size)
+
+        # Networks allowed to query this server
+        tmp = get_config_value('allow-from')
+        self.assertEqual(tmp, ','.join(allow_from))
+
+        # Addresses to listen for DNS queries
+        tmp = get_config_value('local-address')
+        self.assertEqual(tmp, ','.join(listen_adress))
+
+        # Maximum amount of time negative entries are cached
+        tmp = get_config_value('max-negative-ttl')
+        self.assertEqual(tmp, negative_ttl)
+
+        # Check for running process
+        self.assertTrue(process_named_running(PROCESS_NAME))
+
+    def test_dnssec(self):
+        """ DNSSEC option testing """
+
+        for network in allow_from:
+            self.session.set(base_path + ['allow-from', network])
+        for address in listen_adress:
+            self.session.set(base_path + ['listen-address', address])
+
+        options = ['off', 'process-no-validate', 'process', 'log-fail', 'validate']
+        for option in options:
+            self.session.set(base_path + ['dnssec', option])
+
+            # commit changes
+            self.session.commit()
+
+            tmp = get_config_value('dnssec')
+            self.assertEqual(tmp, option)
+
+            # Check for running process
+            self.assertTrue(process_named_running(PROCESS_NAME))
+
+    def test_external_nameserver(self):
+        """ Externe Domain Name Servers (DNS) addresses """
+
+        for network in allow_from:
+            self.session.set(base_path + ['allow-from', network])
+        for address in listen_adress:
+            self.session.set(base_path + ['listen-address', address])
+
+        nameservers = ['192.0.2.1', '192.0.2.2']
+        for nameserver in nameservers:
+            self.session.set(base_path + ['name-server', nameserver])
+
+        # commit changes
+        self.session.commit()
+
+        tmp = get_config_value(r'\+.', file=FORWARD_FILE)
+        self.assertEqual(tmp, ', '.join(nameservers))
+
+        # Check for running process
+        self.assertTrue(process_named_running(PROCESS_NAME))
+
+    def test_domain_forwarding(self):
+        """ Externe Domain Name Servers (DNS) addresses """
+
+        for network in allow_from:
+            self.session.set(base_path + ['allow-from', network])
+        for address in listen_adress:
+            self.session.set(base_path + ['listen-address', address])
+
+        domains = ['vyos.io', 'vyos.net']
+        nameservers = ['192.0.2.1', '192.0.2.2']
+        for domain in domains:
+            for nameserver in nameservers:
+                self.session.set(base_path + ['domain', domain, 'server', nameserver])
+
+        # commit changes
+        self.session.commit()
+
+        for domain in domains:
+            tmp = get_config_value(domain, file=FORWARD_FILE)
+            self.assertEqual(tmp, ', '.join(nameservers))
+
+        # Check for running process
+        self.assertTrue(process_named_running(PROCESS_NAME))
+
+if __name__ == '__main__':
+    unittest.main()
-- 
cgit v1.2.3


From 58ead7415a3fe8d786bdb6fd2a99d0a57770dbd7 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Thu, 24 Sep 2020 19:54:15 +0200
Subject: smoketest: (re-)use process_named_running() from vyos.util

---
 smoketest/scripts/cli/test_interfaces_macsec.py     |  6 +++---
 smoketest/scripts/cli/test_interfaces_wireless.py   | 10 ++++------
 smoketest/scripts/cli/test_service_dns_dynamic.py   | 21 +++++----------------
 .../scripts/cli/test_service_dns_forwarding.py      |  2 --
 smoketest/scripts/cli/test_service_mdns-repeater.py |  4 ++--
 smoketest/scripts/cli/test_service_pppoe-server.py  |  8 ++++----
 smoketest/scripts/cli/test_service_router-advert.py |  6 ++----
 smoketest/scripts/cli/test_service_snmp.py          | 14 ++++++++------
 smoketest/scripts/cli/test_service_ssh.py           | 15 +++++++--------
 smoketest/scripts/cli/test_system_lcd.py            |  9 +++++----
 smoketest/scripts/cli/test_system_ntp.py            | 13 ++++++++-----
 smoketest/scripts/cli/test_vpn_openconnect.py       |  8 +++-----
 12 files changed, 51 insertions(+), 65 deletions(-)

(limited to 'smoketest')

diff --git a/smoketest/scripts/cli/test_interfaces_macsec.py b/smoketest/scripts/cli/test_interfaces_macsec.py
index fab5433de..6d1be86ba 100755
--- a/smoketest/scripts/cli/test_interfaces_macsec.py
+++ b/smoketest/scripts/cli/test_interfaces_macsec.py
@@ -17,13 +17,13 @@
 import re
 import unittest
 
-from psutil import process_iter
 from base_interfaces_test import BasicInterfaceTest
 from netifaces import interfaces
 
-from vyos.ifconfig import Section
 from vyos.configsession import ConfigSessionError
+from vyos.ifconfig import Section
 from vyos.util import read_file
+from vyos.util import process_named_running
 
 def get_config_value(interface, key):
     tmp = read_file(f'/run/wpa_supplicant/{interface}.conf')
@@ -103,7 +103,7 @@ class MACsecInterfaceTest(BasicInterfaceTest.BaseTest):
             self.assertEqual(tmp, '1460')
 
             # Check for running process
-            self.assertTrue("wpa_supplicant" in (p.name() for p in process_iter()))
+            self.assertTrue(process_named_running('wpa_supplicant'))
 
     def test_mandatory_toptions(self):
         interface = 'macsec1'
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 691f633b7..0e93b6432 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -19,8 +19,7 @@ import re
 import unittest
 
 from base_interfaces_test import BasicInterfaceTest
-from psutil import process_iter
-
+from vyos.util import process_named_running
 from vyos.util import check_kmod
 from vyos.util import read_file
 
@@ -54,10 +53,10 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
         for option, option_value in self._options.items():
             if 'type access-point' in option_value:
                 # Check for running process
-                self.assertIn('hostapd', (p.name() for p in process_iter()))
+                self.assertTrue(process_named_running('hostapd'))
             elif 'type station' in option_value:
                 # Check for running process
-                self.assertIn('wpa_supplicant', (p.name() for p in process_iter()))
+                self.assertTrue(process_named_running('wpa_supplicant'))
             else:
                 self.assertTrue(False)
 
@@ -137,8 +136,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.BaseTest):
             self.assertIn(value, tmp)
 
         # Check for running process
-        self.assertIn('hostapd', (p.name() for p in process_iter()))
-
+        self.assertTrue(process_named_running('hostapd'))
 
 if __name__ == '__main__':
     check_kmod('mac80211_hwsim')
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index be52360ed..51fa38912 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -19,10 +19,11 @@ import os
 import unittest
 
 from getpass import getuser
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
 from vyos.util import read_file
 
+PROCESS_NAME = 'ddclient'
 DDCLIENT_CONF = '/run/ddclient/ddclient.conf'
 base_path = ['service', 'dns', 'dynamic']
 
@@ -32,17 +33,6 @@ def get_config_value(key):
     tmp = tmp[0].rstrip(',')
     return tmp
 
-def check_process():
-    """
-    Check for running process, process name changes dynamically e.g.
-    "ddclient - sleeping for 270 seconds", thus we need a different approach
-    """
-    running = False
-    for p in process_iter():
-        if "ddclient" in p.name():
-            running = True
-    return running
-
 class TestServiceDDNS(unittest.TestCase):
     def setUp(self):
         self.session = ConfigSession(os.getpid())
@@ -104,8 +94,7 @@ class TestServiceDDNS(unittest.TestCase):
                 self.assertTrue(pwd == "'" + password + "'")
 
             # Check for running process
-            self.assertTrue(check_process())
-
+            self.assertTrue(process_named_running(PROCESS_NAME))
 
     def test_rfc2136(self):
         """ Check if DDNS service can be configured and runs """
@@ -135,7 +124,7 @@ class TestServiceDDNS(unittest.TestCase):
         # TODO: inspect generated configuration file
 
         # Check for running process
-        self.assertTrue(check_process())
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
index 0ae27a4d4..717b5b56d 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -18,8 +18,6 @@ import re
 import os
 import unittest
 
-from psutil import process_iter
-
 from vyos.configsession import ConfigSession, ConfigSessionError
 from vyos.util import read_file
 from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py
index 18900b6d2..de73b9914 100755
--- a/smoketest/scripts/cli/test_service_mdns-repeater.py
+++ b/smoketest/scripts/cli/test_service_mdns-repeater.py
@@ -17,8 +17,8 @@
 import os
 import unittest
 
-from psutil import process_iter
 from vyos.configsession import ConfigSession
+from vyos.util import process_named_running
 
 base_path = ['service', 'mdns', 'repeater']
 intf_base = ['interfaces', 'dummy']
@@ -45,7 +45,7 @@ class TestServiceMDNSrepeater(unittest.TestCase):
         self.session.commit()
 
         # Check for running process
-        self.assertTrue("mdns-repeater" in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running('mdns-repeater'))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py
index 901ca792d..3a6b12ef4 100755
--- a/smoketest/scripts/cli/test_service_pppoe-server.py
+++ b/smoketest/scripts/cli/test_service_pppoe-server.py
@@ -14,15 +14,15 @@
 # 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
 
 from configparser import ConfigParser
-from psutil import process_iter
 from vyos.configsession import ConfigSession
 from vyos.configsession import ConfigSessionError
+from vyos.util import process_named_running
 
+process_name = 'accel-pppd'
 base_path = ['service', 'pppoe-server']
 local_if = ['interfaces', 'dummy', 'dum667']
 pppoe_conf = '/run/accel-pppd/pppoe.conf'
@@ -116,7 +116,7 @@ class TestServicePPPoEServer(unittest.TestCase):
         self.assertEqual(conf['connlimit']['limit'], '20/min')
 
         # Check for running process
-        self.assertTrue('accel-pppd' in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running(process_name))
 
     def test_radius_auth(self):
         """ Test configuration of RADIUS authentication for PPPoE server """
@@ -161,7 +161,7 @@ class TestServicePPPoEServer(unittest.TestCase):
         self.assertFalse(conf['ppp'].getboolean('ccp'))
 
         # Check for running process
-        self.assertTrue('accel-pppd' in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running(process_name))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index ec2110c8a..238f59e6d 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -18,9 +18,9 @@ import re
 import os
 import unittest
 
-from psutil import process_iter
 from vyos.configsession import ConfigSession
 from vyos.util import read_file
+from vyos.util import process_named_running
 
 RADVD_CONF = '/run/radvd/radvd.conf'
 
@@ -90,10 +90,8 @@ class TestServiceRADVD(unittest.TestCase):
         tmp = get_config_value('AdvOnLink')
         self.assertEqual(tmp, 'off')
 
-
-
         # Check for running process
-        self.assertTrue('radvd' in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running('radvd'))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py
index fb5f5393f..067a3c76b 100755
--- a/smoketest/scripts/cli/test_service_snmp.py
+++ b/smoketest/scripts/cli/test_service_snmp.py
@@ -19,12 +19,15 @@ import re
 import unittest
 
 from vyos.validate import is_ipv4
-from psutil import process_iter
 
-from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
 from vyos.util import read_file
+from vyos.util import process_named_running
 
+PROCESS_NAME = 'snmpd'
 SNMPD_CONF = '/etc/snmp/snmpd.conf'
+
 base_path = ['service', 'snmp']
 
 def get_config_value(key):
@@ -78,7 +81,7 @@ class TestSNMPService(unittest.TestCase):
         self.assertTrue(expected in config)
 
         # Check for running process
-        self.assertTrue("snmpd" in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
 
     def test_snmpv3_sha(self):
@@ -113,7 +116,7 @@ class TestSNMPService(unittest.TestCase):
         # TODO: read in config file and check values
 
         # Check for running process
-        self.assertTrue("snmpd" in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
     def test_snmpv3_md5(self):
         """ Check if SNMPv3 can be configured with MD5 authentication and service runs"""
@@ -147,8 +150,7 @@ class TestSNMPService(unittest.TestCase):
         # TODO: read in config file and check values
 
         # Check for running process
-        self.assertTrue("snmpd" in (p.name() for p in process_iter()))
-
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index 79850fe44..0cd00ccce 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -18,10 +18,12 @@ import re
 import os
 import unittest
 
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
 from vyos.util import read_file
+from vyos.util import process_named_running
 
+PROCESS_NAME = 'sshd'
 SSHD_CONF = '/run/ssh/sshd_config'
 base_path = ['service', 'ssh']
 
@@ -30,9 +32,6 @@ def get_config_value(key):
     tmp = re.findall(f'\n?{key}\s+(.*)', tmp)
     return tmp
 
-def is_service_running():
-    return 'sshd' in (p.name() for p in process_iter())
-
 class TestServiceSSH(unittest.TestCase):
     def setUp(self):
         self.session = ConfigSession(os.getpid())
@@ -62,7 +61,7 @@ class TestServiceSSH(unittest.TestCase):
         self.assertEqual('22', port)
 
         # Check for running process
-        self.assertTrue(is_service_running())
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
     def test_ssh_single(self):
         """ Check if SSH service can be configured and runs """
@@ -101,7 +100,7 @@ class TestServiceSSH(unittest.TestCase):
         self.assertTrue("100" in keepalive)
 
         # Check for running process
-        self.assertTrue(is_service_running())
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
     def test_ssh_multi(self):
         """ Check if SSH service can be configured and runs with multiple
@@ -128,7 +127,7 @@ class TestServiceSSH(unittest.TestCase):
             self.assertIn(address, tmp)
 
         # Check for running process
-        self.assertTrue(is_service_running())
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_system_lcd.py b/smoketest/scripts/cli/test_system_lcd.py
index 931a91c53..9385799b0 100755
--- a/smoketest/scripts/cli/test_system_lcd.py
+++ b/smoketest/scripts/cli/test_system_lcd.py
@@ -18,9 +18,10 @@ import os
 import unittest
 
 from configparser import ConfigParser
-from psutil import process_iter
 from vyos.configsession import ConfigSession
+from vyos.util import process_named_running
 
+config_file = '/run/LCDd/LCDd.conf'
 base_path = ['system', 'lcd']
 
 class TestSystemLCD(unittest.TestCase):
@@ -42,13 +43,13 @@ class TestSystemLCD(unittest.TestCase):
 
         # load up ini-styled LCDd.conf
         conf = ConfigParser()
-        conf.read('/run/LCDd/LCDd.conf')
+        conf.read(config_file)
 
         self.assertEqual(conf['CFontzPacket']['Model'], '533')
         self.assertEqual(conf['CFontzPacket']['Device'], '/dev/ttyS1')
 
-        # both processes running
-        self.assertTrue('LCDd' in (p.name() for p in process_iter()))
+        # Check for running process
+        self.assertTrue(process_named_running('LCDd'))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py
index 856a28916..2a7c64870 100755
--- a/smoketest/scripts/cli/test_system_ntp.py
+++ b/smoketest/scripts/cli/test_system_ntp.py
@@ -18,11 +18,14 @@ import re
 import os
 import unittest
 
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
-from vyos.template import vyos_address_from_cidr, vyos_netmask_from_cidr
+from vyos.configsession import ConfigSession
+from vyos.configsession import ConfigSessionError
+from vyos.template import vyos_address_from_cidr
+from vyos.template import vyos_netmask_from_cidr
 from vyos.util import read_file
+from vyos.util import process_named_running
 
+PROCESS_NAME = 'ntpd'
 NTP_CONF = '/etc/ntp.conf'
 base_path = ['system', 'ntp']
 
@@ -63,7 +66,7 @@ class TestSystemNTP(unittest.TestCase):
             self.assertTrue(test in tmp)
 
         # Check for running process
-        self.assertTrue("ntpd" in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
     def test_ntp_clients(self):
         """ Test the allowed-networks statement """
@@ -102,7 +105,7 @@ class TestSystemNTP(unittest.TestCase):
         self.assertEqual(tmp, test)
 
         # Check for running process
-        self.assertTrue("ntpd" in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running(PROCESS_NAME))
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py
index d2b82d686..2ba6aabf9 100755
--- a/smoketest/scripts/cli/test_vpn_openconnect.py
+++ b/smoketest/scripts/cli/test_vpn_openconnect.py
@@ -14,13 +14,11 @@
 # 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
 
-from psutil import process_iter
-from vyos.configsession import ConfigSession, ConfigSessionError
-from vyos.util import read_file
+from vyos.configsession import ConfigSession
+from vyos.util import process_named_running
 
 OCSERV_CONF = '/run/ocserv/ocserv.conf'
 base_path = ['vpn', 'openconnect']
@@ -52,7 +50,7 @@ class TestVpnOpenconnect(unittest.TestCase):
         self.session.commit()
 
         # Check for running process
-        self.assertTrue("ocserv-main" in (p.name() for p in process_iter()))
+        self.assertTrue(process_named_running('ocserv-main'))
 
 if __name__ == '__main__':
     unittest.main()
-- 
cgit v1.2.3


From 44698fb03cf2017e56adbd161c66be585822b87a Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Thu, 24 Sep 2020 20:35:25 +0200
Subject: smoketest: dns: dynamic: add missing import statement

Commit 58ead741 ("smoketest: (re-)use process_named_running() from vyos.util")
missed an import statement for process_named_running(). This has been fixed.
---
 smoketest/scripts/cli/test_service_dns_dynamic.py | 1 +
 1 file changed, 1 insertion(+)

(limited to 'smoketest')

diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index 51fa38912..c7ac87135 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -22,6 +22,7 @@ from getpass import getuser
 from vyos.configsession import ConfigSession
 from vyos.configsession import ConfigSessionError
 from vyos.util import read_file
+from vyos.util import process_named_running
 
 PROCESS_NAME = 'ddclient'
 DDCLIENT_CONF = '/run/ddclient/ddclient.conf'
-- 
cgit v1.2.3


From f39f5dde342aa5e14d1fb4155920c61ac5fd11b1 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Fri, 25 Sep 2020 19:21:36 +0200
Subject: dns: forwarding: T2921: migrate to get_config_dict()

---
 data/configd-include.json                          |   1 +
 data/templates/dns-forwarding/recursor.conf.tmpl   |   2 +-
 .../recursor.forward-zones.conf.tmpl               |   4 +-
 .../recursor.vyos-hostsd.conf.lua.tmpl             |   4 +-
 interface-definitions/dns-forwarding.xml.in        |  11 +-
 .../scripts/cli/test_service_dns_forwarding.py     |  35 ++++-
 src/conf_mode/dns_forwarding.py                    | 172 ++++++++-------------
 src/services/vyos-hostsd                           |  21 +--
 src/utils/vyos-hostsd-client                       |   6 +-
 9 files changed, 122 insertions(+), 134 deletions(-)

(limited to 'smoketest')

diff --git a/data/configd-include.json b/data/configd-include.json
index 0c75657e0..2711a29b8 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -2,6 +2,7 @@
 "bcast_relay.py",
 "dhcp_relay.py",
 "dhcpv6_relay.py",
+"dns_forwarding.py",
 "dynamic_dns.py",
 "firewall_options.py",
 "host_name.py",
diff --git a/data/templates/dns-forwarding/recursor.conf.tmpl b/data/templates/dns-forwarding/recursor.conf.tmpl
index d233b8abc..b0ae3cc61 100644
--- a/data/templates/dns-forwarding/recursor.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.conf.tmpl
@@ -21,7 +21,7 @@ max-cache-entries={{ cache_size }}
 max-negative-ttl={{ negative_ttl }}
 
 # ignore-hosts-file
-export-etc-hosts={{ export_hosts_file }}
+export-etc-hosts={{ 'no' if ignore_hosts_file is defined else 'yes' }}
 
 # listen-address
 local-address={{ listen_address | join(',') }}
diff --git a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl
index e62b9bb81..90f35ae1c 100644
--- a/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl
+++ b/data/templates/dns-forwarding/recursor.forward-zones.conf.tmpl
@@ -19,10 +19,10 @@
 +.={{ n.dot_zone_ns }}
 {% endif %}
 
-{% if forward_zones %}
+{% if forward_zones is defined %}
 # zones added via 'service dns forwarding domain'
 {%   for zone, zonedata in forward_zones.items() %}
-{%     if zonedata['recursion-desired'] %}+{% endif %}{{ zone }}={{ zonedata['nslist']|join(', ') }}
+{{ "+" if zonedata['recursion_desired'] is defined }}{{ zone }}={{ zonedata['server']|join(', ') }}
 {%   endfor %}
 {% endif %}
 
diff --git a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
index 8fefae0b2..784d5c360 100644
--- a/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
+++ b/data/templates/dns-forwarding/recursor.vyos-hostsd.conf.lua.tmpl
@@ -13,10 +13,10 @@ addNTA("{{ a }}.", "{{ tag }} alias")
 {%   endfor %}
 {% endif %}
 
-{% if forward_zones %}
+{% if forward_zones is defined %}
 -- from 'service dns forwarding domain'
 {%   for zone, zonedata in forward_zones.items() %}
-{%     if zonedata['addNTA'] %}
+{%     if zonedata['addnta'] is defined %}
 addNTA("{{ zone }}", "static")
 {%     endif %}
 {%   endfor %}
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index aaf8bb27d..07e63d54a 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -16,7 +16,7 @@
             <children>
               <leafNode name="cache-size">
                 <properties>
-                  <help>DNS forwarding cache size</help>
+                  <help>DNS forwarding cache size (default: 10000)</help>
                   <valueHelp>
                     <format>0-10000</format>
                     <description>DNS forwarding cache size</description>
@@ -25,6 +25,7 @@
                     <validator name="numeric" argument="--range 0-10000"/>
                   </constraint>
                 </properties>
+                <defaultValue>10000</defaultValue>
               </leafNode>
               <leafNode name="dhcp">
                 <properties>
@@ -37,7 +38,7 @@
               </leafNode>
               <leafNode name="dnssec">
                 <properties>
-                  <help>DNSSEC mode</help>
+                  <help>DNSSEC mode (default: process-no-validate)</help>
                   <completionHelp>
                     <list>off process-no-validate process log-fail validate</list>
                   </completionHelp>
@@ -62,9 +63,10 @@
                     <description>Full blown DNSSEC validation. Send SERVFAIL to clients on bogus responses.</description>
                   </valueHelp>
                   <constraint>
-                    <regex>(off|process-no-validate|process|log-fail|validate)</regex>
+                    <regex>^(off|process-no-validate|process|log-fail|validate)$</regex>
                   </constraint>
                 </properties>
+                <defaultValue>process-no-validate</defaultValue>
               </leafNode>
               <tagNode name="domain">
                 <properties>
@@ -146,7 +148,7 @@
               </leafNode>
               <leafNode name="negative-ttl">
                 <properties>
-                  <help>Maximum amount of time negative entries are cached</help>
+                  <help>Maximum amount of time negative entries are cached (default: 3600)</help>
                   <valueHelp>
                     <format>0-7200</format>
                     <description>Seconds to cache NXDOMAIN entries</description>
@@ -155,6 +157,7 @@
                     <validator name="numeric" argument="--range 0-7200"/>
                   </constraint>
                 </properties>
+                <defaultValue>3600</defaultValue>
               </leafNode>
               <leafNode name="name-server">
                 <properties>
diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
index 717b5b56d..5e2f3dfbd 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -24,6 +24,7 @@ from vyos.util import process_named_running
 
 CONFIG_FILE = '/run/powerdns/recursor.conf'
 FORWARD_FILE = '/run/powerdns/recursor.forward-zones.conf'
+HOSTSD_FILE = '/run/powerdns/recursor.vyos-hostsd.conf.lua'
 PROCESS_NAME= 'pdns-r/worker'
 
 base_path = ['service', 'dns', 'forwarding']
@@ -69,6 +70,9 @@ class TestServicePowerDNS(unittest.TestCase):
         # configure DNSSEC
         self.session.set(base_path + ['dnssec', 'validate'])
 
+        # Do not use local /etc/hosts file in name resolution
+        self.session.set(base_path + ['ignore-hosts-file'])
+
         # commit changes
         self.session.commit()
 
@@ -88,6 +92,10 @@ class TestServicePowerDNS(unittest.TestCase):
         tmp = get_config_value('max-negative-ttl')
         self.assertEqual(tmp, negative_ttl)
 
+        # Do not use local /etc/hosts file in name resolution
+        tmp = get_config_value('export-etc-hosts')
+        self.assertEqual(tmp, 'no')
+
         # Check for running process
         self.assertTrue(process_named_running(PROCESS_NAME))
 
@@ -130,6 +138,11 @@ class TestServicePowerDNS(unittest.TestCase):
         tmp = get_config_value(r'\+.', file=FORWARD_FILE)
         self.assertEqual(tmp, ', '.join(nameservers))
 
+        # Do not use local /etc/hosts file in name resolution
+        # default: yes
+        tmp = get_config_value('export-etc-hosts')
+        self.assertEqual(tmp, 'yes')
+
         # Check for running process
         self.assertTrue(process_named_running(PROCESS_NAME))
 
@@ -141,21 +154,39 @@ class TestServicePowerDNS(unittest.TestCase):
         for address in listen_adress:
             self.session.set(base_path + ['listen-address', address])
 
-        domains = ['vyos.io', 'vyos.net']
+        domains = ['vyos.io', 'vyos.net', 'vyos.com']
         nameservers = ['192.0.2.1', '192.0.2.2']
         for domain in domains:
             for nameserver in nameservers:
                 self.session.set(base_path + ['domain', domain, 'server', nameserver])
 
+            # Test 'recursion-desired' flag for only one domain
+            if domain == domains[0]:
+                self.session.set(base_path + ['domain', domain, 'recursion-desired'])
+
+            # Test 'negative trust anchor' flag for the second domain only
+            if domain == domains[1]:
+                self.session.set(base_path + ['domain', domain, 'addnta'])
+
         # commit changes
         self.session.commit()
 
+        # Test configured name-servers
+        hosts_conf = read_file(HOSTSD_FILE)
         for domain in domains:
-            tmp = get_config_value(domain, file=FORWARD_FILE)
+            # Test 'recursion-desired' flag for the first domain only
+            if domain == domains[0]: key =f'\+{domain}'
+            else: key =f'{domain}'
+            tmp = get_config_value(key, file=FORWARD_FILE)
             self.assertEqual(tmp, ', '.join(nameservers))
 
+            # Test 'negative trust anchor' flag for the second domain only
+            if domain == domains[1]:
+                self.assertIn(f'addNTA("{domain}", "static")', hosts_conf)
+
         # Check for running process
         self.assertTrue(process_named_running(PROCESS_NAME))
 
 if __name__ == '__main__':
     unittest.main()
+
diff --git a/src/conf_mode/dns_forwarding.py b/src/conf_mode/dns_forwarding.py
index d6eb76d91..5101c1e79 100755
--- a/src/conf_mode/dns_forwarding.py
+++ b/src/conf_mode/dns_forwarding.py
@@ -17,14 +17,17 @@
 import os
 
 from sys import exit
-from copy import deepcopy
 
 from vyos.config import Config
+from vyos.configdict import dict_merge
 from vyos.hostsd_client import Client as hostsd_client
-from vyos import ConfigError
-from vyos.util import call, chown
+from vyos.util import call
+from vyos.util import chown
+from vyos.util import vyos_dict_search
 from vyos.template import render
+from vyos.xml import defaults
 
+from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
@@ -35,116 +38,63 @@ pdns_rec_hostsd_lua_conf_file = f'{pdns_rec_run_dir}/recursor.vyos-hostsd.conf.l
 pdns_rec_hostsd_zones_file = f'{pdns_rec_run_dir}/recursor.forward-zones.conf'
 pdns_rec_config_file = f'{pdns_rec_run_dir}/recursor.conf'
 
-default_config_data = {
-    'allow_from': [],
-    'cache_size': 10000,
-    'export_hosts_file': 'yes',
-    'listen_address': [],
-    'name_servers': [],
-    'negative_ttl': 3600,
-    'system': False,
-    'domains': {},
-    'dnssec': 'process-no-validate',
-    'dhcp_interfaces': []
-}
-
 hostsd_tag = 'static'
 
-def get_config(conf):
-    dns = deepcopy(default_config_data)
+def get_config(config=None):
+    if config:
+        conf = config
+    else:
+        conf = Config()
     base = ['service', 'dns', 'forwarding']
-
     if not conf.exists(base):
         return None
 
-    conf.set_level(base)
-
-    if conf.exists(['allow-from']):
-        dns['allow_from'] = conf.return_values(['allow-from'])
-
-    if conf.exists(['cache-size']):
-        cache_size = conf.return_value(['cache-size'])
-        dns['cache_size'] = cache_size
-
-    if conf.exists('negative-ttl'):
-        negative_ttl = conf.return_value(['negative-ttl'])
-        dns['negative_ttl'] = negative_ttl
-
-    if conf.exists(['domain']):
-        for domain in conf.list_nodes(['domain']):
-            conf.set_level(base + ['domain', domain])
-            entry = {
-                'nslist': bracketize_ipv6_addrs(conf.return_values(['server'])),
-                'addNTA': conf.exists(['addnta']),
-                'recursion-desired': conf.exists(['recursion-desired'])
-            }
-            dns['domains'][domain] = entry
-
-        conf.set_level(base)
+    dns = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+    # We have gathered the dict representation of the CLI, but there are default
+    # options which we need to update into the dictionary retrived.
+    default_values = defaults(base)
+    dns = dict_merge(default_values, dns)
 
-    if conf.exists(['ignore-hosts-file']):
-        dns['export_hosts_file'] = "no"
+    # some additions to the default dictionary
+    if 'system' in dns:
+        base_nameservers = ['system', 'name-server']
+        if conf.exists(base_nameservers):
+            dns.update({'system_name_server': conf.return_values(base_nameservers)})
 
-    if conf.exists(['name-server']):
-        dns['name_servers'] = bracketize_ipv6_addrs(
-                conf.return_values(['name-server']))
-
-    if conf.exists(['system']):
-        dns['system'] = True
-
-    if conf.exists(['listen-address']):
-        dns['listen_address'] = conf.return_values(['listen-address'])
-
-    if conf.exists(['dnssec']):
-        dns['dnssec'] = conf.return_value(['dnssec'])
-
-    if conf.exists(['dhcp']):
-        dns['dhcp_interfaces'] = conf.return_values(['dhcp'])
+        base_nameservers_dhcp = ['system', 'name-servers-dhcp']
+        if conf.exists(base_nameservers_dhcp):
+            dns.update({'system_name_server_dhcp': conf.return_values(base_nameservers_dhcp)})
 
     return dns
 
-def bracketize_ipv6_addrs(addrs):
-    """Wraps each IPv6 addr in addrs in [], leaving IPv4 addrs untouched."""
-    return ['[{0}]'.format(a) if a.count(':') > 1 else a for a in addrs]
-
-def verify(conf, dns):
+def verify(dns):
     # bail out early - looks like removal from running config
-    if dns is None:
+    if not dns:
         return None
 
-    if not dns['listen_address']:
-        raise ConfigError(
-            "Error: DNS forwarding requires a listen-address")
-
-    if not dns['allow_from']:
-        raise ConfigError(
-                "Error: DNS forwarding requires an allow-from network")
-
-    if dns['domains']:
-        for domain in dns['domains']:
-            if not dns['domains'][domain]['nslist']:
-                raise ConfigError((
-                    f'Error: No server configured for domain {domain}'))
-
-    no_system_nameservers = False
-    conf.set_level([])
-    if dns['system'] and not (
-            conf.exists(['system', 'name-server']) or
-            conf.exists(['system', 'name-servers-dhcp']) ):
-        no_system_nameservers = True
-        print(("DNS forwarding warning: No 'system name-server' or "
-                "'system name-servers-dhcp' set\n"))
-
-    if (no_system_nameservers or not dns['system']) and not (
-            dns['name_servers'] or dns['dhcp_interfaces']):
-        print(("DNS forwarding warning: No 'dhcp', 'name-server' or 'system' "
-            "nameservers set. Forwarding will operate as a recursor.\n"))
+    if 'listen_address' not in dns:
+        raise ConfigError('DNS forwarding requires a listen-address')
+
+    if 'allow_from' not in dns:
+        raise ConfigError('DNS forwarding requires an allow-from network')
+
+    # we can not use vyos_dict_search() when testing for domain servers
+    # as a domain will contains dot's which is out dictionary delimiter.
+    if 'domain' in dns:
+        for domain in dns['domain']:
+            if 'server' not in dns['domain'][domain]:
+                raise ConfigError(f'No server configured for domain {domain}!')
+
+    if 'system' in dns:
+        if not ('system_name_server' in dns or 'system_name_server_dhcp' in dns):
+            print("Warning: No 'system name-server' or 'system " \
+                  "name-servers-dhcp' configured")
 
     return None
 
 def generate(dns):
     # bail out early - looks like removal from running config
-    if dns is None:
+    if not dns:
         return None
 
     render(pdns_rec_config_file, 'dns-forwarding/recursor.conf.tmpl',
@@ -154,17 +104,18 @@ def generate(dns):
             dns, trim_blocks=True, user=pdns_rec_user, group=pdns_rec_group)
 
     # if vyos-hostsd didn't create its files yet, create them (empty)
-    for f in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]:
-        with open(f, 'a'):
+    for file in [pdns_rec_hostsd_lua_conf_file, pdns_rec_hostsd_zones_file]:
+        with open(file, 'a'):
             pass
-        chown(f, user=pdns_rec_user, group=pdns_rec_group)
+        chown(file, user=pdns_rec_user, group=pdns_rec_group)
 
     return None
 
 def apply(dns):
-    if dns is None:
+    if not dns:
         # DNS forwarding is removed in the commit
-        call("systemctl stop pdns-recursor.service")
+        call('systemctl stop pdns-recursor.service')
+
         if os.path.isfile(pdns_rec_config_file):
             os.unlink(pdns_rec_config_file)
     else:
@@ -174,8 +125,8 @@ def apply(dns):
         # add static nameservers to hostsd so they can be joined with other
         # sources
         hc.delete_name_servers([hostsd_tag])
-        if dns['name_servers']:
-            hc.add_name_servers({hostsd_tag: dns['name_servers']})
+        if 'name_server' in dns:
+            hc.add_name_servers({hostsd_tag: dns['name_server']})
 
         # delete all nameserver tags
         hc.delete_name_server_tags_recursor(hc.get_name_server_tags_recursor())
@@ -184,32 +135,33 @@ def apply(dns):
         # our own tag (static)
         hc.add_name_server_tags_recursor([hostsd_tag])
 
-        if dns['system']:
+        if 'system' in dns:
             hc.add_name_server_tags_recursor(['system'])
         else:
             hc.delete_name_server_tags_recursor(['system'])
 
         # add dhcp nameserver tags for configured interfaces
-        for intf in dns['dhcp_interfaces']:
-            hc.add_name_server_tags_recursor(['dhcp-' + intf, 'dhcpv6-' + intf ])
+        if 'system_name_server_dhcp' in dns:
+            for interface in dns['system_name_server_dhcp']:
+                hc.add_name_server_tags_recursor(['dhcp-' + interface,
+                                                  'dhcpv6-' + interface ])
 
         # hostsd will generate the forward-zones file
         # the list and keys() are required as get returns a dict, not list
         hc.delete_forward_zones(list(hc.get_forward_zones().keys()))
-        if dns['domains']:
-            hc.add_forward_zones(dns['domains'])
+        if 'domain' in dns:
+            hc.add_forward_zones(dns['domain'])
 
         # call hostsd to generate forward-zones and its lua-config-file
         hc.apply()
 
         ### finally (re)start pdns-recursor
-        call("systemctl restart pdns-recursor.service")
+        call('systemctl restart pdns-recursor.service')
 
 if __name__ == '__main__':
     try:
-        conf = Config()
-        c = get_config(conf)
-        verify(conf, c)
+        c = get_config()
+        verify(c)
         generate(c)
         apply(c)
     except ConfigError as e:
diff --git a/src/services/vyos-hostsd b/src/services/vyos-hostsd
index 0079f7e5c..59dbeda17 100755
--- a/src/services/vyos-hostsd
+++ b/src/services/vyos-hostsd
@@ -107,16 +107,17 @@
 #
 ### forward_zones
 ## Additional zones added to pdns-recursor forward-zones-file.
-## If recursion-desired is true, '+' will be prepended to the zone line.
-## If addNTA is true, a NTA will be added via lua-config-file.
+## If recursion_desired is true, '+' will be prepended to the zone line.
+## If addnta is true, a NTA (Negative Trust Anchor) will be added via
+## lua-config-file.
 #
 # { 'type': 'forward_zones',
 #   'op': 'add',
 #   'data': {
 #       '<str zone>': {
-#           'nslist': ['<str nameserver>', ...],
-#           'addNTA': <bool>,
-#           'recursion-desired': <bool>
+#           'server': ['<str nameserver>', ...],
+#           'addnta': <bool>,
+#           'recursion_desired': <bool>
 #         }
 #       ...
 #     }
@@ -305,12 +306,12 @@ tag_regex_schema = op_type_schema.extend({
 forward_zone_add_schema = op_type_schema.extend({
     'data': {
         str: {
-            'nslist': [str],
-            'addNTA': bool,
-            'recursion-desired': bool
+            'server': [str],
+            'addnta': Any({}, None),
+            'recursion_desired': Any({}, None),
             }
         }
-    }, required=True)
+    }, required=False)
 
 hosts_add_schema = op_type_schema.extend({
     'data': {
@@ -586,7 +587,7 @@ if __name__ == '__main__':
 
     context = zmq.Context()
     socket = context.socket(zmq.REP)
-    
+
     # Set the right permissions on the socket, then change it back
     o_mask = os.umask(0o007)
     socket.bind(SOCKET_PATH)
diff --git a/src/utils/vyos-hostsd-client b/src/utils/vyos-hostsd-client
index 48ebc83f7..d4d38315a 100755
--- a/src/utils/vyos-hostsd-client
+++ b/src/utils/vyos-hostsd-client
@@ -99,9 +99,9 @@ try:
             raise ValueError("--nameservers is required for this operation")
         client.add_forward_zones(
                 { args.add_forward_zone: {
-                    'nslist': args.nameservers,
-                    'addNTA': args.addnta,
-                    'recursion-desired': args.recursion_desired
+                    'server': args.nameservers,
+                    'addnta': args.addnta,
+                    'recursion_desired': args.recursion_desired
                     }
                 })
     elif args.delete_forward_zones:
-- 
cgit v1.2.3


From dfa949c5b758e2954ed5c6ad455fe586965cd156 Mon Sep 17 00:00:00 2001
From: Christian Poessinger <christian@poessinger.com>
Date: Sat, 26 Sep 2020 10:06:27 +0200
Subject: smoketest: platform: check for required virtio/vmxnet drivers

---
 smoketest/scripts/system/test_kernel_options.py | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

(limited to 'smoketest')

diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
index 6316521f6..043567c4f 100755
--- a/smoketest/scripts/system/test_kernel_options.py
+++ b/smoketest/scripts/system/test_kernel_options.py
@@ -42,6 +42,22 @@ class TestKernelModules(unittest.TestCase):
             tmp = re.findall(f'{option}=(y|m)', config)
             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',
+                       'CONFIG_VIRTIO_NET', 'CONFIG_VIRTIO_CONSOLE',
+                       'CONFIG_VIRTIO', 'CONFIG_VIRTIO_PCI',
+                       'CONFIG_VIRTIO_BALLOON', 'CONFIG_CRYPTO_DEV_VIRTIO',
+                       'CONFIG_X86_PLATFORM_DEVICES']:
+            tmp = re.findall(f'{option}=(y|m)', config)
+            self.assertTrue(tmp)
+
+    def test_vmware_support(self):
+        """ The bond/lacp interface must be enabled in the OS Kernel """
+        for option in ['CONFIG_VMXNET3']:
+            tmp = re.findall(f'{option}=(y|m)', config)
+            self.assertTrue(tmp)
+
 if __name__ == '__main__':
     unittest.main()
 
-- 
cgit v1.2.3