# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""
Encapsulate functionality of the dhcpd Daemon. Support writing out a
configuration file as well as starting and stopping the service.
"""

import os
import signal

from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib import utils

# Filenames used for execution.
DHCPV6_SERVER_EXECUTABLE = '/usr/local/sbin/dhcpd'
DHCPV6_SERVER_CONFIG_FILE = '/tmp/dhcpv6_test.conf'
DHCPV6_SERVER_PID_FILE = '/tmp/dhcpv6_test.pid'

DHCPV6_SERVER_ADDRESS = '2001:db8:0:1::1'
DHCPV6_SERVER_SUBNET_PREFIX = '2001:db8:0:1::'
DHCPV6_SERVER_SUBNET_PREFIX_LENGTH = 64
DHCPV6_ADDRESS_RANGE_LOW = 0x100
DHCPV6_ADDRESS_RANGE_HIGH = 0x1ff
DHCPV6_PREFIX_DELEGATION_INDEX_LOW = 0x1
DHCPV6_PREFIX_DELEGATION_INDEX_HIGH = 0xf
DHCPV6_PREFIX_DELEGATION_RANGE_FORMAT = '2001:db8:0:%x00::'
DHCPV6_PREFIX_DELEGATION_RANGE_LOW = (DHCPV6_PREFIX_DELEGATION_RANGE_FORMAT %
                                      (DHCPV6_PREFIX_DELEGATION_INDEX_LOW))
DHCPV6_PREFIX_DELEGATION_RANGE_HIGH = (DHCPV6_PREFIX_DELEGATION_RANGE_FORMAT %
                                       (DHCPV6_PREFIX_DELEGATION_INDEX_HIGH))
DHCPV6_PREFIX_DELEGATION_PREFIX_LENGTH = 56
DHCPV6_DEFAULT_LEASE_TIME = 600
DHCPV6_MAX_LEASE_TIME = 7200
DHCPV6_NAME_SERVERS = 'fec0:0:0:1::1'
DHCPV6_DOMAIN_SEARCH = 'domain.example'

CONFIG_DEFAULT_LEASE_TIME = 'default_lease_time'
CONFIG_MAX_LEASE_TIME = 'max_lease_time'
CONFIG_SUBNET = 'subnet'
CONFIG_RANGE = 'range'
CONFIG_NAME_SERVERS = 'name_servers'
CONFIG_DOMAIN_SEARCH = 'domain_search'
CONFIG_PREFIX_RANGE = 'prefix_range'

class Dhcpv6TestServer(object):
    """
    This is an embodiment of the DHCPv6 server (dhcpd) process.  It converts an
    config dict into parameters for the dhcpd configuration file and
    manages startup and cleanup of the process.
    """

    def __init__(self, interface = None):
        if not os.path.exists(DHCPV6_SERVER_EXECUTABLE):
            raise error.TestNAError('Could not find executable %s; '
                                    'this is likely an old version of '
                                    'ChromiumOS' %
                                    DHCPV6_SERVER_EXECUTABLE)
        self._interface = interface
        # "2001:db8:0:1::/64"
        subnet = '%s/%d' % (DHCPV6_SERVER_SUBNET_PREFIX,
                            DHCPV6_SERVER_SUBNET_PREFIX_LENGTH)
        # "2001:db8:0:1::100 2001:db8:1::1ff"
        range = '%s%x %s%x' % (DHCPV6_SERVER_SUBNET_PREFIX,
                               DHCPV6_ADDRESS_RANGE_LOW,
                               DHCPV6_SERVER_SUBNET_PREFIX,
                               DHCPV6_ADDRESS_RANGE_HIGH)
        # "2001:db8:0:100:: 2001:db8:1:f00:: /56"
        prefix_range = '%s %s /%d' % (DHCPV6_PREFIX_DELEGATION_RANGE_LOW,
                                      DHCPV6_PREFIX_DELEGATION_RANGE_HIGH,
                                      DHCPV6_PREFIX_DELEGATION_PREFIX_LENGTH)
        self._config = {
            CONFIG_DEFAULT_LEASE_TIME: DHCPV6_DEFAULT_LEASE_TIME,
            CONFIG_MAX_LEASE_TIME: DHCPV6_MAX_LEASE_TIME,
            CONFIG_SUBNET: subnet,
            CONFIG_RANGE: range,
            CONFIG_NAME_SERVERS: DHCPV6_NAME_SERVERS,
            CONFIG_DOMAIN_SEARCH: DHCPV6_DOMAIN_SEARCH,
            CONFIG_PREFIX_RANGE: prefix_range
        }


    def _write_config_file(self):
        """
        Write out a configuration file for DHCPv6 server to use.
        """
        config = '\n'.join([
                     'default-lease-time %(default_lease_time)d;',
                     'max-lease-time %(max_lease_time)d;',
                     'subnet6 %(subnet)s {',
                     '  range6 %(range)s;',
                     '  option dhcp6.name-servers %(name_servers)s;',
                     '  option dhcp6.domain-search \"%(domain_search)s\";',
                     '  prefix6 %(prefix_range)s;',
                     '}'
                     '']) % self._config
        with open(DHCPV6_SERVER_CONFIG_FILE, 'w') as f:
            f.write(config)


    def _cleanup(self):
        """
        Cleanup temporary files.  If PID file exists, also kill the
        associated process.
        """
        if os.path.exists(DHCPV6_SERVER_PID_FILE):
            pid = int(file(DHCPV6_SERVER_PID_FILE).read())
            os.remove(DHCPV6_SERVER_PID_FILE)
            try:
                os.kill(pid, signal.SIGTERM)
            except OSError:
                pass
        if os.path.exists(DHCPV6_SERVER_CONFIG_FILE):
            os.remove(DHCPV6_SERVER_CONFIG_FILE)


    def start(self):
        """
        Start the DHCPv6 server.  The server will daemonize itself and
        run in the background.
        """
        self._cleanup()
        self._write_config_file()
        utils.system('%s -6 -pf %s -cf %s %s' %
                     (DHCPV6_SERVER_EXECUTABLE,
                      DHCPV6_SERVER_PID_FILE,
                      DHCPV6_SERVER_CONFIG_FILE,
                      self._interface))


    def stop(self):
        """
        Halt the DHCPv6 server.
        """
        self._cleanup()