mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-04 07:55:18 +00:00
[2437] supported python wrapper of checkZone().
This commit is contained in:
@@ -27,6 +27,7 @@ libb10_pydnspp_la_SOURCES += edns_python.cc edns_python.h
|
|||||||
libb10_pydnspp_la_SOURCES += message_python.cc message_python.h
|
libb10_pydnspp_la_SOURCES += message_python.cc message_python.h
|
||||||
libb10_pydnspp_la_SOURCES += rrset_collection_python.cc
|
libb10_pydnspp_la_SOURCES += rrset_collection_python.cc
|
||||||
libb10_pydnspp_la_SOURCES += rrset_collection_python.h
|
libb10_pydnspp_la_SOURCES += rrset_collection_python.h
|
||||||
|
libb10_pydnspp_la_SOURCES += zone_checker_python.cc zone_checker_python.h
|
||||||
|
|
||||||
libb10_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
|
libb10_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
|
||||||
libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
|
libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
|
||||||
|
@@ -57,6 +57,9 @@
|
|||||||
#include "tsig_python.h"
|
#include "tsig_python.h"
|
||||||
#include "tsig_rdata_python.h"
|
#include "tsig_rdata_python.h"
|
||||||
#include "tsigrecord_python.h"
|
#include "tsigrecord_python.h"
|
||||||
|
#include "zone_checker_python.h"
|
||||||
|
|
||||||
|
#include "zone_checker_python_inc.cc"
|
||||||
|
|
||||||
using namespace isc::dns;
|
using namespace isc::dns;
|
||||||
using namespace isc::dns::python;
|
using namespace isc::dns::python;
|
||||||
@@ -729,6 +732,11 @@ initModulePart_TSIGRecord(PyObject* mod) {
|
|||||||
return (true);
|
return (true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyMethodDef methods[] = {
|
||||||
|
{ "check_zone", internal::pyCheckZone, METH_VARARGS, dns_checkZone_doc },
|
||||||
|
{ NULL, NULL, 0, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
PyModuleDef pydnspp = {
|
PyModuleDef pydnspp = {
|
||||||
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
|
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
|
||||||
"pydnspp",
|
"pydnspp",
|
||||||
@@ -738,13 +746,13 @@ PyModuleDef pydnspp = {
|
|||||||
"and OutputBuffer for instance), and others may be necessary, but "
|
"and OutputBuffer for instance), and others may be necessary, but "
|
||||||
"were not up to now.",
|
"were not up to now.",
|
||||||
-1,
|
-1,
|
||||||
NULL,
|
methods,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
}
|
} // unnamed namespace
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit_pydnspp(void) {
|
PyInit_pydnspp(void) {
|
||||||
|
@@ -19,6 +19,7 @@ PYTESTS += tsig_rdata_python_test.py
|
|||||||
PYTESTS += tsigerror_python_test.py
|
PYTESTS += tsigerror_python_test.py
|
||||||
PYTESTS += tsigkey_python_test.py
|
PYTESTS += tsigkey_python_test.py
|
||||||
PYTESTS += tsigrecord_python_test.py
|
PYTESTS += tsigrecord_python_test.py
|
||||||
|
PYTESTS += zone_checker_python_test.py
|
||||||
|
|
||||||
EXTRA_DIST = $(PYTESTS)
|
EXTRA_DIST = $(PYTESTS)
|
||||||
EXTRA_DIST += testutil.py
|
EXTRA_DIST += testutil.py
|
||||||
|
178
src/lib/dns/python/tests/zone_checker_python_test.py
Normal file
178
src/lib/dns/python/tests/zone_checker_python_test.py
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Copyright (C) 2013 Internet Systems Consortium.
|
||||||
|
#
|
||||||
|
# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
|
||||||
|
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
|
||||||
|
# INTERNET SYSTEMS CONSORTIUM 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.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import sys
|
||||||
|
from pydnspp import *
|
||||||
|
|
||||||
|
# A separate exception class raised from some tests to see if it's propagated.
|
||||||
|
class FakeException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ZoneCheckerTest(unittest.TestCase):
|
||||||
|
def __callback(self, reason, reasons):
|
||||||
|
# Issue callback for check_zone(). It simply records the given reason
|
||||||
|
# string in the given list.
|
||||||
|
reasons.append(reason)
|
||||||
|
|
||||||
|
def test_check(self):
|
||||||
|
errors = []
|
||||||
|
warns = []
|
||||||
|
|
||||||
|
# A successful case with no warning.
|
||||||
|
rrsets = RRsetCollection(b'example.org. 0 SOA . . 0 0 0 0 0\n' +
|
||||||
|
b'example.org. 0 NS ns.example.org.\n' +
|
||||||
|
b'ns.example.org. 0 A 192.0.2.1\n',
|
||||||
|
Name('example.org'), RRClass.IN())
|
||||||
|
self.assertTrue(check_zone(Name('example.org'), RRClass.IN(),
|
||||||
|
rrsets,
|
||||||
|
(lambda r: self.__callback(r, errors),
|
||||||
|
lambda r: self.__callback(r, warns))))
|
||||||
|
self.assertEqual([], errors)
|
||||||
|
self.assertEqual([], warns)
|
||||||
|
|
||||||
|
# Check fails and one additional warning.
|
||||||
|
rrsets = RRsetCollection(b'example.org. 0 NS ns.example.org.',
|
||||||
|
Name('example.org'), RRClass.IN())
|
||||||
|
self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
|
||||||
|
(lambda r: self.__callback(r, errors),
|
||||||
|
lambda r: self.__callback(r, warns))))
|
||||||
|
self.assertEqual(['zone example.org/IN: has 0 SOA records'], errors)
|
||||||
|
self.assertEqual(['zone example.org/IN: NS has no address records ' +
|
||||||
|
'(A or AAAA)'], warns)
|
||||||
|
|
||||||
|
# Same RRset collection, suppressing callbacks
|
||||||
|
errors = []
|
||||||
|
warns = []
|
||||||
|
self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
|
||||||
|
(None, None)))
|
||||||
|
self.assertEqual([], errors)
|
||||||
|
self.assertEqual([], warns)
|
||||||
|
|
||||||
|
def test_check_badarg(self):
|
||||||
|
rrsets = RRsetCollection()
|
||||||
|
# Bad types
|
||||||
|
self.assertRaises(TypeError, check_zone, 1, RRClass.IN(), rrsets,
|
||||||
|
(None, None))
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), 1, rrsets,
|
||||||
|
(None, None))
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
1, (None, None))
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
rrsets, 1)
|
||||||
|
|
||||||
|
# Bad callbacks
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
rrsets, (None, None, None))
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
rrsets, (1, None))
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
rrsets, (None, 1))
|
||||||
|
|
||||||
|
# Extra/missing args
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
rrsets, (None, None), 1)
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
rrsets)
|
||||||
|
check_zone(Name('example'), RRClass.IN(), rrsets, (None, None))
|
||||||
|
|
||||||
|
def test_check_callback_fail(self):
|
||||||
|
# Let the call raise a Python exception. It should be propagated to
|
||||||
|
# the top level.
|
||||||
|
def __bad_callback(reason):
|
||||||
|
raise FakeException('error in callback')
|
||||||
|
|
||||||
|
# Using an empty collection, triggering an error callback.
|
||||||
|
self.assertRaises(FakeException, check_zone, Name('example.org'),
|
||||||
|
RRClass.IN(), RRsetCollection(),
|
||||||
|
(__bad_callback, None))
|
||||||
|
|
||||||
|
# An unusual case: the callback is expected to return None, but if it
|
||||||
|
# returns an actual object it shouldn't cause leak inside the callback.
|
||||||
|
class RefChecker:
|
||||||
|
pass
|
||||||
|
def __callback(reason, checker):
|
||||||
|
return checker
|
||||||
|
|
||||||
|
ref_checker = RefChecker()
|
||||||
|
orig_refcnt = sys.getrefcount(ref_checker)
|
||||||
|
check_zone(Name('example.org'), RRClass.IN(), RRsetCollection(),
|
||||||
|
(lambda r: __callback(r, ref_checker), None))
|
||||||
|
self.assertEqual(orig_refcnt, sys.getrefcount(ref_checker))
|
||||||
|
|
||||||
|
def test_check_custom_collection(self):
|
||||||
|
# Test if check_zone() works with pure-Python RRsetCollection.
|
||||||
|
|
||||||
|
class FakeRRsetCollection(RRsetCollectionBase):
|
||||||
|
# This is the Python-only collection class. Its find() makes
|
||||||
|
# the check pass by default, by returning hardcoded RRsets.
|
||||||
|
# If raise_on_find is set to True, find() raises an exception.
|
||||||
|
# If find_result is set to something other than False, find()
|
||||||
|
# returns that specified value.
|
||||||
|
|
||||||
|
def __init__(self, raise_on_find=False, find_result=False):
|
||||||
|
self.__raise_on_find = raise_on_find
|
||||||
|
self.__find_result = find_result
|
||||||
|
|
||||||
|
def find(self, name, rrclass, rrtype):
|
||||||
|
if self.__raise_on_find:
|
||||||
|
raise FakeException('find error')
|
||||||
|
if self.__find_result is not False:
|
||||||
|
return self.__find_result
|
||||||
|
if rrtype == RRType.SOA():
|
||||||
|
soa = RRset(Name('example'), RRClass.IN(), rrtype,
|
||||||
|
RRTTL(0))
|
||||||
|
soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
|
||||||
|
'. . 0 0 0 0 0'))
|
||||||
|
return soa
|
||||||
|
if rrtype == RRType.NS():
|
||||||
|
ns = RRset(Name('example'), RRClass.IN(), rrtype,
|
||||||
|
RRTTL(0))
|
||||||
|
ns.add_rdata(Rdata(RRType.NS(), RRClass.IN(),
|
||||||
|
'example.org'))
|
||||||
|
return ns
|
||||||
|
return None
|
||||||
|
|
||||||
|
# A successful case. Just checking it works in that case.
|
||||||
|
rrsets = FakeRRsetCollection()
|
||||||
|
self.assertTrue(check_zone(Name('example'), RRClass.IN(), rrsets,
|
||||||
|
(None, None)))
|
||||||
|
|
||||||
|
# Likewise, normal case but zone check fails.
|
||||||
|
rrsets = FakeRRsetCollection(False, None)
|
||||||
|
self.assertFalse(check_zone(Name('example'), RRClass.IN(), rrsets,
|
||||||
|
(None, None)))
|
||||||
|
|
||||||
|
# Our find() returns a bad type of result.
|
||||||
|
rrsets = FakeRRsetCollection(False, 1)
|
||||||
|
self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
|
||||||
|
rrsets, (None, None))
|
||||||
|
|
||||||
|
# Our find() returns an empty SOA RRset. C++ zone checker code
|
||||||
|
# throws, which results in IscException.
|
||||||
|
rrsets = FakeRRsetCollection(False, RRset(Name('example'),
|
||||||
|
RRClass.IN(),
|
||||||
|
RRType.SOA(), RRTTL(0)))
|
||||||
|
self.assertRaises(IscException, check_zone, Name('example'),
|
||||||
|
RRClass.IN(), rrsets, (None, None))
|
||||||
|
|
||||||
|
# Our find() raises an exception. That exception is propagated to
|
||||||
|
# the top level.
|
||||||
|
rrsets = FakeRRsetCollection(True)
|
||||||
|
self.assertRaises(FakeException, check_zone, Name('example'),
|
||||||
|
RRClass.IN(), rrsets, (None, None))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
224
src/lib/dns/python/zone_checker_python.cc
Normal file
224
src/lib/dns/python/zone_checker_python.cc
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
// Copyright (C) 2013 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.
|
||||||
|
|
||||||
|
// Enable this if you use s# variants with PyArg_ParseTuple(), see
|
||||||
|
// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
|
||||||
|
#define PY_SSIZE_T_CLEAN
|
||||||
|
|
||||||
|
// Python.h needs to be placed at the head of the program file, see:
|
||||||
|
// http://docs.python.org/py3k/extending/extending.html#a-simple-example
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
#include <util/python/pycppwrapper_util.h>
|
||||||
|
|
||||||
|
#include <dns/python/name_python.h>
|
||||||
|
#include <dns/python/rrclass_python.h>
|
||||||
|
#include <dns/python/rrtype_python.h>
|
||||||
|
#include <dns/python/rrset_python.h>
|
||||||
|
#include <dns/python/rrset_collection_python.h>
|
||||||
|
#include <dns/python/zone_checker_python.h>
|
||||||
|
#include <dns/python/pydnspp_common.h>
|
||||||
|
|
||||||
|
#include <exceptions/exceptions.h>
|
||||||
|
|
||||||
|
#include <dns/name.h>
|
||||||
|
#include <dns/rrclass.h>
|
||||||
|
#include <dns/rrtype.h>
|
||||||
|
#include <dns/rrset.h>
|
||||||
|
#include <dns/rrset_collection_base.h>
|
||||||
|
#include <dns/zone_checker.h>
|
||||||
|
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using isc::util::python::PyObjectContainer;
|
||||||
|
using namespace isc::dns;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// This is a template for a common pattern of type mismatch error handling,
|
||||||
|
// provided to save typing and repeating the mostly identical patterns.
|
||||||
|
PyObject*
|
||||||
|
setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
|
||||||
|
PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
|
||||||
|
var_name, type_name, pobj->ob_type->tp_name);
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace dns {
|
||||||
|
namespace python {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// This is used to abort check_zone() and go back to the top level.
|
||||||
|
// We use a separate exception so it won't be caught in the middle.
|
||||||
|
class InternalException : public std::exception {
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is a "wrapper" RRsetCollection subclass. It's constructed with
|
||||||
|
// a Python RRsetCollection object, and its find() calls the Python version
|
||||||
|
// of RRsetCollection.find(). This way, the check_zone() wrapper will work
|
||||||
|
// for pure-Python RRsetCollection classes, too.
|
||||||
|
class PyRRsetCollection : public RRsetCollectionBase {
|
||||||
|
public:
|
||||||
|
PyRRsetCollection(PyObject* po_rrsets) : po_rrsets_(po_rrsets) {}
|
||||||
|
|
||||||
|
virtual ConstRRsetPtr find(const Name& name, const RRClass& rrclass,
|
||||||
|
const RRType& rrtype) const {
|
||||||
|
try {
|
||||||
|
// Convert C++ args to Python objects, and builds argument tuple
|
||||||
|
// to the Python method. This should basically succeed.
|
||||||
|
PyObjectContainer poc_name(createNameObject(name));
|
||||||
|
PyObjectContainer poc_rrclass(createRRClassObject(rrclass));
|
||||||
|
PyObjectContainer poc_rrtype(createRRTypeObject(rrtype));
|
||||||
|
PyObjectContainer poc_args(Py_BuildValue("(OOOO)",
|
||||||
|
po_rrsets_,
|
||||||
|
poc_name.get(),
|
||||||
|
poc_rrclass.get(),
|
||||||
|
poc_rrtype.get()));
|
||||||
|
|
||||||
|
// Call the Python method.
|
||||||
|
// PyObject_CallMethod is dirty and requires mutable C-string for
|
||||||
|
// method name and arguments. While it's unlikely for these to
|
||||||
|
// be modified, we err on the side of caution and make copies.
|
||||||
|
char method_name[sizeof("find")];
|
||||||
|
char method_args[sizeof("(OOO)")];
|
||||||
|
std::strcpy(method_name, "find");
|
||||||
|
std::strcpy(method_args, "(OOO)");
|
||||||
|
PyObjectContainer poc_result(
|
||||||
|
PyObject_CallMethod(po_rrsets_, method_name, method_args,
|
||||||
|
poc_name.get(), poc_rrclass.get(),
|
||||||
|
poc_rrtype.get()));
|
||||||
|
PyObject* const po_result = poc_result.get();
|
||||||
|
if (po_result == Py_None) {
|
||||||
|
return (ConstRRsetPtr());
|
||||||
|
} else if (PyRRset_Check(po_result)) {
|
||||||
|
return (PyRRset_ToRRsetPtr(po_result));
|
||||||
|
} else {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "invalid type for "
|
||||||
|
"RRsetCollection.find(): must be None "
|
||||||
|
"or RRset");
|
||||||
|
throw InternalException();
|
||||||
|
}
|
||||||
|
} catch (const isc::util::python::PyCPPWrapperException& ex) {
|
||||||
|
// This normally means the method call fails. Propagate the
|
||||||
|
// already-set Python error to the top level. Other C++ exceptions
|
||||||
|
// are really unexpected, so we also (implicitly) propagate it
|
||||||
|
// to the top level and recognize it as "unexpected failure".
|
||||||
|
throw InternalException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual IterPtr getBeginning() {
|
||||||
|
isc_throw(NotImplemented, "iterator support is not yet available");
|
||||||
|
}
|
||||||
|
virtual IterPtr getEnd() {
|
||||||
|
isc_throw(NotImplemented, "iterator support is not yet available");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PyObject* const po_rrsets_;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
callback(const string& reason, PyObject* obj) {
|
||||||
|
PyObjectContainer poc_args(Py_BuildValue("(s#)", reason.c_str(),
|
||||||
|
reason.size()));
|
||||||
|
PyObject* po_result = PyObject_CallObject(obj, poc_args.get());
|
||||||
|
if (po_result == NULL) {
|
||||||
|
throw InternalException();
|
||||||
|
}
|
||||||
|
Py_DECREF(po_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZoneCheckerCallbacks::IssueCallback
|
||||||
|
PyCallable_ToCallback(PyObject* obj) {
|
||||||
|
if (obj == Py_None) {
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
return (boost::bind(callback, _1, obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject*
|
||||||
|
pyCheckZone(PyObject*, PyObject* args) {
|
||||||
|
try {
|
||||||
|
PyObject* po_name;
|
||||||
|
PyObject* po_rrclass;
|
||||||
|
PyObject* po_rrsets;
|
||||||
|
PyObject* po_error;
|
||||||
|
PyObject* po_warn;
|
||||||
|
|
||||||
|
if (PyArg_ParseTuple(args, "OOO(OO)", &po_name, &po_rrclass,
|
||||||
|
&po_rrsets, &po_error, &po_warn)) {
|
||||||
|
if (!PyName_Check(po_name)) {
|
||||||
|
return (setTypeError(po_name, "zone_name", "Name"));
|
||||||
|
}
|
||||||
|
if (!PyRRClass_Check(po_rrclass)) {
|
||||||
|
return (setTypeError(po_rrclass, "zone_rrclass", "RRClass"));
|
||||||
|
}
|
||||||
|
if (!PyObject_TypeCheck(po_rrsets, &rrset_collection_base_type)) {
|
||||||
|
return (setTypeError(po_rrsets, "zone_rrsets",
|
||||||
|
"RRsetCollectionBase"));
|
||||||
|
}
|
||||||
|
if (po_error != Py_None && PyCallable_Check(po_error) == 0) {
|
||||||
|
return (setTypeError(po_error, "error", "callable or None"));
|
||||||
|
}
|
||||||
|
if (po_warn != Py_None && PyCallable_Check(po_warn) == 0) {
|
||||||
|
return (setTypeError(po_warn, "warn", "callable or None"));
|
||||||
|
}
|
||||||
|
|
||||||
|
PyRRsetCollection py_rrsets(po_rrsets);
|
||||||
|
if (checkZone(PyName_ToName(po_name),
|
||||||
|
PyRRClass_ToRRClass(po_rrclass), py_rrsets,
|
||||||
|
ZoneCheckerCallbacks(
|
||||||
|
PyCallable_ToCallback(po_error),
|
||||||
|
PyCallable_ToCallback(po_warn)))) {
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
} else {
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const InternalException& ex) {
|
||||||
|
// Normally, error string should have been set already. For some
|
||||||
|
// rare cases such as memory allocation failure, we set the last-resort
|
||||||
|
// error string.
|
||||||
|
if (PyErr_Occurred() == NULL) {
|
||||||
|
PyErr_SetString(PyExc_SystemError,
|
||||||
|
"Unexpected failure in check_zone()");
|
||||||
|
}
|
||||||
|
return (NULL);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
const string ex_what = "Unexpected failure in check_zone(): " +
|
||||||
|
string(ex.what());
|
||||||
|
PyErr_SetString(po_IscException, ex_what.c_str());
|
||||||
|
return (NULL);
|
||||||
|
} catch (...) {
|
||||||
|
PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace internal
|
||||||
|
} // namespace python
|
||||||
|
} // namespace dns
|
||||||
|
} // namespace isc
|
35
src/lib/dns/python/zone_checker_python.h
Normal file
35
src/lib/dns/python/zone_checker_python.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (C) 2013 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.
|
||||||
|
|
||||||
|
#ifndef PYTHON_ZONE_CHECKER_H
|
||||||
|
#define PYTHON_ZONE_CHECKER_H 1
|
||||||
|
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
namespace isc {
|
||||||
|
namespace dns {
|
||||||
|
namespace python {
|
||||||
|
namespace internal {
|
||||||
|
|
||||||
|
PyObject* pyCheckZone(PyObject* self, PyObject* args);
|
||||||
|
|
||||||
|
} // namespace python
|
||||||
|
} // namespace python
|
||||||
|
} // namespace dns
|
||||||
|
} // namespace isc
|
||||||
|
#endif // PYTHON_ZONE_CHECKER_H
|
||||||
|
|
||||||
|
// Local Variables:
|
||||||
|
// mode: c++
|
||||||
|
// End:
|
79
src/lib/dns/python/zone_checker_python_inc.cc
Normal file
79
src/lib/dns/python/zone_checker_python_inc.cc
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
namespace {
|
||||||
|
// Modifications
|
||||||
|
// - callbacks => (error, warn)
|
||||||
|
// - recover paragraph before itemization (it's a bug of convert script)
|
||||||
|
// - correct broken format for nested items (another bug of script)
|
||||||
|
// - true/false => True/False
|
||||||
|
// - removed Exception section (for simplicity)
|
||||||
|
const char* const dns_checkZone_doc = "\
|
||||||
|
check_zone(zone_name, zone_class, zone_rrsets, (error, warn)) -> bool\n\
|
||||||
|
\n\
|
||||||
|
Perform basic integrity checks on zone RRsets.\n\
|
||||||
|
\n\
|
||||||
|
This function performs some lightweight checks on zone's SOA and\n\
|
||||||
|
(apex) NS records. Here, lightweight means it doesn't require\n\
|
||||||
|
traversing the entire zone, and should be expected to complete\n\
|
||||||
|
reasonably quickly regardless of the size of the zone.\n\
|
||||||
|
\n\
|
||||||
|
It distinguishes \"critical\" errors and other undesirable issues: the\n\
|
||||||
|
former should be interpreted as the resulting zone shouldn't be used\n\
|
||||||
|
further, e.g, by an authoritative server implementation; the latter\n\
|
||||||
|
means the issues are better to be addressed but are not necessarily\n\
|
||||||
|
considered to make the zone invalid. Critical errors are reported via\n\
|
||||||
|
the error() function, and non critical issues are reported via warn().\n\
|
||||||
|
\n\
|
||||||
|
Specific checks performed by this function is as follows. Failure of\n\
|
||||||
|
a check is considered a critical error unless noted otherwise:\n\
|
||||||
|
\n\
|
||||||
|
- There is exactly one SOA RR at the zone apex.\n\
|
||||||
|
- There is at least one NS RR at the zone apex.\n\
|
||||||
|
- For each apex NS record, if the NS name (the RDATA of the record) is\n\
|
||||||
|
in the zone (i.e., it's a subdomain of the zone origin and above any\n\
|
||||||
|
zone cut due to delegation), check the following:\n\
|
||||||
|
- the NS name should have an address record (AAAA or A). Failure of\n\
|
||||||
|
this check is considered a non critical issue.\n\
|
||||||
|
- the NS name does not have a CNAME. This is prohibited by Section\n\
|
||||||
|
10.3 of RFC 2181.\n\
|
||||||
|
- the NS name is not subject to DNAME substitution. This is prohibited\n\
|
||||||
|
by Section 4 of RFC 6672.\n\
|
||||||
|
\n\
|
||||||
|
In addition, when the check is completed without any\n\
|
||||||
|
critical error, this function guarantees that RRsets for the SOA and\n\
|
||||||
|
(apex) NS stored in the passed RRset collection have the expected\n\
|
||||||
|
type of Rdata objects, i.e., generic.SOA and generic.NS,\n\
|
||||||
|
respectively. (This is normally expected to be the case, but not\n\
|
||||||
|
guaranteed by the API).\n\
|
||||||
|
\n\
|
||||||
|
As for the check on the existence of AAAA or A records for NS names,\n\
|
||||||
|
it should be noted that BIND 9 treats this as a critical error. It's\n\
|
||||||
|
not clear whether it's an implementation dependent behavior or based\n\
|
||||||
|
on the protocol standard (it looks like the former), but to make it\n\
|
||||||
|
sure we need to confirm there is even no wildcard match for the names.\n\
|
||||||
|
This should be a very rare configuration, and more expensive to\n\
|
||||||
|
detect, so we do not check this condition, and treat this case as a\n\
|
||||||
|
non critical issue.\n\
|
||||||
|
\n\
|
||||||
|
This function indicates the result of the checks (whether there is a\n\
|
||||||
|
critical error) via the return value: It returns True if there is no\n\
|
||||||
|
critical error and returns False otherwise. It doesn't throw an\n\
|
||||||
|
exception on encountering an error so that it can report as many\n\
|
||||||
|
errors as possible in a single call. If an exception is a better way\n\
|
||||||
|
to signal the error, the caller can pass a callable object as error()\n\
|
||||||
|
that throws.\n\
|
||||||
|
\n\
|
||||||
|
This function can still throw an exception if it finds a really bogus\n\
|
||||||
|
condition that is most likely to be an implementation bug of the\n\
|
||||||
|
caller. Such cases include when an RRset contained in the RRset\n\
|
||||||
|
collection is empty.\n\
|
||||||
|
\n\
|
||||||
|
Parameters:\n\
|
||||||
|
zone_name The name of the zone to be checked\n\
|
||||||
|
zone_class The RR class of the zone to be checked\n\
|
||||||
|
zone_rrsets The collection of RRsets of the zone\n\
|
||||||
|
error Callable object used to report errors\n\
|
||||||
|
warn Callable object used to report non-critical issues\n\
|
||||||
|
\n\
|
||||||
|
Return Value(s): True if no critical errors are found; False\n\
|
||||||
|
otherwise.\n\
|
||||||
|
";
|
||||||
|
} // unnamed namespace
|
Reference in New Issue
Block a user