Commit 70ea2e6d authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3413] libdns++ is now less python dependent

 - no longer builds src/lib/dns/python directory
 - gen-rdatacode.py is no longer necessary for build
parent bcdf6647
/gen-rdatacode.py
/rdataclass.cc
/rdataclass.h
/rrclass.h
/rrparamregistry.cc
/rrtype.h
/s-rdatacode
SUBDIRS = . tests python
SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
......@@ -7,7 +7,7 @@ AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
CLEANFILES = *.gcno *.gcda
CLEANFILES += rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc s-rdatacode
CLEANFILES += s-rdatacode
# These two are created with rrtype/class.h, so not explicitly listed in
# BUILT_SOURCES.
CLEANFILES += python/rrtype_constants_inc.cc
......@@ -156,8 +156,10 @@ libkea_dns___la_CPPFLAGS = $(AM_CPPFLAGS)
libkea_dns___la_LIBADD = $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
libkea_dns___la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
nodist_libdns___include_HEADERS = rdataclass.h rrclass.h rrtype.h
nodist_libkea_dns___la_SOURCES = rdataclass.cc rrparamregistry.cc
# The following files used to be generated, but they are now part of the git tree:
# rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc
libkea_dns___la_SOURCES += rdataclass.h rrclass.h rrtype.h
libkea_dns___la_SOURCES += rdataclass.cc rrparamregistry.cc
rrclass.h: rrclass-placeholder.h
rrtype.h: rrtype-placeholder.h
......@@ -167,7 +169,12 @@ s-rdatacode: Makefile $(EXTRA_DIST)
$(PYTHON) ./gen-rdatacode.py
touch $@
rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc: s-rdatacode
# In ticket #3413 we removed the whole BIND10/Bundy framework. We also want
# to not require Python3, hence instead of generating the code every time,
# we added the generated files to our repo. It is still possible to regenerate
# those files, but that step is no longer required for successful compilation.
#rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc: s-rdatacode
libdns___includedir = $(includedir)/$(PACKAGE_NAME)/dns
libdns___include_HEADERS = \
......
/rrclass_constants_inc.cc
/rrtype_constants_inc.cc
/pydnspp_config.h
SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
# There is a build problem with python3.4 (a new field tp_finalize) has been
# added and now compiler complains about it not being properly initialized in
# construtor. Since the whole python thing goes away, it's counter-productive
# to spend any time on making this work on both python3.3 and 3.4, so
# ingoring the warning seems the way to go.
AM_CXXFLAGS += -Wno-error
lib_LTLIBRARIES = libkea-pydnspp.la
libkea_pydnspp_la_SOURCES = pydnspp_common.cc pydnspp_common.h
libkea_pydnspp_la_SOURCES += pydnspp_config.h pydnspp_towire.h
libkea_pydnspp_la_SOURCES += name_python.cc name_python.h
libkea_pydnspp_la_SOURCES += nsec3hash_python.cc nsec3hash_python.h
libkea_pydnspp_la_SOURCES += rrset_python.cc rrset_python.h
libkea_pydnspp_la_SOURCES += rrclass_python.cc rrclass_python.h
libkea_pydnspp_la_SOURCES += rrtype_python.cc rrtype_python.h
libkea_pydnspp_la_SOURCES += rrttl_python.cc rrttl_python.h
libkea_pydnspp_la_SOURCES += rdata_python.cc rdata_python.h
libkea_pydnspp_la_SOURCES += serial_python.cc serial_python.h
libkea_pydnspp_la_SOURCES += messagerenderer_python.cc messagerenderer_python.h
libkea_pydnspp_la_SOURCES += rcode_python.cc rcode_python.h
libkea_pydnspp_la_SOURCES += opcode_python.cc opcode_python.h
libkea_pydnspp_la_SOURCES += question_python.cc question_python.h
libkea_pydnspp_la_SOURCES += tsigkey_python.cc tsigkey_python.h
libkea_pydnspp_la_SOURCES += tsigerror_python.cc tsigerror_python.h
libkea_pydnspp_la_SOURCES += tsig_rdata_python.cc tsig_rdata_python.h
libkea_pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
libkea_pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
libkea_pydnspp_la_SOURCES += edns_python.cc edns_python.h
libkea_pydnspp_la_SOURCES += message_python.cc message_python.h
libkea_pydnspp_la_SOURCES += rrset_collection_python.cc
libkea_pydnspp_la_SOURCES += rrset_collection_python.h
libkea_pydnspp_la_SOURCES += zone_checker_python.cc zone_checker_python.h
libkea_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
libkea_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
libkea_pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
pyexec_LTLIBRARIES = pydnspp.la
pydnspp_la_SOURCES = pydnspp.cc
pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
# Note: PYTHON_CXXFLAGS may have some -Wno... workaround, which must be
# placed after -Wextra defined in AM_CXXFLAGS
pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
EXTRA_DIST = tsigerror_python_inc.cc
EXTRA_DIST += message_python_inc.cc
EXTRA_DIST += nsec3hash_python_inc.cc
EXTRA_DIST += rrset_collection_python_inc.cc
EXTRA_DIST += zone_checker_python_inc.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
pydnspp_la_LDFLAGS += -module -avoid-version
pydnspp_la_LIBADD = $(top_builddir)/src/lib/dns/libkea-dns++.la
pydnspp_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
pydnspp_la_LIBADD += libkea-pydnspp.la
pydnspp_la_LIBADD += $(PYTHON_LIB)
This is an implementation of the python wrappers for isc::dns.
When compiled the module is called pydnspp. If we
decide to always need it we can add a default import under
lib/python/isc.
To use it from the source tree, you must add src/lib/dns/python/.libs
to your PYTHONPATH environment variable. Within python you can then use
> import pydnspp
> rrc = pydnspp.RRClass("IN")
etc.
Notes:
this implementation is not a complete 1-to-1 mapping of the C++ API;
some of the functionality is not needed the way we use it in Python.
For instance, we did not implement the buffer classes;
everywhere in the API where buffers are used, you can pass a bytearray
object.
We also (currently) left out some 'lowlevel' wrappers, for instance for
specific Rdata types.
If you have specific functionality you do need, please ask for it and we
will add it.
The 'main' module is defined in pydnspp.cc.
There is a pydnspp_common.[cc|h] for helper functions.
Implementation notes:
Writing wrappers for a lot of classes is mostly a case of repetition.
There are a lot of things one must do, and that is why nearly everyone
immediately and continually has the urge to make generators for this.
We have added a little more documentation than is strictly necessary to
rrclass.h, for reference to new readers.
To keep it maintainable as the original API changes, we use two
techniques;
1. Full unittests. Or at least as full as possible. These unit tests
test the *wrapper* code, not necessarily the underlying c++ code,
which has its own unit tests. There is of course some overlap.
2. Structure. I have tried to structure the wrapper files as much as
possible, see below.
Structure:
Since we are moving a lot of wrappers into one module, the specific
classes are split over several files, each one having the name of the
original header file they are wrapping (e.g. the wrapper for name.h
becomes name_python.cc).
At the top we first declare any exceptions, constants, and enums. These
are all called po_<C++-name> (the actual python name will be set once
they are added to the module in the module inialization function).
Each class needs a struct that contains a pointer to an instance of the
object (and any helper data). We call these structs s_<C++ class name>.
Then we declare (but not define!) all methods we will export to
python. These are named <C++ class name>_<C++ method name>.
We will also need an _init and _destroy function for all of these.
After the function declarations we define the method array; this
contains the name of the methods as they appear in python, the wrapping
function here, and documentation strings.
Next is the type description; this is used for the wrapper code to
convert native classes from and to python objects, and defines
type-specific pointers (for instance to the method table mentioned above,
but also to a __str__ function should one be defined, how the object
should behave when it is used as a Sequence, etc.). For most classes,
almost all values here shall be NULL.
This has the name <lowercase C++ class name>_type.
After that we define the exported functions we defined earlier. In some
cases these need the type we just defined, and the type needed the
function names, so for consistency, all functions are defined after,
but declared before the type.
This is repeated for every class we export.
Finally we define the function to add the class, constants, exceptions,
and enums to the module. This function is called from the init function
in pydnspp.cc, has the name
initModulePart_<c++ class name>, returns a boolean
(true on success, false on failure), and takes the module as a
PyObject*. There is a convenience function called addClassVariable to
add constants and enums to classes.
add statics for RRClass::IN() (RRClass.IN()) etc.
(and replace RRClass("IN") in tests with those)
same for RRType? (xfrout.py.in line 256)
creating a render message and not setting opcode/rcode results in a segfault later (nullpointer)
(is this cpp or python problem?)
The function set wrapped is not complete; for instance, in
MessageRenderer, we really only provide the high-level readout
functions. Do we need access to the writers? (there is one set() right
now).
segfault when comparing with bad type like int (at least for Name and Rcode, but probably for the rest too)
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <Python.h>
#include <cassert>
#include <dns/edns.h>
#include <dns/exceptions.h>
#include <dns/messagerenderer.h>
#include <util/python/pycppwrapper_util.h>
#include "edns_python.h"
#include "name_python.h"
#include "rrclass_python.h"
#include "rrtype_python.h"
#include "rrttl_python.h"
#include "rdata_python.h"
#include "messagerenderer_python.h"
#include "pydnspp_common.h"
using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::dns::python;
using namespace isc::util;
using namespace isc::util::python;
namespace {
class s_EDNS : public PyObject {
public:
EDNS* cppobj;
};
typedef CPPPyObjectContainer<s_EDNS, EDNS> EDNSContainer;
// General creation and destruction
int EDNS_init(s_EDNS* self, PyObject* args);
void EDNS_destroy(s_EDNS* self);
// These are the functions we export
PyObject* EDNS_toText(const s_EDNS* self);
// This is a second version of toText, we need one where the argument
// is a PyObject*, for the str() function in python.
PyObject* EDNS_str(PyObject* self);
PyObject* EDNS_toWire(const s_EDNS* self, PyObject* args);
PyObject* EDNS_getVersion(const s_EDNS* self);
PyObject* EDNS_getDNSSECAwareness(const s_EDNS* self);
PyObject* EDNS_setDNSSECAwareness(s_EDNS* self, PyObject* args);
PyObject* EDNS_getUDPSize(const s_EDNS* self);
PyObject* EDNS_setUDPSize(s_EDNS* self, PyObject* args);
PyObject* EDNS_createFromRR(const s_EDNS* null_self, PyObject* args);
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
// 2. Our static function here
// 3. Argument type
// 4. Documentation
PyMethodDef EDNS_methods[] = {
{ "to_text", reinterpret_cast<PyCFunction>(EDNS_toText), METH_NOARGS,
"Returns the string representation" },
{ "to_wire", reinterpret_cast<PyCFunction>(EDNS_toWire), METH_VARARGS,
"Converts the EDNS object to wire format.\n"
"The argument can be either a MessageRenderer or an object that "
"implements the sequence interface. If the object is mutable "
"(for instance a bytearray()), the wire data is added in-place.\n"
"If it is not (for instance a bytes() object), a new object is "
"returned" },
{ "get_version",
reinterpret_cast<PyCFunction>(EDNS_getVersion), METH_NOARGS,
"Returns the version of EDNS." },
{ "get_dnssec_awareness",
reinterpret_cast<PyCFunction>(EDNS_getDNSSECAwareness), METH_NOARGS,
"Returns whether the message sender is DNSSEC aware." },
{ "set_dnssec_awareness",
reinterpret_cast<PyCFunction>(EDNS_setDNSSECAwareness), METH_VARARGS,
"Specifies whether the sender of the message containing this "
"EDNS is DNSSEC aware." },
{ "get_udp_size",
reinterpret_cast<PyCFunction>(EDNS_getUDPSize), METH_NOARGS,
"Return the maximum buffer size of UDP messages for the sender "
"of the message." },
{ "set_udp_size",
reinterpret_cast<PyCFunction>(EDNS_setUDPSize), METH_VARARGS,
"Specify the maximum buffer size of UDP messages that use this EDNS." },
{ "create_from_rr",
reinterpret_cast<PyCFunction>(EDNS_createFromRR),
METH_VARARGS | METH_STATIC,
"Create a new EDNS object from a set of RR parameters, also providing "
"the extended RCODE value." },
{ NULL, NULL, 0, NULL }
};
EDNS*
createFromRR(const Name& name, const RRClass& rrclass, const RRType& rrtype,
const RRTTL& rrttl, const Rdata& rdata, uint8_t& extended_rcode)
{
try {
return (createEDNSFromRR(name, rrclass, rrtype, rrttl, rdata,
extended_rcode));
} catch (const isc::InvalidParameter& ex) {
PyErr_SetString(po_InvalidParameter, ex.what());
} catch (const DNSMessageFORMERR& ex) {
PyErr_SetString(po_DNSMessageFORMERR, ex.what());
} catch (const DNSMessageBADVERS& ex) {
PyErr_SetString(po_DNSMessageBADVERS, ex.what());
} catch (...) {
PyErr_SetString(po_IscException, "Unexpected exception");
}
return (NULL);
}
int
EDNS_init(s_EDNS* self, PyObject* args) {
uint8_t version = EDNS::SUPPORTED_VERSION;
const PyObject* name;
const PyObject* rrclass;
const PyObject* rrtype;
const PyObject* rrttl;
const PyObject* rdata;
if (PyArg_ParseTuple(args, "|b", &version)) {
try {
self->cppobj = new EDNS(version);
} catch (const isc::InvalidParameter& ex) {
PyErr_SetString(po_InvalidParameter, ex.what());
return (-1);
} catch (...) {
PyErr_SetString(po_IscException, "Unexpected exception");
return (-1);
}
return (0);
} else if (PyArg_ParseTuple(args, "O!O!O!O!O!", &name_type, &name,
&rrclass_type, &rrclass, &rrtype_type, &rrtype,
&rrttl_type, &rrttl, &rdata_type, &rdata)) {
// We use createFromRR() even if we don't need to know extended_rcode
// in this context so that we can share the try-catch logic with
// EDNS_createFromRR() (see below).
uint8_t extended_rcode;
self->cppobj = createFromRR(PyName_ToName(name),
PyRRClass_ToRRClass(rrclass),
PyRRType_ToRRType(rrtype),
PyRRTTL_ToRRTTL(rrttl),
PyRdata_ToRdata(rdata), extended_rcode);
return (self->cppobj != NULL ? 0 : -1);
}
PyErr_Clear();
PyErr_SetString(PyExc_TypeError, "Invalid arguments to EDNS constructor");
return (-1);
}
void
EDNS_destroy(s_EDNS* const self) {
delete self->cppobj;
self->cppobj = NULL;
Py_TYPE(self)->tp_free(self);
}
PyObject*
EDNS_toText(const s_EDNS* const self) {
// Py_BuildValue makes python objects from native data
return (Py_BuildValue("s", self->cppobj->toText().c_str()));
}
PyObject*
EDNS_str(PyObject* self) {
// Simply call the to_text method we already defined
return (PyObject_CallMethod(self,
const_cast<char*>("to_text"),
const_cast<char*>("")));
}
PyObject*
EDNS_toWire(const s_EDNS* const self, PyObject* args) {
PyObject* bytes;
uint8_t extended_rcode;
PyObject* renderer;
if (PyArg_ParseTuple(args, "Ob", &bytes, &extended_rcode) &&
PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
OutputBuffer buffer(0);
self->cppobj->toWire(buffer, extended_rcode);
PyObject* rd_bytes = PyBytes_FromStringAndSize(
static_cast<const char*>(buffer.getData()), buffer.getLength());
PyObject* result = PySequence_InPlaceConcat(bytes_o, rd_bytes);
// We need to release the object we temporarily created here
// to prevent memory leak
Py_DECREF(rd_bytes);
return (result);
} else if (PyArg_ParseTuple(args, "O!b", &messagerenderer_type,
&renderer, &extended_rcode)) {
const unsigned int n = self->cppobj->toWire(
PyMessageRenderer_ToMessageRenderer(renderer), extended_rcode);
return (Py_BuildValue("I", n));
}
PyErr_Clear();
PyErr_SetString(PyExc_TypeError, "Incorrect arguments for EDNS.to_wire()");
return (NULL);
}
PyObject*
EDNS_getVersion(const s_EDNS* const self) {
return (Py_BuildValue("B", self->cppobj->getVersion()));
}
PyObject*
EDNS_getDNSSECAwareness(const s_EDNS* const self) {
if (self->cppobj->getDNSSECAwareness()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
PyObject*
EDNS_setDNSSECAwareness(s_EDNS* self, PyObject* args) {
const PyObject *b;
if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &b)) {
return (NULL);
}
self->cppobj->setDNSSECAwareness(b == Py_True);
Py_RETURN_NONE;
}
PyObject*
EDNS_getUDPSize(const s_EDNS* const self) {
return (Py_BuildValue("I", self->cppobj->getUDPSize()));
}
PyObject*
EDNS_setUDPSize(s_EDNS* self, PyObject* args) {
long size;
if (!PyArg_ParseTuple(args, "l", &size)) {
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
"No valid type in set_udp_size argument");
return (NULL);
}
if (size < 0 || size > 0xffff) {
PyErr_SetString(PyExc_ValueError,
"UDP size is not an unsigned 16-bit integer");
return (NULL);
}
self->cppobj->setUDPSize(size);
Py_RETURN_NONE;
}
PyObject*
EDNS_createFromRR(const s_EDNS* null_self, PyObject* args) {
const PyObject* name;
const PyObject* rrclass;
const PyObject* rrtype;
const PyObject* rrttl;
const PyObject* rdata;
assert(null_self == NULL);
if (PyArg_ParseTuple(args, "O!O!O!O!O!", &name_type, &name,
&rrclass_type, &rrclass, &rrtype_type, &rrtype,
&rrttl_type, &rrttl, &rdata_type, &rdata)) {
uint8_t extended_rcode;
s_EDNS* edns_obj = PyObject_New(s_EDNS, &edns_type);
if (edns_obj == NULL) {
return (NULL);
}
edns_obj->cppobj = createFromRR(PyName_ToName(name),
PyRRClass_ToRRClass(rrclass),
PyRRType_ToRRType(rrtype),
PyRRTTL_ToRRTTL(rrttl),
PyRdata_ToRdata(rdata),
extended_rcode);
if (edns_obj->cppobj != NULL) {
PyObject* extrcode_obj = Py_BuildValue("B", extended_rcode);
return (Py_BuildValue("OO", edns_obj, extrcode_obj));
}
Py_DECREF(edns_obj);
return (NULL);
}
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
"Incorrect arguments for EDNS.create_from_rr()");
return (NULL);
}
} // end of anonymous namespace
namespace isc {
namespace dns {
namespace python {
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_EDNS
// Most of the functions are not actually implemented and NULL here.
PyTypeObject edns_type = {
PyVarObject_HEAD_INIT(NULL, 0)
"pydnspp.EDNS",
sizeof(s_EDNS), // tp_basicsize
0, // tp_itemsize
(destructor)EDNS_destroy, // 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
EDNS_str, // tp_str
NULL, // tp_getattro
NULL, // tp_setattro
NULL, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
"The EDNS class encapsulates DNS extensions "
"provided by the EDNSx protocol.",
NULL, // tp_traverse
NULL, // tp_clear
NULL, // tp_richcompare
0, // tp_weaklistoffset
NULL, // tp_iter
NULL, // tp_iternext
EDNS_methods, // 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
(initproc)EDNS_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
};
PyObject*
createEDNSObject(const EDNS& source) {
EDNSContainer container(PyObject_New(s_EDNS, &edns_type));
container.set(new EDNS(source));
return (container.release());
}
bool
PyEDNS_Check(PyObject* obj) {
if (obj == NULL) {
isc_throw(PyCPPWrapperException, "obj argument NULL in typecheck");
}
return (PyObject_TypeCheck(obj, &edns_type));
}
const EDNS&
PyEDNS_ToEDNS(const PyObject* edns_obj) {
if (edns_obj == NULL) {
isc_throw(PyCPPWrapperException,
"obj argument NULL in EDNS PyObject conversion");
}