From: Francis Dupont Date: Thu, 16 Jun 2016 03:02:03 +0000 (+0200) Subject: [fdxhook] hook example written in python X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=949ac53349c84a4aab19ce254e94a4913f3e2dde;p=thirdparty%2Fkea.git [fdxhook] hook example written in python --- diff --git a/src/hooks/external/python/cshenv b/src/hooks/external/python/cshenv new file mode 100644 index 0000000000..9c824c4002 --- /dev/null +++ b/src/hooks/external/python/cshenv @@ -0,0 +1,7 @@ +setenv KEATOP /tmp/kea + +setenv KEASRC $KEATOP/src/lib + +setenv DYLD_LIBRARY_PATH $KEASRC/dhcpsrv/.libs:$KEASRC/eval/.libs:$KEASRC/dhcp_ddns/.libs:$KEASRC/stats/.libs:$KEASRC/hooks/.libs:$KEASRC/config/.libs:$KEASRC/dhcp/.libs:$KEASRC/asiolink/.libs:$KEASRC/dns/.libs:$KEASRC/cc/.libs:$KEASRC/cryptolink/.libs:$KEASRC/log/.libs:$KEASRC/util/threads/.libs:$KEASRC/util/.libs:$KEASRC/exceptions/.libs + +setenv PYTHONPATH $KEATOP/src/hooks/external/python diff --git a/src/hooks/external/python/dso.cc b/src/hooks/external/python/dso.cc new file mode 100644 index 0000000000..a10f938f78 --- /dev/null +++ b/src/hooks/external/python/dso.cc @@ -0,0 +1,171 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include + +#include +#include + +#include + +using namespace std; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace isc::python; + +namespace { + // Program name in Unicode + wchar_t* wprogname; + + // Script module + PyObject* modscript; + + // Pkt4 receive handler + PyObject* pkt4_rcv_hndl; +}; + +extern "C" { + +// Framework functions + +// version +int version() { + return (KEA_HOOKS_VERSION); +} + +// load +int load(LibraryHandle& handle) { + // Add kea as a built-in module + if (PyImport_AppendInittab("kea", PyInit_kea) < 0) { + cerr << "PyImport_AppendInittab failed\n"; + return (1); + } + + // Set the program name (default "kea") + ConstElementPtr program = handle.getParameter("program"); + string progname = "kea"; + if (program && program->getType() == Element::string) { + progname = program->stringValue(); + } else { + cout << "no \"program\" parameter: using \"kea\"\n"; + } + wprogname = Py_DecodeLocale(progname.c_str(), NULL); + if (!wprogname) { + cerr << "Py_DecodeLocale failed\n"; + return (2); + } + Py_SetProgramName(wprogname); + + // Initialize the python interpreter without signal handlers + Py_InitializeEx(0); + + // Get the script module name (default "hook" for the "hook.py" file) + ConstElementPtr script = handle.getParameter("script"); + string scptname = "hook"; + if (script && script->getType() == Element::string) { + scptname = script->stringValue(); + } else { + cout << "no \"script\" parameter: using \"hook\"\n"; + } + PyObject* pyscript = PyUnicode_DecodeFSDefault(scptname.c_str()); + if (!pyscript) { + PyErr_Print(); + cerr << "PyUnicode_DecodeFSDefault failed\n"; + return (3); + } + modscript = PyImport_Import(pyscript); + Py_DECREF(pyscript); + if (!modscript) { + PyErr_Print(); + cerr << "PyImport_Import failed\n"; + return (4); + } + + pkt4_rcv_hndl = PyObject_GetAttrString(modscript, "pkt4_receive"); + if (pkt4_rcv_hndl) { + if (!PyCallable_Check(pkt4_rcv_hndl)) { + cerr << "pkt4_receive is not callable\n"; + Py_DECREF(pkt4_rcv_hndl); + pkt4_rcv_hndl = NULL; + } else { + cout << "got pkt4_receive\n"; + } + } + + return (0); +} + +// unload +int unload() { + // Free script module + Py_XDECREF(modscript); + + // Shutdown + Py_Finalize(); + + // Free wprogname + PyMem_RawFree(wprogname); + + return (0); +} + +// pkt4_receive hook +int pkt4_receive(CalloutHandle& handle) { + if (!pkt4_rcv_hndl) { + return (0); + } + cout << "pkt4_receive: enter\n"; + + Pkt4Ptr query4; + handle.getArgument("query4", query4); + if (!query4) { + cerr << "pkt4_receive: null query4\n"; + return (0); + } + + PyObject* query = pkt4_type.tp_alloc(&pkt4_type, 0); + if (!query) { + PyErr_Print(); + cerr << "pkt4_alloc failed\n"; + return (0); + } + (static_cast(query))->object = query4; + + PyObject* args = Py_BuildValue("(O)", query); + if (!args) { + PyErr_Print(); + cerr << "Py_BuildValue failed\n"; + return (0); + } + PyObject* ret = PyObject_CallObject(pkt4_rcv_hndl, args); + Py_DECREF(args); + if (!ret) { + PyErr_Print(); + cerr << "pkt4_rcv_hndl failed\n"; + return (0); + } + if (!PyLong_Check(ret)) { + PyErr_Print(); + cerr << "pkt4_rcv_hndl didn't return an int\n"; + Py_DECREF(ret); + return (0); + } + int result = static_cast(PyLong_AsLong(ret)); + Py_DECREF(ret); + if (PyErr_Occurred()) { + PyErr_Print(); + cerr << "PyLong_AsLong failed\n"; + return (0); + } + + cout << "pkt4_receive: return " << result << "\n"; + return (result); +} + +} diff --git a/src/hooks/external/python/hook.py b/src/hooks/external/python/hook.py new file mode 100644 index 0000000000..b2908c9d4f --- /dev/null +++ b/src/hooks/external/python/hook.py @@ -0,0 +1,26 @@ +# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +'''Support for Kea hook in python''' + +print("hook.py is loading") + +import kea + +NEXT_STEP_CONTINUE = 0 +NEXT_STEP_SKIP = 1 +NEXT_STEP_DROP = 2 + +def pkt4_receive(query4): + """pkt4_receive hook point. + + parameter: inout Pkt4Ptr query4 + return: next step + """ + print("pkt4_receive: handler is called with", query4) + return NEXT_STEP_CONTINUE + +print("hook.py loaded") diff --git a/src/hooks/external/python/module.cc b/src/hooks/external/python/module.cc new file mode 100644 index 0000000000..5e0250d5fe --- /dev/null +++ b/src/hooks/external/python/module.cc @@ -0,0 +1,56 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include +#include + +using namespace std; +using namespace isc::python; + +namespace { // anonymous namespace + +// Module definition +PyModuleDef kea = { + PyModuleDef_HEAD_INIT, + "kea", // m_name + "kea hook support", // m_doc + -1, // m_size + NULL, // m_methods + NULL, // m_slots + NULL, // m_traverse + NULL, // m_clear + NULL // m_free +}; + +} // end of anonymous namespace + +// Module initialization +PyMODINIT_FUNC +PyInit_kea(void) { + // Create module + PyObject* mod = PyModule_Create(&kea); + if (!mod) { + return (NULL); + } + + // Initialize option + if (!initmod_option(mod)) { + Py_DECREF(mod); + return (NULL); + } + + // Initialize pkt4 + if (!initmod_pkt4(mod)) { + Py_DECREF(mod); + return (NULL); + } + + // Constants + + return (mod); +} diff --git a/src/hooks/external/python/module.h b/src/hooks/external/python/module.h new file mode 100644 index 0000000000..c645e3347e --- /dev/null +++ b/src/hooks/external/python/module.h @@ -0,0 +1,12 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef KEA_PYTHON_MODULE_H +#define KEA_PYTHON_MODULE_H 1 + +PyMODINIT_FUNC PyInit_kea(void); + +#endif // KEA_PYTHON_MODULE_H diff --git a/src/hooks/external/python/poption.cc b/src/hooks/external/python/poption.cc new file mode 100644 index 0000000000..bf1c416379 --- /dev/null +++ b/src/hooks/external/python/poption.cc @@ -0,0 +1,328 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#define PY_SSIZE_T_CLEAN +#include + +#include + +using namespace std; +using namespace isc::dhcp; +using namespace isc::python; + +// Constructor +py_option::py_option() {} + +namespace { // anonymous namespace + +// tp_init +int +option_init(PyObject* obj, PyObject* args, PyObject*) { + unsigned char u; + unsigned short int t; + PyBytesObject* d = NULL; + + if (!PyArg_ParseTuple(args, "bHS", &u, &t, &d)) { + return (-1); + } + Option::Universe universe; + switch (u) { + case 4: + universe = Option::V4; + if (t > 255) { + PyErr_Format(PyExc_ValueError, + "out of range type for DHCPv4: %u", + static_cast(t)); + return (-1); + } + break; + case 6: + universe = Option::V6; + break; + default: + PyErr_Format(PyExc_ValueError, + "universe must be 4 or 6 (not %u)", + static_cast(u)); + return (-1); + } + + OptionBuffer data; + data.resize(PyBytes_GET_SIZE(d)); + memmove(&data[0], PyBytes_AS_STRING(d), data.size()); + + py_option* const self = static_cast(obj); + self->object.reset(new Option(universe, t, data)); + + return (0); +} + +// tp_dealloc +void +option_dealloc(PyObject* obj) { + py_option* const self = static_cast(obj); + self->object.reset(); + Py_TYPE(self)->tp_free(self); +} + +// tp_str +PyObject* +option_str(PyObject* obj) { + py_option* const self = static_cast(obj); + return (PyUnicode_FromString(self->object->toText(0).c_str())); +} + +// getUniverse() method +PyObject* +getUniverse(PyObject* obj) { + py_option* const self = static_cast(obj); + switch (self->object->getUniverse()) { + case Option::V4: + return (PyLong_FromLong(4l)); + case Option::V6: + return (PyLong_FromLong(6l)); + default: + PyErr_SetString(PyExc_SystemError, "getUniverse"); + return (NULL); + } +} + +// toBinary(bool include_header = false) method +PyObject* +toBinary(PyObject* obj, PyObject* args) { + int ih = 0; + + if (!PyArg_ParseTuple(args, "|p", &ih)) { + return (NULL); + } + + py_option* const self = static_cast(obj); + vector bin = self->object->toBinary(ih != 0); + return (PyBytes_FromStringAndSize(reinterpret_cast(&bin[0]), + static_cast(bin.size()))); +} + +// getType() method +PyObject* +getType(PyObject* obj) { + py_option* const self = static_cast(obj); + return (PyLong_FromLong(static_cast(self->object->getType()))); +} + +// len() method +PyObject* +len(PyObject* obj) { + py_option* const self = static_cast(obj); + return (PyLong_FromLong(static_cast(self->object->len()))); +} + +// getHeaderLen() method +PyObject* +getHeaderLen(PyObject* obj) { + py_option* const self = static_cast(obj); + return (PyLong_FromLong(static_cast(self->object->getHeaderLen()))); +} + +// getData() method +PyObject* +getData(PyObject* obj) { + py_option* const self = static_cast(obj); + const OptionBuffer& data = self->object->getData(); + return (PyBytes_FromStringAndSize(reinterpret_cast(&data[0]), + static_cast(data.size()))); +} + +// addOption(OptionPtr opt) method +PyObject* +addOption(PyObject* obj, PyObject* args) { + PyObject* s = NULL; + + if (!PyArg_ParseTuple(args, "O!", &option_type, &s)) { + return (NULL); + } + py_option* const sub = static_cast(s); + py_option* const self = static_cast(obj); + self->object->addOption(sub->object); + Py_RETURN_NONE; +} + +// getOption(uint16_t type) method +PyObject* +getOption(PyObject* obj, PyObject* args) { + unsigned short t; + + if (!PyArg_ParseTuple(args, "H", &t)) { + return (NULL); + } + py_option* const self = static_cast(obj); + OptionPtr sub = self->object->getOption(t); + if (sub) { + PyObject* ret = option_type.tp_alloc(&option_type, 0); + if (ret) { + (static_cast(ret))->object = sub; + } + return (ret); + } + Py_RETURN_NONE; +} + +// TODO: getOptions() method + +// delOption(uint16_t type) method +PyObject* +delOption(PyObject* obj, PyObject* args) { + unsigned short t; + + if (!PyArg_ParseTuple(args, "H", &t)) { + return (NULL); + } + py_option* const self = static_cast(obj); + bool ret = self->object->delOption(t); + return (PyBool_FromLong(ret ? 1l : 0l)); +} + +// setData() method +PyObject* +setData(PyObject* obj, PyObject* args) { + PyBytesObject* d = NULL; + + if (!PyArg_ParseTuple(args, "S", &d)) { + return (NULL); + } + vector data; + data.resize(PyBytes_GET_SIZE(d)); + memmove(&data[0], PyBytes_AS_STRING(d), data.size()); + + py_option* const self = static_cast(obj); + self->object->setData(data.begin(), data.end()); + Py_RETURN_NONE; +} + +// setEncapsulatedSpace(const string& encapsulated_space) method +PyObject* +setEncapsulatedSpace(PyObject* obj, PyObject* args) { + const char* es = NULL; + + if (!PyArg_ParseTuple(args, "s", &es)) { + return (NULL); + } + const string& encapsulated_space(es); + + py_option* const self = static_cast(obj); + self->object->setEncapsulatedSpace(encapsulated_space); + Py_RETURN_NONE; +} + +// getEncapsulatedSpace() method +PyObject* +getEncapsulatedSpace(PyObject* obj) { + py_option* const self = static_cast(obj); + return (Py_BuildValue("s", self->object->getEncapsulatedSpace().c_str())); +} + +// Method table +PyMethodDef option_method[] = { + { "getUniverse", (PyCFunction)getUniverse, METH_NOARGS, + "returns option universe (4 or 6" }, + { "toBinary", toBinary, METH_VARARGS, + "returns binary representation of the option" }, + { "getType", (PyCFunction)getType, METH_NOARGS, + "returns option type" }, + { "len", (PyCFunction)len, METH_NOARGS, + "returns length of the complete option" }, + { "getHeaderLen", (PyCFunction)getHeaderLen, METH_NOARGS, + "returns length of option header" }, + { "getData", (PyCFunction)getData, METH_NOARGS, + "returns actual data" }, + { "addOption", addOption, METH_VARARGS, + "adds a sub-option" }, + { "getOption", getOption, METH_VARARGS, + "return suboption of specific type" }, + { "delOption", delOption, METH_VARARGS, + "attempts to delete first suboption of requested type" }, + { "setData", setData, METH_VARARGS, + "sets content of this option" }, + { "setEncapsulatedSpace", setEncapsulatedSpace, METH_VARARGS, + "sets the name of the option space encapsulated by this option" }, + { "getEncapsulatedSpace", (PyCFunction)getEncapsulatedSpace, METH_NOARGS, + "returns the name of the option space encapsulated by this option" }, + { NULL, NULL, 0, NULL } +}; + +} // end of anonymous namespace + +namespace isc { +namespace python { + +// Python option type definition +PyTypeObject option_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "kea-option", // tp_name + sizeof(py_option), // tp_basicsize + 0, // tp_itemsize + option_dealloc, // 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 + option_str, // tp_str + NULL, // tp_getattro + NULL, // tp_setattro + NULL, // tp_as_buffer + Py_TPFLAGS_DEFAULT, // tp_flags + "kea option", // tp_doc + NULL, // tp_traverse + NULL, // tp_clear + NULL, // tp_richcompare + 0, // tp_weaklistoffset + NULL, // tp_iter + NULL, // tp_iternext + option_method, // 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 + option_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 + NULL // tp_finalize +}; + +bool +initmod_option(PyObject* mod) { + // Initialize the description object + if (PyType_Ready(&option_type) < 0) { + return (false); + } + // Add it to the module + void* p = &option_type; + if (PyModule_AddObject(mod, "Option", static_cast(p)) < 0) { + return (false); + } + Py_INCREF(&option_type); + + return (true); +} + +} // namespace python +} // namespace isc diff --git a/src/hooks/external/python/poption.h b/src/hooks/external/python/poption.h new file mode 100644 index 0000000000..3478f022d4 --- /dev/null +++ b/src/hooks/external/python/poption.h @@ -0,0 +1,32 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef POPTION_H +#define POPTION_H 1 + +#include + +#include + +namespace isc { +namespace python { + +// Python option class +class py_option : public PyObject { + py_option(); + +public: + isc::dhcp::OptionPtr object; +}; + +extern PyTypeObject option_type; + +bool initmod_option(PyObject* mod); + +} // namespace python +} // namespace isc + +#endif // POPTION_H diff --git a/src/hooks/external/python/ppkt4.cc b/src/hooks/external/python/ppkt4.cc new file mode 100644 index 0000000000..d891f52233 --- /dev/null +++ b/src/hooks/external/python/ppkt4.cc @@ -0,0 +1,326 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#define PY_SSIZE_T_CLEAN +#include + +#include +#include + +using namespace std; +using namespace isc::dhcp; +using namespace isc::python; + +// Constructor +py_pkt4::py_pkt4() {} + +namespace { // anonymous namespace + +// tp_init +int +pkt4_init(PyObject*, PyObject*, PyObject*) { + PyErr_SetString(PyExc_SystemError, + "pkt4 cannot be directly constructed"); + return (-1); +} + +// tp_dealloc +void +pkt4_dealloc(PyObject* obj) { + py_pkt4* const self = static_cast(obj); + self->object.reset(); + Py_TYPE(self)->tp_free(self); +} + +// tp_str +PyObject* +pkt4_str(PyObject* obj) { + py_pkt4* const self = static_cast(obj); + return (PyUnicode_FromString(self->object->toText().c_str())); +} + +// addOption(const OptionPtr opt) method +PyObject* +addOption(PyObject* obj, PyObject* args) { + PyObject* s = NULL; + + if (!PyArg_ParseTuple(args, "O!", &option_type, &s)) { + return (NULL); + } + py_option* const sub = static_cast(s); + py_pkt4* const self = static_cast(obj); + self->object->addOption(sub->object); + Py_RETURN_NONE; +} + +// delOption(uint16_t type) method +PyObject* +delOption(PyObject* obj, PyObject* args) { + unsigned short t; + + if (!PyArg_ParseTuple(args, "H", &t)) { + return (NULL); + } + py_pkt4* const self = static_cast(obj); + bool ret = self->object->delOption(t); + return (PyBool_FromLong(ret ? 1l : 0l)); +} + +// len() method +PyObject* +len(PyObject* obj) { + py_pkt4* const self = static_cast(obj); + return (PyLong_FromLong(static_cast(self->object->len()))); +} + +// getType() method +PyObject* +getType(PyObject* obj) { + py_pkt4* const self = static_cast(obj); + return (PyLong_FromLong(static_cast(self->object->getType()))); +} + +// setType(uint8_t type) method +PyObject* +setType(PyObject* obj, PyObject* args) { + unsigned char t; + + if (!PyArg_ParseTuple(args, "b", &t)) { + return (NULL); + } + py_pkt4* const self = static_cast(obj); + self->object->setType(t); + Py_RETURN_NONE; +} + +// setTransid(uint32_t transid) method +PyObject* +setTransid(PyObject* obj, PyObject* args) { + unsigned int t; + + if (!PyArg_ParseTuple(args, "I", &t)) { + return (NULL); + } + py_pkt4* const self = static_cast(obj); + self->object->setTransid(static_cast(t)); + Py_RETURN_NONE; +} + +// getTransid() method +PyObject* +getTransid(PyObject* obj) { + py_pkt4* const self = static_cast(obj); + uint32_t transid = self->object->getTransid(); + return (PyLong_FromUnsignedLong(static_cast(transid))); +} + +// inClass(const isc::dhcp::ClientClass& client_class) method +PyObject* +inClass(PyObject* obj, PyObject* args) { + const char* cc = NULL; + + if (!PyArg_ParseTuple(args, "s", &cc)) { + return (NULL); + } + const ClientClass& client_class(cc); + + py_pkt4* const self = static_cast(obj); + bool ret = self->object->inClass(client_class); + return (PyBool_FromLong(ret ? 1l : 0l)); +} + +// addClass(const isc::dhcp::ClientClass& client_class) method +PyObject* +addClass(PyObject* obj, PyObject* args) { + const char* cc = NULL; + + if (!PyArg_ParseTuple(args, "s", &cc)) { + return (NULL); + } + const ClientClass& client_class(cc); + + py_pkt4* const self = static_cast(obj); + self->object->addClass(client_class); + Py_RETURN_NONE; +} + +// TODO: getClasses() method + +// getOption(uint16_t type) method +PyObject* +getOption(PyObject* obj, PyObject* args) { + unsigned short t; + + if (!PyArg_ParseTuple(args, "H", &t)) { + return (NULL); + } + py_pkt4* const self = static_cast(obj); + OptionPtr sub = self->object->getOption(t); + if (sub) { + PyObject* ret = option_type.tp_alloc(&option_type, 0); + if (ret) { + (static_cast(ret))->object = sub; + } + return (ret); + } + Py_RETURN_NONE; +} + +// TODO: getTimestamp() method + +// TODO: set/getLocal/RemoteAddr/Port methods + +// setIndex(uint32_t ifindex) method +PyObject* +setIndex(PyObject* obj, PyObject* args) { + unsigned int i; + + if (!PyArg_ParseTuple(args, "I", &i)) { + return (NULL); + } + py_pkt4* const self = static_cast(obj); + self->object->setIndex(static_cast(i)); + Py_RETURN_NONE; +} + +// getIndex() method +PyObject* +getIndex(PyObject* obj) { + py_pkt4* const self = static_cast(obj); + uint32_t ifindex = self->object->getIndex(); + return (PyLong_FromUnsignedLong(static_cast(ifindex))); +} + +// getIface() method +PyObject* +getIface(PyObject* obj) { + py_pkt4* const self = static_cast(obj); + return (Py_BuildValue("s", self->object->getIface().c_str())); +} + +// setIface(const std::string& iface) method +PyObject* +setIface(PyObject* obj, PyObject* args) { + const char* ifn = NULL; + + if (!PyArg_ParseTuple(args, "s", &ifn)) { + return (NULL); + } + const string iface(ifn); + + py_pkt4* const self = static_cast(obj); + self->object->setIface(iface); + Py_RETURN_NONE; +} + +// Method table +PyMethodDef pkt4_method[] = { + { "addOption", addOption, METH_VARARGS, + "adds an option to this packet" }, + { "delOption", delOption, METH_VARARGS, + "attempts to delete first suboption of requested type" }, + { "len", (PyCFunction)len, METH_NOARGS, + "returns packet size in binary format" }, + { "getType", (PyCFunction)getType, METH_NOARGS, + "returns message type" }, + { "setType", setType, METH_VARARGS, + "sets message type" }, + { "setTransid", setTransid, METH_VARARGS, + "sets transaction-id value" }, + { "getTransid", (PyCFunction)getTransid, METH_NOARGS, + "returns value of transaction-id field" }, + { "inClass", inClass, METH_VARARGS, + "checks whether a packet belongs to a given class" }, + { "addClass", addClass, METH_VARARGS, + "adds packet to a specified class" }, + { "getOption", getOption, METH_VARARGS, + "returns the first option of specified type" }, + { "setIndex", setIndex, METH_VARARGS, + "sets interface index" }, + { "getIndex", (PyCFunction)getIndex, METH_NOARGS, + "returns interface index" }, + { "getIface", (PyCFunction)getIface, METH_NOARGS, + "return interface name" }, + { "setIface", setIface, METH_VARARGS, + "sets interface name" }, + { NULL, NULL, 0, NULL } +}; + +} // end of anonymous namespace + +namespace isc { +namespace python { + +// Python pkt4 type definition +PyTypeObject pkt4_type = { + PyVarObject_HEAD_INIT(NULL, 0) + "kea-pkt4", // tp_name + sizeof(py_pkt4), // tp_basicsize + 0, // tp_itemsize + pkt4_dealloc, // 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 + pkt4_str, // tp_str + NULL, // tp_getattro + NULL, // tp_setattro + NULL, // tp_as_buffer + Py_TPFLAGS_DEFAULT, // tp_flags + "kea pkt4", // tp_doc + NULL, // tp_traverse + NULL, // tp_clear + NULL, // tp_richcompare + 0, // tp_weaklistoffset + NULL, // tp_iter + NULL, // tp_iternext + pkt4_method, // 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 + pkt4_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 + NULL // tp_finalize +}; + +bool +initmod_pkt4(PyObject* mod) { + // Initialize the description object + if (PyType_Ready(&pkt4_type) < 0) { + return (false); + } + // Add it to the module + void* p = &pkt4_type; + if (PyModule_AddObject(mod, "Pkt4", static_cast(p)) < 0) { + return (false); + } + Py_INCREF(&pkt4_type); + + return (true); +} + +} // namespace python +} // namespace isc diff --git a/src/hooks/external/python/ppkt4.h b/src/hooks/external/python/ppkt4.h new file mode 100644 index 0000000000..b2a714e529 --- /dev/null +++ b/src/hooks/external/python/ppkt4.h @@ -0,0 +1,32 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef PPKT4_H +#define PPKT4_H 1 + +#include + +#include + +namespace isc { +namespace python { + +// Python pkt4 class +class py_pkt4 : public PyObject { + py_pkt4(); + +public: + isc::dhcp::Pkt4Ptr object; +}; + +extern PyTypeObject pkt4_type; + +bool initmod_pkt4(PyObject* mod); + +} // namespace python +} // namespace isc + +#endif // PPKT4_H diff --git a/src/hooks/external/python/tests.cc b/src/hooks/external/python/tests.cc new file mode 100644 index 0000000000..30e69da571 --- /dev/null +++ b/src/hooks/external/python/tests.cc @@ -0,0 +1,147 @@ +// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace std; +using namespace isc; +using namespace isc::asiolink; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::hooks; +using namespace isc::log; + +// config fragment for hooks-libraries +const string config = + "{ \"hooks-libraries\": [" + " { \"library\": \"kea.so\", " + " \"parameters\": " + " { \"program\": \"kea\", " + " \"script\": \"hook\" }" + " }] }"; + +// main routine +int main() { + // must be first + int hi_pkt4_receive = HooksManager::registerHook("pkt4_receive"); + cout << "pkt4_receive is hook#" << hi_pkt4_receive << "\n"; + + initLogger(); + + // check if there is a library already loaded + vector hooks_libraries = HooksManager::getLibraryNames(); + if (!hooks_libraries.empty()) { + cerr << "hooks_libraries is not empty\n"; + } + + // parse config into json + ElementPtr json = Element::fromJSON(config); + if (!json) { + cerr << "fatal: fromJSON failed\n"; + exit(-1); + } + cout << "config parsed\n"; + + // call the hooks-libraries parser + boost::shared_ptr parser; + try { + const map& cmap = json->mapValue(); + if (cmap.empty()) { + cerr << "fatal: config map is empty\n"; + exit(-1); + } + if (cmap.size() > 1) { + cerr << "config map has more than one element\n"; + } + if (cmap.count("hooks-libraries") == 0) { + cerr << "fatal: no \"hooks-libraries\" in config\n"; + exit(-1); + } + const ConstElementPtr& hl_value = cmap.find("hooks-libraries")->second; + if (!hl_value) { + cerr << "fatal: empty \"hooks-libraries\" value\n"; + exit(-1); + } + parser.reset(new HooksLibrariesParser("hooks-libraries")); + parser->build(hl_value); + parser->commit(); + cout << "config committed\n"; + } catch (const Exception& ex) { + cerr << "fatal: config parsing failed: " << ex.what() << "\n"; + exit(-1); + } + + // check if the library was loaded + HookLibsCollection libraries; + bool changed = false; + parser->getLibraries(libraries, changed); + if (!changed) { + cerr << "commit didn't change libraries\n"; + } + if (libraries.empty()) { + cerr << "fatal: no libraries\n"; + exit(-1); + } + if (libraries.size() > 1) { + cerr << "more than one library\n"; + } + cout << "library is \"" + libraries[0].first + "\"\n"; + if (libraries[0].first != "kea.so") { + cerr << "fatal: library is not \"kea.so\"\n"; + exit(-1); + } + ConstElementPtr params = libraries[0].second; + if (!params) { + cerr << "no parameters\n"; + } else { + cout << "got " << params->size() << " parameters\n"; + } + + // note we can't know this way if it was successfully loaded + + // get the callout + if (!HooksManager::calloutsPresent(hi_pkt4_receive)) { + cerr << "fatal: no callout present for pkt4_receive\n"; + exit(-1); + } + + // from pkt4_unittests.cc + Pkt4Ptr pkt(new Pkt4(DHCPDISCOVER, 0x12345678)); + const uint8_t macAddr[] = {0, 1, 2, 3, 4, 5}; + vector vectorMacAddr(macAddr, macAddr + sizeof(macAddr)); + pkt->setHWAddr(6, 6, vectorMacAddr); + pkt->setHops(13); + // Transaction-id is already set. + pkt->setSecs(42); + pkt->setFlags(BOOTP_BROADCAST); + pkt->setCiaddr(IOAddress("192.0.2.1")); + pkt->setYiaddr(IOAddress("1.2.3.4")); + pkt->setSiaddr(IOAddress("192.0.2.255")); + pkt->setGiaddr(IOAddress("255.255.255.255")); + // Chaddr already set with setHWAddr(). + + // from dhcp4_srv.cc + CalloutHandlePtr co_handle = getCalloutHandle(pkt); + co_handle->deleteAllArguments(); + co_handle->setArgument("query4", pkt); + cout << "calling pkt4_receive callout\n"; + HooksManager::callCallouts(hi_pkt4_receive, *co_handle); + cout << "pkt4_receive callout status " << co_handle->getStatus() << "\n"; + co_handle->getArgument("query4", pkt); + + // TODO... + + exit(0); +}