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
-SUBDIRS = . tests
+SUBDIRS = . io tests
python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
--- /dev/null
+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)
--- /dev/null
+"""
+Here are function and classes for forwarding socket sessions between processes.
+"""
--- /dev/null
+# 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 *
--- /dev/null
+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
--- /dev/null
+// 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);
+}
--- /dev/null
+// 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:
--- /dev/null
+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
--- /dev/null
+// 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
--- /dev/null
+// 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:
--- /dev/null
+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
--- /dev/null
+// 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* 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
--- /dev/null
+// 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:
--- /dev/null
+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
+PATHS1 = $(ENV_LIBRARY_PATH)=$(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$$$(ENV_LIBRARY_PATH)
+PATHS2 = $(abs_top_builddir)/src/lib/dns/.libs
+LIBRARY_PATH_PLACEHOLDER += $(PATHS1):$(PATHS2)
+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
+ 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_builddir) \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
+
+CLEANFILES = *.unix
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
--- /dev/null
+# 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 + '/test.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()