]> git.ipfire.org Git - thirdparty/systemd.git/blame - test/networkd-test.py
cryptsetup-generator: allow overriding crypttab path with $SYSTEMD_CRYPTAB
[thirdparty/systemd.git] / test / networkd-test.py
CommitLineData
4ddb85b1 1#!/usr/bin/env python3
35df7443 2# SPDX-License-Identifier: LGPL-2.1+
4ddb85b1
MP
3#
4# networkd integration test
5# This uses temporary configuration in /run and temporary veth devices, and
6# does not write anything on disk or change any system configuration;
7# but it assumes (and checks at the beginning) that networkd is not currently
8# running.
daad34df
MP
9#
10# This can be run on a normal installation, in QEMU, nspawn (with
11# --private-network), LXD (with "--config raw.lxc=lxc.aa_profile=unconfined"),
12# or LXC system containers. You need at least the "ip" tool from the iproute
13# package; it is recommended to install dnsmasq too to get full test coverage.
14#
4ddb85b1
MP
15# ATTENTION: This uses the *installed* networkd, not the one from the built
16# source tree.
17#
810adae9 18# © 2015 Canonical Ltd.
4ddb85b1 19# Author: Martin Pitt <martin.pitt@ubuntu.com>
4ddb85b1 20
ec89276c 21import errno
4ddb85b1 22import os
278391c2
BOT
23import shutil
24import socket
25import subprocess
4ddb85b1 26import sys
278391c2 27import tempfile
4ddb85b1
MP
28import time
29import unittest
4ddb85b1 30
ec89276c 31HAVE_DNSMASQ = shutil.which('dnsmasq') is not None
09b8826e 32IS_CONTAINER = subprocess.call(['systemd-detect-virt', '--quiet', '--container']) == 0
ec89276c
DM
33
34NETWORK_UNITDIR = '/run/systemd/network'
35
36NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online',
37 path='/usr/lib/systemd:/lib/systemd')
4ddb85b1 38
30b42a9a
MP
39RESOLV_CONF = '/run/systemd/resolve/resolv.conf'
40
c4a0a2d5
MP
41tmpmounts = []
42running_units = []
43stopped_units = []
44
4ddb85b1 45
ec89276c 46def setUpModule():
c4a0a2d5
MP
47 global tmpmounts
48
ec89276c
DM
49 """Initialize the environment, and perform sanity checks on it."""
50 if NETWORKD_WAIT_ONLINE is None:
51 raise OSError(errno.ENOENT, 'systemd-networkd-wait-online not found')
52
c4a0a2d5
MP
53 # Do not run any tests if the system is using networkd already and it's not virtualized
54 if (subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0 and
55 subprocess.call(['systemd-detect-virt', '--quiet']) != 0):
56 raise unittest.SkipTest('not virtualized and networkd is already active')
ff750729 57
c4a0a2d5
MP
58 # Ensure we don't mess with an existing networkd config
59 for u in ['systemd-networkd.socket', 'systemd-networkd', 'systemd-resolved']:
60 if subprocess.call(['systemctl', 'is-active', '--quiet', u]) == 0:
61 subprocess.call(['systemctl', 'stop', u])
62 running_units.append(u)
63 else:
64 stopped_units.append(u)
ff750729
LP
65
66 # create static systemd-network user for networkd-test-router.service (it
67 # needs to do some stuff as root and can't start as user; but networkd
68 # still insists on the user)
2de705cd
FS
69 if subprocess.call(['getent', 'passwd', 'systemd-network']) != 0:
70 subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network'])
ff750729 71
c4a0a2d5
MP
72 for d in ['/etc/systemd/network', '/run/systemd/network',
73 '/run/systemd/netif', '/run/systemd/resolve']:
74 if os.path.isdir(d):
75 subprocess.check_call(["mount", "-t", "tmpfs", "none", d])
76 tmpmounts.append(d)
77 if os.path.isdir('/run/systemd/resolve'):
78 os.chmod('/run/systemd/resolve', 0o755)
62fb7e80 79 shutil.chown('/run/systemd/resolve', 'systemd-resolve', 'systemd-resolve')
ff750729
LP
80 if os.path.isdir('/run/systemd/netif'):
81 os.chmod('/run/systemd/netif', 0o755)
82 shutil.chown('/run/systemd/netif', 'systemd-network', 'systemd-network')
ec89276c
DM
83
84 # Avoid "Failed to open /dev/tty" errors in containers.
85 os.environ['SYSTEMD_LOG_TARGET'] = 'journal'
86
87 # Ensure the unit directory exists so tests can dump files into it.
88 os.makedirs(NETWORK_UNITDIR, exist_ok=True)
89
90
c4a0a2d5
MP
91def tearDownModule():
92 global tmpmounts
93 for d in tmpmounts:
94 subprocess.check_call(["umount", d])
95 for u in stopped_units:
96 subprocess.call(["systemctl", "stop", u])
97 for u in running_units:
98 subprocess.call(["systemctl", "restart", u])
99
100
ec89276c
DM
101class NetworkdTestingUtilities:
102 """Provide a set of utility functions to facilitate networkd tests.
103
104 This class must be inherited along with unittest.TestCase to define
105 some required methods.
106 """
107
618b196e
DM
108 def add_veth_pair(self, veth, peer, veth_options=(), peer_options=()):
109 """Add a veth interface pair, and queue them to be removed."""
110 subprocess.check_call(['ip', 'link', 'add', 'name', veth] +
111 list(veth_options) +
112 ['type', 'veth', 'peer', 'name', peer] +
113 list(peer_options))
114 self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', peer])
115
fef740ae
LP
116 def write_config(self, path, contents):
117 """"Write a configuration file, and queue it to be removed."""
118
119 with open(path, 'w') as f:
120 f.write(contents)
121
122 self.addCleanup(os.remove, path)
123
ec89276c
DM
124 def write_network(self, unit_name, contents):
125 """Write a network unit file, and queue it to be removed."""
fef740ae 126 self.write_config(os.path.join(NETWORK_UNITDIR, unit_name), contents)
ec89276c
DM
127
128 def write_network_dropin(self, unit_name, dropin_name, contents):
129 """Write a network unit drop-in, and queue it to be removed."""
278391c2
BOT
130 dropin_dir = os.path.join(NETWORK_UNITDIR, "{}.d".format(unit_name))
131 dropin_path = os.path.join(dropin_dir, "{}.conf".format(dropin_name))
ec89276c
DM
132
133 os.makedirs(dropin_dir, exist_ok=True)
b56be296 134 self.addCleanup(os.rmdir, dropin_dir)
ec89276c
DM
135 with open(dropin_path, 'w') as dropin:
136 dropin.write(contents)
137 self.addCleanup(os.remove, dropin_path)
138
b56be296
DJL
139 def read_attr(self, link, attribute):
140 """Read a link attributed from the sysfs."""
141 # Note we we don't want to check if interface `link' is managed, we
142 # want to evaluate link variable and pass the value of the link to
143 # assert_link_states e.g. eth0=managed.
144 self.assert_link_states(**{link:'managed'})
145 with open(os.path.join('/sys/class/net', link, attribute)) as f:
146 return f.readline().strip()
147
a09dc546
DM
148 def assert_link_states(self, **kwargs):
149 """Match networkctl link states to the given ones.
150
151 Each keyword argument should be the name of a network interface
152 with its expected value of the "SETUP" column in output from
153 networkctl. The interfaces have five seconds to come online
154 before the check is performed. Every specified interface must
155 be present in the output, and any other interfaces found in the
156 output are ignored.
157
158 A special interface state "managed" is supported, which matches
159 any value in the "SETUP" column other than "unmanaged".
160 """
161 if not kwargs:
162 return
163 interfaces = set(kwargs)
164
165 # Wait for the requested interfaces, but don't fail for them.
166 subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] +
278391c2 167 ['--interface={}'.format(iface) for iface in kwargs])
a09dc546
DM
168
169 # Validate each link state found in the networkctl output.
170 out = subprocess.check_output(['networkctl', '--no-legend']).rstrip()
171 for line in out.decode('utf-8').split('\n'):
172 fields = line.split()
173 if len(fields) >= 5 and fields[1] in kwargs:
174 iface = fields[1]
175 expected = kwargs[iface]
176 actual = fields[-1]
177 if (actual != expected and
178 not (expected == 'managed' and actual != 'unmanaged')):
278391c2 179 self.fail("Link {} expects state {}, found {}".format(iface, expected, actual))
a09dc546
DM
180 interfaces.remove(iface)
181
182 # Ensure that all requested interfaces have been covered.
183 if interfaces:
278391c2 184 self.fail("Missing links in status output: {}".format(interfaces))
a09dc546 185
ec89276c 186
b56be296
DJL
187class BridgeTest(NetworkdTestingUtilities, unittest.TestCase):
188 """Provide common methods for testing networkd against servers."""
189
190 def setUp(self):
191 self.write_network('port1.netdev', '''\
192[NetDev]
193Name=port1
194Kind=dummy
195MACAddress=12:34:56:78:9a:bc''')
196 self.write_network('port2.netdev', '''\
197[NetDev]
198Name=port2
199Kind=dummy
200MACAddress=12:34:56:78:9a:bd''')
201 self.write_network('mybridge.netdev', '''\
202[NetDev]
203Name=mybridge
204Kind=bridge''')
205 self.write_network('port1.network', '''\
206[Match]
207Name=port1
208[Network]
209Bridge=mybridge''')
210 self.write_network('port2.network', '''\
211[Match]
212Name=port2
213[Network]
214Bridge=mybridge''')
215 self.write_network('mybridge.network', '''\
216[Match]
217Name=mybridge
218[Network]
219DNS=192.168.250.1
220Address=192.168.250.33/24
221Gateway=192.168.250.1''')
207f5f4d 222 subprocess.call(['systemctl', 'reset-failed', 'systemd-networkd', 'systemd-resolved'])
b56be296
DJL
223 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
224
225 def tearDown(self):
226 subprocess.check_call(['systemctl', 'stop', 'systemd-networkd'])
227 subprocess.check_call(['ip', 'link', 'del', 'mybridge'])
228 subprocess.check_call(['ip', 'link', 'del', 'port1'])
229 subprocess.check_call(['ip', 'link', 'del', 'port2'])
230
231 def test_bridge_init(self):
232 self.assert_link_states(
233 port1='managed',
234 port2='managed',
235 mybridge='managed')
236
237 def test_bridge_port_priority(self):
238 self.assertEqual(self.read_attr('port1', 'brport/priority'), '32')
239 self.write_network_dropin('port1.network', 'priority', '''\
240[Bridge]
241Priority=28
242''')
243 subprocess.check_call(['systemctl', 'restart', 'systemd-networkd'])
244 self.assertEqual(self.read_attr('port1', 'brport/priority'), '28')
245
246 def test_bridge_port_priority_set_zero(self):
247 """It should be possible to set the bridge port priority to 0"""
248 self.assertEqual(self.read_attr('port2', 'brport/priority'), '32')
249 self.write_network_dropin('port2.network', 'priority', '''\
250[Bridge]
251Priority=0
252''')
253 subprocess.check_call(['systemctl', 'restart', 'systemd-networkd'])
254 self.assertEqual(self.read_attr('port2', 'brport/priority'), '0')
255
4319c181
SS
256 def test_bridge_port_property(self):
257 """Test the "[Bridge]" section keys"""
258 self.assertEqual(self.read_attr('port2', 'brport/priority'), '32')
259 self.write_network_dropin('port2.network', 'property', '''\
260[Bridge]
261UnicastFlood=true
262HairPin=true
263UseBPDU=true
264FastLeave=true
265AllowPortToBeRoot=true
266Cost=555
267Priority=23
268''')
269 subprocess.check_call(['systemctl', 'restart', 'systemd-networkd'])
270
271 self.assertEqual(self.read_attr('port2', 'brport/priority'), '23')
272 self.assertEqual(self.read_attr('port2', 'brport/hairpin_mode'), '1')
273 self.assertEqual(self.read_attr('port2', 'brport/path_cost'), '555')
274 self.assertEqual(self.read_attr('port2', 'brport/multicast_fast_leave'), '1')
275 self.assertEqual(self.read_attr('port2', 'brport/unicast_flood'), '1')
276 self.assertEqual(self.read_attr('port2', 'brport/bpdu_guard'), '1')
277 self.assertEqual(self.read_attr('port2', 'brport/root_block'), '1')
278
ec89276c
DM
279class ClientTestBase(NetworkdTestingUtilities):
280 """Provide common methods for testing networkd against servers."""
281
fd0cec03
MP
282 @classmethod
283 def setUpClass(klass):
284 klass.orig_log_level = subprocess.check_output(
285 ['systemctl', 'show', '--value', '--property', 'LogLevel'],
286 universal_newlines=True).strip()
6c34ed51 287 subprocess.check_call(['systemd-analyze', 'log-level', 'debug'])
fd0cec03
MP
288
289 @classmethod
290 def tearDownClass(klass):
6c34ed51 291 subprocess.check_call(['systemd-analyze', 'log-level', klass.orig_log_level])
fd0cec03 292
4ddb85b1
MP
293 def setUp(self):
294 self.iface = 'test_eth42'
295 self.if_router = 'router_eth42'
296 self.workdir_obj = tempfile.TemporaryDirectory()
297 self.workdir = self.workdir_obj.name
ec89276c 298 self.config = 'test_eth42.network'
4ddb85b1
MP
299
300 # get current journal cursor
fd0cec03 301 subprocess.check_output(['journalctl', '--sync'])
4ddb85b1
MP
302 out = subprocess.check_output(['journalctl', '-b', '--quiet',
303 '--no-pager', '-n0', '--show-cursor'],
304 universal_newlines=True)
305 self.assertTrue(out.startswith('-- cursor:'))
306 self.journal_cursor = out.split()[-1]
307
207f5f4d 308 subprocess.call(['systemctl', 'reset-failed', 'systemd-networkd', 'systemd-resolved'])
c44c1b8a 309
4ddb85b1
MP
310 def tearDown(self):
311 self.shutdown_iface()
4ddb85b1 312 subprocess.call(['systemctl', 'stop', 'systemd-networkd'])
9e0c296a
MP
313 subprocess.call(['ip', 'link', 'del', 'dummy0'],
314 stderr=subprocess.DEVNULL)
4ddb85b1
MP
315
316 def show_journal(self, unit):
317 '''Show journal of given unit since start of the test'''
318
278391c2 319 print('---- {} ----'.format(unit))
fd0cec03 320 subprocess.check_output(['journalctl', '--sync'])
4ddb85b1
MP
321 sys.stdout.flush()
322 subprocess.call(['journalctl', '-b', '--no-pager', '--quiet',
323 '--cursor', self.journal_cursor, '-u', unit])
324
325 def create_iface(self, ipv6=False):
326 '''Create test interface with DHCP server behind it'''
327
328 raise NotImplementedError('must be implemented by a subclass')
329
330 def shutdown_iface(self):
331 '''Remove test interface and stop DHCP server'''
332
333 raise NotImplementedError('must be implemented by a subclass')
334
335 def print_server_log(self):
336 '''Print DHCP server log for debugging failures'''
337
338 raise NotImplementedError('must be implemented by a subclass')
339
74c13b76 340 def start_unit(self, unit):
d26fdaa2 341 try:
74c13b76 342 subprocess.check_call(['systemctl', 'start', unit])
d26fdaa2 343 except subprocess.CalledProcessError:
74c13b76 344 self.show_journal(unit)
d26fdaa2 345 raise
74c13b76
MP
346
347 def do_test(self, coldplug=True, ipv6=False, extra_opts='',
348 online_timeout=10, dhcp_mode='yes'):
349 self.start_unit('systemd-resolved')
ec89276c 350 self.write_network(self.config, '''\
38d78d1e 351[Match]
278391c2 352Name={}
4ddb85b1 353[Network]
278391c2
BOT
354DHCP={}
355{}'''.format(self.iface, dhcp_mode, extra_opts))
4ddb85b1
MP
356
357 if coldplug:
358 # create interface first, then start networkd
359 self.create_iface(ipv6=ipv6)
74c13b76 360 self.start_unit('systemd-networkd')
e8c0de91 361 elif coldplug is not None:
4ddb85b1 362 # start networkd first, then create interface
74c13b76 363 self.start_unit('systemd-networkd')
4ddb85b1 364 self.create_iface(ipv6=ipv6)
e8c0de91
MP
365 else:
366 # "None" means test sets up interface by itself
74c13b76 367 self.start_unit('systemd-networkd')
4ddb85b1
MP
368
369 try:
ec89276c 370 subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface',
4ddb85b1
MP
371 self.iface, '--timeout=%i' % online_timeout])
372
373 if ipv6:
374 # check iface state and IP 6 address; FIXME: we need to wait a bit
375 # longer, as the iface is "configured" already with IPv4 *or*
376 # IPv6, but we want to wait for both
00d5eaaf 377 for _ in range(10):
4ddb85b1
MP
378 out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.iface])
379 if b'state UP' in out and b'inet6 2600' in out and b'inet 192.168' in out:
380 break
381 time.sleep(1)
382 else:
383 self.fail('timed out waiting for IPv6 configuration')
384
385 self.assertRegex(out, b'inet6 2600::.* scope global .*dynamic')
386 self.assertRegex(out, b'inet6 fe80::.* scope link')
387 else:
388 # should have link-local address on IPv6 only
389 out = subprocess.check_output(['ip', '-6', 'a', 'show', 'dev', self.iface])
cda39975 390 self.assertRegex(out, br'inet6 fe80::.* scope link')
4ddb85b1
MP
391 self.assertNotIn(b'scope global', out)
392
393 # should have IPv4 address
394 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
395 self.assertIn(b'state UP', out)
cda39975 396 self.assertRegex(out, br'inet 192.168.5.\d+/.* scope global dynamic')
4ddb85b1
MP
397
398 # check networkctl state
399 out = subprocess.check_output(['networkctl'])
278391c2
BOT
400 self.assertRegex(out, (r'{}\s+ether\s+[a-z-]+\s+unmanaged'.format(self.if_router)).encode())
401 self.assertRegex(out, (r'{}\s+ether\s+routable\s+configured'.format(self.iface)).encode())
4ddb85b1
MP
402
403 out = subprocess.check_output(['networkctl', 'status', self.iface])
cda39975
ZJS
404 self.assertRegex(out, br'Type:\s+ether')
405 self.assertRegex(out, br'State:\s+routable.*configured')
406 self.assertRegex(out, br'Address:\s+192.168.5.\d+')
4ddb85b1 407 if ipv6:
cda39975 408 self.assertRegex(out, br'2600::')
4ddb85b1 409 else:
cda39975
ZJS
410 self.assertNotIn(br'2600::', out)
411 self.assertRegex(out, br'fe80::')
412 self.assertRegex(out, br'Gateway:\s+192.168.5.1')
413 self.assertRegex(out, br'DNS:\s+192.168.5.1')
4ddb85b1
MP
414 except (AssertionError, subprocess.CalledProcessError):
415 # show networkd status, journal, and DHCP server log on failure
ec89276c 416 with open(os.path.join(NETWORK_UNITDIR, self.config)) as f:
278391c2 417 print('\n---- {} ----\n{}'.format(self.config, f.read()))
4ddb85b1
MP
418 print('---- interface status ----')
419 sys.stdout.flush()
420 subprocess.call(['ip', 'a', 'show', 'dev', self.iface])
278391c2 421 print('---- networkctl status {} ----'.format(self.iface))
4ddb85b1 422 sys.stdout.flush()
345997f3
FS
423 rc = subprocess.call(['networkctl', 'status', self.iface])
424 if rc != 0:
425 print("'networkctl status' exited with an unexpected code {}".format(rc))
4ddb85b1
MP
426 self.show_journal('systemd-networkd.service')
427 self.print_server_log()
428 raise
429
30b42a9a
MP
430 for timeout in range(50):
431 with open(RESOLV_CONF) as f:
432 contents = f.read()
433 if 'nameserver 192.168.5.1\n' in contents:
434 break
435 time.sleep(0.1)
436 else:
437 self.fail('nameserver 192.168.5.1 not found in ' + RESOLV_CONF)
4ddb85b1 438
e8c0de91 439 if coldplug is False:
4ddb85b1
MP
440 # check post-down.d hook
441 self.shutdown_iface()
442
443 def test_coldplug_dhcp_yes_ip4(self):
444 # we have a 12s timeout on RA, so we need to wait longer
445 self.do_test(coldplug=True, ipv6=False, online_timeout=15)
446
447 def test_coldplug_dhcp_yes_ip4_no_ra(self):
448 # with disabling RA explicitly things should be fast
449 self.do_test(coldplug=True, ipv6=False,
f921f573 450 extra_opts='IPv6AcceptRA=False')
4ddb85b1
MP
451
452 def test_coldplug_dhcp_ip4_only(self):
453 # we have a 12s timeout on RA, so we need to wait longer
454 self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4',
455 online_timeout=15)
456
457 def test_coldplug_dhcp_ip4_only_no_ra(self):
458 # with disabling RA explicitly things should be fast
459 self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4',
f921f573 460 extra_opts='IPv6AcceptRA=False')
4ddb85b1
MP
461
462 def test_coldplug_dhcp_ip6(self):
463 self.do_test(coldplug=True, ipv6=True)
464
465 def test_hotplug_dhcp_ip4(self):
466 # With IPv4 only we have a 12s timeout on RA, so we need to wait longer
467 self.do_test(coldplug=False, ipv6=False, online_timeout=15)
468
469 def test_hotplug_dhcp_ip6(self):
470 self.do_test(coldplug=False, ipv6=True)
471
94363cbb 472 def test_route_only_dns(self):
ec89276c 473 self.write_network('myvpn.netdev', '''\
38d78d1e 474[NetDev]
94363cbb
MP
475Name=dummy0
476Kind=dummy
477MACAddress=12:34:56:78:9a:bc''')
ec89276c 478 self.write_network('myvpn.network', '''\
38d78d1e 479[Match]
94363cbb
MP
480Name=dummy0
481[Network]
829c0672 482Address=192.168.42.100/24
94363cbb
MP
483DNS=192.168.42.1
484Domains= ~company''')
94363cbb 485
09b8826e
MP
486 try:
487 self.do_test(coldplug=True, ipv6=False,
488 extra_opts='IPv6AcceptRouterAdvertisements=False')
489 except subprocess.CalledProcessError as e:
490 # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848
491 if IS_CONTAINER and e.cmd == ['systemctl', 'start', 'systemd-networkd']:
492 raise unittest.SkipTest('https://github.com/systemd/systemd/issues/11848')
493 else:
494 raise
94363cbb 495
30b42a9a
MP
496 with open(RESOLV_CONF) as f:
497 contents = f.read()
94363cbb
MP
498 # ~company is not a search domain, only a routing domain
499 self.assertNotRegex(contents, 'search.*company')
30b42a9a
MP
500 # our global server should appear
501 self.assertIn('nameserver 192.168.5.1\n', contents)
b9fe94ca
MP
502 # should not have domain-restricted server as global server
503 self.assertNotIn('nameserver 192.168.42.1\n', contents)
504
505 def test_route_only_dns_all_domains(self):
ec89276c 506 self.write_network('myvpn.netdev', '''[NetDev]
b9fe94ca
MP
507Name=dummy0
508Kind=dummy
509MACAddress=12:34:56:78:9a:bc''')
ec89276c 510 self.write_network('myvpn.network', '''[Match]
b9fe94ca
MP
511Name=dummy0
512[Network]
829c0672 513Address=192.168.42.100/24
b9fe94ca
MP
514DNS=192.168.42.1
515Domains= ~company ~.''')
b9fe94ca 516
09b8826e
MP
517 try:
518 self.do_test(coldplug=True, ipv6=False,
519 extra_opts='IPv6AcceptRouterAdvertisements=False')
520 except subprocess.CalledProcessError as e:
521 # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848
522 if IS_CONTAINER and e.cmd == ['systemctl', 'start', 'systemd-networkd']:
523 raise unittest.SkipTest('https://github.com/systemd/systemd/issues/11848')
524 else:
525 raise
b9fe94ca
MP
526
527 with open(RESOLV_CONF) as f:
528 contents = f.read()
529
530 # ~company is not a search domain, only a routing domain
531 self.assertNotRegex(contents, 'search.*company')
532
533 # our global server should appear
534 self.assertIn('nameserver 192.168.5.1\n', contents)
535 # should have company server as global server due to ~.
536 self.assertIn('nameserver 192.168.42.1\n', contents)
94363cbb 537
4ddb85b1 538
ec89276c 539@unittest.skipUnless(HAVE_DNSMASQ, 'dnsmasq not installed')
4ddb85b1
MP
540class DnsmasqClientTest(ClientTestBase, unittest.TestCase):
541 '''Test networkd client against dnsmasq'''
542
543 def setUp(self):
544 super().setUp()
545 self.dnsmasq = None
e8c0de91 546 self.iface_mac = 'de:ad:be:ef:47:11'
4ddb85b1 547
b9fe94ca 548 def create_iface(self, ipv6=False, dnsmasq_opts=None):
4ddb85b1
MP
549 '''Create test interface with DHCP server behind it'''
550
551 # add veth pair
e8c0de91
MP
552 subprocess.check_call(['ip', 'link', 'add', 'name', self.iface,
553 'address', self.iface_mac,
554 'type', 'veth', 'peer', 'name', self.if_router])
4ddb85b1
MP
555
556 # give our router an IP
557 subprocess.check_call(['ip', 'a', 'flush', 'dev', self.if_router])
558 subprocess.check_call(['ip', 'a', 'add', '192.168.5.1/24', 'dev', self.if_router])
559 if ipv6:
560 subprocess.check_call(['ip', 'a', 'add', '2600::1/64', 'dev', self.if_router])
561 subprocess.check_call(['ip', 'link', 'set', self.if_router, 'up'])
562
563 # add DHCP server
564 self.dnsmasq_log = os.path.join(self.workdir, 'dnsmasq.log')
565 lease_file = os.path.join(self.workdir, 'dnsmasq.leases')
566 if ipv6:
567 extra_opts = ['--enable-ra', '--dhcp-range=2600::10,2600::20']
568 else:
569 extra_opts = []
b9fe94ca
MP
570 if dnsmasq_opts:
571 extra_opts += dnsmasq_opts
4ddb85b1
MP
572 self.dnsmasq = subprocess.Popen(
573 ['dnsmasq', '--keep-in-foreground', '--log-queries',
574 '--log-facility=' + self.dnsmasq_log, '--conf-file=/dev/null',
575 '--dhcp-leasefile=' + lease_file, '--bind-interfaces',
576 '--interface=' + self.if_router, '--except-interface=lo',
577 '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts)
578
579 def shutdown_iface(self):
580 '''Remove test interface and stop DHCP server'''
581
582 if self.if_router:
583 subprocess.check_call(['ip', 'link', 'del', 'dev', self.if_router])
584 self.if_router = None
585 if self.dnsmasq:
586 self.dnsmasq.kill()
587 self.dnsmasq.wait()
588 self.dnsmasq = None
589
590 def print_server_log(self):
591 '''Print DHCP server log for debugging failures'''
592
593 with open(self.dnsmasq_log) as f:
278391c2 594 sys.stdout.write('\n\n---- dnsmasq log ----\n{}\n------\n\n'.format(f.read()))
4ddb85b1 595
b9fe94ca
MP
596 def test_resolved_domain_restricted_dns(self):
597 '''resolved: domain-restricted DNS servers'''
598
6592c9c8
MP
599 # FIXME: resolvectl query fails with enabled DNSSEC against our dnsmasq
600 conf = '/run/systemd/resolved.conf.d/test-disable-dnssec.conf'
601 os.makedirs(os.path.dirname(conf), exist_ok=True)
602 with open(conf, 'w') as f:
603 f.write('[Resolve]\nDNSSEC=no\n')
604 self.addCleanup(os.remove, conf)
605
b9fe94ca
MP
606 # create interface for generic connections; this will map all DNS names
607 # to 192.168.42.1
608 self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1'])
ec89276c 609 self.write_network('general.network', '''\
b9fe94ca 610[Match]
278391c2 611Name={}
b9fe94ca
MP
612[Network]
613DHCP=ipv4
278391c2 614IPv6AcceptRA=False'''.format(self.iface))
b9fe94ca
MP
615
616 # create second device/dnsmasq for a .company/.lab VPN interface
617 # static IPs for simplicity
618b196e 618 self.add_veth_pair('testvpnclient', 'testvpnrouter')
b9fe94ca
MP
619 subprocess.check_call(['ip', 'a', 'flush', 'dev', 'testvpnrouter'])
620 subprocess.check_call(['ip', 'a', 'add', '10.241.3.1/24', 'dev', 'testvpnrouter'])
621 subprocess.check_call(['ip', 'link', 'set', 'testvpnrouter', 'up'])
622
623 vpn_dnsmasq_log = os.path.join(self.workdir, 'dnsmasq-vpn.log')
624 vpn_dnsmasq = subprocess.Popen(
625 ['dnsmasq', '--keep-in-foreground', '--log-queries',
626 '--log-facility=' + vpn_dnsmasq_log, '--conf-file=/dev/null',
627 '--dhcp-leasefile=/dev/null', '--bind-interfaces',
628 '--interface=testvpnrouter', '--except-interface=lo',
629 '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4'])
630 self.addCleanup(vpn_dnsmasq.wait)
631 self.addCleanup(vpn_dnsmasq.kill)
632
ec89276c 633 self.write_network('vpn.network', '''\
b9fe94ca
MP
634[Match]
635Name=testvpnclient
636[Network]
637IPv6AcceptRA=False
638Address=10.241.3.2/24
639DNS=10.241.3.1
640Domains= ~company ~lab''')
641
74c13b76 642 self.start_unit('systemd-networkd')
ec89276c 643 subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', self.iface,
b9fe94ca
MP
644 '--interface=testvpnclient', '--timeout=20'])
645
646 # ensure we start fresh with every test
647 subprocess.check_call(['systemctl', 'restart', 'systemd-resolved'])
648
649 # test vpnclient specific domains; these should *not* be answered by
650 # the general DNS
3deb28f2 651 out = subprocess.check_output(['resolvectl', 'query', 'math.lab'])
b9fe94ca 652 self.assertIn(b'math.lab: 10.241.3.3', out)
3deb28f2 653 out = subprocess.check_output(['resolvectl', 'query', 'kettle.cantina.company'])
b9fe94ca
MP
654 self.assertIn(b'kettle.cantina.company: 10.241.4.4', out)
655
656 # test general domains
3deb28f2 657 out = subprocess.check_output(['resolvectl', 'query', 'megasearch.net'])
b9fe94ca
MP
658 self.assertIn(b'megasearch.net: 192.168.42.1', out)
659
660 with open(self.dnsmasq_log) as f:
661 general_log = f.read()
662 with open(vpn_dnsmasq_log) as f:
663 vpn_log = f.read()
664
665 # VPN domains should only be sent to VPN DNS
666 self.assertRegex(vpn_log, 'query.*math.lab')
667 self.assertRegex(vpn_log, 'query.*cantina.company')
27e2e323
MP
668 self.assertNotIn('.lab', general_log)
669 self.assertNotIn('.company', general_log)
b9fe94ca
MP
670
671 # general domains should not be sent to the VPN DNS
672 self.assertRegex(general_log, 'query.*megasearch.net')
673 self.assertNotIn('megasearch.net', vpn_log)
674
4050e04b
MP
675 def test_resolved_etc_hosts(self):
676 '''resolved queries to /etc/hosts'''
677
678 # FIXME: -t MX query fails with enabled DNSSEC (even when using
ca56805c 679 # the known negative trust anchor .internal instead of .example.com)
4050e04b
MP
680 conf = '/run/systemd/resolved.conf.d/test-disable-dnssec.conf'
681 os.makedirs(os.path.dirname(conf), exist_ok=True)
682 with open(conf, 'w') as f:
17508549 683 f.write('[Resolve]\nDNSSEC=no\nLLMNR=no\nMulticastDNS=no\n')
4050e04b
MP
684 self.addCleanup(os.remove, conf)
685
ca56805c 686 # create /etc/hosts bind mount which resolves my.example.com for IPv4
4050e04b
MP
687 hosts = os.path.join(self.workdir, 'hosts')
688 with open(hosts, 'w') as f:
ca56805c 689 f.write('172.16.99.99 my.example.com\n')
4050e04b
MP
690 subprocess.check_call(['mount', '--bind', hosts, '/etc/hosts'])
691 self.addCleanup(subprocess.call, ['umount', '/etc/hosts'])
692 subprocess.check_call(['systemctl', 'stop', 'systemd-resolved.service'])
693
694 # note: different IPv4 address here, so that it's easy to tell apart
695 # what resolved the query
ca56805c
MP
696 self.create_iface(dnsmasq_opts=['--host-record=my.example.com,172.16.99.1,2600::99:99',
697 '--host-record=other.example.com,172.16.0.42,2600::42',
698 '--mx-host=example.com,mail.example.com'],
4050e04b
MP
699 ipv6=True)
700 self.do_test(coldplug=None, ipv6=True)
701
702 try:
703 # family specific queries
ca56805c
MP
704 out = subprocess.check_output(['resolvectl', 'query', '-4', 'my.example.com'])
705 self.assertIn(b'my.example.com: 172.16.99.99', out)
4050e04b
MP
706 # we don't expect an IPv6 answer; if /etc/hosts has any IP address,
707 # it's considered a sufficient source
ca56805c 708 self.assertNotEqual(subprocess.call(['resolvectl', 'query', '-6', 'my.example.com']), 0)
4050e04b 709 # "any family" query; IPv4 should come from /etc/hosts
ca56805c
MP
710 out = subprocess.check_output(['resolvectl', 'query', 'my.example.com'])
711 self.assertIn(b'my.example.com: 172.16.99.99', out)
4050e04b 712 # IP → name lookup; again, takes the /etc/hosts one
3deb28f2 713 out = subprocess.check_output(['resolvectl', 'query', '172.16.99.99'])
ca56805c 714 self.assertIn(b'172.16.99.99: my.example.com', out)
4050e04b
MP
715
716 # non-address RRs should fall back to DNS
ca56805c
MP
717 out = subprocess.check_output(['resolvectl', 'query', '--type=MX', 'example.com'])
718 self.assertIn(b'example.com IN MX 1 mail.example.com', out)
4050e04b
MP
719
720 # other domains query DNS
ca56805c 721 out = subprocess.check_output(['resolvectl', 'query', 'other.example.com'])
4050e04b 722 self.assertIn(b'172.16.0.42', out)
3deb28f2 723 out = subprocess.check_output(['resolvectl', 'query', '172.16.0.42'])
ca56805c 724 self.assertIn(b'172.16.0.42: other.example.com', out)
4050e04b
MP
725 except (AssertionError, subprocess.CalledProcessError):
726 self.show_journal('systemd-resolved.service')
727 self.print_server_log()
728 raise
729
e8c0de91
MP
730 def test_transient_hostname(self):
731 '''networkd sets transient hostname from DHCP'''
732
89748b0a
MP
733 orig_hostname = socket.gethostname()
734 self.addCleanup(socket.sethostname, orig_hostname)
735 # temporarily move /etc/hostname away; restart hostnamed to pick it up
736 if os.path.exists('/etc/hostname'):
737 subprocess.check_call(['mount', '--bind', '/dev/null', '/etc/hostname'])
738 self.addCleanup(subprocess.call, ['umount', '/etc/hostname'])
739 subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
8e0ba0c9 740 self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service'])
89748b0a 741
278391c2 742 self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)])
e8c0de91
MP
743 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=False', dhcp_mode='ipv4')
744
fd0cec03
MP
745 try:
746 # should have received the fixed IP above
747 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
748 self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic')
2926b130
MP
749 # should have set transient hostname in hostnamed; this is
750 # sometimes a bit lagging (issue #4753), so retry a few times
751 for retry in range(1, 6):
752 out = subprocess.check_output(['hostnamectl'])
753 if b'testgreen' in out:
754 break
755 time.sleep(5)
756 sys.stdout.write('[retry %i] ' % retry)
757 sys.stdout.flush()
758 else:
278391c2 759 self.fail('Transient hostname not found in hostnamectl:\n{}'.format(out.decode()))
fd0cec03
MP
760 # and also applied to the system
761 self.assertEqual(socket.gethostname(), 'testgreen')
762 except AssertionError:
763 self.show_journal('systemd-networkd.service')
764 self.show_journal('systemd-hostnamed.service')
765 self.print_server_log()
766 raise
89748b0a
MP
767
768 def test_transient_hostname_with_static(self):
769 '''transient hostname is not applied if static hostname exists'''
770
771 orig_hostname = socket.gethostname()
772 self.addCleanup(socket.sethostname, orig_hostname)
0373fc5b 773
89748b0a 774 if not os.path.exists('/etc/hostname'):
0373fc5b
LP
775 self.write_config('/etc/hostname', "foobarqux")
776 else:
777 self.write_config('/run/hostname.tmp', "foobarqux")
778 subprocess.check_call(['mount', '--bind', '/run/hostname.tmp', '/etc/hostname'])
779 self.addCleanup(subprocess.call, ['umount', '/etc/hostname'])
780
781 socket.sethostname("foobarqux");
782
89748b0a 783 subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
8e0ba0c9 784 self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service'])
89748b0a 785
278391c2 786 self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)])
89748b0a
MP
787 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=False', dhcp_mode='ipv4')
788
fd0cec03
MP
789 try:
790 # should have received the fixed IP above
791 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
792 self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic')
793 # static hostname wins over transient one, thus *not* applied
0373fc5b 794 self.assertEqual(socket.gethostname(), "foobarqux")
fd0cec03
MP
795 except AssertionError:
796 self.show_journal('systemd-networkd.service')
797 self.show_journal('systemd-hostnamed.service')
798 self.print_server_log()
799 raise
e8c0de91 800
4ddb85b1
MP
801
802class NetworkdClientTest(ClientTestBase, unittest.TestCase):
803 '''Test networkd client against networkd server'''
804
805 def setUp(self):
806 super().setUp()
807 self.dnsmasq = None
808
2c99aba7 809 def create_iface(self, ipv6=False, dhcpserver_opts=None):
4ddb85b1
MP
810 '''Create test interface with DHCP server behind it'''
811
812 # run "router-side" networkd in own mount namespace to shield it from
813 # "client-side" configuration and networkd
814 (fd, script) = tempfile.mkstemp(prefix='networkd-router.sh')
815 self.addCleanup(os.remove, script)
816 with os.fdopen(fd, 'w+') as f:
38d78d1e 817 f.write('''\
7629744a 818#!/bin/sh
819set -eu
4ddb85b1
MP
820mkdir -p /run/systemd/network
821mkdir -p /run/systemd/netif
822mount -t tmpfs none /run/systemd/network
823mount -t tmpfs none /run/systemd/netif
824[ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus
825# create router/client veth pair
826cat << EOF > /run/systemd/network/test.netdev
827[NetDev]
828Name=%(ifr)s
829Kind=veth
830
831[Peer]
832Name=%(ifc)s
833EOF
834
835cat << EOF > /run/systemd/network/test.network
836[Match]
837Name=%(ifr)s
838
839[Network]
840Address=192.168.5.1/24
841%(addr6)s
842DHCPServer=yes
843
844[DHCPServer]
845PoolOffset=10
846PoolSize=50
847DNS=192.168.5.1
2c99aba7 848%(dhopts)s
4ddb85b1
MP
849EOF
850
851# run networkd as in systemd-networkd.service
5ed0dcf4 852exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ { s/^.*=//; s/^[@+-]//; s/^!*//; p}')
2c99aba7
MP
853''' % {'ifr': self.if_router, 'ifc': self.iface, 'addr6': ipv6 and 'Address=2600::1/64' or '',
854 'dhopts': dhcpserver_opts or ''})
4ddb85b1
MP
855
856 os.fchmod(fd, 0o755)
857
858 subprocess.check_call(['systemd-run', '--unit=networkd-test-router.service',
859 '-p', 'InaccessibleDirectories=-/etc/systemd/network',
860 '-p', 'InaccessibleDirectories=-/run/systemd/network',
861 '-p', 'InaccessibleDirectories=-/run/systemd/netif',
862 '--service-type=notify', script])
863
864 # wait until devices got created
00d5eaaf 865 for _ in range(50):
4ddb85b1
MP
866 out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.if_router])
867 if b'state UP' in out and b'scope global' in out:
868 break
869 time.sleep(0.1)
870
871 def shutdown_iface(self):
872 '''Remove test interface and stop DHCP server'''
873
874 if self.if_router:
875 subprocess.check_call(['systemctl', 'stop', 'networkd-test-router.service'])
876 # ensure failed transient unit does not stay around
877 subprocess.call(['systemctl', 'reset-failed', 'networkd-test-router.service'])
878 subprocess.call(['ip', 'link', 'del', 'dev', self.if_router])
879 self.if_router = None
880
881 def print_server_log(self):
882 '''Print DHCP server log for debugging failures'''
883
884 self.show_journal('networkd-test-router.service')
885
886 @unittest.skip('networkd does not have DHCPv6 server support')
887 def test_hotplug_dhcp_ip6(self):
888 pass
889
890 @unittest.skip('networkd does not have DHCPv6 server support')
891 def test_coldplug_dhcp_ip6(self):
892 pass
893
d2bc1251
MP
894 def test_search_domains(self):
895
896 # we don't use this interface for this test
897 self.if_router = None
898
ec89276c 899 self.write_network('test.netdev', '''\
38d78d1e 900[NetDev]
d2bc1251
MP
901Name=dummy0
902Kind=dummy
903MACAddress=12:34:56:78:9a:bc''')
ec89276c 904 self.write_network('test.network', '''\
38d78d1e 905[Match]
d2bc1251
MP
906Name=dummy0
907[Network]
829c0672 908Address=192.168.42.100/24
d2bc1251
MP
909DNS=192.168.42.1
910Domains= one two three four five six seven eight nine ten''')
d2bc1251 911
74c13b76 912 self.start_unit('systemd-networkd')
d2bc1251 913
30b42a9a
MP
914 for timeout in range(50):
915 with open(RESOLV_CONF) as f:
916 contents = f.read()
917 if ' one' in contents:
918 break
919 time.sleep(0.1)
920 self.assertRegex(contents, 'search .*one two three four')
921 self.assertNotIn('seven\n', contents)
922 self.assertIn('# Too many search domains configured, remaining ones ignored.\n', contents)
d2bc1251
MP
923
924 def test_search_domains_too_long(self):
925
926 # we don't use this interface for this test
927 self.if_router = None
928
929 name_prefix = 'a' * 60
930
ec89276c 931 self.write_network('test.netdev', '''\
38d78d1e 932[NetDev]
d2bc1251
MP
933Name=dummy0
934Kind=dummy
935MACAddress=12:34:56:78:9a:bc''')
ec89276c 936 self.write_network('test.network', '''\
38d78d1e 937[Match]
d2bc1251
MP
938Name=dummy0
939[Network]
829c0672 940Address=192.168.42.100/24
d2bc1251 941DNS=192.168.42.1
38d78d1e 942Domains={p}0 {p}1 {p}2 {p}3 {p}4'''.format(p=name_prefix))
d2bc1251 943
74c13b76 944 self.start_unit('systemd-networkd')
d2bc1251 945
30b42a9a
MP
946 for timeout in range(50):
947 with open(RESOLV_CONF) as f:
948 contents = f.read()
949 if ' one' in contents:
950 break
951 time.sleep(0.1)
38d78d1e 952 self.assertRegex(contents, 'search .*{p}0 {p}1 {p}2'.format(p=name_prefix))
30b42a9a 953 self.assertIn('# Total length of all search domains is too long, remaining ones ignored.', contents)
d2bc1251 954
047a0dac
JSB
955 def test_dropin(self):
956 # we don't use this interface for this test
957 self.if_router = None
958
ec89276c 959 self.write_network('test.netdev', '''\
047a0dac
JSB
960[NetDev]
961Name=dummy0
962Kind=dummy
963MACAddress=12:34:56:78:9a:bc''')
ec89276c 964 self.write_network('test.network', '''\
047a0dac
JSB
965[Match]
966Name=dummy0
967[Network]
829c0672 968Address=192.168.42.100/24
047a0dac 969DNS=192.168.42.1''')
ec89276c 970 self.write_network_dropin('test.network', 'dns', '''\
047a0dac
JSB
971[Network]
972DNS=127.0.0.1''')
973
74c13b76
MP
974 self.start_unit('systemd-resolved')
975 self.start_unit('systemd-networkd')
047a0dac
JSB
976
977 for timeout in range(50):
978 with open(RESOLV_CONF) as f:
979 contents = f.read()
f5cf985e 980 if ' 127.0.0.1' in contents and '192.168.42.1' in contents:
047a0dac
JSB
981 break
982 time.sleep(0.1)
983 self.assertIn('nameserver 192.168.42.1\n', contents)
984 self.assertIn('nameserver 127.0.0.1\n', contents)
985
2c99aba7
MP
986 def test_dhcp_timezone(self):
987 '''networkd sets time zone from DHCP'''
988
989 def get_tz():
990 out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.timedate1',
991 '/org/freedesktop/timedate1', 'org.freedesktop.timedate1', 'Timezone'])
992 assert out.startswith(b's "')
993 out = out.strip()
994 assert out.endswith(b'"')
995 return out[3:-1].decode()
996
997 orig_timezone = get_tz()
998 self.addCleanup(subprocess.call, ['timedatectl', 'set-timezone', orig_timezone])
999
1000 self.create_iface(dhcpserver_opts='EmitTimezone=yes\nTimezone=Pacific/Honolulu')
1001 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4')
1002
1003 # should have applied the received timezone
1004 try:
1005 self.assertEqual(get_tz(), 'Pacific/Honolulu')
1006 except AssertionError:
1007 self.show_journal('systemd-networkd.service')
1008 self.show_journal('systemd-hostnamed.service')
1009 raise
1010
1011
618b196e
DM
1012class MatchClientTest(unittest.TestCase, NetworkdTestingUtilities):
1013 """Test [Match] sections in .network files.
1014
1015 Be aware that matching the test host's interfaces will wipe their
1016 configuration, so as a precaution, all network files should have a
1017 restrictive [Match] section to only ever interfere with the
1018 temporary veth interfaces created here.
1019 """
1020
1021 def tearDown(self):
1022 """Stop networkd."""
1023 subprocess.call(['systemctl', 'stop', 'systemd-networkd'])
1024
1025 def test_basic_matching(self):
1026 """Verify the Name= line works throughout this class."""
1027 self.add_veth_pair('test_if1', 'fake_if2')
1028 self.write_network('test.network', "[Match]\nName=test_*\n[Network]")
1029 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1030 self.assert_link_states(test_if1='managed', fake_if2='unmanaged')
1031
1032 def test_inverted_matching(self):
1033 """Verify that a '!'-prefixed value inverts the match."""
1034 # Use a MAC address as the interfaces' common matching attribute
1035 # to avoid depending on udev, to support testing in containers.
1036 mac = '00:01:02:03:98:99'
1037 self.add_veth_pair('test_veth', 'test_peer',
1038 ['addr', mac], ['addr', mac])
1039 self.write_network('no-veth.network', """\
1040[Match]
278391c2 1041MACAddress={}
618b196e 1042Name=!nonexistent *peer*
278391c2 1043[Network]""".format(mac))
618b196e
DM
1044 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1045 self.assert_link_states(test_veth='managed', test_peer='unmanaged')
1046
1047
a09dc546
DM
1048class UnmanagedClientTest(unittest.TestCase, NetworkdTestingUtilities):
1049 """Test if networkd manages the correct interfaces."""
1050
1051 def setUp(self):
1052 """Write .network files to match the named veth devices."""
1053 # Define the veth+peer pairs to be created.
1054 # Their pairing doesn't actually matter, only their names do.
1055 self.veths = {
1056 'm1def': 'm0unm',
1057 'm1man': 'm1unm',
1058 }
1059
1060 # Define the contents of .network files to be read in order.
1061 self.configs = (
1062 "[Match]\nName=m1def\n",
1063 "[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n",
1064 "[Match]\nName=m1*\n[Link]\nUnmanaged=no\n",
1065 )
1066
1067 # Write out the .network files to be cleaned up automatically.
1068 for i, config in enumerate(self.configs):
1069 self.write_network("%02d-test.network" % i, config)
1070
1071 def tearDown(self):
1072 """Stop networkd."""
1073 subprocess.call(['systemctl', 'stop', 'systemd-networkd'])
1074
1075 def create_iface(self):
1076 """Create temporary veth pairs for interface matching."""
1077 for veth, peer in self.veths.items():
618b196e 1078 self.add_veth_pair(veth, peer)
a09dc546
DM
1079
1080 def test_unmanaged_setting(self):
1081 """Verify link states with Unmanaged= settings, hot-plug."""
1082 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1083 self.create_iface()
1084 self.assert_link_states(m1def='managed',
1085 m1man='managed',
1086 m1unm='unmanaged',
1087 m0unm='unmanaged')
1088
1089 def test_unmanaged_setting_coldplug(self):
1090 """Verify link states with Unmanaged= settings, cold-plug."""
1091 self.create_iface()
1092 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1093 self.assert_link_states(m1def='managed',
1094 m1man='managed',
1095 m1unm='unmanaged',
1096 m0unm='unmanaged')
1097
1098 def test_catchall_config(self):
1099 """Verify link states with a catch-all config, hot-plug."""
1100 # Don't actually catch ALL interfaces. It messes up the host.
1101 self.write_network('all.network', "[Match]\nName=m[01]???\n")
1102 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1103 self.create_iface()
1104 self.assert_link_states(m1def='managed',
1105 m1man='managed',
1106 m1unm='unmanaged',
1107 m0unm='managed')
1108
1109 def test_catchall_config_coldplug(self):
1110 """Verify link states with a catch-all config, cold-plug."""
1111 # Don't actually catch ALL interfaces. It messes up the host.
1112 self.write_network('all.network', "[Match]\nName=m[01]???\n")
1113 self.create_iface()
1114 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1115 self.assert_link_states(m1def='managed',
1116 m1man='managed',
1117 m1unm='unmanaged',
1118 m0unm='managed')
1119
1120
4ddb85b1
MP
1121if __name__ == '__main__':
1122 unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout,
1123 verbosity=2))