BIND 10 trac929, updated. 5d337b755cd45d7b0caa5364115fe17874a9bcb0 [trac929] add unit tests of the methods using some dummy spec files with the dummy data of statistics specification - getStatisticsSpec() - validateStatistics() - validateStatistics() - check_format()
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Jul 22 12:42:58 UTC 2011
The branch, trac929 has been updated
discards 9ab3a2e5c02edc14f22562dc7a4d7e9087bca735 (commit)
discards 456de3a5647fa836547b6238169aebbec933f852 (commit)
discards 343ee47dcdf0d1d48eba525189db5c0cc789d4f3 (commit)
discards 2d74f6935838788ac864de5f647734dd4aff085c (commit)
discards 9162b03e7e66c7f3763e854ef6d7c56dd576aae6 (commit)
discards 406019a4e371e1de2412bd206e299601eba545d9 (commit)
discards 1d22cafef31ee83ab4ed646e67f9cd57ab1d30ce (commit)
discards 2fc3572cc76548463f8342cd2f0d9b92b14bb732 (commit)
discards 39901e5b831ba424c8aa17d06ecba87d53fdce4c (commit)
discards 42042bbaeaa7bc1523bb23e3e6cb9f3a84a1638a (commit)
discards 26100f264a9c48aa80748256db1e94c6091ba729 (commit)
via 5d337b755cd45d7b0caa5364115fe17874a9bcb0 (commit)
via 2754d0434a4899124c5254360ccded525d62316e (commit)
via e454c03a1226fbc90d7f58229a91f3e4bc3775c3 (commit)
via dc8dba4288d537cdeaea2521376e9a424668ef27 (commit)
via 67f979971289b86b6ad19e3ab35f8179844a07af (commit)
via f1bde7e005ae6d5b73bdadcec5eac7970ea7281b (commit)
via 2f3e919448a6bd19e8c4234e480465c5a44e81f7 (commit)
via 02609c155d0bd13b339cdd1a12aa032f3f4799bc (commit)
via 88ce31058262b9fece0cab7cf4f279dc9f480954 (commit)
via 2a0159076d2c3b6f55b3e81c7e64abf31d47274d (commit)
via 7018b05373f706d54a6a0fc2d8977c44292b273f (commit)
via 902ad260c2399b597fe22bba461481a09502b9d5 (commit)
via 486bf91e0ecc5fbecfe637e1e75ebe373d42509b (commit)
via 687c2d4bdc959da433c141d920820875b701c2be (commit)
via 640a5da59304684d4fe304f2616e8dcf9f234d41 (commit)
via 10e4a07adce6af4794166cc783eca4fed188cd42 (commit)
via bb79e799885427437f01e6456c03a206886ae9ff (commit)
via 1c88cc3b00870a93c01688dd5742f5a19e0d0f76 (commit)
via ae79f5fe81a38b64a541adc67194404de5dc8cc5 (commit)
via d185c72f14dab4b4ca10dd01e6ea9b7aeb42b2df (commit)
via 50070c824270d5da1db0b716db73b726d458e9f7 (commit)
via 66ebc54e863f58b86c3ae65ca9f4764906c9a348 (commit)
via 3912ef9b24104abea0e9344ff24deeed700712e3 (commit)
via 5471e816ab36a6182b2223dea461fc8d086ed9e7 (commit)
via 686ed44b82c009ddb63ed064d46ce44fcade5fbe (commit)
via f551799e8c3de59be0a6a7c5168194b93987e876 (commit)
via bb1028fd4f52135f4a2c8175d9bf1b90043df1cc (commit)
via 7d85a63f7bd3ef5926b92dd8f7d9c1588cf6e286 (commit)
via 582348ce86ac20e0bb92079e5f15ba9b05f60a66 (commit)
via 92bf1032800f3365a5d8eb5052a2a045495ca646 (commit)
via ebc15cde7e0fa14a61127be51267a5ad0c430f90 (commit)
via df79b8d3306394ae123fb4c558f7239146e9f0d6 (commit)
via 6d784213ea929dfa06099d7d85ed87709a7f408e (commit)
via e77575c3c85c7e219137b2c616ad104e5b28eb20 (commit)
via 49f1d2d2e7f75432465ddd4acae2579c018aab33 (commit)
via ed9c17ed1627872d701c76336aff407d3ad5c44e (commit)
via b0e38303e79e2a487e37a9dcadd5f1730cdeae9e (commit)
via 93145c09728dbfb7fe5bd77b5a3671e911c41deb (commit)
via 1c1bd99f0add79535b62f6723d7e942661007653 (commit)
via 1d03e4212cffa7fcf57d0f3a4fcdc1920c959e40 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (9ab3a2e5c02edc14f22562dc7a4d7e9087bca735)
\
N -- N -- N (5d337b755cd45d7b0caa5364115fe17874a9bcb0)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 5d337b755cd45d7b0caa5364115fe17874a9bcb0
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Tue Jul 19 21:02:12 2011 +0900
[trac929]
add unit tests of the methods using some dummy spec files with the dummy data of statistics specification
- getStatisticsSpec()
- validateStatistics()
- validateStatistics()
- check_format()
add include of boost_foreach
update the year of copyright
commit 2754d0434a4899124c5254360ccded525d62316e
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Tue Jul 19 20:57:32 2011 +0900
[trac929]
add some methods for statistics specification
- getStatisticsSpec()
- validateStatistics()
- validateStatistics()
update the year of copyright
commit e454c03a1226fbc90d7f58229a91f3e4bc3775c3
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Tue Jul 19 20:55:39 2011 +0900
[trac929]
addition and modification as a variant of the statistics part of module_spec.py
- add check_format which checks whether the given element is a valid statistics specification
- modify check_data_specification to add check of statistics specification
- add getStatisticsSpec() which returns statistics specification
- add two of validateStatistics which check whether specified data is valid for statistics specification
- modify validateItem to add check of item_format in specification
update the year of copyright
commit dc8dba4288d537cdeaea2521376e9a424668ef27
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jul 8 15:47:09 2011 +0900
[trac929] add unittest of "get_statistics_spec"
commit 67f979971289b86b6ad19e3ab35f8179844a07af
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jul 8 15:46:57 2011 +0900
[trac929] add unittests for the functions:
- validate_format
- check_format
- validate_format
commit f1bde7e005ae6d5b73bdadcec5eac7970ea7281b
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jul 8 15:46:46 2011 +0900
[trac929] add "validate_statistics" which validates statistics specification in the spec file
It checks data types and data format of statistics specification
commit 2f3e919448a6bd19e8c4234e480465c5a44e81f7
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jul 8 15:46:27 2011 +0900
[trac929] add "get_statistics_spec" into cfgmgr.py
it pushes contents in statistics category of each spec file.
commit 02609c155d0bd13b339cdd1a12aa032f3f4799bc
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jul 8 15:45:28 2011 +0900
[trac929] add COMMAND_GET_STATISTICS_SPEC for "get_statistics_spec"
commit 88ce31058262b9fece0cab7cf4f279dc9f480954
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jul 8 15:45:15 2011 +0900
[trac929] add a statistics category into "spec2.spec"
and modify message string to be compared with in EXPECT_EQ
commit 2a0159076d2c3b6f55b3e81c7e64abf31d47274d
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jul 8 15:44:40 2011 +0900
[trac929] add some spec files for unittest of statistics category
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 13 +++
src/bin/auth/auth_srv.cc | 2 +-
src/bin/stats/stats_httpd.py.in | 18 ++++-
src/bin/stats/tests/b10-stats-httpd_test.py | 89 ++++++++++++++++++++
src/bin/xfrout/tests/Makefile.am | 2 +-
src/bin/xfrout/tests/xfrout_test.py.in | 118 ++++++++++++++++++++++-----
src/bin/xfrout/xfrout.py.in | 98 ++++++++++++++++-------
src/bin/xfrout/xfrout.spec.pre.in | 26 +++++--
src/bin/xfrout/xfrout_messages.mes | 11 +++
9 files changed, 316 insertions(+), 61 deletions(-)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index fc9e8b4..8f86551 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+274. [bug] naokikambe
+ add unittests for functions xml_handler, xsd_handler and xsl_handler
+ respectively to make sure their behaviors are correct, regardless of
+ whether type which xml.etree.ElementTree.tostring() after Python3.2
+ returns is str or byte.
+ (Trac #1021, git 486bf91e0ecc5fbecfe637e1e75ebe373d42509b)
+
+273. [func] vorner
+ It is possible to specify ACL for the xfrout module. It is in the ACL
+ configuration key and has the usual ACL syntax. It currently supports
+ only the source address. Default ACL accepts everything.
+ (Trac #772, git 50070c824270d5da1db0b716db73b726d458e9f7)
+
272. [func] jinmei
libdns++/pydnspp: TSIG signing now handles truncated DNS messages
(i.e. with TC bit on) with TSIG correctly.
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index f29fd05..f96e642 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -290,7 +290,7 @@ makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
message->toWire(renderer);
}
LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_ERROR_RESPONSE)
- .arg(message->toText());
+ .arg(renderer.getLength()).arg(*message);
}
}
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index ea68e7d..74298cf 100755
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -385,7 +385,14 @@ class StatsHttpd:
annotation.append(documentation)
element.append(annotation)
xsd_root.append(element)
- xsd_string = xml.etree.ElementTree.tostring(xsd_root)
+ # The coding conversion is tricky. xml..tostring() of Python 3.2
+ # returns bytes (not string) regardless of the coding, while
+ # tostring() of Python 3.1 returns a string. To support both
+ # cases transparently, we first make sure tostring() returns
+ # bytes by specifying utf-8 and then convert the result to a
+ # plain string (code below assume it).
+ xsd_string = str(xml.etree.ElementTree.tostring(xsd_root, encoding='utf-8'),
+ encoding='us-ascii')
self.xsd_body = self.open_template(XSD_TEMPLATE_LOCATION).substitute(
xsd_string=xsd_string,
xsd_namespace=XSD_NAMESPACE
@@ -410,7 +417,14 @@ class StatsHttpd:
tr.append(td1)
tr.append(td2)
xsd_root.append(tr)
- xsl_string = xml.etree.ElementTree.tostring(xsd_root)
+ # The coding conversion is tricky. xml..tostring() of Python 3.2
+ # returns bytes (not string) regardless of the coding, while
+ # tostring() of Python 3.1 returns a string. To support both
+ # cases transparently, we first make sure tostring() returns
+ # bytes by specifying utf-8 and then convert the result to a
+ # plain string (code below assume it).
+ xsl_string = str(xml.etree.ElementTree.tostring(xsd_root, encoding='utf-8'),
+ encoding='us-ascii')
self.xsl_body = self.open_template(XSL_TEMPLATE_LOCATION).substitute(
xsl_string=xsl_string,
xsd_namespace=XSD_NAMESPACE)
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index a7f83a5..6d72dc2 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -402,6 +402,95 @@ class TestStatsHttpd(unittest.TestCase):
)
self.assertEqual(ret, 1)
+ def test_xml_handler(self):
+ orig_get_stats_data = stats_httpd.StatsHttpd.get_stats_data
+ stats_httpd.StatsHttpd.get_stats_data = lambda x: {'foo':'bar'}
+ xml_body1 = stats_httpd.StatsHttpd().open_template(
+ stats_httpd.XML_TEMPLATE_LOCATION).substitute(
+ xml_string='<foo>bar</foo>',
+ xsd_namespace=stats_httpd.XSD_NAMESPACE,
+ xsd_url_path=stats_httpd.XSD_URL_PATH,
+ xsl_url_path=stats_httpd.XSL_URL_PATH)
+ xml_body2 = stats_httpd.StatsHttpd().xml_handler()
+ self.assertEqual(type(xml_body1), str)
+ self.assertEqual(type(xml_body2), str)
+ self.assertEqual(xml_body1, xml_body2)
+ stats_httpd.StatsHttpd.get_stats_data = lambda x: {'bar':'foo'}
+ xml_body2 = stats_httpd.StatsHttpd().xml_handler()
+ self.assertNotEqual(xml_body1, xml_body2)
+ stats_httpd.StatsHttpd.get_stats_data = orig_get_stats_data
+
+ def test_xsd_handler(self):
+ orig_get_stats_spec = stats_httpd.StatsHttpd.get_stats_spec
+ stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+ [{
+ "item_name": "foo",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "bar",
+ "item_description": "foo is bar",
+ "item_title": "Foo"
+ }]
+ xsd_body1 = stats_httpd.StatsHttpd().open_template(
+ stats_httpd.XSD_TEMPLATE_LOCATION).substitute(
+ xsd_string='<all>' \
+ + '<element maxOccurs="1" minOccurs="1" name="foo" type="string">' \
+ + '<annotation><appinfo>Foo</appinfo>' \
+ + '<documentation>foo is bar</documentation>' \
+ + '</annotation></element></all>',
+ xsd_namespace=stats_httpd.XSD_NAMESPACE)
+ xsd_body2 = stats_httpd.StatsHttpd().xsd_handler()
+ self.assertEqual(type(xsd_body1), str)
+ self.assertEqual(type(xsd_body2), str)
+ self.assertEqual(xsd_body1, xsd_body2)
+ stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+ [{
+ "item_name": "bar",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "foo",
+ "item_description": "bar is foo",
+ "item_title": "bar"
+ }]
+ xsd_body2 = stats_httpd.StatsHttpd().xsd_handler()
+ self.assertNotEqual(xsd_body1, xsd_body2)
+ stats_httpd.StatsHttpd.get_stats_spec = orig_get_stats_spec
+
+ def test_xsl_handler(self):
+ orig_get_stats_spec = stats_httpd.StatsHttpd.get_stats_spec
+ stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+ [{
+ "item_name": "foo",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "bar",
+ "item_description": "foo is bar",
+ "item_title": "Foo"
+ }]
+ xsl_body1 = stats_httpd.StatsHttpd().open_template(
+ stats_httpd.XSL_TEMPLATE_LOCATION).substitute(
+ xsl_string='<xsl:template match="*"><tr>' \
+ + '<td class="title" title="foo is bar">Foo</td>' \
+ + '<td><xsl:value-of select="foo" /></td>' \
+ + '</tr></xsl:template>',
+ xsd_namespace=stats_httpd.XSD_NAMESPACE)
+ xsl_body2 = stats_httpd.StatsHttpd().xsl_handler()
+ self.assertEqual(type(xsl_body1), str)
+ self.assertEqual(type(xsl_body2), str)
+ self.assertEqual(xsl_body1, xsl_body2)
+ stats_httpd.StatsHttpd.get_stats_spec = lambda x: \
+ [{
+ "item_name": "bar",
+ "item_type": "string",
+ "item_optional": False,
+ "item_default": "foo",
+ "item_description": "bar is foo",
+ "item_title": "bar"
+ }]
+ xsl_body2 = stats_httpd.StatsHttpd().xsl_handler()
+ self.assertNotEqual(xsl_body1, xsl_body2)
+ stats_httpd.StatsHttpd.get_stats_spec = orig_get_stats_spec
+
def test_for_without_B10_FROM_SOURCE(self):
# just lets it go through the code without B10_FROM_SOURCE env
# variable
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index 6ca2b42..99f4843 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 7ab4a58..e353a60 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -24,6 +24,7 @@ from pydnspp import *
from xfrout import *
import xfrout
import isc.log
+import isc.acl.dns
TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
@@ -117,8 +118,11 @@ class TestXfroutSession(unittest.TestCase):
def setUp(self):
self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
- #self.log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
- self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(), TSIGKeyRing())
+ self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(),
+ TSIGKeyRing(), ('127.0.0.1', 12345),
+ # When not testing ACLs, simply accept
+ isc.acl.dns.REQUEST_LOADER.load(
+ [{"action": "ACCEPT"}]))
self.mdata = bytes(b'\xd6=\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
self.soa_record = (4, 3, 'example.com.', 'com.example.', 3600, 'SOA', None, 'master.example.com. admin.example.com. 1234 3600 1800 2419200 7200')
@@ -138,6 +142,36 @@ class TestXfroutSession(unittest.TestCase):
self.assertEqual(rcode.to_text(), "NOERROR")
self.assertTrue(self.xfrsess._tsig_ctx is not None)
+ # ACL checks, put some ACL inside
+ self.xfrsess._acl = isc.acl.dns.REQUEST_LOADER.load([
+ {
+ "from": "127.0.0.1",
+ "action": "ACCEPT"
+ },
+ {
+ "from": "192.0.2.1",
+ "action": "DROP"
+ }
+ ])
+ # Localhost (the default in this test) is accepted
+ rcode, msg = self.xfrsess._parse_query_message(self.mdata)
+ self.assertEqual(rcode.to_text(), "NOERROR")
+ # This should be dropped completely, therefore returning None
+ self.xfrsess._remote = ('192.0.2.1', 12345)
+ rcode, msg = self.xfrsess._parse_query_message(self.mdata)
+ self.assertEqual(None, rcode)
+ # This should be refused, therefore REFUSED
+ self.xfrsess._remote = ('192.0.2.2', 12345)
+ rcode, msg = self.xfrsess._parse_query_message(self.mdata)
+ self.assertEqual(rcode.to_text(), "REFUSED")
+ # If the TSIG check fails, it should not check ACL
+ # (If it checked ACL as well, it would just drop the request)
+ self.xfrsess._remote = ('192.0.2.1', 12345)
+ self.xfrsess._tsig_key_ring = TSIGKeyRing()
+ rcode, msg = self.xfrsess._parse_query_message(request_data)
+ self.assertEqual(rcode.to_text(), "NOTAUTH")
+ self.assertTrue(self.xfrsess._tsig_ctx is not None)
+
def test_get_query_zone_name(self):
msg = self.getmsg()
self.assertEqual(self.xfrsess._get_query_zone_name(msg), "example.com.")
@@ -196,20 +230,6 @@ class TestXfroutSession(unittest.TestCase):
self.assertEqual(msg.get_rcode(), rcode)
self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_AA))
- def test_reply_query_with_format_error(self):
- msg = self.getmsg()
- self.xfrsess._reply_query_with_format_error(msg, self.sock)
- get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rcode().to_text(), "FORMERR")
-
- # tsig signed message
- msg = self.getmsg()
- self.xfrsess._tsig_ctx = self.create_mock_tsig_ctx(TSIGError.NOERROR)
- self.xfrsess._reply_query_with_format_error(msg, self.sock)
- get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rcode().to_text(), "FORMERR")
- self.assertTrue(self.message_has_tsig(get_msg))
-
def test_create_rrset_from_db_record(self):
rrset = self.xfrsess._create_rrset_from_db_record(self.soa_record)
self.assertEqual(rrset.get_name().to_text(), "example.com.")
@@ -516,18 +536,42 @@ class MyCCSession():
class MyUnixSockServer(UnixSockServer):
def __init__(self):
- self._lock = threading.Lock()
- self._transfers_counter = 0
self._shutdown_event = threading.Event()
self._max_transfers_out = 10
self._cc = MyCCSession()
- #self._log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
+ self._common_init()
class TestUnixSockServer(unittest.TestCase):
def setUp(self):
self.write_sock, self.read_sock = socket.socketpair()
self.unix = MyUnixSockServer()
+ def test_guess_remote(self):
+ """Test we can guess the remote endpoint when we have only the
+ file descriptor. This is needed, because we get only that one
+ from auth."""
+ # We test with UDP, as it can be "connected" without other
+ # endpoint
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.connect(('127.0.0.1', 12345))
+ self.assertEqual(('127.0.0.1', 12345),
+ self.unix._guess_remote(sock.fileno()))
+ if socket.has_ipv6:
+ # Don't check IPv6 address on hosts not supporting them
+ sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
+ sock.connect(('::1', 12345))
+ self.assertEqual(('::1', 12345, 0, 0),
+ self.unix._guess_remote(sock.fileno()))
+ # Try when pretending there's no IPv6 support
+ # (No need to pretend when there's really no IPv6)
+ xfrout.socket.has_ipv6 = False
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.connect(('127.0.0.1', 12345))
+ self.assertEqual(('127.0.0.1', 12345),
+ self.unix._guess_remote(sock.fileno()))
+ # Return it back
+ xfrout.socket.has_ipv6 = True
+
def test_receive_query_message(self):
send_msg = b"\xd6=\x00\x00\x00\x01\x00"
msg_len = struct.pack('H', socket.htons(len(send_msg)))
@@ -536,15 +580,37 @@ class TestUnixSockServer(unittest.TestCase):
recv_msg = self.unix._receive_query_message(self.read_sock)
self.assertEqual(recv_msg, send_msg)
- def test_updata_config_data(self):
+ def check_default_ACL(self):
+ context = isc.acl.dns.RequestContext(socket.getaddrinfo("127.0.0.1",
+ 1234, 0, socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP,
+ socket.AI_NUMERICHOST)[0][4])
+ self.assertEqual(isc.acl.acl.ACCEPT, self.unix._acl.execute(context))
+
+ def check_loaded_ACL(self):
+ context = isc.acl.dns.RequestContext(socket.getaddrinfo("127.0.0.1",
+ 1234, 0, socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP,
+ socket.AI_NUMERICHOST)[0][4])
+ self.assertEqual(isc.acl.acl.ACCEPT, self.unix._acl.execute(context))
+ context = isc.acl.dns.RequestContext(socket.getaddrinfo("192.0.2.1",
+ 1234, 0, socket.SOCK_DGRAM,
+ socket.IPPROTO_UDP,
+ socket.AI_NUMERICHOST)[0][4])
+ self.assertEqual(isc.acl.acl.REJECT, self.unix._acl.execute(context))
+
+ def test_update_config_data(self):
+ self.check_default_ACL()
tsig_key_str = 'example.com:SFuWd/q99SzF8Yzd1QbB9g=='
tsig_key_list = [tsig_key_str]
bad_key_list = ['bad..example.com:SFuWd/q99SzF8Yzd1QbB9g==']
self.unix.update_config_data({'transfers_out':10 })
self.assertEqual(self.unix._max_transfers_out, 10)
self.assertTrue(self.unix.tsig_key_ring is not None)
+ self.check_default_ACL()
- self.unix.update_config_data({'transfers_out':9, 'tsig_key_ring':tsig_key_list})
+ self.unix.update_config_data({'transfers_out':9,
+ 'tsig_key_ring':tsig_key_list})
self.assertEqual(self.unix._max_transfers_out, 9)
self.assertEqual(self.unix.tsig_key_ring.size(), 1)
self.unix.tsig_key_ring.remove(Name("example.com."))
@@ -555,6 +621,16 @@ class TestUnixSockServer(unittest.TestCase):
self.assertRaises(None, self.unix.update_config_data(config_data))
self.assertEqual(self.unix.tsig_key_ring.size(), 0)
+ # Load the ACL
+ self.unix.update_config_data({'query_acl': [{'from': '127.0.0.1',
+ 'action': 'ACCEPT'}]})
+ self.check_loaded_ACL()
+ # Pass a wrong data there and check it does not replace the old one
+ self.assertRaises(isc.acl.acl.LoaderError,
+ self.unix.update_config_data,
+ {'query_acl': ['Something bad']})
+ self.check_loaded_ACL()
+
def test_get_db_file(self):
self.assertEqual(self.unix.get_db_file(), "initdb.file")
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index b44b099..2e94369 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -48,6 +48,9 @@ except ImportError as e:
# must keep running, so we warn about it and move forward.
log.error(XFROUT_IMPORT, str(e))
+from isc.acl.acl import ACCEPT, REJECT, DROP
+from isc.acl.dns import REQUEST_LOADER
+
isc.util.process.rename()
def init_paths():
@@ -92,16 +95,16 @@ def get_rrset_len(rrset):
class XfroutSession():
- def __init__(self, sock_fd, request_data, server, tsig_key_ring):
- # The initializer for the superclass may call functions
- # that need _log to be set, so we set it first
+ def __init__(self, sock_fd, request_data, server, tsig_key_ring, remote,
+ acl):
self._sock_fd = sock_fd
self._request_data = request_data
self._server = server
- #self._log = log
self._tsig_key_ring = tsig_key_ring
self._tsig_ctx = None
self._tsig_len = 0
+ self._remote = remote
+ self._acl = acl
self.handle()
def create_tsig_ctx(self, tsig_record, tsig_key_ring):
@@ -114,7 +117,7 @@ class XfroutSession():
self.dns_xfrout_start(self._sock_fd, self._request_data)
#TODO, avoid catching all exceptions
except Exception as e:
- logger.error(XFROUT_HANDLE_QUERY_ERROR, str(e))
+ logger.error(XFROUT_HANDLE_QUERY_ERROR, e)
pass
os.close(self._sock_fd)
@@ -141,8 +144,25 @@ class XfroutSession():
# TSIG related checks
rcode = self._check_request_tsig(msg, mdata)
+ if rcode == Rcode.NOERROR():
+ # ACL checks
+ acl_result = self._acl.execute(
+ isc.acl.dns.RequestContext(self._remote))
+ if acl_result == DROP:
+ logger.info(XFROUT_QUERY_DROPPED,
+ self._get_query_zone_name(msg),
+ self._get_query_zone_class(msg),
+ self._remote[0], self._remote[1])
+ return None, None
+ elif acl_result == REJECT:
+ logger.info(XFROUT_QUERY_REJECTED,
+ self._get_query_zone_name(msg),
+ self._get_query_zone_class(msg),
+ self._remote[0], self._remote[1])
+ return Rcode.REFUSED(), msg
+
except Exception as err:
- logger.error(XFROUT_PARSE_QUERY_ERROR, str(err))
+ logger.error(XFROUT_PARSE_QUERY_ERROR, err)
return Rcode.FORMERR(), None
return rcode, msg
@@ -183,18 +203,11 @@ class XfroutSession():
def _reply_query_with_error_rcode(self, msg, sock_fd, rcode_):
- msg.make_response()
- msg.set_rcode(rcode_)
- self._send_message(sock_fd, msg, self._tsig_ctx)
-
-
- def _reply_query_with_format_error(self, msg, sock_fd):
- '''query message format isn't legal.'''
if not msg:
return # query message is invalid. send nothing back.
msg.make_response()
- msg.set_rcode(Rcode.FORMERR())
+ msg.set_rcode(rcode_)
self._send_message(sock_fd, msg, self._tsig_ctx)
def _zone_has_soa(self, zone):
@@ -244,10 +257,13 @@ class XfroutSession():
def dns_xfrout_start(self, sock_fd, msg_query):
rcode_, msg = self._parse_query_message(msg_query)
#TODO. create query message and parse header
- if rcode_ == Rcode.NOTAUTH():
+ if rcode_ is None: # Dropped by ACL
+ return
+ elif rcode_ == Rcode.NOTAUTH() or rcode_ == Rcode.REFUSED():
return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
elif rcode_ != Rcode.NOERROR():
- return self._reply_query_with_format_error(msg, sock_fd)
+ return self._reply_query_with_error_rcode(msg, sock_fd,
+ Rcode.FORMERR())
zone_name = self._get_query_zone_name(msg)
zone_class_str = self._get_query_zone_class(msg)
@@ -257,7 +273,7 @@ class XfroutSession():
if rcode_ != Rcode.NOERROR():
logger.info(XFROUT_AXFR_TRANSFER_FAILED, zone_name,
zone_class_str, rcode_.to_text())
- return self. _reply_query_with_error_rcode(msg, sock_fd, rcode_)
+ return self._reply_query_with_error_rcode(msg, sock_fd, rcode_)
try:
logger.info(XFROUT_AXFR_TRANSFER_STARTED, zone_name, zone_class_str)
@@ -375,14 +391,20 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
self._sock_file = sock_file
socketserver_mixin.NoPollMixIn.__init__(self)
ThreadingUnixStreamServer.__init__(self, sock_file, handle_class)
- self._lock = threading.Lock()
- self._transfers_counter = 0
self._shutdown_event = shutdown_event
self._write_sock, self._read_sock = socket.socketpair()
- #self._log = log
+ self._common_init()
self.update_config_data(config_data)
self._cc = cc
+ def _common_init(self):
+ self._lock = threading.Lock()
+ self._transfers_counter = 0
+ # This default value will probably get overwritten by the (same)
+ # default value from the spec file. This is here just to make
+ # sure and to make the default value in tests consistent.
+ self._acl = REQUEST_LOADER.load('[{"action": "ACCEPT"}]')
+
def _receive_query_message(self, sock):
''' receive request message from sock'''
# receive data length
@@ -465,10 +487,28 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
t.daemon = True
t.start()
+ def _guess_remote(self, sock_fd):
+ """
+ Guess remote address and port of the socket. The sock_fd must be a
+ socket
+ """
+ # This uses a trick. If the socket is IPv4 in reality and we pretend
+ # it to be IPv6, it returns IPv4 address anyway. This doesn't seem
+ # to care about the SOCK_STREAM parameter at all (which it really is,
+ # except for testing)
+ if socket.has_ipv6:
+ sock = socket.fromfd(sock_fd, socket.AF_INET6, socket.SOCK_STREAM)
+ else:
+ # To make it work even on hosts without IPv6 support
+ # (Any idea how to simulate this in test?)
+ sock = socket.fromfd(sock_fd, socket.AF_INET, socket.SOCK_STREAM)
+ return sock.getpeername()
def finish_request(self, sock_fd, request_data):
'''Finish one request by instantiating RequestHandlerClass.'''
- self.RequestHandlerClass(sock_fd, request_data, self, self.tsig_key_ring)
+ self.RequestHandlerClass(sock_fd, request_data, self,
+ self.tsig_key_ring,
+ self._guess_remote(sock_fd), self._acl)
def _remove_unused_sock_file(self, sock_file):
'''Try to remove the socket file. If the file is being used
@@ -512,6 +552,8 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
def update_config_data(self, new_config):
'''Apply the new config setting of xfrout module. '''
logger.info(XFROUT_NEW_CONFIG)
+ if 'query_acl' in new_config:
+ self._acl = REQUEST_LOADER.load(new_config['query_acl'])
self._lock.acquire()
self._max_transfers_out = new_config.get('transfers_out')
self.set_tsig_key_ring(new_config.get('tsig_key_ring'))
@@ -563,16 +605,12 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
class XfroutServer:
def __init__(self):
self._unix_socket_server = None
- #self._log = None
self._listen_sock_file = UNIX_SOCKET_FILE
self._shutdown_event = threading.Event()
self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, self.config_handler, self.command_handler)
self._config_data = self._cc.get_full_config()
self._cc.start()
self._cc.add_remote_config(AUTH_SPECFILE_LOCATION);
- #self._log = isc.log.NSLogger(self._config_data.get('log_name'), self._config_data.get('log_file'),
- # self._config_data.get('log_severity'), self._config_data.get('log_versions'),
- # self._config_data.get('log_max_bytes'), True)
self._start_xfr_query_listener()
self._start_notifier()
@@ -601,11 +639,13 @@ class XfroutServer:
continue
self._config_data[key] = new_config[key]
- #if self._log:
- # self._log.update_config(new_config)
-
if self._unix_socket_server:
- self._unix_socket_server.update_config_data(self._config_data)
+ try:
+ self._unix_socket_server.update_config_data(self._config_data)
+ except Exception as e:
+ answer = create_answer(1,
+ "Failed to handle new configuration: " +
+ str(e))
return answer
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index 2efa3d7..8ecbb0b 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -16,27 +16,27 @@
},
{
"item_name": "log_file",
- "item_type": "string",
+ "item_type": "string",
"item_optional": false,
"item_default": "@@LOCALSTATEDIR@@/@PACKAGE@/log/Xfrout.log"
},
{
"item_name": "log_severity",
- "item_type": "string",
+ "item_type": "string",
"item_optional": false,
- "item_default": "debug"
+ "item_default": "debug"
},
{
"item_name": "log_versions",
- "item_type": "integer",
+ "item_type": "integer",
"item_optional": false,
- "item_default": 5
+ "item_default": 5
},
{
"item_name": "log_max_bytes",
- "item_type": "integer",
+ "item_type": "integer",
"item_optional": false,
- "item_default": 1048576
+ "item_default": 1048576
},
{
"item_name": "tsig_key_ring",
@@ -49,6 +49,18 @@
"item_type": "string",
"item_optional": true
}
+ },
+ {
+ "item_name": "query_acl",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [{"action": "ACCEPT"}],
+ "list_item_spec":
+ {
+ "item_name": "acl_element",
+ "item_type": "any",
+ "item_optional": true
+ }
}
],
"commands": [
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index 2dada54..19b104e 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -95,6 +95,17 @@ in the log message, but at this point no specific information other
than that could be given. This points to incomplete exception handling
in the code.
+% XFROUT_QUERY_DROPPED request to transfer %1/%2 to [%3]:%4 dropped
+The xfrout process silently dropped a request to transfer zone to given host.
+This is required by the ACLs. The %1 and %2 represent the zone name and class,
+the %3 and %4 the IP address and port of the peer requesting the transfer.
+
+% XFROUT_QUERY_REJECTED request to transfer %1/%2 to [%3]:%4 rejected
+The xfrout process rejected (by REFUSED rcode) a request to transfer zone to
+given host. This is because of ACLs. The %1 and %2 represent the zone name and
+class, the %3 and %4 the IP address and port of the peer requesting the
+transfer.
+
% XFROUT_RECEIVE_FILE_DESCRIPTOR_ERROR error receiving the file descriptor for an XFR connection
There was an error receiving the file descriptor for the transfer
request. Normally, the request is received by b10-auth, and passed on
More information about the bind10-changes
mailing list