]> git.ipfire.org Git - thirdparty/systemd.git/blame - test/networkd-test.py
test: use `useradd` instead of `adduser`
[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
MP
422 sys.stdout.flush()
423 subprocess.call(['networkctl', 'status', self.iface])
424 self.show_journal('systemd-networkd.service')
425 self.print_server_log()
426 raise
427
30b42a9a
MP
428 for timeout in range(50):
429 with open(RESOLV_CONF) as f:
430 contents = f.read()
431 if 'nameserver 192.168.5.1\n' in contents:
432 break
433 time.sleep(0.1)
434 else:
435 self.fail('nameserver 192.168.5.1 not found in ' + RESOLV_CONF)
4ddb85b1 436
e8c0de91 437 if coldplug is False:
4ddb85b1
MP
438 # check post-down.d hook
439 self.shutdown_iface()
440
441 def test_coldplug_dhcp_yes_ip4(self):
442 # we have a 12s timeout on RA, so we need to wait longer
443 self.do_test(coldplug=True, ipv6=False, online_timeout=15)
444
445 def test_coldplug_dhcp_yes_ip4_no_ra(self):
446 # with disabling RA explicitly things should be fast
447 self.do_test(coldplug=True, ipv6=False,
f921f573 448 extra_opts='IPv6AcceptRA=False')
4ddb85b1
MP
449
450 def test_coldplug_dhcp_ip4_only(self):
451 # we have a 12s timeout on RA, so we need to wait longer
452 self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4',
453 online_timeout=15)
454
455 def test_coldplug_dhcp_ip4_only_no_ra(self):
456 # with disabling RA explicitly things should be fast
457 self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4',
f921f573 458 extra_opts='IPv6AcceptRA=False')
4ddb85b1
MP
459
460 def test_coldplug_dhcp_ip6(self):
461 self.do_test(coldplug=True, ipv6=True)
462
463 def test_hotplug_dhcp_ip4(self):
464 # With IPv4 only we have a 12s timeout on RA, so we need to wait longer
465 self.do_test(coldplug=False, ipv6=False, online_timeout=15)
466
467 def test_hotplug_dhcp_ip6(self):
468 self.do_test(coldplug=False, ipv6=True)
469
94363cbb 470 def test_route_only_dns(self):
ec89276c 471 self.write_network('myvpn.netdev', '''\
38d78d1e 472[NetDev]
94363cbb
MP
473Name=dummy0
474Kind=dummy
475MACAddress=12:34:56:78:9a:bc''')
ec89276c 476 self.write_network('myvpn.network', '''\
38d78d1e 477[Match]
94363cbb
MP
478Name=dummy0
479[Network]
829c0672 480Address=192.168.42.100/24
94363cbb
MP
481DNS=192.168.42.1
482Domains= ~company''')
94363cbb 483
09b8826e
MP
484 try:
485 self.do_test(coldplug=True, ipv6=False,
486 extra_opts='IPv6AcceptRouterAdvertisements=False')
487 except subprocess.CalledProcessError as e:
488 # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848
489 if IS_CONTAINER and e.cmd == ['systemctl', 'start', 'systemd-networkd']:
490 raise unittest.SkipTest('https://github.com/systemd/systemd/issues/11848')
491 else:
492 raise
94363cbb 493
30b42a9a
MP
494 with open(RESOLV_CONF) as f:
495 contents = f.read()
94363cbb
MP
496 # ~company is not a search domain, only a routing domain
497 self.assertNotRegex(contents, 'search.*company')
30b42a9a
MP
498 # our global server should appear
499 self.assertIn('nameserver 192.168.5.1\n', contents)
b9fe94ca
MP
500 # should not have domain-restricted server as global server
501 self.assertNotIn('nameserver 192.168.42.1\n', contents)
502
503 def test_route_only_dns_all_domains(self):
ec89276c 504 self.write_network('myvpn.netdev', '''[NetDev]
b9fe94ca
MP
505Name=dummy0
506Kind=dummy
507MACAddress=12:34:56:78:9a:bc''')
ec89276c 508 self.write_network('myvpn.network', '''[Match]
b9fe94ca
MP
509Name=dummy0
510[Network]
829c0672 511Address=192.168.42.100/24
b9fe94ca
MP
512DNS=192.168.42.1
513Domains= ~company ~.''')
b9fe94ca 514
09b8826e
MP
515 try:
516 self.do_test(coldplug=True, ipv6=False,
517 extra_opts='IPv6AcceptRouterAdvertisements=False')
518 except subprocess.CalledProcessError as e:
519 # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848
520 if IS_CONTAINER and e.cmd == ['systemctl', 'start', 'systemd-networkd']:
521 raise unittest.SkipTest('https://github.com/systemd/systemd/issues/11848')
522 else:
523 raise
b9fe94ca
MP
524
525 with open(RESOLV_CONF) as f:
526 contents = f.read()
527
528 # ~company is not a search domain, only a routing domain
529 self.assertNotRegex(contents, 'search.*company')
530
531 # our global server should appear
532 self.assertIn('nameserver 192.168.5.1\n', contents)
533 # should have company server as global server due to ~.
534 self.assertIn('nameserver 192.168.42.1\n', contents)
94363cbb 535
4ddb85b1 536
ec89276c 537@unittest.skipUnless(HAVE_DNSMASQ, 'dnsmasq not installed')
4ddb85b1
MP
538class DnsmasqClientTest(ClientTestBase, unittest.TestCase):
539 '''Test networkd client against dnsmasq'''
540
541 def setUp(self):
542 super().setUp()
543 self.dnsmasq = None
e8c0de91 544 self.iface_mac = 'de:ad:be:ef:47:11'
4ddb85b1 545
b9fe94ca 546 def create_iface(self, ipv6=False, dnsmasq_opts=None):
4ddb85b1
MP
547 '''Create test interface with DHCP server behind it'''
548
549 # add veth pair
e8c0de91
MP
550 subprocess.check_call(['ip', 'link', 'add', 'name', self.iface,
551 'address', self.iface_mac,
552 'type', 'veth', 'peer', 'name', self.if_router])
4ddb85b1
MP
553
554 # give our router an IP
555 subprocess.check_call(['ip', 'a', 'flush', 'dev', self.if_router])
556 subprocess.check_call(['ip', 'a', 'add', '192.168.5.1/24', 'dev', self.if_router])
557 if ipv6:
558 subprocess.check_call(['ip', 'a', 'add', '2600::1/64', 'dev', self.if_router])
559 subprocess.check_call(['ip', 'link', 'set', self.if_router, 'up'])
560
561 # add DHCP server
562 self.dnsmasq_log = os.path.join(self.workdir, 'dnsmasq.log')
563 lease_file = os.path.join(self.workdir, 'dnsmasq.leases')
564 if ipv6:
565 extra_opts = ['--enable-ra', '--dhcp-range=2600::10,2600::20']
566 else:
567 extra_opts = []
b9fe94ca
MP
568 if dnsmasq_opts:
569 extra_opts += dnsmasq_opts
4ddb85b1
MP
570 self.dnsmasq = subprocess.Popen(
571 ['dnsmasq', '--keep-in-foreground', '--log-queries',
572 '--log-facility=' + self.dnsmasq_log, '--conf-file=/dev/null',
573 '--dhcp-leasefile=' + lease_file, '--bind-interfaces',
574 '--interface=' + self.if_router, '--except-interface=lo',
575 '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts)
576
577 def shutdown_iface(self):
578 '''Remove test interface and stop DHCP server'''
579
580 if self.if_router:
581 subprocess.check_call(['ip', 'link', 'del', 'dev', self.if_router])
582 self.if_router = None
583 if self.dnsmasq:
584 self.dnsmasq.kill()
585 self.dnsmasq.wait()
586 self.dnsmasq = None
587
588 def print_server_log(self):
589 '''Print DHCP server log for debugging failures'''
590
591 with open(self.dnsmasq_log) as f:
278391c2 592 sys.stdout.write('\n\n---- dnsmasq log ----\n{}\n------\n\n'.format(f.read()))
4ddb85b1 593
b9fe94ca
MP
594 def test_resolved_domain_restricted_dns(self):
595 '''resolved: domain-restricted DNS servers'''
596
6592c9c8
MP
597 # FIXME: resolvectl query fails with enabled DNSSEC against our dnsmasq
598 conf = '/run/systemd/resolved.conf.d/test-disable-dnssec.conf'
599 os.makedirs(os.path.dirname(conf), exist_ok=True)
600 with open(conf, 'w') as f:
601 f.write('[Resolve]\nDNSSEC=no\n')
602 self.addCleanup(os.remove, conf)
603
b9fe94ca
MP
604 # create interface for generic connections; this will map all DNS names
605 # to 192.168.42.1
606 self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1'])
ec89276c 607 self.write_network('general.network', '''\
b9fe94ca 608[Match]
278391c2 609Name={}
b9fe94ca
MP
610[Network]
611DHCP=ipv4
278391c2 612IPv6AcceptRA=False'''.format(self.iface))
b9fe94ca
MP
613
614 # create second device/dnsmasq for a .company/.lab VPN interface
615 # static IPs for simplicity
618b196e 616 self.add_veth_pair('testvpnclient', 'testvpnrouter')
b9fe94ca
MP
617 subprocess.check_call(['ip', 'a', 'flush', 'dev', 'testvpnrouter'])
618 subprocess.check_call(['ip', 'a', 'add', '10.241.3.1/24', 'dev', 'testvpnrouter'])
619 subprocess.check_call(['ip', 'link', 'set', 'testvpnrouter', 'up'])
620
621 vpn_dnsmasq_log = os.path.join(self.workdir, 'dnsmasq-vpn.log')
622 vpn_dnsmasq = subprocess.Popen(
623 ['dnsmasq', '--keep-in-foreground', '--log-queries',
624 '--log-facility=' + vpn_dnsmasq_log, '--conf-file=/dev/null',
625 '--dhcp-leasefile=/dev/null', '--bind-interfaces',
626 '--interface=testvpnrouter', '--except-interface=lo',
627 '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4'])
628 self.addCleanup(vpn_dnsmasq.wait)
629 self.addCleanup(vpn_dnsmasq.kill)
630
ec89276c 631 self.write_network('vpn.network', '''\
b9fe94ca
MP
632[Match]
633Name=testvpnclient
634[Network]
635IPv6AcceptRA=False
636Address=10.241.3.2/24
637DNS=10.241.3.1
638Domains= ~company ~lab''')
639
74c13b76 640 self.start_unit('systemd-networkd')
ec89276c 641 subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', self.iface,
b9fe94ca
MP
642 '--interface=testvpnclient', '--timeout=20'])
643
644 # ensure we start fresh with every test
645 subprocess.check_call(['systemctl', 'restart', 'systemd-resolved'])
646
647 # test vpnclient specific domains; these should *not* be answered by
648 # the general DNS
3deb28f2 649 out = subprocess.check_output(['resolvectl', 'query', 'math.lab'])
b9fe94ca 650 self.assertIn(b'math.lab: 10.241.3.3', out)
3deb28f2 651 out = subprocess.check_output(['resolvectl', 'query', 'kettle.cantina.company'])
b9fe94ca
MP
652 self.assertIn(b'kettle.cantina.company: 10.241.4.4', out)
653
654 # test general domains
3deb28f2 655 out = subprocess.check_output(['resolvectl', 'query', 'megasearch.net'])
b9fe94ca
MP
656 self.assertIn(b'megasearch.net: 192.168.42.1', out)
657
658 with open(self.dnsmasq_log) as f:
659 general_log = f.read()
660 with open(vpn_dnsmasq_log) as f:
661 vpn_log = f.read()
662
663 # VPN domains should only be sent to VPN DNS
664 self.assertRegex(vpn_log, 'query.*math.lab')
665 self.assertRegex(vpn_log, 'query.*cantina.company')
27e2e323
MP
666 self.assertNotIn('.lab', general_log)
667 self.assertNotIn('.company', general_log)
b9fe94ca
MP
668
669 # general domains should not be sent to the VPN DNS
670 self.assertRegex(general_log, 'query.*megasearch.net')
671 self.assertNotIn('megasearch.net', vpn_log)
672
4050e04b
MP
673 def test_resolved_etc_hosts(self):
674 '''resolved queries to /etc/hosts'''
675
676 # FIXME: -t MX query fails with enabled DNSSEC (even when using
ca56805c 677 # the known negative trust anchor .internal instead of .example.com)
4050e04b
MP
678 conf = '/run/systemd/resolved.conf.d/test-disable-dnssec.conf'
679 os.makedirs(os.path.dirname(conf), exist_ok=True)
680 with open(conf, 'w') as f:
17508549 681 f.write('[Resolve]\nDNSSEC=no\nLLMNR=no\nMulticastDNS=no\n')
4050e04b
MP
682 self.addCleanup(os.remove, conf)
683
ca56805c 684 # create /etc/hosts bind mount which resolves my.example.com for IPv4
4050e04b
MP
685 hosts = os.path.join(self.workdir, 'hosts')
686 with open(hosts, 'w') as f:
ca56805c 687 f.write('172.16.99.99 my.example.com\n')
4050e04b
MP
688 subprocess.check_call(['mount', '--bind', hosts, '/etc/hosts'])
689 self.addCleanup(subprocess.call, ['umount', '/etc/hosts'])
690 subprocess.check_call(['systemctl', 'stop', 'systemd-resolved.service'])
691
692 # note: different IPv4 address here, so that it's easy to tell apart
693 # what resolved the query
ca56805c
MP
694 self.create_iface(dnsmasq_opts=['--host-record=my.example.com,172.16.99.1,2600::99:99',
695 '--host-record=other.example.com,172.16.0.42,2600::42',
696 '--mx-host=example.com,mail.example.com'],
4050e04b
MP
697 ipv6=True)
698 self.do_test(coldplug=None, ipv6=True)
699
700 try:
701 # family specific queries
ca56805c
MP
702 out = subprocess.check_output(['resolvectl', 'query', '-4', 'my.example.com'])
703 self.assertIn(b'my.example.com: 172.16.99.99', out)
4050e04b
MP
704 # we don't expect an IPv6 answer; if /etc/hosts has any IP address,
705 # it's considered a sufficient source
ca56805c 706 self.assertNotEqual(subprocess.call(['resolvectl', 'query', '-6', 'my.example.com']), 0)
4050e04b 707 # "any family" query; IPv4 should come from /etc/hosts
ca56805c
MP
708 out = subprocess.check_output(['resolvectl', 'query', 'my.example.com'])
709 self.assertIn(b'my.example.com: 172.16.99.99', out)
4050e04b 710 # IP → name lookup; again, takes the /etc/hosts one
3deb28f2 711 out = subprocess.check_output(['resolvectl', 'query', '172.16.99.99'])
ca56805c 712 self.assertIn(b'172.16.99.99: my.example.com', out)
4050e04b
MP
713
714 # non-address RRs should fall back to DNS
ca56805c
MP
715 out = subprocess.check_output(['resolvectl', 'query', '--type=MX', 'example.com'])
716 self.assertIn(b'example.com IN MX 1 mail.example.com', out)
4050e04b
MP
717
718 # other domains query DNS
ca56805c 719 out = subprocess.check_output(['resolvectl', 'query', 'other.example.com'])
4050e04b 720 self.assertIn(b'172.16.0.42', out)
3deb28f2 721 out = subprocess.check_output(['resolvectl', 'query', '172.16.0.42'])
ca56805c 722 self.assertIn(b'172.16.0.42: other.example.com', out)
4050e04b
MP
723 except (AssertionError, subprocess.CalledProcessError):
724 self.show_journal('systemd-resolved.service')
725 self.print_server_log()
726 raise
727
e8c0de91
MP
728 def test_transient_hostname(self):
729 '''networkd sets transient hostname from DHCP'''
730
89748b0a
MP
731 orig_hostname = socket.gethostname()
732 self.addCleanup(socket.sethostname, orig_hostname)
733 # temporarily move /etc/hostname away; restart hostnamed to pick it up
734 if os.path.exists('/etc/hostname'):
735 subprocess.check_call(['mount', '--bind', '/dev/null', '/etc/hostname'])
736 self.addCleanup(subprocess.call, ['umount', '/etc/hostname'])
737 subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
8e0ba0c9 738 self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service'])
89748b0a 739
278391c2 740 self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)])
e8c0de91
MP
741 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=False', dhcp_mode='ipv4')
742
fd0cec03
MP
743 try:
744 # should have received the fixed IP above
745 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
746 self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic')
2926b130
MP
747 # should have set transient hostname in hostnamed; this is
748 # sometimes a bit lagging (issue #4753), so retry a few times
749 for retry in range(1, 6):
750 out = subprocess.check_output(['hostnamectl'])
751 if b'testgreen' in out:
752 break
753 time.sleep(5)
754 sys.stdout.write('[retry %i] ' % retry)
755 sys.stdout.flush()
756 else:
278391c2 757 self.fail('Transient hostname not found in hostnamectl:\n{}'.format(out.decode()))
fd0cec03
MP
758 # and also applied to the system
759 self.assertEqual(socket.gethostname(), 'testgreen')
760 except AssertionError:
761 self.show_journal('systemd-networkd.service')
762 self.show_journal('systemd-hostnamed.service')
763 self.print_server_log()
764 raise
89748b0a
MP
765
766 def test_transient_hostname_with_static(self):
767 '''transient hostname is not applied if static hostname exists'''
768
769 orig_hostname = socket.gethostname()
770 self.addCleanup(socket.sethostname, orig_hostname)
0373fc5b 771
89748b0a 772 if not os.path.exists('/etc/hostname'):
0373fc5b
LP
773 self.write_config('/etc/hostname', "foobarqux")
774 else:
775 self.write_config('/run/hostname.tmp', "foobarqux")
776 subprocess.check_call(['mount', '--bind', '/run/hostname.tmp', '/etc/hostname'])
777 self.addCleanup(subprocess.call, ['umount', '/etc/hostname'])
778
779 socket.sethostname("foobarqux");
780
89748b0a 781 subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
8e0ba0c9 782 self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service'])
89748b0a 783
278391c2 784 self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)])
89748b0a
MP
785 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=False', dhcp_mode='ipv4')
786
fd0cec03
MP
787 try:
788 # should have received the fixed IP above
789 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
790 self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic')
791 # static hostname wins over transient one, thus *not* applied
0373fc5b 792 self.assertEqual(socket.gethostname(), "foobarqux")
fd0cec03
MP
793 except AssertionError:
794 self.show_journal('systemd-networkd.service')
795 self.show_journal('systemd-hostnamed.service')
796 self.print_server_log()
797 raise
e8c0de91 798
4ddb85b1
MP
799
800class NetworkdClientTest(ClientTestBase, unittest.TestCase):
801 '''Test networkd client against networkd server'''
802
803 def setUp(self):
804 super().setUp()
805 self.dnsmasq = None
806
2c99aba7 807 def create_iface(self, ipv6=False, dhcpserver_opts=None):
4ddb85b1
MP
808 '''Create test interface with DHCP server behind it'''
809
810 # run "router-side" networkd in own mount namespace to shield it from
811 # "client-side" configuration and networkd
812 (fd, script) = tempfile.mkstemp(prefix='networkd-router.sh')
813 self.addCleanup(os.remove, script)
814 with os.fdopen(fd, 'w+') as f:
38d78d1e 815 f.write('''\
7629744a 816#!/bin/sh
817set -eu
4ddb85b1
MP
818mkdir -p /run/systemd/network
819mkdir -p /run/systemd/netif
820mount -t tmpfs none /run/systemd/network
821mount -t tmpfs none /run/systemd/netif
822[ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus
823# create router/client veth pair
824cat << EOF > /run/systemd/network/test.netdev
825[NetDev]
826Name=%(ifr)s
827Kind=veth
828
829[Peer]
830Name=%(ifc)s
831EOF
832
833cat << EOF > /run/systemd/network/test.network
834[Match]
835Name=%(ifr)s
836
837[Network]
838Address=192.168.5.1/24
839%(addr6)s
840DHCPServer=yes
841
842[DHCPServer]
843PoolOffset=10
844PoolSize=50
845DNS=192.168.5.1
2c99aba7 846%(dhopts)s
4ddb85b1
MP
847EOF
848
849# run networkd as in systemd-networkd.service
5ed0dcf4 850exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ { s/^.*=//; s/^[@+-]//; s/^!*//; p}')
2c99aba7
MP
851''' % {'ifr': self.if_router, 'ifc': self.iface, 'addr6': ipv6 and 'Address=2600::1/64' or '',
852 'dhopts': dhcpserver_opts or ''})
4ddb85b1
MP
853
854 os.fchmod(fd, 0o755)
855
856 subprocess.check_call(['systemd-run', '--unit=networkd-test-router.service',
857 '-p', 'InaccessibleDirectories=-/etc/systemd/network',
858 '-p', 'InaccessibleDirectories=-/run/systemd/network',
859 '-p', 'InaccessibleDirectories=-/run/systemd/netif',
860 '--service-type=notify', script])
861
862 # wait until devices got created
00d5eaaf 863 for _ in range(50):
4ddb85b1
MP
864 out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.if_router])
865 if b'state UP' in out and b'scope global' in out:
866 break
867 time.sleep(0.1)
868
869 def shutdown_iface(self):
870 '''Remove test interface and stop DHCP server'''
871
872 if self.if_router:
873 subprocess.check_call(['systemctl', 'stop', 'networkd-test-router.service'])
874 # ensure failed transient unit does not stay around
875 subprocess.call(['systemctl', 'reset-failed', 'networkd-test-router.service'])
876 subprocess.call(['ip', 'link', 'del', 'dev', self.if_router])
877 self.if_router = None
878
879 def print_server_log(self):
880 '''Print DHCP server log for debugging failures'''
881
882 self.show_journal('networkd-test-router.service')
883
884 @unittest.skip('networkd does not have DHCPv6 server support')
885 def test_hotplug_dhcp_ip6(self):
886 pass
887
888 @unittest.skip('networkd does not have DHCPv6 server support')
889 def test_coldplug_dhcp_ip6(self):
890 pass
891
d2bc1251
MP
892 def test_search_domains(self):
893
894 # we don't use this interface for this test
895 self.if_router = None
896
ec89276c 897 self.write_network('test.netdev', '''\
38d78d1e 898[NetDev]
d2bc1251
MP
899Name=dummy0
900Kind=dummy
901MACAddress=12:34:56:78:9a:bc''')
ec89276c 902 self.write_network('test.network', '''\
38d78d1e 903[Match]
d2bc1251
MP
904Name=dummy0
905[Network]
829c0672 906Address=192.168.42.100/24
d2bc1251
MP
907DNS=192.168.42.1
908Domains= one two three four five six seven eight nine ten''')
d2bc1251 909
74c13b76 910 self.start_unit('systemd-networkd')
d2bc1251 911
30b42a9a
MP
912 for timeout in range(50):
913 with open(RESOLV_CONF) as f:
914 contents = f.read()
915 if ' one' in contents:
916 break
917 time.sleep(0.1)
918 self.assertRegex(contents, 'search .*one two three four')
919 self.assertNotIn('seven\n', contents)
920 self.assertIn('# Too many search domains configured, remaining ones ignored.\n', contents)
d2bc1251
MP
921
922 def test_search_domains_too_long(self):
923
924 # we don't use this interface for this test
925 self.if_router = None
926
927 name_prefix = 'a' * 60
928
ec89276c 929 self.write_network('test.netdev', '''\
38d78d1e 930[NetDev]
d2bc1251
MP
931Name=dummy0
932Kind=dummy
933MACAddress=12:34:56:78:9a:bc''')
ec89276c 934 self.write_network('test.network', '''\
38d78d1e 935[Match]
d2bc1251
MP
936Name=dummy0
937[Network]
829c0672 938Address=192.168.42.100/24
d2bc1251 939DNS=192.168.42.1
38d78d1e 940Domains={p}0 {p}1 {p}2 {p}3 {p}4'''.format(p=name_prefix))
d2bc1251 941
74c13b76 942 self.start_unit('systemd-networkd')
d2bc1251 943
30b42a9a
MP
944 for timeout in range(50):
945 with open(RESOLV_CONF) as f:
946 contents = f.read()
947 if ' one' in contents:
948 break
949 time.sleep(0.1)
38d78d1e 950 self.assertRegex(contents, 'search .*{p}0 {p}1 {p}2'.format(p=name_prefix))
30b42a9a 951 self.assertIn('# Total length of all search domains is too long, remaining ones ignored.', contents)
d2bc1251 952
047a0dac
JSB
953 def test_dropin(self):
954 # we don't use this interface for this test
955 self.if_router = None
956
ec89276c 957 self.write_network('test.netdev', '''\
047a0dac
JSB
958[NetDev]
959Name=dummy0
960Kind=dummy
961MACAddress=12:34:56:78:9a:bc''')
ec89276c 962 self.write_network('test.network', '''\
047a0dac
JSB
963[Match]
964Name=dummy0
965[Network]
829c0672 966Address=192.168.42.100/24
047a0dac 967DNS=192.168.42.1''')
ec89276c 968 self.write_network_dropin('test.network', 'dns', '''\
047a0dac
JSB
969[Network]
970DNS=127.0.0.1''')
971
74c13b76
MP
972 self.start_unit('systemd-resolved')
973 self.start_unit('systemd-networkd')
047a0dac
JSB
974
975 for timeout in range(50):
976 with open(RESOLV_CONF) as f:
977 contents = f.read()
f5cf985e 978 if ' 127.0.0.1' in contents and '192.168.42.1' in contents:
047a0dac
JSB
979 break
980 time.sleep(0.1)
981 self.assertIn('nameserver 192.168.42.1\n', contents)
982 self.assertIn('nameserver 127.0.0.1\n', contents)
983
2c99aba7
MP
984 def test_dhcp_timezone(self):
985 '''networkd sets time zone from DHCP'''
986
987 def get_tz():
988 out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.timedate1',
989 '/org/freedesktop/timedate1', 'org.freedesktop.timedate1', 'Timezone'])
990 assert out.startswith(b's "')
991 out = out.strip()
992 assert out.endswith(b'"')
993 return out[3:-1].decode()
994
995 orig_timezone = get_tz()
996 self.addCleanup(subprocess.call, ['timedatectl', 'set-timezone', orig_timezone])
997
998 self.create_iface(dhcpserver_opts='EmitTimezone=yes\nTimezone=Pacific/Honolulu')
999 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4')
1000
1001 # should have applied the received timezone
1002 try:
1003 self.assertEqual(get_tz(), 'Pacific/Honolulu')
1004 except AssertionError:
1005 self.show_journal('systemd-networkd.service')
1006 self.show_journal('systemd-hostnamed.service')
1007 raise
1008
1009
618b196e
DM
1010class MatchClientTest(unittest.TestCase, NetworkdTestingUtilities):
1011 """Test [Match] sections in .network files.
1012
1013 Be aware that matching the test host's interfaces will wipe their
1014 configuration, so as a precaution, all network files should have a
1015 restrictive [Match] section to only ever interfere with the
1016 temporary veth interfaces created here.
1017 """
1018
1019 def tearDown(self):
1020 """Stop networkd."""
1021 subprocess.call(['systemctl', 'stop', 'systemd-networkd'])
1022
1023 def test_basic_matching(self):
1024 """Verify the Name= line works throughout this class."""
1025 self.add_veth_pair('test_if1', 'fake_if2')
1026 self.write_network('test.network', "[Match]\nName=test_*\n[Network]")
1027 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1028 self.assert_link_states(test_if1='managed', fake_if2='unmanaged')
1029
1030 def test_inverted_matching(self):
1031 """Verify that a '!'-prefixed value inverts the match."""
1032 # Use a MAC address as the interfaces' common matching attribute
1033 # to avoid depending on udev, to support testing in containers.
1034 mac = '00:01:02:03:98:99'
1035 self.add_veth_pair('test_veth', 'test_peer',
1036 ['addr', mac], ['addr', mac])
1037 self.write_network('no-veth.network', """\
1038[Match]
278391c2 1039MACAddress={}
618b196e 1040Name=!nonexistent *peer*
278391c2 1041[Network]""".format(mac))
618b196e
DM
1042 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1043 self.assert_link_states(test_veth='managed', test_peer='unmanaged')
1044
1045
a09dc546
DM
1046class UnmanagedClientTest(unittest.TestCase, NetworkdTestingUtilities):
1047 """Test if networkd manages the correct interfaces."""
1048
1049 def setUp(self):
1050 """Write .network files to match the named veth devices."""
1051 # Define the veth+peer pairs to be created.
1052 # Their pairing doesn't actually matter, only their names do.
1053 self.veths = {
1054 'm1def': 'm0unm',
1055 'm1man': 'm1unm',
1056 }
1057
1058 # Define the contents of .network files to be read in order.
1059 self.configs = (
1060 "[Match]\nName=m1def\n",
1061 "[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n",
1062 "[Match]\nName=m1*\n[Link]\nUnmanaged=no\n",
1063 )
1064
1065 # Write out the .network files to be cleaned up automatically.
1066 for i, config in enumerate(self.configs):
1067 self.write_network("%02d-test.network" % i, config)
1068
1069 def tearDown(self):
1070 """Stop networkd."""
1071 subprocess.call(['systemctl', 'stop', 'systemd-networkd'])
1072
1073 def create_iface(self):
1074 """Create temporary veth pairs for interface matching."""
1075 for veth, peer in self.veths.items():
618b196e 1076 self.add_veth_pair(veth, peer)
a09dc546
DM
1077
1078 def test_unmanaged_setting(self):
1079 """Verify link states with Unmanaged= settings, hot-plug."""
1080 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1081 self.create_iface()
1082 self.assert_link_states(m1def='managed',
1083 m1man='managed',
1084 m1unm='unmanaged',
1085 m0unm='unmanaged')
1086
1087 def test_unmanaged_setting_coldplug(self):
1088 """Verify link states with Unmanaged= settings, cold-plug."""
1089 self.create_iface()
1090 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1091 self.assert_link_states(m1def='managed',
1092 m1man='managed',
1093 m1unm='unmanaged',
1094 m0unm='unmanaged')
1095
1096 def test_catchall_config(self):
1097 """Verify link states with a catch-all config, hot-plug."""
1098 # Don't actually catch ALL interfaces. It messes up the host.
1099 self.write_network('all.network', "[Match]\nName=m[01]???\n")
1100 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1101 self.create_iface()
1102 self.assert_link_states(m1def='managed',
1103 m1man='managed',
1104 m1unm='unmanaged',
1105 m0unm='managed')
1106
1107 def test_catchall_config_coldplug(self):
1108 """Verify link states with a catch-all config, cold-plug."""
1109 # Don't actually catch ALL interfaces. It messes up the host.
1110 self.write_network('all.network', "[Match]\nName=m[01]???\n")
1111 self.create_iface()
1112 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
1113 self.assert_link_states(m1def='managed',
1114 m1man='managed',
1115 m1unm='unmanaged',
1116 m0unm='managed')
1117
1118
4ddb85b1
MP
1119if __name__ == '__main__':
1120 unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout,
1121 verbosity=2))