BIND 10 master, updated. f279d996354eded4defa219a393efa362e157406 Merge branch 'work/startcreator'

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Jul 26 14:29:36 UTC 2011


The branch, master has been updated
       via  f279d996354eded4defa219a393efa362e157406 (commit)
       via  69336de84b2ae1b5b6a59fa8d817daa1108cea27 (commit)
       via  12186e267fb75a77027dc046f78db6ace99b8571 (commit)
       via  c62810c526d75363ed4d668bbdb6b21a5a294a7b (commit)
       via  0710846d8d7a38079b9570aeec9abfb94341af79 (commit)
       via  9517f61cb8ad4f8074b5e6e33c663ca9ed581908 (commit)
       via  3da7e8747dcea9b45c8bc4c17b946be7d5ff9576 (commit)
       via  d9e757fb15b711464cfc8ba344f2563f3e2b9195 (commit)
       via  517c31a58af1f7b97f308e77caeb8cbe9ef99cf1 (commit)
       via  4c485d0b112721d3a2b2939ab61db14b7608c98c (commit)
      from  900a3c5828be90bfce2a7b8e2e6edc0d4509df6a (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit f279d996354eded4defa219a393efa362e157406
Merge: 900a3c5828be90bfce2a7b8e2e6edc0d4509df6a 69336de84b2ae1b5b6a59fa8d817daa1108cea27
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Jul 26 16:21:54 2011 +0200

    Merge branch 'work/startcreator'

commit 69336de84b2ae1b5b6a59fa8d817daa1108cea27
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Tue Jul 26 16:19:51 2011 +0200

    [trac800] Sort the message definitions

commit 12186e267fb75a77027dc046f78db6ace99b8571
Author: Jelte Jansen <jelte at isc.org>
Date:   Tue Jul 26 15:00:33 2011 +0200

    [trac800] fix a few typos

commit c62810c526d75363ed4d668bbdb6b21a5a294a7b
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Jul 25 18:38:53 2011 +0200

    [trac800] Makefile fixes

commit 0710846d8d7a38079b9570aeec9abfb94341af79
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Jul 25 18:15:43 2011 +0200

    [trac800] Fix tests depending on boss
    
    Why do they include boss?

commit 9517f61cb8ad4f8074b5e6e33c663ca9ed581908
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Jul 25 18:02:06 2011 +0200

    [trac800] Actually starting the creator

commit 3da7e8747dcea9b45c8bc4c17b946be7d5ff9576
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Jul 25 14:40:06 2011 +0200

    [trac800] WrappedSocket

commit d9e757fb15b711464cfc8ba344f2563f3e2b9195
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Jul 25 13:48:15 2011 +0200

    [trac800] Provide logging for the sockcreator parser

commit 517c31a58af1f7b97f308e77caeb8cbe9ef99cf1
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Jul 25 12:44:09 2011 +0200

    [trac800] The parser

commit 4c485d0b112721d3a2b2939ab61db14b7608c98c
Author: Michal 'vorner' Vaner <michal.vaner at nic.cz>
Date:   Mon Jul 25 10:52:14 2011 +0200

    [trac800] Interface and tests for the sockcreator parser

-----------------------------------------------------------------------

Summary of changes:
 configure.ac                                      |    3 +-
 src/bin/bind10/Makefile.am                        |   10 +-
 src/bin/{stats/tests/http => bind10}/__init__.py  |    0 
 src/bin/bind10/bind10_messages.mes                |   65 ++++-
 src/bin/bind10/{bind10.py.in => bind10_src.py.in} |   34 +++-
 src/bin/bind10/sockcreator.py                     |  226 +++++++++++++++
 src/bin/bind10/tests/Makefile.am                  |    5 +-
 src/bin/bind10/tests/bind10_test.py.in            |   10 +-
 src/bin/bind10/tests/sockcreator_test.py.in       |  315 +++++++++++++++++++++
 src/bin/dhcp6/tests/Makefile.am                   |    2 +-
 src/bin/dhcp6/tests/dhcp6_test.py                 |    2 +-
 src/bin/sockcreator/README                        |    2 +-
 12 files changed, 651 insertions(+), 23 deletions(-)
 copy src/bin/{stats/tests/http => bind10}/__init__.py (100%)
 rename src/bin/bind10/{bind10.py.in => bind10_src.py.in} (96%)
 create mode 100644 src/bin/bind10/sockcreator.py
 create mode 100644 src/bin/bind10/tests/sockcreator_test.py.in

-----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 369f973..0ede949 100644
--- a/configure.ac
+++ b/configure.ac
@@ -904,9 +904,10 @@ AC_OUTPUT([doc/version.ent
            src/bin/zonemgr/run_b10-zonemgr.sh
            src/bin/stats/stats.py
            src/bin/stats/stats_httpd.py
-           src/bin/bind10/bind10.py
+           src/bin/bind10/bind10_src.py
            src/bin/bind10/run_bind10.sh
            src/bin/bind10/tests/bind10_test.py
+           src/bin/bind10/tests/sockcreator_test.py
            src/bin/bindctl/run_bindctl.sh
            src/bin/bindctl/bindctl_main.py
            src/bin/bindctl/tests/bindctl_test
diff --git a/src/bin/bind10/Makefile.am b/src/bin/bind10/Makefile.am
index 126c429..1a5ce64 100644
--- a/src/bin/bind10/Makefile.am
+++ b/src/bin/bind10/Makefile.am
@@ -1,7 +1,11 @@
 SUBDIRS = . tests
 
 sbin_SCRIPTS = bind10
-CLEANFILES = bind10 bind10.pyc bind10_messages.py bind10_messages.pyc
+CLEANFILES = bind10 bind10_src.pyc bind10_messages.py bind10_messages.pyc \
+	sockcreator.pyc
+
+python_PYTHON = __init__.py sockcreator.py
+pythondir = $(pyexecdir)/bind10
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 pyexec_DATA = bind10_messages.py
@@ -24,9 +28,9 @@ bind10_messages.py: bind10_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message -p $(top_srcdir)/src/bin/bind10/bind10_messages.mes
 
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
-bind10: bind10.py
+bind10: bind10_src.py
 	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
-	       -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bind10.py >$@
+	       -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bind10_src.py >$@
 	chmod a+x $@
 
 pytest:
diff --git a/src/bin/bind10/__init__.py b/src/bin/bind10/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
deleted file mode 100755
index a624383..0000000
--- a/src/bin/bind10/bind10.py.in
+++ /dev/null
@@ -1,1037 +0,0 @@
-#!@PYTHON@
-
-# Copyright (C) 2010,2011  Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-"""
-This file implements the Boss of Bind (BoB, or bob) program.
-
-Its purpose is to start up the BIND 10 system, and then manage the
-processes, by starting and stopping processes, plus restarting
-processes that exit.
-
-To start the system, it first runs the c-channel program (msgq), then
-connects to that. It then runs the configuration manager, and reads
-its own configuration. Then it proceeds to starting other modules.
-
-The Python subprocess module is used for starting processes, but
-because this is not efficient for managing groups of processes,
-SIGCHLD signals are caught and processed using the signal module.
-
-Most of the logic is contained in the BoB class. However, since Python
-requires that signal processing happen in the main thread, we do
-signal handling outside of that class, in the code running for
-__main__.
-"""
-
-import sys; sys.path.append ('@@PYTHONPATH@@')
-import os
-
-# If B10_FROM_SOURCE is set in the environment, we use data files
-# from a directory relative to that, otherwise we use the ones
-# installed on the system
-if "B10_FROM_SOURCE" in os.environ:
-    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/bind10/bob.spec"
-else:
-    PREFIX = "@prefix@"
-    DATAROOTDIR = "@datarootdir@"
-    SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-    
-import subprocess
-import signal
-import re
-import errno
-import time
-import select
-import random
-import socket
-from optparse import OptionParser, OptionValueError
-import io
-import pwd
-import posix
-
-import isc.cc
-import isc.util.process
-import isc.net.parse
-import isc.log
-from bind10_messages import *
-
-isc.log.init("b10-boss")
-logger = isc.log.Logger("boss")
-
-# Pending system-wide debug level definitions, the ones we
-# use here are hardcoded for now
-DBG_PROCESS = 10
-DBG_COMMANDS = 30
-
-# Assign this process some longer name
-isc.util.process.rename(sys.argv[0])
-
-# This is the version that gets displayed to the user.
-# The VERSION string consists of the module name, the module version
-# number, and the overall BIND 10 version number (set in configure.ac).
-VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
-
-# This is for bind10.boottime of stats module
-_BASETIME = time.gmtime()
-
-class RestartSchedule:
-    """
-Keeps state when restarting something (in this case, a process).
-
-When a process dies unexpectedly, we need to restart it. However, if 
-it fails to restart for some reason, then we should not simply keep
-restarting it at high speed.
-
-A more sophisticated algorithm can be developed, but for now we choose
-a simple set of rules:
-
-  * If a process was been running for >=10 seconds, we restart it
-    right away.
-  * If a process was running for <10 seconds, we wait until 10 seconds
-    after it was started.
-
-To avoid programs getting into lockstep, we use a normal distribution
-to avoid being restarted at exactly 10 seconds."""
-
-    def __init__(self, restart_frequency=10.0):
-        self.restart_frequency = restart_frequency
-        self.run_start_time = None
-        self.run_stop_time = None
-        self.restart_time = None
-    
-    def set_run_start_time(self, when=None):
-        if when is None:
-            when = time.time()
-        self.run_start_time = when
-        sigma = self.restart_frequency * 0.05
-        self.restart_time = when + random.normalvariate(self.restart_frequency, 
-                                                        sigma)
-
-    def set_run_stop_time(self, when=None):
-        """We don't actually do anything with stop time now, but it 
-        might be useful for future algorithms."""
-        if when is None:
-            when = time.time()
-        self.run_stop_time = when
-
-    def get_restart_time(self, when=None):
-        if when is None:
-            when = time.time()
-        return max(when, self.restart_time)
-
-class ProcessInfoError(Exception): pass
-
-class ProcessInfo:
-    """Information about a process"""
-
-    dev_null = open(os.devnull, "w")
-
-    def __init__(self, name, args, env={}, dev_null_stdout=False,
-                 dev_null_stderr=False, uid=None, username=None):
-        self.name = name 
-        self.args = args
-        self.env = env
-        self.dev_null_stdout = dev_null_stdout
-        self.dev_null_stderr = dev_null_stderr
-        self.restart_schedule = RestartSchedule()
-        self.uid = uid
-        self.username = username
-        self.process = None
-        self.pid = None
-
-    def _preexec_work(self):
-        """Function used before running a program that needs to run as a
-        different user."""
-        # First, put us into a separate process group so we don't get
-        # SIGINT signals on Ctrl-C (the boss will shut everthing down by
-        # other means).
-        os.setpgrp()
-        # Second, set the user ID if one has been specified
-        if self.uid is not None:
-            try:
-                posix.setuid(self.uid)
-            except OSError as e:
-                if e.errno == errno.EPERM:
-                    # if we failed to change user due to permission report that
-                    raise ProcessInfoError("Unable to change to user %s (uid %d)" % (self.username, self.uid))
-                else:
-                    # otherwise simply re-raise whatever error we found
-                    raise
-
-    def _spawn(self):
-        if self.dev_null_stdout:
-            spawn_stdout = self.dev_null
-        else:
-            spawn_stdout = None
-        if self.dev_null_stderr:
-            spawn_stderr = self.dev_null
-        else:
-            spawn_stderr = None
-        # Environment variables for the child process will be a copy of those
-        # of the boss process with any additional specific variables given
-        # on construction (self.env).
-        spawn_env = os.environ
-        spawn_env.update(self.env)
-        if 'B10_FROM_SOURCE' not in os.environ:
-            spawn_env['PATH'] = "@@LIBEXECDIR@@:" + spawn_env['PATH']
-        self.process = subprocess.Popen(self.args,
-                                        stdin=subprocess.PIPE,
-                                        stdout=spawn_stdout,
-                                        stderr=spawn_stderr,
-                                        close_fds=True,
-                                        env=spawn_env,
-                                        preexec_fn=self._preexec_work)
-        self.pid = self.process.pid
-        self.restart_schedule.set_run_start_time()
-
-    # spawn() and respawn() are the same for now, but in the future they
-    # may have different functionality
-    def spawn(self):
-        self._spawn()
-
-    def respawn(self):
-        self._spawn()
-
-class CChannelConnectError(Exception): pass
-
-class BoB:
-    """Boss of BIND class."""
-    
-    def __init__(self, msgq_socket_file=None, data_path=None,
-    config_filename=None, nocache=False, verbose=False, setuid=None,
-    username=None, cmdctl_port=None, brittle=False):
-        """
-            Initialize the Boss of BIND. This is a singleton (only one can run).
-        
-            The msgq_socket_file specifies the UNIX domain socket file that the
-            msgq process listens on.  If verbose is True, then the boss reports
-            what it is doing.
-
-            Data path and config filename are passed trough to config manager
-            (if provided) and specify the config file to be used.
-
-            The cmdctl_port is passed to cmdctl and specify on which port it
-            should listen.
-        """
-        self.cc_session = None
-        self.ccs = None
-        self.cfg_start_auth = True
-        self.cfg_start_resolver = False
-        self.cfg_start_dhcp6 = False
-        self.cfg_start_dhcp4 = False
-        self.started_auth_family = False
-        self.started_resolver_family = False
-        self.curproc = None
-        self.dead_processes = {}
-        self.msgq_socket_file = msgq_socket_file
-        self.nocache = nocache
-        self.processes = {}
-        self.expected_shutdowns = {}
-        self.runnable = False
-        self.uid = setuid
-        self.username = username
-        self.verbose = verbose
-        self.data_path = data_path
-        self.config_filename = config_filename
-        self.cmdctl_port = cmdctl_port
-        self.brittle = brittle
-
-    def config_handler(self, new_config):
-        # If this is initial update, don't do anything now, leave it to startup
-        if not self.runnable:
-            return
-        # Now we declare few functions used only internally here. Besides the
-        # benefit of not polluting the name space, they are closures, so we
-        # don't need to pass some variables
-        def start_stop(name, started, start, stop):
-            if not'start_' + name in new_config:
-                return
-            if new_config['start_' + name]:
-                if not started:
-                    if self.uid is not None:
-                        logger.info(BIND10_START_AS_NON_ROOT, name)
-                    start()
-            else:
-                stop()
-        # These four functions are passed to start_stop (smells like functional
-        # programming little bit)
-        def resolver_on():
-            self.start_resolver(self.c_channel_env)
-            self.started_resolver_family = True
-        def resolver_off():
-            self.stop_resolver()
-            self.started_resolver_family = False
-        def auth_on():
-            self.start_auth(self.c_channel_env)
-            self.start_xfrout(self.c_channel_env)
-            self.start_xfrin(self.c_channel_env)
-            self.start_zonemgr(self.c_channel_env)
-            self.started_auth_family = True
-        def auth_off():
-            self.stop_zonemgr()
-            self.stop_xfrin()
-            self.stop_xfrout()
-            self.stop_auth()
-            self.started_auth_family = False
-
-        # The real code of the config handler function follows here
-        logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
-                     new_config)
-        start_stop('resolver', self.started_resolver_family, resolver_on,
-            resolver_off)
-        start_stop('auth', self.started_auth_family, auth_on, auth_off)
-
-        answer = isc.config.ccsession.create_answer(0)
-        return answer
-
-    def get_processes(self):
-        pids = list(self.processes.keys())
-        pids.sort()
-        process_list = [ ]
-        for pid in pids:
-            process_list.append([pid, self.processes[pid].name])
-        return process_list
-
-    def command_handler(self, command, args):
-        logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
-        answer = isc.config.ccsession.create_answer(1, "command not implemented")
-        if type(command) != str:
-            answer = isc.config.ccsession.create_answer(1, "bad command")
-        else:
-            if command == "shutdown":
-                self.runnable = False
-                answer = isc.config.ccsession.create_answer(0)
-            elif command == "sendstats":
-                # send statistics data to the stats daemon immediately
-                cmd = isc.config.ccsession.create_command(
-                    'set', { "stats_data": {
-                            'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
-                            }})
-                seq = self.cc_session.group_sendmsg(cmd, 'Stats')
-                self.cc_session.group_recvmsg(True, seq)
-                answer = isc.config.ccsession.create_answer(0)
-            elif command == "ping":
-                answer = isc.config.ccsession.create_answer(0, "pong")
-            elif command == "show_processes":
-                answer = isc.config.ccsession. \
-                    create_answer(0, self.get_processes())
-            else:
-                answer = isc.config.ccsession.create_answer(1,
-                                                            "Unknown command")
-        return answer
-
-    def kill_started_processes(self):
-        """
-            Called as part of the exception handling when a process fails to
-            start, this runs through the list of started processes, killing
-            each one.  It then clears that list.
-        """
-        logger.info(BIND10_KILLING_ALL_PROCESSES)
-
-        for pid in self.processes:
-            logger.info(BIND10_KILL_PROCESS, self.processes[pid].name)
-            self.processes[pid].process.kill()
-        self.processes = {}
-
-    def read_bind10_config(self):
-        """
-            Reads the parameters associated with the BoB module itself.
-
-            At present these are the components to start although arguably this
-            information should be in the configuration for the appropriate
-            module itself. (However, this would cause difficulty in the case of
-            xfrin/xfrout and zone manager as we don't need to start those if we
-            are not running the authoritative server.)
-        """
-        logger.info(BIND10_READING_BOSS_CONFIGURATION)
-
-        config_data = self.ccs.get_full_config()
-        self.cfg_start_auth = config_data.get("start_auth")
-        self.cfg_start_resolver = config_data.get("start_resolver")
-
-        logger.info(BIND10_CONFIGURATION_START_AUTH, self.cfg_start_auth)
-        logger.info(BIND10_CONFIGURATION_START_RESOLVER, self.cfg_start_resolver)
-
-    def log_starting(self, process, port = None, address = None):
-        """
-            A convenience function to output a "Starting xxx" message if the
-            logging is set to DEBUG with debuglevel DBG_PROCESS or higher.
-            Putting this into a separate method ensures
-            that the output form is consistent across all processes.
-
-            The process name (passed as the first argument) is put into
-            self.curproc, and is used to indicate which process failed to
-            start if there is an error (and is used in the "Started" message
-            on success).  The optional port and address information are
-            appended to the message (if present).
-        """
-        self.curproc = process
-        if port is None and address is None:
-            logger.info(BIND10_STARTING_PROCESS, self.curproc)
-        elif address is None:
-            logger.info(BIND10_STARTING_PROCESS_PORT, self.curproc,
-                        port)
-        else:
-            logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
-                        self.curproc, address, port)
-
-    def log_started(self, pid = None):
-        """
-            A convenience function to output a 'Started xxxx (PID yyyy)'
-            message.  As with starting_message(), this ensures a consistent
-            format.
-        """
-        if pid is None:
-            logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
-        else:
-            logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
-
-    # The next few methods start the individual processes of BIND-10.  They
-    # are called via start_all_processes().  If any fail, an exception is
-    # raised which is caught by the caller of start_all_processes(); this kills
-    # processes started up to that point before terminating the program.
-
-    def start_msgq(self, c_channel_env):
-        """
-            Start the message queue and connect to the command channel.
-        """
-        self.log_starting("b10-msgq")
-        c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
-                                True, not self.verbose, uid=self.uid,
-                                username=self.username)
-        c_channel.spawn()
-        self.processes[c_channel.pid] = c_channel
-        self.log_started(c_channel.pid)
-
-        # Now connect to the c-channel
-        cc_connect_start = time.time()
-        while self.cc_session is None:
-            # if we have been trying for "a while" give up
-            if (time.time() - cc_connect_start) > 5:
-                raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
-
-            # try to connect, and if we can't wait a short while
-            try:
-                self.cc_session = isc.cc.Session(self.msgq_socket_file)
-            except isc.cc.session.SessionError:
-                time.sleep(0.1)
-
-    def start_cfgmgr(self, c_channel_env):
-        """
-            Starts the configuration manager process
-        """
-        self.log_starting("b10-cfgmgr")
-        args = ["b10-cfgmgr"]
-        if self.data_path is not None:
-            args.append("--data-path=" + self.data_path)
-        if self.config_filename is not None:
-            args.append("--config-filename=" + self.config_filename)
-        bind_cfgd = ProcessInfo("b10-cfgmgr", args,
-                                c_channel_env, uid=self.uid,
-                                username=self.username)
-        bind_cfgd.spawn()
-        self.processes[bind_cfgd.pid] = bind_cfgd
-        self.log_started(bind_cfgd.pid)
-
-        # sleep until b10-cfgmgr is fully up and running, this is a good place
-        # to have a (short) timeout on synchronized groupsend/receive
-        # TODO: replace the sleep by a listen for ConfigManager started
-        # message
-        time.sleep(1)
-
-    def start_ccsession(self, c_channel_env):
-        """
-            Start the CC Session
-
-            The argument c_channel_env is unused but is supplied to keep the
-            argument list the same for all start_xxx methods.
-        """
-        self.log_starting("ccsession")
-        self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, 
-                                      self.config_handler,
-                                      self.command_handler)
-        self.ccs.start()
-        self.log_started()
-
-    # A couple of utility methods for starting processes...
-
-    def start_process(self, name, args, c_channel_env, port=None, address=None):
-        """
-            Given a set of command arguments, start the process and output
-            appropriate log messages.  If the start is successful, the process
-            is added to the list of started processes.
-
-            The port and address arguments are for log messages only.
-        """
-        self.log_starting(name, port, address)
-        newproc = ProcessInfo(name, args, c_channel_env)
-        newproc.spawn()
-        self.processes[newproc.pid] = newproc
-        self.log_started(newproc.pid)
-
-    def start_simple(self, name, c_channel_env, port=None, address=None):
-        """
-            Most of the BIND-10 processes are started with the command:
-
-                <process-name> [-v]
-
-            ... where -v is appended if verbose is enabled.  This method
-            generates the arguments from the name and starts the process.
-
-            The port and address arguments are for log messages only.
-        """
-        # Set up the command arguments.
-        args = [name]
-        if self.verbose:
-            args += ['-v']
-
-        # ... and start the process
-        self.start_process(name, args, c_channel_env, port, address)
-
-    # The next few methods start up the rest of the BIND-10 processes.
-    # Although many of these methods are little more than a call to
-    # start_simple, they are retained (a) for testing reasons and (b) as a place
-    # where modifications can be made if the process start-up sequence changes
-    # for a given process.
-
-    def start_auth(self, c_channel_env):
-        """
-            Start the Authoritative server
-        """
-        authargs = ['b10-auth']
-        if self.nocache:
-            authargs += ['-n']
-        if self.uid:
-            authargs += ['-u', str(self.uid)]
-        if self.verbose:
-            authargs += ['-v']
-
-        # ... and start
-        self.start_process("b10-auth", authargs, c_channel_env)
-
-    def start_resolver(self, c_channel_env):
-        """
-            Start the Resolver.  At present, all these arguments and switches
-            are pure speculation.  As with the auth daemon, they should be
-            read from the configuration database.
-        """
-        self.curproc = "b10-resolver"
-        # XXX: this must be read from the configuration manager in the future
-        resargs = ['b10-resolver']
-        if self.uid:
-            resargs += ['-u', str(self.uid)]
-        if self.verbose:
-            resargs += ['-v']
-
-        # ... and start
-        self.start_process("b10-resolver", resargs, c_channel_env)
-
-    def start_xfrout(self, c_channel_env):
-        self.start_simple("b10-xfrout", c_channel_env)
-
-    def start_xfrin(self, c_channel_env):
-        self.start_simple("b10-xfrin", c_channel_env)
-
-    def start_zonemgr(self, c_channel_env):
-        self.start_simple("b10-zonemgr", c_channel_env)
-
-    def start_stats(self, c_channel_env):
-        self.start_simple("b10-stats", c_channel_env)
-
-    def start_stats_httpd(self, c_channel_env):
-        self.start_simple("b10-stats-httpd", c_channel_env)
-
-    def start_dhcp6(self, c_channel_env):
-        self.start_simple("b10-dhcp6", c_channel_env)
-
-    def start_cmdctl(self, c_channel_env):
-        """
-            Starts the command control process
-        """
-        args = ["b10-cmdctl"]
-        if self.cmdctl_port is not None:
-            args.append("--port=" + str(self.cmdctl_port))
-        self.start_process("b10-cmdctl", args, c_channel_env, self.cmdctl_port)
-
-    def start_all_processes(self):
-        """
-            Starts up all the processes.  Any exception generated during the
-            starting of the processes is handled by the caller.
-        """
-        c_channel_env = self.c_channel_env
-        self.start_msgq(c_channel_env)
-        self.start_cfgmgr(c_channel_env)
-        self.start_ccsession(c_channel_env)
-
-        # Extract the parameters associated with Bob.  This can only be
-        # done after the CC Session is started.
-        self.read_bind10_config()
-
-        # Continue starting the processes.  The authoritative server (if
-        # selected):
-        if self.cfg_start_auth:
-            self.start_auth(c_channel_env)
-
-        # ... and resolver (if selected):
-        if self.cfg_start_resolver:
-            self.start_resolver(c_channel_env)
-            self.started_resolver_family = True
-
-        # Everything after the main components can run as non-root.
-        # TODO: this is only temporary - once the privileged socket creator is
-        # fully working, nothing else will run as root.
-        if self.uid is not None:
-            posix.setuid(self.uid)
-
-        # xfrin/xfrout and the zone manager are only meaningful if the
-        # authoritative server has been started.
-        if self.cfg_start_auth:
-            self.start_xfrout(c_channel_env)
-            self.start_xfrin(c_channel_env)
-            self.start_zonemgr(c_channel_env)
-            self.started_auth_family = True
-
-        # ... and finally start the remaining processes
-        self.start_stats(c_channel_env)
-        self.start_stats_httpd(c_channel_env)
-        self.start_cmdctl(c_channel_env)
-
-        if self.cfg_start_dhcp6:
-            self.start_dhcp6(c_channel_env)
-
-    def startup(self):
-        """
-            Start the BoB instance.
-
-            Returns None if successful, otherwise an string describing the
-            problem.
-        """
-        # Try to connect to the c-channel daemon, to see if it is already
-        # running
-        c_channel_env = {}
-        if self.msgq_socket_file is not None:
-             c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
-        logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
-        # try to connect, and if we can't wait a short while
-        try:
-            self.cc_session = isc.cc.Session(self.msgq_socket_file)
-            logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
-            return "b10-msgq already running, or socket file not cleaned , cannot start"
-        except isc.cc.session.SessionError:
-            # this is the case we want, where the msgq is not running
-            pass
-
-        # Start all processes.  If any one fails to start, kill all started
-        # processes and exit with an error indication.
-        try:
-            self.c_channel_env = c_channel_env
-            self.start_all_processes()
-        except Exception as e:
-            self.kill_started_processes()
-            return "Unable to start " + self.curproc + ": " + str(e)
-
-        # Started successfully
-        self.runnable = True
-        return None
-
-    def stop_all_processes(self):
-        """Stop all processes."""
-        cmd = { "command": ['shutdown']}
-
-        self.cc_session.group_sendmsg(cmd, 'Cmdctl', 'Cmdctl')
-        self.cc_session.group_sendmsg(cmd, "ConfigManager", "ConfigManager")
-        self.cc_session.group_sendmsg(cmd, "Auth", "Auth")
-        self.cc_session.group_sendmsg(cmd, "Resolver", "Resolver")
-        self.cc_session.group_sendmsg(cmd, "Xfrout", "Xfrout")
-        self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
-        self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
-        self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
-        self.cc_session.group_sendmsg(cmd, "StatsHttpd", "StatsHttpd")
-
-    def stop_process(self, process, recipient):
-        """
-        Stop the given process, friendly-like. The process is the name it has
-        (in logs, etc), the recipient is the address on msgq.
-        """
-        logger.info(BIND10_STOP_PROCESS, process)
-        # TODO: Some timeout to solve processes that don't want to die would
-        # help. We can even store it in the dict, it is used only as a set
-        self.expected_shutdowns[process] = 1
-        # Ask the process to die willingly
-        self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
-            recipient)
-
-    # Series of stop_process wrappers
-    def stop_resolver(self):
-        self.stop_process('b10-resolver', 'Resolver')
-
-    def stop_auth(self):
-        self.stop_process('b10-auth', 'Auth')
-
-    def stop_xfrout(self):
-        self.stop_process('b10-xfrout', 'Xfrout')
-
-    def stop_xfrin(self):
-        self.stop_process('b10-xfrin', 'Xfrin')
-
-    def stop_zonemgr(self):
-        self.stop_process('b10-zonemgr', 'Zonemgr')
-
-    def shutdown(self):
-        """Stop the BoB instance."""
-        logger.info(BIND10_SHUTDOWN)
-        # first try using the BIND 10 request to stop
-        try:
-            self.stop_all_processes()
-        except:
-            pass
-        # XXX: some delay probably useful... how much is uncertain
-        # I have changed the delay from 0.5 to 1, but sometime it's 
-        # still not enough.
-        time.sleep(1)  
-        self.reap_children()
-        # next try sending a SIGTERM
-        processes_to_stop = list(self.processes.values())
-        for proc_info in processes_to_stop:
-            logger.info(BIND10_SEND_SIGTERM, proc_info.name,
-                        proc_info.pid)
-            try:
-                proc_info.process.terminate()
-            except OSError:
-                # ignore these (usually ESRCH because the child
-                # finally exited)
-                pass
-        # finally, send SIGKILL (unmaskable termination) until everybody dies
-        while self.processes:
-            # XXX: some delay probably useful... how much is uncertain
-            time.sleep(0.1)  
-            self.reap_children()
-            processes_to_stop = list(self.processes.values())
-            for proc_info in processes_to_stop:
-                logger.info(BIND10_SEND_SIGKILL, proc_info.name,
-                            proc_info.pid)
-                try:
-                    proc_info.process.kill()
-                except OSError:
-                    # ignore these (usually ESRCH because the child
-                    # finally exited)
-                    pass
-        logger.info(BIND10_SHUTDOWN_COMPLETE)
-
-    def _get_process_exit_status(self):
-        return os.waitpid(-1, os.WNOHANG)
-
-    def reap_children(self):
-        """Check to see if any of our child processes have exited, 
-        and note this for later handling. 
-        """
-        while True:
-            try:
-                (pid, exit_status) = self._get_process_exit_status()
-            except OSError as o:
-                if o.errno == errno.ECHILD: break
-                # XXX: should be impossible to get any other error here
-                raise
-            if pid == 0: break
-            if pid in self.processes:
-                # One of the processes we know about.  Get information on it.
-                proc_info = self.processes.pop(pid)
-                proc_info.restart_schedule.set_run_stop_time()
-                self.dead_processes[proc_info.pid] = proc_info
-
-                # Write out message, but only if in the running state:
-                # During startup and shutdown, these messages are handled
-                # elsewhere.
-                if self.runnable:
-                    if exit_status is None:
-                        logger.warn(BIND10_PROCESS_ENDED_NO_EXIT_STATUS,
-                                    proc_info.name, proc_info.pid)
-                    else:
-                        logger.warn(BIND10_PROCESS_ENDED_WITH_EXIT_STATUS,
-                                    proc_info.name, proc_info.pid,
-                                    exit_status)
-
-                    # Was it a special process?
-                    if proc_info.name == "b10-msgq":
-                        logger.fatal(BIND10_MSGQ_DAEMON_ENDED)
-                        self.runnable = False
-
-                # If we're in 'brittle' mode, we want to shutdown after
-                # any process dies.
-                if self.brittle:
-                    self.runnable = False
-            else:
-                logger.info(BIND10_UNKNOWN_CHILD_PROCESS_ENDED, pid)
-
-    def restart_processes(self):
-        """
-            Restart any dead processes:
-
-            * Returns the time when the next process is ready to be restarted. 
-            * If the server is shutting down, returns 0.
-            * If there are no processes, returns None.
-
-            The values returned can be safely passed into select() as the 
-            timeout value.
-        """
-        next_restart = None
-        # if we're shutting down, then don't restart
-        if not self.runnable:
-            return 0
-        # otherwise look through each dead process and try to restart
-        still_dead = {}
-        now = time.time()
-        for proc_info in self.dead_processes.values():
-            if proc_info.name in self.expected_shutdowns:
-                # We don't restart, we wanted it to die
-                del self.expected_shutdowns[proc_info.name]
-                continue
-            restart_time = proc_info.restart_schedule.get_restart_time(now)
-            if restart_time > now:
-                if (next_restart is None) or (next_restart > restart_time):
-                    next_restart = restart_time
-                still_dead[proc_info.pid] = proc_info
-            else:
-                logger.info(BIND10_RESURRECTING_PROCESS, proc_info.name)
-                try:
-                    proc_info.respawn()
-                    self.processes[proc_info.pid] = proc_info
-                    logger.info(BIND10_RESURRECTED_PROCESS, proc_info.name, proc_info.pid)
-                except:
-                    still_dead[proc_info.pid] = proc_info
-        # remember any processes that refuse to be resurrected
-        self.dead_processes = still_dead
-        # return the time when the next process is ready to be restarted
-        return next_restart
-
-# global variables, needed for signal handlers
-options = None
-boss_of_bind = None
-
-def reaper(signal_number, stack_frame):
-    """A child process has died (SIGCHLD received)."""
-    # don't do anything... 
-    # the Python signal handler has been set up to write
-    # down a pipe, waking up our select() bit
-    pass
-
-def get_signame(signal_number):
-    """Return the symbolic name for a signal."""
-    for sig in dir(signal):
-        if sig.startswith("SIG") and sig[3].isalnum():
-            if getattr(signal, sig) == signal_number:
-                return sig
-    return "Unknown signal %d" % signal_number
-
-# XXX: perhaps register atexit() function and invoke that instead
-def fatal_signal(signal_number, stack_frame):
-    """We need to exit (SIGINT or SIGTERM received)."""
-    global options
-    global boss_of_bind
-    logger.info(BIND10_RECEIVED_SIGNAL, get_signame(signal_number))
-    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-    boss_of_bind.runnable = False
-
-def process_rename(option, opt_str, value, parser):
-    """Function that renames the process if it is requested by a option."""
-    isc.util.process.rename(value)
-
-def parse_args(args=sys.argv[1:], Parser=OptionParser):
-    """
-    Function for parsing command line arguments. Returns the
-    options object from OptionParser.
-    """
-    parser = Parser(version=VERSION)
-    parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
-                      type="string", default=None,
-                      help="UNIX domain socket file the b10-msgq daemon will use")
-    parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
-                      default=False, help="disable hot-spot cache in authoritative DNS server")
-    parser.add_option("-u", "--user", dest="user", type="string", default=None,
-                      help="Change user after startup (must run as root)")
-    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
-                      help="display more about what is going on")
-    parser.add_option("--pretty-name", type="string", action="callback",
-                      callback=process_rename,
-                      help="Set the process name (displayed in ps, top, ...)")
-    parser.add_option("-c", "--config-file", action="store",
-                      dest="config_file", default=None,
-                      help="Configuration database filename")
-    parser.add_option("-p", "--data-path", dest="data_path",
-                      help="Directory to search for configuration files",
-                      default=None)
-    parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
-                      default=None, help="Port of command control")
-    parser.add_option("--pid-file", dest="pid_file", type="string",
-                      default=None,
-                      help="file to dump the PID of the BIND 10 process")
-    parser.add_option("--brittle", dest="brittle", action="store_true",
-                      help="debugging flag: exit if any component dies")
-
-    (options, args) = parser.parse_args(args)
-
-    if options.cmdctl_port is not None:
-        try:
-            isc.net.parse.port_parse(options.cmdctl_port)
-        except ValueError as e:
-            parser.error(e)
-
-    if args:
-        parser.print_help()
-        sys.exit(1)
-
-    return options
-
-def dump_pid(pid_file):
-    """
-    Dump the PID of the current process to the specified file.  If the given
-    file is None this function does nothing.  If the file already exists,
-    the existing content will be removed.  If a system error happens in
-    creating or writing to the file, the corresponding exception will be
-    propagated to the caller.
-    """
-    if pid_file is None:
-        return
-    f = open(pid_file, "w")
-    f.write('%d\n' % os.getpid())
-    f.close()
-
-def unlink_pid_file(pid_file):
-    """
-    Remove the given file, which is basically expected to be the PID file
-    created by dump_pid().  The specified may or may not exist; if it
-    doesn't this function does nothing.  Other system level errors in removing
-    the file will be propagated as the corresponding exception.
-    """
-    if pid_file is None:
-        return
-    try:
-        os.unlink(pid_file)
-    except OSError as error:
-        if error.errno is not errno.ENOENT:
-            raise
-
-
-def main():
-    global options
-    global boss_of_bind
-    # Enforce line buffering on stdout, even when not a TTY
-    sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
-
-    options = parse_args()
-
-    # Check user ID.
-    setuid = None
-    username = None
-    if options.user:
-        # Try getting information about the user, assuming UID passed.
-        try:
-            pw_ent = pwd.getpwuid(int(options.user))
-            setuid = pw_ent.pw_uid
-            username = pw_ent.pw_name
-        except ValueError:
-            pass
-        except KeyError:
-            pass
-
-        # Next try getting information about the user, assuming user name 
-        # passed.
-        # If the information is both a valid user name and user number, we
-        # prefer the name because we try it second. A minor point, hopefully.
-        try:
-            pw_ent = pwd.getpwnam(options.user)
-            setuid = pw_ent.pw_uid
-            username = pw_ent.pw_name
-        except KeyError:
-            pass
-
-        if setuid is None:
-            logger.fatal(BIND10_INVALID_USER, options.user)
-            sys.exit(1)
-
-    # Announce startup.
-    logger.info(BIND10_STARTING, VERSION)
-
-    # Create wakeup pipe for signal handlers
-    wakeup_pipe = os.pipe()
-    signal.set_wakeup_fd(wakeup_pipe[1])
-
-    # Set signal handlers for catching child termination, as well
-    # as our own demise.
-    signal.signal(signal.SIGCHLD, reaper)
-    signal.siginterrupt(signal.SIGCHLD, False)
-    signal.signal(signal.SIGINT, fatal_signal)
-    signal.signal(signal.SIGTERM, fatal_signal)
-
-    # Block SIGPIPE, as we don't want it to end this process
-    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
-
-    # Go bob!
-    boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
-                       options.config_file, options.nocache, options.verbose,
-                       setuid, username, options.cmdctl_port, options.brittle)
-    startup_result = boss_of_bind.startup()
-    if startup_result:
-        logger.fatal(BIND10_STARTUP_ERROR, startup_result)
-        sys.exit(1)
-    logger.info(BIND10_STARTUP_COMPLETE)
-    dump_pid(options.pid_file)
-
-    # In our main loop, we check for dead processes or messages 
-    # on the c-channel.
-    wakeup_fd = wakeup_pipe[0]
-    ccs_fd = boss_of_bind.ccs.get_socket().fileno()
-    while boss_of_bind.runnable:
-        # clean up any processes that exited
-        boss_of_bind.reap_children()
-        next_restart = boss_of_bind.restart_processes()
-        if next_restart is None:
-            wait_time = None
-        else:
-            wait_time = max(next_restart - time.time(), 0)
-
-        # select() can raise EINTR when a signal arrives, 
-        # even if they are resumable, so we have to catch
-        # the exception
-        try:
-            (rlist, wlist, xlist) = select.select([wakeup_fd, ccs_fd], [], [], 
-                                                  wait_time)
-        except select.error as err:
-            if err.args[0] == errno.EINTR:
-                (rlist, wlist, xlist) = ([], [], [])
-            else:
-                logger.fatal(BIND10_SELECT_ERROR, err)
-                break
-
-        for fd in rlist + xlist:
-            if fd == ccs_fd:
-                try:
-                    boss_of_bind.ccs.check_command()
-                except isc.cc.session.ProtocolError:
-                    logger.fatal(BIND10_MSGQ_DISAPPEARED)
-                    self.runnable = False
-                    break
-            elif fd == wakeup_fd:
-                os.read(wakeup_fd, 32)
-
-    # shutdown
-    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-    boss_of_bind.shutdown()
-    unlink_pid_file(options.pid_file)
-    sys.exit(0)
-
-if __name__ == "__main__":
-    main()
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index 3f5f637..e10bc7c 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -32,15 +32,15 @@ started according to the configuration.
 The boss process was started with the -u option, to drop root privileges
 and continue running as the specified user, but the user is unknown.
 
+% BIND10_KILLING_ALL_PROCESSES killing all started processes
+The boss module was not able to start every process it needed to start
+during startup, and will now kill the processes that did get started.
+
 % BIND10_KILL_PROCESS killing process %1
 The boss module is sending a kill signal to process with the given name,
 as part of the process of killing all started processes during a failed
 startup, as described for BIND10_KILLING_ALL_PROCESSES
 
-% BIND10_KILLING_ALL_PROCESSES killing all started processes
-The boss module was not able to start every process it needed to start
-during startup, and will now kill the processes that did get started.
-
 % BIND10_MSGQ_ALREADY_RUNNING msgq daemon already running, cannot start
 There already appears to be a message bus daemon running. Either an
 old process was not shut down correctly, and needs to be killed, or
@@ -113,12 +113,49 @@ it shall send SIGKILL signals to the processes still alive.
 All child processes have been stopped, and the boss process will now
 stop itself.
 
-% BIND10_START_AS_NON_ROOT starting %1 as a user, not root. This might fail.
-The given module is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
+% BIND10_SOCKCREATOR_BAD_CAUSE unknown error cause from socket creator: %1
+The socket creator reported an error when creating a socket. But the function
+which failed is unknown (not one of 'S' for socket or 'B' for bind).
+
+% BIND10_SOCKCREATOR_BAD_RESPONSE unknown response for socket request: %1
+The boss requested a socket from the creator, but the answer is unknown. This
+looks like a programmer error.
+
+% BIND10_SOCKCREATOR_CRASHED the socket creator crashed
+The socket creator terminated unexpectadly. It is not possible to restart it
+(because the boss already gave up root privileges), so the system is going
+to terminate.
+
+% BIND10_SOCKCREATOR_EOF eof while expecting data from socket creator
+There should be more data from the socket creator, but it closed the socket.
+It probably crashed.
+
+% BIND10_SOCKCREATOR_INIT initializing socket creator parser
+The boss module initializes routines for parsing the socket creator
+protocol.
+
+% BIND10_SOCKCREATOR_KILL killing the socket creator
+The socket creator is being terminated the aggressive way, by sending it
+sigkill. This should not happen usually.
+
+% BIND10_SOCKCREATOR_TERMINATE terminating socket creator
+The boss module sends a request to terminate to the socket creator.
+
+% BIND10_SOCKCREATOR_TRANSPORT_ERROR transport error when talking to the socket creator: %1
+Either sending or receiving data from the socket creator failed with the given
+error. The creator probably crashed or some serious OS-level problem happened,
+as the communication happens only on local host.
+
+% BIND10_SOCKET_CREATED successfully created socket %1
+The socket creator successfully created and sent a requested socket, it has
+the given file number.
+
+% BIND10_SOCKET_ERROR error on %1 call in the creator: %2/%3
+The socket creator failed to create the requested socket. It failed on the
+indicated OS API function with given error.
+
+% BIND10_SOCKET_GET requesting socket [%1]:%2 of type %3 from the creator
+The boss forwards a request for a socket to the socket creator.
 
 % BIND10_STARTED_PROCESS started %1
 The given process has successfully been started.
@@ -147,6 +184,13 @@ All modules have been successfully started, and BIND 10 is now running.
 There was a fatal error when BIND10 was trying to start. The error is
 shown, and BIND10 will now shut down.
 
+% BIND10_START_AS_NON_ROOT starting %1 as a user, not root. This might fail.
+The given module is being started or restarted without root privileges.
+If the module needs these privileges, it may have problems starting.
+Note that this issue should be resolved by the pending 'socket-creator'
+process; once that has been implemented, modules should not need root
+privileges anymore. See tickets #800 and #801 for more information.
+
 % BIND10_STOP_PROCESS asking %1 to shut down
 The boss module is sending a shutdown command to the given module over
 the message channel.
@@ -154,4 +198,3 @@ the message channel.
 % BIND10_UNKNOWN_CHILD_PROCESS_ENDED unknown child pid %1 exited
 An unknown child process has exited. The PID is printed, but no further
 action will be taken by the boss process.
-
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
new file mode 100755
index 0000000..bbb17a2
--- /dev/null
+++ b/src/bin/bind10/bind10_src.py.in
@@ -0,0 +1,1069 @@
+#!@PYTHON@
+
+# Copyright (C) 2010,2011  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+This file implements the Boss of Bind (BoB, or bob) program.
+
+Its purpose is to start up the BIND 10 system, and then manage the
+processes, by starting and stopping processes, plus restarting
+processes that exit.
+
+To start the system, it first runs the c-channel program (msgq), then
+connects to that. It then runs the configuration manager, and reads
+its own configuration. Then it proceeds to starting other modules.
+
+The Python subprocess module is used for starting processes, but
+because this is not efficient for managing groups of processes,
+SIGCHLD signals are caught and processed using the signal module.
+
+Most of the logic is contained in the BoB class. However, since Python
+requires that signal processing happen in the main thread, we do
+signal handling outside of that class, in the code running for
+__main__.
+"""
+
+import sys; sys.path.append ('@@PYTHONPATH@@')
+import os
+
+# If B10_FROM_SOURCE is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_SOURCE" in os.environ:
+    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/bind10/bob.spec"
+else:
+    PREFIX = "@prefix@"
+    DATAROOTDIR = "@datarootdir@"
+    SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+    
+import subprocess
+import signal
+import re
+import errno
+import time
+import select
+import random
+import socket
+from optparse import OptionParser, OptionValueError
+import io
+import pwd
+import posix
+
+import isc.cc
+import isc.util.process
+import isc.net.parse
+import isc.log
+from bind10_messages import *
+import bind10.sockcreator
+
+isc.log.init("b10-boss")
+logger = isc.log.Logger("boss")
+
+# Pending system-wide debug level definitions, the ones we
+# use here are hardcoded for now
+DBG_PROCESS = 10
+DBG_COMMANDS = 30
+
+# Assign this process some longer name
+isc.util.process.rename(sys.argv[0])
+
+# This is the version that gets displayed to the user.
+# The VERSION string consists of the module name, the module version
+# number, and the overall BIND 10 version number (set in configure.ac).
+VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
+
+# This is for bind10.boottime of stats module
+_BASETIME = time.gmtime()
+
+class RestartSchedule:
+    """
+Keeps state when restarting something (in this case, a process).
+
+When a process dies unexpectedly, we need to restart it. However, if 
+it fails to restart for some reason, then we should not simply keep
+restarting it at high speed.
+
+A more sophisticated algorithm can be developed, but for now we choose
+a simple set of rules:
+
+  * If a process was been running for >=10 seconds, we restart it
+    right away.
+  * If a process was running for <10 seconds, we wait until 10 seconds
+    after it was started.
+
+To avoid programs getting into lockstep, we use a normal distribution
+to avoid being restarted at exactly 10 seconds."""
+
+    def __init__(self, restart_frequency=10.0):
+        self.restart_frequency = restart_frequency
+        self.run_start_time = None
+        self.run_stop_time = None
+        self.restart_time = None
+    
+    def set_run_start_time(self, when=None):
+        if when is None:
+            when = time.time()
+        self.run_start_time = when
+        sigma = self.restart_frequency * 0.05
+        self.restart_time = when + random.normalvariate(self.restart_frequency, 
+                                                        sigma)
+
+    def set_run_stop_time(self, when=None):
+        """We don't actually do anything with stop time now, but it 
+        might be useful for future algorithms."""
+        if when is None:
+            when = time.time()
+        self.run_stop_time = when
+
+    def get_restart_time(self, when=None):
+        if when is None:
+            when = time.time()
+        return max(when, self.restart_time)
+
+class ProcessInfoError(Exception): pass
+
+class ProcessInfo:
+    """Information about a process"""
+
+    dev_null = open(os.devnull, "w")
+
+    def __init__(self, name, args, env={}, dev_null_stdout=False,
+                 dev_null_stderr=False, uid=None, username=None):
+        self.name = name 
+        self.args = args
+        self.env = env
+        self.dev_null_stdout = dev_null_stdout
+        self.dev_null_stderr = dev_null_stderr
+        self.restart_schedule = RestartSchedule()
+        self.uid = uid
+        self.username = username
+        self.process = None
+        self.pid = None
+
+    def _preexec_work(self):
+        """Function used before running a program that needs to run as a
+        different user."""
+        # First, put us into a separate process group so we don't get
+        # SIGINT signals on Ctrl-C (the boss will shut everthing down by
+        # other means).
+        os.setpgrp()
+        # Second, set the user ID if one has been specified
+        if self.uid is not None:
+            try:
+                posix.setuid(self.uid)
+            except OSError as e:
+                if e.errno == errno.EPERM:
+                    # if we failed to change user due to permission report that
+                    raise ProcessInfoError("Unable to change to user %s (uid %d)" % (self.username, self.uid))
+                else:
+                    # otherwise simply re-raise whatever error we found
+                    raise
+
+    def _spawn(self):
+        if self.dev_null_stdout:
+            spawn_stdout = self.dev_null
+        else:
+            spawn_stdout = None
+        if self.dev_null_stderr:
+            spawn_stderr = self.dev_null
+        else:
+            spawn_stderr = None
+        # Environment variables for the child process will be a copy of those
+        # of the boss process with any additional specific variables given
+        # on construction (self.env).
+        spawn_env = os.environ
+        spawn_env.update(self.env)
+        if 'B10_FROM_SOURCE' not in os.environ:
+            spawn_env['PATH'] = "@@LIBEXECDIR@@:" + spawn_env['PATH']
+        self.process = subprocess.Popen(self.args,
+                                        stdin=subprocess.PIPE,
+                                        stdout=spawn_stdout,
+                                        stderr=spawn_stderr,
+                                        close_fds=True,
+                                        env=spawn_env,
+                                        preexec_fn=self._preexec_work)
+        self.pid = self.process.pid
+        self.restart_schedule.set_run_start_time()
+
+    # spawn() and respawn() are the same for now, but in the future they
+    # may have different functionality
+    def spawn(self):
+        self._spawn()
+
+    def respawn(self):
+        self._spawn()
+
+class CChannelConnectError(Exception): pass
+
+class BoB:
+    """Boss of BIND class."""
+    
+    def __init__(self, msgq_socket_file=None, data_path=None,
+    config_filename=None, nocache=False, verbose=False, setuid=None,
+    username=None, cmdctl_port=None, brittle=False):
+        """
+            Initialize the Boss of BIND. This is a singleton (only one can run).
+        
+            The msgq_socket_file specifies the UNIX domain socket file that the
+            msgq process listens on.  If verbose is True, then the boss reports
+            what it is doing.
+
+            Data path and config filename are passed trough to config manager
+            (if provided) and specify the config file to be used.
+
+            The cmdctl_port is passed to cmdctl and specify on which port it
+            should listen.
+        """
+        self.cc_session = None
+        self.ccs = None
+        self.cfg_start_auth = True
+        self.cfg_start_resolver = False
+        self.cfg_start_dhcp6 = False
+        self.cfg_start_dhcp4 = False
+        self.started_auth_family = False
+        self.started_resolver_family = False
+        self.curproc = None
+        self.dead_processes = {}
+        self.msgq_socket_file = msgq_socket_file
+        self.nocache = nocache
+        self.processes = {}
+        self.expected_shutdowns = {}
+        self.runnable = False
+        self.uid = setuid
+        self.username = username
+        self.verbose = verbose
+        self.data_path = data_path
+        self.config_filename = config_filename
+        self.cmdctl_port = cmdctl_port
+        self.brittle = brittle
+        self.sockcreator = None
+
+    def config_handler(self, new_config):
+        # If this is initial update, don't do anything now, leave it to startup
+        if not self.runnable:
+            return
+        # Now we declare few functions used only internally here. Besides the
+        # benefit of not polluting the name space, they are closures, so we
+        # don't need to pass some variables
+        def start_stop(name, started, start, stop):
+            if not'start_' + name in new_config:
+                return
+            if new_config['start_' + name]:
+                if not started:
+                    if self.uid is not None:
+                        logger.info(BIND10_START_AS_NON_ROOT, name)
+                    start()
+            else:
+                stop()
+        # These four functions are passed to start_stop (smells like functional
+        # programming little bit)
+        def resolver_on():
+            self.start_resolver(self.c_channel_env)
+            self.started_resolver_family = True
+        def resolver_off():
+            self.stop_resolver()
+            self.started_resolver_family = False
+        def auth_on():
+            self.start_auth(self.c_channel_env)
+            self.start_xfrout(self.c_channel_env)
+            self.start_xfrin(self.c_channel_env)
+            self.start_zonemgr(self.c_channel_env)
+            self.started_auth_family = True
+        def auth_off():
+            self.stop_zonemgr()
+            self.stop_xfrin()
+            self.stop_xfrout()
+            self.stop_auth()
+            self.started_auth_family = False
+
+        # The real code of the config handler function follows here
+        logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
+                     new_config)
+        start_stop('resolver', self.started_resolver_family, resolver_on,
+            resolver_off)
+        start_stop('auth', self.started_auth_family, auth_on, auth_off)
+
+        answer = isc.config.ccsession.create_answer(0)
+        return answer
+
+    def get_processes(self):
+        pids = list(self.processes.keys())
+        pids.sort()
+        process_list = [ ]
+        for pid in pids:
+            process_list.append([pid, self.processes[pid].name])
+        return process_list
+
+    def command_handler(self, command, args):
+        logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
+        answer = isc.config.ccsession.create_answer(1, "command not implemented")
+        if type(command) != str:
+            answer = isc.config.ccsession.create_answer(1, "bad command")
+        else:
+            if command == "shutdown":
+                self.runnable = False
+                answer = isc.config.ccsession.create_answer(0)
+            elif command == "sendstats":
+                # send statistics data to the stats daemon immediately
+                cmd = isc.config.ccsession.create_command(
+                    'set', { "stats_data": {
+                            'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
+                            }})
+                seq = self.cc_session.group_sendmsg(cmd, 'Stats')
+                self.cc_session.group_recvmsg(True, seq)
+                answer = isc.config.ccsession.create_answer(0)
+            elif command == "ping":
+                answer = isc.config.ccsession.create_answer(0, "pong")
+            elif command == "show_processes":
+                answer = isc.config.ccsession. \
+                    create_answer(0, self.get_processes())
+            else:
+                answer = isc.config.ccsession.create_answer(1,
+                                                            "Unknown command")
+        return answer
+
+    def start_creator(self):
+        self.curproc = 'b10-sockcreator'
+        self.sockcreator = bind10.sockcreator.Creator("@@LIBEXECDIR@@:" +
+                                                      os.environ['PATH'])
+
+    def stop_creator(self, kill=False):
+        if self.sockcreator is None:
+            return
+        if kill:
+            self.sockcreator.kill()
+        else:
+            self.sockcreator.terminate()
+        self.sockcreator = None
+
+    def kill_started_processes(self):
+        """
+            Called as part of the exception handling when a process fails to
+            start, this runs through the list of started processes, killing
+            each one.  It then clears that list.
+        """
+        logger.info(BIND10_KILLING_ALL_PROCESSES)
+
+        self.stop_creator(True)
+
+        for pid in self.processes:
+            logger.info(BIND10_KILL_PROCESS, self.processes[pid].name)
+            self.processes[pid].process.kill()
+        self.processes = {}
+
+    def read_bind10_config(self):
+        """
+            Reads the parameters associated with the BoB module itself.
+
+            At present these are the components to start although arguably this
+            information should be in the configuration for the appropriate
+            module itself. (However, this would cause difficulty in the case of
+            xfrin/xfrout and zone manager as we don't need to start those if we
+            are not running the authoritative server.)
+        """
+        logger.info(BIND10_READING_BOSS_CONFIGURATION)
+
+        config_data = self.ccs.get_full_config()
+        self.cfg_start_auth = config_data.get("start_auth")
+        self.cfg_start_resolver = config_data.get("start_resolver")
+
+        logger.info(BIND10_CONFIGURATION_START_AUTH, self.cfg_start_auth)
+        logger.info(BIND10_CONFIGURATION_START_RESOLVER, self.cfg_start_resolver)
+
+    def log_starting(self, process, port = None, address = None):
+        """
+            A convenience function to output a "Starting xxx" message if the
+            logging is set to DEBUG with debuglevel DBG_PROCESS or higher.
+            Putting this into a separate method ensures
+            that the output form is consistent across all processes.
+
+            The process name (passed as the first argument) is put into
+            self.curproc, and is used to indicate which process failed to
+            start if there is an error (and is used in the "Started" message
+            on success).  The optional port and address information are
+            appended to the message (if present).
+        """
+        self.curproc = process
+        if port is None and address is None:
+            logger.info(BIND10_STARTING_PROCESS, self.curproc)
+        elif address is None:
+            logger.info(BIND10_STARTING_PROCESS_PORT, self.curproc,
+                        port)
+        else:
+            logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
+                        self.curproc, address, port)
+
+    def log_started(self, pid = None):
+        """
+            A convenience function to output a 'Started xxxx (PID yyyy)'
+            message.  As with starting_message(), this ensures a consistent
+            format.
+        """
+        if pid is None:
+            logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
+        else:
+            logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
+
+    # The next few methods start the individual processes of BIND-10.  They
+    # are called via start_all_processes().  If any fail, an exception is
+    # raised which is caught by the caller of start_all_processes(); this kills
+    # processes started up to that point before terminating the program.
+
+    def start_msgq(self, c_channel_env):
+        """
+            Start the message queue and connect to the command channel.
+        """
+        self.log_starting("b10-msgq")
+        c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
+                                True, not self.verbose, uid=self.uid,
+                                username=self.username)
+        c_channel.spawn()
+        self.processes[c_channel.pid] = c_channel
+        self.log_started(c_channel.pid)
+
+        # Now connect to the c-channel
+        cc_connect_start = time.time()
+        while self.cc_session is None:
+            # if we have been trying for "a while" give up
+            if (time.time() - cc_connect_start) > 5:
+                raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
+
+            # try to connect, and if we can't wait a short while
+            try:
+                self.cc_session = isc.cc.Session(self.msgq_socket_file)
+            except isc.cc.session.SessionError:
+                time.sleep(0.1)
+
+    def start_cfgmgr(self, c_channel_env):
+        """
+            Starts the configuration manager process
+        """
+        self.log_starting("b10-cfgmgr")
+        args = ["b10-cfgmgr"]
+        if self.data_path is not None:
+            args.append("--data-path=" + self.data_path)
+        if self.config_filename is not None:
+            args.append("--config-filename=" + self.config_filename)
+        bind_cfgd = ProcessInfo("b10-cfgmgr", args,
+                                c_channel_env, uid=self.uid,
+                                username=self.username)
+        bind_cfgd.spawn()
+        self.processes[bind_cfgd.pid] = bind_cfgd
+        self.log_started(bind_cfgd.pid)
+
+        # sleep until b10-cfgmgr is fully up and running, this is a good place
+        # to have a (short) timeout on synchronized groupsend/receive
+        # TODO: replace the sleep by a listen for ConfigManager started
+        # message
+        time.sleep(1)
+
+    def start_ccsession(self, c_channel_env):
+        """
+            Start the CC Session
+
+            The argument c_channel_env is unused but is supplied to keep the
+            argument list the same for all start_xxx methods.
+        """
+        self.log_starting("ccsession")
+        self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, 
+                                      self.config_handler,
+                                      self.command_handler)
+        self.ccs.start()
+        self.log_started()
+
+    # A couple of utility methods for starting processes...
+
+    def start_process(self, name, args, c_channel_env, port=None, address=None):
+        """
+            Given a set of command arguments, start the process and output
+            appropriate log messages.  If the start is successful, the process
+            is added to the list of started processes.
+
+            The port and address arguments are for log messages only.
+        """
+        self.log_starting(name, port, address)
+        newproc = ProcessInfo(name, args, c_channel_env)
+        newproc.spawn()
+        self.processes[newproc.pid] = newproc
+        self.log_started(newproc.pid)
+
+    def start_simple(self, name, c_channel_env, port=None, address=None):
+        """
+            Most of the BIND-10 processes are started with the command:
+
+                <process-name> [-v]
+
+            ... where -v is appended if verbose is enabled.  This method
+            generates the arguments from the name and starts the process.
+
+            The port and address arguments are for log messages only.
+        """
+        # Set up the command arguments.
+        args = [name]
+        if self.verbose:
+            args += ['-v']
+
+        # ... and start the process
+        self.start_process(name, args, c_channel_env, port, address)
+
+    # The next few methods start up the rest of the BIND-10 processes.
+    # Although many of these methods are little more than a call to
+    # start_simple, they are retained (a) for testing reasons and (b) as a place
+    # where modifications can be made if the process start-up sequence changes
+    # for a given process.
+
+    def start_auth(self, c_channel_env):
+        """
+            Start the Authoritative server
+        """
+        authargs = ['b10-auth']
+        if self.nocache:
+            authargs += ['-n']
+        if self.uid:
+            authargs += ['-u', str(self.uid)]
+        if self.verbose:
+            authargs += ['-v']
+
+        # ... and start
+        self.start_process("b10-auth", authargs, c_channel_env)
+
+    def start_resolver(self, c_channel_env):
+        """
+            Start the Resolver.  At present, all these arguments and switches
+            are pure speculation.  As with the auth daemon, they should be
+            read from the configuration database.
+        """
+        self.curproc = "b10-resolver"
+        # XXX: this must be read from the configuration manager in the future
+        resargs = ['b10-resolver']
+        if self.uid:
+            resargs += ['-u', str(self.uid)]
+        if self.verbose:
+            resargs += ['-v']
+
+        # ... and start
+        self.start_process("b10-resolver", resargs, c_channel_env)
+
+    def start_xfrout(self, c_channel_env):
+        self.start_simple("b10-xfrout", c_channel_env)
+
+    def start_xfrin(self, c_channel_env):
+        self.start_simple("b10-xfrin", c_channel_env)
+
+    def start_zonemgr(self, c_channel_env):
+        self.start_simple("b10-zonemgr", c_channel_env)
+
+    def start_stats(self, c_channel_env):
+        self.start_simple("b10-stats", c_channel_env)
+
+    def start_stats_httpd(self, c_channel_env):
+        self.start_simple("b10-stats-httpd", c_channel_env)
+
+    def start_dhcp6(self, c_channel_env):
+        self.start_simple("b10-dhcp6", c_channel_env)
+
+    def start_cmdctl(self, c_channel_env):
+        """
+            Starts the command control process
+        """
+        args = ["b10-cmdctl"]
+        if self.cmdctl_port is not None:
+            args.append("--port=" + str(self.cmdctl_port))
+        self.start_process("b10-cmdctl", args, c_channel_env, self.cmdctl_port)
+
+    def start_all_processes(self):
+        """
+            Starts up all the processes.  Any exception generated during the
+            starting of the processes is handled by the caller.
+        """
+        # The socket creator first, as it is the only thing that needs root
+        self.start_creator()
+        # TODO: Once everything uses the socket creator, we can drop root
+        # privileges right now
+
+        c_channel_env = self.c_channel_env
+        self.start_msgq(c_channel_env)
+        self.start_cfgmgr(c_channel_env)
+        self.start_ccsession(c_channel_env)
+
+        # Extract the parameters associated with Bob.  This can only be
+        # done after the CC Session is started.
+        self.read_bind10_config()
+
+        # Continue starting the processes.  The authoritative server (if
+        # selected):
+        if self.cfg_start_auth:
+            self.start_auth(c_channel_env)
+
+        # ... and resolver (if selected):
+        if self.cfg_start_resolver:
+            self.start_resolver(c_channel_env)
+            self.started_resolver_family = True
+
+        # Everything after the main components can run as non-root.
+        # TODO: this is only temporary - once the privileged socket creator is
+        # fully working, nothing else will run as root.
+        if self.uid is not None:
+            posix.setuid(self.uid)
+
+        # xfrin/xfrout and the zone manager are only meaningful if the
+        # authoritative server has been started.
+        if self.cfg_start_auth:
+            self.start_xfrout(c_channel_env)
+            self.start_xfrin(c_channel_env)
+            self.start_zonemgr(c_channel_env)
+            self.started_auth_family = True
+
+        # ... and finally start the remaining processes
+        self.start_stats(c_channel_env)
+        self.start_stats_httpd(c_channel_env)
+        self.start_cmdctl(c_channel_env)
+
+        if self.cfg_start_dhcp6:
+            self.start_dhcp6(c_channel_env)
+
+    def startup(self):
+        """
+            Start the BoB instance.
+
+            Returns None if successful, otherwise an string describing the
+            problem.
+        """
+        # Try to connect to the c-channel daemon, to see if it is already
+        # running
+        c_channel_env = {}
+        if self.msgq_socket_file is not None:
+             c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
+        logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
+        # try to connect, and if we can't wait a short while
+        try:
+            self.cc_session = isc.cc.Session(self.msgq_socket_file)
+            logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
+            return "b10-msgq already running, or socket file not cleaned , cannot start"
+        except isc.cc.session.SessionError:
+            # this is the case we want, where the msgq is not running
+            pass
+
+        # Start all processes.  If any one fails to start, kill all started
+        # processes and exit with an error indication.
+        try:
+            self.c_channel_env = c_channel_env
+            self.start_all_processes()
+        except Exception as e:
+            self.kill_started_processes()
+            return "Unable to start " + self.curproc + ": " + str(e)
+
+        # Started successfully
+        self.runnable = True
+        return None
+
+    def stop_all_processes(self):
+        """Stop all processes."""
+        cmd = { "command": ['shutdown']}
+
+        self.cc_session.group_sendmsg(cmd, 'Cmdctl', 'Cmdctl')
+        self.cc_session.group_sendmsg(cmd, "ConfigManager", "ConfigManager")
+        self.cc_session.group_sendmsg(cmd, "Auth", "Auth")
+        self.cc_session.group_sendmsg(cmd, "Resolver", "Resolver")
+        self.cc_session.group_sendmsg(cmd, "Xfrout", "Xfrout")
+        self.cc_session.group_sendmsg(cmd, "Xfrin", "Xfrin")
+        self.cc_session.group_sendmsg(cmd, "Zonemgr", "Zonemgr")
+        self.cc_session.group_sendmsg(cmd, "Stats", "Stats")
+        self.cc_session.group_sendmsg(cmd, "StatsHttpd", "StatsHttpd")
+        # Terminate the creator last
+        self.stop_creator()
+
+    def stop_process(self, process, recipient):
+        """
+        Stop the given process, friendly-like. The process is the name it has
+        (in logs, etc), the recipient is the address on msgq.
+        """
+        logger.info(BIND10_STOP_PROCESS, process)
+        # TODO: Some timeout to solve processes that don't want to die would
+        # help. We can even store it in the dict, it is used only as a set
+        self.expected_shutdowns[process] = 1
+        # Ask the process to die willingly
+        self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
+            recipient)
+
+    # Series of stop_process wrappers
+    def stop_resolver(self):
+        self.stop_process('b10-resolver', 'Resolver')
+
+    def stop_auth(self):
+        self.stop_process('b10-auth', 'Auth')
+
+    def stop_xfrout(self):
+        self.stop_process('b10-xfrout', 'Xfrout')
+
+    def stop_xfrin(self):
+        self.stop_process('b10-xfrin', 'Xfrin')
+
+    def stop_zonemgr(self):
+        self.stop_process('b10-zonemgr', 'Zonemgr')
+
+    def shutdown(self):
+        """Stop the BoB instance."""
+        logger.info(BIND10_SHUTDOWN)
+        # first try using the BIND 10 request to stop
+        try:
+            self.stop_all_processes()
+        except:
+            pass
+        # XXX: some delay probably useful... how much is uncertain
+        # I have changed the delay from 0.5 to 1, but sometime it's 
+        # still not enough.
+        time.sleep(1)  
+        self.reap_children()
+        # next try sending a SIGTERM
+        processes_to_stop = list(self.processes.values())
+        for proc_info in processes_to_stop:
+            logger.info(BIND10_SEND_SIGTERM, proc_info.name,
+                        proc_info.pid)
+            try:
+                proc_info.process.terminate()
+            except OSError:
+                # ignore these (usually ESRCH because the child
+                # finally exited)
+                pass
+        # finally, send SIGKILL (unmaskable termination) until everybody dies
+        while self.processes:
+            # XXX: some delay probably useful... how much is uncertain
+            time.sleep(0.1)  
+            self.reap_children()
+            processes_to_stop = list(self.processes.values())
+            for proc_info in processes_to_stop:
+                logger.info(BIND10_SEND_SIGKILL, proc_info.name,
+                            proc_info.pid)
+                try:
+                    proc_info.process.kill()
+                except OSError:
+                    # ignore these (usually ESRCH because the child
+                    # finally exited)
+                    pass
+        logger.info(BIND10_SHUTDOWN_COMPLETE)
+
+    def _get_process_exit_status(self):
+        return os.waitpid(-1, os.WNOHANG)
+
+    def reap_children(self):
+        """Check to see if any of our child processes have exited, 
+        and note this for later handling. 
+        """
+        while True:
+            try:
+                (pid, exit_status) = self._get_process_exit_status()
+            except OSError as o:
+                if o.errno == errno.ECHILD: break
+                # XXX: should be impossible to get any other error here
+                raise
+            if pid == 0: break
+            if self.sockcreator is not None and self.sockcreator.pid() == pid:
+                # This is the socket creator, started and terminated
+                # differently. This can't be restarted.
+                if self.runnable:
+                    logger.fatal(BIND10_SOCKCREATOR_CRASHED)
+                    self.sockcreator = None
+                    self.runnable = False
+            elif pid in self.processes:
+                # One of the processes we know about.  Get information on it.
+                proc_info = self.processes.pop(pid)
+                proc_info.restart_schedule.set_run_stop_time()
+                self.dead_processes[proc_info.pid] = proc_info
+
+                # Write out message, but only if in the running state:
+                # During startup and shutdown, these messages are handled
+                # elsewhere.
+                if self.runnable:
+                    if exit_status is None:
+                        logger.warn(BIND10_PROCESS_ENDED_NO_EXIT_STATUS,
+                                    proc_info.name, proc_info.pid)
+                    else:
+                        logger.warn(BIND10_PROCESS_ENDED_WITH_EXIT_STATUS,
+                                    proc_info.name, proc_info.pid,
+                                    exit_status)
+
+                    # Was it a special process?
+                    if proc_info.name == "b10-msgq":
+                        logger.fatal(BIND10_MSGQ_DAEMON_ENDED)
+                        self.runnable = False
+
+                # If we're in 'brittle' mode, we want to shutdown after
+                # any process dies.
+                if self.brittle:
+                    self.runnable = False
+            else:
+                logger.info(BIND10_UNKNOWN_CHILD_PROCESS_ENDED, pid)
+
+    def restart_processes(self):
+        """
+            Restart any dead processes:
+
+            * Returns the time when the next process is ready to be restarted. 
+            * If the server is shutting down, returns 0.
+            * If there are no processes, returns None.
+
+            The values returned can be safely passed into select() as the 
+            timeout value.
+        """
+        next_restart = None
+        # if we're shutting down, then don't restart
+        if not self.runnable:
+            return 0
+        # otherwise look through each dead process and try to restart
+        still_dead = {}
+        now = time.time()
+        for proc_info in self.dead_processes.values():
+            if proc_info.name in self.expected_shutdowns:
+                # We don't restart, we wanted it to die
+                del self.expected_shutdowns[proc_info.name]
+                continue
+            restart_time = proc_info.restart_schedule.get_restart_time(now)
+            if restart_time > now:
+                if (next_restart is None) or (next_restart > restart_time):
+                    next_restart = restart_time
+                still_dead[proc_info.pid] = proc_info
+            else:
+                logger.info(BIND10_RESURRECTING_PROCESS, proc_info.name)
+                try:
+                    proc_info.respawn()
+                    self.processes[proc_info.pid] = proc_info
+                    logger.info(BIND10_RESURRECTED_PROCESS, proc_info.name, proc_info.pid)
+                except:
+                    still_dead[proc_info.pid] = proc_info
+        # remember any processes that refuse to be resurrected
+        self.dead_processes = still_dead
+        # return the time when the next process is ready to be restarted
+        return next_restart
+
+# global variables, needed for signal handlers
+options = None
+boss_of_bind = None
+
+def reaper(signal_number, stack_frame):
+    """A child process has died (SIGCHLD received)."""
+    # don't do anything... 
+    # the Python signal handler has been set up to write
+    # down a pipe, waking up our select() bit
+    pass
+
+def get_signame(signal_number):
+    """Return the symbolic name for a signal."""
+    for sig in dir(signal):
+        if sig.startswith("SIG") and sig[3].isalnum():
+            if getattr(signal, sig) == signal_number:
+                return sig
+    return "Unknown signal %d" % signal_number
+
+# XXX: perhaps register atexit() function and invoke that instead
+def fatal_signal(signal_number, stack_frame):
+    """We need to exit (SIGINT or SIGTERM received)."""
+    global options
+    global boss_of_bind
+    logger.info(BIND10_RECEIVED_SIGNAL, get_signame(signal_number))
+    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+    boss_of_bind.runnable = False
+
+def process_rename(option, opt_str, value, parser):
+    """Function that renames the process if it is requested by a option."""
+    isc.util.process.rename(value)
+
+def parse_args(args=sys.argv[1:], Parser=OptionParser):
+    """
+    Function for parsing command line arguments. Returns the
+    options object from OptionParser.
+    """
+    parser = Parser(version=VERSION)
+    parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
+                      type="string", default=None,
+                      help="UNIX domain socket file the b10-msgq daemon will use")
+    parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
+                      default=False, help="disable hot-spot cache in authoritative DNS server")
+    parser.add_option("-u", "--user", dest="user", type="string", default=None,
+                      help="Change user after startup (must run as root)")
+    parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+                      help="display more about what is going on")
+    parser.add_option("--pretty-name", type="string", action="callback",
+                      callback=process_rename,
+                      help="Set the process name (displayed in ps, top, ...)")
+    parser.add_option("-c", "--config-file", action="store",
+                      dest="config_file", default=None,
+                      help="Configuration database filename")
+    parser.add_option("-p", "--data-path", dest="data_path",
+                      help="Directory to search for configuration files",
+                      default=None)
+    parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
+                      default=None, help="Port of command control")
+    parser.add_option("--pid-file", dest="pid_file", type="string",
+                      default=None,
+                      help="file to dump the PID of the BIND 10 process")
+    parser.add_option("--brittle", dest="brittle", action="store_true",
+                      help="debugging flag: exit if any component dies")
+
+    (options, args) = parser.parse_args(args)
+
+    if options.cmdctl_port is not None:
+        try:
+            isc.net.parse.port_parse(options.cmdctl_port)
+        except ValueError as e:
+            parser.error(e)
+
+    if args:
+        parser.print_help()
+        sys.exit(1)
+
+    return options
+
+def dump_pid(pid_file):
+    """
+    Dump the PID of the current process to the specified file.  If the given
+    file is None this function does nothing.  If the file already exists,
+    the existing content will be removed.  If a system error happens in
+    creating or writing to the file, the corresponding exception will be
+    propagated to the caller.
+    """
+    if pid_file is None:
+        return
+    f = open(pid_file, "w")
+    f.write('%d\n' % os.getpid())
+    f.close()
+
+def unlink_pid_file(pid_file):
+    """
+    Remove the given file, which is basically expected to be the PID file
+    created by dump_pid().  The specified may or may not exist; if it
+    doesn't this function does nothing.  Other system level errors in removing
+    the file will be propagated as the corresponding exception.
+    """
+    if pid_file is None:
+        return
+    try:
+        os.unlink(pid_file)
+    except OSError as error:
+        if error.errno is not errno.ENOENT:
+            raise
+
+
+def main():
+    global options
+    global boss_of_bind
+    # Enforce line buffering on stdout, even when not a TTY
+    sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
+
+    options = parse_args()
+
+    # Check user ID.
+    setuid = None
+    username = None
+    if options.user:
+        # Try getting information about the user, assuming UID passed.
+        try:
+            pw_ent = pwd.getpwuid(int(options.user))
+            setuid = pw_ent.pw_uid
+            username = pw_ent.pw_name
+        except ValueError:
+            pass
+        except KeyError:
+            pass
+
+        # Next try getting information about the user, assuming user name 
+        # passed.
+        # If the information is both a valid user name and user number, we
+        # prefer the name because we try it second. A minor point, hopefully.
+        try:
+            pw_ent = pwd.getpwnam(options.user)
+            setuid = pw_ent.pw_uid
+            username = pw_ent.pw_name
+        except KeyError:
+            pass
+
+        if setuid is None:
+            logger.fatal(BIND10_INVALID_USER, options.user)
+            sys.exit(1)
+
+    # Announce startup.
+    logger.info(BIND10_STARTING, VERSION)
+
+    # Create wakeup pipe for signal handlers
+    wakeup_pipe = os.pipe()
+    signal.set_wakeup_fd(wakeup_pipe[1])
+
+    # Set signal handlers for catching child termination, as well
+    # as our own demise.
+    signal.signal(signal.SIGCHLD, reaper)
+    signal.siginterrupt(signal.SIGCHLD, False)
+    signal.signal(signal.SIGINT, fatal_signal)
+    signal.signal(signal.SIGTERM, fatal_signal)
+
+    # Block SIGPIPE, as we don't want it to end this process
+    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
+
+    # Go bob!
+    boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
+                       options.config_file, options.nocache, options.verbose,
+                       setuid, username, options.cmdctl_port, options.brittle)
+    startup_result = boss_of_bind.startup()
+    if startup_result:
+        logger.fatal(BIND10_STARTUP_ERROR, startup_result)
+        sys.exit(1)
+    logger.info(BIND10_STARTUP_COMPLETE)
+    dump_pid(options.pid_file)
+
+    # In our main loop, we check for dead processes or messages 
+    # on the c-channel.
+    wakeup_fd = wakeup_pipe[0]
+    ccs_fd = boss_of_bind.ccs.get_socket().fileno()
+    while boss_of_bind.runnable:
+        # clean up any processes that exited
+        boss_of_bind.reap_children()
+        next_restart = boss_of_bind.restart_processes()
+        if next_restart is None:
+            wait_time = None
+        else:
+            wait_time = max(next_restart - time.time(), 0)
+
+        # select() can raise EINTR when a signal arrives, 
+        # even if they are resumable, so we have to catch
+        # the exception
+        try:
+            (rlist, wlist, xlist) = select.select([wakeup_fd, ccs_fd], [], [], 
+                                                  wait_time)
+        except select.error as err:
+            if err.args[0] == errno.EINTR:
+                (rlist, wlist, xlist) = ([], [], [])
+            else:
+                logger.fatal(BIND10_SELECT_ERROR, err)
+                break
+
+        for fd in rlist + xlist:
+            if fd == ccs_fd:
+                try:
+                    boss_of_bind.ccs.check_command()
+                except isc.cc.session.ProtocolError:
+                    logger.fatal(BIND10_MSGQ_DISAPPEARED)
+                    self.runnable = False
+                    break
+            elif fd == wakeup_fd:
+                os.read(wakeup_fd, 32)
+
+    # shutdown
+    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
+    boss_of_bind.shutdown()
+    unlink_pid_file(options.pid_file)
+    sys.exit(0)
+
+if __name__ == "__main__":
+    main()
diff --git a/src/bin/bind10/sockcreator.py b/src/bin/bind10/sockcreator.py
new file mode 100644
index 0000000..9fcc74e
--- /dev/null
+++ b/src/bin/bind10/sockcreator.py
@@ -0,0 +1,226 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import socket
+import struct
+import os
+import subprocess
+from bind10_messages import *
+from libutil_io_python import recv_fd
+
+logger = isc.log.Logger("boss")
+
+"""
+Module that comunicates with the privileged socket creator (b10-sockcreator).
+"""
+
+class CreatorError(Exception):
+    """
+    Exception for socket creator related errors.
+
+    It has two members: fatal and errno and they are just holding the values
+    passed to the __init__ function.
+    """
+
+    def __init__(self, message, fatal, errno=None):
+        """
+        Creates the exception. The message argument is the usual string.
+        The fatal one tells if the error is fatal (eg. the creator crashed)
+        and errno is the errno value returned from socket creator, if
+        applicable.
+        """
+        Exception.__init__(self, message)
+        self.fatal = fatal
+        self.errno = errno
+
+class Parser:
+    """
+    This class knows the sockcreator language. It creates commands, sends them
+    and receives the answers and parses them.
+
+    It does not start it, the communication channel must be provided.
+
+    In theory, anything here can throw a fatal CreatorError exception, but it
+    happens only in case something like the creator process crashes. Any other
+    occasions are mentioned explicitly.
+    """
+
+    def __init__(self, creator_socket):
+        """
+        Creates the parser. The creator_socket is socket to the socket creator
+        process that will be used for communication. However, the object must
+        have a read_fd() method to read the file descriptor. This slightly
+        unusual trick with modifying an object is used to easy up testing.
+
+        You can use WrappedSocket in production code to add the method to any
+        ordinary socket.
+        """
+        self.__socket = creator_socket
+        logger.info(BIND10_SOCKCREATOR_INIT)
+
+    def terminate(self):
+        """
+        Asks the creator process to terminate and waits for it to close the
+        socket. Does not return anything. Raises a CreatorError if there is
+        still data on the socket, if there is an error closing the socket,
+        or if the socket had already been closed.
+        """
+        if self.__socket is None:
+            raise CreatorError('Terminated already', True)
+        logger.info(BIND10_SOCKCREATOR_TERMINATE)
+        try:
+            self.__socket.sendall(b'T')
+            # Wait for an EOF - it will return empty data
+            eof = self.__socket.recv(1)
+            if len(eof) != 0:
+                raise CreatorError('Protocol error - data after terminated',
+                                   True)
+            self.__socket = None
+        except socket.error as se:
+            self.__socket = None
+            raise CreatorError(str(se), True)
+
+    def get_socket(self, address, port, socktype):
+        """
+        Asks the socket creator process to create a socket. Pass an address
+        (the isc.net.IPaddr object), port number and socket type (either
+        string "UDP", "TCP" or constant socket.SOCK_DGRAM or
+        socket.SOCK_STREAM.
+
+        Blocks until it is provided by the socket creator process (which
+        should be fast, as it is on localhost) and returns the file descriptor
+        number. It raises a CreatorError exception if the creation fails.
+        """
+        if self.__socket is None:
+            raise CreatorError('Socket requested on terminated creator', True)
+        # First, assemble the request from parts
+        logger.info(BIND10_SOCKET_GET, address, port, socktype)
+        data = b'S'
+        if socktype == 'UDP' or socktype == socket.SOCK_DGRAM:
+            data += b'U'
+        elif socktype == 'TCP' or socktype == socket.SOCK_STREAM:
+            data += b'T'
+        else:
+            raise ValueError('Unknown socket type: ' + str(socktype))
+        if address.family == socket.AF_INET:
+            data += b'4'
+        elif address.family == socket.AF_INET6:
+            data += b'6'
+        else:
+            raise ValueError('Unknown address family in address')
+        data += struct.pack('!H', port)
+        data += address.addr
+        try:
+            # Send the request
+            self.__socket.sendall(data)
+            answer = self.__socket.recv(1)
+            if answer == b'S':
+                # Success!
+                result = self.__socket.read_fd()
+                logger.info(BIND10_SOCKET_CREATED, result)
+                return result
+            elif answer == b'E':
+                # There was an error, read the error as well
+                error = self.__socket.recv(1)
+                errno = struct.unpack('i',
+                                      self.__read_all(len(struct.pack('i',
+                                                                      0))))
+                if error == b'S':
+                    cause = 'socket'
+                elif error == b'B':
+                    cause = 'bind'
+                else:
+                    self.__socket = None
+                    logger.fatal(BIND10_SOCKCREATOR_BAD_CAUSE, error)
+                    raise CreatorError('Unknown error cause' + str(answer), True)
+                logger.error(BIND10_SOCKET_ERROR, cause, errno[0],
+                             os.strerror(errno[0]))
+                raise CreatorError('Error creating socket on ' + cause, False,
+                                   errno[0])
+            else:
+                self.__socket = None
+                logger.fatal(BIND10_SOCKCREATOR_BAD_RESPONSE, answer)
+                raise CreatorError('Unknown response ' + str(answer), True)
+        except socket.error as se:
+            self.__socket = None
+            logger.fatal(BIND10_SOCKCREATOR_TRANSPORT_ERROR, str(se))
+            raise CreatorError(str(se), True)
+
+    def __read_all(self, length):
+        """
+        Keeps reading until length data is read or EOF or error happens.
+
+        EOF is considered error as well and throws a CreatorError.
+        """
+        result = b''
+        while len(result) < length:
+            data = self.__socket.recv(length - len(result))
+            if len(data) == 0:
+                self.__socket = None
+                logger.fatal(BIND10_SOCKCREATOR_EOF)
+                raise CreatorError('Unexpected EOF', True)
+            result += data
+        return result
+
+class WrappedSocket:
+    """
+    This class wraps a socket and adds a read_fd method, so it can be used
+    for the Parser class conveniently. It simply copies all its guts into
+    itself and implements the method.
+    """
+    def __init__(self, socket):
+        # Copy whatever can be copied from the socket
+        for name in dir(socket):
+            if name not in ['__class__', '__weakref__']:
+                setattr(self, name, getattr(socket, name))
+        # Keep the socket, so we can prevent it from being garbage-collected
+        # and closed before we are removed ourself
+        self.__orig_socket = socket
+
+    def read_fd(self):
+        """
+        Read the file descriptor from the socket.
+        """
+        return recv_fd(self.fileno())
+
+# FIXME: Any idea how to test this? Starting an external process doesn't sound
+# OK
+class Creator(Parser):
+    """
+    This starts the socket creator and allows asking for the sockets.
+    """
+    def __init__(self, path):
+        (local, remote) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        # Popen does not like, for some reason, having the same socket for
+        # stdin as well as stdout, so we dup it before passing it there.
+        remote2 = socket.fromfd(remote.fileno(), socket.AF_UNIX,
+                                socket.SOCK_STREAM)
+        env = os.environ
+        env['PATH'] = path
+        self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
+                                          stdin=remote.fileno(),
+                                          stdout=remote2.fileno())
+        remote.close()
+        remote2.close()
+        Parser.__init__(self, WrappedSocket(local))
+
+    def pid(self):
+        return self.__process.pid
+
+    def kill(self):
+        logger.warn(BIND10_SOCKCREATOR_KILL)
+        if self.__process is not None:
+            self.__process.kill()
+            self.__process = None
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index 3d8d57a..4a40ec8 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -1,8 +1,7 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 #PYTESTS = args_test.py bind10_test.py
 # NOTE: this has a generated test found in the builddir
-PYTESTS = bind10_test.py
-EXTRA_DIST = $(PYTESTS)
+PYTESTS = bind10_test.py sockcreator_test.py
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
 # required by loadable python modules.
@@ -21,7 +20,7 @@ endif
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
-	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bind10 \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 		$(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
 	done
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 9d794a6..077190c 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -13,7 +13,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-from bind10 import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
+from bind10_src import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
 
 # XXX: environment tests are currently disabled, due to the preprocessor
 #      setup that we have now complicating the environment
@@ -193,6 +193,13 @@ class MockBob(BoB):
         self.cmdctl = False
         self.c_channel_env = {}
         self.processes = { }
+        self.creator = False
+
+    def start_creator(self):
+        self.creator = True
+
+    def stop_creator(self, kill=False):
+        self.creator = False
 
     def read_bind10_config(self):
         # Configuration options are set directly
@@ -337,6 +344,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
         self.assertEqual(bob.msgq, core)
         self.assertEqual(bob.cfgmgr, core)
         self.assertEqual(bob.ccsession, core)
+        self.assertEqual(bob.creator, core)
         self.assertEqual(bob.auth, auth)
         self.assertEqual(bob.resolver, resolver)
         self.assertEqual(bob.xfrout, auth)
diff --git a/src/bin/bind10/tests/sockcreator_test.py.in b/src/bin/bind10/tests/sockcreator_test.py.in
new file mode 100644
index 0000000..7fb522f
--- /dev/null
+++ b/src/bin/bind10/tests/sockcreator_test.py.in
@@ -0,0 +1,315 @@
+# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# This test file is generated .py.in -> .py just to be in the build dir,
+# same as the rest of the tests. Saves a lot of stuff in makefile.
+
+"""
+Tests for the bind10.sockcreator module.
+"""
+
+import unittest
+import struct
+import socket
+from isc.net.addr import IPAddr
+import isc.log
+from libutil_io_python import send_fd
+from bind10.sockcreator import Parser, CreatorError, WrappedSocket
+
+class FakeCreator:
+    """
+    Class emulating the socket to the socket creator. It can be given expected
+    data to receive (and check) and responses to give to the Parser class
+    during testing.
+    """
+
+    class InvalidPlan(Exception):
+        """
+        Raised when someone wants to recv when sending is planned or vice
+        versa.
+        """
+        pass
+
+    class InvalidData(Exception):
+        """
+        Raises when the data passed to sendall are not the same as expected.
+        """
+        pass
+
+    def __init__(self, plan):
+        """
+        Create the object. The plan variable contains list of expected actions,
+        in form:
+
+        [('r', 'Data to return from recv'), ('s', 'Data expected on sendall'),
+             , ('d', 'File descriptor number to return from read_sock'), ('e',
+             None), ...]
+
+        It modifies the array as it goes.
+        """
+        self.__plan = plan
+
+    def __get_plan(self, expected):
+        if len(self.__plan) == 0:
+            raise InvalidPlan('Nothing more planned')
+        (kind, data) = self.__plan[0]
+        if kind == 'e':
+            self.__plan.pop(0)
+            raise socket.error('False socket error')
+        if kind != expected:
+            raise InvalidPlan('Planned ' + kind + ', but ' + expected +
+                'requested')
+        return data
+
+    def recv(self, maxsize):
+        """
+        Emulate recv. Returs maxsize bytes from the current recv plan. If
+        there are data left from previous recv call, it is used first.
+
+        If no recv is planned, raises InvalidPlan.
+        """
+        data = self.__get_plan('r')
+        result, rest = data[:maxsize], data[maxsize:]
+        if len(rest) > 0:
+            self.__plan[0] = ('r', rest)
+        else:
+            self.__plan.pop(0)
+        return result
+
+    def read_fd(self):
+        """
+        Emulate the reading of file descriptor. Returns one from a plan.
+
+        It raises InvalidPlan if no socket is planned now.
+        """
+        fd = self.__get_plan('f')
+        self.__plan.pop(0)
+        return fd
+
+    def sendall(self, data):
+        """
+        Checks that the data passed are correct according to plan. It raises
+        InvalidData if the data differs or InvalidPlan when sendall is not
+        expected.
+        """
+        planned = self.__get_plan('s')
+        dlen = len(data)
+        prefix, rest = planned[:dlen], planned[dlen:]
+        if prefix != data:
+            raise InvalidData('Expected "' + str(prefix)+ '", got "' +
+                str(data) + '"')
+        if len(rest) > 0:
+            self.__plan[0] = ('s', rest)
+        else:
+            self.__plan.pop(0)
+
+    def all_used(self):
+        """
+        Returns if the whole plan was consumed.
+        """
+        return len(self.__plan) == 0
+
+class ParserTests(unittest.TestCase):
+    """
+    Testcases for the Parser class.
+    """
+    def __terminate(self):
+        creator = FakeCreator([('s', b'T'), ('r', b'')])
+        parser = Parser(creator)
+        self.assertEqual(None, parser.terminate())
+        self.assertTrue(creator.all_used())
+        return parser
+
+    def test_terminate(self):
+        """
+        Test if the command to terminate is correct and it waits for reading the
+        EOF.
+        """
+        self.__terminate()
+
+    def test_terminate_error1(self):
+        """
+        Test it reports an exception when there's error terminating the creator.
+        This one raises an error when receiving the EOF.
+        """
+        creator = FakeCreator([('s', b'T'), ('e', None)])
+        parser = Parser(creator)
+        with self.assertRaises(CreatorError) as cm:
+            parser.terminate()
+        self.assertTrue(cm.exception.fatal)
+        self.assertEqual(None, cm.exception.errno)
+
+    def test_terminate_error2(self):
+        """
+        Test it reports an exception when there's error terminating the creator.
+        This one raises an error when sending data.
+        """
+        creator = FakeCreator([('e', None)])
+        parser = Parser(creator)
+        with self.assertRaises(CreatorError) as cm:
+            parser.terminate()
+        self.assertTrue(cm.exception.fatal)
+        self.assertEqual(None, cm.exception.errno)
+
+    def test_terminate_twice(self):
+        """
+        Test we can't terminate twice.
+        """
+        parser = self.__terminate()
+        with self.assertRaises(CreatorError) as cm:
+            parser.terminate()
+        self.assertTrue(cm.exception.fatal)
+        self.assertEqual(None, cm.exception.errno)
+
+    def test_terminate_error3(self):
+        """
+        Test it reports an exception when there's error terminating the creator.
+        This one sends data when it should have terminated.
+        """
+        creator = FakeCreator([('s', b'T'), ('r', b'Extra data')])
+        parser = Parser(creator)
+        with self.assertRaises(CreatorError) as cm:
+            parser.terminate()
+        self.assertTrue(cm.exception.fatal)
+        self.assertEqual(None, cm.exception.errno)
+
+    def test_crash(self):
+        """
+        Tests that the parser correctly raises exception when it crashes
+        unexpectedly.
+        """
+        creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'')])
+        parser = Parser(creator)
+        with self.assertRaises(CreatorError) as cm:
+            parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
+        self.assertTrue(creator.all_used())
+        # Is the exception correct?
+        self.assertTrue(cm.exception.fatal)
+        self.assertEqual(None, cm.exception.errno)
+
+    def test_error(self):
+        """
+        Tests that the parser correctly raises non-fatal exception when
+        the socket can not be created.
+        """
+        # We split the int to see if it can cope with data coming in
+        # different packets
+        intpart = struct.pack('@i', 42)
+        creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'ES' +
+            intpart[:1]), ('r', intpart[1:])])
+        parser = Parser(creator)
+        with self.assertRaises(CreatorError) as cm:
+            parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
+        self.assertTrue(creator.all_used())
+        # Is the exception correct?
+        self.assertFalse(cm.exception.fatal)
+        self.assertEqual(42, cm.exception.errno)
+
+    def __error(self, plan):
+        creator = FakeCreator(plan)
+        parser = Parser(creator)
+        with self.assertRaises(CreatorError) as cm:
+            parser.get_socket(IPAddr('0.0.0.0'), 0, socket.SOCK_DGRAM)
+        self.assertTrue(creator.all_used())
+        self.assertTrue(cm.exception.fatal)
+
+    def test_error_send(self):
+        self.__error([('e', None)])
+
+    def test_error_recv(self):
+        self.__error([('s', b'SU4\0\0\0\0\0\0'), ('e', None)])
+
+    def test_error_read_fd(self):
+        self.__error([('s', b'SU4\0\0\0\0\0\0'), ('r', b'S'), ('e', None)])
+
+    def __create(self, addr, socktype, encoded):
+        creator = FakeCreator([('s', b'S' + encoded), ('r', b'S'), ('f', 42)])
+        parser = Parser(creator)
+        self.assertEqual(42, parser.get_socket(IPAddr(addr), 42, socktype))
+
+    def test_create1(self):
+        self.__create('192.0.2.0', 'UDP', b'U4\0\x2A\xC0\0\x02\0')
+
+    def test_create2(self):
+        self.__create('2001:db8::', socket.SOCK_STREAM,
+            b'T6\0\x2A\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\0\0')
+
+    def test_create_terminated(self):
+        """
+        Test we can't request sockets after it was terminated.
+        """
+        parser = self.__terminate()
+        with self.assertRaises(CreatorError) as cm:
+            parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
+        self.assertTrue(cm.exception.fatal)
+        self.assertEqual(None, cm.exception.errno)
+
+    def test_invalid_socktype(self):
+        """
+        Test invalid socket type is rejected
+        """
+        self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
+                          IPAddr('0.0.0.0'), 42, 'RAW')
+
+    def test_invalid_family(self):
+        """
+        Test it rejects invalid address family.
+        """
+        # Note: this produces a bad logger output, since this address
+        # can not be converted to string, so the original message with
+        # placeholders is output. This should not happen in practice, so
+        # it is harmless.
+        addr = IPAddr('0.0.0.0')
+        addr.family = 42
+        self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
+                          addr, 42, socket.SOCK_DGRAM)
+
+class WrapTests(unittest.TestCase):
+    """
+    Tests for the wrap_socket function.
+    """
+    def test_wrap(self):
+        # We construct two pairs of socket. The receiving side of one pair will
+        # be wrapped. Then we send one of the other pair through this pair and
+        # check the received one can be used as a socket
+
+        # The transport socket
+        (t1, t2) = socket.socketpair()
+        # The payload socket
+        (p1, p2) = socket.socketpair()
+
+        t2 = WrappedSocket(t2)
+
+        # Transfer the descriptor
+        send_fd(t1.fileno(), p1.fileno())
+        p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
+
+        # Now, pass some data trough the socket
+        p1.send(b'A')
+        data = p2.recv(1)
+        self.assertEqual(b'A', data)
+
+        # Test the wrapping didn't hurt the socket's usual methods
+        t1.send(b'B')
+        data = t2.recv(1)
+        self.assertEqual(b'B', data)
+        t2.send(b'C')
+        data = t1.recv(1)
+        self.assertEqual(b'C', data)
+
+if __name__ == '__main__':
+    isc.log.init("bind10") # FIXME Should this be needed?
+    isc.log.resetUnitTestRootLogger()
+    unittest.main()
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index a35284f..219dcff 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -15,7 +15,7 @@ endif
 check-local:
 	for pytest in $(PYTESTS) ; do \
 	echo Running test: $$pytest ; \
-	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bind10 \
+	env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin:$(abs_top_builddir)/src/bin/bind10:$(abs_top_builddir)/src/lib/util/io/.libs \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
 		$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
diff --git a/src/bin/dhcp6/tests/dhcp6_test.py b/src/bin/dhcp6/tests/dhcp6_test.py
index 61ec009..5ae1f5e 100644
--- a/src/bin/dhcp6/tests/dhcp6_test.py
+++ b/src/bin/dhcp6/tests/dhcp6_test.py
@@ -13,7 +13,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-from bind10 import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+from bind10_src import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
 
 import unittest
 import sys
diff --git a/src/bin/sockcreator/README b/src/bin/sockcreator/README
index 4dbbee7..e142d19 100644
--- a/src/bin/sockcreator/README
+++ b/src/bin/sockcreator/README
@@ -3,7 +3,7 @@ The socket creator
 
 The only thing we need higher rights than standard user is binding sockets to
 ports lower than 1024. So we will have a separate process that keeps the
-rights, while the rests drop them for security reasons.
+rights, while the rest drops them for security reasons.
 
 This process is the socket creator. Its goal is to be as simple as possible
 and to contain as little code as possible to minimise the amount of code




More information about the bind10-changes mailing list