#! /usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # # tasst - Test A Simple Socket Transport # library of test helpers for passt & pasta # # tasst/dhcpv6.py - Helpers for testing DHCPv6 # # Copyright Red Hat # Author: David Gibson import contextlib import ipaddress import os from tasst import Tasst, TasstSubData from tasst.address import IpiAllocator, TEST_NET6_TASST_A from tasst.nstool import UnshareSite from tasst.site import Site from tasst.typing import typecheck class Dhcpv6TasstInfo: def __init__(self, site, ifname, addr): self.site = typecheck(site, Site) self.ifname = typecheck(ifname, str) self.addr = typecheck(addr, ipaddress.IPv6Address) site.require_cmds('ip') class BaseDhcpv6Tasst(Tasst): """ Test DHCPv6 behaviour. :avocado: disable """ DHCLIENT = '/sbin/dhclient' def setup_dhcpv6(self): raise NotImplementedError("{} must implement setup_dhcpv6() method".format(type(self).__name__)) @contextlib.contextmanager def dhclientv6(self): with self.setup_dhcpv6() as d6ti: d6ti.site.require_cmds(self.DHCLIENT) pidfile = os.path.join(self.workdir, 'dhclient.pid') leasefile = os.path.join(self.workdir, 'dhclient.leases') # We need '-nc' because we may be running with # capabilities but not UID 0. Without -nc dhclient drops # capabilities before invoking dhclient-script, so it's # unable to actually configure the interface d6ti.site.fg('{} -6 -v -nc -pf {} -lf {} {}' .format(self.DHCLIENT, pidfile, leasefile, d6ti.ifname), sudo=True) yield d6ti pid = int(d6ti.site.output('cat {}'.format(pidfile))) d6ti.site.fg('kill {}'.format(pid)) def test_addr(self): with self.dhclientv6() as d6ti: addrs = [a.ip for a in d6ti.site.addrs(d6ti.ifname, family='inet6', scope='global')] self.assertIn(d6ti.addr, addrs) # Might also have a SLAAC address class MetaDhcpv6Tasst(BaseDhcpv6Tasst): """Ugly workaround for https://github.com/avocado-framework/avocado/issues/5680. Explicitly apply the "meta" tag to inherited tests :avocado: disable :avocado: tags=meta """ def test_addr(self): super().test_addr() class Dhcpd6Tasst(MetaDhcpv6Tasst): """ :avocado: tags=meta """ DHCPD = 'dhcpd' SUBNET = TEST_NET6_TASST_A @contextlib.contextmanager def setup_dhcpv6(self): ifname = 'clientif' server_ifname = 'serverif' with UnshareSite(type(self).__name__ + '.client', '-Un') as client, \ UnshareSite(type(self).__name__ + '.server', '-n', parent=client, sudo=True) as server: server.require_cmds(self.DHCPD) client.veth(ifname, server_ifname, server) # Allocate IPs, and sort out link local addressing ipa = IpiAllocator(self.SUBNET) (server_ip6,) = ipa.next_ipis() (client_ip6,) = ipa.next_ipis() server.ifup('lo') server.ifup(server_ifname, server_ip6) client.ifup('lo') client.ifup(ifname) (server_ip6_ll,) = server.addr_wait(server_ifname, family='inet6', scope='link') # Configure the DHCP server confpath = os.path.join(self.workdir, 'dhcpd.conf') open(confpath, 'w').write(''' subnet6 {} {{ range6 {} {}; }} '''.format(self.SUBNET, client_ip6.ip, client_ip6.ip)) pidpath = os.path.join(self.workdir, 'dhcpd.pid') leasepath = os.path.join(self.workdir, 'dhcpd.leases') open(leasepath, 'w').write('') opts = ('-f -d -6 -cf {} -lf {} -pf {}'.format(confpath, leasepath, pidpath)) server.fg('{} -t {}'.format(self.DHCPD, opts)) # test config with server.bg('{} {}'.format(self.DHCPD, opts), sudo=True) as dhcpd: yield Dhcpv6TasstInfo(client, ifname, client_ip6.ip) pid = int(open(pidpath).read()) server.fg('kill {}'.format(pid)) status = dhcpd.wait()