Browse Source

[master] Merge branch 'trac1452b'

JINMEI Tatuya 13 years ago
parent
commit
0b0d1a0d3a

+ 2 - 0
configure.ac

@@ -927,6 +927,8 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/acl/tests/Makefile
                  src/lib/python/isc/util/Makefile
                  src/lib/python/isc/util/tests/Makefile
+                 src/lib/python/isc/util/io/Makefile
+                 src/lib/python/isc/util/io/tests/Makefile
                  src/lib/python/isc/datasrc/Makefile
                  src/lib/python/isc/datasrc/tests/Makefile
                  src/lib/python/isc/dns/Makefile

+ 1 - 1
src/lib/python/isc/util/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . io tests
 
 python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
 

+ 41 - 0
src/lib/python/isc/util/io/Makefile.am

@@ -0,0 +1,41 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+python_PYTHON = __init__.py
+pythondir = $(PYTHON_SITEPKG_DIR)/isc/util/io
+
+pyexec_LTLIBRARIES = socketsession.la
+pyexecdir = $(PYTHON_SITEPKG_DIR)/isc/util/io
+
+socketsession_la_SOURCES = socketsession_python.cc socketsession_python.h
+socketsession_la_SOURCES += socketsessionforwarder_python.cc
+socketsession_la_SOURCES += socketsessionforwarder_python.h
+socketsession_la_SOURCES += socketsessionreceiver_python.cc
+socketsession_la_SOURCES += socketsessionreceiver_python.h
+socketsession_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+socketsession_la_LDFLAGS = $(PYTHON_LDFLAGS)
+# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
+# placed after -Wextra defined in AM_CXXFLAGS
+socketsession_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
+
+# Python prefers .so, while some OSes (specifically MacOS) use a different
+# suffix for dynamic objects.  -module is necessary to work this around.
+socketsession_la_LDFLAGS += -module
+socketsession_la_LIBADD = $(top_builddir)/src/lib/util/io/libutil_io.la
+socketsession_la_LIBADD += $(PYTHON_LIB)
+
+# This is not installed, it helps locate the module during tests
+EXTRA_DIST = __init__.py socketsession.py
+
+EXTRA_DIST += socketsession_inc.cc
+EXTRA_DIST += socketsessionforwarder_inc.cc socketsessionreceiver_inc.cc
+
+CLEANFILES = __init__.pyc socketsession.pyc
+
+CLEANDIRS = __pycache__
+
+clean-local:
+	rm -rf $(CLEANDIRS)

+ 3 - 0
src/lib/python/isc/util/io/__init__.py

@@ -0,0 +1,3 @@
+"""
+Here are function and classes for forwarding socket sessions between processes.
+"""

+ 26 - 0
src/lib/python/isc/util/io/socketsession.py

@@ -0,0 +1,26 @@
+# Copyright (C) 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 is not installed.  See python/isc/log/__init__.py for the trick.
+
+import os
+import sys
+
+for base in sys.path[:]:
+    libdir = os.path.join(base, 'isc/util/io/.libs')
+    if os.path.exists(libdir):
+        sys.path.insert(0, libdir)
+
+from socketsession import *

+ 122 - 0
src/lib/python/isc/util/io/socketsession_inc.cc

@@ -0,0 +1,122 @@
+namespace {
+const char* const socketsession_doc = "\
+This module defines a set of classes that support forwarding a\n\
+\"socket session\" from one process to another.  A socket session is a\n\
+conceptual tuple of the following elements:\n\
+\n\
+- A network socket\n\
+- The local and remote endpoints of a (IP) communication taking place\n\
+  on the socket. In practice an endpoint is a pair of an IP address\n\
+  and TCP or UDP port number.\n\
+- Some amount of data sent from the remote endpoint and received on\n\
+  the socket. We call it (socket) session data in this documentation.\n\
+\n\
+Note that this is a conceptual definition. Depending on the underlying\n\
+implementation and/or the network protocol, some of the elements could\n\
+be part of others; for example, if it's an established TCP connection,\n\
+the local and remote endpoints would be able to be retrieved from the\n\
+socket using the standard getsockname() and getpeername() system\n\
+calls. But in this definition we separate these to be more generic.\n\
+Also, as a matter of fact our intended usage includes non-connected\n\
+UDP communications, in which case at least the remote endpoint should\n\
+be provided separately from the socket.\n\
+\n\
+In the actual implementation we represent a socket as a Python socket\n\
+object, which contains the information of the address family\n\
+(e.g. AF_INET6), socket type (e.g. SOCK_STREAM), and protocol\n\
+(e.g. IPPROTO_TCP).\n\
+\n\
+We use the Python socket address tuple to represent endpoints.\n\
+\n\
+Socket session data is an opaque blob in the form of a Python byte\n\
+object.\n\
+\n\
+To forward a socket session between processes, we use connected UNIX\n\
+domain sockets established between the processes. The file descriptor\n\
+will be forwarded through the sockets as an ancillary data item of\n\
+type SCM_RIGHTS. Other elements of the session will be transferred as\n\
+normal data over the connection.\n\
+\n\
+We provide two classes to help applications forward socket sessions:\n\
+SocketSessionForwarder is the sender of the UNIX domain connection,\n\
+while SocketSessionReceiver is the receiver (this interface assumes\n\
+one direction of forwarding).\n\
+\n\
+Note: this paragraph and following discussions on the internal\n\
+protocol are for reference purposes only; it's not necessary to\n\
+understand how to use the API.\n\
+SocketSessionForwarder and SocketSessionReceiver objects (internally)\n\
+use a straightforward protocol to pass elements of socket sessions.\n\
+Once the connection is established, the forwarder object first forwards\n\
+the file descriptor with 1-byte dummy data.  It then forwards a\n\
+\"(socket) session header\", which contains all other elements of\n\
+the session except the file descriptor (already forwarded) and session\n\
+data.  The wire format of the header is as follows:\n\
+\n\
+- The length of the header (16-bit unsigned integer)\n\
+- Address family\n\
+- Socket type\n\
+- Protocol\n\
+- Size of the local endpoint in bytes\n\
+- Local endpoint (a copy of the memory image of the corresponding\n\
+  sockaddr)\n\
+- Size of the remote endpoint in bytes\n\
+- Remote endpoint (same as local endpoint)\n\
+- Size of session data in bytes\n\
+\n\
+The type of the fields is 32-bit unsigned integer unless explicitly\n\
+noted, and all fields are formatted in the network byte order.\n\
+\n\
+The socket session data immediately follows the session header.\n\
+\n\
+Note that the fields do not necessarily be in the network byte order\n\
+because they are expected to be exchanged on the same machine.\n\
+Likewise, integer elements such as address family do not necessarily\n\
+be represented as an fixed-size value (i.e., 32-bit). But fixed size\n\
+fields are used in order to ensure maximum portability in such a\n\
+(rare) case where the forwarder and the receiver are built with\n\
+different compilers that have different definitions of int. Also,\n\
+since sockaddr fields are generally formatted in the network byte\n\
+order, other fields are defined so to be consistent.\n\
+\n\
+One basic assumption in the API of this module is socket sessions\n\
+should be forwarded without blocking, thus eliminating the need for\n\
+incremental read/write or blocking other important services such as\n\
+responding to requests from the application's clients. This assumption\n\
+should be held as long as both the forwarder and receiver have\n\
+sufficient resources to handle the forwarding process since the\n\
+communication is local. But a forward attempt could still block if the\n\
+receiver is busy (or even hang up) and cannot keep up with the volume\n\
+of incoming sessions.\n\
+\n\
+So, in this implementation, the forwarder uses non blocking writes to\n\
+forward sessions. If a write attempt could block, it immediately gives\n\
+up the operation with an exception. The corresponding application is\n\
+expected to catch it, close the connection, and perform any necessary\n\
+recovery for that application (that would normally be re-establish the\n\
+connection with a new receiver, possibly after confirming the\n\
+receiving side is still alive). On the other hand, the receiver\n\
+implementation assumes it's possible that it only receive incomplete\n\
+elements of a session (such as in the case where the forwarder writes\n\
+part of the entire session and gives up the connection). The receiver\n\
+implementation throws an exception when it encounters an incomplete\n\
+session. Like the case of the forwarder application, the receiver\n\
+application is expected to catch it, close the connection, and perform\n\
+any necessary recovery steps.\n\
+\n\
+Note that the receiver implementation uses blocking read. So it's\n\
+application's responsibility to ensure that there's at least some data\n\
+in the connection when the receiver object is requested to receive a\n\
+session (unless this operation can be blocking, e.g., by the use of a\n\
+separate thread). Also, if the forwarder implementation or application\n\
+is malicious or extremely buggy and intentionally sends partial\n\
+session and keeps the connection, the receiver could block in\n\
+receiving a session. In general, we assume the forwarder doesn't do\n\
+intentional blocking as it's a local node and is generally a module of\n\
+the same (BIND 10) system. The minimum requirement for the forwarder\n\
+implementation (and application) is to make sure the connection is\n\
+closed once it detects an error on it. Even a naive implementation\n\
+that simply dies due to the exception will meet this requirement.\n\
+\n\
+";
+} // unnamed namespace

+ 79 - 0
src/lib/python/isc/util/io/socketsession_python.cc

@@ -0,0 +1,79 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include "socketsessionreceiver_python.h"
+#include "socketsessionforwarder_python.h"
+
+using namespace isc::util::io::python;
+using namespace isc::util::python;
+
+#include "socketsession_inc.cc"
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+PyObject* po_SocketSessionError;
+}
+}
+}
+}
+
+namespace {
+
+PyModuleDef socketsession = {
+    { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
+    "isc.util.io.socketsession",
+    socketsession_doc,
+    -1,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL
+};
+} // end of unnamed namespace
+
+PyMODINIT_FUNC
+PyInit_socketsession(void) {
+    PyObject* mod = PyModule_Create(&socketsession);
+    if (mod == NULL) {
+        return (NULL);
+    }
+
+    try {
+        po_SocketSessionError =
+            PyErr_NewException("isc.util.io.SocketSessionError", NULL, NULL);
+        PyObjectContainer(po_SocketSessionError).
+            installToModule(mod, "SocketSessionError");
+    } catch (...) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
+    if (!initModulePart_SocketSessionForwarder(mod)) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+    if (!initModulePart_SocketSessionReceiver(mod)) {
+        Py_DECREF(mod);
+        return (NULL);
+    }
+
+    return (mod);
+}

+ 35 - 0
src/lib/python/isc/util/io/socketsession_python.h

@@ -0,0 +1,35 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+#ifndef __PYTHON_SOCKETSESSION_H
+#define __PYTHON_SOCKETSESSION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+
+extern PyObject* po_SocketSessionError;
+
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc
+#endif // __PYTHON_SOCKETSESSION_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 136 - 0
src/lib/python/isc/util/io/socketsessionforwarder_inc.cc

@@ -0,0 +1,136 @@
+namespace {
+// Modifications:
+//  reference to the module description (instead of "utility")
+//  exception description
+const char* const SocketSessionForwarder_doc = "\
+The forwarder of socket sessions.\n\
+\n\
+An object of this class maintains a UNIX domain socket (normally\n\
+expected to be connected to a SocketSessionReceiver object) and\n\
+forwards socket sessions to the receiver.\n\
+\n\
+See the description of socketsession module for other details of how\n\
+the session forwarding works.\n\
+\n\
+SocketSessionForwarder(unix_file)\n\
+\n\
+    The constructor.\n\
+\n\
+    It's constructed with path information of the intended receiver,\n\
+    but does not immediately establish a connection to the receiver;\n\
+    connect_to_receiver() must be called to establish it. These are\n\
+    separated so that an object of class can be initialized (possibly\n\
+    as an attribute of a higher level application class object)\n\
+    without knowing the receiver is ready for accepting new\n\
+    forwarders. The separate connect interface allows the object to be\n\
+    reused when it detects connection failure and tries to re-\n\
+    establish it after closing the failed one.\n\
+\n\
+    On construction, it also installs a signal filter for SIGPIPE to\n\
+    ignore it. Since this class uses a stream-type connected UNIX\n\
+    domain socket, if the receiver (abruptly) closes the connection a\n\
+    subsequent write operation on the socket would trigger a SIGPIPE\n\
+    signal, which kills the caller process by default. This behavior\n\
+    would be undesirable in many cases, so this implementation always\n\
+    disables the signal.\n\
+\n\
+    This approach has some drawbacks, however; first, since signal\n\
+    handling is process (or thread) wide, ignoring it may not what the\n\
+    application wants. On the other hand, if the application changes\n\
+    how the signal is handled after instantiating this class, the new\n\
+    behavior affects the class operation. Secondly, even if ignoring\n\
+    the signal is the desired operation, it's a waste to set the\n\
+    filter every time this class object is constructed. It's\n\
+    sufficient to do it once. We still adopt this behavior based on\n\
+    the observation that in most cases applications would like to\n\
+    ignore SIGPIPE (or simply doesn't care about it) and that this\n\
+    class is not instantiated so often (so the wasteful setting\n\
+    overhead should be marginal). On the other hand, doing it every\n\
+    time is beneficial if the application is threaded and different\n\
+    threads create different forwarder objects (and if signals work\n\
+    per thread).\n\
+\n\
+    Exceptions:\n\
+      SocketSessionError unix_file is invalid as a path name of a UNIX\n\
+                 domain socket or error happens in setting a filter for\n\
+                 SIGPIPE (see above)\n\
+      SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+    Parameters:\n\
+      unix_file  Path name of the receiver.\n\
+\n\
+";
+
+// Modifications:
+//  exception description
+const char* const SocketSessionForwarder_connectToReceiver_doc = "\
+connect_to_receiver()\n\
+\n\
+Establish a connection to the receiver.\n\
+\n\
+This method establishes a connection to the receiver at the path given\n\
+on construction. It makes the underlying UNIX domain socket non\n\
+blocking, so this method (or subsequent push() calls) does not block.\n\
+\n\
+Exceptions:\n\
+  TypeError  The method is called while an already established\n\
+             connection is still active.\n\
+  SocketSessionError A system error in socket operation.\n\
+  SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+";
+
+// Modifications:
+//  bullet description
+//  parameters
+//  exception description
+const char* const SocketSessionForwarder_push_doc = "\
+push(sock, family, type, protocol, local_end, remote_end, data)\n\
+\n\
+Forward a socket session to the receiver.\n\
+\n\
+This method takes a set of parameters that represent a single socket\n\
+session, renders them in the \"wire\" format according to the internal\n\
+protocol (see socketsession module) and forwards them to the\n\
+receiver through the UNIX domain connection.\n\
+\n\
+The connection must have been established by connect_to_receiver().\n\
+\n\
+For simplicity and for the convenience of detecting application\n\
+errors, this method imposes some restrictions on the parameters:\n\
+\n\
+- Socket family must be either AF_INET or AF_INET6\n\
+- The address family (sa_family) member of the local and remote end\n\
+  points must be equal to the family parameter\n\
+- Socket session data must not be empty\n\
+- Data length must not exceed 65535\n\
+\n\
+These are not architectural limitation, and might be loosened in future\n\
+versions as we see the need for flexibility.\n\
+\n\
+Since the underlying UNIX domain socket is non blocking (see the\n\
+description for the constructor), a call to this method should either\n\
+return immediately or result in exception (in case of \"would\n\
+block\").\n\
+\n\
+Exceptions:\n\
+  TypeError  The method is called before establishing a connection or\n\
+             given parameters are invalid, or the given socket address\n\
+             is valid.\n\
+  SocketSessionError A system error in socket operation, including the\n\
+             case where the write operation would block.\n\
+\n\
+Parameters:\n\
+  sock       (int) The socket file descriptor\n\
+  family     (int) The address family (such as socket.AF_INET6) of the\n\
+             socket\n\
+  type       (int) The socket type (such as socket.SOCK_DGRAM) of the\n\
+             socket\n\
+  protocol   (int) The transport protocol (such as socket.IPPROTO_UDP)\n\
+             of the socket\n\
+  local_end  (socket address) The local end point of the session\n\
+  remote_end (socket address) The remote end point of the session\n\
+  data       (byte) the session data\n\
+\n\
+";
+} // unnamed namespace

+ 305 - 0
src/lib/python/isc/util/io/socketsessionforwarder_python.cc

@@ -0,0 +1,305 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <boost/lexical_cast.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <util/io/sockaddr_util.h>
+#include <util/io/socketsession.h>
+#include <util/python/pycppwrapper_util.h>
+
+#include "socketsession_python.h"
+#include "socketsessionforwarder_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::util::io;
+using namespace isc::util::io::internal;
+using namespace isc::util::io::python;
+using boost::lexical_cast;
+
+// Trivial constructor.
+s_SocketSessionForwarder::s_SocketSessionForwarder() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "socketsessionforwarder_inc.cc"
+
+namespace {
+
+int
+SocketSessionForwarder_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_SocketSessionForwarder* self =
+        static_cast<s_SocketSessionForwarder*>(po_self);
+    try {
+        const char* unix_file;
+        if (PyArg_ParseTuple(args, "s", &unix_file)) {
+            self->cppobj = new SocketSessionForwarder(unix_file);
+            return (0);
+        }
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to construct SocketSessionForwarder object: " +
+            string(ex.what());
+        PyErr_SetString(po_SocketSessionError, ex_what.c_str());
+        return (-1);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (-1);
+    }
+
+    return (-1);
+}
+
+void
+SocketSessionForwarder_destroy(PyObject* po_self) {
+    s_SocketSessionForwarder* self =
+        static_cast<s_SocketSessionForwarder*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+// Internal exception class thrown when address parsing fails
+class AddressParseError: public isc::Exception {
+public:
+    AddressParseError(const char *file, size_t line, const char *what):
+        isc::Exception(file, line, what) {}
+};
+
+// Convert a Python socket address object to an addrinfo structure by
+// getaddrinfo.
+void
+parsePySocketAddress(PyObject* obj, int type, int protocol,
+                     struct sockaddr_storage* ss)
+{
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_socktype = type;
+    hints.ai_protocol = protocol;
+    hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+    const char* addr;
+    int port, flowinfo, scopeid;
+    struct addrinfo *res;
+    if (PyArg_ParseTuple(obj, "si", &addr, &port)) {
+        // Possibly an IPv4 address.
+        hints.ai_family = AF_INET;
+        const int error = getaddrinfo(addr,
+                                      lexical_cast<string>(port).c_str(),
+                                      &hints, &res);
+        if (error == 0) {
+            assert(res->ai_addrlen <= sizeof(*ss));
+            memcpy(ss, res->ai_addr, res->ai_addrlen);
+            return;
+        }
+        isc_throw(AddressParseError, "Invalid or unsupported socket address: "
+                  << gai_strerror(error));
+    }
+    PyErr_Clear();
+    if (PyArg_ParseTuple(obj, "siii", &addr, &port, &flowinfo, &scopeid)) {
+        // Possibly an IPv6 address.  We ignore flowinfo.
+        hints.ai_family = AF_INET6;
+        const int error = getaddrinfo(addr,
+                                      lexical_cast<string>(port).c_str(),
+                                      &hints, &res);
+        if (error == 0) {
+            assert(res->ai_addrlen <= sizeof(*ss));
+            memcpy(ss, res->ai_addr, res->ai_addrlen);
+            void* p = ss;
+            static_cast<struct sockaddr_in6*>(p)->sin6_scope_id = scopeid;
+            return;
+        }
+        isc_throw(AddressParseError, "Invalid or unsupported socket address: "
+                  << gai_strerror(error));
+    }
+    PyErr_Clear();
+    isc_throw(AddressParseError, "Invalid or unsupported socket address, must "
+              "be AF_INET or AF_INET6 socket address.");
+}
+
+PyObject*
+SocketSessionForwarder_connectToReceiver(PyObject* po_self, PyObject*) {
+    s_SocketSessionForwarder* const self =
+        static_cast<s_SocketSessionForwarder*>(po_self);
+
+    try {
+        self->cppobj->connectToReceiver();
+        Py_RETURN_NONE;
+    } catch (const isc::BadValue& ex) {
+        PyErr_SetString(PyExc_TypeError, ex.what());
+        return (NULL);
+    } catch (const SocketSessionError& ex) {
+        PyErr_SetString(po_SocketSessionError, ex.what());
+        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in connecting to receiver: " +
+            string(ex.what());
+        PyErr_SetString(PyExc_SystemError, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+}
+
+PyObject*
+SocketSessionForwarder_push(PyObject* po_self, PyObject* args) {
+    s_SocketSessionForwarder* const self =
+        static_cast<s_SocketSessionForwarder*>(po_self);
+
+    try {
+        int fd, family, type, protocol;
+        PyObject* po_local_end;
+        PyObject* po_remote_end;
+        Py_buffer py_buf;
+
+        if (!PyArg_ParseTuple(args, "iiiiOOy*", &fd, &family, &type, &protocol,
+                              &po_local_end, &po_remote_end, &py_buf)) {
+            return (NULL);
+        }
+        struct sockaddr_storage ss_local, ss_remote;
+        parsePySocketAddress(po_local_end, type, protocol, &ss_local);
+        parsePySocketAddress(po_remote_end, type, protocol, &ss_remote);
+        self->cppobj->push(fd, family, type, protocol,
+                           *convertSockAddr(&ss_local),
+                           *convertSockAddr(&ss_remote),
+                           py_buf.buf, py_buf.len);
+        Py_RETURN_NONE;
+    } catch (const AddressParseError& ex) {
+        PyErr_SetString(PyExc_TypeError, ex.what());
+        return (NULL);
+    } catch (const isc::BadValue& ex) {
+        PyErr_SetString(PyExc_TypeError, ex.what());
+        return (NULL);
+    } catch (const SocketSessionError& ex) {
+        PyErr_SetString(po_SocketSessionError, ex.what());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    } 
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef SocketSessionForwarder_methods[] = {
+    { "push", SocketSessionForwarder_push, METH_VARARGS,
+      SocketSessionForwarder_push_doc },
+    { "connect_to_receiver", SocketSessionForwarder_connectToReceiver,
+      METH_NOARGS, SocketSessionForwarder_connectToReceiver_doc },
+    { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_SocketSessionForwarder
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject socketsessionforwarder_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "isc.util.io.SocketSessionForwarder",
+    sizeof(s_SocketSessionForwarder),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    SocketSessionForwarder_destroy,                 // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash
+    NULL,                               // tp_call
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    SocketSessionForwarder_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    SocketSessionForwarder_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    SocketSessionForwarder_init,        // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_SocketSessionForwarder(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&socketsessionforwarder_type) < 0) {
+        return (false);
+    }
+    void* p = &socketsessionforwarder_type;
+    if (PyModule_AddObject(mod, "SocketSessionForwarder",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+    Py_INCREF(&socketsessionforwarder_type);
+
+    return (true);
+}
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc

+ 45 - 0
src/lib/python/isc/util/io/socketsessionforwarder_python.h

@@ -0,0 +1,45 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+#ifndef __PYTHON_SOCKETSESSIONFORWARDER_H
+#define __PYTHON_SOCKETSESSIONFORWARDER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+namespace io {
+class SocketSessionForwarder;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_SocketSessionForwarder : public PyObject {
+public:
+    s_SocketSessionForwarder();
+    SocketSessionForwarder* cppobj;
+};
+
+extern PyTypeObject socketsessionforwarder_type;
+
+bool initModulePart_SocketSessionForwarder(PyObject* mod);
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc
+#endif // __PYTHON_SOCKETSESSIONFORWARDER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 89 - 0
src/lib/python/isc/util/io/socketsessionreceiver_inc.cc

@@ -0,0 +1,89 @@
+namespace {
+// Modifications
+//   - about return value
+//   - socket session "utility" => module
+const char* const SocketSessionReceiver_doc = "\
+The receiver of socket sessions.\n\
+\n\
+An object of this class holds a UNIX domain socket for an established\n\
+connection, receives socket sessions from the remote forwarder, and\n\
+provides the session to the application as a tuple of corresponding\n\
+elements.\n\
+\n\
+Note that this class is instantiated with an already connected socket;\n\
+it's not a listening socket that is accepting connection requests from\n\
+forwarders. It's application's responsibility to create the listening\n\
+socket, listen on it and accept connections. Once the connection is\n\
+established, the application would construct a SocketSessionReceiver\n\
+object with the socket for the newly established connection. This\n\
+behavior is based on the design decision that the application should\n\
+decide when it performs (possibly) blocking operations (see\n\
+socketsession module for more details).\n\
+\n\
+See the description of socketsession module for other details of how\n\
+the session forwarding works.\n\
+\n\
+SocketSessionReceiver(socket)\n\
+\n\
+    The constructor.\n\
+\n\
+    Exceptions:\n\
+      TypeError  The given parameter is not a valid socket object\n\
+      SocketSessionError Any error on an operation that is performed\n\
+                 on the given socket as part of initialization.\n\
+      SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+    Parameters:\n\
+      socket     A python socket object of a UNIX domain family for an\n\
+                 established connection with a forwarder.\n\
+\n\
+";
+
+// Modifications
+//  - socket session utility -> module
+//  - return value (not a SocketSession object, but a Python tuple)
+//  - remove the validity note (we copy it here, so there's no such
+//    restriction)
+//  - caller's responsibility: only responsible for closing the socket.
+//  - text around the bullets
+//  - exception
+const char* const SocketSessionReceiver_pop_doc = "\
+pop() -> (socket, socket address, socket address, byte)\n\
+\n\
+Receive a socket session from the forwarder.\n\
+\n\
+This method receives wire-format data (see socketsession module) for\n\
+a socket session on the UNIX domain socket, performs some validation\n\
+on the data, and returns the session information as a tuple.\n\
+\n\
+The caller is responsible for closing the received socket.\n\
+\n\
+It ensures the following:\n\
+\n\
+- The socket's address family is either AF_INET or AF_INET6\n\
+- The family element of the socket addresses for the local and remote\n\
+  end points must be equal to the socket's address family\n\
+- The socket session data is not empty and does not exceed 65535\n\
+  bytes.\n\
+\n\
+If the validation fails or an unexpected system error happens\n\
+(including a connection close in the meddle of reception), it throws\n\
+an SocketSessionError exception. When this happens, it's very\n\
+unlikely that a subsequent call to this method succeeds, so in\n\
+reality the application is expected to destruct it and close the\n\
+socket in such a case.\n\
+\n\
+Exceptions:\n\
+  SocketSessionError Invalid data is received or a system error on\n\
+             socket operation happens.\n\
+  SystemError Unexpected errors such as resource allocation failure\n\
+\n\
+Return Value(s): A tuple corresponding to the extracted socket session:\n\
+  socket     A Python socket object corresponding to the socket passed\n\
+             by the forwarder\n\
+  socket address A Python socket address (which is a tuple) for the local\n\
+             end point\n\
+  socket address A Python socket address for the remote endpoint\n\
+  data       A Python byte object that stores the session data\n\
+";
+} // unnamed namespace

+ 326 - 0
src/lib/python/isc/util/io/socketsessionreceiver_python.cc

@@ -0,0 +1,326 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include <string>
+#include <stdexcept>
+
+#include <boost/lexical_cast.hpp>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <util/io/socketsession.h>
+
+#include "socketsession_python.h"
+#include "socketsessionreceiver_python.h"
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::util::io;
+using namespace isc::util::io::python;
+using boost::lexical_cast;
+
+// Trivial constructor.
+s_SocketSessionReceiver::s_SocketSessionReceiver() : cppobj(NULL) {
+}
+
+// Import pydoc text
+#include "socketsessionreceiver_inc.cc"
+
+namespace {
+// This C structure corresponds to a Python callable object for
+// socket.fromfd().
+// See json_dumps_obj in dns_requestloader_python.cc for background rationale
+// of this trick.
+PyObject* socket_fromfd_obj = NULL;
+
+int
+SocketSessionReceiver_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_SocketSessionReceiver* self =
+        static_cast<s_SocketSessionReceiver*>(po_self);
+    try {
+        // The constructor expects a Python socket object.  We'll extract
+        // the underlying file descriptor using the fileno method (in the
+        // duck typing manner) and pass it to the C++ constructor.
+        // PyObject_CallMethod() could return NULL (especially if the given
+        // object is of the wrong type and doesn't have the "fileno" method),
+        // in which case PyObjectContainer will detect it and throw
+        // PyCPPWrapperException, which will be converted to the Python
+        // TypeError below.
+        PyObject* po_sock;
+        if (PyArg_ParseTuple(args, "O", &po_sock)) {
+            PyObjectContainer fd_container(PyObject_CallMethod(
+                                               po_sock,
+                                               const_cast<char*>("fileno"),
+                                               NULL));
+            PyObjectContainer fdarg_container(
+                Py_BuildValue("(O)", fd_container.get()));
+            int fd;
+            if (PyArg_ParseTuple(fdarg_container.get(), "i", &fd)) {
+                self->cppobj = new SocketSessionReceiver(fd);
+                return (0);
+            }
+            PyErr_SetString(PyExc_TypeError, "Given object's fileno() doesn't "
+                            "return an integer, probably not a valid socket "
+                            "object");
+        }
+    } catch (const PyCPPWrapperException& ex) {
+        // This could happen due to memory allocation failure, but it's more
+        // likely that the object doesn't have the "fileno()" method or it
+        // returns an unexpected type of value.  So we adjust the error
+        // message accordingly.
+        PyErr_SetString(PyExc_TypeError, "Failed to parse parameter, "
+                        "probably not a valid socket object");
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Failed to construct SocketSessionReceiver object: " +
+            string(ex.what());
+        PyErr_SetString(po_SocketSessionError, ex_what.c_str());
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+    }
+
+    return (-1);
+}
+
+PyObject*
+createPySocketAddress(const struct sockaddr& sa) {
+    socklen_t salen;
+    if (sa.sa_family == AF_INET) {
+        salen = sizeof(struct sockaddr_in);
+    } else if (sa.sa_family == AF_INET6) {
+        salen = sizeof(struct sockaddr_in6);
+    } else {
+        isc_throw(SocketSessionError, "Unsupported socket address family: "
+                  << static_cast<int>(sa.sa_family));
+    }
+
+    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+    const int error = getnameinfo(&sa, salen, hbuf, sizeof(hbuf), sbuf,
+                                  sizeof(sbuf),
+                                  NI_NUMERICHOST | NI_NUMERICSERV);
+    if (error != 0) {
+        isc_throw(SocketSessionError, "Unrecognized socket address format: "
+                  << gai_strerror(error));
+    }
+    if (sa.sa_family == AF_INET) {
+        return (Py_BuildValue("(si)", hbuf, lexical_cast<int>(sbuf)));
+    }
+    // We know it's AF_INET6 at this point.  We need some special trick for
+    // non-0 scope (zone) ID: getnameinfo() may convert the address to a
+    // textual representation using the extension described in RFC 4007,
+    // in which case it contains a delimiter character '%'.  We need to remove
+    // it before constructing the tuple.  The scope (zone) ID is preserved
+    // in the corresponding field of the tuple.
+    const void* p = &sa;
+    const struct sockaddr_in6* sin6 =
+        static_cast<const struct sockaddr_in6*>(p);
+    char* cp = strchr(hbuf, '%');
+    if (cp != NULL) {
+        *cp = '\0';
+    }
+    return (Py_BuildValue("(siii)", hbuf, lexical_cast<int>(sbuf), 0,
+                          sin6->sin6_scope_id));
+}
+
+void
+SocketSessionReceiver_destroy(PyObject* po_self) {
+    s_SocketSessionReceiver* self =
+        static_cast<s_SocketSessionReceiver*>(po_self);
+    delete self->cppobj;
+    self->cppobj = NULL;
+    Py_TYPE(self)->tp_free(self);
+}
+
+// A helper struct to automatically close a socket in an RAII manner.
+struct ScopedSocket : boost::noncopyable {
+    ScopedSocket(int fd) : fd_(fd) {}
+    ~ScopedSocket() {
+        close(fd_);
+    }
+    const int fd_;
+};
+
+PyObject*
+SocketSessionReceiver_pop(PyObject* po_self, PyObject*) {
+    s_SocketSessionReceiver* const self =
+        static_cast<s_SocketSessionReceiver*>(po_self);
+
+    try {
+        // retrieve the session, and the convert it to a corresponding
+        // Python tuple.
+        const SocketSession session = self->cppobj->pop();
+
+        // We need to immediately store the socket file descriptor in a
+        // ScopedSocket object.  socket.fromfd() will dup() the FD, so we need
+        // to close our copy even if an exception is thrown.
+        ScopedSocket sock(session.getSocket());
+
+        // Build Python socket object
+        PyObjectContainer c_args(Py_BuildValue("(iiii)", sock.fd_,
+                                               session.getFamily(),
+                                               session.getType(),
+                                               session.getProtocol()));
+        PyObjectContainer c_sock(PyObject_CallObject(socket_fromfd_obj,
+                                                     c_args.get()));
+        // Convert the local and remote sockaddr to Python socket address objs
+        PyObjectContainer c_local(createPySocketAddress(
+                                      session.getLocalEndpoint()));
+        PyObjectContainer c_remote(createPySocketAddress(
+                                       session.getRemoteEndpoint()));
+        // Convert the session data to Python byte object.
+        PyObjectContainer c_data(Py_BuildValue("y#", session.getData(),
+                                               session.getDataLength()));
+
+        // Build a tuple from them and return it.
+        return (Py_BuildValue("(OOOO)", c_sock.get(), c_local.get(),
+                              c_remote.get(), c_data.get()));
+    } catch (const SocketSessionError& ex) {
+        PyErr_SetString(po_SocketSessionError, ex.what());
+        return (NULL);
+    } catch (const exception& ex) {
+        const string ex_what =
+            "Unexpected failure in receiving a socket session: " +
+            string(ex.what());
+        PyErr_SetString(PyExc_SystemError, ex_what.c_str());
+        return (NULL);
+    } catch (...) {
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+        return (NULL);
+    }
+}
+
+// These are the functions we export
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef SocketSessionReceiver_methods[] = {
+    { "pop", SocketSessionReceiver_pop, METH_NOARGS,
+      SocketSessionReceiver_pop_doc },
+    { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace util {
+namespace io {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_SocketSessionReceiver
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject socketsessionreceiver_type = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "isc.util.io.SocketSessionReceiver",
+    sizeof(s_SocketSessionReceiver),                 // tp_basicsize
+    0,                                  // tp_itemsize
+    SocketSessionReceiver_destroy,                 // tp_dealloc
+    NULL,                               // tp_print
+    NULL,                               // tp_getattr
+    NULL,                               // tp_setattr
+    NULL,                               // tp_reserved
+    NULL,                               // tp_repr
+    NULL,                               // tp_as_number
+    NULL,                               // tp_as_sequence
+    NULL,                               // tp_as_mapping
+    NULL,                               // tp_hash
+    NULL,                               // tp_call
+    NULL,                               // tp_str
+    NULL,                               // tp_getattro
+    NULL,                               // tp_setattro
+    NULL,                               // tp_as_buffer
+    Py_TPFLAGS_DEFAULT,                 // tp_flags
+    SocketSessionReceiver_doc,
+    NULL,                               // tp_traverse
+    NULL,                               // tp_clear
+    NULL,                               // tp_richcompare
+    0,                                  // tp_weaklistoffset
+    NULL,                               // tp_iter
+    NULL,                               // tp_iternext
+    SocketSessionReceiver_methods,                   // tp_methods
+    NULL,                               // tp_members
+    NULL,                               // tp_getset
+    NULL,                               // tp_base
+    NULL,                               // tp_dict
+    NULL,                               // tp_descr_get
+    NULL,                               // tp_descr_set
+    0,                                  // tp_dictoffset
+    SocketSessionReceiver_init,                    // tp_init
+    NULL,                               // tp_alloc
+    PyType_GenericNew,                  // tp_new
+    NULL,                               // tp_free
+    NULL,                               // tp_is_gc
+    NULL,                               // tp_bases
+    NULL,                               // tp_mro
+    NULL,                               // tp_cache
+    NULL,                               // tp_subclasses
+    NULL,                               // tp_weaklist
+    NULL,                               // tp_del
+    0                                   // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_SocketSessionReceiver(PyObject* mod) {
+    // We initialize the static description object with PyType_Ready(),
+    // then add it to the module. This is not just a check! (leaving
+    // this out results in segmentation faults)
+    if (PyType_Ready(&socketsessionreceiver_type) < 0) {
+        return (false);
+    }
+    void* p = &socketsessionreceiver_type;
+    if (PyModule_AddObject(mod, "SocketSessionReceiver",
+                           static_cast<PyObject*>(p)) < 0) {
+        return (false);
+    }
+
+    PyObject* socket_module = PyImport_AddModule("socket");
+    if (socket_module != NULL) {
+        PyObject* socket_dict = PyModule_GetDict(socket_module);
+        if (socket_dict != NULL) {
+            socket_fromfd_obj = PyDict_GetItemString(socket_dict, "fromfd");
+        }
+    }
+    if (socket_fromfd_obj != NULL) {
+        Py_INCREF(socket_fromfd_obj);
+    } else {
+        PyErr_SetString(PyExc_RuntimeError,
+                        "isc.util.io.SocketSessionReceiver needs "
+                        "socket.fromfd(), but it's missing");
+        return (false);
+    }
+
+    Py_INCREF(&socketsessionreceiver_type);
+
+    return (true);
+}
+
+} // namespace python
+} // namespace io
+} // namespace util
+} // namespace isc

+ 46 - 0
src/lib/python/isc/util/io/socketsessionreceiver_python.h

@@ -0,0 +1,46 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC 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.
+
+#ifndef __PYTHON_SOCKETSESSIONRECEIVER_H
+#define __PYTHON_SOCKETSESSIONRECEIVER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace util {
+namespace io {
+class SocketSessionReceiver;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+class s_SocketSessionReceiver : public PyObject {
+public:
+    s_SocketSessionReceiver();
+    SocketSessionReceiver* cppobj;
+};
+
+extern PyTypeObject socketsessionreceiver_type;
+
+bool initModulePart_SocketSessionReceiver(PyObject* mod);
+
+} // namespace io
+} // namespace python
+} // namespace util
+} // namespace isc
+#endif // __PYTHON_SOCKETSESSIONRECEIVER_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 35 - 0
src/lib/python/isc/util/io/tests/Makefile.am

@@ -0,0 +1,35 @@
+PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
+PYTESTS = socketsession_test.py
+EXTRA_DIST = $(PYTESTS)
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+	touch $(abs_top_srcdir)/.coverage
+	rm -f .coverage
+	${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+# XXX: we pollute the top builddir for creating a temporary test file
+# so we can minimize the risk of exceeding the limit of file name path size
+# for a UNIX domain socket.
+	for pytest in $(PYTESTS) ; do \
+	echo Running test: $$pytest ; \
+	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/lib/isc/python/util/io/.libs \
+	$(LIBRARY_PATH_PLACEHOLDER) \
+	TESTDATAOBJDIR=$(abs_top_builddir) \
+	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+	done
+
+CLEANFILES = $(abs_top_builddir)/ssessiontest.unix
+
+CLEANDIRS = __pycache__
+
+clean-local:
+	rm -rf $(CLEANDIRS)

+ 253 - 0
src/lib/python/isc/util/io/tests/socketsession_test.py

@@ -0,0 +1,253 @@
+# Copyright (C) 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.
+
+import os, signal, socket, unittest
+from socket import AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM, IPPROTO_UDP, \
+    IPPROTO_TCP
+from isc.util.io.socketsession import *
+
+TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
+TEST_UNIX_FILE = TESTDATA_OBJDIR + '/ssessiontest.unix'
+TEST_DATA = b'BIND10 test'
+TEST_PORT = 53535
+
+class TestForwarder(unittest.TestCase):
+    '''In general, this is a straightforward port of the C++ counterpart.
+
+    In some cases test cases are simplified or have Python specific cases.
+
+    '''
+
+    def setUp(self):
+        self.forwarder = SocketSessionForwarder(TEST_UNIX_FILE)
+        if os.path.exists(TEST_UNIX_FILE):
+            os.unlink(TEST_UNIX_FILE)
+        self.large_text = b'a' * 65535
+
+    def tearDown(self):
+        if os.path.exists(TEST_UNIX_FILE):
+            os.unlink(TEST_UNIX_FILE)
+
+    def start_listen(self):
+        self.listen_sock = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
+        self.listen_sock.bind(TEST_UNIX_FILE)
+        self.listen_sock.listen(10)
+
+    def accept_forwarder(self):
+        self.listen_sock.setblocking(False)
+        s, _ = self.listen_sock.accept()
+        s.setblocking(True)
+        return s
+
+    def test_init(self):
+        # check bad arguments.  valid cases will covered in other tests.
+        self.assertRaises(TypeError, SocketSessionForwarder, 1)
+        self.assertRaises(TypeError, SocketSessionForwarder,
+                          'test.unix', 'test.unix')
+
+    def test_badpush(self):
+        # bad numbers of parameters
+        self.assertRaises(TypeError, self.forwarder.push, 1)
+        self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
+                          ('192.0.2.1', 5300), TEST_DATA, 0)
+        # contain a bad type of parameter
+        self.assertRaises(TypeError, self.forwarder.push, 0, 'AF_INET',
+                          SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
+                          ('192.0.2.1', 5300), TEST_DATA)
+        # bad local address
+        self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, ('127.0.0..1', 53),
+                            ('192.0.2.1', 5300), TEST_DATA)
+        self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, '127.0.0.1',
+                            ('192.0.2.1', 5300), TEST_DATA)
+        # bad remote address
+        self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET6,
+                          SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53),
+                            ('2001:db8:::3', 5300), TEST_DATA)
+
+        # push before connect
+        self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+                          ('192.0.2.2', 53), TEST_DATA)
+
+        # Now connect the forwarder for the rest of tests
+        self.start_listen()
+        self.forwarder.connect_to_receiver()
+
+        # Inconsistent address family
+        self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
+                          ('192.0.2.2', 53), TEST_DATA)
+        self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET6,
+                          SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
+                          ('192.0.2.2', 53), TEST_DATA)
+
+        # Empty data: we reject them at least for now
+        self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+                          ('192.0.2.2', 53), b'')
+
+        # Too big data: we reject them at least for now
+        self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+                          ('192.0.2.2', 53), b'd' * 65536)
+
+        # Close the receptor before push.  It will result in SIGPIPE (should be
+        # ignored) and EPIPE, which will be converted to SocketSessionError.
+        self.listen_sock.close()
+        self.assertRaises(SocketSessionError, self.forwarder.push, 1, AF_INET,
+                          SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
+                          ('192.0.2.2', 53), TEST_DATA)
+
+    def create_socket(self, family, type, protocol, addr, do_listen):
+        s = socket.socket(family, type, protocol)
+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        s.bind(addr)
+        if do_listen and protocol == IPPROTO_TCP:
+            s.listen(1)
+        return s
+
+    def check_push_and_pop(self, family, type, protocol, local, remote,
+                           data, new_connection):
+        sock = self.create_socket(family, type, protocol, local, True)
+        fwd_fd = sock.fileno()
+        if protocol == IPPROTO_TCP:
+            client_addr = ('::1', 0, 0, 0) if family == AF_INET6 \
+                else ('127.0.0.1', 0)
+            client_sock = self.create_socket(family, type, protocol,
+                                             client_addr, False)
+            client_sock.setblocking(False)
+            try:
+                client_sock.connect(local)
+            except socket.error:
+                pass
+            server_sock, _ = sock.accept()
+            fwd_fd = server_sock.fileno()
+
+        # If a new connection is required, start the "server", have the
+        # internal forwarder connect to it, and then internally accept it.
+        if new_connection:
+            self.start_listen()
+            self.forwarder.connect_to_receiver()
+            self.accept_sock = self.accept_forwarder()
+
+        # Then push one socket session via the forwarder.
+        self.forwarder.push(fwd_fd, family, type, protocol, local, remote,
+                            data)
+
+        # Pop the socket session we just pushed from a local receiver, and
+        # check the content.
+        receiver = SocketSessionReceiver(self.accept_sock)
+        signal.alarm(1)
+        sock_session = receiver.pop()
+        signal.alarm(0)
+        passed_sock = sock_session[0]
+        self.assertNotEqual(fwd_fd, passed_sock.fileno())
+        self.assertEqual(family, passed_sock.family)
+        self.assertEqual(type, passed_sock.type)
+        self.assertEqual(protocol, passed_sock.proto)
+        self.assertEqual(local, sock_session[1])
+        self.assertEqual(remote, sock_session[2])
+        self.assertEqual(data, sock_session[3])
+
+        # Check if the passed FD is usable by sending some data from it.
+        passed_sock.setblocking(True)
+        if protocol == IPPROTO_UDP:
+            self.assertEqual(len(TEST_DATA), passed_sock.sendto(TEST_DATA,
+                                                                local))
+            sock.settimeout(10)
+            self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0])
+        else:
+            server_sock.close()
+            self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA))
+            client_sock.setblocking(True)
+            client_sock.settimeout(10)
+            self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA)))
+
+    def test_push_and_pop(self):
+        # This is a straightforward port of C++ pushAndPop test.
+        local6 = ('::1', TEST_PORT, 0, 0)
+        remote6 = ('2001:db8::1', 5300, 0, 0)
+        self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+                                local6, remote6, TEST_DATA, True)
+        self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
+                                local6, remote6, TEST_DATA, False)
+
+        local4 = ('127.0.0.1', TEST_PORT)
+        remote4 = ('192.0.2.2', 5300)
+        self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+                                local4, remote4, TEST_DATA, False)
+        self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
+                                local4, remote4, TEST_DATA, False)
+
+        self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+                                local6, remote6, self.large_text, False)
+        self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
+                                local6, remote6, self.large_text, False)
+        self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+                                local4, remote4, self.large_text, False)
+        self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
+                                local4, remote4, self.large_text, False)
+
+        # Python specific: check for an IPv6 scoped address with non 0
+        # scope (zone) ID
+        scope6 = ('fe80::1', TEST_PORT, 0, 1)
+        self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+                                local6, scope6, TEST_DATA, False)
+
+    def test_push_too_fast(self):
+        # A straightforward port of C++ pushTooFast test.
+        def multi_push(forwarder, addr, data):
+            for i in range(0, 10):
+                forwarder.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP, addr,
+                               addr, data)
+        self.start_listen()
+        self.forwarder.connect_to_receiver()
+        self.assertRaises(SocketSessionError, multi_push, self.forwarder,
+                          ('192.0.2.1', 53), self.large_text)
+
+    def test_bad_pop(self):
+        # This is a subset of C++ badPop test.  We only check pop() raises
+        # SocketSessionError when it internally fails to get the FD.
+        # Other cases would require passing a valid FD from the test,
+        # which would make the test too complicated.  As a wrapper checking
+        # one common failure case should be reasonably sufficient.
+
+        self.start_listen()
+        s = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
+        s.setblocking(False)
+        s.connect(TEST_UNIX_FILE)
+        accept_sock = self.accept_forwarder()
+        receiver = SocketSessionReceiver(accept_sock)
+        s.close()
+        self.assertRaises(SocketSessionError, receiver.pop)
+
+class TestReceiver(unittest.TestCase):
+    # We only check a couple of failure cases on construction.  Valid cases
+    # are covered in TestForwarder.
+
+    def test_bad_init(self):
+        class FakeSocket:
+            # pretending to be th standard socket class, but its fileno() is
+            # bogus.
+            def fileno(self):
+                return None
+        self.assertRaises(TypeError, SocketSessionReceiver, 1)
+        self.assertRaises(TypeError, SocketSessionReceiver, FakeSocket())
+
+if __name__ == '__main__':
+    unittest.main()

+ 41 - 60
src/lib/util/python/wrapper_template.cc

@@ -52,56 +52,12 @@ namespace {
 // Shortcut type which would be convenient for adding class variables safely.
 typedef CPPPyObjectContainer<s_@CPPCLASS@, @CPPCLASS@> @CPPCLASS@Container;
 
-//
-// We declare the functions here, the definitions are below
-// the type definition of the object, since both can use the other
-//
-
-// General creation and destruction
-int @CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args);
-void @CPPCLASS@_destroy(s_@CPPCLASS@* self);
-
-// These are the functions we export
-// ADD/REMOVE/MODIFY THE FOLLOWING AS APPROPRIATE FOR THE ACTUAL CLASS.
-//
-PyObject* @CPPCLASS@_toText(const s_@CPPCLASS@* const self);
-PyObject* @CPPCLASS@_str(PyObject* self);
-PyObject* @CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
-                            const s_@CPPCLASS@* const other, int op);
-
-// This is quite specific pydnspp.  For other wrappers this should probably
-// be removed.
-PyObject* @CPPCLASS@_toWire(const s_@CPPCLASS@* self, PyObject* args);
-
-// These are the functions we export
-// For a minimal support, we don't need them.
-
-// This list contains the actual set of functions we have in
-// python. Each entry has
-// 1. Python method name
-// 2. Our static function here
-// 3. Argument type
-// 4. Documentation
-PyMethodDef @CPPCLASS@_methods[] = {
-    { "to_text", reinterpret_cast<PyCFunction>(@CPPCLASS@_toText), METH_NOARGS,
-      "Returns the text representation" },
-    // This is quite specific pydnspp.  For other wrappers this should probably
-    // be removed:
-    { "to_wire", reinterpret_cast<PyCFunction>(@CPPCLASS@_toWire), METH_VARARGS,
-      "Converts the @CPPCLASS@ object to wire format.\n"
-      "The argument can be either a MessageRenderer or an object that "
-      "implements the sequence interface. If the object is mutable "
-      "(for instance a bytearray()), the wire data is added in-place.\n"
-      "If it is not (for instance a bytes() object), a new object is "
-      "returned" },
-    { NULL, NULL, 0, NULL }
-};
-
 // This is a template of typical code logic of python class initialization
 // with C++ backend.  You'll need to adjust it according to details of the
 // actual C++ class.
 int
-@CPPCLASS@_init(s_@CPPCLASS@* self, PyObject* args) {
+@CPPCLASS@_init(PyObject* po_self, PyObject* args, PyObject*) {
+    s_@CPPCLASS@* self = static_cast<s_@CPPCLASS@*>(po_self);
     try {
         if (PyArg_ParseTuple(args, "REPLACE ME")) {
             // YOU'LL NEED SOME VALIDATION, PREPARATION, ETC, HERE.
@@ -114,13 +70,14 @@ int
         PyErr_SetString(po_IscException, ex_what.c_str());
         return (-1);
     } catch (...) {
-        PyErr_SetString(po_IscException,
-                        "Unexpected exception in constructing @CPPCLASS@");
+        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
         return (-1);
     }
 
-    PyErr_SetString(PyExc_TypeError,
-                    "Invalid arguments to @CPPCLASS@ constructor");
+    // If we are here PyArg_ParseTuple() failed and TypeError should have
+    // been set.  If the constructor is more complicated and the control
+    // could reach this point for other reasons, an appropriate Python
+    // exception should be set by PyErr_SetString.
 
     return (-1);
 }
@@ -128,7 +85,8 @@ int
 // This is a template of typical code logic of python object destructor.
 // In many cases you can use it without modification, but check that carefully.
 void
-@CPPCLASS@_destroy(s_@CPPCLASS@* const self) {
+@CPPCLASS@_destroy(PyObject* po_self) {
+    s_@CPPCLASS@* self = static_cast<s_@CPPCLASS@*>(po_self);
     delete self->cppobj;
     self->cppobj = NULL;
     Py_TYPE(self)->tp_free(self);
@@ -137,7 +95,8 @@ void
 // This should be able to be used without modification as long as the
 // underlying C++ class has toText().
 PyObject*
-@CPPCLASS@_toText(const s_@CPPCLASS@* const self) {
+@CPPCLASS@_toText(PyObject* po_self) {
+    const s_@CPPCLASS@* self = static_cast<const s_@CPPCLASS@*>(po_self);
     try {
         // toText() could throw, so we need to catch any exceptions below.
         return (Py_BuildValue("s", self->cppobj->toText().c_str()));
@@ -160,11 +119,17 @@ PyObject*
                                 const_cast<char*>("")));
 }
 
+// This is quite specific isc.dns.  For other wrappers this should probably
+// be removed.
+PyObject* @CPPCLASS@_toWire(PyObject* self, PyObject* args) {
+}
+
 PyObject* 
-@CPPCLASS@_richcmp(const s_@CPPCLASS@* const self,
-                   const s_@CPPCLASS@* const other,
-                   const int op)
-{
+@CPPCLASS@_richcmp(PyObject* po_self, PyObject* po_other, const int op) {
+    const s_@CPPCLASS@* const self = static_cast<const s_@CPPCLASS@*>(po_self);
+    const s_@CPPCLASS@* const other =
+        static_cast<const s_@CPPCLASS@*>(po_other);
+
     bool c = false;
 
     // Check for null and if the types match. If different type,
@@ -200,6 +165,22 @@ PyObject*
         Py_RETURN_FALSE;
     }
 }
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef @CPPCLASS@_methods[] = {
+    { "to_text", @CPPCLASS@_toText, METH_NOARGS,
+      @CPPCLASS@_toText_doc },
+    // This is quite specific isc.dns.  For other wrappers this should probably
+    // be removed:
+    { "to_wire", @CPPCLASS@_toWire, METH_VARARGS,
+      @CPPCLASS@_toWire_doc },
+    { NULL, NULL, 0, NULL }
+};
 } // end of unnamed namespace
 
 namespace isc {
@@ -213,7 +194,7 @@ PyTypeObject @cppclass@_type = {
     "@MODULE@.@CPPCLASS@",
     sizeof(s_@CPPCLASS@),                 // tp_basicsize
     0,                                  // tp_itemsize
-    reinterpret_cast<destructor>(@CPPCLASS@_destroy),       // tp_dealloc
+    @CPPCLASS@_destroy,                 // tp_dealloc
     NULL,                               // tp_print
     NULL,                               // tp_getattr
     NULL,                               // tp_setattr
@@ -230,11 +211,11 @@ PyTypeObject @cppclass@_type = {
     NULL,                               // tp_setattro
     NULL,                               // tp_as_buffer
     Py_TPFLAGS_DEFAULT,                 // tp_flags
-    "The @CPPCLASS@ class objects is...(COMPLETE THIS)",
+    @CPPCLASS@_doc,
     NULL,                               // tp_traverse
     NULL,                               // tp_clear
     // THIS MAY HAVE TO BE CHANGED TO NULL:
-    reinterpret_cast<richcmpfunc>(@CPPCLASS@_richcmp), // tp_richcompare
+    @CPPCLASS@_richcmp,                 // tp_richcompare
     0,                                  // tp_weaklistoffset
     NULL,                               // tp_iter
     NULL,                               // tp_iternext
@@ -246,7 +227,7 @@ PyTypeObject @cppclass@_type = {
     NULL,                               // tp_descr_get
     NULL,                               // tp_descr_set
     0,                                  // tp_dictoffset
-    reinterpret_cast<initproc>(@CPPCLASS@_init),            // tp_init
+    @CPPCLASS@_init,                    // tp_init
     NULL,                               // tp_alloc
     PyType_GenericNew,                  // tp_new
     NULL,                               // tp_free