mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-31 14:05:33 +00:00
[1452b] implemented python wrapper for socket session related classes.
This commit is contained in:
@@ -925,6 +925,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,4 +1,4 @@
|
||||
SUBDIRS = . tests
|
||||
SUBDIRS = . io tests
|
||||
|
||||
python_PYTHON = __init__.py process.py socketserver_mixin.py file.py
|
||||
|
||||
|
41
src/lib/python/isc/util/io/Makefile.am
Normal file
41
src/lib/python/isc/util/io/Makefile.am
Normal file
@@ -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
src/lib/python/isc/util/io/__init__.py
Normal file
3
src/lib/python/isc/util/io/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Here are function and classes for forwarding socket sessions between processes.
|
||||
"""
|
26
src/lib/python/isc/util/io/socketsession.py
Normal file
26
src/lib/python/isc/util/io/socketsession.py
Normal file
@@ -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
src/lib/python/isc/util/io/socketsession_inc.cc
Normal file
122
src/lib/python/isc/util/io/socketsession_inc.cc
Normal file
@@ -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
src/lib/python/isc/util/io/socketsession_python.cc
Normal file
79
src/lib/python/isc/util/io/socketsession_python.cc
Normal file
@@ -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
src/lib/python/isc/util/io/socketsession_python.h
Normal file
35
src/lib/python/isc/util/io/socketsession_python.h
Normal file
@@ -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
src/lib/python/isc/util/io/socketsessionforwarder_inc.cc
Normal file
136
src/lib/python/isc/util/io/socketsessionforwarder_inc.cc
Normal file
@@ -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
src/lib/python/isc/util/io/socketsessionforwarder_python.cc
Normal file
305
src/lib/python/isc/util/io/socketsessionforwarder_python.cc
Normal file
@@ -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
src/lib/python/isc/util/io/socketsessionforwarder_python.h
Normal file
45
src/lib/python/isc/util/io/socketsessionforwarder_python.h
Normal file
@@ -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
src/lib/python/isc/util/io/socketsessionreceiver_inc.cc
Normal file
89
src/lib/python/isc/util/io/socketsessionreceiver_inc.cc
Normal file
@@ -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
|
321
src/lib/python/isc/util/io/socketsessionreceiver_python.cc
Normal file
321
src/lib/python/isc/util/io/socketsessionreceiver_python.cc
Normal file
@@ -0,0 +1,321 @@
|
||||
// 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
|
46
src/lib/python/isc/util/io/socketsessionreceiver_python.h
Normal file
46
src/lib/python/isc/util/io/socketsessionreceiver_python.h
Normal file
@@ -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:
|
34
src/lib/python/isc/util/io/tests/Makefile.am
Normal file
34
src/lib/python/isc/util/io/tests/Makefile.am
Normal file
@@ -0,0 +1,34 @@
|
||||
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)
|
253
src/lib/python/isc/util/io/tests/socketsession_test.py
Normal file
253
src/lib/python/isc/util/io/tests/socketsession_test.py
Normal file
@@ -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 + '/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()
|
Reference in New Issue
Block a user