|
@@ -23,11 +23,12 @@ import bind10_config
|
|
from isc.dns import *
|
|
from isc.dns import *
|
|
import isc.ddns.session
|
|
import isc.ddns.session
|
|
from isc.ddns.zone_config import ZoneConfig
|
|
from isc.ddns.zone_config import ZoneConfig
|
|
-from isc.ddns.logger import ClientFormatter
|
|
+from isc.ddns.logger import ClientFormatter, ZoneFormatter
|
|
from isc.config.ccsession import *
|
|
from isc.config.ccsession import *
|
|
-from isc.cc import SessionError, SessionTimeout
|
|
+from isc.cc import SessionError, SessionTimeout, ProtocolError
|
|
import isc.util.process
|
|
import isc.util.process
|
|
import isc.util.cio.socketsession
|
|
import isc.util.cio.socketsession
|
|
|
|
+from isc.notify.notify_out import ZONE_NEW_DATA_READY_CMD
|
|
import isc.server_common.tsig_keyring
|
|
import isc.server_common.tsig_keyring
|
|
from isc.datasrc import DataSourceClient
|
|
from isc.datasrc import DataSourceClient
|
|
import select
|
|
import select
|
|
@@ -79,6 +80,9 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + '/auth.spec'
|
|
|
|
|
|
isc.util.process.rename()
|
|
isc.util.process.rename()
|
|
|
|
|
|
|
|
+# Cooperating modules
|
|
|
|
+XFROUT_MODULE_NAME = 'Xfrout'
|
|
|
|
+
|
|
class DDNSConfigError(Exception):
|
|
class DDNSConfigError(Exception):
|
|
'''An exception indicating an error in updating ddns configuration.
|
|
'''An exception indicating an error in updating ddns configuration.
|
|
|
|
|
|
@@ -193,7 +197,7 @@ class DDNSServer:
|
|
# DDNS Protocol handling class.
|
|
# DDNS Protocol handling class.
|
|
self._UpdateSessionClass = isc.ddns.session.UpdateSession
|
|
self._UpdateSessionClass = isc.ddns.session.UpdateSession
|
|
|
|
|
|
- class SessionError(Exception):
|
|
+ class InternalError(Exception):
|
|
'''Exception for internal errors in an update session.
|
|
'''Exception for internal errors in an update session.
|
|
|
|
|
|
This exception is expected to be caught within the server class,
|
|
This exception is expected to be caught within the server class,
|
|
@@ -301,8 +305,8 @@ class DDNSServer:
|
|
isc.server_common.tsig_keyring.get_keyring())
|
|
isc.server_common.tsig_keyring.get_keyring())
|
|
tsig_error = tsig_ctx.verify(tsig_record, req_data)
|
|
tsig_error = tsig_ctx.verify(tsig_record, req_data)
|
|
if tsig_error != TSIGError.NOERROR:
|
|
if tsig_error != TSIGError.NOERROR:
|
|
- raise SessionError("Failed to verify request's TSIG: " +
|
|
+ raise self.InternalError("Failed to verify request's TSIG: " +
|
|
- str(tsig_error))
|
|
+ str(tsig_error))
|
|
return tsig_ctx
|
|
return tsig_ctx
|
|
|
|
|
|
def handle_request(self, req_session):
|
|
def handle_request(self, req_session):
|
|
@@ -339,13 +343,14 @@ class DDNSServer:
|
|
# as an internal error and don't bother to respond.
|
|
# as an internal error and don't bother to respond.
|
|
try:
|
|
try:
|
|
if sock.proto == socket.IPPROTO_TCP:
|
|
if sock.proto == socket.IPPROTO_TCP:
|
|
- raise SessionError('TCP requests are not yet supported')
|
|
+ raise self.InternalError('TCP requests are not yet supported')
|
|
self.__request_msg.clear(Message.PARSE)
|
|
self.__request_msg.clear(Message.PARSE)
|
|
# specify PRESERVE_ORDER as we need to handle each RR separately.
|
|
# specify PRESERVE_ORDER as we need to handle each RR separately.
|
|
self.__request_msg.from_wire(req_data, Message.PRESERVE_ORDER)
|
|
self.__request_msg.from_wire(req_data, Message.PRESERVE_ORDER)
|
|
if self.__request_msg.get_opcode() != Opcode.UPDATE():
|
|
if self.__request_msg.get_opcode() != Opcode.UPDATE():
|
|
- raise SessionError('Update request has unexpected opcode: ' +
|
|
+ raise self.InternalError('Update request has unexpected '
|
|
- str(self.__request_msg.get_opcode()))
|
|
+ 'opcode: ' +
|
|
|
|
+ str(self.__request_msg.get_opcode()))
|
|
tsig_ctx = self.__check_request_tsig(self.__request_msg, req_data)
|
|
tsig_ctx = self.__check_request_tsig(self.__request_msg, req_data)
|
|
except Exception as ex:
|
|
except Exception as ex:
|
|
logger.error(DDNS_REQUEST_PARSE_FAIL, ex)
|
|
logger.error(DDNS_REQUEST_PARSE_FAIL, ex)
|
|
@@ -371,15 +376,75 @@ class DDNSServer:
|
|
msg.to_wire(self.__response_renderer, tsig_ctx)
|
|
msg.to_wire(self.__response_renderer, tsig_ctx)
|
|
else:
|
|
else:
|
|
msg.to_wire(self.__response_renderer)
|
|
msg.to_wire(self.__response_renderer)
|
|
|
|
+
|
|
|
|
+ ret = self.__send_response(sock, self.__response_renderer.get_data(),
|
|
|
|
+ remote_addr)
|
|
|
|
+ if result == isc.ddns.session.UPDATE_SUCCESS:
|
|
|
|
+ self.__notify_update(zname, zclass)
|
|
|
|
+ return ret
|
|
|
|
+
|
|
|
|
+ def __send_response(self, sock, data, dest):
|
|
|
|
+ '''Send DDNS response to the client.
|
|
|
|
+
|
|
|
|
+ Right now, this is a straightforward subroutine of handle_request(),
|
|
|
|
+ but is intended to be extended evetually so that it can handle more
|
|
|
|
+ comlicated operations for TCP (which requires asynchronous write).
|
|
|
|
+ Further, when we support multiple requests over a single TCP
|
|
|
|
+ connection, this method may even be shared by multiple methods.
|
|
|
|
+
|
|
|
|
+ Parameters:
|
|
|
|
+ sock: (python socket) the socket to which the response should be sent.
|
|
|
|
+ data: (binary) the response data
|
|
|
|
+ dest: (python socket address) the destion address to which the response
|
|
|
|
+ should be sent.
|
|
|
|
+
|
|
|
|
+ Return: True if the send operation succeds; otherwise False.
|
|
|
|
+
|
|
|
|
+ '''
|
|
try:
|
|
try:
|
|
- sock.sendto(self.__response_renderer.get_data(), remote_addr)
|
|
+ sock.sendto(data, dest)
|
|
except socket.error as ex:
|
|
except socket.error as ex:
|
|
- logger.error(DDNS_RESPONSE_SOCKET_ERROR,
|
|
+ logger.error(DDNS_RESPONSE_SOCKET_ERROR, ClientFormatter(dest), ex)
|
|
- ClientFormatter(remote_addr), ex)
|
|
|
|
return False
|
|
return False
|
|
|
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
+ def __notify_update(self, zname, zclass):
|
|
|
|
+ '''Notify other modules of the update.
|
|
|
|
+
|
|
|
|
+ Note that we use blocking communication here. While the internal
|
|
|
|
+ communication bus is generally expected to be pretty responsive and
|
|
|
|
+ error free, notable delay can still occur, and in worse cases timeouts
|
|
|
|
+ or connection reset can happen. In these cases, even if the trouble
|
|
|
|
+ is temporary, the update service will be suspended for a while.
|
|
|
|
+ For a longer term we'll need to switch to asynchronous communication,
|
|
|
|
+ but for now we rely on the blocking operation.
|
|
|
|
+
|
|
|
|
+ Note also that we directly refer to the "protected" member of
|
|
|
|
+ ccsession (_cc._session) rather than creating a separate channel.
|
|
|
|
+ It's probably not the best practice, but hopefully we can introduce
|
|
|
|
+ a cleaner way when we support asynchronous communication.
|
|
|
|
+ At the moment we prefer the brevity with the use of internal channel
|
|
|
|
+ of the cc session.
|
|
|
|
+
|
|
|
|
+ '''
|
|
|
|
+ param = {'zone_name': zname.to_text(), 'zone_class': zclass.to_text()}
|
|
|
|
+ msg = create_command(ZONE_NEW_DATA_READY_CMD, param)
|
|
|
|
+ modname = XFROUT_MODULE_NAME
|
|
|
|
+ try:
|
|
|
|
+ seq = self._cc._session.group_sendmsg(msg, modname)
|
|
|
|
+ answer, _ = self._cc._session.group_recvmsg(False, seq)
|
|
|
|
+ rcode, error_msg = parse_answer(answer)
|
|
|
|
+ except (SessionTimeout, SessionError, ProtocolError) as ex:
|
|
|
|
+ rcode = 1
|
|
|
|
+ error_msg = str(ex)
|
|
|
|
+ if rcode == 0:
|
|
|
|
+ logger.debug(TRACE_BASIC, DDNS_UPDATE_NOTIFY, modname,
|
|
|
|
+ ZoneFormatter(zname, zclass))
|
|
|
|
+ else:
|
|
|
|
+ logger.error(DDNS_UPDATE_NOTIFY_FAIL, modname,
|
|
|
|
+ ZoneFormatter(zname, zclass), error_msg)
|
|
|
|
+
|
|
def handle_session(self, fileno):
|
|
def handle_session(self, fileno):
|
|
"""
|
|
"""
|
|
Handle incoming session on the socket with given fileno.
|
|
Handle incoming session on the socket with given fileno.
|