]> git.ipfire.org Git - thirdparty/systemd.git/blame - test/networkd-test.py
hwdb_parse: adjust indentation and imports following pylint advice
[thirdparty/systemd.git] / test / networkd-test.py
CommitLineData
4ddb85b1
MP
1#!/usr/bin/env python3
2#
3# networkd integration test
4# This uses temporary configuration in /run and temporary veth devices, and
5# does not write anything on disk or change any system configuration;
6# but it assumes (and checks at the beginning) that networkd is not currently
7# running.
daad34df
MP
8#
9# This can be run on a normal installation, in QEMU, nspawn (with
10# --private-network), LXD (with "--config raw.lxc=lxc.aa_profile=unconfined"),
11# or LXC system containers. You need at least the "ip" tool from the iproute
12# package; it is recommended to install dnsmasq too to get full test coverage.
13#
4ddb85b1
MP
14# ATTENTION: This uses the *installed* networkd, not the one from the built
15# source tree.
16#
17# (C) 2015 Canonical Ltd.
18# Author: Martin Pitt <martin.pitt@ubuntu.com>
19#
20# systemd is free software; you can redistribute it and/or modify it
21# under the terms of the GNU Lesser General Public License as published by
22# the Free Software Foundation; either version 2.1 of the License, or
23# (at your option) any later version.
24
25# systemd is distributed in the hope that it will be useful, but
26# WITHOUT ANY WARRANTY; without even the implied warranty of
27# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
28# Lesser General Public License for more details.
29#
30# You should have received a copy of the GNU Lesser General Public License
31# along with systemd; If not, see <http://www.gnu.org/licenses/>.
32
33import os
34import sys
35import time
36import unittest
37import tempfile
38import subprocess
39import shutil
89748b0a 40import socket
4ddb85b1
MP
41
42networkd_active = subprocess.call(['systemctl', 'is-active', '--quiet',
43 'systemd-networkd']) == 0
44have_dnsmasq = shutil.which('dnsmasq')
45
30b42a9a
MP
46RESOLV_CONF = '/run/systemd/resolve/resolv.conf'
47
4ddb85b1
MP
48
49@unittest.skipIf(networkd_active,
50 'networkd is already active')
51class ClientTestBase:
fd0cec03
MP
52 @classmethod
53 def setUpClass(klass):
54 klass.orig_log_level = subprocess.check_output(
55 ['systemctl', 'show', '--value', '--property', 'LogLevel'],
56 universal_newlines=True).strip()
57 subprocess.check_call(['systemd-analyze', 'set-log-level', 'debug'])
58
59 @classmethod
60 def tearDownClass(klass):
61 subprocess.check_call(['systemd-analyze', 'set-log-level', klass.orig_log_level])
62
4ddb85b1
MP
63 def setUp(self):
64 self.iface = 'test_eth42'
65 self.if_router = 'router_eth42'
66 self.workdir_obj = tempfile.TemporaryDirectory()
67 self.workdir = self.workdir_obj.name
68 self.config = '/run/systemd/network/test_eth42.network'
4ddb85b1
MP
69
70 # avoid "Failed to open /dev/tty" errors in containers
71 os.environ['SYSTEMD_LOG_TARGET'] = 'journal'
72
73 # determine path to systemd-networkd-wait-online
74 for p in ['/usr/lib/systemd/systemd-networkd-wait-online',
75 '/lib/systemd/systemd-networkd-wait-online']:
76 if os.path.exists(p):
77 self.networkd_wait_online = p
78 break
79 else:
80 self.fail('systemd-networkd-wait-online not found')
81
82 # get current journal cursor
fd0cec03 83 subprocess.check_output(['journalctl', '--sync'])
4ddb85b1
MP
84 out = subprocess.check_output(['journalctl', '-b', '--quiet',
85 '--no-pager', '-n0', '--show-cursor'],
86 universal_newlines=True)
87 self.assertTrue(out.startswith('-- cursor:'))
88 self.journal_cursor = out.split()[-1]
89
90 def tearDown(self):
91 self.shutdown_iface()
4ddb85b1 92 subprocess.call(['systemctl', 'stop', 'systemd-networkd'])
9e0c296a
MP
93 subprocess.call(['ip', 'link', 'del', 'dummy0'],
94 stderr=subprocess.DEVNULL)
4ddb85b1 95
38d78d1e
ZJS
96 def writeConfig(self, fname, contents):
97 os.makedirs(os.path.dirname(fname), exist_ok=True)
98 with open(fname, 'w') as f:
99 f.write(contents)
100 self.addCleanup(os.remove, fname)
101
4ddb85b1
MP
102 def show_journal(self, unit):
103 '''Show journal of given unit since start of the test'''
104
105 print('---- %s ----' % unit)
fd0cec03 106 subprocess.check_output(['journalctl', '--sync'])
4ddb85b1
MP
107 sys.stdout.flush()
108 subprocess.call(['journalctl', '-b', '--no-pager', '--quiet',
109 '--cursor', self.journal_cursor, '-u', unit])
110
111 def create_iface(self, ipv6=False):
112 '''Create test interface with DHCP server behind it'''
113
114 raise NotImplementedError('must be implemented by a subclass')
115
116 def shutdown_iface(self):
117 '''Remove test interface and stop DHCP server'''
118
119 raise NotImplementedError('must be implemented by a subclass')
120
121 def print_server_log(self):
122 '''Print DHCP server log for debugging failures'''
123
124 raise NotImplementedError('must be implemented by a subclass')
125
126 def do_test(self, coldplug=True, ipv6=False, extra_opts='',
127 online_timeout=10, dhcp_mode='yes'):
30b42a9a 128 subprocess.check_call(['systemctl', 'start', 'systemd-resolved'])
38d78d1e
ZJS
129 self.writeConfig(self.config, '''\
130[Match]
4ddb85b1
MP
131Name=%s
132[Network]
133DHCP=%s
134%s''' % (self.iface, dhcp_mode, extra_opts))
135
136 if coldplug:
137 # create interface first, then start networkd
138 self.create_iface(ipv6=ipv6)
139 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
e8c0de91 140 elif coldplug is not None:
4ddb85b1
MP
141 # start networkd first, then create interface
142 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
143 self.create_iface(ipv6=ipv6)
e8c0de91
MP
144 else:
145 # "None" means test sets up interface by itself
146 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
4ddb85b1
MP
147
148 try:
149 subprocess.check_call([self.networkd_wait_online, '--interface',
150 self.iface, '--timeout=%i' % online_timeout])
151
152 if ipv6:
153 # check iface state and IP 6 address; FIXME: we need to wait a bit
154 # longer, as the iface is "configured" already with IPv4 *or*
155 # IPv6, but we want to wait for both
00d5eaaf 156 for _ in range(10):
4ddb85b1
MP
157 out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.iface])
158 if b'state UP' in out and b'inet6 2600' in out and b'inet 192.168' in out:
159 break
160 time.sleep(1)
161 else:
162 self.fail('timed out waiting for IPv6 configuration')
163
164 self.assertRegex(out, b'inet6 2600::.* scope global .*dynamic')
165 self.assertRegex(out, b'inet6 fe80::.* scope link')
166 else:
167 # should have link-local address on IPv6 only
168 out = subprocess.check_output(['ip', '-6', 'a', 'show', 'dev', self.iface])
169 self.assertRegex(out, b'inet6 fe80::.* scope link')
170 self.assertNotIn(b'scope global', out)
171
172 # should have IPv4 address
173 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
174 self.assertIn(b'state UP', out)
175 self.assertRegex(out, b'inet 192.168.5.\d+/.* scope global dynamic')
176
177 # check networkctl state
178 out = subprocess.check_output(['networkctl'])
179 self.assertRegex(out, ('%s\s+ether\s+routable\s+unmanaged' % self.if_router).encode())
180 self.assertRegex(out, ('%s\s+ether\s+routable\s+configured' % self.iface).encode())
181
182 out = subprocess.check_output(['networkctl', 'status', self.iface])
183 self.assertRegex(out, b'Type:\s+ether')
184 self.assertRegex(out, b'State:\s+routable.*configured')
185 self.assertRegex(out, b'Address:\s+192.168.5.\d+')
186 if ipv6:
187 self.assertRegex(out, b'2600::')
188 else:
189 self.assertNotIn(b'2600::', out)
190 self.assertRegex(out, b'fe80::')
191 self.assertRegex(out, b'Gateway:\s+192.168.5.1')
192 self.assertRegex(out, b'DNS:\s+192.168.5.1')
193 except (AssertionError, subprocess.CalledProcessError):
194 # show networkd status, journal, and DHCP server log on failure
195 with open(self.config) as f:
196 print('\n---- %s ----\n%s' % (self.config, f.read()))
197 print('---- interface status ----')
198 sys.stdout.flush()
199 subprocess.call(['ip', 'a', 'show', 'dev', self.iface])
200 print('---- networkctl status %s ----' % self.iface)
201 sys.stdout.flush()
202 subprocess.call(['networkctl', 'status', self.iface])
203 self.show_journal('systemd-networkd.service')
204 self.print_server_log()
205 raise
206
30b42a9a
MP
207 for timeout in range(50):
208 with open(RESOLV_CONF) as f:
209 contents = f.read()
210 if 'nameserver 192.168.5.1\n' in contents:
211 break
212 time.sleep(0.1)
213 else:
214 self.fail('nameserver 192.168.5.1 not found in ' + RESOLV_CONF)
4ddb85b1 215
e8c0de91 216 if coldplug is False:
4ddb85b1
MP
217 # check post-down.d hook
218 self.shutdown_iface()
219
220 def test_coldplug_dhcp_yes_ip4(self):
221 # we have a 12s timeout on RA, so we need to wait longer
222 self.do_test(coldplug=True, ipv6=False, online_timeout=15)
223
224 def test_coldplug_dhcp_yes_ip4_no_ra(self):
225 # with disabling RA explicitly things should be fast
226 self.do_test(coldplug=True, ipv6=False,
f921f573 227 extra_opts='IPv6AcceptRA=False')
4ddb85b1
MP
228
229 def test_coldplug_dhcp_ip4_only(self):
230 # we have a 12s timeout on RA, so we need to wait longer
231 self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4',
232 online_timeout=15)
233
234 def test_coldplug_dhcp_ip4_only_no_ra(self):
235 # with disabling RA explicitly things should be fast
236 self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4',
f921f573 237 extra_opts='IPv6AcceptRA=False')
4ddb85b1
MP
238
239 def test_coldplug_dhcp_ip6(self):
240 self.do_test(coldplug=True, ipv6=True)
241
242 def test_hotplug_dhcp_ip4(self):
243 # With IPv4 only we have a 12s timeout on RA, so we need to wait longer
244 self.do_test(coldplug=False, ipv6=False, online_timeout=15)
245
246 def test_hotplug_dhcp_ip6(self):
247 self.do_test(coldplug=False, ipv6=True)
248
94363cbb 249 def test_route_only_dns(self):
38d78d1e
ZJS
250 self.writeConfig('/run/systemd/network/myvpn.netdev', '''\
251[NetDev]
94363cbb
MP
252Name=dummy0
253Kind=dummy
254MACAddress=12:34:56:78:9a:bc''')
38d78d1e
ZJS
255 self.writeConfig('/run/systemd/network/myvpn.network', '''\
256[Match]
94363cbb
MP
257Name=dummy0
258[Network]
259Address=192.168.42.100
260DNS=192.168.42.1
261Domains= ~company''')
94363cbb
MP
262
263 self.do_test(coldplug=True, ipv6=False,
264 extra_opts='IPv6AcceptRouterAdvertisements=False')
265
30b42a9a
MP
266 with open(RESOLV_CONF) as f:
267 contents = f.read()
94363cbb
MP
268 # ~company is not a search domain, only a routing domain
269 self.assertNotRegex(contents, 'search.*company')
30b42a9a
MP
270 # our global server should appear
271 self.assertIn('nameserver 192.168.5.1\n', contents)
b9fe94ca
MP
272 # should not have domain-restricted server as global server
273 self.assertNotIn('nameserver 192.168.42.1\n', contents)
274
275 def test_route_only_dns_all_domains(self):
276 with open('/run/systemd/network/myvpn.netdev', 'w') as f:
277 f.write('''[NetDev]
278Name=dummy0
279Kind=dummy
280MACAddress=12:34:56:78:9a:bc''')
281 with open('/run/systemd/network/myvpn.network', 'w') as f:
282 f.write('''[Match]
283Name=dummy0
284[Network]
285Address=192.168.42.100
286DNS=192.168.42.1
287Domains= ~company ~.''')
288 self.addCleanup(os.remove, '/run/systemd/network/myvpn.netdev')
289 self.addCleanup(os.remove, '/run/systemd/network/myvpn.network')
290
291 self.do_test(coldplug=True, ipv6=False,
292 extra_opts='IPv6AcceptRouterAdvertisements=False')
293
294 with open(RESOLV_CONF) as f:
295 contents = f.read()
296
297 # ~company is not a search domain, only a routing domain
298 self.assertNotRegex(contents, 'search.*company')
299
300 # our global server should appear
301 self.assertIn('nameserver 192.168.5.1\n', contents)
302 # should have company server as global server due to ~.
303 self.assertIn('nameserver 192.168.42.1\n', contents)
94363cbb 304
4ddb85b1
MP
305
306@unittest.skipUnless(have_dnsmasq, 'dnsmasq not installed')
307class DnsmasqClientTest(ClientTestBase, unittest.TestCase):
308 '''Test networkd client against dnsmasq'''
309
310 def setUp(self):
311 super().setUp()
312 self.dnsmasq = None
e8c0de91 313 self.iface_mac = 'de:ad:be:ef:47:11'
4ddb85b1 314
b9fe94ca 315 def create_iface(self, ipv6=False, dnsmasq_opts=None):
4ddb85b1
MP
316 '''Create test interface with DHCP server behind it'''
317
318 # add veth pair
e8c0de91
MP
319 subprocess.check_call(['ip', 'link', 'add', 'name', self.iface,
320 'address', self.iface_mac,
321 'type', 'veth', 'peer', 'name', self.if_router])
4ddb85b1
MP
322
323 # give our router an IP
324 subprocess.check_call(['ip', 'a', 'flush', 'dev', self.if_router])
325 subprocess.check_call(['ip', 'a', 'add', '192.168.5.1/24', 'dev', self.if_router])
326 if ipv6:
327 subprocess.check_call(['ip', 'a', 'add', '2600::1/64', 'dev', self.if_router])
328 subprocess.check_call(['ip', 'link', 'set', self.if_router, 'up'])
329
330 # add DHCP server
331 self.dnsmasq_log = os.path.join(self.workdir, 'dnsmasq.log')
332 lease_file = os.path.join(self.workdir, 'dnsmasq.leases')
333 if ipv6:
334 extra_opts = ['--enable-ra', '--dhcp-range=2600::10,2600::20']
335 else:
336 extra_opts = []
b9fe94ca
MP
337 if dnsmasq_opts:
338 extra_opts += dnsmasq_opts
4ddb85b1
MP
339 self.dnsmasq = subprocess.Popen(
340 ['dnsmasq', '--keep-in-foreground', '--log-queries',
341 '--log-facility=' + self.dnsmasq_log, '--conf-file=/dev/null',
342 '--dhcp-leasefile=' + lease_file, '--bind-interfaces',
343 '--interface=' + self.if_router, '--except-interface=lo',
344 '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts)
345
346 def shutdown_iface(self):
347 '''Remove test interface and stop DHCP server'''
348
349 if self.if_router:
350 subprocess.check_call(['ip', 'link', 'del', 'dev', self.if_router])
351 self.if_router = None
352 if self.dnsmasq:
353 self.dnsmasq.kill()
354 self.dnsmasq.wait()
355 self.dnsmasq = None
356
357 def print_server_log(self):
358 '''Print DHCP server log for debugging failures'''
359
360 with open(self.dnsmasq_log) as f:
361 sys.stdout.write('\n\n---- dnsmasq log ----\n%s\n------\n\n' % f.read())
362
b9fe94ca
MP
363 def test_resolved_domain_restricted_dns(self):
364 '''resolved: domain-restricted DNS servers'''
365
366 # create interface for generic connections; this will map all DNS names
367 # to 192.168.42.1
368 self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1'])
369 self.writeConfig('/run/systemd/network/general.network', '''\
370[Match]
371Name=%s
372[Network]
373DHCP=ipv4
374IPv6AcceptRA=False''' % self.iface)
375
376 # create second device/dnsmasq for a .company/.lab VPN interface
377 # static IPs for simplicity
378 subprocess.check_call(['ip', 'link', 'add', 'name', 'testvpnclient', 'type',
379 'veth', 'peer', 'name', 'testvpnrouter'])
380 self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', 'testvpnrouter'])
381 subprocess.check_call(['ip', 'a', 'flush', 'dev', 'testvpnrouter'])
382 subprocess.check_call(['ip', 'a', 'add', '10.241.3.1/24', 'dev', 'testvpnrouter'])
383 subprocess.check_call(['ip', 'link', 'set', 'testvpnrouter', 'up'])
384
385 vpn_dnsmasq_log = os.path.join(self.workdir, 'dnsmasq-vpn.log')
386 vpn_dnsmasq = subprocess.Popen(
387 ['dnsmasq', '--keep-in-foreground', '--log-queries',
388 '--log-facility=' + vpn_dnsmasq_log, '--conf-file=/dev/null',
389 '--dhcp-leasefile=/dev/null', '--bind-interfaces',
390 '--interface=testvpnrouter', '--except-interface=lo',
391 '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4'])
392 self.addCleanup(vpn_dnsmasq.wait)
393 self.addCleanup(vpn_dnsmasq.kill)
394
395 self.writeConfig('/run/systemd/network/vpn.network', '''\
396[Match]
397Name=testvpnclient
398[Network]
399IPv6AcceptRA=False
400Address=10.241.3.2/24
401DNS=10.241.3.1
402Domains= ~company ~lab''')
403
404 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
405 subprocess.check_call([self.networkd_wait_online, '--interface', self.iface,
406 '--interface=testvpnclient', '--timeout=20'])
407
408 # ensure we start fresh with every test
409 subprocess.check_call(['systemctl', 'restart', 'systemd-resolved'])
410
411 # test vpnclient specific domains; these should *not* be answered by
412 # the general DNS
413 out = subprocess.check_output(['systemd-resolve', 'math.lab'])
414 self.assertIn(b'math.lab: 10.241.3.3', out)
415 out = subprocess.check_output(['systemd-resolve', 'kettle.cantina.company'])
416 self.assertIn(b'kettle.cantina.company: 10.241.4.4', out)
417
418 # test general domains
419 out = subprocess.check_output(['systemd-resolve', 'megasearch.net'])
420 self.assertIn(b'megasearch.net: 192.168.42.1', out)
421
422 with open(self.dnsmasq_log) as f:
423 general_log = f.read()
424 with open(vpn_dnsmasq_log) as f:
425 vpn_log = f.read()
426
427 # VPN domains should only be sent to VPN DNS
428 self.assertRegex(vpn_log, 'query.*math.lab')
429 self.assertRegex(vpn_log, 'query.*cantina.company')
430 self.assertNotIn('lab', general_log)
431 self.assertNotIn('company', general_log)
432
433 # general domains should not be sent to the VPN DNS
434 self.assertRegex(general_log, 'query.*megasearch.net')
435 self.assertNotIn('megasearch.net', vpn_log)
436
e8c0de91
MP
437 def test_transient_hostname(self):
438 '''networkd sets transient hostname from DHCP'''
439
89748b0a
MP
440 orig_hostname = socket.gethostname()
441 self.addCleanup(socket.sethostname, orig_hostname)
442 # temporarily move /etc/hostname away; restart hostnamed to pick it up
443 if os.path.exists('/etc/hostname'):
444 subprocess.check_call(['mount', '--bind', '/dev/null', '/etc/hostname'])
445 self.addCleanup(subprocess.call, ['umount', '/etc/hostname'])
446 subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
447
e8c0de91
MP
448 self.create_iface(dnsmasq_opts=['--dhcp-host=%s,192.168.5.210,testgreen' % self.iface_mac])
449 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=False', dhcp_mode='ipv4')
450
fd0cec03
MP
451 try:
452 # should have received the fixed IP above
453 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
454 self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic')
2926b130
MP
455 # should have set transient hostname in hostnamed; this is
456 # sometimes a bit lagging (issue #4753), so retry a few times
457 for retry in range(1, 6):
458 out = subprocess.check_output(['hostnamectl'])
459 if b'testgreen' in out:
460 break
461 time.sleep(5)
462 sys.stdout.write('[retry %i] ' % retry)
463 sys.stdout.flush()
464 else:
465 self.fail('Transient hostname not found in hostnamectl:\n%s' % out.decode())
fd0cec03
MP
466 # and also applied to the system
467 self.assertEqual(socket.gethostname(), 'testgreen')
468 except AssertionError:
469 self.show_journal('systemd-networkd.service')
470 self.show_journal('systemd-hostnamed.service')
471 self.print_server_log()
472 raise
89748b0a
MP
473
474 def test_transient_hostname_with_static(self):
475 '''transient hostname is not applied if static hostname exists'''
476
477 orig_hostname = socket.gethostname()
478 self.addCleanup(socket.sethostname, orig_hostname)
479 if not os.path.exists('/etc/hostname'):
480 self.writeConfig('/etc/hostname', orig_hostname)
481 subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service'])
482
483 self.create_iface(dnsmasq_opts=['--dhcp-host=%s,192.168.5.210,testgreen' % self.iface_mac])
484 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=False', dhcp_mode='ipv4')
485
fd0cec03
MP
486 try:
487 # should have received the fixed IP above
488 out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface])
489 self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic')
490 # static hostname wins over transient one, thus *not* applied
491 self.assertEqual(socket.gethostname(), orig_hostname)
492 except AssertionError:
493 self.show_journal('systemd-networkd.service')
494 self.show_journal('systemd-hostnamed.service')
495 self.print_server_log()
496 raise
e8c0de91 497
4ddb85b1
MP
498
499class NetworkdClientTest(ClientTestBase, unittest.TestCase):
500 '''Test networkd client against networkd server'''
501
502 def setUp(self):
503 super().setUp()
504 self.dnsmasq = None
505
2c99aba7 506 def create_iface(self, ipv6=False, dhcpserver_opts=None):
4ddb85b1
MP
507 '''Create test interface with DHCP server behind it'''
508
509 # run "router-side" networkd in own mount namespace to shield it from
510 # "client-side" configuration and networkd
511 (fd, script) = tempfile.mkstemp(prefix='networkd-router.sh')
512 self.addCleanup(os.remove, script)
513 with os.fdopen(fd, 'w+') as f:
38d78d1e
ZJS
514 f.write('''\
515#!/bin/sh -eu
4ddb85b1
MP
516mkdir -p /run/systemd/network
517mkdir -p /run/systemd/netif
518mount -t tmpfs none /run/systemd/network
519mount -t tmpfs none /run/systemd/netif
520[ ! -e /run/dbus ] || mount -t tmpfs none /run/dbus
521# create router/client veth pair
522cat << EOF > /run/systemd/network/test.netdev
523[NetDev]
524Name=%(ifr)s
525Kind=veth
526
527[Peer]
528Name=%(ifc)s
529EOF
530
531cat << EOF > /run/systemd/network/test.network
532[Match]
533Name=%(ifr)s
534
535[Network]
536Address=192.168.5.1/24
537%(addr6)s
538DHCPServer=yes
539
540[DHCPServer]
541PoolOffset=10
542PoolSize=50
543DNS=192.168.5.1
2c99aba7 544%(dhopts)s
4ddb85b1
MP
545EOF
546
547# run networkd as in systemd-networkd.service
548exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ { s/^.*=//; p}')
2c99aba7
MP
549''' % {'ifr': self.if_router, 'ifc': self.iface, 'addr6': ipv6 and 'Address=2600::1/64' or '',
550 'dhopts': dhcpserver_opts or ''})
4ddb85b1
MP
551
552 os.fchmod(fd, 0o755)
553
554 subprocess.check_call(['systemd-run', '--unit=networkd-test-router.service',
555 '-p', 'InaccessibleDirectories=-/etc/systemd/network',
556 '-p', 'InaccessibleDirectories=-/run/systemd/network',
557 '-p', 'InaccessibleDirectories=-/run/systemd/netif',
558 '--service-type=notify', script])
559
560 # wait until devices got created
00d5eaaf 561 for _ in range(50):
4ddb85b1
MP
562 out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.if_router])
563 if b'state UP' in out and b'scope global' in out:
564 break
565 time.sleep(0.1)
566
567 def shutdown_iface(self):
568 '''Remove test interface and stop DHCP server'''
569
570 if self.if_router:
571 subprocess.check_call(['systemctl', 'stop', 'networkd-test-router.service'])
572 # ensure failed transient unit does not stay around
573 subprocess.call(['systemctl', 'reset-failed', 'networkd-test-router.service'])
574 subprocess.call(['ip', 'link', 'del', 'dev', self.if_router])
575 self.if_router = None
576
577 def print_server_log(self):
578 '''Print DHCP server log for debugging failures'''
579
580 self.show_journal('networkd-test-router.service')
581
582 @unittest.skip('networkd does not have DHCPv6 server support')
583 def test_hotplug_dhcp_ip6(self):
584 pass
585
586 @unittest.skip('networkd does not have DHCPv6 server support')
587 def test_coldplug_dhcp_ip6(self):
588 pass
589
d2bc1251
MP
590 def test_search_domains(self):
591
592 # we don't use this interface for this test
593 self.if_router = None
594
38d78d1e
ZJS
595 self.writeConfig('/run/systemd/network/test.netdev', '''\
596[NetDev]
d2bc1251
MP
597Name=dummy0
598Kind=dummy
599MACAddress=12:34:56:78:9a:bc''')
38d78d1e
ZJS
600 self.writeConfig('/run/systemd/network/test.network', '''\
601[Match]
d2bc1251
MP
602Name=dummy0
603[Network]
604Address=192.168.42.100
605DNS=192.168.42.1
606Domains= one two three four five six seven eight nine ten''')
d2bc1251
MP
607
608 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
609
30b42a9a
MP
610 for timeout in range(50):
611 with open(RESOLV_CONF) as f:
612 contents = f.read()
613 if ' one' in contents:
614 break
615 time.sleep(0.1)
616 self.assertRegex(contents, 'search .*one two three four')
617 self.assertNotIn('seven\n', contents)
618 self.assertIn('# Too many search domains configured, remaining ones ignored.\n', contents)
d2bc1251
MP
619
620 def test_search_domains_too_long(self):
621
622 # we don't use this interface for this test
623 self.if_router = None
624
625 name_prefix = 'a' * 60
626
38d78d1e
ZJS
627 self.writeConfig('/run/systemd/network/test.netdev', '''\
628[NetDev]
d2bc1251
MP
629Name=dummy0
630Kind=dummy
631MACAddress=12:34:56:78:9a:bc''')
38d78d1e
ZJS
632 self.writeConfig('/run/systemd/network/test.network', '''\
633[Match]
d2bc1251
MP
634Name=dummy0
635[Network]
636Address=192.168.42.100
637DNS=192.168.42.1
38d78d1e 638Domains={p}0 {p}1 {p}2 {p}3 {p}4'''.format(p=name_prefix))
d2bc1251
MP
639
640 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
641
30b42a9a
MP
642 for timeout in range(50):
643 with open(RESOLV_CONF) as f:
644 contents = f.read()
645 if ' one' in contents:
646 break
647 time.sleep(0.1)
38d78d1e 648 self.assertRegex(contents, 'search .*{p}0 {p}1 {p}2'.format(p=name_prefix))
30b42a9a 649 self.assertIn('# Total length of all search domains is too long, remaining ones ignored.', contents)
d2bc1251 650
047a0dac
JSB
651 def test_dropin(self):
652 # we don't use this interface for this test
653 self.if_router = None
654
655 self.writeConfig('/run/systemd/network/test.netdev', '''\
656[NetDev]
657Name=dummy0
658Kind=dummy
659MACAddress=12:34:56:78:9a:bc''')
660 self.writeConfig('/run/systemd/network/test.network', '''\
661[Match]
662Name=dummy0
663[Network]
664Address=192.168.42.100
665DNS=192.168.42.1''')
666 self.writeConfig('/run/systemd/network/test.network.d/dns.conf', '''\
667[Network]
668DNS=127.0.0.1''')
669
670 subprocess.check_call(['systemctl', 'start', 'systemd-networkd'])
671
672 for timeout in range(50):
673 with open(RESOLV_CONF) as f:
674 contents = f.read()
675 if ' 127.0.0.1' in contents:
676 break
677 time.sleep(0.1)
678 self.assertIn('nameserver 192.168.42.1\n', contents)
679 self.assertIn('nameserver 127.0.0.1\n', contents)
680
2c99aba7
MP
681 def test_dhcp_timezone(self):
682 '''networkd sets time zone from DHCP'''
683
684 def get_tz():
685 out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.timedate1',
686 '/org/freedesktop/timedate1', 'org.freedesktop.timedate1', 'Timezone'])
687 assert out.startswith(b's "')
688 out = out.strip()
689 assert out.endswith(b'"')
690 return out[3:-1].decode()
691
692 orig_timezone = get_tz()
693 self.addCleanup(subprocess.call, ['timedatectl', 'set-timezone', orig_timezone])
694
695 self.create_iface(dhcpserver_opts='EmitTimezone=yes\nTimezone=Pacific/Honolulu')
696 self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4')
697
698 # should have applied the received timezone
699 try:
700 self.assertEqual(get_tz(), 'Pacific/Honolulu')
701 except AssertionError:
702 self.show_journal('systemd-networkd.service')
703 self.show_journal('systemd-hostnamed.service')
704 raise
705
706
4ddb85b1
MP
707if __name__ == '__main__':
708 unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout,
709 verbosity=2))