]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
test-network: various cleanups 23969/head
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sat, 9 Jul 2022 11:05:23 +0000 (20:05 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 11 Jul 2022 17:57:24 +0000 (19:57 +0200)
- introduce several helper functions
- do not list unit files, but remove the runtime unit directory in
  tearDown().
- do not list used interfaces, but remove all interfaces previously not
  exists in tearDown().
- save routes and routing policy rules before running tests, and flush
  unnecessary routes and rules in each tearDown() calls.
- drop many time.sleep() calls.
- call tearDown() after each sub tests.
- shorten code.
- several coding style fixes.
- etc, etc...

Hopefully, this improves performance of the test.

test/test-network/systemd-networkd-tests.py

index 344b5be1f9df129adcecced3bba6bcc4e3e16078..1e78b966aa235c9bf9c5b1aa79851669385e337e 100755 (executable)
@@ -12,6 +12,7 @@ import argparse
 import errno
 import itertools
 import os
+import pathlib
 import re
 import shutil
 import signal
@@ -19,299 +20,589 @@ import subprocess
 import sys
 import time
 import unittest
-from shutil import copytree
-from pathlib import Path
-
-network_unit_file_path='/run/systemd/network'
-networkd_runtime_directory='/run/systemd/netif'
-networkd_conf_dropin_path='/run/systemd/networkd.conf.d'
-networkd_ci_path='/run/networkd-ci'
-network_sysctl_ipv6_path='/proc/sys/net/ipv6/conf'
-network_sysctl_ipv4_path='/proc/sys/net/ipv4/conf'
-
-udev_rules_dir='/run/udev/rules.d'
-
-dnsmasq_pid_file='/run/networkd-ci/test-dnsmasq.pid'
-dnsmasq_log_file='/run/networkd-ci/test-dnsmasq.log'
-dnsmasq_lease_file='/run/networkd-ci/test-dnsmasq.lease'
-
-isc_dhcpd_pid_file='/run/networkd-ci/test-isc-dhcpd.pid'
-isc_dhcpd_lease_file='/run/networkd-ci/test-isc-dhcpd.lease'
-
-systemd_lib_paths=['/usr/lib/systemd', '/lib/systemd']
-which_paths=':'.join(systemd_lib_paths + os.getenv('PATH', os.defpath).lstrip(':').split(':'))
-
-networkd_bin=shutil.which('systemd-networkd', path=which_paths)
-resolved_bin=shutil.which('systemd-resolved', path=which_paths)
-udevd_bin=shutil.which('systemd-udevd', path=which_paths)
-wait_online_bin=shutil.which('systemd-networkd-wait-online', path=which_paths)
-networkctl_bin=shutil.which('networkctl', path=which_paths)
-resolvectl_bin=shutil.which('resolvectl', path=which_paths)
-timedatectl_bin=shutil.which('timedatectl', path=which_paths)
-
-use_valgrind=False
-enable_debug=True
+
+network_unit_dir = '/run/systemd/network'
+networkd_conf_dropin_dir = '/run/systemd/networkd.conf.d'
+networkd_ci_temp_dir = '/run/networkd-ci'
+udev_rules_dir = '/run/udev/rules.d'
+
+dnsmasq_pid_file = '/run/networkd-ci/test-dnsmasq.pid'
+dnsmasq_log_file = '/run/networkd-ci/test-dnsmasq.log'
+dnsmasq_lease_file = '/run/networkd-ci/test-dnsmasq.lease'
+
+isc_dhcpd_pid_file = '/run/networkd-ci/test-isc-dhcpd.pid'
+isc_dhcpd_lease_file = '/run/networkd-ci/test-isc-dhcpd.lease'
+
+systemd_lib_paths = ['/usr/lib/systemd', '/lib/systemd']
+which_paths = ':'.join(systemd_lib_paths + os.getenv('PATH', os.defpath).lstrip(':').split(':'))
+
+networkd_bin = shutil.which('systemd-networkd', path=which_paths)
+resolved_bin = shutil.which('systemd-resolved', path=which_paths)
+udevd_bin = shutil.which('systemd-udevd', path=which_paths)
+wait_online_bin = shutil.which('systemd-networkd-wait-online', path=which_paths)
+networkctl_bin = shutil.which('networkctl', path=which_paths)
+resolvectl_bin = shutil.which('resolvectl', path=which_paths)
+timedatectl_bin = shutil.which('timedatectl', path=which_paths)
+
+use_valgrind = False
+enable_debug = True
 env = {}
 wait_online_env = {}
-asan_options=None
-lsan_options=None
-ubsan_options=None
-with_coverage=False
-
-running_units = []
+asan_options = None
+lsan_options = None
+ubsan_options = None
+with_coverage = False
+
+active_units = []
+protected_links = {
+    'erspan0',
+    'gre0',
+    'gretap0',
+    'ifb0',
+    'ifb1',
+    'ip6_vti0',
+    'ip6gre0',
+    'ip6tnl0',
+    'ip_vti0',
+    'lo',
+    'sit0',
+    'tunl0',
+}
+saved_routes = None
+saved_ipv4_rules = None
+saved_ipv6_rules = None
+
+def rm_f(path):
+    if os.path.exists(path):
+        os.remove(path)
+
+def rm_rf(path):
+    shutil.rmtree(path, ignore_errors=True)
+
+def cp(src, dst):
+    shutil.copy(src, dst)
+
+def cp_r(src, dst):
+    shutil.copytree(src, dst, copy_function=shutil.copy)
+
+def mkdir_p(path):
+    os.makedirs(path, exist_ok=True)
+
+def touch(path):
+    pathlib.Path(path).touch()
+
+def check_output(*command, text=True, **kwargs):
+    # This checks the result and returns stdout (and stderr) on success.
+    command = command[0].split() + list(command[1:])
+    return subprocess.run(command, check=True, universal_newlines=text, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs).stdout.rstrip()
 
-def check_output(*command, **kwargs):
-    # This replaces both check_output and check_call (output can be ignored)
+def call(*command, text=True, **kwargs):
+    # This returns returncode. stdout and stderr are shown in console
     command = command[0].split() + list(command[1:])
-    return subprocess.check_output(command, universal_newlines=True, **kwargs).rstrip()
+    return subprocess.run(command, check=False, universal_newlines=text, **kwargs).returncode
 
-def call(*command, **kwargs):
+def call_quiet(*command, text=True, **kwargs):
     command = command[0].split() + list(command[1:])
-    return subprocess.call(command, universal_newlines=True, **kwargs)
+    return subprocess.run(command, check=False, universal_newlines=text, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs).returncode
 
-def run(*command, **kwargs):
+def run(*command, text=True, **kwargs):
+    # This returns CompletedProcess instance.
     command = command[0].split() + list(command[1:])
-    return subprocess.run(command, universal_newlines=True, check=False, **kwargs)
+    return subprocess.run(command, check=False, universal_newlines=text, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
 
 def is_module_available(module_name):
     lsmod_output = check_output('lsmod')
     module_re = re.compile(rf'^{re.escape(module_name)}\b', re.MULTILINE)
-    return module_re.search(lsmod_output) or not call('modprobe', module_name, stderr=subprocess.DEVNULL)
+    return module_re.search(lsmod_output) or call_quiet('modprobe', module_name) == 0
 
 def expectedFailureIfModuleIsNotAvailable(module_name):
     def f(func):
-        if not is_module_available(module_name):
-            return unittest.expectedFailure(func)
-        return func
+        return func if is_module_available(module_name) else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfERSPANv0IsNotSupported():
     # erspan version 0 is supported since f989d546a2d5a9f001f6f8be49d98c10ab9b1897 (v5.8)
     def f(func):
-        rc = call('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 0', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            call('ip link del erspan99')
-            return func
-
-        return unittest.expectedFailure(func)
+        rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 0')
+        remove_link('erspan99')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfERSPANv2IsNotSupported():
     # erspan version 2 is supported since f551c91de262ba36b20c3ac19538afb4f4507441 (v4.16)
     def f(func):
-        rc = call('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 2', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            call('ip link del erspan99')
-            return func
-
-        return unittest.expectedFailure(func)
+        rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 2')
+        remove_link('erspan99')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfRoutingPolicyPortRangeIsNotAvailable():
     def f(func):
-        rc = call('ip rule add from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            call('ip rule del from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7')
-            return func
-
-        return unittest.expectedFailure(func)
+        rc = call_quiet('ip rule add from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7')
+        call_quiet('ip rule del from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable():
     def f(func):
-        rc = call('ip rule add not from 192.168.100.19 ipproto tcp table 7', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            call('ip rule del not from 192.168.100.19 ipproto tcp table 7')
-            return func
-
-        return unittest.expectedFailure(func)
+        rc = call_quiet('ip rule add not from 192.168.100.19 ipproto tcp table 7')
+        call_quiet('ip rule del not from 192.168.100.19 ipproto tcp table 7')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable():
     def f(func):
-        support = False
-        rc = call('ip rule add from 192.168.100.19 table 7 uidrange 200-300', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            ret = run('ip rule list from 192.168.100.19 table 7', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-            if ret.returncode == 0 and 'uidrange 200-300' in ret.stdout.rstrip():
-                support = True
-            call('ip rule del from 192.168.100.19 table 7 uidrange 200-300')
-
-        if support:
-            return func
-
-        return unittest.expectedFailure(func)
+        supported = False
+        if call_quiet('ip rule add from 192.168.100.19 table 7 uidrange 200-300') == 0:
+            ret = run('ip rule list from 192.168.100.19 table 7')
+            supported = ret.returncode == 0 and 'uidrange 200-300' in ret.stdout
+            call_quiet('ip rule del from 192.168.100.19 table 7 uidrange 200-300')
+        return func if supported else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfLinkFileFieldIsNotSet():
     def f(func):
-        support = False
-        rc = call('ip link add name dummy99 type dummy', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            ret = run('udevadm info -w10s /sys/class/net/dummy99', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-            if ret.returncode == 0 and 'E: ID_NET_LINK_FILE=' in ret.stdout.rstrip():
-                support = True
-            call('ip link del dummy99')
-
-        if support:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add name dummy99 type dummy')
+        ret = run('udevadm info -w10s /sys/class/net/dummy99')
+        supported = ret.returncode == 0 and 'E: ID_NET_LINK_FILE=' in ret.stdout
+        remove_link('dummy99')
+        return func if supported else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfNexthopIsNotAvailable():
     def f(func):
-        rc = call('ip nexthop list', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            return func
-
-        return unittest.expectedFailure(func)
+        rc = call_quiet('ip nexthop list')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfRTA_VIAIsNotSupported():
     def f(func):
-        call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL)
-        call('ip link set up dev dummy98', stderr=subprocess.DEVNULL)
-        call('ip route add 2001:1234:5:8fff:ff:ff:ff:fe/128 dev dummy98', stderr=subprocess.DEVNULL)
-        rc = call('ip route add 10.10.10.10 via inet6 2001:1234:5:8fff:ff:ff:ff:fe dev dummy98', stderr=subprocess.DEVNULL)
-        call('ip link del dummy98', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add dummy98 type dummy')
+        call_quiet('ip link set up dev dummy98')
+        call_quiet('ip route add 2001:1234:5:8fff:ff:ff:ff:fe/128 dev dummy98')
+        rc = call_quiet('ip route add 10.10.10.10 via inet6 2001:1234:5:8fff:ff:ff:ff:fe dev dummy98')
+        remove_link('dummy98')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfAlternativeNameIsNotAvailable():
     def f(func):
-        supported = False
-        call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL)
-        rc = call('ip link prop add dev dummy98 altname hogehogehogehogehoge', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            rc = call('ip link show dev hogehogehogehogehoge', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-            if rc == 0:
-                supported = True
-
-        call('ip link del dummy98', stderr=subprocess.DEVNULL)
-        if supported:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add dummy98 type dummy')
+        supported = \
+            call_quiet('ip link prop add dev dummy98 altname hogehogehogehogehoge') == 0 and \
+            call_quiet('ip link show dev hogehogehogehogehoge') == 0
+        remove_link('dummy98')
+        return func if supported else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfNetdevsimWithSRIOVIsNotAvailable():
     def f(func):
-        call('rmmod netdevsim', stderr=subprocess.DEVNULL)
-        rc = call('modprobe netdevsim', stderr=subprocess.DEVNULL)
-        if rc != 0:
-            return unittest.expectedFailure(func)
+        def finalize(func, supported):
+            call_quiet('rmmod netdevsim')
+            return func if supported else unittest.expectedFailure(func)
+
+        call_quiet('rmmod netdevsim')
+        if call_quiet('modprobe netdevsim') != 0:
+            return finalize(func, False)
 
         try:
             with open('/sys/bus/netdevsim/new_device', mode='w', encoding='utf-8') as f:
                 f.write('99 1')
         except OSError:
-            return unittest.expectedFailure(func)
+            return finalize(func, False)
 
-        call('udevadm settle')
-        call('udevadm info -w10s /sys/devices/netdevsim99/net/eni99np1', stderr=subprocess.DEVNULL)
-        try:
-            with open('/sys/class/net/eni99np1/device/sriov_numvfs', mode='w', encoding='utf-8') as f:
-                f.write('3')
-        except OSError:
-            call('rmmod netdevsim', stderr=subprocess.DEVNULL)
-            return unittest.expectedFailure(func)
+        if not os.path.exists('/sys/bus/netdevsim/devices/netdevsim99/sriov_numvfs'):
+            return finalize(func, False)
 
-        call('rmmod netdevsim', stderr=subprocess.DEVNULL)
-        return func
+        # Also checks if udevd supports .link files, as it seems disabled on CentOS CI (Arch).
+        rc = call_quiet('udevadm info -w10s /sys/class/net/eni99np1')
+        return finalize(func, rc == 0)
 
     return f
 
 def expectedFailureIfCAKEIsNotAvailable():
     def f(func):
-        call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL)
-        rc = call('tc qdisc add dev dummy98 parent root cake', stderr=subprocess.DEVNULL)
-        call('ip link del dummy98', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add dummy98 type dummy')
+        rc = call_quiet('tc qdisc add dev dummy98 parent root cake')
+        remove_link('dummy98')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfPIEIsNotAvailable():
     def f(func):
-        call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL)
-        rc = call('tc qdisc add dev dummy98 parent root pie', stderr=subprocess.DEVNULL)
-        call('ip link del dummy98', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add dummy98 type dummy')
+        rc = call_quiet('tc qdisc add dev dummy98 parent root pie')
+        remove_link('dummy98')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfHHFIsNotAvailable():
     def f(func):
-        call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL)
-        rc = call('tc qdisc add dev dummy98 parent root hhf', stderr=subprocess.DEVNULL)
-        call('ip link del dummy98', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add dummy98 type dummy')
+        rc = call_quiet('tc qdisc add dev dummy98 parent root hhf')
+        remove_link('dummy98')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfETSIsNotAvailable():
     def f(func):
-        call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL)
-        rc = call('tc qdisc add dev dummy98 parent root ets bands 10', stderr=subprocess.DEVNULL)
-        call('ip link del dummy98', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add dummy98 type dummy')
+        rc = call_quiet('tc qdisc add dev dummy98 parent root ets bands 10')
+        remove_link('dummy98')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
 def expectedFailureIfFQPIEIsNotAvailable():
     def f(func):
-        call('ip link add dummy98 type dummy', stderr=subprocess.DEVNULL)
-        rc = call('tc qdisc add dev dummy98 parent root fq_pie', stderr=subprocess.DEVNULL)
-        call('ip link del dummy98', stderr=subprocess.DEVNULL)
-        if rc == 0:
-            return func
-
-        return unittest.expectedFailure(func)
+        call_quiet('ip link add dummy98 type dummy')
+        rc = call_quiet('tc qdisc add dev dummy98 parent root fq_pie')
+        remove_link('dummy98')
+        return func if rc == 0 else unittest.expectedFailure(func)
 
     return f
 
-def setUpModule():
-    os.makedirs(network_unit_file_path, exist_ok=True)
-    os.makedirs(networkd_conf_dropin_path, exist_ok=True)
-    os.makedirs(networkd_ci_path, exist_ok=True)
-    os.makedirs(udev_rules_dir, exist_ok=True)
+def copy_network_unit(*units, copy_dropins=True):
+    """
+    Copy networkd unit files into the testbed.
 
-    shutil.rmtree(networkd_ci_path)
-    copytree(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf'), networkd_ci_path)
+    Any networkd unit file type can be specified, as well as drop-in files.
 
-    shutil.copy(os.path.join(networkd_ci_path, '00-debug-net.rules'), udev_rules_dir)
+    By default, all drop-ins for a specified unit file are copied in;
+    to avoid that specify dropins=False.
 
-    for u in ['systemd-networkd.socket', 'systemd-networkd.service', 'systemd-resolved.service',
-              'systemd-udevd-kernel.socket', 'systemd-udevd-control.socket', 'systemd-udevd.service',
+    When a drop-in file is specified, its unit file is also copied in automatically.
+    """
+    mkdir_p(network_unit_dir)
+    for unit in units:
+        if copy_dropins and os.path.exists(os.path.join(networkd_ci_temp_dir, unit + '.d')):
+            cp_r(os.path.join(networkd_ci_temp_dir, unit + '.d'), os.path.join(network_unit_dir, unit + '.d'))
+        if unit.endswith('.conf'):
+            dropin = unit
+            unit = os.path.dirname(dropin).rstrip('.d')
+            dropindir = os.path.join(network_unit_dir, unit + '.d')
+            mkdir_p(dropindir)
+            cp(os.path.join(networkd_ci_temp_dir, dropin), dropindir)
+        cp(os.path.join(networkd_ci_temp_dir, unit), network_unit_dir)
+
+def remove_network_unit(*units):
+    """
+    Remove previously copied unit files from the testbed.
+
+    Drop-ins will be removed automatically.
+    """
+    for unit in units:
+        rm_f(os.path.join(network_unit_dir, unit))
+        rm_rf(os.path.join(network_unit_dir, unit + '.d'))
+
+def clear_network_units():
+    rm_rf(network_unit_dir)
+
+def copy_networkd_conf_dropin(*dropins):
+    """Copy networkd.conf dropin files into the testbed."""
+    mkdir_p(networkd_conf_dropin_dir)
+    for dropin in dropins:
+        cp(os.path.join(networkd_ci_temp_dir, dropin), networkd_conf_dropin_dir)
+
+def remove_networkd_conf_dropin(*dropins):
+    """Remove previously copied networkd.conf dropin files from the testbed."""
+    for dropin in dropins:
+        rm_f(os.path.join(networkd_conf_dropin_dir, dropin))
+
+def clear_networkd_conf_dropins():
+    rm_rf(networkd_conf_dropin_dir)
+
+def copy_udev_rule(*rules):
+    """Copy udev rules"""
+    mkdir_p(udev_rules_dir)
+    for rule in rules:
+        cp(os.path.join(networkd_ci_temp_dir, rule), udev_rules_dir)
+
+def remove_udev_rule(*rules):
+    """Remove previously copied udev rules"""
+    for rule in rules:
+        rm_f(os.path.join(udev_rules_dir, rule))
+
+def clear_udev_rules():
+    rm_rf(udev_rules_dir)
+
+def save_active_units():
+    for u in ['systemd-udevd-kernel.socket', 'systemd-udevd-control.socket', 'systemd-udevd.service',
+              'systemd-networkd.socket', 'systemd-networkd.service',
+              'systemd-resolved.service',
               'firewalld.service']:
         if call(f'systemctl is-active --quiet {u}') == 0:
-            check_output(f'systemctl stop {u}')
-            running_units.append(u)
+            call(f'systemctl stop {u}')
+            active_units.append(u)
+
+def restore_active_units():
+    if 'systemd-networkd.socket' in active_units:
+        call('systemctl stop systemd-networkd.socket systemd-networkd.service')
+    if 'systemd-udevd-kernel.socket' in active_units or 'systemd-udevd-control.socket' in active_units:
+        call('systemctl stop systemd-udevd-kernel.socket systemd-udevd-control.socket systemd-udevd.service')
+    for u in active_units:
+        call(f'systemctl restart {u}')
+
+def link_exists(link):
+    return os.path.exists(os.path.join('/sys/class/net', link, 'ifindex'))
+
+def remove_link(*links, protect=False):
+    for link in links:
+        if protect and link in protected_links:
+            continue
+        if link_exists(link):
+            call(f'ip link del dev {link}')
+
+def save_existing_links():
+    links = os.listdir('/sys/class/net')
+    for link in links:
+        if link_exists(link):
+            protected_links.add(link)
+
+    print('### The following links will be protected:')
+    print(', '.join(sorted(list(protected_links))))
+
+def flush_links():
+    links = os.listdir('/sys/class/net')
+    remove_link(*links, protect=True)
+
+def flush_nexthops():
+    # Currently, the 'ip nexthop' command does not have 'save' and 'restore'.
+    # Hence, we cannot restore nexthops in a simple way.
+    # Let's assume there is no nexthop used in the system
+    call_quiet('ip nexthop flush')
+
+def save_routes():
+    # pylint: disable=global-statement
+    global saved_routes
+    saved_routes = check_output('ip route show table all')
+    print('### The following routes will be protected:')
+    print(saved_routes)
+
+def flush_routes():
+    have = False
+    output = check_output('ip route show table all')
+    for line in output.splitlines():
+        if line in saved_routes:
+            continue
+        if 'proto kernel' in line:
+            continue
+        if ' dev ' in line and not ' dev lo ' in line:
+            continue
+        if not have:
+            have = True
+            print('### Removing routes that did not exist when the test started.')
+        print(f'# {line}')
+        call(f'ip route del {line}')
+
+def save_routing_policy_rules():
+    # pylint: disable=global-statement
+    global saved_ipv4_rules, saved_ipv6_rules
+    def save(ipv):
+        output = check_output(f'ip -{ipv} rule show')
+        print(f'### The following IPv{ipv} routing policy rules will be protected:')
+        print(output)
+        return output
+
+    saved_ipv4_rules = save(4)
+    saved_ipv6_rules = save(6)
+
+def flush_routing_policy_rules():
+    def flush(ipv, saved_rules):
+        have = False
+        output = check_output(f'ip -{ipv} rule show')
+        for line in output.splitlines():
+            if line in saved_rules:
+                continue
+            if not have:
+                have = True
+                print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.')
+            print(f'# {line}')
+            words = line.split()
+            priority = words[0].rstrip(':')
+            call(f'ip -{ipv} rule del priority {priority} ' + ' '.join(words[1:]))
+
+    flush(4, saved_ipv4_rules)
+    flush(6, saved_ipv6_rules)
+
+def flush_fou_ports():
+    ret = run('ip fou show')
+    if ret.returncode != 0:
+        return # fou may not be supported
+    for line in ret.stdout.splitlines():
+        port = line.split()[1]
+        call(f'ip fou del port {port}')
+
+def flush_l2tp_tunnels():
+    output = check_output('ip l2tp show tunnel')
+    for line in output.splitlines():
+        words = line.split()
+        if words[0] == 'Tunnel':
+            tid = words[1].rstrip(',')
+            call(f'ip l2tp del tunnel tunnel_id {tid}')
+
+def read_link_attr(*args):
+    with open(os.path.join('/sys/class/net', *args), encoding='utf-8') as f:
+        return f.readline().strip()
+
+def read_link_state_file(link):
+    ifindex = read_link_attr(link, 'ifindex')
+    path = os.path.join('/run/systemd/netif/links', ifindex)
+    with open(path, encoding='utf-8') as f:
+        return f.read()
+
+def read_ip_sysctl_attr(link, attribute, ipv):
+    with open(os.path.join('/proc/sys/net', ipv, 'conf', link, attribute), encoding='utf-8') as f:
+        return f.readline().strip()
+
+def read_ipv6_sysctl_attr(link, attribute):
+    return read_ip_sysctl_attr(link, attribute, 'ipv6')
+
+def read_ipv4_sysctl_attr(link, attribute):
+    return read_ip_sysctl_attr(link, attribute, 'ipv4')
+
+def start_dnsmasq(*additional_options, interface='veth-peer', lease_time='2m', ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'):
+    command = (
+        'dnsmasq',
+        f'--log-facility={dnsmasq_log_file}',
+        '--log-queries=extra',
+        '--log-dhcp',
+        f'--pid-file={dnsmasq_pid_file}',
+        '--conf-file=/dev/null',
+        '--bind-interfaces',
+        f'--interface={interface}',
+        f'--dhcp-leasefile={dnsmasq_lease_file}',
+        '--enable-ra',
+        f'--dhcp-range={ipv6_range},{lease_time}',
+        f'--dhcp-range={ipv4_range},{lease_time}',
+        '--dhcp-option=option:mtu,1492',
+        f'--dhcp-option=option:router,{ipv4_router}',
+        '--port=0',
+        '--no-resolv',
+    ) + additional_options
+    check_output(*command)
+
+def stop_by_pid_file(pid_file):
+    if not os.path.exists(pid_file):
+        return
+    with open(pid_file, 'r', encoding='utf-8') as f:
+        pid = f.read().rstrip(' \t\r\n\0')
+        os.kill(int(pid), signal.SIGTERM)
+        for _ in range(25):
+            try:
+                os.kill(int(pid), 0)
+                print(f"PID {pid} is still alive, waiting...")
+                time.sleep(.2)
+            except OSError as e:
+                if e.errno == errno.ESRCH:
+                    break
+                print(f"Unexpected exception when waiting for {pid} to die: {e.errno}")
+    os.remove(pid_file)
+
+def stop_dnsmasq():
+    stop_by_pid_file(dnsmasq_pid_file)
+    rm_f(dnsmasq_lease_file)
+    rm_f(dnsmasq_log_file)
+
+def read_dnsmasq_log_file():
+    with open(dnsmasq_log_file, encoding='utf-8') as f:
+        return f.read()
+
+def start_isc_dhcpd(conf_file, ipv, interface='veth-peer'):
+    conf_file_path = os.path.join(networkd_ci_temp_dir, conf_file)
+    isc_dhcpd_command = f'dhcpd {ipv} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}'
+    touch(isc_dhcpd_lease_file)
+    check_output(isc_dhcpd_command)
+
+def stop_isc_dhcpd():
+    stop_by_pid_file(isc_dhcpd_pid_file)
+    rm_f(isc_dhcpd_lease_file)
+
+def stop_networkd(show_logs=True):
+    if show_logs:
+        invocation_id = check_output('systemctl show systemd-networkd.service -p InvocationID --value')
+    check_output('systemctl stop systemd-networkd.socket')
+    check_output('systemctl stop systemd-networkd.service')
+    if show_logs:
+        print(check_output('journalctl _SYSTEMD_INVOCATION_ID=' + invocation_id))
+
+def start_networkd():
+    check_output('systemctl start systemd-networkd')
+
+def restart_networkd(show_logs=True):
+    stop_networkd(show_logs)
+    start_networkd()
+
+def networkctl_reconfigure(*links):
+    check_output(*networkctl_cmd, 'reconfigure', *links, env=env)
+
+def networkctl_reload(sleep_time=1):
+    check_output(*networkctl_cmd, 'reload', env=env)
+    # 'networkctl reload' asynchronously reconfigure links.
+    # Hence, we need to wait for a short time for link to be in configuring state.
+    if sleep_time > 0:
+        time.sleep(sleep_time)
+
+def setup_common():
+    print()
+
+def tear_down_common():
+    # 1. stop DHCP servers
+    stop_dnsmasq()
+    stop_isc_dhcpd()
+
+    # 2. remove modules
+    call_quiet('rmmod netdevsim')
+    call_quiet('rmmod sch_teql')
+
+    # 3. remove network namespace
+    call_quiet('ip netns del ns99')
+
+    # 4. remove links
+    flush_links()
+
+    # 5. stop networkd
+    stop_networkd(show_logs=True)
+
+    # 6. remove configs
+    clear_network_units()
+    clear_networkd_conf_dropins()
+
+    # 7. flush settings
+    flush_fou_ports()
+    flush_l2tp_tunnels()
+    flush_nexthops()
+    flush_routing_policy_rules()
+    flush_routes()
+
+def setUpModule():
+    rm_rf(networkd_ci_temp_dir)
+    cp_r(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf'), networkd_ci_temp_dir)
+
+    clear_network_units()
+    clear_networkd_conf_dropins()
+    clear_udev_rules()
+
+    copy_udev_rule('00-debug-net.rules')
+
+    # Save current state
+    save_active_units()
+    save_existing_links()
+    save_routes()
+    save_routing_policy_rules()
 
     drop_in = [
         '[Unit]',
@@ -329,8 +620,8 @@ def setUpModule():
         ]
     else:
         drop_in += [
-                'ExecStart=!!' + networkd_bin,
-                f'ExecReload={networkctl_bin} reload',
+            'ExecStart=!!' + networkd_bin,
+            f'ExecReload={networkctl_bin} reload',
         ]
     if enable_debug:
         drop_in += ['Environment=SYSTEMD_LOG_LEVEL=debug']
@@ -350,7 +641,7 @@ def setUpModule():
             'ProtectHome=no',
         ]
 
-    os.makedirs('/run/systemd/system/systemd-networkd.service.d', exist_ok=True)
+    mkdir_p('/run/systemd/system/systemd-networkd.service.d')
     with open('/run/systemd/system/systemd-networkd.service.d/00-override.conf', mode='w', encoding='utf-8') as f:
         f.write('\n'.join(drop_in))
 
@@ -381,7 +672,7 @@ def setUpModule():
             'ProtectHome=no',
         ]
 
-    os.makedirs('/run/systemd/system/systemd-resolved.service.d', exist_ok=True)
+    mkdir_p('/run/systemd/system/systemd-resolved.service.d')
     with open('/run/systemd/system/systemd-resolved.service.d/00-override.conf', mode='w', encoding='utf-8') as f:
         f.write('\n'.join(drop_in))
 
@@ -391,7 +682,7 @@ def setUpModule():
         'ExecStart=!!' + udevd_bin,
     ]
 
-    os.makedirs('/run/systemd/system/systemd-udevd.service.d', exist_ok=True)
+    mkdir_p('/run/systemd/system/systemd-udevd.service.d')
     with open('/run/systemd/system/systemd-udevd.service.d/00-override.conf', mode='w', encoding='utf-8') as f:
         f.write('\n'.join(drop_in))
 
@@ -399,225 +690,69 @@ def setUpModule():
     print(check_output('systemctl cat systemd-networkd.service'))
     print(check_output('systemctl cat systemd-resolved.service'))
     print(check_output('systemctl cat systemd-udevd.service'))
-    check_output('systemctl restart systemd-resolved')
-    check_output('systemctl restart systemd-udevd')
+    check_output('systemctl restart systemd-resolved.service')
+    check_output('systemctl restart systemd-udevd.service')
 
 def tearDownModule():
-    shutil.rmtree(networkd_ci_path)
-    os.remove(os.path.join(udev_rules_dir, '00-debug-net.rules'))
-
-    for u in ['systemd-networkd.socket', 'systemd-networkd.service', 'systemd-resolved.service']:
-        check_output(f'systemctl stop {u}')
-
-    shutil.rmtree('/run/systemd/system/systemd-networkd.service.d')
-    shutil.rmtree('/run/systemd/system/systemd-resolved.service.d')
-    shutil.rmtree('/run/systemd/system/systemd-udevd.service.d')
+    rm_rf(networkd_ci_temp_dir)
+    clear_udev_rules()
+    clear_network_units()
+    clear_networkd_conf_dropins()
+
+    rm_rf('/run/systemd/system/systemd-networkd.service.d')
+    rm_rf('/run/systemd/system/systemd-resolved.service.d')
+    rm_rf('/run/systemd/system/systemd-udevd.service.d')
     check_output('systemctl daemon-reload')
-    check_output('systemctl restart systemd-udevd.service')
-
-    for u in running_units:
-        check_output(f'systemctl start {u}')
-
-def read_link_attr(*args):
-    with open(os.path.join('/sys/class/net/', *args), encoding='utf-8') as f:
-        return f.readline().strip()
-
-def get_ifindex_by_name(link):
-    return read_link_attr(f'{link}/ifindex')
-
-def read_link_state_file(link):
-    ifindex = get_ifindex_by_name(link)
-    path = os.path.join('/run/systemd/netif/links/', ifindex)
-    with open(path, encoding='utf-8') as f:
-        return f.read()
-
-def read_bridge_port_attr(bridge, link, attribute):
-    path_bridge = os.path.join('/sys/devices/virtual/net', bridge)
-    path_port = 'lower_' + link + '/brport'
-    path = os.path.join(path_bridge, path_port)
-
-    with open(os.path.join(path, attribute), encoding='utf-8') as f:
-        return f.readline().strip()
-
-def link_exists(link):
-    return os.path.exists(os.path.join('/sys/class/net', link))
-
-def remove_links(links):
-    for link in links:
-        if link_exists(link):
-            call('ip link del dev', link)
-    time.sleep(1)
-
-def remove_fou_ports(ports):
-    for port in ports:
-        call('ip fou del port', port, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-
-def remove_routing_policy_rule_tables(tables):
-    for table in tables:
-        rc = 0
-        while rc == 0:
-            rc = call('ip rule del table', table, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-        rc = 0
-        while rc == 0:
-            rc = call('ip -6 rule del table', table, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-
-def remove_routes(routes):
-    for route_type, addr in routes:
-        call('ip route del', route_type, addr, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
-
-def remove_blackhole_nexthops():
-    ret = run('ip nexthop show dev lo', stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
-    if ret.returncode == 0:
-        for line in ret.stdout.rstrip().splitlines():
-            dev_id = line.split()[1]
-            call(f'ip nexthop del id {dev_id}')
-
-def remove_l2tp_tunnels(tunnel_ids):
-    output = check_output('ip l2tp show tunnel')
-    for tid in tunnel_ids:
-        words='Tunnel ' + tid + ', encap'
-        if words in output:
-            call('ip l2tp del tunnel tid', tid)
-    time.sleep(1)
-
-def read_ipv6_sysctl_attr(link, attribute):
-    with open(os.path.join(os.path.join(network_sysctl_ipv6_path, link), attribute), encoding='utf-8') as f:
-        return f.readline().strip()
-
-def read_ipv4_sysctl_attr(link, attribute):
-    with open(os.path.join(os.path.join(network_sysctl_ipv4_path, link), attribute), encoding='utf-8') as f:
-        return f.readline().strip()
+    restore_active_units()
 
-def copy_unit_to_networkd_unit_path(*units, dropins=True):
-    """Copy networkd unit files into the testbed.
-
-    Any networkd unit file type can be specified, as well as drop-in files.
-
-    By default, all drop-ins for a specified unit file are copied in;
-    to avoid that specify dropins=False.
-
-    When a drop-in file is specified, its unit file is also copied in automatically.
-    """
-    print()
-    for unit in units:
-        if dropins and os.path.exists(os.path.join(networkd_ci_path, unit + '.d')):
-            copytree(os.path.join(networkd_ci_path, unit + '.d'), os.path.join(network_unit_file_path, unit + '.d'))
-        if unit.endswith('.conf'):
-            dropin = unit
-            dropindir = os.path.join(network_unit_file_path, os.path.dirname(dropin))
-            os.makedirs(dropindir, exist_ok=True)
-            shutil.copy(os.path.join(networkd_ci_path, dropin), dropindir)
-            unit = os.path.dirname(dropin).rstrip('.d')
-        shutil.copy(os.path.join(networkd_ci_path, unit), network_unit_file_path)
-
-def remove_unit_from_networkd_path(units):
-    """Remove previously copied unit files from the testbed.
-
-    Drop-ins will be removed automatically.
-    """
-    for unit in units:
-        if os.path.exists(os.path.join(network_unit_file_path, unit)):
-            os.remove(os.path.join(network_unit_file_path, unit))
-            if os.path.exists(os.path.join(network_unit_file_path, unit + '.d')):
-                shutil.rmtree(os.path.join(network_unit_file_path, unit + '.d'))
-
-def copy_networkd_conf_dropin(*dropins):
-    """Copy networkd.conf dropin files into the testbed."""
-    for dropin in dropins:
-        shutil.copy(os.path.join(networkd_ci_path, dropin), networkd_conf_dropin_path)
-
-def remove_networkd_conf_dropin(dropins):
-    """Remove previously copied networkd.conf dropin files from the testbed."""
-    for dropin in dropins:
-        if os.path.exists(os.path.join(networkd_conf_dropin_path, dropin)):
-            os.remove(os.path.join(networkd_conf_dropin_path, dropin))
-
-def start_dnsmasq(additional_options='', interface='veth-peer', ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20', lease_time='1h'):
-    dnsmasq_command = f'dnsmasq -8 {dnsmasq_log_file} --log-queries=extra --log-dhcp --pid-file={dnsmasq_pid_file} --conf-file=/dev/null --bind-interfaces --interface={interface} --enable-ra --dhcp-range={ipv6_range},{lease_time} --dhcp-range={ipv4_range},{lease_time} -R --dhcp-leasefile={dnsmasq_lease_file} --dhcp-option=26,1492 --dhcp-option=option:router,{ipv4_router} --port=0 ' + additional_options
-    check_output(dnsmasq_command)
-
-def stop_by_pid_file(pid_file):
-    if os.path.exists(pid_file):
-        with open(pid_file, 'r', encoding='utf-8') as f:
-            pid = f.read().rstrip(' \t\r\n\0')
-            os.kill(int(pid), signal.SIGTERM)
-            for _ in range(25):
-                try:
-                    os.kill(int(pid), 0)
-                    print(f"PID {pid} is still alive, waiting...")
-                    time.sleep(.2)
-                except OSError as e:
-                    if e.errno == errno.ESRCH:
-                        break
-                    print(f"Unexpected exception when waiting for {pid} to die: {e.errno}")
-
-        os.remove(pid_file)
-
-def stop_dnsmasq():
-    stop_by_pid_file(dnsmasq_pid_file)
-
-def read_dnsmasq_log_file():
-    if os.path.exists(dnsmasq_log_file):
-        with open(dnsmasq_log_file, encoding='utf-8') as f:
-            return f.read()
-
-def remove_dnsmasq_lease_file():
-    if os.path.exists(dnsmasq_lease_file):
-        os.remove(dnsmasq_lease_file)
-
-def remove_dnsmasq_log_file():
-    if os.path.exists(dnsmasq_log_file):
-        os.remove(dnsmasq_log_file)
-
-def start_isc_dhcpd(interface, conf_file, ip):
-    conf_file_path = os.path.join(networkd_ci_path, conf_file)
-    isc_dhcpd_command = f'dhcpd {ip} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}'
-    Path(isc_dhcpd_lease_file).touch()
-    check_output(isc_dhcpd_command)
+class Utilities():
+    # pylint: disable=no-member
 
-def stop_isc_dhcpd():
-    stop_by_pid_file(isc_dhcpd_pid_file)
-    if os.path.exists(isc_dhcpd_lease_file):
-        os.remove(isc_dhcpd_lease_file)
+    def check_link_exists(self, link, expected=True):
+        if expected:
+            self.assertTrue(link_exists(link))
+        else:
+            self.assertFalse(link_exists(link))
 
-def remove_networkd_state_files():
-    if os.path.exists(os.path.join(networkd_runtime_directory, 'state')):
-        os.remove(os.path.join(networkd_runtime_directory, 'state'))
+    def check_link_attr(self, *args):
+        self.assertEqual(read_link_attr(*args[:-1]), args[-1])
 
-def stop_networkd(show_logs=True, remove_state_files=True):
-    if show_logs:
-        invocation_id = check_output('systemctl show systemd-networkd -p InvocationID --value')
-    check_output('systemctl stop systemd-networkd.socket')
-    check_output('systemctl stop systemd-networkd.service')
-    if show_logs:
-        print(check_output('journalctl _SYSTEMD_INVOCATION_ID=' + invocation_id))
-    if remove_state_files:
-        remove_networkd_state_files()
+    def check_bridge_port_attr(self, master, port, attribute, expected, allow_enoent=False):
+        path = os.path.join('/sys/devices/virtual/net', master, 'lower_' + port, 'brport', attribute)
+        if allow_enoent and not os.path.exists(path):
+            return
+        with open(path, encoding='utf-8') as f:
+            self.assertEqual(f.readline().strip(), expected)
 
-def start_networkd(sleep_sec=0):
-    check_output('systemctl start systemd-networkd')
-    if sleep_sec > 0:
-        time.sleep(sleep_sec)
+    def check_ipv4_sysctl_attr(self, link, attribute, expected):
+        self.assertEqual(read_ipv4_sysctl_attr(link, attribute), expected)
 
-def restart_networkd(sleep_sec=0, show_logs=True, remove_state_files=True):
-    stop_networkd(show_logs, remove_state_files)
-    start_networkd(sleep_sec)
+    def check_ipv6_sysctl_attr(self, link, attribute, expected):
+        self.assertEqual(read_ipv6_sysctl_attr(link, attribute), expected)
 
-class Utilities():
-    # pylint: disable=no-member
+    def wait_links(self, *links, timeout=20, fail_assert=True):
+        def links_exist(*links):
+            for link in links:
+                if not link_exists(link):
+                    return False
+            return True
 
-    def check_link_exists(self, link):
-        self.assertTrue(link_exists(link))
+        for iteration in range(timeout + 1):
+            if iteration > 0:
+                time.sleep(1)
 
-    def check_link_attr(self, *args):
-        self.assertEqual(read_link_attr(*args[:-1]), args[-1])
+            if links_exist(*links):
+                return True
+        if fail_assert:
+            self.fail('Timed out waiting for all links to be created: ' + ', '.join(list(links)))
+        return False
 
     def wait_activated(self, link, state='down', timeout=20, fail_assert=True):
         # wait for the interface is activated.
         invocation_id = check_output('systemctl show systemd-networkd -p InvocationID --value')
         needle = f'{link}: Bringing link {state}'
         flag = state.upper()
-        for iteration in range(timeout+1):
+        for iteration in range(timeout + 1):
             if iteration != 0:
                 time.sleep(1)
             if not link_exists(link):
@@ -694,12 +829,12 @@ class Utilities():
             args += ['--ipv6']
         try:
             check_output(*args, env=wait_online_env)
-        except subprocess.CalledProcessError:
+        except subprocess.CalledProcessError as e:
+            print(e.stdout) # show logs only on failure
             for link in links_with_operstate:
                 name = link.split(':')[0]
                 if link_exists(name):
-                    output = check_output(*networkctl_cmd, '-n', '0', 'status', name, env=env)
-                    print(output)
+                    call(*networkctl_cmd, '-n', '0', 'status', name, env=env)
             raise
         if not bool_any and setup_state:
             for link in links_with_operstate:
@@ -727,35 +862,15 @@ class Utilities():
 
 class NetworkctlTests(unittest.TestCase, Utilities):
 
-    links = [
-        'dummy98',
-        'test1',
-        'veth99',
-    ]
-
-    units = [
-        '11-dummy.netdev',
-        '11-dummy-mtu.netdev',
-        '11-dummy.network',
-        '12-dummy.netdev',
-        '12-dummy.link',
-        '25-address-static.network',
-        '25-veth.netdev',
-        '26-netdev-link-local-addressing-yes.network',
-    ]
-
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     @expectedFailureIfAlternativeNameIsNotAvailable()
     def test_altname(self):
-        copy_unit_to_networkd_unit_path('26-netdev-link-local-addressing-yes.network', '12-dummy.netdev', '12-dummy.link')
+        copy_network_unit('26-netdev-link-local-addressing-yes.network', '12-dummy.netdev', '12-dummy.link')
         check_output('udevadm control --reload')
         start_networkd()
         self.wait_online(['dummy98:degraded'])
@@ -764,7 +879,7 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'hogehogehogehogehogehoge')
 
     def test_reconfigure(self):
-        copy_unit_to_networkd_unit_path('25-address-static.network', '12-dummy.netdev')
+        copy_network_unit('25-address-static.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -778,7 +893,7 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         check_output('ip address del 10.1.2.4/16 dev dummy98')
         check_output('ip address del 10.2.2.4/16 dev dummy98')
 
-        check_output(*networkctl_cmd, 'reconfigure', 'dummy98', env=env)
+        networkctl_reconfigure('dummy98')
         self.wait_online(['dummy98:routable'])
 
         output = check_output('ip -4 address show dev dummy98')
@@ -787,9 +902,9 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         self.assertIn('inet 10.1.2.4/16 brd 10.1.255.255 scope global secondary dummy98', output)
         self.assertIn('inet 10.2.2.4/16 brd 10.2.255.255 scope global dummy98', output)
 
-        remove_unit_from_networkd_path(['25-address-static.network'])
+        remove_network_unit('25-address-static.network')
 
-        check_output(*networkctl_cmd, 'reload', env=env)
+        networkctl_reload()
         self.wait_operstate('dummy98', 'degraded', setup_state='unmanaged')
 
         output = check_output('ip -4 address show dev dummy98')
@@ -798,8 +913,8 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         self.assertNotIn('inet 10.1.2.4/16 brd 10.1.255.255 scope global secondary dummy98', output)
         self.assertNotIn('inet 10.2.2.4/16 brd 10.2.255.255 scope global dummy98', output)
 
-        copy_unit_to_networkd_unit_path('25-address-static.network')
-        check_output(*networkctl_cmd, 'reload', env=env)
+        copy_network_unit('25-address-static.network')
+        networkctl_reload()
         self.wait_online(['dummy98:routable'])
 
         output = check_output('ip -4 address show dev dummy98')
@@ -809,30 +924,30 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         self.assertIn('inet 10.2.2.4/16 brd 10.2.255.255 scope global dummy98', output)
 
     def test_reload(self):
-        start_networkd(3)
+        start_networkd()
 
-        copy_unit_to_networkd_unit_path('11-dummy.netdev')
-        check_output(*networkctl_cmd, 'reload', env=env)
+        copy_network_unit('11-dummy.netdev')
+        networkctl_reload()
         self.wait_operstate('test1', 'off', setup_state='unmanaged')
 
-        copy_unit_to_networkd_unit_path('11-dummy.network')
-        check_output(*networkctl_cmd, 'reload', env=env)
+        copy_network_unit('11-dummy.network')
+        networkctl_reload()
         self.wait_online(['test1:degraded'])
 
-        remove_unit_from_networkd_path(['11-dummy.network'])
-        check_output(*networkctl_cmd, 'reload', env=env)
+        remove_network_unit('11-dummy.network')
+        networkctl_reload()
         self.wait_operstate('test1', 'degraded', setup_state='unmanaged')
 
-        remove_unit_from_networkd_path(['11-dummy.netdev'])
-        check_output(*networkctl_cmd, 'reload', env=env)
+        remove_network_unit('11-dummy.netdev')
+        networkctl_reload()
         self.wait_operstate('test1', 'degraded', setup_state='unmanaged')
 
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network')
-        check_output(*networkctl_cmd, 'reload', env=env)
+        copy_network_unit('11-dummy.netdev', '11-dummy.network')
+        networkctl_reload()
         self.wait_operstate('test1', 'degraded')
 
     def test_glob(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network')
+        copy_network_unit('11-dummy.netdev', '11-dummy.network')
         start_networkd()
 
         self.wait_online(['test1:degraded'])
@@ -858,7 +973,7 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'test1')
 
     def test_mtu(self):
-        copy_unit_to_networkd_unit_path('11-dummy-mtu.netdev', '11-dummy.network')
+        copy_network_unit('11-dummy-mtu.netdev', '11-dummy.network')
         start_networkd()
 
         self.wait_online(['test1:degraded'])
@@ -867,7 +982,7 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'MTU: 1600')
 
     def test_type(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network')
+        copy_network_unit('11-dummy.netdev', '11-dummy.network')
         start_networkd()
         self.wait_online(['test1:degraded'])
 
@@ -881,7 +996,7 @@ class NetworkctlTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfLinkFileFieldIsNotSet()
     def test_udev_link_file(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network')
+        copy_network_unit('11-dummy.netdev', '11-dummy.network')
         start_networkd()
         self.wait_online(['test1:degraded'])
 
@@ -896,245 +1011,27 @@ class NetworkctlTests(unittest.TestCase, Utilities):
         self.assertRegex(output, r'Network File: n/a')
 
     def test_delete_links(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '11-dummy.network',
-                                        '25-veth.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('11-dummy.netdev', '11-dummy.network',
+                          '25-veth.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['test1:degraded', 'veth99:degraded', 'veth-peer:degraded'])
 
         check_output(*networkctl_cmd, 'delete', 'test1', 'veth99', env=env)
-        self.assertFalse(link_exists('test1'))
-        self.assertFalse(link_exists('veth99'))
-        self.assertFalse(link_exists('veth-peer'))
+        self.check_link_exists('test1', expected=False)
+        self.check_link_exists('veth99', expected=False)
+        self.check_link_exists('veth-peer', expected=False)
 
 class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
-    links_remove_earlier = [
-        'xfrm98',
-        'xfrm99',
-    ]
-
-    links = [
-        '6rdtun99',
-        'bareudp99',
-        'batadv99',
-        'bond98',
-        'bond99',
-        'bridge99',
-        'dropin-test',
-        'dummy98',
-        'erspan98',
-        'erspan99',
-        'geneve99',
-        'gretap96',
-        'gretap98',
-        'gretap99',
-        'gretun96',
-        'gretun97',
-        'gretun98',
-        'gretun99',
-        'ifb99',
-        'ip6gretap98',
-        'ip6gretap99',
-        'ip6gretun96',
-        'ip6gretun97',
-        'ip6gretun98',
-        'ip6gretun99',
-        'ip6tnl-external',
-        'ip6tnl-slaac',
-        'ip6tnl97',
-        'ip6tnl98',
-        'ip6tnl99',
-        'ipiptun96',
-        'ipiptun97',
-        'ipiptun98',
-        'ipiptun99',
-        'ipvlan99',
-        'ipvtap99',
-        'isataptun99',
-        'macvlan99',
-        'macvtap99',
-        'nlmon99',
-        'sittun96',
-        'sittun97',
-        'sittun98',
-        'sittun99',
-        'tap99',
-        'test1',
-        'tun99',
-        'vcan99',
-        'veth-mtu',
-        'veth99',
-        'vlan99',
-        'vrf99',
-        'vti6tun97',
-        'vti6tun98',
-        'vti6tun99',
-        'vtitun96',
-        'vtitun97',
-        'vtitun98',
-        'vtitun99',
-        'vxcan99',
-        'vxlan-slaac',
-        'vxlan97',
-        'vxlan98',
-        'vxlan99',
-        'wg97',
-        'wg98',
-        'wg99',
-    ]
-
-    units = [
-        '10-dropin-test.netdev',
-        '11-dummy.netdev',
-        '11-dummy.network',
-        '12-dummy.netdev',
-        '13-not-match-udev-property.network',
-        '14-match-udev-property.network',
-        '15-name-conflict-test.netdev',
-        '21-macvlan.netdev',
-        '21-macvtap.netdev',
-        '21-vlan-test1.network',
-        '21-vlan.netdev',
-        '21-vlan.network',
-        '25-6rd-tunnel.netdev',
-        '25-bareudp.netdev',
-        '25-batadv.netdev',
-        '25-bond.netdev',
-        '25-bond-balanced-tlb.netdev',
-        '25-bridge.netdev',
-        '25-bridge-configure-without-carrier.network',
-        '25-bridge.network',
-        '25-erspan0-tunnel-local-any.netdev',
-        '25-erspan0-tunnel.netdev',
-        '25-erspan1-tunnel-local-any.netdev',
-        '25-erspan1-tunnel.netdev',
-        '25-erspan2-tunnel-local-any.netdev',
-        '25-erspan2-tunnel.netdev',
-        '25-fou-gretap.netdev',
-        '25-fou-gre.netdev',
-        '25-fou-ipip.netdev',
-        '25-fou-ipproto-gre.netdev',
-        '25-fou-ipproto-ipip.netdev',
-        '25-fou-sit.netdev',
-        '25-geneve.netdev',
-        '25-gretap-tunnel-local-any.netdev',
-        '25-gretap-tunnel.netdev',
-        '25-gre-tunnel-any-any.netdev',
-        '25-gre-tunnel-local-any.netdev',
-        '25-gre-tunnel-remote-any.netdev',
-        '25-gre-tunnel.netdev',
-        '25-ifb.netdev',
-        '25-ip6gretap-tunnel-local-any.netdev',
-        '25-ip6gretap-tunnel.netdev',
-        '25-ip6gre-tunnel-any-any.netdev',
-        '25-ip6gre-tunnel-local-any.netdev',
-        '25-ip6gre-tunnel-remote-any.netdev',
-        '25-ip6gre-tunnel.netdev',
-        '25-ip6tnl-tunnel-external.netdev',
-        '25-ip6tnl-tunnel-local-any.netdev',
-        '25-ip6tnl-tunnel-local-slaac.netdev',
-        '25-ip6tnl-tunnel-local-slaac.network',
-        '25-ip6tnl-tunnel-remote-any.netdev',
-        '25-ip6tnl-tunnel.netdev',
-        '25-ipip-tunnel-any-any.netdev',
-        '25-ipip-tunnel-independent.netdev',
-        '25-ipip-tunnel-independent-loopback.netdev',
-        '25-ipip-tunnel-local-any.netdev',
-        '25-ipip-tunnel-remote-any.netdev',
-        '25-ipip-tunnel.netdev',
-        '25-ipvlan.netdev',
-        '25-ipvtap.netdev',
-        '25-isatap-tunnel.netdev',
-        '25-macsec.key',
-        '25-macsec.netdev',
-        '25-macsec.network',
-        '25-nlmon.netdev',
-        '25-sit-tunnel-any-any.netdev',
-        '25-sit-tunnel-local-any.netdev',
-        '25-sit-tunnel-remote-any.netdev',
-        '25-sit-tunnel.netdev',
-        '25-tap.netdev',
-        '25-tun.netdev',
-        '25-tunnel-any-any.network',
-        '25-tunnel-local-any.network',
-        '25-tunnel-remote-any.network',
-        '25-tunnel.network',
-        '25-vcan.netdev',
-        '25-veth-mtu.netdev',
-        '25-veth.netdev',
-        '25-vrf.netdev',
-        '25-vti6-tunnel-any-any.netdev',
-        '25-vti6-tunnel-local-any.netdev',
-        '25-vti6-tunnel-remote-any.netdev',
-        '25-vti6-tunnel.netdev',
-        '25-vti-tunnel-any-any.netdev',
-        '25-vti-tunnel-local-any.netdev',
-        '25-vti-tunnel-remote-any.netdev',
-        '25-vti-tunnel.netdev',
-        '25-vxcan.netdev',
-        '25-vxlan-independent.netdev',
-        '25-vxlan-ipv6.netdev',
-        '25-vxlan-local-slaac.netdev',
-        '25-vxlan-local-slaac.network',
-        '25-vxlan-veth99.network',
-        '25-vxlan.netdev',
-        '25-wireguard-23-peers.netdev',
-        '25-wireguard-23-peers.network',
-        '25-wireguard-no-peer.netdev',
-        '25-wireguard-no-peer.network',
-        '25-wireguard-preshared-key.txt',
-        '25-wireguard-private-key.txt',
-        '25-wireguard.netdev',
-        '25-wireguard.network',
-        '25-xfrm.netdev',
-        '25-xfrm-independent.netdev',
-        '25-6rd.network',
-        '25-erspan.network',
-        '25-gre.network',
-        '25-gretap.network',
-        '25-gretun.network',
-        '25-ip6gretap.network',
-        '25-ip6gretun.network',
-        '25-ip6tnl-slaac.network',
-        '25-ip6tnl.network',
-        '25-ipip.network',
-        '25-ipv6-prefix.network',
-        '25-ipvlan.network',
-        '25-ipvtap.network',
-        '25-isatap.network',
-        '26-macsec.network',
-        '25-macvlan.network',
-        '25-macvtap.network',
-        '26-netdev-link-local-addressing-yes.network',
-        '25-sit.network',
-        '25-vti6.network',
-        '25-vti.network',
-        '25-vxlan-ipv6.network',
-        '25-vxlan-test1.network',
-        '25-vxlan.network',
-        '25-xfrm.network',
-    ]
-
-    fou_ports = [
-        '55555',
-        '55556']
-
     def setUp(self):
-        remove_fou_ports(self.fou_ports)
-        remove_links(self.links_remove_earlier)
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_fou_ports(self.fou_ports)
-        remove_links(self.links_remove_earlier)
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_dropin_and_name_conflict(self):
-        copy_unit_to_networkd_unit_path('10-dropin-test.netdev', '15-name-conflict-test.netdev')
+        copy_network_unit('10-dropin-test.netdev', '15-name-conflict-test.netdev')
         start_networkd()
 
         self.wait_online(['dropin-test:off'], setup_state='unmanaged')
@@ -1144,7 +1041,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '00:50:56:c0:00:28')
 
     def test_match_udev_property(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '13-not-match-udev-property.network', '14-match-udev-property.network')
+        copy_network_unit('12-dummy.netdev', '13-not-match-udev-property.network', '14-match-udev-property.network')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -1153,7 +1050,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'Network File: /run/systemd/network/14-match-udev-property')
 
     def test_wait_online_any(self):
-        copy_unit_to_networkd_unit_path('25-bridge.netdev', '25-bridge.network', '11-dummy.netdev', '11-dummy.network')
+        copy_network_unit('25-bridge.netdev', '25-bridge.network', '11-dummy.netdev', '11-dummy.network')
         start_networkd()
 
         self.wait_online(['bridge99', 'test1:degraded'], bool_any=True)
@@ -1163,7 +1060,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('bareudp')
     def test_bareudp(self):
-        copy_unit_to_networkd_unit_path('25-bareudp.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-bareudp.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['bareudp99:degraded'])
@@ -1175,7 +1072,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('batman-adv')
     def test_batadv(self):
-        copy_unit_to_networkd_unit_path('25-batadv.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-batadv.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['batadv99:degraded'])
@@ -1185,7 +1082,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'batadv')
 
     def test_bridge(self):
-        copy_unit_to_networkd_unit_path('25-bridge.netdev', '25-bridge-configure-without-carrier.network')
+        copy_network_unit('25-bridge.netdev', '25-bridge-configure-without-carrier.network')
         start_networkd()
 
         self.wait_online(['bridge99:no-carrier'])
@@ -1214,25 +1111,25 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertIn('vlan_default_pvid 9 ', output)
 
     def test_bond(self):
-        copy_unit_to_networkd_unit_path('25-bond.netdev', '25-bond-balanced-tlb.netdev')
+        copy_network_unit('25-bond.netdev', '25-bond-balanced-tlb.netdev')
         start_networkd()
 
         self.wait_online(['bond99:off', 'bond98:off'], setup_state='unmanaged')
 
-        self.assertEqual('802.3ad 4',         read_link_attr('bond99', 'bonding', 'mode'))
-        self.assertEqual('layer3+4 1',        read_link_attr('bond99', 'bonding', 'xmit_hash_policy'))
-        self.assertEqual('1000',              read_link_attr('bond99', 'bonding', 'miimon'))
-        self.assertEqual('fast 1',            read_link_attr('bond99', 'bonding', 'lacp_rate'))
-        self.assertEqual('2000',              read_link_attr('bond99', 'bonding', 'updelay'))
-        self.assertEqual('2000',              read_link_attr('bond99', 'bonding', 'downdelay'))
-        self.assertEqual('4',                 read_link_attr('bond99', 'bonding', 'resend_igmp'))
-        self.assertEqual('1',                 read_link_attr('bond99', 'bonding', 'min_links'))
-        self.assertEqual('1218',              read_link_attr('bond99', 'bonding', 'ad_actor_sys_prio'))
-        self.assertEqual('811',               read_link_attr('bond99', 'bonding', 'ad_user_port_key'))
-        self.assertEqual('00:11:22:33:44:55', read_link_attr('bond99', 'bonding', 'ad_actor_system'))
-
-        self.assertEqual('balance-tlb 5',     read_link_attr('bond98', 'bonding', 'mode'))
-        self.assertEqual('1',                 read_link_attr('bond98', 'bonding', 'tlb_dynamic_lb'))
+        self.check_link_attr('bond99', 'bonding', 'mode',              '802.3ad 4')
+        self.check_link_attr('bond99', 'bonding', 'xmit_hash_policy',  'layer3+4 1')
+        self.check_link_attr('bond99', 'bonding', 'miimon',            '1000')
+        self.check_link_attr('bond99', 'bonding', 'lacp_rate',         'fast 1')
+        self.check_link_attr('bond99', 'bonding', 'updelay',           '2000')
+        self.check_link_attr('bond99', 'bonding', 'downdelay',         '2000')
+        self.check_link_attr('bond99', 'bonding', 'resend_igmp',       '4')
+        self.check_link_attr('bond99', 'bonding', 'min_links',         '1')
+        self.check_link_attr('bond99', 'bonding', 'ad_actor_sys_prio', '1218')
+        self.check_link_attr('bond99', 'bonding', 'ad_user_port_key',  '811')
+        self.check_link_attr('bond99', 'bonding', 'ad_actor_system',   '00:11:22:33:44:55')
+
+        self.check_link_attr('bond98', 'bonding', 'mode',              'balance-tlb 5')
+        self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb',    '1')
 
         output = check_output(*networkctl_cmd, '-n', '0', 'status', 'bond99', env=env)
         print(output)
@@ -1246,8 +1143,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertIn('Mode: balance-tlb', output)
 
     def test_vlan(self):
-        copy_unit_to_networkd_unit_path('21-vlan.netdev', '11-dummy.netdev',
-                                        '21-vlan.network', '21-vlan-test1.network')
+        copy_network_unit('21-vlan.netdev', '11-dummy.netdev',
+                          '21-vlan.network', '21-vlan-test1.network')
         start_networkd()
 
         self.wait_online(['test1:degraded', 'vlan99:routable'])
@@ -1275,13 +1172,18 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'inet 192.168.23.5/24 brd 192.168.23.255 scope global vlan99')
 
     def test_macvtap(self):
+        first = True
         for mode in ['private', 'vepa', 'bridge', 'passthru']:
+            if first:
+                first = False
+            else:
+                self.tearDown()
+
+            print(f'### test_macvtap(mode={mode})')
             with self.subTest(mode=mode):
-                if mode != 'private':
-                    self.tearDown()
-                copy_unit_to_networkd_unit_path('21-macvtap.netdev', '26-netdev-link-local-addressing-yes.network',
-                                                '11-dummy.netdev', '25-macvtap.network')
-                with open(os.path.join(network_unit_file_path, '21-macvtap.netdev'), mode='a', encoding='utf-8') as f:
+                copy_network_unit('21-macvtap.netdev', '26-netdev-link-local-addressing-yes.network',
+                                  '11-dummy.netdev', '25-macvtap.network')
+                with open(os.path.join(network_unit_dir, '21-macvtap.netdev'), mode='a', encoding='utf-8') as f:
                     f.write('[MACVTAP]\nMode=' + mode)
                 start_networkd()
 
@@ -1293,13 +1195,18 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
                 self.assertRegex(output, 'macvtap mode ' + mode + ' ')
 
     def test_macvlan(self):
+        first = True
         for mode in ['private', 'vepa', 'bridge', 'passthru']:
+            if first:
+                first = False
+            else:
+                self.tearDown()
+
+            print(f'### test_macvlan(mode={mode})')
             with self.subTest(mode=mode):
-                if mode != 'private':
-                    self.tearDown()
-                copy_unit_to_networkd_unit_path('21-macvlan.netdev', '26-netdev-link-local-addressing-yes.network',
-                                                '11-dummy.netdev', '25-macvlan.network')
-                with open(os.path.join(network_unit_file_path, '21-macvlan.netdev'), mode='a', encoding='utf-8') as f:
+                copy_network_unit('21-macvlan.netdev', '26-netdev-link-local-addressing-yes.network',
+                                  '11-dummy.netdev', '25-macvlan.network')
+                with open(os.path.join(network_unit_dir, '21-macvlan.netdev'), mode='a', encoding='utf-8') as f:
                     f.write('[MACVLAN]\nMode=' + mode)
                 start_networkd()
 
@@ -1315,14 +1222,10 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
                 self.assertRegex(output, ' mtu 2000 ')
                 self.assertRegex(output, 'macvlan mode ' + mode + ' ')
 
-                rc = call("ip link del test1")
-                self.assertEqual(rc, 0)
-                time.sleep(1)
-
-                rc = call("ip link add test1 type dummy")
-                self.assertEqual(rc, 0)
+                remove_link('test1')
                 time.sleep(1)
 
+                check_output("ip link add test1 type dummy")
                 self.wait_online(['macvlan99:degraded',
                                   'test1:carrier' if mode == 'passthru' else 'test1:degraded'])
 
@@ -1337,13 +1240,18 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('ipvlan')
     def test_ipvlan(self):
+        first = True
         for mode, flag in [['L2', 'private'], ['L3', 'vepa'], ['L3S', 'bridge']]:
+            if first:
+                first = False
+            else:
+                self.tearDown()
+
+            print(f'### test_ipvlan(mode={mode}, flag={flag})')
             with self.subTest(mode=mode, flag=flag):
-                if mode != 'L2':
-                    self.tearDown()
-                copy_unit_to_networkd_unit_path('25-ipvlan.netdev', '26-netdev-link-local-addressing-yes.network',
-                                                '11-dummy.netdev', '25-ipvlan.network')
-                with open(os.path.join(network_unit_file_path, '25-ipvlan.netdev'), mode='a', encoding='utf-8') as f:
+                copy_network_unit('25-ipvlan.netdev', '26-netdev-link-local-addressing-yes.network',
+                                  '11-dummy.netdev', '25-ipvlan.network')
+                with open(os.path.join(network_unit_dir, '25-ipvlan.netdev'), mode='a', encoding='utf-8') as f:
                     f.write('[IPVLAN]\nMode=' + mode + '\nFlags=' + flag)
 
                 start_networkd()
@@ -1355,13 +1263,18 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('ipvtap')
     def test_ipvtap(self):
+        first = True
         for mode, flag in [['L2', 'private'], ['L3', 'vepa'], ['L3S', 'bridge']]:
+            if first:
+                first = False
+            else:
+                self.tearDown()
+
+            print(f'### test_ipvtap(mode={mode}, flag={flag})')
             with self.subTest(mode=mode, flag=flag):
-                if mode != 'L2':
-                    self.tearDown()
-                copy_unit_to_networkd_unit_path('25-ipvtap.netdev', '26-netdev-link-local-addressing-yes.network',
-                                                '11-dummy.netdev', '25-ipvtap.network')
-                with open(os.path.join(network_unit_file_path, '25-ipvtap.netdev'), mode='a', encoding='utf-8') as f:
+                copy_network_unit('25-ipvtap.netdev', '26-netdev-link-local-addressing-yes.network',
+                                  '11-dummy.netdev', '25-ipvtap.network')
+                with open(os.path.join(network_unit_dir, '25-ipvtap.netdev'), mode='a', encoding='utf-8') as f:
                     f.write('[IPVTAP]\nMode=' + mode + '\nFlags=' + flag)
 
                 start_networkd()
@@ -1372,8 +1285,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
                 self.assertRegex(output, 'ipvtap  *mode ' + mode.lower() + ' ' + flag)
 
     def test_veth(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '26-netdev-link-local-addressing-yes.network',
-                                        '25-veth-mtu.netdev')
+        copy_network_unit('25-veth.netdev', '26-netdev-link-local-addressing-yes.network',
+                          '25-veth-mtu.netdev')
         start_networkd()
 
         self.wait_online(['veth99:degraded', 'veth-peer:degraded', 'veth-mtu:degraded', 'veth-mtu-peer:degraded'])
@@ -1395,7 +1308,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'mtu 1800')
 
     def test_tun(self):
-        copy_unit_to_networkd_unit_path('25-tun.netdev')
+        copy_network_unit('25-tun.netdev')
         start_networkd()
 
         self.wait_online(['tun99:off'], setup_state='unmanaged')
@@ -1406,7 +1319,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'tun (type tun pi on vnet_hdr on multi_queue|addrgenmode) ')
 
     def test_tap(self):
-        copy_unit_to_networkd_unit_path('25-tap.netdev')
+        copy_network_unit('25-tap.netdev')
         start_networkd()
 
         self.wait_online(['tap99:off'], setup_state='unmanaged')
@@ -1418,31 +1331,31 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('vrf')
     def test_vrf(self):
-        copy_unit_to_networkd_unit_path('25-vrf.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-vrf.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['vrf99:carrier'])
 
     @expectedFailureIfModuleIsNotAvailable('vcan')
     def test_vcan(self):
-        copy_unit_to_networkd_unit_path('25-vcan.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-vcan.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['vcan99:carrier'])
 
     @expectedFailureIfModuleIsNotAvailable('vxcan')
     def test_vxcan(self):
-        copy_unit_to_networkd_unit_path('25-vxcan.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-vxcan.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['vxcan99:carrier', 'vxcan-peer:carrier'])
 
     @expectedFailureIfModuleIsNotAvailable('wireguard')
     def test_wireguard(self):
-        copy_unit_to_networkd_unit_path('25-wireguard.netdev', '25-wireguard.network',
-                                        '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network',
-                                        '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt',
-                                        '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network')
+        copy_network_unit('25-wireguard.netdev', '25-wireguard.network',
+                          '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network',
+                          '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt',
+                          '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network')
         start_networkd()
         self.wait_online(['wg99:routable', 'wg98:routable', 'wg97:carrier'])
 
@@ -1554,7 +1467,7 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
             self.assertEqual(output, '0x4d3')
 
     def test_geneve(self):
-        copy_unit_to_networkd_unit_path('25-geneve.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-geneve.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['geneve99:degraded'])
@@ -1567,11 +1480,11 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'udp6zerocsumrx')
 
     def test_ipip_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-ipip.network',
-                                        '25-ipip-tunnel.netdev', '25-tunnel.network',
-                                        '25-ipip-tunnel-local-any.netdev', '25-tunnel-local-any.network',
-                                        '25-ipip-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
-                                        '25-ipip-tunnel-any-any.netdev', '25-tunnel-any-any.network')
+        copy_network_unit('12-dummy.netdev', '25-ipip.network',
+                          '25-ipip-tunnel.netdev', '25-tunnel.network',
+                          '25-ipip-tunnel-local-any.netdev', '25-tunnel-local-any.network',
+                          '25-ipip-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+                          '25-ipip-tunnel-any-any.netdev', '25-tunnel-any-any.network')
         start_networkd()
         self.wait_online(['ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', 'dummy98:degraded'])
 
@@ -1589,11 +1502,11 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'ipip (ipip )?remote any local any dev dummy98')
 
     def test_gre_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-gretun.network',
-                                        '25-gre-tunnel.netdev', '25-tunnel.network',
-                                        '25-gre-tunnel-local-any.netdev', '25-tunnel-local-any.network',
-                                        '25-gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
-                                        '25-gre-tunnel-any-any.netdev', '25-tunnel-any-any.network')
+        copy_network_unit('12-dummy.netdev', '25-gretun.network',
+                          '25-gre-tunnel.netdev', '25-tunnel.network',
+                          '25-gre-tunnel-local-any.netdev', '25-tunnel-local-any.network',
+                          '25-gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+                          '25-gre-tunnel-any-any.netdev', '25-tunnel-any-any.network')
         start_networkd()
         self.wait_online(['gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', 'dummy98:degraded'])
 
@@ -1627,20 +1540,16 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertNotRegex(output, 'oseq')
 
     def test_ip6gre_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-ip6gretun.network',
-                                        '25-ip6gre-tunnel.netdev', '25-tunnel.network',
-                                        '25-ip6gre-tunnel-local-any.netdev', '25-tunnel-local-any.network',
-                                        '25-ip6gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
-                                        '25-ip6gre-tunnel-any-any.netdev', '25-tunnel-any-any.network')
-        start_networkd(5)
+        copy_network_unit('12-dummy.netdev', '25-ip6gretun.network',
+                          '25-ip6gre-tunnel.netdev', '25-tunnel.network',
+                          '25-ip6gre-tunnel-local-any.netdev', '25-tunnel-local-any.network',
+                          '25-ip6gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+                          '25-ip6gre-tunnel-any-any.netdev', '25-tunnel-any-any.network')
+        start_networkd()
 
         # Old kernels seem not to support IPv6LL address on ip6gre tunnel, So please do not use wait_online() here.
 
-        self.check_link_exists('dummy98')
-        self.check_link_exists('ip6gretun99')
-        self.check_link_exists('ip6gretun98')
-        self.check_link_exists('ip6gretun97')
-        self.check_link_exists('ip6gretun96')
+        self.wait_links('dummy98', 'ip6gretun99', 'ip6gretun98', 'ip6gretun97', 'ip6gretun96')
 
         output = check_output('ip -d link show ip6gretun99')
         print(output)
@@ -1656,9 +1565,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'ip6gre remote any local any dev dummy98')
 
     def test_gretap_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-gretap.network',
-                                        '25-gretap-tunnel.netdev', '25-tunnel.network',
-                                        '25-gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network')
+        copy_network_unit('12-dummy.netdev', '25-gretap.network',
+                          '25-gretap-tunnel.netdev', '25-tunnel.network',
+                          '25-gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network')
         start_networkd()
         self.wait_online(['gretap99:routable', 'gretap98:routable', 'dummy98:degraded'])
 
@@ -1678,9 +1587,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'oseq')
 
     def test_ip6gretap_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-ip6gretap.network',
-                                        '25-ip6gretap-tunnel.netdev', '25-tunnel.network',
-                                        '25-ip6gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network')
+        copy_network_unit('12-dummy.netdev', '25-ip6gretap.network',
+                          '25-ip6gretap-tunnel.netdev', '25-tunnel.network',
+                          '25-ip6gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network')
         start_networkd()
         self.wait_online(['ip6gretap99:routable', 'ip6gretap98:routable', 'dummy98:degraded'])
 
@@ -1692,11 +1601,11 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local any dev dummy98')
 
     def test_vti_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-vti.network',
-                                        '25-vti-tunnel.netdev', '25-tunnel.network',
-                                        '25-vti-tunnel-local-any.netdev', '25-tunnel-local-any.network',
-                                        '25-vti-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
-                                        '25-vti-tunnel-any-any.netdev', '25-tunnel-any-any.network')
+        copy_network_unit('12-dummy.netdev', '25-vti.network',
+                          '25-vti-tunnel.netdev', '25-tunnel.network',
+                          '25-vti-tunnel-local-any.netdev', '25-tunnel-local-any.network',
+                          '25-vti-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+                          '25-vti-tunnel-any-any.netdev', '25-tunnel-any-any.network')
         start_networkd()
         self.wait_online(['vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', 'dummy98:degraded'])
 
@@ -1714,10 +1623,10 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'vti remote any local any dev dummy98')
 
     def test_vti6_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-vti6.network',
-                                        '25-vti6-tunnel.netdev', '25-tunnel.network',
-                                        '25-vti6-tunnel-local-any.netdev', '25-tunnel-local-any.network',
-                                        '25-vti6-tunnel-remote-any.netdev', '25-tunnel-remote-any.network')
+        copy_network_unit('12-dummy.netdev', '25-vti6.network',
+                          '25-vti6-tunnel.netdev', '25-tunnel.network',
+                          '25-vti6-tunnel-local-any.netdev', '25-tunnel-local-any.network',
+                          '25-vti6-tunnel-remote-any.netdev', '25-tunnel-remote-any.network')
         start_networkd()
         self.wait_online(['vti6tun99:routable', 'vti6tun98:routable', 'vti6tun97:routable', 'dummy98:degraded'])
 
@@ -1732,13 +1641,13 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'vti6 remote (any|::) local 2a00:ffde:4567:edde::4987 dev dummy98')
 
     def test_ip6tnl_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-ip6tnl.network',
-                                        '25-ip6tnl-tunnel.netdev', '25-tunnel.network',
-                                        '25-ip6tnl-tunnel-local-any.netdev', '25-tunnel-local-any.network',
-                                        '25-ip6tnl-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
-                                        '25-veth.netdev', '25-ip6tnl-slaac.network', '25-ipv6-prefix.network',
-                                        '25-ip6tnl-tunnel-local-slaac.netdev', '25-ip6tnl-tunnel-local-slaac.network',
-                                        '25-ip6tnl-tunnel-external.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('12-dummy.netdev', '25-ip6tnl.network',
+                          '25-ip6tnl-tunnel.netdev', '25-tunnel.network',
+                          '25-ip6tnl-tunnel-local-any.netdev', '25-tunnel-local-any.network',
+                          '25-ip6tnl-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+                          '25-veth.netdev', '25-ip6tnl-slaac.network', '25-ipv6-prefix.network',
+                          '25-ip6tnl-tunnel-local-slaac.netdev', '25-ip6tnl-tunnel-local-slaac.network',
+                          '25-ip6tnl-tunnel-external.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
         self.wait_online(['ip6tnl99:routable', 'ip6tnl98:routable', 'ip6tnl97:routable',
                           'ip6tnl-slaac:degraded', 'ip6tnl-external:degraded',
@@ -1770,11 +1679,11 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertIn('default dev ip6tnl-slaac proto static', output)
 
     def test_sit_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-sit.network',
-                                        '25-sit-tunnel.netdev', '25-tunnel.network',
-                                        '25-sit-tunnel-local-any.netdev', '25-tunnel-local-any.network',
-                                        '25-sit-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
-                                        '25-sit-tunnel-any-any.netdev', '25-tunnel-any-any.network')
+        copy_network_unit('12-dummy.netdev', '25-sit.network',
+                          '25-sit-tunnel.netdev', '25-tunnel.network',
+                          '25-sit-tunnel-local-any.netdev', '25-tunnel-local-any.network',
+                          '25-sit-tunnel-remote-any.netdev', '25-tunnel-remote-any.network',
+                          '25-sit-tunnel-any-any.netdev', '25-tunnel-any-any.network')
         start_networkd()
         self.wait_online(['sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', 'dummy98:degraded'])
 
@@ -1792,8 +1701,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, "sit (ip6ip )?remote any local any dev dummy98")
 
     def test_isatap_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-isatap.network',
-                                        '25-isatap-tunnel.netdev', '25-tunnel.network')
+        copy_network_unit('12-dummy.netdev', '25-isatap.network',
+                          '25-isatap-tunnel.netdev', '25-tunnel.network')
         start_networkd()
         self.wait_online(['isataptun99:routable', 'dummy98:degraded'])
 
@@ -1802,8 +1711,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, "isatap ")
 
     def test_6rd_tunnel(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-6rd.network',
-                                        '25-6rd-tunnel.netdev', '25-tunnel.network')
+        copy_network_unit('12-dummy.netdev', '25-6rd.network',
+                          '25-6rd-tunnel.netdev', '25-tunnel.network')
         start_networkd()
         self.wait_online(['sittun99:routable', 'dummy98:degraded'])
 
@@ -1813,9 +1722,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfERSPANv0IsNotSupported()
     def test_erspan_tunnel_v0(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-erspan.network',
-                                        '25-erspan0-tunnel.netdev', '25-tunnel.network',
-                                        '25-erspan0-tunnel-local-any.netdev', '25-tunnel-local-any.network')
+        copy_network_unit('12-dummy.netdev', '25-erspan.network',
+                          '25-erspan0-tunnel.netdev', '25-tunnel.network',
+                          '25-erspan0-tunnel-local-any.netdev', '25-tunnel-local-any.network')
         start_networkd()
         self.wait_online(['erspan99:routable', 'erspan98:routable', 'dummy98:degraded'])
 
@@ -1839,9 +1748,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertIn('iseq', output)
 
     def test_erspan_tunnel_v1(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-erspan.network',
-                                        '25-erspan1-tunnel.netdev', '25-tunnel.network',
-                                        '25-erspan1-tunnel-local-any.netdev', '25-tunnel-local-any.network')
+        copy_network_unit('12-dummy.netdev', '25-erspan.network',
+                          '25-erspan1-tunnel.netdev', '25-tunnel.network',
+                          '25-erspan1-tunnel-local-any.netdev', '25-tunnel-local-any.network')
         start_networkd()
         self.wait_online(['erspan99:routable', 'erspan98:routable', 'dummy98:degraded'])
 
@@ -1870,9 +1779,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfERSPANv2IsNotSupported()
     def test_erspan_tunnel_v2(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-erspan.network',
-                                        '25-erspan2-tunnel.netdev', '25-tunnel.network',
-                                        '25-erspan2-tunnel-local-any.netdev', '25-tunnel-local-any.network')
+        copy_network_unit('12-dummy.netdev', '25-erspan.network',
+                          '25-erspan2-tunnel.netdev', '25-tunnel.network',
+                          '25-erspan2-tunnel-local-any.netdev', '25-tunnel-local-any.network')
         start_networkd()
         self.wait_online(['erspan99:routable', 'erspan98:routable', 'dummy98:degraded'])
 
@@ -1900,22 +1809,22 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertIn('oseq', output)
 
     def test_tunnel_independent(self):
-        copy_unit_to_networkd_unit_path('25-ipip-tunnel-independent.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-ipip-tunnel-independent.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['ipiptun99:carrier'])
 
     def test_tunnel_independent_loopback(self):
-        copy_unit_to_networkd_unit_path('25-ipip-tunnel-independent-loopback.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-ipip-tunnel-independent-loopback.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['ipiptun99:carrier'])
 
     @expectedFailureIfModuleIsNotAvailable('xfrm_interface')
     def test_xfrm(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-xfrm.network',
-                                        '25-xfrm.netdev', '25-xfrm-independent.netdev',
-                                        '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('12-dummy.netdev', '25-xfrm.network',
+                          '25-xfrm.netdev', '25-xfrm-independent.netdev',
+                          '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['dummy98:degraded', 'xfrm98:degraded', 'xfrm99:degraded'])
@@ -1936,9 +1845,9 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         # Maybe, error handling in lookup_id() in sd-netlink/generic-netlink.c needs to be updated.
         self.assertTrue(is_module_available('fou'))
 
-        copy_unit_to_networkd_unit_path('25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev',
-                                        '25-fou-ipip.netdev', '25-fou-sit.netdev',
-                                        '25-fou-gre.netdev', '25-fou-gretap.netdev')
+        copy_network_unit('25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev',
+                          '25-fou-ipip.netdev', '25-fou-sit.netdev',
+                          '25-fou-gre.netdev', '25-fou-gretap.netdev')
         start_networkd()
 
         self.wait_online(['ipiptun96:off', 'sittun96:off', 'gretun96:off', 'gretap96:off'], setup_state='unmanaged')
@@ -1962,12 +1871,12 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'encap fou encap-sport auto encap-dport 55556')
 
     def test_vxlan(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '25-vxlan-test1.network',
-                                        '25-vxlan.netdev', '25-vxlan.network',
-                                        '25-vxlan-ipv6.netdev', '25-vxlan-ipv6.network',
-                                        '25-vxlan-independent.netdev', '26-netdev-link-local-addressing-yes.network',
-                                        '25-veth.netdev', '25-vxlan-veth99.network', '25-ipv6-prefix.network',
-                                        '25-vxlan-local-slaac.netdev', '25-vxlan-local-slaac.network')
+        copy_network_unit('11-dummy.netdev', '25-vxlan-test1.network',
+                          '25-vxlan.netdev', '25-vxlan.network',
+                          '25-vxlan-ipv6.netdev', '25-vxlan-ipv6.network',
+                          '25-vxlan-independent.netdev', '26-netdev-link-local-addressing-yes.network',
+                          '25-veth.netdev', '25-vxlan-veth99.network', '25-ipv6-prefix.network',
+                          '25-vxlan-local-slaac.netdev', '25-vxlan-local-slaac.network')
         start_networkd()
 
         self.wait_online(['test1:degraded', 'veth99:routable', 'veth-peer:degraded',
@@ -2014,8 +1923,8 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
 
     @unittest.skip(reason="Causes kernel panic on recent kernels: https://bugzilla.kernel.org/show_bug.cgi?id=208315")
     def test_macsec(self):
-        copy_unit_to_networkd_unit_path('25-macsec.netdev', '25-macsec.network', '25-macsec.key',
-                                        '26-macsec.network', '12-dummy.netdev')
+        copy_network_unit('25-macsec.netdev', '25-macsec.network', '25-macsec.key',
+                          '26-macsec.network', '12-dummy.netdev')
         start_networkd()
 
         self.wait_online(['dummy98:degraded', 'macsec99:routable'])
@@ -2042,51 +1951,30 @@ class NetworkdNetDevTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '0: PN [0-9]*, state off, key 02030400000000000000000000000000')
 
     def test_nlmon(self):
-        copy_unit_to_networkd_unit_path('25-nlmon.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-nlmon.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['nlmon99:carrier'])
 
     @expectedFailureIfModuleIsNotAvailable('ifb')
     def test_ifb(self):
-        copy_unit_to_networkd_unit_path('25-ifb.netdev', '26-netdev-link-local-addressing-yes.network')
+        copy_network_unit('25-ifb.netdev', '26-netdev-link-local-addressing-yes.network')
         start_networkd()
 
         self.wait_online(['ifb99:degraded'])
 
 class NetworkdL2TPTests(unittest.TestCase, Utilities):
 
-    links =[
-        'l2tp-ses1',
-        'l2tp-ses2',
-        'l2tp-ses3',
-        'l2tp-ses4',
-        'test1']
-
-    units = [
-        '11-dummy.netdev',
-        '25-l2tp-dummy.network',
-        '25-l2tp.network',
-        '25-l2tp-ip.netdev',
-        '25-l2tp-udp.netdev']
-
-    l2tp_tunnel_ids = [ '10' ]
-
     def setUp(self):
-        remove_l2tp_tunnels(self.l2tp_tunnel_ids)
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_l2tp_tunnels(self.l2tp_tunnel_ids)
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     @expectedFailureIfModuleIsNotAvailable('l2tp_eth')
     def test_l2tp_udp(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '25-l2tp-dummy.network',
-                                        '25-l2tp-udp.netdev', '25-l2tp.network')
+        copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network',
+                          '25-l2tp-udp.netdev', '25-l2tp.network')
         start_networkd()
 
         self.wait_online(['test1:routable', 'l2tp-ses1:degraded', 'l2tp-ses2:degraded'])
@@ -2113,8 +2001,8 @@ class NetworkdL2TPTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('l2tp_ip')
     def test_l2tp_ip(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '25-l2tp-dummy.network',
-                                        '25-l2tp-ip.netdev', '25-l2tp.network')
+        copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network',
+                          '25-l2tp-ip.netdev', '25-l2tp.network')
         start_networkd()
 
         self.wait_online(['test1:routable', 'l2tp-ses3:degraded', 'l2tp-ses4:degraded'])
@@ -2138,127 +2026,23 @@ class NetworkdL2TPTests(unittest.TestCase, Utilities):
         self.assertRegex(output, "interface name: l2tp-ses4")
 
 class NetworkdNetworkTests(unittest.TestCase, Utilities):
-    links = [
-        'bond199',
-        'dummy98',
-        'dummy99',
-        'gretun97',
-        'ip6gretun97',
-        'test1',
-        'veth-peer',
-        'veth99',
-        'vlan99',
-        'vrf99',
-    ]
-
-    units = [
-        '11-dummy.netdev',
-        '12-dummy.netdev',
-        '12-dummy.network',
-        '21-vlan.netdev',
-        '21-vlan-test1.network',
-        '23-active-slave.network',
-        '24-keep-configuration-static.network',
-        '24-search-domain.network',
-        '25-address-ipv4acd-veth99.network',
-        '25-address-link-section.network',
-        '25-address-peer-ipv4.network',
-        '25-address-static.network',
-        '25-activation-policy.network',
-        '25-bind-carrier.network',
-        '25-bond-active-backup-slave.netdev',
-        '25-fibrule-invert.network',
-        '25-fibrule-port-range.network',
-        '25-fibrule-uidrange.network',
-        '25-gre-tunnel-remote-any.netdev',
-        '25-ip6gre-tunnel-remote-any.netdev',
-        '25-ipv6-address-label-section.network',
-        '25-ipv6-proxy-ndp.network',
-        '25-link-local-addressing-no.network',
-        '25-link-local-addressing-yes.network',
-        '25-link-section-unmanaged.network',
-        '25-neighbor-section.network',
-        '25-neighbor-next.network',
-        '25-neighbor-ipv6.network',
-        '25-neighbor-ip-dummy.network',
-        '25-neighbor-ip.network',
-        '25-nexthop-dummy.network',
-        '25-nexthop-nothing.network',
-        '25-nexthop.network',
-        '25-qdisc-cake.network',
-        '25-qdisc-clsact-and-htb.network',
-        '25-qdisc-drr.network',
-        '25-qdisc-ets.network',
-        '25-qdisc-fq_pie.network',
-        '25-qdisc-hhf.network',
-        '25-qdisc-ingress-netem-compat.network',
-        '25-qdisc-pie.network',
-        '25-qdisc-qfq.network',
-        '25-prefix-route-with-vrf.network',
-        '25-prefix-route-without-vrf.network',
-        '25-route-ipv6-src.network',
-        '25-route-static.network',
-        '25-route-via-ipv6.network',
-        '25-route-vrf.network',
-        '25-gateway-static.network',
-        '25-gateway-next-static.network',
-        '25-sysctl-disable-ipv6.network',
-        '25-sysctl.network',
-        '25-test1.network',
-        '25-veth-peer.network',
-        '25-veth.netdev',
-        '25-vrf.netdev',
-        '25-vrf.network',
-        '26-link-local-addressing-ipv6.network',
-        '25-dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network',
-        '25-dhcp-server-with-ipv6-prefix.network',
-        '25-ipv6ra-prefix-client-with-static-ipv4-address.network',
-        '25-ipv6-prefix-with-delay.network',
-        '25-routing-policy-rule-dummy98.network',
-        '25-routing-policy-rule-test1.network',
-        '25-routing-policy-rule-reconfigure1.network',
-        '25-routing-policy-rule-reconfigure2.network',
-    ]
-
-    networkd_conf_dropins = [
-        'networkd-manage-foreign-routes-no.conf',
-    ]
-
-    routing_policy_rule_tables = ['7', '8', '9', '10', '1011']
-    routes = [['blackhole', '202.54.1.2'], ['unreachable', '202.54.1.3'], ['prohibit', '202.54.1.4']]
 
     def setUp(self):
-        remove_blackhole_nexthops()
-        remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
-        remove_routes(self.routes)
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
-        call('ip netns del ns99', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+        setup_common()
 
     def tearDown(self):
-        remove_blackhole_nexthops()
-        remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
-        remove_routes(self.routes)
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        remove_networkd_conf_dropin(self.networkd_conf_dropins)
-        stop_networkd(show_logs=True)
-        call('ip netns del ns99', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+        tear_down_common()
 
     def test_address_static(self):
         # test for #22515. The address will be removed and replaced with /64 prefix.
-        rc = call('ip link add dummy98 type dummy')
-        self.assertEqual(rc, 0)
-        rc = call('ip link set dev dummy98 up')
-        self.assertEqual(rc, 0)
-        rc = call('ip -6 address add 2001:db8:0:f101::15/128 dev dummy98')
-        self.assertEqual(rc, 0)
+        check_output('ip link add dummy98 type dummy')
+        check_output('ip link set dev dummy98 up')
+        check_output('ip -6 address add 2001:db8:0:f101::15/128 dev dummy98')
         self.wait_address('dummy98', '2001:db8:0:f101::15/128', ipv='-6')
-        rc = call('ip -4 address add 10.3.2.3/16 brd 10.3.255.250 scope global label dummy98:hoge dev dummy98')
-        self.assertEqual(rc, 0)
+        check_output('ip -4 address add 10.3.2.3/16 brd 10.3.255.250 scope global label dummy98:hoge dev dummy98')
         self.wait_address('dummy98', '10.3.2.3/16 brd 10.3.255.250', ipv='-4')
 
-        copy_unit_to_networkd_unit_path('25-address-static.network', '12-dummy.netdev')
+        copy_network_unit('25-address-static.network', '12-dummy.netdev')
         start_networkd()
 
         self.wait_online(['dummy98:routable'])
@@ -2274,7 +2058,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'inet 10.9.0.1/16 (metric 128 |)brd 10.9.255.255 scope global dummy98')
 
         # test for ENOBUFS issue #17012
-        for i in range(1,254):
+        for i in range(1, 254):
             self.assertIn(f'inet 10.3.3.{i}/16 brd 10.3.255.255', output)
 
         # invalid sections
@@ -2309,8 +2093,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
         # Tests for #20891.
         # 1. set preferred lifetime forever to drop the deprecated flag for testing #20891.
-        self.assertEqual(call('ip address change 10.7.8.9/16 dev dummy98 preferred_lft forever'), 0)
-        self.assertEqual(call('ip address change 2001:db8:1:f101::1/64 dev dummy98 preferred_lft forever'), 0)
+        check_output('ip address change 10.7.8.9/16 dev dummy98 preferred_lft forever')
+        check_output('ip address change 2001:db8:1:f101::1/64 dev dummy98 preferred_lft forever')
         output = check_output('ip -4 address show dev dummy98')
         print(output)
         self.assertNotIn('deprecated', output)
@@ -2319,7 +2103,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertNotIn('deprecated', output)
 
         # 2. reconfigure the interface.
-        check_output(*networkctl_cmd, 'reconfigure', 'dummy98', env=env)
+        networkctl_reconfigure('dummy98')
         self.wait_online(['dummy98:routable'])
 
         # 3. check the deprecated flag is set for the address configured with PreferredLifetime=0
@@ -2332,7 +2116,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
         # test for ENOBUFS issue #17012
         output = check_output('ip -4 address show dev dummy98')
-        for i in range(1,254):
+        for i in range(1, 254):
             self.assertIn(f'inet 10.3.3.{i}/16 brd 10.3.255.255', output)
 
         # TODO: check json string
@@ -2346,7 +2130,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         check_output('ip netns exec ns99 ip link set veth-peer up')
         check_output('ip netns exec ns99 ip address add 192.168.100.10/24 dev veth-peer')
 
-        copy_unit_to_networkd_unit_path('25-address-ipv4acd-veth99.network', dropins=False)
+        copy_network_unit('25-address-ipv4acd-veth99.network', copy_dropins=False)
         start_networkd()
         self.wait_online(['veth99:routable'])
 
@@ -2355,9 +2139,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertNotIn('192.168.100.10/24', output)
         self.assertIn('192.168.100.11/24', output)
 
-        copy_unit_to_networkd_unit_path('25-address-ipv4acd-veth99.network.d/conflict-address.conf')
-        run(*networkctl_cmd, 'reload', env=env)
-        time.sleep(1)
+        copy_network_unit('25-address-ipv4acd-veth99.network.d/conflict-address.conf')
+        networkctl_reload()
         self.wait_operstate('veth99', operstate='routable', setup_state='configuring', setup_timeout=10)
 
         output = check_output('ip -4 address show dev veth99')
@@ -2367,7 +2150,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     def test_address_peer_ipv4(self):
         # test for issue #17304
-        copy_unit_to_networkd_unit_path('25-address-peer-ipv4.network', '12-dummy.netdev')
+        copy_network_unit('25-address-peer-ipv4.network', '12-dummy.netdev')
 
         for trial in range(2):
             if trial == 0:
@@ -2382,14 +2165,14 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('vrf')
     def test_prefix_route(self):
-        copy_unit_to_networkd_unit_path('25-prefix-route-with-vrf.network', '12-dummy.netdev',
-                                        '25-prefix-route-without-vrf.network', '11-dummy.netdev',
-                                        '25-vrf.netdev', '25-vrf.network')
+        copy_network_unit('25-prefix-route-with-vrf.network', '12-dummy.netdev',
+                          '25-prefix-route-without-vrf.network', '11-dummy.netdev',
+                          '25-vrf.netdev', '25-vrf.network')
         for trial in range(2):
             if trial == 0:
                 start_networkd()
             else:
-                restart_networkd(3)
+                restart_networkd()
 
             self.wait_online(['dummy98:routable', 'test1:routable', 'vrf99:carrier'])
 
@@ -2448,12 +2231,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
             self.assertRegex(output, 'ff00::/8 (proto kernel )?metric 256 (linkdown )?pref medium')
 
     def test_configure_without_carrier(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev')
+        copy_network_unit('11-dummy.netdev')
         start_networkd()
         self.wait_operstate('test1', 'off', '')
         check_output('ip link set dev test1 up carrier off')
 
-        copy_unit_to_networkd_unit_path('25-test1.network.d/configure-without-carrier.conf', dropins=False)
+        copy_network_unit('25-test1.network.d/configure-without-carrier.conf', copy_dropins=False)
         restart_networkd()
         self.wait_online(['test1:no-carrier'])
 
@@ -2472,12 +2255,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
                 self.assertRegex(output, routable_map[carrier])
 
     def test_configure_without_carrier_yes_ignore_carrier_loss_no(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev')
+        copy_network_unit('11-dummy.netdev')
         start_networkd()
         self.wait_operstate('test1', 'off', '')
         check_output('ip link set dev test1 up carrier off')
 
-        copy_unit_to_networkd_unit_path('25-test1.network')
+        copy_network_unit('25-test1.network')
         restart_networkd()
         self.wait_online(['test1:no-carrier'])
 
@@ -2500,7 +2283,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
                 self.assertRegex(output, routable_map[carrier])
 
     def test_routing_policy_rule(self):
-        copy_unit_to_networkd_unit_path('25-routing-policy-rule-test1.network', '11-dummy.netdev')
+        copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev')
         start_networkd()
         self.wait_online(['test1:degraded'])
 
@@ -2538,14 +2321,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
     def test_routing_policy_rule_issue_11280(self):
-        copy_unit_to_networkd_unit_path('25-routing-policy-rule-test1.network', '11-dummy.netdev',
-                                        '25-routing-policy-rule-dummy98.network', '12-dummy.netdev')
+        copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev',
+                          '25-routing-policy-rule-dummy98.network', '12-dummy.netdev')
 
-        for _ in range(3):
-            # Remove state files only first time
-            start_networkd(3)
+        for trial in range(3):
+            restart_networkd(show_logs=(trial > 0))
             self.wait_online(['test1:degraded', 'dummy98:degraded'])
-            time.sleep(1)
 
             output = check_output('ip rule list table 7')
             print(output)
@@ -2555,10 +2336,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
             print(output)
             self.assertRegex(output, '112:     from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8')
 
-            stop_networkd(remove_state_files=False)
-
     def test_routing_policy_rule_reconfigure(self):
-        copy_unit_to_networkd_unit_path('25-routing-policy-rule-reconfigure2.network', '11-dummy.netdev')
+        copy_network_unit('25-routing-policy-rule-reconfigure2.network', '11-dummy.netdev')
         start_networkd()
         self.wait_online(['test1:degraded'])
 
@@ -2573,9 +2352,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertIn('10112:  from all oif test1 lookup 1011', output)
 
-        copy_unit_to_networkd_unit_path('25-routing-policy-rule-reconfigure1.network', '11-dummy.netdev')
-        run(*networkctl_cmd, 'reload', env=env)
-        time.sleep(1)
+        copy_network_unit('25-routing-policy-rule-reconfigure1.network', '11-dummy.netdev')
+        networkctl_reload()
         self.wait_online(['test1:degraded'])
 
         output = check_output('ip rule list table 1011')
@@ -2590,11 +2368,11 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertNotIn('10112:       from all oif test1 lookup 1011', output)
         self.assertIn('10113:  from all iif test1 lookup 1011', output)
 
-        run('ip rule delete priority 10111')
-        run('ip rule delete priority 10112')
-        run('ip rule delete priority 10113')
-        run('ip rule delete priority 10114')
-        run('ip -6 rule delete priority 10113')
+        call('ip rule delete priority 10111')
+        call('ip rule delete priority 10112')
+        call('ip rule delete priority 10113')
+        call('ip rule delete priority 10114')
+        call('ip -6 rule delete priority 10113')
 
         output = check_output('ip rule list table 1011')
         print(output)
@@ -2604,7 +2382,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertEqual(output, '')
 
-        run(*networkctl_cmd, 'reconfigure', 'test1', env=env)
+        networkctl_reconfigure('test1')
         self.wait_online(['test1:degraded'])
 
         output = check_output('ip rule list table 1011')
@@ -2620,7 +2398,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfRoutingPolicyPortRangeIsNotAvailable()
     def test_routing_policy_rule_port_range(self):
-        copy_unit_to_networkd_unit_path('25-fibrule-port-range.network', '11-dummy.netdev')
+        copy_network_unit('25-fibrule-port-range.network', '11-dummy.netdev')
         start_networkd()
         self.wait_online(['test1:degraded'])
 
@@ -2635,7 +2413,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfRoutingPolicyIPProtoIsNotAvailable()
     def test_routing_policy_rule_invert(self):
-        copy_unit_to_networkd_unit_path('25-fibrule-invert.network', '11-dummy.netdev')
+        copy_network_unit('25-fibrule-invert.network', '11-dummy.netdev')
         start_networkd()
         self.wait_online(['test1:degraded'])
 
@@ -2648,7 +2426,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable()
     def test_routing_policy_rule_uidrange(self):
-        copy_unit_to_networkd_unit_path('25-fibrule-uidrange.network', '11-dummy.netdev')
+        copy_network_unit('25-fibrule-uidrange.network', '11-dummy.netdev')
         start_networkd()
         self.wait_online(['test1:degraded'])
 
@@ -2663,7 +2441,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         if not manage_foreign_routes:
             copy_networkd_conf_dropin('networkd-manage-foreign-routes-no.conf')
 
-        copy_unit_to_networkd_unit_path('25-route-static.network', '12-dummy.netdev')
+        copy_network_unit('25-route-static.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -2765,9 +2543,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         # TODO: check json string
         check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
-        copy_unit_to_networkd_unit_path('25-address-static.network')
-        check_output(*networkctl_cmd, 'reload', env=env)
-        time.sleep(1)
+        copy_network_unit('25-address-static.network')
+        networkctl_reload()
         self.wait_online(['dummy98:routable'])
 
         # check all routes managed by Manager are removed
@@ -2801,9 +2578,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertEqual(output, '')
 
-        remove_unit_from_networkd_path(['25-address-static.network'])
-        check_output(*networkctl_cmd, 'reload', env=env)
-        time.sleep(1)
+        remove_network_unit('25-address-static.network')
+        networkctl_reload()
         self.wait_online(['dummy98:routable'])
 
         # check all routes managed by Manager are reconfigured
@@ -2837,8 +2613,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertIn('prohibit 2001:1234:5678::4 dev lo proto static', output)
 
-        rc = call("ip link del dummy98")
-        self.assertEqual(rc, 0)
+        remove_link('dummy98')
         time.sleep(2)
 
         # check all routes managed by Manager are removed
@@ -2875,13 +2650,20 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.tearDown()
 
     def test_route_static(self):
+        first = True
         for manage_foreign_routes in [True, False]:
+            if first:
+                first = False
+            else:
+                self.tearDown()
+
+            print(f'### test_route_static(manage_foreign_routes={manage_foreign_routes})')
             with self.subTest(manage_foreign_routes=manage_foreign_routes):
                 self._test_route_static(manage_foreign_routes)
 
     @expectedFailureIfRTA_VIAIsNotSupported()
     def test_route_via_ipv6(self):
-        copy_unit_to_networkd_unit_path('25-route-via-ipv6.network', '12-dummy.netdev')
+        copy_network_unit('25-route-via-ipv6.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -2902,8 +2684,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('vrf')
     def test_route_vrf(self):
-        copy_unit_to_networkd_unit_path('25-route-vrf.network', '12-dummy.netdev',
-                                        '25-vrf.netdev', '25-vrf.network')
+        copy_network_unit('25-route-vrf.network', '12-dummy.netdev',
+                          '25-vrf.netdev', '25-vrf.network')
         start_networkd()
         self.wait_online(['dummy98:routable', 'vrf99:carrier'])
 
@@ -2916,30 +2698,30 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertNotRegex(output, 'default via 192.168.100.1')
 
     def test_gateway_reconfigure(self):
-        copy_unit_to_networkd_unit_path('25-gateway-static.network', '12-dummy.netdev')
+        copy_network_unit('25-gateway-static.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
         print('### ip -4 route show dev dummy98 default')
         output = check_output('ip -4 route show dev dummy98 default')
         print(output)
-        self.assertRegex(output, 'default via 149.10.124.59 proto static')
-        self.assertNotRegex(output, '149.10.124.60')
+        self.assertIn('default via 149.10.124.59 proto static', output)
+        self.assertNotIn('149.10.124.60', output)
 
-        remove_unit_from_networkd_path(['25-gateway-static.network'])
-        copy_unit_to_networkd_unit_path('25-gateway-next-static.network')
-        restart_networkd(3)
+        remove_network_unit('25-gateway-static.network')
+        copy_network_unit('25-gateway-next-static.network')
+        networkctl_reload()
         self.wait_online(['dummy98:routable'])
         print('### ip -4 route show dev dummy98 default')
         output = check_output('ip -4 route show dev dummy98 default')
         print(output)
-        self.assertNotRegex(output, '149.10.124.59')
-        self.assertRegex(output, 'default via 149.10.124.60 proto static')
+        self.assertNotIn('149.10.124.59', output)
+        self.assertIn('default via 149.10.124.60 proto static', output)
 
     def test_ip_route_ipv6_src_route(self):
         # a dummy device does not make the addresses go through tentative state, so we
         # reuse a bond from an earlier test, which does make the addresses go through
         # tentative state, and do our test on that
-        copy_unit_to_networkd_unit_path('23-active-slave.network', '25-route-ipv6-src.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev')
+        copy_network_unit('23-active-slave.network', '25-route-ipv6-src.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:enslaved', 'bond199:routable'])
 
@@ -2950,7 +2732,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '2001:1234:56:8f63::2')
 
     def test_ip_link_mac_address(self):
-        copy_unit_to_networkd_unit_path('25-address-link-section.network', '12-dummy.netdev')
+        copy_network_unit('25-address-link-section.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:degraded'])
 
@@ -2959,15 +2741,13 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '00:01:02:aa:bb:cc')
 
     def test_ip_link_unmanaged(self):
-        copy_unit_to_networkd_unit_path('25-link-section-unmanaged.network', '12-dummy.netdev')
-        start_networkd(5)
-
-        self.check_link_exists('dummy98')
+        copy_network_unit('25-link-section-unmanaged.network', '12-dummy.netdev')
+        start_networkd()
 
         self.wait_operstate('dummy98', 'off', setup_state='unmanaged')
 
     def test_ipv6_address_label(self):
-        copy_unit_to_networkd_unit_path('25-ipv6-address-label-section.network', '12-dummy.netdev')
+        copy_network_unit('25-ipv6-address-label-section.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:degraded'])
 
@@ -2976,18 +2756,18 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '2004:da8:1::/64')
 
     def test_ipv6_proxy_ndp(self):
-        copy_unit_to_networkd_unit_path('25-ipv6-proxy-ndp.network', '12-dummy.netdev')
+        copy_network_unit('25-ipv6-proxy-ndp.network', '12-dummy.netdev')
         start_networkd()
 
         self.wait_online(['dummy98:routable'])
 
         output = check_output('ip neighbor show proxy dev dummy98')
         print(output)
-        for i in range(1,5):
+        for i in range(1, 5):
             self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy')
 
     def test_neighbor_section(self):
-        copy_unit_to_networkd_unit_path('25-neighbor-section.network', '12-dummy.netdev')
+        copy_network_unit('25-neighbor-section.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:degraded'], timeout='40s')
 
@@ -3001,7 +2781,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
     def test_neighbor_reconfigure(self):
-        copy_unit_to_networkd_unit_path('25-neighbor-section.network', '12-dummy.netdev')
+        copy_network_unit('25-neighbor-section.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:degraded'], timeout='40s')
 
@@ -3011,9 +2791,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '192.168.10.1.*00:00:5e:00:02:65.*PERMANENT')
         self.assertRegex(output, '2004:da8:1::1.*00:00:5e:00:02:66.*PERMANENT')
 
-        remove_unit_from_networkd_path(['25-neighbor-section.network'])
-        copy_unit_to_networkd_unit_path('25-neighbor-next.network')
-        restart_networkd(3)
+        remove_network_unit('25-neighbor-section.network')
+        copy_network_unit('25-neighbor-next.network')
+        networkctl_reload()
         self.wait_online(['dummy98:degraded'], timeout='40s')
         print('### ip neigh list dev dummy98')
         output = check_output('ip neigh list dev dummy98')
@@ -3023,8 +2803,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertNotRegex(output, '2004:da8:1::1.*PERMANENT')
 
     def test_neighbor_gre(self):
-        copy_unit_to_networkd_unit_path('25-neighbor-ip.network', '25-neighbor-ipv6.network', '25-neighbor-ip-dummy.network',
-                                        '12-dummy.netdev', '25-gre-tunnel-remote-any.netdev', '25-ip6gre-tunnel-remote-any.netdev')
+        copy_network_unit('25-neighbor-ip.network', '25-neighbor-ipv6.network', '25-neighbor-ip-dummy.network',
+                          '12-dummy.netdev', '25-gre-tunnel-remote-any.netdev', '25-ip6gre-tunnel-remote-any.netdev')
         start_networkd()
         self.wait_online(['dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable'], timeout='40s')
 
@@ -3040,8 +2820,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
     def test_link_local_addressing(self):
-        copy_unit_to_networkd_unit_path('25-link-local-addressing-yes.network', '11-dummy.netdev',
-                                        '25-link-local-addressing-no.network', '12-dummy.netdev')
+        copy_network_unit('25-link-local-addressing-yes.network', '11-dummy.netdev',
+                          '25-link-local-addressing-no.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['test1:degraded', 'dummy98:carrier'])
 
@@ -3066,12 +2846,12 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         #    stable_secret (RFC7217)
         # 3: generate stable privacy addresses, using a random secret if unset
 
-        self.assertEqual(read_ipv6_sysctl_attr('test1', 'stable_secret'), '0123:4567:89ab:cdef:0123:4567:89ab:cdef')
-        self.assertEqual(read_ipv6_sysctl_attr('test1', 'addr_gen_mode'), '2')
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'addr_gen_mode'), '1')
+        self.check_ipv6_sysctl_attr('test1', 'stable_secret', '0123:4567:89ab:cdef:0123:4567:89ab:cdef')
+        self.check_ipv6_sysctl_attr('test1', 'addr_gen_mode', '2')
+        self.check_ipv6_sysctl_attr('dummy98', 'addr_gen_mode', '1')
 
     def test_link_local_addressing_ipv6ll(self):
-        copy_unit_to_networkd_unit_path('26-link-local-addressing-ipv6.network', '12-dummy.netdev')
+        copy_network_unit('26-link-local-addressing-ipv6.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:degraded'])
 
@@ -3080,9 +2860,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertRegex(output, 'inet6 .* scope link')
 
-        copy_unit_to_networkd_unit_path('25-link-local-addressing-no.network')
-        check_output(*networkctl_cmd, 'reload', env=env)
-        time.sleep(1)
+        copy_network_unit('25-link-local-addressing-no.network')
+        networkctl_reload()
         self.wait_online(['dummy98:carrier'])
 
         # Check if the IPv6LL address is removed.
@@ -3090,9 +2869,8 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertNotRegex(output, 'inet6 .* scope link')
 
-        remove_unit_from_networkd_path(['25-link-local-addressing-no.network'])
-        check_output(*networkctl_cmd, 'reload', env=env)
-        time.sleep(1)
+        remove_network_unit('25-link-local-addressing-no.network')
+        networkctl_reload()
         self.wait_online(['dummy98:degraded'])
 
         # Check if a new IPv6LL address is assigned.
@@ -3101,21 +2879,21 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'inet6 .* scope link')
 
     def test_sysctl(self):
-        copy_unit_to_networkd_unit_path('25-sysctl.network', '12-dummy.netdev')
+        copy_network_unit('25-sysctl.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:degraded'])
 
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'forwarding'), '1')
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'use_tempaddr'), '2')
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'dad_transmits'), '3')
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'hop_limit'), '5')
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'proxy_ndp'), '1')
-        self.assertEqual(read_ipv4_sysctl_attr('dummy98', 'forwarding'),'1')
-        self.assertEqual(read_ipv4_sysctl_attr('dummy98', 'proxy_arp'), '1')
-        self.assertEqual(read_ipv4_sysctl_attr('dummy98', 'accept_local'), '1')
+        self.check_ipv6_sysctl_attr('dummy98', 'forwarding', '1')
+        self.check_ipv6_sysctl_attr('dummy98', 'use_tempaddr', '2')
+        self.check_ipv6_sysctl_attr('dummy98', 'dad_transmits', '3')
+        self.check_ipv6_sysctl_attr('dummy98', 'hop_limit', '5')
+        self.check_ipv6_sysctl_attr('dummy98', 'proxy_ndp', '1')
+        self.check_ipv4_sysctl_attr('dummy98', 'forwarding', '1')
+        self.check_ipv4_sysctl_attr('dummy98', 'proxy_arp', '1')
+        self.check_ipv4_sysctl_attr('dummy98', 'accept_local', '1')
 
     def test_sysctl_disable_ipv6(self):
-        copy_unit_to_networkd_unit_path('25-sysctl-disable-ipv6.network', '12-dummy.netdev')
+        copy_network_unit('25-sysctl-disable-ipv6.network', '12-dummy.netdev')
 
         print('## Disable ipv6')
         check_output('sysctl net.ipv6.conf.all.disable_ipv6=1')
@@ -3139,13 +2917,13 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'default')
         self.assertRegex(output, 'via 2607:5300:203:39ff:ff:ff:ff:ff')
 
-        check_output('ip link del dummy98')
+        remove_link('dummy98')
 
         print('## Enable ipv6')
         check_output('sysctl net.ipv6.conf.all.disable_ipv6=0')
         check_output('sysctl net.ipv6.conf.default.disable_ipv6=0')
 
-        restart_networkd(3)
+        restart_networkd()
         self.wait_online(['dummy98:routable'])
 
         output = check_output('ip -4 address show dummy98')
@@ -3167,7 +2945,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         check_output('ip link set dummy98 up')
         time.sleep(2)
 
-        copy_unit_to_networkd_unit_path('25-bind-carrier.network', '11-dummy.netdev')
+        copy_network_unit('25-bind-carrier.network', '11-dummy.netdev')
         start_networkd()
         self.wait_online(['test1:routable'])
 
@@ -3186,7 +2964,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'inet 192.168.10.30/24 brd 192.168.10.255 scope global test1')
         self.wait_operstate('test1', 'routable')
 
-        check_output('ip link del dummy98')
+        remove_link('dummy98')
         time.sleep(2)
         output = check_output('ip address show test1')
         print(output)
@@ -3211,13 +2989,13 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'inet 192.168.10.30/24 brd 192.168.10.255 scope global test1')
         self.wait_operstate('test1', 'routable')
 
-    def _test_activation_policy(self, test, interface):
+    def _test_activation_policy(self, interface, test):
         conffile = '25-activation-policy.network'
         if test:
             conffile = f'{conffile}.d/{test}.conf'
         if interface == 'vlan99':
-            copy_unit_to_networkd_unit_path('21-vlan.netdev', '21-vlan-test1.network')
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', conffile, dropins=False)
+            copy_network_unit('21-vlan.netdev', '21-vlan-test1.network')
+        copy_network_unit('11-dummy.netdev', conffile, copy_dropins=False)
         start_networkd()
 
         always = test.startswith('always')
@@ -3251,13 +3029,17 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
                 time.sleep(1)
 
     def test_activation_policy(self):
+        first = True
         for interface in ['test1', 'vlan99']:
-            with self.subTest(interface=interface):
-                for test in ['up', 'always-up', 'manual', 'always-down', 'down', '']:
-                    with self.subTest(test=test):
-                        self.setUp()
-                        self._test_activation_policy(test, interface)
-                        self.tearDown()
+            for test in ['up', 'always-up', 'manual', 'always-down', 'down', '']:
+                if first:
+                    first = False
+                else:
+                    self.tearDown()
+
+                print(f'### test_activation_policy(interface={interface}, test={test})')
+                with self.subTest(interface=interface, test=test):
+                    self._test_activation_policy(interface, test)
 
     def _test_activation_policy_required_for_online(self, policy, required):
         conffile = '25-activation-policy.network'
@@ -3266,7 +3048,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
             units += [f'{conffile}.d/{policy}.conf']
         if required:
             units += [f'{conffile}.d/required-{required}.conf']
-        copy_unit_to_networkd_unit_path(*units, dropins=False)
+        copy_network_unit(*units, copy_dropins=False)
         start_networkd()
 
         if policy.endswith('down'):
@@ -3298,15 +3080,20 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, f'Required For Online: {yesno}')
 
     def test_activation_policy_required_for_online(self):
+        first = True
         for policy in ['up', 'always-up', 'manual', 'always-down', 'down', 'bound', '']:
             for required in ['yes', 'no', '']:
+                if first:
+                    first = False
+                else:
+                    self.tearDown()
+
+                print(f'### test_activation_policy_required_for_online(policy={policy}, required={required})')
                 with self.subTest(policy=policy, required=required):
-                    self.setUp()
                     self._test_activation_policy_required_for_online(policy, required)
-                    self.tearDown()
 
     def test_domain(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '24-search-domain.network')
+        copy_network_unit('12-dummy.netdev', '24-search-domain.network')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3330,7 +3117,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         output = check_output('ip route show dev dummy98')
         print(output)
 
-        copy_unit_to_networkd_unit_path('24-keep-configuration-static.network')
+        copy_network_unit('24-keep-configuration-static.network')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3402,18 +3189,15 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
             # TODO: check json string
             check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
-        copy_unit_to_networkd_unit_path('25-nexthop.network', '25-veth.netdev', '25-veth-peer.network',
-                                        '12-dummy.netdev', '25-nexthop-dummy.network')
+        copy_network_unit('25-nexthop.network', '25-veth.netdev', '25-veth-peer.network',
+                          '12-dummy.netdev', '25-nexthop-dummy.network')
         start_networkd()
 
         check_nexthop(self)
 
-        remove_unit_from_networkd_path(['25-nexthop.network'])
-        copy_unit_to_networkd_unit_path('25-nexthop-nothing.network')
-        rc = call(*networkctl_cmd, 'reload', env=env)
-        self.assertEqual(rc, 0)
-        time.sleep(1)
-
+        remove_network_unit('25-nexthop.network')
+        copy_network_unit('25-nexthop-nothing.network')
+        networkctl_reload()
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
         output = check_output('ip nexthop list dev veth99')
@@ -3423,18 +3207,14 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         print(output)
         self.assertEqual(output, '')
 
-        remove_unit_from_networkd_path(['25-nexthop-nothing.network'])
-        copy_unit_to_networkd_unit_path('25-nexthop.network')
-        rc = call(*networkctl_cmd, 'reconfigure', 'dummy98', env=env)
-        self.assertEqual(rc, 0)
-        rc = call(*networkctl_cmd, 'reload', env=env)
-        self.assertEqual(rc, 0)
-        time.sleep(1)
+        remove_network_unit('25-nexthop-nothing.network')
+        copy_network_unit('25-nexthop.network')
+        networkctl_reconfigure('dummy98')
+        networkctl_reload()
 
         check_nexthop(self)
 
-        rc = call('ip link del veth99')
-        self.assertEqual(rc, 0)
+        remove_link('veth99')
         time.sleep(2)
 
         output = check_output('ip nexthop list dev lo')
@@ -3442,11 +3222,10 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertEqual(output, '')
 
     def test_qdisc(self):
-        copy_unit_to_networkd_unit_path('25-qdisc-clsact-and-htb.network', '12-dummy.netdev',
-                                        '25-qdisc-ingress-netem-compat.network', '11-dummy.netdev')
+        copy_network_unit('25-qdisc-clsact-and-htb.network', '12-dummy.netdev',
+                          '25-qdisc-ingress-netem-compat.network', '11-dummy.netdev')
         check_output('modprobe sch_teql max_equalizers=2')
         start_networkd()
-
         self.wait_online(['dummy98:routable', 'test1:routable'])
 
         output = check_output('tc qdisc show dev test1')
@@ -3524,10 +3303,9 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'cburst 123457')
 
     def test_qdisc2(self):
-        copy_unit_to_networkd_unit_path('25-qdisc-drr.network', '12-dummy.netdev',
-                                        '25-qdisc-qfq.network', '11-dummy.netdev')
+        copy_network_unit('25-qdisc-drr.network', '12-dummy.netdev',
+                          '25-qdisc-qfq.network', '11-dummy.netdev')
         start_networkd()
-
         self.wait_online(['dummy98:routable', 'test1:routable'])
 
         output = check_output('tc qdisc show dev dummy98')
@@ -3547,7 +3325,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfCAKEIsNotAvailable()
     def test_qdisc_cake(self):
-        copy_unit_to_networkd_unit_path('25-qdisc-cake.network', '12-dummy.netdev')
+        copy_network_unit('25-qdisc-cake.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3569,7 +3347,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfPIEIsNotAvailable()
     def test_qdisc_pie(self):
-        copy_unit_to_networkd_unit_path('25-qdisc-pie.network', '12-dummy.netdev')
+        copy_network_unit('25-qdisc-pie.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3580,7 +3358,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfHHFIsNotAvailable()
     def test_qdisc_hhf(self):
-        copy_unit_to_networkd_unit_path('25-qdisc-hhf.network', '12-dummy.netdev')
+        copy_network_unit('25-qdisc-hhf.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3591,7 +3369,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfETSIsNotAvailable()
     def test_qdisc_ets(self):
-        copy_unit_to_networkd_unit_path('25-qdisc-ets.network', '12-dummy.netdev')
+        copy_network_unit('25-qdisc-ets.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3605,7 +3383,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfFQPIEIsNotAvailable()
     def test_qdisc_fq_pie(self):
-        copy_unit_to_networkd_unit_path('25-qdisc-fq_pie.network', '12-dummy.netdev')
+        copy_network_unit('25-qdisc-fq_pie.network', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3616,7 +3394,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'limit 200000p')
 
     def test_wait_online_ipv4(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-with-ipv6-prefix.network', '25-dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-with-ipv6-prefix.network', '25-dhcp-client-ipv4-ipv6ra-prefix-client-with-delay.network')
         start_networkd()
 
         self.wait_online(['veth99:routable'], ipv4=True)
@@ -3624,7 +3402,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.wait_address('veth99', r'192.168.5.[0-9]+', ipv='-4', timeout_sec=1)
 
     def test_wait_online_ipv6(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-ipv6-prefix-with-delay.network', '25-ipv6ra-prefix-client-with-static-ipv4-address.network')
+        copy_network_unit('25-veth.netdev', '25-ipv6-prefix-with-delay.network', '25-ipv6ra-prefix-client-with-static-ipv4-address.network')
         start_networkd()
 
         self.wait_online(['veth99:routable'], ipv6=True)
@@ -3632,26 +3410,15 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.wait_address('veth99', r'2002:da8:1:0:1034:56ff:fe78:9abc', ipv='-6', timeout_sec=1)
 
 class NetworkdStateFileTests(unittest.TestCase, Utilities):
-    links = [
-        'dummy98',
-    ]
-
-    units = [
-        '12-dummy.netdev',
-        '25-state-file-tests.network',
-    ]
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_state_file(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '25-state-file-tests.network')
+        copy_network_unit('12-dummy.netdev', '25-state-file-tests.network')
         start_networkd()
         self.wait_online(['dummy98:routable'])
 
@@ -3731,39 +3498,19 @@ class NetworkdStateFileTests(unittest.TestCase, Utilities):
         self.assertIn('DNSSEC=no', output)
 
 class NetworkdBondTests(unittest.TestCase, Utilities):
-    links = [
-        'bond199',
-        'bond99',
-        'dummy98',
-        'test1']
-
-    units = [
-        '11-dummy.netdev',
-        '12-dummy.netdev',
-        '23-active-slave.network',
-        '23-bond199.network',
-        '23-keep-master.network',
-        '23-primary-slave.network',
-        '25-bond-active-backup-slave.netdev',
-        '25-bond.netdev',
-        '25-bond99.network',
-        '25-bond-slave.network']
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_bond_keep_master(self):
         check_output('ip link add bond199 type bond mode active-backup')
         check_output('ip link add dummy98 type dummy')
         check_output('ip link set dummy98 master bond199')
 
-        copy_unit_to_networkd_unit_path('23-keep-master.network')
+        copy_network_unit('23-keep-master.network')
         start_networkd()
         self.wait_online(['dummy98:enslaved'])
 
@@ -3776,7 +3523,7 @@ class NetworkdBondTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'master bond199')
 
     def test_bond_active_slave(self):
-        copy_unit_to_networkd_unit_path('23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev')
+        copy_network_unit('23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:enslaved', 'bond199:degraded'])
 
@@ -3785,7 +3532,7 @@ class NetworkdBondTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'active_slave dummy98')
 
     def test_bond_primary_slave(self):
-        copy_unit_to_networkd_unit_path('23-primary-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev')
+        copy_network_unit('23-primary-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev')
         start_networkd()
         self.wait_online(['dummy98:enslaved', 'bond199:degraded'])
 
@@ -3794,8 +3541,8 @@ class NetworkdBondTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'primary dummy98')
 
     def test_bond_operstate(self):
-        copy_unit_to_networkd_unit_path('25-bond.netdev', '11-dummy.netdev', '12-dummy.netdev',
-                                        '25-bond99.network','25-bond-slave.network')
+        copy_network_unit('25-bond.netdev', '11-dummy.netdev', '12-dummy.netdev',
+                          '25-bond99.network', '25-bond-slave.network')
         start_networkd()
         self.wait_online(['dummy98:enslaved', 'test1:enslaved', 'bond99:routable'])
 
@@ -3839,50 +3586,16 @@ class NetworkdBondTests(unittest.TestCase, Utilities):
             self.assertNotRegex(output, 'NO-CARRIER')
 
 class NetworkdBridgeTests(unittest.TestCase, Utilities):
-    links = [
-        'bridge99',
-        'dummy98',
-        'test1',
-        'vlan99',
-    ]
-
-    units = [
-        '11-dummy.netdev',
-        '12-dummy.netdev',
-        '21-vlan.netdev',
-        '21-vlan.network',
-        '23-keep-master.network',
-        '26-bridge.netdev',
-        '26-bridge-configure-without-carrier.network',
-        '26-bridge-issue-20373.netdev',
-        '26-bridge-mdb-master.network',
-        '26-bridge-mdb-slave.network',
-        '26-bridge-slave-interface-1.network',
-        '26-bridge-slave-interface-2.network',
-        '26-bridge-vlan-master-issue-20373.network',
-        '26-bridge-vlan-master.network',
-        '26-bridge-vlan-slave-issue-20373.network',
-        '26-bridge-vlan-slave.network',
-        '25-bridge99-ignore-carrier-loss.network',
-        '25-bridge99.network'
-    ]
-
-    routing_policy_rule_tables = ['100']
 
     def setUp(self):
-        remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_routing_policy_rule_tables(self.routing_policy_rule_tables)
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_bridge_vlan(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '26-bridge-vlan-slave.network',
-                                        '26-bridge.netdev', '26-bridge-vlan-master.network')
+        copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave.network',
+                          '26-bridge.netdev', '26-bridge-vlan-master.network')
         start_networkd()
         self.wait_online(['test1:enslaved', 'bridge99:degraded'])
 
@@ -3901,9 +3614,9 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         self.assertNotRegex(output, '4095')
 
     def test_bridge_vlan_issue_20373(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '26-bridge-vlan-slave-issue-20373.network',
-                                        '26-bridge-issue-20373.netdev', '26-bridge-vlan-master-issue-20373.network',
-                                        '21-vlan.netdev', '21-vlan.network')
+        copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave-issue-20373.network',
+                          '26-bridge-issue-20373.netdev', '26-bridge-vlan-master-issue-20373.network',
+                          '21-vlan.netdev', '21-vlan.network')
         start_networkd()
         self.wait_online(['test1:enslaved', 'bridge99:degraded', 'vlan99:routable'])
 
@@ -3920,8 +3633,8 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         self.assertIn('600', output)
 
     def test_bridge_mdb(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '26-bridge-mdb-slave.network',
-                                        '26-bridge.netdev', '26-bridge-mdb-master.network')
+        copy_network_unit('11-dummy.netdev', '26-bridge-mdb-slave.network',
+                          '26-bridge.netdev', '26-bridge-mdb-master.network')
         start_networkd()
         self.wait_online(['test1:enslaved', 'bridge99:degraded'])
 
@@ -3931,7 +3644,7 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'dev bridge99 port test1 grp 224.0.1.1 permanent *vid 4065')
 
         # Old kernel may not support bridge MDB entries on bridge master
-        if call('bridge mdb add dev bridge99 port bridge99 grp 224.0.1.3 temp vid 4068', stderr=subprocess.DEVNULL) == 0:
+        if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 224.0.1.3 temp vid 4068') == 0:
             self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066')
             self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067')
 
@@ -3941,7 +3654,7 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         check_output('ip link add dummy98 type dummy')
         check_output('ip link set dummy98 master bridge99')
 
-        copy_unit_to_networkd_unit_path('23-keep-master.network')
+        copy_network_unit('23-keep-master.network')
         start_networkd()
         self.wait_online(['dummy98:enslaved'])
 
@@ -3952,25 +3665,23 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
 
         output = check_output('bridge -d link show dummy98')
         print(output)
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'path_cost'), '400')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood'), '0')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost',            '400')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode',         '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood',        '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood',      '0')
         # CONFIG_BRIDGE_IGMP_SNOOPING=y
-        if os.path.exists('/sys/devices/virtual/net/bridge00/lower_dummy98/brport/multicast_to_unicast'):
-            self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast'), '1')
-        if os.path.exists('/sys/devices/virtual/net/bridge99/lower_dummy98/brport/neigh_suppress'):
-            self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'learning'), '0')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'priority'), '23')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'root_block'), '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True)
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress',       '1', allow_enoent=True)
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'learning',             '0')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'priority',             '23')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard',           '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block',           '1')
 
     def test_bridge_property(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev',
-                                        '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network',
-                                        '25-bridge99.network')
+        copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev',
+                          '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network',
+                          '25-bridge99.network')
         start_networkd()
         self.wait_online(['dummy98:enslaved', 'test1:enslaved', 'bridge99:routable'])
 
@@ -3990,29 +3701,25 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
 
         output = check_output('bridge -d link show dummy98')
         print(output)
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'path_cost'), '400')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'isolated'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood'), '0')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost',            '400')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode',         '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'isolated',             '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood',        '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood',      '0')
         # CONFIG_BRIDGE_IGMP_SNOOPING=y
-        if os.path.exists('/sys/devices/virtual/net/bridge00/lower_dummy98/brport/multicast_to_unicast'):
-            self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast'), '1')
-        if os.path.exists('/sys/devices/virtual/net/bridge99/lower_dummy98/brport/neigh_suppress'):
-            self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'learning'), '0')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'priority'), '23')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard'), '1')
-        self.assertEqual(read_bridge_port_attr('bridge99', 'dummy98', 'root_block'), '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True)
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress',       '1', allow_enoent=True)
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'learning',             '0')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'priority',             '23')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard',           '1')
+        self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block',           '1')
 
         output = check_output('bridge -d link show test1')
         print(output)
-        self.assertEqual(read_bridge_port_attr('bridge99', 'test1', 'priority'), '0')
+        self.check_bridge_port_attr('bridge99', 'test1', 'priority',               '0')
 
         check_output('ip address add 192.168.0.16/24 dev bridge99')
-        time.sleep(1)
-
         output = check_output('ip addr show bridge99')
         print(output)
         self.assertRegex(output, '192.168.0.16/24')
@@ -4023,11 +3730,11 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         print(output)
         self.assertRegex(output, 'ff00::/8 table local (proto kernel )?metric 256 (linkdown )?pref medium')
 
-        self.assertEqual(call('ip link del test1'), 0)
+        remove_link('test1')
 
         self.wait_operstate('bridge99', 'degraded-carrier')
 
-        check_output('ip link del dummy98')
+        remove_link('dummy98')
 
         self.wait_operstate('bridge99', 'no-carrier')
 
@@ -4043,8 +3750,8 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'ff00::/8 table local (proto kernel )?metric 256 (linkdown )?pref medium')
 
     def test_bridge_configure_without_carrier(self):
-        copy_unit_to_networkd_unit_path('26-bridge.netdev', '26-bridge-configure-without-carrier.network',
-                                        '11-dummy.netdev')
+        copy_network_unit('26-bridge.netdev', '26-bridge-configure-without-carrier.network',
+                          '11-dummy.netdev')
         start_networkd()
 
         # With ConfigureWithoutCarrier=yes, the bridge should remain configured for all these situations
@@ -4091,17 +3798,14 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
                 self.assertRegex(output, '10.1.2.1')
 
     def test_bridge_ignore_carrier_loss(self):
-        copy_unit_to_networkd_unit_path('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev',
-                                        '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network',
-                                        '25-bridge99-ignore-carrier-loss.network')
+        copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev',
+                          '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network',
+                          '25-bridge99-ignore-carrier-loss.network')
         start_networkd()
         self.wait_online(['dummy98:enslaved', 'test1:enslaved', 'bridge99:routable'])
 
         check_output('ip address add 192.168.0.16/24 dev bridge99')
-        time.sleep(1)
-
-        check_output('ip link del test1')
-        check_output('ip link del dummy98')
+        remove_link('test1', 'dummy98')
         time.sleep(3)
 
         output = check_output('ip address show bridge99')
@@ -4111,8 +3815,8 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'inet 192.168.0.16/24 scope global secondary bridge99')
 
     def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self):
-        copy_unit_to_networkd_unit_path('26-bridge.netdev', '26-bridge-slave-interface-1.network',
-                                        '25-bridge99-ignore-carrier-loss.network')
+        copy_network_unit('26-bridge.netdev', '26-bridge-slave-interface-1.network',
+                          '25-bridge99-ignore-carrier-loss.network')
         start_networkd()
         self.wait_online(['bridge99:no-carrier'])
 
@@ -4120,7 +3824,7 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
             check_output('ip link add dummy98 type dummy')
             check_output('ip link set dummy98 up')
             if trial < 3:
-                check_output('ip link del dummy98')
+                remove_link('dummy98')
 
         self.wait_online(['bridge99:routable', 'dummy98:enslaved'])
 
@@ -4133,34 +3837,24 @@ class NetworkdBridgeTests(unittest.TestCase, Utilities):
         self.assertIn('from all to 8.8.8.8 lookup 100', output)
 
 class NetworkdSRIOVTests(unittest.TestCase, Utilities):
-    units = [
-        '25-sriov-udev.network',
-        '25-sriov.link',
-        '25-sriov.network',
-    ]
 
     def setUp(self):
-        stop_networkd(show_logs=False)
-        call('rmmod netdevsim', stderr=subprocess.DEVNULL)
+        setup_common()
 
     def tearDown(self):
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
-        call('rmmod netdevsim', stderr=subprocess.DEVNULL)
+        tear_down_common()
 
     @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable()
     def test_sriov(self):
-        call('modprobe netdevsim', stderr=subprocess.DEVNULL)
+        call('modprobe netdevsim')
 
         with open('/sys/bus/netdevsim/new_device', mode='w', encoding='utf-8') as f:
             f.write('99 1')
 
-        call('udevadm settle')
-        call('udevadm info -w10s /sys/devices/netdevsim99/net/eni99np1', stderr=subprocess.DEVNULL)
-        with open('/sys/class/net/eni99np1/device/sriov_numvfs', mode='w', encoding='utf-8') as f:
+        with open('/sys/bus/netdevsim/devices/netdevsim99/sriov_numvfs', mode='w', encoding='utf-8') as f:
             f.write('3')
 
-        copy_unit_to_networkd_unit_path('25-sriov.network')
+        copy_network_unit('25-sriov.network')
         start_networkd()
         self.wait_online(['eni99np1:routable'])
 
@@ -4170,15 +3864,15 @@ class NetworkdSRIOVTests(unittest.TestCase, Utilities):
                          'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *'
                          'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *'
                          'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off'
-        )
+                         )
 
     @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable()
     def test_sriov_udev(self):
-        call('modprobe netdevsim', stderr=subprocess.DEVNULL)
-
-        copy_unit_to_networkd_unit_path('25-sriov.link', '25-sriov-udev.network')
+        copy_network_unit('25-sriov.link', '25-sriov-udev.network')
         call('udevadm control --reload')
 
+        call('modprobe netdevsim')
+
         with open('/sys/bus/netdevsim/new_device', mode='w', encoding='utf-8') as f:
             f.write('99 1')
 
@@ -4191,11 +3885,11 @@ class NetworkdSRIOVTests(unittest.TestCase, Utilities):
                          'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *'
                          'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *'
                          'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off'
-        )
+                         )
         self.assertNotIn('vf 3', output)
         self.assertNotIn('vf 4', output)
 
-        with open(os.path.join(network_unit_file_path, '25-sriov.link'), mode='a', encoding='utf-8') as f:
+        with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
             f.write('[Link]\nSR-IOVVirtualFunctions=4\n')
 
         call('udevadm control --reload')
@@ -4208,10 +3902,10 @@ class NetworkdSRIOVTests(unittest.TestCase, Utilities):
                          'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *'
                          'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *'
                          'vf 3'
-        )
+                         )
         self.assertNotIn('vf 4', output)
 
-        with open(os.path.join(network_unit_file_path, '25-sriov.link'), mode='a', encoding='utf-8') as f:
+        with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
             f.write('[Link]\nSR-IOVVirtualFunctions=\n')
 
         call('udevadm control --reload')
@@ -4224,10 +3918,10 @@ class NetworkdSRIOVTests(unittest.TestCase, Utilities):
                          'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *'
                          'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *'
                          'vf 3'
-        )
+                         )
         self.assertNotIn('vf 4', output)
 
-        with open(os.path.join(network_unit_file_path, '25-sriov.link'), mode='a', encoding='utf-8') as f:
+        with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
             f.write('[Link]\nSR-IOVVirtualFunctions=2\n')
 
         call('udevadm control --reload')
@@ -4238,12 +3932,12 @@ class NetworkdSRIOVTests(unittest.TestCase, Utilities):
         self.assertRegex(output,
                          'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *'
                          'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off'
-        )
+                         )
         self.assertNotIn('vf 2', output)
         self.assertNotIn('vf 3', output)
         self.assertNotIn('vf 4', output)
 
-        with open(os.path.join(network_unit_file_path, '25-sriov.link'), mode='a', encoding='utf-8') as f:
+        with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f:
             f.write('[Link]\nSR-IOVVirtualFunctions=\n')
 
         call('udevadm control --reload')
@@ -4255,29 +3949,20 @@ class NetworkdSRIOVTests(unittest.TestCase, Utilities):
                          'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *'
                          'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *'
                          'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off'
-        )
+                         )
         self.assertNotIn('vf 3', output)
         self.assertNotIn('vf 4', output)
 
 class NetworkdLLDPTests(unittest.TestCase, Utilities):
-    links = ['veth99']
-
-    units = [
-        '23-emit-lldp.network',
-        '24-lldp.network',
-        '25-veth.netdev']
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_lldp(self):
-        copy_unit_to_networkd_unit_path('23-emit-lldp.network', '24-lldp.network', '25-veth.netdev')
+        copy_network_unit('23-emit-lldp.network', '24-lldp.network', '25-veth.netdev')
         start_networkd()
         self.wait_online(['veth99:degraded', 'veth-peer:degraded'])
 
@@ -4293,27 +3978,15 @@ class NetworkdLLDPTests(unittest.TestCase, Utilities):
             self.fail()
 
 class NetworkdRATests(unittest.TestCase, Utilities):
-    links = ['veth99']
-
-    units = [
-        '25-veth.netdev',
-        '25-ipv6-prefix.network',
-        '25-ipv6-prefix-veth.network',
-        '25-ipv6-prefix-veth-token-static.network',
-        '25-ipv6-prefix-veth-token-prefixstable.network',
-        '25-ipv6-prefix-veth-token-prefixstable-without-address.network']
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_ipv6_prefix_delegation(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth.network')
+        copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:degraded'])
 
@@ -4331,7 +4004,7 @@ class NetworkdRATests(unittest.TestCase, Utilities):
         self.assertRegex(output, '2002:da8:1:0')
 
     def test_ipv6_token_static(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
+        copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:degraded'])
 
@@ -4343,7 +4016,7 @@ class NetworkdRATests(unittest.TestCase, Utilities):
         self.assertRegex(output, '2002:da8:2:0:fa:de:ca:fe')
 
     def test_ipv6_token_prefixstable(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network')
+        copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:degraded'])
 
@@ -4353,7 +4026,7 @@ class NetworkdRATests(unittest.TestCase, Utilities):
         self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc', output) # EUI64
 
     def test_ipv6_token_prefixstable_without_address(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable-without-address.network')
+        copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable-without-address.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:degraded'])
 
@@ -4363,35 +4036,15 @@ class NetworkdRATests(unittest.TestCase, Utilities):
         self.assertIn('2002:da8:2:0:f689:561a:8eda:7443', output)
 
 class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
-    links = [
-        'dummy98',
-        'veth99',
-    ]
-
-    units = [
-        '12-dummy.netdev',
-        '25-veth.netdev',
-        '25-dhcp-client.network',
-        '25-dhcp-client-static-lease.network',
-        '25-dhcp-client-timezone-router.network',
-        '25-dhcp-server.network',
-        '25-dhcp-server-downstream.network',
-        '25-dhcp-server-static-lease.network',
-        '25-dhcp-server-timezone-router.network',
-        '25-dhcp-server-uplink.network',
-    ]
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_dhcp_server(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
@@ -4403,8 +4056,8 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'NTP: 192.168.5.1\n *192.168.5.11')
 
     def test_dhcp_server_with_uplink(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network',
-                                        '12-dummy.netdev', '25-dhcp-server-uplink.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network',
+                          '12-dummy.netdev', '25-dhcp-server-uplink.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
@@ -4416,7 +4069,7 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
         self.assertIn('NTP: 192.168.5.1', output)
 
     def test_emit_router_timezone(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-client-timezone-router.network', '25-dhcp-server-timezone-router.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-client-timezone-router.network', '25-dhcp-server-timezone-router.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
@@ -4427,7 +4080,7 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
         self.assertIn('Time Zone: Europe/Berlin', output)
 
     def test_dhcp_server_static_lease(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-client-static-lease.network', '25-dhcp-server-static-lease.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-client-static-lease.network', '25-dhcp-server-static-lease.network')
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
@@ -4436,33 +4089,20 @@ class NetworkdDHCPServerTests(unittest.TestCase, Utilities):
         self.assertIn('Address: 10.1.1.200 (DHCP4 via 10.1.1.1)', output)
 
 class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
-    links = [
-        'client',
-        'server',
-        'client-peer',
-        'server-peer',
-        ]
-
-    units = [
-        '25-agent-veth-client.netdev',
-        '25-agent-veth-server.netdev',
-        '25-agent-client.network',
-        '25-agent-server.network',
-        '25-agent-client-peer.network',
-        '25-agent-server-peer.network',
-        ]
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_relay_agent(self):
-        copy_unit_to_networkd_unit_path(*self.units)
+        copy_network_unit('25-agent-veth-client.netdev',
+                          '25-agent-veth-server.netdev',
+                          '25-agent-client.network',
+                          '25-agent-server.network',
+                          '25-agent-client-peer.network',
+                          '25-agent-server-peer.network')
         start_networkd()
 
         self.wait_online(['client:routable'])
@@ -4472,44 +4112,15 @@ class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities):
         self.assertRegex(output, r'Address: 192.168.5.150 \(DHCP4 via 192.168.5.1\)')
 
 class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
-    links = [
-        'veth99',
-        'vrf99']
-
-    units = [
-        '25-veth.netdev',
-        '25-vrf.netdev',
-        '25-vrf.network',
-        '25-dhcp-client-anonymize.network',
-        '25-dhcp-client-gateway-onlink-implicit.network',
-        '25-dhcp-client-ipv4-only.network',
-        '25-dhcp-client-ipv4-use-routes-use-gateway.network',
-        '25-dhcp-client-ipv6-only.network',
-        '25-dhcp-client-keep-configuration-dhcp-on-stop.network',
-        '25-dhcp-client-keep-configuration-dhcp.network',
-        '25-dhcp-client-vrf.network',
-        '25-dhcp-client-with-ipv4ll.network',
-        '25-dhcp-client.network',
-        '25-dhcp-server-veth-peer.network',
-        '25-static.network']
 
     def setUp(self):
-        stop_dnsmasq()
-        remove_dnsmasq_lease_file()
-        remove_dnsmasq_log_file()
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        stop_dnsmasq()
-        remove_dnsmasq_lease_file()
-        remove_dnsmasq_log_file()
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_dhcp_client_ipv6_only(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network')
 
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
@@ -4533,13 +4144,14 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'token :: dev veth99')
 
     def test_dhcp_client_ipv4_only(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network')
 
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
-        start_dnsmasq(ipv4_range='192.168.5.110,192.168.5.119',
-                      additional_options='--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7 --dhcp-option=option:domain-search,example.com --dhcp-alternate-port=67,5555',
-                      lease_time='2m')
+        start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7',
+                      '--dhcp-option=option:domain-search,example.com',
+                      '--dhcp-alternate-port=67,5555',
+                      ipv4_range='192.168.5.110,192.168.5.119')
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
         print('## ip address show dev veth99 scope global')
@@ -4588,10 +4200,10 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
 
         # change address range, DNS servers, and Domains
         stop_dnsmasq()
-        remove_dnsmasq_log_file()
-        start_dnsmasq(ipv4_range='192.168.5.120,192.168.5.129',
-                      additional_options='--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8 --dhcp-option=option:domain-search,foo.example.com --dhcp-alternate-port=67,5555',
-                      lease_time='2m')
+        start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8',
+                      '--dhcp-option=option:domain-search,foo.example.com',
+                      '--dhcp-alternate-port=67,5555',
+                      ipv4_range='192.168.5.120,192.168.5.129',)
 
         # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120
         print('Wait for the DHCP lease to be expired')
@@ -4647,11 +4259,16 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertIn('26:mtu', output)
 
     def test_dhcp_client_ipv4_use_routes_gateway(self):
+        first = True
         for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4):
-            self.setUp()
+            if first:
+                first = False
+            else:
+                self.tearDown()
+
+            print(f'### test_dhcp_client_ipv4_use_routes_gateway(routes={routes}, gateway={gateway}, dns_and_ntp_routes={dns_and_ntp_routes}, classless={classless})')
             with self.subTest(routes=routes, gateway=gateway, dns_and_ntp_routes=dns_and_ntp_routes, classless=classless):
                 self._test_dhcp_client_ipv4_use_routes_gateway(routes, gateway, dns_and_ntp_routes, classless)
-            self.tearDown()
 
     def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns_and_ntp_routes, classless):
         testunit = '25-dhcp-client-ipv4-use-routes-use-gateway.network'
@@ -4659,14 +4276,20 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         testunits.append(f'{testunit}.d/use-routes-{use_routes}.conf')
         testunits.append(f'{testunit}.d/use-gateway-{use_gateway}.conf')
         testunits.append(f'{testunit}.d/use-dns-and-ntp-routes-{dns_and_ntp_routes}.conf')
-        copy_unit_to_networkd_unit_path(*testunits, dropins=False)
+        copy_network_unit(*testunits, copy_dropins=False)
 
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
-        additional_options = '--dhcp-option=option:dns-server,192.168.5.10,8.8.8.8 --dhcp-option=option:ntp-server,192.168.5.11,9.9.9.9 --dhcp-option=option:static-route,192.168.5.100,192.168.5.2,8.8.8.8,192.168.5.3'
+        additional_options = [
+            '--dhcp-option=option:dns-server,192.168.5.10,8.8.8.8',
+            '--dhcp-option=option:ntp-server,192.168.5.11,9.9.9.9',
+            '--dhcp-option=option:static-route,192.168.5.100,192.168.5.2,8.8.8.8,192.168.5.3'
+        ]
         if classless:
-            additional_options += ' --dhcp-option=option:classless-static-route,0.0.0.0/0,192.168.5.4,8.0.0.0/8,192.168.5.5'
-        start_dnsmasq(additional_options=additional_options, lease_time='2m')
+            additional_options += [
+                '--dhcp-option=option:classless-static-route,0.0.0.0/0,192.168.5.4,8.0.0.0/8,192.168.5.5'
+            ]
+        start_dnsmasq(*additional_options)
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
         output = check_output('ip -4 route show dev veth99')
@@ -4724,7 +4347,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
     def test_dhcp_client_settings_anonymize(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network')
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
         start_dnsmasq()
@@ -4738,12 +4361,12 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertNotIn('26:mtu', output)
 
     def test_dhcp_keep_configuration_dhcp(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev',
-                                        '25-dhcp-server-veth-peer.network',
-                                        '25-dhcp-client-keep-configuration-dhcp.network')
+        copy_network_unit('25-veth.netdev',
+                          '25-dhcp-server-veth-peer.network',
+                          '25-dhcp-client-keep-configuration-dhcp.network')
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
-        start_dnsmasq(lease_time='2m')
+        start_dnsmasq()
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
         output = check_output('ip address show dev veth99 scope global')
@@ -4772,7 +4395,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *'
                          'valid_lft forever preferred_lft forever')
 
-        with open(os.path.join(network_unit_file_path, '25-dhcp-client-keep-configuration-dhcp.network'), mode='a', encoding='utf-8') as f:
+        with open(os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dhcp.network'), mode='a', encoding='utf-8') as f:
             f.write('[Network]\nDHCP=no\n')
 
         start_networkd()
@@ -4785,12 +4408,12 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
                          'valid_lft forever preferred_lft forever')
 
     def test_dhcp_keep_configuration_dhcp_on_stop(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev',
-                                        '25-dhcp-server-veth-peer.network',
-                                        '25-dhcp-client-keep-configuration-dhcp-on-stop.network')
+        copy_network_unit('25-veth.netdev',
+                          '25-dhcp-server-veth-peer.network',
+                          '25-dhcp-client-keep-configuration-dhcp-on-stop.network')
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
-        start_dnsmasq(lease_time='2m')
+        start_dnsmasq()
         self.wait_online(['veth99:routable', 'veth-peer:routable'])
 
         output = check_output('ip address show dev veth99 scope global')
@@ -4804,7 +4427,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         print(output)
         self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99')
 
-        start_networkd(3)
+        start_networkd()
         self.wait_online(['veth-peer:routable'])
 
         output = check_output('ip address show dev veth99 scope global')
@@ -4812,7 +4435,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertNotIn('192.168.5.', output)
 
     def test_dhcp_client_reuse_address_as_static(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network')
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
         start_dnsmasq()
@@ -4828,9 +4451,9 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         static_network = '\n'.join(['[Match]', 'Name=veth99', '[Network]', 'IPv6AcceptRA=no', 'Address=' + ipv4_address, 'Address=' + ipv6_address])
         print(static_network)
 
-        remove_unit_from_networkd_path(['25-dhcp-client.network'])
+        remove_network_unit('25-dhcp-client.network')
 
-        with open(os.path.join(network_unit_file_path, '25-static.network'), mode='w', encoding='utf-8') as f:
+        with open(os.path.join(network_unit_dir, '25-static.network'), mode='w', encoding='utf-8') as f:
             f.write(static_network)
 
         restart_networkd()
@@ -4848,8 +4471,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
 
     @expectedFailureIfModuleIsNotAvailable('vrf')
     def test_dhcp_client_vrf(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-vrf.network',
-                                        '25-vrf.netdev', '25-vrf.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-vrf.network',
+                          '25-vrf.netdev', '25-vrf.network')
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
         start_dnsmasq()
@@ -4891,8 +4514,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertEqual(output, '')
 
     def test_dhcp_client_gateway_onlink_implicit(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network',
-                                        '25-dhcp-client-gateway-onlink-implicit.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network',
+                          '25-dhcp-client-gateway-onlink-implicit.network')
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
         start_dnsmasq()
@@ -4910,8 +4533,8 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'onlink')
 
     def test_dhcp_client_with_ipv4ll(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network',
-                                        '25-dhcp-client-with-ipv4ll.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network',
+                          '25-dhcp-client-with-ipv4ll.network')
         start_networkd()
         # we need to increase timeout above default, as this will need to wait for
         # systemd-networkd to get the dhcpv4 transient failure event
@@ -4922,7 +4545,7 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         self.assertNotIn('192.168.5.', output)
         self.assertRegex(output, r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link')
 
-        start_dnsmasq(lease_time='2m')
+        start_dnsmasq()
         print('Wait for a DHCP lease to be acquired and the IPv4LL address to be dropped')
         self.wait_address('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4')
         self.wait_address_dropped('veth99', r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4')
@@ -4946,18 +4569,15 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
 
     def test_dhcp_client_use_dns(self):
         def check(self, ipv4, ipv6):
-            os.makedirs(os.path.join(network_unit_file_path, '25-dhcp-client.network.d'), exist_ok=True)
-            with open(os.path.join(network_unit_file_path, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f:
+            os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)
+            with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f:
                 f.write('[DHCPv4]\nUseDNS=')
                 f.write('yes' if ipv4 else 'no')
                 f.write('\n[DHCPv6]\nUseDNS=')
                 f.write('yes' if ipv6 else 'no')
                 f.write('\n[IPv6AcceptRA]\nUseDNS=no')
 
-            check_output(*networkctl_cmd, 'reload', env=env)
-            # 'networkctl reload' asynchronously reconfigure links.
-            # Hence, we need to wait for a short time for the link to be in configuring state.
-            time.sleep(1)
+            networkctl_reload()
             self.wait_online(['veth99:routable'])
 
             # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses.
@@ -4981,11 +4601,12 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
             # TODO: check json string
             check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', dropins=False)
+        copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
 
         start_networkd()
         self.wait_online(['veth-peer:carrier'])
-        start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1 --dhcp-option=option6:dns-server,[2600::1]')
+        start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1',
+                      '--dhcp-option=option6:dns-server,[2600::1]')
 
         check(self, True, True)
         check(self, True, False)
@@ -4993,63 +4614,25 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
         check(self, False, False)
 
 class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
-    links = [
-        'dummy97',
-        'dummy98',
-        'dummy99',
-        'test1',
-        'veth97',
-        'veth98',
-        'veth99',
-    ]
-
-    units = [
-        '11-dummy.netdev',
-        '12-dummy.netdev',
-        '13-dummy.netdev',
-        '25-veth.netdev',
-        '25-veth-downstream-veth97.netdev',
-        '25-veth-downstream-veth98.netdev',
-        '80-6rd-tunnel.network',
-        '25-dhcp-pd-downstream-dummy97.network',
-        '25-dhcp-pd-downstream-dummy98.network',
-        '25-dhcp-pd-downstream-dummy99.network',
-        '25-dhcp-pd-downstream-test1.network',
-        '25-dhcp-pd-downstream-veth97.network',
-        '25-dhcp-pd-downstream-veth97-peer.network',
-        '25-dhcp-pd-downstream-veth98.network',
-        '25-dhcp-pd-downstream-veth98-peer.network',
-        '25-dhcp4-6rd-server.network',
-        '25-dhcp4-6rd-upstream.network',
-        '25-dhcp6pd-server.network',
-        '25-dhcp6pd-upstream.network',
-    ]
 
     def setUp(self):
-        stop_isc_dhcpd()
-        stop_dnsmasq()
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        stop_isc_dhcpd()
-        stop_dnsmasq()
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_dhcp6pd(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream.network',
-                                        '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network',
-                                        '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network',
-                                        '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network',
-                                        '25-dhcp-pd-downstream-dummy97.network',
-                                        '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network',
-                                        '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream.network',
+                          '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network',
+                          '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network',
+                          '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network',
+                          '25-dhcp-pd-downstream-dummy97.network',
+                          '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network',
+                          '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network')
 
         start_networkd()
         self.wait_online(['veth-peer:routable'])
-        start_isc_dhcpd('veth-peer', 'isc-dhcpd-dhcp6pd.conf', ip='-6')
+        start_isc_dhcpd(conf_file='isc-dhcpd-dhcp6pd.conf', ipv='-6')
         self.wait_online(['veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded',
                           'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable'])
 
@@ -5205,7 +4788,7 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         self.assertRegex(output, '3ffe:501:ffff:[2-9a-f]01::/64 proto kernel metric [0-9]* expires')
 
         # Test case for reconfigure
-        check_output(*networkctl_cmd, 'reconfigure', 'dummy98', 'dummy99', env=env)
+        networkctl_reconfigure('dummy98', 'dummy99')
         self.wait_online(['dummy98:routable'])
 
         print('### ip -6 address show dev dummy98 scope global')
@@ -5412,14 +4995,14 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         self.assertIn(f'via ::10.0.0.1 dev {tunnel_name}', output)
 
     def test_dhcp4_6rd(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-dhcp4-6rd-server.network', '25-dhcp4-6rd-upstream.network',
-                                        '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network',
-                                        '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network',
-                                        '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network',
-                                        '25-dhcp-pd-downstream-dummy97.network',
-                                        '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network',
-                                        '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network',
-                                        '80-6rd-tunnel.network')
+        copy_network_unit('25-veth.netdev', '25-dhcp4-6rd-server.network', '25-dhcp4-6rd-upstream.network',
+                          '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network',
+                          '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network',
+                          '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network',
+                          '25-dhcp-pd-downstream-dummy97.network',
+                          '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network',
+                          '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network',
+                          '80-6rd-tunnel.network')
 
         start_networkd()
         self.wait_online(['veth-peer:routable'])
@@ -5428,7 +5011,9 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         # 6rd-prefix: 2001:db8::/32
         # br-addresss: 10.0.0.1
 
-        start_dnsmasq(additional_options='--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', ipv4_range='10.100.100.100,10.100.100.200', ipv4_router='10.0.0.1', lease_time='2m')
+        start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01',
+                      ipv4_range='10.100.100.100,10.100.100.200',
+                      ipv4_router='10.0.0.1')
         self.wait_online(['veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded',
                           'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable'])
 
@@ -5448,7 +5033,7 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         self.verify_dhcp4_6rd(tunnel_name)
 
         # Test case for reconfigure
-        check_output(*networkctl_cmd, 'reconfigure', 'dummy98', 'dummy99', env=env)
+        networkctl_reconfigure('dummy98', 'dummy99')
         self.wait_online(['dummy98:routable', 'dummy99:degraded'])
 
         self.verify_dhcp4_6rd(tunnel_name)
@@ -5462,32 +5047,16 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         self.verify_dhcp4_6rd(tunnel_name)
 
 class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
-    links = [
-        'dummy98',
-        'veth99',
-    ]
-
-    units = [
-        '12-dummy.netdev',
-        '25-veth.netdev',
-        '25-ipv6ra-prefix-client-deny-list.network',
-        '25-ipv6ra-prefix-client.network',
-        '25-ipv6ra-prefix.network',
-        '25-ipv6ra-uplink.network',
-    ]
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def test_ipv6_route_prefix(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-ipv6ra-prefix-client.network', '25-ipv6ra-prefix.network',
-                                        '12-dummy.netdev', '25-ipv6ra-uplink.network')
+        copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client.network', '25-ipv6ra-prefix.network',
+                          '12-dummy.netdev', '25-ipv6ra-uplink.network')
 
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable'])
@@ -5526,8 +5095,8 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
         check_output(*networkctl_cmd, '--json=short', 'status', env=env)
 
     def test_ipv6_route_prefix_deny_list(self):
-        copy_unit_to_networkd_unit_path('25-veth.netdev', '25-ipv6ra-prefix-client-deny-list.network', '25-ipv6ra-prefix.network',
-                                        '12-dummy.netdev', '25-ipv6ra-uplink.network')
+        copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client-deny-list.network', '25-ipv6ra-prefix.network',
+                          '12-dummy.netdev', '25-ipv6ra-uplink.network')
 
         start_networkd()
         self.wait_online(['veth99:routable', 'veth-peer:routable', 'dummy98:routable'])
@@ -5558,23 +5127,12 @@ class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities):
         self.assertIn('example.com', output)
 
 class NetworkdMTUTests(unittest.TestCase, Utilities):
-    links = ['dummy98']
-
-    units = [
-        '12-dummy.netdev',
-        '12-dummy-mtu.netdev',
-        '12-dummy-mtu.link',
-        '12-dummy.network',
-        ]
 
     def setUp(self):
-        remove_links(self.links)
-        stop_networkd(show_logs=False)
+        setup_common()
 
     def tearDown(self):
-        remove_links(self.links)
-        remove_unit_from_networkd_path(self.units)
-        stop_networkd(show_logs=True)
+        tear_down_common()
 
     def check_mtu(self, mtu, ipv6_mtu=None, reset=True):
         if not ipv6_mtu:
@@ -5583,14 +5141,14 @@ class NetworkdMTUTests(unittest.TestCase, Utilities):
         # test normal start
         start_networkd()
         self.wait_online(['dummy98:routable'])
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'mtu'), ipv6_mtu)
-        self.assertEqual(read_link_attr('dummy98', 'mtu'), mtu)
+        self.check_link_attr('dummy98', 'mtu', mtu)
+        self.check_ipv6_sysctl_attr('dummy98', 'mtu', ipv6_mtu)
 
         # test normal restart
         restart_networkd()
         self.wait_online(['dummy98:routable'])
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'mtu'), ipv6_mtu)
-        self.assertEqual(read_link_attr('dummy98', 'mtu'), mtu)
+        self.check_link_attr('dummy98', 'mtu', mtu)
+        self.check_ipv6_sysctl_attr('dummy98', 'mtu', ipv6_mtu)
 
         if reset:
             self.reset_check_mtu(mtu, ipv6_mtu)
@@ -5600,24 +5158,24 @@ class NetworkdMTUTests(unittest.TestCase, Utilities):
         stop_networkd()
 
         # note - changing the device mtu resets the ipv6 mtu
-        run('ip link set up mtu 1501 dev dummy98')
-        run('ip link set up mtu 1500 dev dummy98')
-        self.assertEqual(read_link_attr('dummy98', 'mtu'), '1500')
-        self.assertEqual(read_ipv6_sysctl_attr('dummy98', 'mtu'), '1500')
+        check_output('ip link set up mtu 1501 dev dummy98')
+        check_output('ip link set up mtu 1500 dev dummy98')
+        self.check_link_attr('dummy98', 'mtu', '1500')
+        self.check_ipv6_sysctl_attr('dummy98', 'mtu', '1500')
 
         self.check_mtu(mtu, ipv6_mtu, reset=False)
 
     def test_mtu_network(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '12-dummy.network.d/mtu.conf')
+        copy_network_unit('12-dummy.netdev', '12-dummy.network.d/mtu.conf')
         self.check_mtu('1600')
 
     def test_mtu_netdev(self):
-        copy_unit_to_networkd_unit_path('12-dummy-mtu.netdev', '12-dummy.network', dropins=False)
+        copy_network_unit('12-dummy-mtu.netdev', '12-dummy.network', copy_dropins=False)
         # note - MTU set by .netdev happens ONLY at device creation!
         self.check_mtu('1600', reset=False)
 
     def test_mtu_link(self):
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '12-dummy-mtu.link', '12-dummy.network', dropins=False)
+        copy_network_unit('12-dummy.netdev', '12-dummy-mtu.link', '12-dummy.network', copy_dropins=False)
         # must reload udev because it only picks up new files after 3 second delay
         call('udevadm control --reload')
         # note - MTU set by .link happens ONLY at udev processing of device 'add' uevent!
@@ -5625,27 +5183,27 @@ class NetworkdMTUTests(unittest.TestCase, Utilities):
 
     def test_ipv6_mtu(self):
         ''' set ipv6 mtu without setting device mtu '''
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1400.conf')
+        copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1400.conf')
         self.check_mtu('1500', '1400')
 
     def test_ipv6_mtu_toolarge(self):
         ''' try set ipv6 mtu over device mtu (it shouldn't work) '''
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf')
+        copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf')
         self.check_mtu('1500', '1500')
 
     def test_mtu_network_ipv6_mtu(self):
         ''' set ipv6 mtu and set device mtu via network file '''
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '12-dummy.network.d/mtu.conf', '12-dummy.network.d/ipv6-mtu-1550.conf')
+        copy_network_unit('12-dummy.netdev', '12-dummy.network.d/mtu.conf', '12-dummy.network.d/ipv6-mtu-1550.conf')
         self.check_mtu('1600', '1550')
 
     def test_mtu_netdev_ipv6_mtu(self):
         ''' set ipv6 mtu and set device mtu via netdev file '''
-        copy_unit_to_networkd_unit_path('12-dummy-mtu.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf')
+        copy_network_unit('12-dummy-mtu.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf')
         self.check_mtu('1600', '1550', reset=False)
 
     def test_mtu_link_ipv6_mtu(self):
         ''' set ipv6 mtu and set device mtu via link file '''
-        copy_unit_to_networkd_unit_path('12-dummy.netdev', '12-dummy-mtu.link', '12-dummy.network.d/ipv6-mtu-1550.conf')
+        copy_network_unit('12-dummy.netdev', '12-dummy-mtu.link', '12-dummy.network.d/ipv6-mtu-1550.conf')
         # must reload udev because it only picks up new files after 3 second delay
         call('udevadm control --reload')
         self.check_mtu('1600', '1550', reset=False)
@@ -5714,15 +5272,15 @@ if __name__ == '__main__':
         wait_online_cmd = [wait_online_bin]
 
     if asan_options:
-        env.update({ 'ASAN_OPTIONS' : asan_options })
+        env.update({'ASAN_OPTIONS': asan_options})
     if lsan_options:
-        env.update({ 'LSAN_OPTIONS' : lsan_options })
+        env.update({'LSAN_OPTIONS': lsan_options})
     if ubsan_options:
-        env.update({ 'UBSAN_OPTIONS' : ubsan_options })
+        env.update({'UBSAN_OPTIONS': ubsan_options})
 
     wait_online_env = env.copy()
     if enable_debug:
-        wait_online_env.update({ 'SYSTEMD_LOG_LEVEL' : 'debug' })
+        wait_online_env.update({'SYSTEMD_LOG_LEVEL': 'debug'})
 
     sys.argv[1:] = unknown_args
     unittest.main(verbosity=3)