Branch data Line data Source code
1 : : // Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
2 : : //
3 : : // Permission to use, copy, modify, and/or distribute this software for any
4 : : // purpose with or without fee is hereby granted, provided that the above
5 : : // copyright notice and this permission notice appear in all copies.
6 : : //
7 : : // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
8 : : // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9 : : // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
10 : : // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 : : // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
12 : : // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 : : // PERFORMANCE OF THIS SOFTWARE.
14 : :
15 : : // Enable this if you use s# variants with PyArg_ParseTuple(), see
16 : : // http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
17 : : //#define PY_SSIZE_T_CLEAN
18 : :
19 : : // Python.h needs to be placed at the head of the program file, see:
20 : : // http://docs.python.org/py3k/extending/extending.html#a-simple-example
21 : : #include <Python.h>
22 : :
23 : : #include <sys/types.h>
24 : : #include <sys/socket.h>
25 : : #include <netinet/in.h>
26 : : #include <netdb.h>
27 : :
28 : : #include <string>
29 : : #include <stdexcept>
30 : :
31 : : #include <boost/lexical_cast.hpp>
32 : :
33 : : #include <util/python/pycppwrapper_util.h>
34 : :
35 : : #include <util/io/socketsession.h>
36 : :
37 : : #include "socketsession_python.h"
38 : : #include "socketsessionreceiver_python.h"
39 : :
40 : : using namespace std;
41 : : using namespace isc::util::python;
42 : : using namespace isc::util::io;
43 : : using namespace isc::util::io::python;
44 : : using boost::lexical_cast;
45 : :
46 : : // Trivial constructor.
47 : 0 : s_SocketSessionReceiver::s_SocketSessionReceiver() : cppobj(NULL) {
48 : 0 : }
49 : :
50 : : // Import pydoc text
51 : : #include "socketsessionreceiver_inc.cc"
52 : :
53 : : namespace {
54 : : // This C structure corresponds to a Python callable object for
55 : : // socket.fromfd().
56 : : // See json_dumps_obj in dns_requestloader_python.cc for background rationale
57 : : // of this trick.
58 : : PyObject* socket_fromfd_obj = NULL;
59 : :
60 : : int
61 : 12 : SocketSessionReceiver_init(PyObject* po_self, PyObject* args, PyObject*) {
62 : : s_SocketSessionReceiver* self =
63 : 12 : static_cast<s_SocketSessionReceiver*>(po_self);
64 : : try {
65 : : // The constructor expects a Python socket object. We'll extract
66 : : // the underlying file descriptor using the fileno method (in the
67 : : // duck typing manner) and pass it to the C++ constructor.
68 : : // PyObject_CallMethod() could return NULL (especially if the given
69 : : // object is of the wrong type and doesn't have the "fileno" method),
70 : : // in which case PyObjectContainer will detect it and throw
71 : : // PyCPPWrapperException, which will be converted to the Python
72 : : // TypeError below.
73 : : PyObject* po_sock;
74 [ + - ][ + - ]: 12 : if (PyArg_ParseTuple(args, "O", &po_sock)) {
75 : : PyObjectContainer fd_container(PyObject_CallMethod(
76 : : po_sock,
77 : : const_cast<char*>("fileno"),
78 [ + - ][ + + ]: 24 : NULL));
[ + - ][ + - ]
79 : : PyObjectContainer fdarg_container(
80 [ + - ][ + - ]: 22 : Py_BuildValue("(O)", fd_container.get()));
[ + - ][ + - ]
81 : : int fd;
82 [ + - ][ + + ]: 11 : if (PyArg_ParseTuple(fdarg_container.get(), "i", &fd)) {
83 [ + - ][ + - ]: 10 : self->cppobj = new SocketSessionReceiver(fd);
84 : : return (0);
85 : : }
86 : : PyErr_SetString(PyExc_TypeError, "Given object's fileno() doesn't "
87 : : "return an integer, probably not a valid socket "
88 [ + - ]: 1 : "object");
89 : : }
90 : 2 : } catch (const PyCPPWrapperException& ex) {
91 : : // This could happen due to memory allocation failure, but it's more
92 : : // likely that the object doesn't have the "fileno()" method or it
93 : : // returns an unexpected type of value. So we adjust the error
94 : : // message accordingly.
95 : : PyErr_SetString(PyExc_TypeError, "Failed to parse parameter, "
96 [ - + ]: 1 : "probably not a valid socket object");
97 : 0 : } catch (const exception& ex) {
98 : : const string ex_what =
99 : : "Failed to construct SocketSessionReceiver object: " +
100 [ # # ][ # # ]: 0 : string(ex.what());
101 [ # # ]: 0 : PyErr_SetString(po_SocketSessionError, ex_what.c_str());
102 [ + - - ]: 1 : } catch (...) {
103 [ # # ]: 0 : PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
104 : : }
105 : :
106 : : return (-1);
107 : : }
108 : :
109 : : PyObject*
110 : 18 : createPySocketAddress(const struct sockaddr& sa) {
111 : : socklen_t salen;
112 [ + + ]: 18 : if (sa.sa_family == AF_INET) {
113 : : salen = sizeof(struct sockaddr_in);
114 [ - + ]: 10 : } else if (sa.sa_family == AF_INET6) {
115 : : salen = sizeof(struct sockaddr_in6);
116 : : } else {
117 [ # # ][ # # ]: 0 : isc_throw(SocketSessionError, "Unsupported socket address family: "
118 : : << static_cast<int>(sa.sa_family));
119 : : }
120 : :
121 : : char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
122 : : const int error = getnameinfo(&sa, salen, hbuf, sizeof(hbuf), sbuf,
123 : : sizeof(sbuf),
124 : 18 : NI_NUMERICHOST | NI_NUMERICSERV);
125 [ - + ]: 18 : if (error != 0) {
126 [ # # ][ # # ]: 0 : isc_throw(SocketSessionError, "Unrecognized socket address format: "
127 : : << gai_strerror(error));
128 : : }
129 [ + + ]: 18 : if (sa.sa_family == AF_INET) {
130 : 8 : return (Py_BuildValue("(si)", hbuf, lexical_cast<int>(sbuf)));
131 : : }
132 : : // We know it's AF_INET6 at this point. We need some special trick for
133 : : // non-0 scope (zone) ID: getnameinfo() may convert the address to a
134 : : // textual representation using the extension described in RFC 4007,
135 : : // in which case it contains a delimiter character '%'. We need to remove
136 : : // it before constructing the tuple. The scope (zone) ID is preserved
137 : : // in the corresponding field of the tuple.
138 : 10 : const void* p = &sa;
139 : : const struct sockaddr_in6* sin6 =
140 : 10 : static_cast<const struct sockaddr_in6*>(p);
141 : 10 : char* cp = strchr(hbuf, '%');
142 [ + + ]: 10 : if (cp != NULL) {
143 : 1 : *cp = '\0';
144 : : }
145 : : return (Py_BuildValue("(siii)", hbuf, lexical_cast<int>(sbuf), 0,
146 : 18 : sin6->sin6_scope_id));
147 : : }
148 : :
149 : : void
150 : 12 : SocketSessionReceiver_destroy(PyObject* po_self) {
151 : : s_SocketSessionReceiver* self =
152 : 12 : static_cast<s_SocketSessionReceiver*>(po_self);
153 [ + + ]: 12 : delete self->cppobj;
154 : 12 : self->cppobj = NULL;
155 : 12 : Py_TYPE(self)->tp_free(self);
156 : 12 : }
157 : :
158 : : // A helper struct to automatically close a socket in an RAII manner.
159 : : struct ScopedSocket : boost::noncopyable {
160 : 9 : ScopedSocket(int fd) : fd_(fd) {}
161 : : ~ScopedSocket() {
162 [ + - ]: 10 : close(fd_);
163 : : }
164 : : const int fd_;
165 : : };
166 : :
167 : : PyObject*
168 : 10 : SocketSessionReceiver_pop(PyObject* po_self, PyObject*) {
169 : : s_SocketSessionReceiver* const self =
170 : 10 : static_cast<s_SocketSessionReceiver*>(po_self);
171 : :
172 : : try {
173 : : // retrieve the session, and the convert it to a corresponding
174 : : // Python tuple.
175 [ + + ]: 10 : const SocketSession session = self->cppobj->pop();
176 : :
177 : : // We need to immediately store the socket file descriptor in a
178 : : // ScopedSocket object. socket.fromfd() will dup() the FD, so we need
179 : : // to close our copy even if an exception is thrown.
180 : 9 : ScopedSocket sock(session.getSocket());
181 : :
182 : : // Build Python socket object
183 : : PyObjectContainer c_args(Py_BuildValue("(iiii)", sock.fd_,
184 : : session.getFamily(),
185 : : session.getType(),
186 [ + - ][ + - ]: 18 : session.getProtocol()));
[ + - ]
187 : : PyObjectContainer c_sock(PyObject_CallObject(socket_fromfd_obj,
188 [ + - ][ + - ]: 18 : c_args.get()));
[ + - ]
189 : : // Convert the local and remote sockaddr to Python socket address objs
190 : : PyObjectContainer c_local(createPySocketAddress(
191 [ + - ][ + - ]: 18 : session.getLocalEndpoint()));
[ + - ]
192 : : PyObjectContainer c_remote(createPySocketAddress(
193 [ + - ][ + - ]: 18 : session.getRemoteEndpoint()));
[ + - ]
194 : : // Convert the session data to Python byte object.
195 : : PyObjectContainer c_data(Py_BuildValue("y#", session.getData(),
196 [ + - ][ + - ]: 18 : session.getDataLength()));
[ + - ]
197 : :
198 : : // Build a tuple from them and return it.
199 : : return (Py_BuildValue("(OOOO)", c_sock.get(), c_local.get(),
200 [ + - ]: 9 : c_remote.get(), c_data.get()));
201 : 2 : } catch (const SocketSessionError& ex) {
202 [ - + ]: 1 : PyErr_SetString(po_SocketSessionError, ex.what());
203 : : return (NULL);
204 : 0 : } catch (const exception& ex) {
205 : : const string ex_what =
206 : : "Unexpected failure in receiving a socket session: " +
207 [ # # ][ # # ]: 0 : string(ex.what());
208 [ # # ]: 0 : PyErr_SetString(PyExc_SystemError, ex_what.c_str());
209 : : return (NULL);
210 [ + - - ]: 1 : } catch (...) {
211 [ # # ]: 0 : PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
212 : : return (NULL);
213 : : }
214 : : }
215 : :
216 : : // These are the functions we export
217 : :
218 : : // This list contains the actual set of functions we have in
219 : : // python. Each entry has
220 : : // 1. Python method name
221 : : // 2. Our static function here
222 : : // 3. Argument type
223 : : // 4. Documentation
224 : : PyMethodDef SocketSessionReceiver_methods[] = {
225 : : { "pop", SocketSessionReceiver_pop, METH_NOARGS,
226 : : SocketSessionReceiver_pop_doc },
227 : : { NULL, NULL, 0, NULL }
228 : : };
229 : : } // end of unnamed namespace
230 : :
231 : : namespace isc {
232 : : namespace util {
233 : : namespace io {
234 : : namespace python {
235 : : // This defines the complete type for reflection in python and
236 : : // parsing of PyObject* to s_SocketSessionReceiver
237 : : // Most of the functions are not actually implemented and NULL here.
238 : : PyTypeObject socketsessionreceiver_type = {
239 : : PyVarObject_HEAD_INIT(NULL, 0)
240 : : "isc.util.cio.SocketSessionReceiver",
241 : : sizeof(s_SocketSessionReceiver), // tp_basicsize
242 : : 0, // tp_itemsize
243 : : SocketSessionReceiver_destroy, // tp_dealloc
244 : : NULL, // tp_print
245 : : NULL, // tp_getattr
246 : : NULL, // tp_setattr
247 : : NULL, // tp_reserved
248 : : NULL, // tp_repr
249 : : NULL, // tp_as_number
250 : : NULL, // tp_as_sequence
251 : : NULL, // tp_as_mapping
252 : : NULL, // tp_hash
253 : : NULL, // tp_call
254 : : NULL, // tp_str
255 : : NULL, // tp_getattro
256 : : NULL, // tp_setattro
257 : : NULL, // tp_as_buffer
258 : : Py_TPFLAGS_DEFAULT, // tp_flags
259 : : SocketSessionReceiver_doc,
260 : : NULL, // tp_traverse
261 : : NULL, // tp_clear
262 : : NULL, // tp_richcompare
263 : : 0, // tp_weaklistoffset
264 : : NULL, // tp_iter
265 : : NULL, // tp_iternext
266 : : SocketSessionReceiver_methods, // tp_methods
267 : : NULL, // tp_members
268 : : NULL, // tp_getset
269 : : NULL, // tp_base
270 : : NULL, // tp_dict
271 : : NULL, // tp_descr_get
272 : : NULL, // tp_descr_set
273 : : 0, // tp_dictoffset
274 : : SocketSessionReceiver_init, // tp_init
275 : : NULL, // tp_alloc
276 : : PyType_GenericNew, // tp_new
277 : : NULL, // tp_free
278 : : NULL, // tp_is_gc
279 : : NULL, // tp_bases
280 : : NULL, // tp_mro
281 : : NULL, // tp_cache
282 : : NULL, // tp_subclasses
283 : : NULL, // tp_weaklist
284 : : NULL, // tp_del
285 : : 0 // tp_version_tag
286 : : };
287 : :
288 : : // Module Initialization, all statics are initialized here
289 : : bool
290 : 2 : initModulePart_SocketSessionReceiver(PyObject* mod) {
291 : : // We initialize the static description object with PyType_Ready(),
292 : : // then add it to the module. This is not just a check! (leaving
293 : : // this out results in segmentation faults)
294 [ + - ]: 2 : if (PyType_Ready(&socketsessionreceiver_type) < 0) {
295 : : return (false);
296 : : }
297 : 2 : void* p = &socketsessionreceiver_type;
298 [ + - ]: 2 : if (PyModule_AddObject(mod, "SocketSessionReceiver",
299 : 2 : static_cast<PyObject*>(p)) < 0) {
300 : : return (false);
301 : : }
302 : :
303 : 2 : PyObject* socket_module = PyImport_AddModule("socket");
304 [ + - ]: 2 : if (socket_module != NULL) {
305 : 2 : PyObject* socket_dict = PyModule_GetDict(socket_module);
306 [ + - ]: 2 : if (socket_dict != NULL) {
307 : 2 : socket_fromfd_obj = PyDict_GetItemString(socket_dict, "fromfd");
308 : : }
309 : : }
310 [ + - ]: 2 : if (socket_fromfd_obj != NULL) {
311 : 2 : Py_INCREF(socket_fromfd_obj);
312 : : } else {
313 : : PyErr_SetString(PyExc_RuntimeError,
314 : : "isc.util.cio.SocketSessionReceiver needs "
315 : 0 : "socket.fromfd(), but it's missing");
316 : 2 : return (false);
317 : : }
318 : :
319 : 2 : Py_INCREF(&socketsessionreceiver_type);
320 : :
321 : 2 : return (true);
322 : : }
323 : :
324 : : } // namespace python
325 : : } // namespace io
326 : : } // namespace util
327 : 0 : } // namespace isc
|