blob: b72b7e7677c9c31e42273fa5be2bd3a1f409ec5a [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
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +000057 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +010058}
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 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +000072 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +010073}
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 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +000088 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +010089}
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 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000104 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100105}
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 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000120 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100121}
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 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000136 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100137}
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 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000152 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100153}
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 }
Gilchrist Dadagloe38c0922020-08-24 19:21:32 +0000183 /* Once we set the IP value in the worker, we don't need it anymore... */
184 Py_XDECREF(value);
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000185 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100186}
187
188static PyObject *ps_python_set_var_ipv6(PyObject *self, PyObject *args)
189{
190 const char *name;
191 int name_len;
192 int scope;
193 PyObject *ipv6;
194 PyObject *value;
195 struct in6_addr ip;
196
197 if (!PyArg_ParseTuple(args, "s#iO", &name, &name_len, &scope, &ipv6))
198 return NULL;
199 if (!PyObject_IsInstance(ipv6, ipv6_address)) {
200 PyErr_Format(spoa_error, "must be 'IPv6Address', not '%s'", ipv6->ob_type->tp_name);
201 return NULL;
202 }
203 /* Execute packed ... I think .. */
204 value = PyObject_GetAttrString(ipv6, "packed");
205 if (value == NULL)
206 return NULL;
207 if (PyString_GET_SIZE(value) != sizeof(ip)) {
208 PyErr_Format(spoa_error, "UPv6 manipulation internal error");
209 return NULL;
210 }
211 memcpy(&ip, PyString_AS_STRING(value), PyString_GET_SIZE(value));
212 if (!set_var_ipv6(worker, name, name_len, scope, &ip)) {
213 PyErr_SetString(spoa_error, "No space left available");
214 return NULL;
215 }
Gilchrist Dadagloe38c0922020-08-24 19:21:32 +0000216 /* Once we set the IP value in the worker, we don't need it anymore... */
217 Py_XDECREF(value);
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000218 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100219}
220
221static PyObject *ps_python_set_var_str(PyObject *self, PyObject *args)
222{
223 const char *name;
224 int name_len;
225 int scope;
226 const char *value;
227 int value_len;
228
229 if (!PyArg_ParseTuple(args, "s#is#", &name, &name_len, &scope, &value, &value_len))
230 return NULL;
231 if (!set_var_string(worker, name, name_len, scope, value, value_len)) {
232 PyErr_SetString(spoa_error, "No space left available");
233 return NULL;
234 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000235 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100236}
237
238static PyObject *ps_python_set_var_bin(PyObject *self, PyObject *args)
239{
240 const char *name;
241 int name_len;
242 int scope;
243 const char *value;
244 int value_len;
245
246 if (!PyArg_ParseTuple(args, "s#is#", &name, &name_len, &scope, &value, &value_len))
247 return NULL;
248 if (!set_var_bin(worker, name, name_len, scope, value, value_len)) {
249 PyErr_SetString(spoa_error, "No space left available");
250 return NULL;
251 }
Gilchrist Dadaglo3f7ece82020-12-08 14:37:07 +0000252 Py_RETURN_NONE;
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100253}
254
255
256static PyMethodDef spoa_methods[] = {
257 {"register_message", ps_python_register_message, METH_VARARGS,
258 "Register binding for SPOA message."},
259 {"set_var_null", ps_python_set_var_null, METH_VARARGS,
260 "Set SPOA NULL variable"},
261 {"set_var_boolean", ps_python_set_var_boolean, METH_VARARGS,
262 "Set SPOA boolean variable"},
263 {"set_var_int32", ps_python_set_var_int32, METH_VARARGS,
264 "Set SPOA int32 variable"},
265 {"set_var_uint32", ps_python_set_var_uint32, METH_VARARGS,
266 "Set SPOA uint32 variable"},
267 {"set_var_int64", ps_python_set_var_int64, METH_VARARGS,
268 "Set SPOA int64 variable"},
269 {"set_var_uint64", ps_python_set_var_uint64, METH_VARARGS,
270 "Set SPOA uint64 variable"},
271 {"set_var_ipv4", ps_python_set_var_ipv4, METH_VARARGS,
272 "Set SPOA ipv4 variable"},
273 {"set_var_ipv6", ps_python_set_var_ipv6, METH_VARARGS,
274 "Set SPOA ipv6 variable"},
275 {"set_var_str", ps_python_set_var_str, METH_VARARGS,
276 "Set SPOA str variable"},
277 {"set_var_bin", ps_python_set_var_bin, METH_VARARGS,
278 "Set SPOA bin variable"},
279 { /* end */ }
280};
281
282static int ps_python_start_worker(struct worker *w)
283{
284 PyObject *m;
285 PyObject *module_name;
286 PyObject *value;
287 int ret;
288
289 Py_SetProgramName("spoa-server");
290 Py_Initialize();
291
292 module_name = PyString_FromString("ipaddress");
293 if (module_name == NULL) {
294 PyErr_Print();
295 return 0;
296 }
297
298 module_ipaddress = PyImport_Import(module_name);
299 Py_DECREF(module_name);
300 if (module_ipaddress == NULL) {
301 PyErr_Print();
302 return 0;
303 }
304
305 ipv4_address = PyObject_GetAttrString(module_ipaddress, "IPv4Address");
306 if (ipv4_address == NULL) {
307 PyErr_Print();
308 return 0;
309 }
310
311 ipv6_address = PyObject_GetAttrString(module_ipaddress, "IPv6Address");
Gilchrist Dadaglo2ded13c2020-08-24 19:21:35 +0000312 if (ipv6_address == NULL) {
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100313 PyErr_Print();
314 return 0;
315 }
316
317 m = Py_InitModule("spoa", spoa_methods);
318 if (m == NULL) {
319 PyErr_Print();
320 return 0;
321 }
322
323 spoa_error = PyErr_NewException("spoa.error", NULL, NULL);
324 Py_INCREF(spoa_error);
325 PyModule_AddObject(m, "error", spoa_error);
326
327
328 value = PyLong_FromLong(SPOE_SCOPE_PROC);
329 if (value == NULL) {
330 PyErr_Print();
331 return 0;
332 }
333
334 ret = PyModule_AddObject(m, "scope_proc", value);
335 if (ret == -1) {
336 PyErr_Print();
337 return 0;
338 }
339
340 value = PyLong_FromLong(SPOE_SCOPE_SESS);
341 if (value == NULL) {
342 PyErr_Print();
343 return 0;
344 }
345
346 ret = PyModule_AddObject(m, "scope_sess", value);
347 if (ret == -1) {
348 PyErr_Print();
349 return 0;
350 }
351
352 value = PyLong_FromLong(SPOE_SCOPE_TXN);
353 if (value == NULL) {
354 PyErr_Print();
355 return 0;
356 }
357
358 ret = PyModule_AddObject(m, "scope_txn", value);
359 if (ret == -1) {
360 PyErr_Print();
361 return 0;
362 }
363
364 value = PyLong_FromLong(SPOE_SCOPE_REQ);
365 if (value == NULL) {
366 PyErr_Print();
367 return 0;
368 }
369
370 ret = PyModule_AddObject(m, "scope_req", value);
371 if (ret == -1) {
372 PyErr_Print();
373 return 0;
374 }
375
376 value = PyLong_FromLong(SPOE_SCOPE_RES);
377 if (value == NULL) {
378 PyErr_Print();
379 return 0;
380 }
381
382 ret = PyModule_AddObject(m, "scope_res", value);
383 if (ret == -1) {
384 PyErr_Print();
385 return 0;
386 }
387
388 empty_array = PyDict_New();
389 if (empty_array == NULL) {
390 PyErr_Print();
391 return 0;
392 }
393
394 worker = w;
395 return 1;
396}
397
398static int ps_python_load_file(struct worker *w, const char *file)
399{
400 FILE *fp;
401 int ret;
402
403 fp = fopen(file, "r");
404 if (fp == NULL) {
405 LOG("python: Cannot read file \"%s\": %s", file, strerror(errno));
406 return 0;
407 }
408
409 ret = PyRun_SimpleFile(fp, file);
410 fclose(fp);
411 if (ret != 0) {
412 PyErr_Print();
413 return 0;
414 }
415
416 return 1;
417}
418
419static int ps_python_exec_message(struct worker *w, void *ref, int nargs, struct spoe_kv *args)
420{
421 int i;
422 PyObject *python_ref = ref;
423 PyObject *fkw;
424 PyObject *kw_args;
425 PyObject *result;
426 PyObject *ent;
427 PyObject *key;
428 PyObject *value;
429 PyObject *func;
430 int ret;
431 char ipbuf[64];
432 const char *p;
433 PyObject *ip_dict;
434 PyObject *ip_name;
435 PyObject *ip_value;
436
437 /* Dict containing arguments */
438
439 kw_args = PyList_New(0);
440 if (kw_args == NULL) {
441 PyErr_Print();
442 return 0;
443 }
444
445 for (i = 0; i < nargs; i++) {
446
447 /* New dict containing one argument */
448
449 ent = PyDict_New();
450 if (ent == NULL) {
451 Py_DECREF(kw_args);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100452 PyErr_Print();
453 return 0;
454 }
455
456 /* Create the name entry */
457
458 key = PyString_FromString("name");
459 if (key == NULL) {
460 Py_DECREF(kw_args);
Gilchrist Dadaglo10cd77a2020-08-24 19:21:34 +0000461 Py_DECREF(ent);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100462 PyErr_Print();
463 return 0;
464 }
465
466 value = PyString_FromStringAndSize(args[i].name.str, args[i].name.len);
467 if (value == NULL) {
468 Py_DECREF(kw_args);
469 Py_DECREF(ent);
470 Py_DECREF(key);
471 PyErr_Print();
472 return 0;
473 }
474
475 ret = PyDict_SetItem(ent, key, value);
476 Py_DECREF(key);
477 Py_DECREF(value);
478 if (ret == -1) {
479 Py_DECREF(kw_args);
480 Py_DECREF(ent);
481 PyErr_Print();
482 return 0;
483 }
484
485 /* Create th value entry */
486
487 key = PyString_FromString("value");
488 if (key == NULL) {
489 Py_DECREF(kw_args);
490 Py_DECREF(ent);
491 PyErr_Print();
492 return 0;
493 }
494
495 switch (args[i].value.type) {
496 case SPOE_DATA_T_NULL:
497 value = Py_None;
498 break;
499 case SPOE_DATA_T_BOOL:
500 value = PyBool_FromLong(args[i].value.u.boolean);
501 break;
502 case SPOE_DATA_T_INT32:
503 value = PyLong_FromLong(args[i].value.u.sint32);
504 break;
505 case SPOE_DATA_T_UINT32:
506 value = PyLong_FromLong(args[i].value.u.uint32);
507 break;
508 case SPOE_DATA_T_INT64:
509 value = PyLong_FromLong(args[i].value.u.sint64);
510 break;
511 case SPOE_DATA_T_UINT64:
512 value = PyLong_FromUnsignedLong(args[i].value.u.uint64);
513 break;
514 case SPOE_DATA_T_IPV4:
515 case SPOE_DATA_T_IPV6:
516 if (args[i].value.type == SPOE_DATA_T_IPV4)
517 p = inet_ntop(AF_INET, &args[i].value.u.ipv4, ipbuf, 64);
518 else
519 p = inet_ntop(AF_INET6, &args[i].value.u.ipv6, ipbuf, 64);
520 if (!p)
521 strcpy(ipbuf, "0.0.0.0");
522
523 func = PyObject_GetAttrString(module_ipaddress, "ip_address");
524 if (func == NULL) {
525 Py_DECREF(kw_args);
526 Py_DECREF(ent);
Gilchrist Dadaglo10cd77a2020-08-24 19:21:34 +0000527 Py_DECREF(key);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100528 PyErr_Print();
529 return 0;
530 }
531 ip_dict = PyDict_New();
532 if (ip_dict == NULL) {
533 Py_DECREF(kw_args);
534 Py_DECREF(ent);
Gilchrist Dadaglo10cd77a2020-08-24 19:21:34 +0000535 Py_DECREF(key);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100536 Py_DECREF(func);
537 PyErr_Print();
538 return 0;
539 }
540 ip_name = PyString_FromString("address");
541 if (ip_name == NULL) {
542 Py_DECREF(kw_args);
543 Py_DECREF(ent);
Gilchrist Dadaglo10cd77a2020-08-24 19:21:34 +0000544 Py_DECREF(key);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100545 Py_DECREF(func);
546 Py_DECREF(ip_dict);
547 PyErr_Print();
548 return 0;
549 }
550 ip_value = PyUnicode_FromString(ipbuf);
551 if (ip_value == NULL) {
552 Py_DECREF(kw_args);
553 Py_DECREF(ent);
Gilchrist Dadaglo10cd77a2020-08-24 19:21:34 +0000554 Py_DECREF(key);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100555 Py_DECREF(func);
556 Py_DECREF(ip_dict);
557 Py_DECREF(ip_name);
558 PyErr_Print();
559 return 0;
560 }
561 ret = PyDict_SetItem(ip_dict, ip_name, ip_value);
562 Py_DECREF(ip_name);
563 Py_DECREF(ip_value);
564 if (ret == -1) {
Gilchrist Dadaglo10cd77a2020-08-24 19:21:34 +0000565 Py_DECREF(kw_args);
566 Py_DECREF(ent);
567 Py_DECREF(key);
568 Py_DECREF(func);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100569 Py_DECREF(ip_dict);
570 PyErr_Print();
571 return 0;
572 }
573 value = PyObject_Call(func, empty_array, ip_dict);
574 Py_DECREF(func);
575 Py_DECREF(ip_dict);
576 break;
577
578 case SPOE_DATA_T_STR:
579 value = PyString_FromStringAndSize(args[i].value.u.buffer.str, args[i].value.u.buffer.len);
580 break;
581 case SPOE_DATA_T_BIN:
582 value = PyString_FromStringAndSize(args[i].value.u.buffer.str, args[i].value.u.buffer.len);
583 break;
584 default:
585 value = Py_None;
586 break;
587 }
588 if (value == NULL) {
589 Py_DECREF(kw_args);
590 Py_DECREF(ent);
591 Py_DECREF(key);
592 PyErr_Print();
593 return 0;
594 }
595
596 ret = PyDict_SetItem(ent, key, value);
597 Py_DECREF(key);
598 Py_DECREF(value);
599 if (ret == -1) {
600 Py_DECREF(kw_args);
601 Py_DECREF(ent);
602 PyErr_Print();
603 return 0;
604 }
605
606 /* Add dict to the list */
607
608 ret = PyList_Append(kw_args, ent);
609 Py_DECREF(ent);
610 if (ret == -1) {
611 Py_DECREF(kw_args);
612 PyErr_Print();
613 return 0;
614 }
615 }
616
617 /* Dictionnary { args = <list-of-args> } for the function */
618
619 fkw = PyDict_New();
620 if (fkw == NULL) {
621 Py_DECREF(kw_args);
622 PyErr_Print();
623 return 0;
624 }
625
626 key = PyString_FromString("args");
627 if (key == NULL) {
628 Py_DECREF(kw_args);
629 Py_DECREF(fkw);
630 PyErr_Print();
631 return 0;
632 }
633
634 ret = PyDict_SetItem(fkw, key, kw_args);
635 Py_DECREF(kw_args);
636 Py_DECREF(key);
637 if (ret == -1) {
638 Py_DECREF(fkw);
639 PyErr_Print();
640 return 0;
641 }
642
643 result = PyObject_Call(python_ref, empty_array, fkw);
Gilchrist Dadaglod01d2802020-08-24 19:21:31 +0000644 Py_DECREF(fkw);
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100645 if (result == NULL) {
646 PyErr_Print();
647 return 0;
648 }
Gilchrist Dadaglod01d2802020-08-24 19:21:31 +0000649 if (result != Py_None) {
650 Py_DECREF(result);
651 }
Thierry FOURNIER00a02252018-02-25 20:59:57 +0100652
653 return 1;
654}
655
656__attribute__((constructor))
657static void __ps_python_init(void)
658{
659 ps_register(&ps_python_bindings);
660}