]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[fdxhook] hook example written in python
authorFrancis Dupont <fdupont@isc.org>
Thu, 16 Jun 2016 03:02:03 +0000 (05:02 +0200)
committerFrancis Dupont <fdupont@isc.org>
Thu, 16 Jun 2016 03:02:03 +0000 (05:02 +0200)
src/hooks/external/python/cshenv [new file with mode: 0644]
src/hooks/external/python/dso.cc [new file with mode: 0644]
src/hooks/external/python/hook.py [new file with mode: 0644]
src/hooks/external/python/module.cc [new file with mode: 0644]
src/hooks/external/python/module.h [new file with mode: 0644]
src/hooks/external/python/poption.cc [new file with mode: 0644]
src/hooks/external/python/poption.h [new file with mode: 0644]
src/hooks/external/python/ppkt4.cc [new file with mode: 0644]
src/hooks/external/python/ppkt4.h [new file with mode: 0644]
src/hooks/external/python/tests.cc [new file with mode: 0644]

diff --git a/src/hooks/external/python/cshenv b/src/hooks/external/python/cshenv
new file mode 100644 (file)
index 0000000..9c824c4
--- /dev/null
@@ -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 (file)
index 0000000..a10f938
--- /dev/null
@@ -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 <Python.h>
+
+#include <hooks/hooks.h>
+
+#include <hooks/external/python/module.h>
+#include <hooks/external/python/ppkt4.h>
+
+#include <iostream>
+
+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<py_pkt4*>(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<int>(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 (file)
index 0000000..b2908c9
--- /dev/null
@@ -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 (file)
index 0000000..5e0250d
--- /dev/null
@@ -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 <Python.h>
+
+#include <hooks/external/python/poption.h>
+#include <hooks/external/python/ppkt4.h>
+
+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 (file)
index 0000000..c645e33
--- /dev/null
@@ -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 (file)
index 0000000..bf1c416
--- /dev/null
@@ -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 <Python.h>
+
+#include <hooks/external/python/poption.h>
+
+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<unsigned>(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<unsigned>(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<py_option*>(obj);
+    self->object.reset(new Option(universe, t, data));
+
+    return (0);
+}
+
+// tp_dealloc
+void
+option_dealloc(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(obj);
+    self->object.reset();
+    Py_TYPE(self)->tp_free(self);
+}
+
+// tp_str
+PyObject*
+option_str(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(obj);
+    return (PyUnicode_FromString(self->object->toText(0).c_str()));
+}
+
+// getUniverse() method
+PyObject*
+getUniverse(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(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<py_option*>(obj);
+    vector<uint8_t> bin = self->object->toBinary(ih != 0);
+    return (PyBytes_FromStringAndSize(reinterpret_cast<char*>(&bin[0]),
+                                      static_cast<Py_ssize_t>(bin.size())));
+}
+
+// getType() method
+PyObject*
+getType(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(obj);
+    return (PyLong_FromLong(static_cast<long>(self->object->getType())));
+}
+
+// len() method
+PyObject*
+len(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(obj);
+    return (PyLong_FromLong(static_cast<long>(self->object->len())));
+}
+
+// getHeaderLen() method
+PyObject*
+getHeaderLen(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(obj);
+    return (PyLong_FromLong(static_cast<long>(self->object->getHeaderLen())));
+}
+
+// getData() method
+PyObject*
+getData(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(obj);
+    const OptionBuffer& data = self->object->getData();
+    return (PyBytes_FromStringAndSize(reinterpret_cast<const char*>(&data[0]),
+                                      static_cast<Py_ssize_t>(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<py_option*>(s);
+    py_option* const self = static_cast<py_option*>(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<py_option*>(obj);
+    OptionPtr sub = self->object->getOption(t);
+    if (sub) {
+        PyObject* ret = option_type.tp_alloc(&option_type, 0);
+        if (ret) {
+            (static_cast<py_option*>(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<py_option*>(obj);
+    bool ret = self->object->delOption(t);
+    return (PyBool_FromLong(ret ? 1l : 0l));
+}
+
+// setData(<byte>) method
+PyObject*
+setData(PyObject* obj, PyObject* args) {
+    PyBytesObject* d = NULL;
+
+    if (!PyArg_ParseTuple(args, "S", &d)) {
+        return (NULL);
+    }
+    vector<uint8_t> data;
+    data.resize(PyBytes_GET_SIZE(d));
+    memmove(&data[0], PyBytes_AS_STRING(d), data.size());
+
+    py_option* const self = static_cast<py_option*>(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<py_option*>(obj);
+    self->object->setEncapsulatedSpace(encapsulated_space);
+    Py_RETURN_NONE;
+}
+
+// getEncapsulatedSpace() method
+PyObject*
+getEncapsulatedSpace(PyObject* obj) {
+    py_option* const self = static_cast<py_option*>(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<PyObject*>(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 (file)
index 0000000..3478f02
--- /dev/null
@@ -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 <Python.h>
+
+#include <dhcp/option.h>
+
+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 (file)
index 0000000..d891f52
--- /dev/null
@@ -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 <Python.h>
+
+#include <hooks/external/python/poption.h>
+#include <hooks/external/python/ppkt4.h>
+
+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<py_pkt4*>(obj);
+    self->object.reset();
+    Py_TYPE(self)->tp_free(self);
+}
+
+// tp_str
+PyObject*
+pkt4_str(PyObject* obj) {
+    py_pkt4* const self = static_cast<py_pkt4*>(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<py_option*>(s);
+    py_pkt4* const self = static_cast<py_pkt4*>(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<py_pkt4*>(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<py_pkt4*>(obj);
+    return (PyLong_FromLong(static_cast<long>(self->object->len())));
+}
+
+// getType() method
+PyObject*
+getType(PyObject* obj) {
+    py_pkt4* const self = static_cast<py_pkt4*>(obj);
+    return (PyLong_FromLong(static_cast<long>(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<py_pkt4*>(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<py_pkt4*>(obj);
+    self->object->setTransid(static_cast<uint32_t>(t));
+    Py_RETURN_NONE;
+}
+
+// getTransid() method
+PyObject*
+getTransid(PyObject* obj) {
+    py_pkt4* const self = static_cast<py_pkt4*>(obj);
+    uint32_t transid = self->object->getTransid();
+    return (PyLong_FromUnsignedLong(static_cast<unsigned long>(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<py_pkt4*>(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<py_pkt4*>(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<py_pkt4*>(obj);
+    OptionPtr sub = self->object->getOption(t);
+    if (sub) {
+        PyObject* ret = option_type.tp_alloc(&option_type, 0);
+        if (ret) {
+            (static_cast<py_option*>(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<py_pkt4*>(obj);
+    self->object->setIndex(static_cast<uint32_t>(i));
+    Py_RETURN_NONE;
+}
+
+// getIndex() method
+PyObject*
+getIndex(PyObject* obj) {
+    py_pkt4* const self = static_cast<py_pkt4*>(obj);
+    uint32_t ifindex = self->object->getIndex();
+    return (PyLong_FromUnsignedLong(static_cast<unsigned long>(ifindex)));
+}
+
+// getIface() method
+PyObject*
+getIface(PyObject* obj) {
+    py_pkt4* const self = static_cast<py_pkt4*>(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<py_pkt4*>(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<PyObject*>(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 (file)
index 0000000..b2a714e
--- /dev/null
@@ -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 <Python.h>
+
+#include <dhcp/pkt4.h>
+
+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 (file)
index 0000000..30e69da
--- /dev/null
@@ -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 <cc/data.h>
+#include <dhcp/pkt4.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/callout_handle_store.h>
+#include <hooks/hooks_manager.h>
+#include <log/logger_support.h>
+
+#include <boost/foreach.hpp>
+
+#include <iostream>
+
+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<string> 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<HooksLibrariesParser> parser;
+    try {
+        const map<string, ConstElementPtr>& 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<uint8_t> 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);
+}