Commit 3a7f572d authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[1209] provided backward compatible behavior for AXFR + non existent zone

parent 4e99a42d
296. [bug] jinmei
295. [bug] jinmei
__init__.py for isc.dns was installed in the wrong directory,
which would now make xfrin fail to start. It was also bad
in that it replaced any existing __init__.py in th public
......@@ -7,15 +7,6 @@
case it should be removed.
(Trac #1285, git af3b17472694f58b3d6a56d0baf64601b0f6a6a1)
295. [func]* jinmei
b10-xfrin: the AXFR implementation is unified with IXFR, and
handles corner cases more carefully. Note: As a result of this
change, xfrin does not create a new (SQLite3) zone in a fresh DB
file upon receiving AXFR any more. Initial zone content must be
prepared by hand (e.g. with b10-loadzone) until a more generic
tool for zone management is provided.
(Trac #1209, git 5ca7b409bccc815cee58c804236504fda1c1c147)
294. [func] jelte, jinmei, vorner
b10-xfrin now supports incoming IXFR. See BIND 10 Guide for
how to configure it and operational notes.
......
......@@ -1276,13 +1276,6 @@ TODO
In the current development release of BIND 10, incoming zone
transfers are only available for SQLite3-based data sources,
that is, they don't work for an in-memory data source.
Furthermore, the corresponding SQLite3 database must be
configured with a list of zone names by hand. One possible way
to do this is to use the <command>b10-loadzone</command> command
to load dummy zone content of the zone for which the secondary
service is provided (and then force transfer using AXFR from the primary
server). In future versions we will provide more convenient way
to set up the secondary.
</simpara></note>
<para>
......
......@@ -1483,11 +1483,14 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
def setUp(self):
self.sqlite3db_src = TESTDATA_SRCDIR + '/example.com.sqlite3'
self.sqlite3db_obj = TESTDATA_OBJDIR + '/example.com.sqlite3.copy'
self.empty_sqlite3db_obj = TESTDATA_OBJDIR + '/empty.sqlite3'
self.sqlite3db_cfg = "{ \"database_file\": \"" +\
self.sqlite3db_obj + "\"}"
super().setUp()
if os.path.exists(self.sqlite3db_obj):
os.unlink(self.sqlite3db_obj)
if os.path.exists(self.empty_sqlite3db_obj):
os.unlink(self.empty_sqlite3db_obj)
shutil.copyfile(self.sqlite3db_src, self.sqlite3db_obj)
self.conn._datasrc_client = DataSourceClient("sqlite3",
self.sqlite3db_cfg)
......@@ -1495,6 +1498,8 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
def tearDown(self):
if os.path.exists(self.sqlite3db_obj):
os.unlink(self.sqlite3db_obj)
if os.path.exists(self.empty_sqlite3db_obj):
os.unlink(self.empty_sqlite3db_obj)
def get_zone_serial(self):
result, finder = self.conn._datasrc_client.find_zone(TEST_ZONE_NAME)
......@@ -1612,20 +1617,30 @@ class TestXFRSessionWithSQLite3(TestXfrinConnection):
self.axfr_failure_check(RRType.AXFR())
def test_do_axfrin_nozone_sqlite3(self):
'''AXFR test with an empty SQLite3 DB file, thus no target zone there.
For now, we provide backward compatible behavior: xfrin will create
the zone (after even setting up the entire schema) in the zone.
Note: a future version of this test will make it fail.
'''
self.conn._db_file = self.empty_sqlite3db_obj
self.conn._datasrc_client = DataSourceClient(
"sqlite3",
"{ \"database_file\": \"" + self.empty_sqlite3db_obj + "\"}")
def create_response():
# Within this test, owner names of the question/RRs don't matter,
# so we use pre-defined names (which are "out of zone") for
# simplicity.
self.conn.reply_data = self.conn.create_response_data(
questions=[Question(TEST_ZONE_NAME, TEST_RRCLASS,
RRType.AXFR())],
answers=[soa_rrset, self._create_ns(), soa_rrset, soa_rrset])
answers=[soa_rrset, self._create_ns(), soa_rrset])
self.conn.response_generator = create_response
self.conn._zone_name = Name('nosuchzone.example')
self.assertEqual(XFRIN_FAIL, self.conn.do_xfrin(False, RRType.AXFR()))
# This should fail in the FirstData state
self.assertEqual(type(XfrinFirstData()),
self.conn._zone_name = Name('example.com')
self.assertEqual(XFRIN_OK, self.conn.do_xfrin(False, RRType.AXFR()))
self.assertEqual(type(XfrinAXFREnd()),
type(self.conn.get_xfrstate()))
self.assertEqual(1234, self.get_zone_serial())
self.assertFalse(self.record_exist(Name('dns01.example.com'),
RRType.A()))
class TestXfrinRecorder(unittest.TestCase):
def setUp(self):
......
......@@ -504,6 +504,25 @@ class XfrinConnection(asyncore.dispatcher):
logger.error(XFRIN_CONNECT_MASTER, self._master_address, str(e))
return False
def _get_zone_soa(self):
result, finder = self._datasrc_client.find_zone(self._zone_name)
if result != DataSourceClient.SUCCESS:
raise XfrinException('Zone not found in the given data ' +
'source: ' + self.zone_str())
result, soa_rrset = finder.find(self._zone_name, RRType.SOA(),
None, ZoneFinder.FIND_DEFAULT)
if result != ZoneFinder.SUCCESS:
raise XfrinException('SOA RR not found in zone: ' +
self.zone_str())
# Especially for database-based zones, a working zone may be in
# a broken state where it has more than one SOA RR. We proactively
# check the condition and abort the xfr attempt if we identify it.
if soa_rrset.get_rdata_count() != 1:
raise XfrinException('Invalid number of SOA RRs for ' +
self.zone_str() + ': ' +
str(soa_rrset.get_rdata_count()))
return soa_rrset
def _create_query(self, query_type):
'''Create an XFR-related query message.
......@@ -528,24 +547,24 @@ class XfrinConnection(asyncore.dispatcher):
if query_type == RRType.IXFR():
# get the zone finder. this must be SUCCESS (not even
# PARTIALMATCH) because we are specifying the zone origin name.
result, finder = self._datasrc_client.find_zone(self._zone_name)
if result != DataSourceClient.SUCCESS:
raise XfrinException('Zone not found in the given data ' +
'source: ' + self.zone_str())
result, soa_rrset = finder.find(self._zone_name, RRType.SOA(),
None, ZoneFinder.FIND_DEFAULT)
if result != ZoneFinder.SUCCESS:
raise XfrinException('SOA RR not found in zone: ' +
self.zone_str())
# Especially for database-based zones, a working zone may be in
# a broken state where it has more than one SOA RR. We proactively
# check the condition and abort the xfr attempt if we identify it.
if soa_rrset.get_rdata_count() != 1:
raise XfrinException('Invalid number of SOA RRs for ' +
self.zone_str() + ': ' +
str(soa_rrset.get_rdata_count()))
msg.add_rrset(Message.SECTION_AUTHORITY, soa_rrset)
self._request_serial = get_soa_serial(soa_rrset.get_rdata()[0])
zone_soa_rr = self._get_zone_soa()
msg.add_rrset(Message.SECTION_AUTHORITY, zone_soa_rr)
self._request_serial = get_soa_serial(zone_soa_rr.get_rdata()[0])
else:
# For AXFR, we temporariy provide backward compatible behavior
# where xfrin is responsible create zone in the corresponding
# DB table. Note that the code below uses the old data source
# API and assumes SQLite3 in an ugly manner. We'll have to
# develop a better way of managing zones in a generic way and
# eliminate the code like the one here.
try:
self._get_zone_soa()
except XfrinException:
def empty_rr_generator():
return []
isc.datasrc.sqlite3_ds.load(self._db_file,
self._zone_name.to_text(),
empty_rr_generator)
return msg
def _send_data(self, data):
......@@ -803,6 +822,9 @@ def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
sock_map = {}
conn = XfrinConnection(sock_map, zone_name, rrclass, datasrc_client,
shutdown_event, master_addrinfo, tsig_key)
# XXX: We still need _db_file for temporary workaround in _create_query().
# This should be removed when we eliminate the need for the workaround.
conn._db_file = db_file
ret = XFRIN_FAIL
if conn.connect_to_master():
ret = conn.do_xfrin(check_soa, request_type)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment