1# Lint as: python2, python3
2# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""
7Encapsulate functionality of the dhcpd Daemon. Support writing out a
8configuration file as well as starting and stopping the service.
9"""
10
11import os
12import signal
13
14from autotest_lib.client.common_lib import error
15from autotest_lib.client.common_lib import utils
16
17# Filenames used for execution.
18DHCPV6_SERVER_EXECUTABLE = '/usr/local/sbin/dhcpd'
19DHCPV6_SERVER_CONFIG_FILE = '/tmp/dhcpv6_test.conf'
20DHCPV6_SERVER_PID_FILE = '/tmp/dhcpv6_test.pid'
21
22DHCPV6_SERVER_ADDRESS = '2001:db8:0:1::1'
23DHCPV6_SERVER_SUBNET_PREFIX = '2001:db8:0:1::'
24DHCPV6_SERVER_SUBNET_PREFIX_LENGTH = 64
25DHCPV6_ADDRESS_RANGE_LOW = 0x100
26DHCPV6_ADDRESS_RANGE_HIGH = 0x1ff
27DHCPV6_PREFIX_DELEGATION_INDEX_LOW = 0x1
28DHCPV6_PREFIX_DELEGATION_INDEX_HIGH = 0xf
29DHCPV6_PREFIX_DELEGATION_RANGE_FORMAT = '2001:db8:0:%x00::'
30DHCPV6_PREFIX_DELEGATION_RANGE_LOW = (DHCPV6_PREFIX_DELEGATION_RANGE_FORMAT %
31                                      (DHCPV6_PREFIX_DELEGATION_INDEX_LOW))
32DHCPV6_PREFIX_DELEGATION_RANGE_HIGH = (DHCPV6_PREFIX_DELEGATION_RANGE_FORMAT %
33                                       (DHCPV6_PREFIX_DELEGATION_INDEX_HIGH))
34DHCPV6_PREFIX_DELEGATION_PREFIX_LENGTH = 56
35DHCPV6_DEFAULT_LEASE_TIME = 600
36DHCPV6_MAX_LEASE_TIME = 7200
37DHCPV6_NAME_SERVERS = 'fec0:0:0:1::1'
38DHCPV6_DOMAIN_SEARCH = 'domain.example'
39
40CONFIG_DEFAULT_LEASE_TIME = 'default_lease_time'
41CONFIG_MAX_LEASE_TIME = 'max_lease_time'
42CONFIG_SUBNET = 'subnet'
43CONFIG_RANGE = 'range'
44CONFIG_NAME_SERVERS = 'name_servers'
45CONFIG_DOMAIN_SEARCH = 'domain_search'
46CONFIG_PREFIX_RANGE = 'prefix_range'
47
48class Dhcpv6TestServer(object):
49    """
50    This is an embodiment of the DHCPv6 server (dhcpd) process.  It converts an
51    config dict into parameters for the dhcpd configuration file and
52    manages startup and cleanup of the process.
53    """
54
55    def __init__(self, interface = None):
56        if not os.path.exists(DHCPV6_SERVER_EXECUTABLE):
57            raise error.TestNAError('Could not find executable %s; '
58                                    'this is likely an old version of '
59                                    'ChromiumOS' %
60                                    DHCPV6_SERVER_EXECUTABLE)
61        self._interface = interface
62        # "2001:db8:0:1::/64"
63        subnet = '%s/%d' % (DHCPV6_SERVER_SUBNET_PREFIX,
64                            DHCPV6_SERVER_SUBNET_PREFIX_LENGTH)
65        # "2001:db8:0:1::100 2001:db8:1::1ff"
66        range = '%s%x %s%x' % (DHCPV6_SERVER_SUBNET_PREFIX,
67                               DHCPV6_ADDRESS_RANGE_LOW,
68                               DHCPV6_SERVER_SUBNET_PREFIX,
69                               DHCPV6_ADDRESS_RANGE_HIGH)
70        # "2001:db8:0:100:: 2001:db8:1:f00:: /56"
71        prefix_range = '%s %s /%d' % (DHCPV6_PREFIX_DELEGATION_RANGE_LOW,
72                                      DHCPV6_PREFIX_DELEGATION_RANGE_HIGH,
73                                      DHCPV6_PREFIX_DELEGATION_PREFIX_LENGTH)
74        self._config = {
75            CONFIG_DEFAULT_LEASE_TIME: DHCPV6_DEFAULT_LEASE_TIME,
76            CONFIG_MAX_LEASE_TIME: DHCPV6_MAX_LEASE_TIME,
77            CONFIG_SUBNET: subnet,
78            CONFIG_RANGE: range,
79            CONFIG_NAME_SERVERS: DHCPV6_NAME_SERVERS,
80            CONFIG_DOMAIN_SEARCH: DHCPV6_DOMAIN_SEARCH,
81            CONFIG_PREFIX_RANGE: prefix_range
82        }
83
84
85    def _write_config_file(self):
86        """
87        Write out a configuration file for DHCPv6 server to use.
88        """
89        config = '\n'.join([
90                     'default-lease-time %(default_lease_time)d;',
91                     'max-lease-time %(max_lease_time)d;',
92                     'subnet6 %(subnet)s {',
93                     '  range6 %(range)s;',
94                     '  option dhcp6.name-servers %(name_servers)s;',
95                     '  option dhcp6.domain-search \"%(domain_search)s\";',
96                     '  prefix6 %(prefix_range)s;',
97                     '}'
98                     '']) % self._config
99        with open(DHCPV6_SERVER_CONFIG_FILE, 'w') as f:
100            f.write(config)
101
102
103    def _cleanup(self):
104        """
105        Cleanup temporary files.  If PID file exists, also kill the
106        associated process.
107        """
108        if os.path.exists(DHCPV6_SERVER_PID_FILE):
109            with open(DHCPV6_SERVER_PID_FILE) as rf:
110                pid = int(rf.read())
111            os.remove(DHCPV6_SERVER_PID_FILE)
112            try:
113                os.kill(pid, signal.SIGTERM)
114            except OSError:
115                pass
116        if os.path.exists(DHCPV6_SERVER_CONFIG_FILE):
117            os.remove(DHCPV6_SERVER_CONFIG_FILE)
118
119
120    def start(self):
121        """
122        Start the DHCPv6 server.  The server will daemonize itself and
123        run in the background.
124        """
125        self._cleanup()
126        self._write_config_file()
127        utils.system('%s -6 -pf %s -cf %s %s' %
128                     (DHCPV6_SERVER_EXECUTABLE,
129                      DHCPV6_SERVER_PID_FILE,
130                      DHCPV6_SERVER_CONFIG_FILE,
131                      self._interface))
132
133
134    def stop(self):
135        """
136        Halt the DHCPv6 server.
137        """
138        self._cleanup()
139