test: add a mock ModemManager for basic coverage of sd-networkd's integration

Just the minimal setup and test case required to cover
https://github.com/systemd/systemd/issues/41389 for now, can be
expanded in the future

Boring boilerplate is bot-made, don't @ me

Co-developed-by: Claude Opus 4.6 noreply@anthropic.com
This commit is contained in:
Luca Boccassi
2026-03-28 15:19:21 +00:00
parent e0ab84e21b
commit abe3d570f8
6 changed files with 595 additions and 0 deletions
@@ -23,6 +23,7 @@ foreach testcase : [
'NetworkdIPv6PrefixTests',
'NetworkdMTUTests',
'NetworkdSysctlTest',
'NetworkdWWANTests',
]
integration_tests += [
integration_test_template + {
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
[Match]
Name=dummy98
[Network]
LLDP=no
LinkLocalAddressing=no
IPv6AcceptRA=no
[MobileNetwork]
APN=internet.test
IPFamily=both
@@ -0,0 +1,13 @@
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<!-- Allow root to own and interact with the mock ModemManager service -->
<policy user="root">
<allow own="org.freedesktop.ModemManager1"/>
<allow send_destination="org.freedesktop.ModemManager1"/>
</policy>
<policy context="default">
<allow send_destination="org.freedesktop.ModemManager1"/>
</policy>
</busconfig>
@@ -73,6 +73,7 @@ resolvectl_bin = shutil.which('resolvectl', path=which_paths)
timedatectl_bin = shutil.which('timedatectl', path=which_paths)
udevadm_bin = shutil.which('udevadm', path=which_paths)
test_ndisc_send = None
test_modem_manager_mock = None
build_dir = None
source_dir = None
@@ -973,6 +974,28 @@ def start_radvd(*additional_options, config_file):
def stop_radvd():
stop_by_pid_file(radvd_pid_file)
def start_modem_manager_mock(*additional_options):
dbus_policy_src = os.path.join(networkd_ci_temp_dir, 'mock-modem-manager.conf')
cp(dbus_policy_src, '/etc/dbus-1/system.d/mock-modem-manager.conf')
check_output('systemctl reload dbus.service')
command = ' '.join([test_modem_manager_mock] + list(additional_options))
with open('/run/systemd/system/test-modem-manager-mock.service', mode='w', encoding='utf-8') as f:
f.write('[Unit]\n'
'Description=Mock ModemManager for networkd testing\n'
'[Service]\n'
'Type=notify\n'
'BusName=org.freedesktop.ModemManager1\n'
f'ExecStart={command}\n')
check_output('systemctl daemon-reload')
check_output('systemctl start test-modem-manager-mock.service')
def stop_modem_manager_mock():
call('systemctl stop test-modem-manager-mock.service')
rm_f('/run/systemd/system/test-modem-manager-mock.service')
call('systemctl daemon-reload')
rm_f('/etc/dbus-1/system.d/mock-modem-manager.conf')
def radvd_check_config(config_file):
if not shutil.which('radvd'):
print('radvd is not installed, assuming the config check failed')
@@ -1099,6 +1122,7 @@ def tear_down_common():
stop_dnsmasq()
stop_isc_dhcpd()
stop_radvd()
stop_modem_manager_mock()
# 2. remove modules
call_quiet('rmmod netdevsim')
@@ -9567,6 +9591,54 @@ class NetworkdSysctlTest(unittest.TestCase, Utilities):
self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/max_addresses'", log)
self.assertNotIn("Sysctl monitor BPF returned error", log)
class NetworkdWWANTests(unittest.TestCase, Utilities):
def setUp(self):
setup_common()
def tearDown(self):
tear_down_common()
def test_wwan_ipv4v6_static(self):
"""Test WWAN bearer with both IPv4 and IPv6 static configuration.
Regression test for https://github.com/systemd/systemd/issues/41389
"""
if not os.path.exists(test_modem_manager_mock):
self.skipTest(f'{test_modem_manager_mock} does not exist.')
copy_network_unit('12-dummy.netdev', '25-wwan-ipv4v6.network')
try:
start_modem_manager_mock(
'--ifname', 'dummy98',
'--ipv4-address', '100.120.244.160',
'--ipv4-gateway', '100.120.244.161',
'--ipv4-prefix', '26',
'--ipv6-address', '2001:db8::1',
'--ipv6-gateway', '2001:db8::2',
'--ipv6-prefix', '64',
)
except (subprocess.CalledProcessError, PermissionError, OSError) as e:
self.skipTest(f'Failed to start mock ModemManager: {e}')
start_networkd()
self.wait_online('dummy98:routable')
output = check_output('ip -4 address show dev dummy98')
print(output)
self.assertIn('100.120.244.160/26', output)
output = check_output('ip -6 address show dev dummy98')
print(output)
self.assertIn('2001:db8::1/64', output)
output = check_output('ip -4 route show dev dummy98')
print(output)
self.assertIn('default via 100.120.244.161', output)
output = check_output('ip -6 route show dev dummy98')
print(output)
self.assertIn('default via 2001:db8::2', output)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--build-dir', help='Path to build dir', dest='build_dir')
@@ -9626,6 +9698,11 @@ if __name__ == '__main__':
else:
test_ndisc_send = '/usr/lib/tests/test-ndisc-send'
if build_dir:
test_modem_manager_mock = os.path.normpath(os.path.join(build_dir, 'test-modem-manager-mock'))
else:
test_modem_manager_mock = '/usr/lib/systemd/tests/unit-tests/manual/test-modem-manager-mock'
if asan_options:
env.update({'ASAN_OPTIONS': asan_options})
if lsan_options: