From: JINMEI Tatuya Date: Fri, 25 May 2012 23:31:22 +0000 (-0700) Subject: [2013] added some basic update request processing in handle_request. X-Git-Tag: trac2351_base~226^2~60^2^2~2^2~19 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7a70191fed37bff9ceb0fed6992fdc6c96ff257f;p=thirdparty%2Fkea.git [2013] added some basic update request processing in handle_request. this is ongoing, with many TODO cases. --- diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in index de691000e4..d1511f902e 100755 --- a/src/bin/ddns/ddns.py.in +++ b/src/bin/ddns/ddns.py.in @@ -20,6 +20,7 @@ import sys; sys.path.append ('@@PYTHONPATH@@') import isc import bind10_config from isc.dns import * +import isc.ddns.session from isc.config.ccsession import * from isc.cc import SessionError, SessionTimeout import isc.util.process @@ -120,6 +121,20 @@ class DDNSServer: self._listen_socket.bind(SOCKET_FILE) self._listen_socket.listen(16) + # DDNS Protocol handling class. Parameterized for convenience of + # tests. Essentially private (and constant), but for tests' + # convenience defined as "protected". + self._UpdateSessionClass = isc.ddns.session.UpdateSession + + class SessionError(Exception): + '''Exception for internal errors in an update session. + + This exception is expected to be caught within the server class, + only used for controling the code flow. + + ''' + pass + def config_handler(self, new_config): '''Update config data.''' # TODO: Handle exceptions and turn them to an error response @@ -180,7 +195,7 @@ class DDNSServer: # continue with the rest logger.error(DDNS_ACCEPT_FAILURE, e) - def handle_request(self, request): + def handle_request(self, req_session): """ This is the place where the actual DDNS processing is done. Other methods are either subroutines of this method or methods doing the @@ -190,12 +205,38 @@ class DDNSServer: It is called with the request being session as received from SocketSessionReceiver, i.e. tuple (socket, local_address, remote_address, data). + """ - # TODO: Implement the magic + # give tuple elements intuitive names + (sock, local_addr, remote_addr, req_data) = req_session + + # The session sender (b10-auth) should have made sure that this is + # a validly formed DNS message of OPCODE being UPDATE, and if it's + # TSIG signed, its key is known to the system and the signature is + # valid. Messages that don't meet these should have been resopnded + # or dropped by the sender, so if such error is detected we treat it + # as an internal error and don't bother to respond. + try: + msg = Message(Message.PARSE) + msg.from_wire(req_data) + if msg.get_opcode() != Opcode.UPDATE(): + raise SessionError('Update request has unexpected opcode: ' + + str(msg.get_opcode())) + # TODO: TSIG check + except Exception as ex: + return False # TODO: Don't propagate most of the exceptions (like datasrc errors), # just drop the packet. - pass + + update_session = self._UpdateSessionClass(msg, remote_addr, None) + result, zname, zclass = update_session.handle() + msg = update_session.get_message() + renderer = MessageRenderer() + msg.to_wire(renderer) + sock.sendto(renderer.get_data(), remote_addr) + + return True def handle_session(self, fileno): """ diff --git a/src/bin/ddns/tests/ddns_test.py b/src/bin/ddns/tests/ddns_test.py index 395aacc512..1bb7684036 100755 --- a/src/bin/ddns/tests/ddns_test.py +++ b/src/bin/ddns/tests/ddns_test.py @@ -17,6 +17,7 @@ import unittest import isc +from isc.ddns.session import * import ddns import isc.config import select @@ -24,6 +25,16 @@ import errno import isc.util.cio.socketsession import socket import os.path +from isc.dns import * + +# Some common test parameters +TEST_ZONE_NAME = Name('example.org') +UPDATE_RRTYPE = RRType.SOA() +TEST_QID = 5353 # arbitrary chosen +TEST_RRCLASS = RRClass.IN() +TEST_SERVER6 = ('2001:db8::53', 53, 0, 0) +TEST_CLIENT6 = ('2001:db8::1', 53000, 0, 0) +TEST_ZONE_RECORD = Question(TEST_ZONE_NAME, TEST_RRCLASS, UPDATE_RRTYPE) class FakeSocket: """ @@ -31,12 +42,17 @@ class FakeSocket: """ def __init__(self, fileno): self.__fileno = fileno + self._sent_data = None + self._sent_addr = None def fileno(self): return self.__fileno def getpeername(self): return "fake_unix_socket" def accept(self): return FakeSocket(self.__fileno + 1) + def sendto(self, data, addr): + self._sent_data = data + self._sent_addr = addr class FakeSessionReceiver: """ @@ -51,6 +67,19 @@ class FakeSessionReceiver: """ return self._socket +class FakeUpdateSession: + def __init__(self, msg, client_addr, zone_config): + self.__msg = msg + + def handle(self): + return UPDATE_SUCCESS, TEST_ZONE_NAME, TEST_RRCLASS + + def get_message(self): + self.__msg.make_response() + self.__msg.clear_section(SECTION_ZONE) + self.__msg.set_rcode(Rcode.NOERROR()) + return self.__msg + class MyCCSession(isc.config.ConfigData): '''Fake session with minimal interface compliance''' def __init__(self): @@ -361,6 +390,69 @@ class TestDDNSServer(unittest.TestCase): self.__select_expected = ([1, 2], [], [], None) self.assertRaises(select.error, self.ddns_server.run) +def create_msg(opcode=Opcode.UPDATE(), zones=[TEST_ZONE_RECORD], tsig_key=None): + msg = Message(Message.RENDER) + msg.set_qid(TEST_QID) + msg.set_opcode(opcode) + msg.set_rcode(Rcode.NOERROR()) + for z in zones: + msg.add_question(z) + + renderer = MessageRenderer() + if tsig_key is not None: + msg.to_wire(renderer, TSIGContext(tsig_key)) + else: + msg.to_wire(renderer) + + # re-read the created data in the parse mode + msg.clear(Message.PARSE) + msg.from_wire(renderer.get_data()) + + return renderer.get_data() + + +class TestDDNSession(unittest.TestCase): + def setUp(self): + cc_session = MyCCSession() + self.assertFalse(cc_session._started) + self.server = ddns.DDNSServer(cc_session) + self.server._UpdateSessionClass = FakeUpdateSession + self.__sock = FakeSocket(-1) + + def check_update_response(self, resp_wire, expected_rcode=Rcode.NOERROR()): + '''Check if given wire data are valid form of update response. + + In this implementation, zone/prerequisite/update sections should be + empty in responses. + + ''' + msg = Message(Message.PARSE) + msg.from_wire(resp_wire) + self.assertEqual(Opcode.UPDATE(), msg.get_opcode()) + self.assertEqual(expected_rcode, msg.get_rcode()) + self.assertEqual(TEST_QID, msg.get_qid()) + for section in [SECTION_ZONE, SECTION_PREREQUISITE, SECTION_UPDATE]: + self.assertEqual(0, msg.get_rr_count(section)) + + def test_handle_request(self): + self.assertTrue(self.server.handle_request((self.__sock, + TEST_SERVER6, TEST_CLIENT6, + create_msg()))) + self.assertEqual(TEST_CLIENT6, self.__sock._sent_addr) + self.check_update_response(self.__sock._sent_data) + + def test_broken_request(self): + # Message data too short + s = self.__sock + self.assertFalse(self.server.handle_request((self.__sock, None, + None, b'x' * 11))) + self.assertEqual((None, None), (s._sent_data, s._sent_addr)) + + # Opcode is not UPDATE + self.assertFalse(self.server.handle_request( + (self.__sock, None, None, create_msg(opcode=Opcode.QUERY())))) + self.assertEqual((None, None), (s._sent_data, s._sent_addr)) + class TestMain(unittest.TestCase): def setUp(self): self._server = MyDDNSServer()