blob: c1e2bac53684159aba682941668091fd5f395ca1 [file] [log] [blame]
Thierry FOURNIER00a02252018-02-25 20:59:57 +01001/* spoa-server: processing Python
2 *
3 * Copyright 2018 OZON / Thierry Fournier <thierry.fournier@ozon.io>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version
8 * 2 of the License, or (at your option) any later version.
9 *
10 */
11
12#include <Python.h>
13
14#include <arpa/inet.h>
15
16#include <errno.h>
17#include <string.h>
18
19#include "spoa.h"
20
21/* Embedding python documentation:
22 *
23 * https://docs.python.org/2/extending/embedding.html
24 * https://docs.python.org/2/extending/extending.html#extending-python-with-c-or-c
25 * https://docs.python.org/2/extending/extending.html#calling-python-functions-from-c
26 */
27
28static PyObject *module_ipaddress;
29static PyObject *ipv4_address;
30static PyObject *ipv6_address;
31static PyObject *spoa_error;
32static PyObject *empty_array;
33static struct worker *worker;
34
35static int ps_python_start_worker(struct worker *w);
36static int ps_python_load_file(struct worker *w, const char *file);
37static int ps_python_exec_message(struct worker *w, void *ref, int nargs, struct spoe_kv *args);
38
39static struct ps ps_python_bindings = {
40 .init_worker = ps_python_start_worker,
41 .load_file = ps_python_load_file,
42 .exec_message = ps_python_exec_message,
43 .ext = ".py",
44};
45
46static PyObject *ps_python_register_message(PyObject *self, PyObject *args)
47{
48 const char *name;
49 PyObject *ref;
50
51 if (!PyArg_ParseTuple(args, "sO!", &name, &PyFunction_Type, &ref))
52 return NULL;
53 Py_XINCREF(ref); /* because the function is intenally refrenced */
54
55 ps_register_message(&ps_python_bindings, name, (void *)ref);
56
57 return Py_None;
58}
59
60static PyObject *ps_python_set_var_null(PyObject *self, PyObject *args)
61{
62 const char *name;
63 int name_len;
64 int scope;
65
66 if (!PyArg_ParseTuple(args, "s#i", &name, &name_len, &scope))
67 return NULL;
68 if (!set_var_null(worker, name, name_len, scope)) {
69 PyErr_SetString(spoa_error, "No space left available");
70 return NULL;
71 }
72 return Py_None;
73}
74
75static PyObject *ps_python_set_var_boolean(PyObject *self, PyObject *args)
76{
77 const char *name;
78 int name_len;
79 int scope;
80 int value;
81
82 if (!PyArg_ParseTuple(args, "s#ii", &name, &name_len, &scope, &value))
83 return NULL;
84 if (!set_var_bool(worker, name, name_len, scope, value)) {
85 PyErr_SetString(spoa_error, "No space left available");
86 return NULL;
87 }
88 return Py_None;
89}
90
91static PyObject *ps_python_set_var_int32(PyObject *self, PyObject *args)
92{
93 const char *name;
94 int name_len;
95 int scope;
96 int32_t value;
97
98 if (!PyArg_ParseTuple(args, "s#ii", &name, &name_len, &scope, &value))
99 return NULL;
100 if (!set_var_int32(worker, name, name_len, scope, value)) {
101 PyErr_SetString(spoa_error, "No space left available");
102 return NULL;
103 }
104 return Py_None;
105}
106
107static PyObject *ps_python_set_var_uint32(PyObject *self, PyObject *args)
108{
109 const char *name;
110 int name_len;
111 int scope;
112 uint32_t value;
113
114 if (!PyArg_ParseTuple(args, "s#iI", &name, &name_len, &scope, &value))
115 return NULL;
116 if (!set_var_uint32(worker, name, name_len, scope, value)) {
117 PyErr_SetString(spoa_error, "No space left available");
118 return NULL;
119 }
120 return Py_None;
121}
122
123static PyObject *ps_python_set_var_int64(PyObject *self, PyObject *args)
124{
125 const char *name;
126 int name_len;
127 int scope;
128 int64_t value;
129
130 if (!PyArg_ParseTuple(args, "s#il", &name, &name_len, &scope, &value))
131 return NULL;
132 if (!set_var_int64(worker, name, name_len, scope, value)) {
133 PyErr_SetString(spoa_error, "No space left available");
134 return NULL;
135 }
136 return Py_None;
137}
138
139static PyObject *ps_python_set_var_uint64(PyObject *self, PyObject *args)
140{
141 const char *name;
142 int name_len;
143 int scope;
144 uint64_t value;
145
146 if (!PyArg_ParseTuple(args, "s#ik", &name, &name_len, &scope, &value))
147 return NULL;
148 if (!set_var_uint64(worker, name, name_len, scope, value)) {
149 PyErr_SetString(spoa_error, "No space left available");
150 return NULL;
151 }
152 return Py_None;
153}
154
155static PyObject *ps_python_set_var_ipv4(PyObject *self, PyObject *args)
156{
157 const char *name;
158 int name_len;
159 int scope;
160 PyObject *ipv4;
161 PyObject *value;
162 struct in_addr ip;
163
164 if (!PyArg_ParseTuple(args, "s#iO", &name, &name_len, &scope, &ipv4))
165 return NULL;
166 if (!PyObject_IsInstance(ipv4, ipv4_address)) {
167 PyErr_Format(spoa_error, "must be 'IPv4Address', not '%s'", ipv4->ob_type->tp_name);
168 return NULL;
169 }
170 /* Execute packed ... I think .. */
171 value = PyObject_GetAttrString(ipv4, "packed");
172 if (value == NULL)
173 return NULL;
174 if (PyString_GET_SIZE(value) != sizeof(ip)) {
175 PyErr_Format(spoa_error, "UPv6 manipulation internal error");
176 return NULL;
177 }
178 memcpy(&ip, PyString_AS_STRING(value), PyString_GET_SIZE(value));
179 if (!set_var_ipv4(worker, name, name_len, scope, &ip)) {
180 PyErr_SetString(spoa_error, "No space left available");
181 return NULL;
182 }
183 return Py_None;
184}
185
186static PyObject *ps_python_set_var_ipv6(PyObject *self, PyObject *args)
187{
188 const char *name;
189 int name_len;
190 int scope;
191 PyObject *ipv6;
192 PyObject *value;
193 struct in6_addr ip;
194
195 if (!PyArg_ParseTuple(args, "s#iO", &name, &name_len, &scope, &ipv6))
196 return NULL;
197 if (!PyObject_IsInstance(ipv6, ipv6_address)) {
198 PyErr_Format(spoa_error, "must be 'IPv6Address', not '%s'", ipv6->ob_type->tp_name);
199 return NULL;
200 }
201 /* Execute packed ... I think .. */
202 value = PyObject_GetAttrString(ipv6, "packed");
203 if (value == NULL)
204 return NULL;
205 if (PyString_GET_SIZE(value) != sizeof(ip)) {
206 PyErr_Format(spoa_error, "UPv6 manipulation internal error");
207 return NULL;
208 }
209 memcpy(&ip, PyString_AS_STRING(value), PyString_GET_SIZE(value));
210 if (!set_var_ipv6(worker, name, name_len, scope, &ip)) {
211 PyErr_SetString(spoa_error, "No space left available");
212 return NULL;
213 }
214 return Py_None;
215}
216
217static PyObject *ps_python_set_var_str(PyObject *self, PyObject *args)
218{
219 const char *name;
220 int name_len;
221 int scope;
222 const char *value;
223 int value_len;
224
225 if (!PyArg_ParseTuple(args, "s#is#", &name, &name_len, &scope, &value, &value_len))
226 return NULL;
227 if (!set_var_string(worker, name, name_len, scope, value, value_len)) {
228 PyErr_SetString(spoa_error, "No space left available");
229 return NULL;
230 }
231 return Py_None;
232}
233
234static PyObject *ps_python_set_var_bin(PyObject *self, PyObject *args)
235{
236 const char *name;
237 int name_len;
238 int scope;
239 const char *value;
240 int value_len;
241
242 if (!PyArg_ParseTuple(args, "s#is#", &name, &name_len, &scope, &value, &value_len))
243 return NULL;
244 if (!set_var_bin(worker, name, name_len, scope, value, value_len)) {
245 PyErr_SetString(spoa_error, "No space left available");
246 return NULL;
247 }
248 return Py_None;
249}
250
251
252static PyMethodDef spoa_methods[] = {
253 {"register_message", ps_python_register_message, METH_VARARGS,
254 "Register binding for SPOA message."},
255 {"set_var_null", ps_python_set_var_null, METH_VARARGS,
256 "Set SPOA NULL variable"},
257 {"set_var_boolean", ps_python_set_var_boolean, METH_VARARGS,
258 "Set SPOA boolean variable"},
259 {"set_var_int32", ps_python_set_var_int32, METH_VARARGS,
260 "Set SPOA int32 variable"},
261 {"set_var_uint32", ps_python_set_var_uint32, METH_VARARGS,
262 "Set SPOA uint32 variable"},
263 {"set_var_int64", ps_python_set_var_int64, METH_VARARGS,
264 "Set SPOA int64 variable"},
265 {"set_var_uint64", ps_python_set_var_uint64, METH_VARARGS,
266 "Set SPOA uint64 variable"},
267 {"set_var_ipv4", ps_python_set_var_ipv4, METH_VARARGS,
268 "Set SPOA ipv4 variable"},
269 {"set_var_ipv6", ps_python_set_var_ipv6, METH_VARARGS,
270 "Set SPOA ipv6 variable"},
271 {"set_var_str", ps_python_set_var_str, METH_VARARGS,
272 "Set SPOA str variable"},
273 {"set_var_bin", ps_python_set_var_bin, METH_VARARGS,
274 "Set SPOA bin variable"},
275 { /* end */ }
276};
277
278static int ps_python_start_worker(struct worker *w)
279{
280 PyObject *m;
281 PyObject *module_name;
282 PyObject *value;
283 int ret;
284
285 Py_SetProgramName("spoa-server");
286 Py_Initialize();
287
288 module_name = PyString_FromString("ipaddress");
289 if (module_name == NULL) {
290 PyErr_Print();
291 return 0;
292 }
293
294 module_ipaddress = PyImport_Import(module_name);
295 Py_DECREF(module_name);
296 if (module_ipaddress == NULL) {
297 PyErr_Print();
298 return 0;
299 }
300
301 ipv4_address = PyObject_GetAttrString(module_ipaddress, "IPv4Address");
302 if (ipv4_address == NULL) {
303 PyErr_Print();
304 return 0;
305 }
306
307 ipv6_address = PyObject_GetAttrString(module_ipaddress, "IPv6Address");
308 if (ipv4_address == NULL) {
309 PyErr_Print();
310 return 0;
311 }
312
313 m = Py_InitModule("spoa", spoa_methods);
314 if (m == NULL) {
315 PyErr_Print();
316 return 0;
317 }
318
319 spoa_error = PyErr_NewException("spoa.error", NULL, NULL);
320 Py_INCREF(spoa_error);
321 PyModule_AddObject(m, "error", spoa_error);
322
323
324 value = PyLong_FromLong(SPOE_SCOPE_PROC);
325 if (value == NULL) {
326 PyErr_Print();
327 return 0;
328 }
329
330 ret = PyModule_AddObject(m, "scope_proc", value);
331 if (ret == -1) {
332 PyErr_Print();
333 return 0;
334 }
335
336 value = PyLong_FromLong(SPOE_SCOPE_SESS);
337 if (value == NULL) {
338 PyErr_Print();
339 return 0;
340 }
341
342 ret = PyModule_AddObject(m, "scope_sess", value);
343 if (ret == -1) {
344 PyErr_Print();
345 return 0;
346 }
347
348 value = PyLong_FromLong(SPOE_SCOPE_TXN);
349 if (value == NULL) {
350 PyErr_Print();
351 return 0;
352 }
353
354 ret = PyModule_AddObject(m, "scope_txn", value);
355 if (ret == -1) {
356 PyErr_Print();
357 return 0;
358 }
359
360 value = PyLong_FromLong(SPOE_SCOPE_REQ);
361 if (value == NULL) {
362 PyErr_Print();
363 return 0;
364 }
365
366 ret = PyModule_AddObject(m, "scope_req", value);
367 if (ret == -1) {
368 PyErr_Print();
369 return 0;
370 }
371
372 value = PyLong_FromLong(SPOE_SCOPE_RES);
373 if (value == NULL) {
374 PyErr_Print();
375 return 0;
376 }
377
378 ret = PyModule_AddObject(m, "scope_res", value);
379 if (ret == -1) {
380 PyErr_Print();
381 return 0;
382 }
383
384 empty_array = PyDict_New();
385 if (empty_array == NULL) {
386 PyErr_Print();
387 return 0;
388 }
389
390 worker = w;
391 return 1;
392}
393
394static int ps_python_load_file(struct worker *w, const char *file)
395{
396 FILE *fp;
397 int ret;
398
399 fp = fopen(file, "r");
400 if (fp == NULL) {
401 LOG("python: Cannot read file \"%s\": %s", file, strerror(errno));
402 return 0;
403 }
404
405 ret = PyRun_SimpleFile(fp, file);
406 fclose(fp);
407 if (ret != 0) {
408 PyErr_Print();
409 return 0;
410 }
411
412 return 1;
413}
414
415static int ps_python_exec_message(struct worker *w, void *ref, int nargs, struct spoe_kv *args)
416{
417 int i;
418 PyObject *python_ref = ref;
419 PyObject *fkw;
420 PyObject *kw_args;
421 PyObject *result;
422 PyObject *ent;
423 PyObject *key;
424 PyObject *value;
425 PyObject *func;
426 int ret;
427 char ipbuf[64];
428 const char *p;
429 PyObject *ip_dict;
430 PyObject *ip_name;
431 PyObject *ip_value;
432
433 /* Dict containing arguments */
434
435 kw_args = PyList_New(0);
436 if (kw_args == NULL) {
437 PyErr_Print();
438 return 0;
439 }
440
441 for (i = 0; i < nargs; i++) {
442
443 /* New dict containing one argument */
444
445 ent = PyDict_New();
446 if (ent == NULL) {
447 Py_DECREF(kw_args);
448 Py_DECREF(ent);
449 PyErr_Print();
450 return 0;
451 }
452
453 /* Create the name entry */
454
455 key = PyString_FromString("name");
456 if (key == NULL) {
457 Py_DECREF(kw_args);
458 PyErr_Print();
459 return 0;
460 }
461
462 value = PyString_FromStringAndSize(args[i].name.str, args[i].name.len);
463 if (value == NULL) {
464 Py_DECREF(kw_args);
465 Py_DECREF(ent);
466 Py_DECREF(key);
467 PyErr_Print();
468 return 0;
469 }
470
471 ret = PyDict_SetItem(ent, key, value);
472 Py_DECREF(key);
473 Py_DECREF(value);
474 if (ret == -1) {
475 Py_DECREF(kw_args);
476 Py_DECREF(ent);
477 PyErr_Print();
478 return 0;
479 }
480
481 /* Create th value entry */
482
483 key = PyString_FromString("value");
484 if (key == NULL) {
485 Py_DECREF(kw_args);
486 Py_DECREF(ent);
487 PyErr_Print();
488 return 0;
489 }
490
491 switch (args[i].value.type) {
492 case SPOE_DATA_T_NULL:
493 value = Py_None;
494 break;
495 case SPOE_DATA_T_BOOL:
496 value = PyBool_FromLong(args[i].value.u.boolean);
497 break;
498 case SPOE_DATA_T_INT32:
499 value = PyLong_FromLong(args[i].value.u.sint32);
500 break;
501 case SPOE_DATA_T_UINT32:
502 value = PyLong_FromLong(args[i].value.u.uint32);
503 break;
504 case SPOE_DATA_T_INT64:
505 value = PyLong_FromLong(args[i].value.u.sint64);
506 break;
507 case SPOE_DATA_T_UINT64:
508 value = PyLong_FromUnsignedLong(args[i].value.u.uint64);
509 break;
510 case SPOE_DATA_T_IPV4:
511 case SPOE_DATA_T_IPV6:
512 if (args[i].value.type == SPOE_DATA_T_IPV4)
513 p = inet_ntop(AF_INET, &args[i].value.u.ipv4, ipbuf, 64);
514 else
515 p = inet_ntop(AF_INET6, &args[i].value.u.ipv6, ipbuf, 64);
516 if (!p)
517 strcpy(ipbuf, "0.0.0.0");
518
519 func = PyObject_GetAttrString(module_ipaddress, "ip_address");
520 if (func == NULL) {
521 Py_DECREF(kw_args);
522 Py_DECREF(ent);
523 PyErr_Print();
524 return 0;
525 }
526 ip_dict = PyDict_New();
527 if (ip_dict == NULL) {
528 Py_DECREF(kw_args);
529 Py_DECREF(ent);
530 Py_DECREF(func);
531 PyErr_Print();
532 return 0;
533 }
534 ip_name = PyString_FromString("address");
535 if (ip_name == NULL) {
536 Py_DECREF(kw_args);
537 Py_DECREF(ent);
538 Py_DECREF(func);
539 Py_DECREF(ip_dict);
540 PyErr_Print();
541 return 0;
542 }
543 ip_value = PyUnicode_FromString(ipbuf);
544 if (ip_value == NULL) {
545 Py_DECREF(kw_args);
546 Py_DECREF(ent);
547 Py_DECREF(func);
548 Py_DECREF(ip_dict);
549 Py_DECREF(ip_name);
550 PyErr_Print();
551 return 0;
552 }
553 ret = PyDict_SetItem(ip_dict, ip_name, ip_value);
554 Py_DECREF(ip_name);
555 Py_DECREF(ip_value);
556 if (ret == -1) {
557 Py_DECREF(ip_dict);
558 PyErr_Print();
559 return 0;
560 }
561 value = PyObject_Call(func, empty_array, ip_dict);
562 Py_DECREF(func);
563 Py_DECREF(ip_dict);
564 break;
565
566 case SPOE_DATA_T_STR:
567 value = PyString_FromStringAndSize(args[i].value.u.buffer.str, args[i].value.u.buffer.len);
568 break;
569 case SPOE_DATA_T_BIN:
570 value = PyString_FromStringAndSize(args[i].value.u.buffer.str, args[i].value.u.buffer.len);
571 break;
572 default:
573 value = Py_None;
574 break;
575 }
576 if (value == NULL) {
577 Py_DECREF(kw_args);
578 Py_DECREF(ent);
579 Py_DECREF(key);
580 PyErr_Print();
581 return 0;
582 }
583
584 ret = PyDict_SetItem(ent, key, value);
585 Py_DECREF(key);
586 Py_DECREF(value);
587 if (ret == -1) {
588 Py_DECREF(kw_args);
589 Py_DECREF(ent);
590 PyErr_Print();
591 return 0;
592 }
593
594 /* Add dict to the list */
595
596 ret = PyList_Append(kw_args, ent);
597 Py_DECREF(ent);
598 if (ret == -1) {
599 Py_DECREF(kw_args);
600 PyErr_Print();
601 return 0;
602 }
603 }
604
605 /* Dictionnary { args = <list-of-args> } for the function */
606
607 fkw = PyDict_New();
608 if (fkw == NULL) {
609 Py_DECREF(kw_args);
610 PyErr_Print();
611 return 0;
612 }
613
614 key = PyString_FromString("args");
615 if (key == NULL) {
616 Py_DECREF(kw_args);
617 Py_DECREF(fkw);
618 PyErr_Print();
619 return 0;
620 }
621
622 ret = PyDict_SetItem(fkw, key, kw_args);
623 Py_DECREF(kw_args);
624 Py_DECREF(key);
625 if (ret == -1) {
626 Py_DECREF(fkw);
627 PyErr_Print();
628 return 0;
629 }
630
631 result = PyObject_Call(python_ref, empty_array, fkw);
Gilchrist Dadaglod01d2802020-08-24 19:21:31 +0000632 Py_DECREF(fkw);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100633 if (result == NULL) {
634 PyErr_Print();
635 return 0;
636 }
Gilchrist Dadaglod01d2802020-08-24 19:21:31 +0000637 if (result != Py_None) {
638 Py_DECREF(result);
639 }
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100640
641 return 1;
642}
643
644__attribute__((constructor))
645static void __ps_python_init(void)
646{
647 ps_register(&ps_python_bindings);
648}