Commit 256c0a08 authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

[1372] more complete setup for IXFR. falling back to AXFR-style was supported.

also refactored the code to better clarify the code flow.
parent 5c92f567
......@@ -36,6 +36,8 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
TEST_ZONE_NAME_STR = "example.com."
TEST_ZONE_NAME = Name(TEST_ZONE_NAME_STR)
TEST_RRCLASS = RRClass.IN()
IXFR_OK_VERSION = 2011111802
IXFR_NG_VERSION = 2011112800
# SOA intended to be used for the new SOA as a result of transfer.
soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
......@@ -83,6 +85,15 @@ class MockDataSrcClient:
def __init__(self, type, config):
pass
def __create_soa(self):
soa_rrset = RRset(self._zone_name, RRClass.IN(), RRType.SOA(),
RRTTL(3600))
soa_rrset.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
'master.example.com. ' +
'admin.example.com. 1234 ' +
'3600 1800 2419200 7200'))
return soa_rrset
def find_zone(self, zone_name):
'''Mock version of find_zone().
......@@ -91,9 +102,10 @@ class MockDataSrcClient:
or PARTIALMATCH.
'''
if zone_name == TEST_ZONE_NAME:
return (isc.datasrc.DataSourceClient.SUCCESS, self)
raise ValueError('Unexpected input to mock client: bug in test case?')
self._zone_name = zone_name
if zone_name == Name('notauth.example.com'):
return (isc.datasrc.DataSourceClient.NOTFOUND, None)
return (isc.datasrc.DataSourceClient.SUCCESS, self)
def find(self, name, rrtype, target, options):
'''Mock ZoneFinder.find().
......@@ -104,6 +116,12 @@ class MockDataSrcClient:
'''
if name == TEST_ZONE_NAME and rrtype == RRType.SOA():
return (ZoneFinder.SUCCESS, self.__create_soa())
elif name == Name('nosoa.example.com') and rrtype == RRType.SOA():
return (ZoneFinder.NXDOMAIN, None)
elif name == Name('multisoa.example.com') and rrtype == RRType.SOA():
soa_rrset = self.__create_soa()
soa_rrset.add_rdata(soa_rrset.get_rdata()[0])
return (ZoneFinder.SUCCESS, soa_rrset)
raise ValueError('Unexpected input to mock finder: bug in test case?')
......@@ -116,20 +134,14 @@ class MockDataSrcClient:
def get_soa(self): # emulate ZoneIterator.get_soa()
if self._zone_name == Name('nosoa.example.com'):
return None
soa_rrset = RRset(self._zone_name, RRClass.IN(), RRType.SOA(),
RRTTL(3600))
soa_rrset.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
'master.example.com. ' +
'admin.example.com. 1234 ' +
'3600 1800 2419200 7200'))
soa_rrset = self.__create_soa()
if self._zone_name == Name('multisoa.example.com'):
soa_rrset.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
'master.example.com. ' +
'admin.example.com. 1300 ' +
'3600 1800 2419200 7200'))
soa_rrset.add_rdata(soa_rrset.get_rdata()[0])
return soa_rrset
def get_journal_reader(self, zone_name, begin_serial, end_serial):
if begin_serial == IXFR_NG_VERSION:
return isc.datasrc.ZoneJournalReader.NO_SUCH_VERSION, self
return isc.datasrc.ZoneJournalReader.SUCCESS, self
class MyCCSession(isc.config.ConfigData):
......@@ -663,8 +675,12 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_check_xfrout_axfr_available(self):
self.xfrsess.ClientClass = MockDataSrcClient
# Successful case. A zone iterator should be set up.
self.assertEqual(self.xfrsess._check_xfrout_available(
self.getmsg(), Name('example.com')), Rcode.NOERROR())
self.assertNotEqual(None, self.xfrsess._iterator)
# Failure cases
self.assertEqual(self.xfrsess._check_xfrout_available(
self.getmsg(), Name('notauth.example.com')), Rcode.NOTAUTH())
self.assertEqual(self.xfrsess._check_xfrout_available(
......@@ -675,10 +691,29 @@ class TestXfroutSession(TestXfroutSessionBase):
def test_check_xfrout_ixfr_available(self):
self.xfrsess.ClientClass = MockDataSrcClient
self.set_request_type(RRType.IXFR())
self.mdata = self.create_request_data(ixfr=2011111802)
request_msg = self.getmsg()
# Successful case of pure IXFR. A zone journal reader should be set
# up.
self.mdata = self.create_request_data(ixfr=IXFR_OK_VERSION)
self.assertEqual(self.xfrsess._check_xfrout_available(
self.getmsg(), Name('example.com')), Rcode.NOERROR())
self.getmsg(), TEST_ZONE_NAME), Rcode.NOERROR())
self.assertNotEqual(None, self.xfrsess._jnl_reader)
# Successful case, but as a result of falling back to AXFR-style
# IXFR. A zone iterator should be set up instead of a journal reader.
self.mdata = self.create_request_data(ixfr=IXFR_NG_VERSION)
self.assertEqual(self.xfrsess._check_xfrout_available(
self.getmsg(), TEST_ZONE_NAME), Rcode.NOERROR())
self.assertNotEqual(None, self.xfrsess._iterator)
self.assertEqual(None, self.xfrsess._jnl_reader)
# Failure cases
self.assertEqual(self.xfrsess._check_xfrout_available(
self.getmsg(), Name('notauth.example.com')), Rcode.NOTAUTH())
self.assertEqual(self.xfrsess._check_xfrout_available(
self.getmsg(), Name('nosoa.example.com')), Rcode.SERVFAIL())
self.assertEqual(self.xfrsess._check_xfrout_available(
self.getmsg(), Name('multisoa.example.com')), Rcode.SERVFAIL())
def test_dns_xfrout_start_formerror(self):
# formerror
......
......@@ -22,7 +22,7 @@ import isc.cc
import threading
import struct
import signal
from isc.datasrc import DataSourceClient, ZoneFinder
from isc.datasrc import DataSourceClient, ZoneFinder, ZoneJournalReader
from socketserver import *
import os
from isc.config.ccsession import *
......@@ -237,8 +237,8 @@ class XfroutSession():
elif self._request_type == RRType.IXFR():
self._request_typestr = 'IXFR'
else:
# Likewise, this should be impossible.
raise Runtimeerror('Unexpected XFR type: ' + \
# Likewise, this should be impossible. (TBD: to be tested)
raise RuntimeError('Unexpected XFR type: ' + \
str(self._request_type))
# ACL checks
......@@ -314,19 +314,82 @@ class XfroutSession():
self._send_message(sock_fd, msg, self._tsig_ctx)
def _get_zone_soa(self, zone_name):
'''Retrieve the SOA RR of the given zone.
It returns a pair of RCODE and the SOA (in the form of RRset).
On success RCODE is NOERROR and returned SOA is not None;
on failure RCODE indicates the appropriate code in the context of
xfr processing, and the returned SOA is None.
'''
result, finder = self._datasrc_client.find_zone(zone_name)
if result != DataSourceClient.SUCCESS:
return None # XXX
result, soa_rrset = finder.find(zone_name, RRType.SOA(),
None, ZoneFinder.FIND_DEFAULT)
return (Rcode.NOTAUTH(), None)
result, soa_rrset = finder.find(zone_name, RRType.SOA(), None,
ZoneFinder.FIND_DEFAULT)
if result != ZoneFinder.SUCCESS:
return None
return (Rcode.SERVFAIL(), None)
# 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:
return None
return soa_rrset
return (Rcode.SERVFAIL(), None)
return (Rcode.NOERROR(), soa_rrset)
def __setup_axfr(self, zone_name):
'''Setup a zone iterator for AXFR or AXFR-style IXFR.
'''
try:
# Note that we disable 'adjust_ttl'. In xfr-out we need to
# preserve as many things as possible (even if it's half
# broken) stored in the zone.
self._iterator = self._datasrc_client.get_iterator(zone_name,
False)
except isc.datasrc.Error:
# If the current name server does not have authority for the
# zone, xfrout can't serve for it, return rcode NOTAUTH.
# Note: this exception can happen for other reasons. We should
# update get_iterator() API so that we can distinguish "no such
# zone" and other cases (#1373). For now we consider all these
# cases as NOTAUTH.
return Rcode.NOTAUTH()
# If we are an authoritative name server for the zone, but fail
# to find the zone's SOA record in datasource, xfrout can't
# provide zone transfer for it.
self._soa = self._iterator.get_soa()
if self._soa is None or self._soa.get_rdata_count() != 1:
return Rcode.SERVFAIL()
return Rcode.NOERROR()
def __setup_ixfr(self, request_msg, zone_name):
'''Setup a zone journal reader for IXFR.
If the underlying data source does not know the requested range
of zone differences it automatically falls back to AXFR-style
IXFR by setting up a zone iterator instead of a journal reader.
'''
# TODO: more error case handling
remote_soa = None
for auth_rrset in request_msg.get_section(Message.SECTION_AUTHORITY):
if auth_rrset.get_type() != RRType.SOA():
continue
remote_soa = auth_rrset
rcode, self._soa = self._get_zone_soa(zone_name)
if rcode != Rcode.NOERROR():
return rcode
code, self._jnl_reader = self._datasrc_client.get_journal_reader(
remote_soa.get_name(), get_soa_serial(remote_soa.get_rdata()[0]),
get_soa_serial(self._soa.get_rdata()[0]))
if code == ZoneJournalReader.NO_SUCH_VERSION:
# fallback to AXFR-style IXFR
self._jnl_reader = None # clear it just in case
return self.__setup_axfr(zone_name)
return Rcode.NOERROR()
def _check_xfrout_available(self, request_msg, zone_name):
'''Check if xfr request can be responsed.
......@@ -340,51 +403,16 @@ class XfroutSession():
# We should eventually generalize this so that we can choose the
# appropriate data source from (possible) multiple candidates.
# We should eventually take into account the RR class here.
# For now, we hardcode a particular type (SQLite3-based), and only
# For now, we hardcode a particular type (SQLite3-based), and only
# consider that one.
datasrc_config = '{ "database_file": "' + \
self._server.get_db_file() + '"}'
self._datasrc_client = self.ClientClass('sqlite3', datasrc_config)
if self._request_type == RRType.AXFR():
try:
# Note that we disable 'adjust_ttl'. In xfr-out we need to
# preserve as many things as possible (even if it's half
# broken) stored in the zone.
self._iterator = self._datasrc_client.get_iterator(zone_name,
False)
except isc.datasrc.Error:
# If the current name server does not have authority for the
# zone, xfrout can't serve for it, return rcode NOTAUTH.
# Note: this exception can happen for other reasons. We should
# update get_iterator() API so that we can distinguish "no such
# zone" and other cases (#1373). For now we consider all these
# cases as NOTAUTH.
return Rcode.NOTAUTH()
self._soa = self._iterator.get_soa()
return self.__setup_axfr(zone_name)
else:
# TODO: error case handling
remote_soa = None
for auth_rrset in \
request_msg.get_section(Message.SECTION_AUTHORITY):
if auth_rrset.get_type() != RRType.SOA():
continue
remote_soa = auth_rrset
self._soa = self._get_zone_soa(remote_soa.get_name())
code, self._jnl_reader = self._datasrc_client.get_journal_reader(
remote_soa.get_name(),
get_soa_serial(remote_soa.get_rdata()[0]),
get_soa_serial(self._soa.get_rdata()[0]))
# If we are an authoritative name server for the zone, but fail
# to find the zone's SOA record in datasource, xfrout can't
# provide zone transfer for it.
if self._soa is None or self._soa.get_rdata_count() != 1:
return Rcode.SERVFAIL()
return Rcode.NOERROR()
return self.__setup_ixfr(request_msg, zone_name)
def dns_xfrout_start(self, sock_fd, msg_query, quota_ok=True):
rcode_, msg = self._parse_query_message(msg_query)
......@@ -458,7 +486,6 @@ class XfroutSession():
msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
self._send_message(sock_fd, msg, self._tsig_ctx)
def _reply_xfrout_query(self, msg, sock_fd):
#TODO, there should be a better way to insert rrset.
msg.make_response()
......
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