mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-08-30 21:45:37 +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 += rrset_collection_python.cc
|
||||
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_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
|
||||
|
@@ -57,6 +57,9 @@
|
||||
#include "tsig_python.h"
|
||||
#include "tsig_rdata_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::python;
|
||||
@@ -729,6 +732,11 @@ initModulePart_TSIGRecord(PyObject* mod) {
|
||||
return (true);
|
||||
}
|
||||
|
||||
PyMethodDef methods[] = {
|
||||
{ "check_zone", internal::pyCheckZone, METH_VARARGS, dns_checkZone_doc },
|
||||
{ NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
PyModuleDef pydnspp = {
|
||||
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
|
||||
"pydnspp",
|
||||
@@ -738,13 +746,13 @@ PyModuleDef pydnspp = {
|
||||
"and OutputBuffer for instance), and others may be necessary, but "
|
||||
"were not up to now.",
|
||||
-1,
|
||||
NULL,
|
||||
methods,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
};
|
||||
}
|
||||
} // unnamed namespace
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit_pydnspp(void) {
|
||||
|
@@ -19,6 +19,7 @@ PYTESTS += tsig_rdata_python_test.py
|
||||
PYTESTS += tsigerror_python_test.py
|
||||
PYTESTS += tsigkey_python_test.py
|
||||
PYTESTS += tsigrecord_python_test.py
|
||||
PYTESTS += zone_checker_python_test.py
|
||||
|
||||
EXTRA_DIST = $(PYTESTS)
|
||||
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