xfrin.py.in 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. #!@PYTHON@
  2. # Copyright (C) 2010 Internet Systems Consortium.
  3. #
  4. # Permission to use, copy, modify, and distribute this software for any
  5. # purpose with or without fee is hereby granted, provided that the above
  6. # copyright notice and this permission notice appear in all copies.
  7. #
  8. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  9. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  10. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  11. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  12. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  13. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  14. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  15. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. # $Id$
  17. import sys; sys.path.append ('@@PYTHONPATH@@')
  18. import os
  19. import signal
  20. import isc
  21. import asyncore
  22. import struct
  23. import threading
  24. import socket
  25. import random
  26. from optparse import OptionParser, OptionValueError
  27. from isc.config.ccsession import *
  28. from isc.notify import notify_out
  29. try:
  30. from pydnspp import *
  31. except ImportError as e:
  32. # C++ loadable module may not be installed; even so the xfrin process
  33. # must keep running, so we warn about it and move forward.
  34. sys.stderr.write('[b10-xfrin] failed to import DNS module: %s\n' % str(e))
  35. # If B10_FROM_BUILD is set in the environment, we use data files
  36. # from a directory relative to that, otherwise we use the ones
  37. # installed on the system
  38. if "B10_FROM_BUILD" in os.environ:
  39. SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrin"
  40. AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
  41. else:
  42. PREFIX = "@prefix@"
  43. DATAROOTDIR = "@datarootdir@"
  44. SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
  45. AUTH_SPECFILE_PATH = SPECFILE_PATH
  46. SPECFILE_LOCATION = SPECFILE_PATH + "/xfrin.spec"
  47. AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
  48. XFROUT_MODULE_NAME = 'Xfrout'
  49. ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
  50. REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
  51. ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
  52. __version__ = 'BIND10'
  53. # define xfrin rcode
  54. XFRIN_OK = 0
  55. XFRIN_FAIL = 1
  56. DEFAULT_MASTER_PORT = '53'
  57. DEFAULT_MASTER = '127.0.0.1'
  58. def log_error(msg):
  59. sys.stderr.write("[b10-xfrin] %s\n" % str(msg))
  60. class XfrinException(Exception):
  61. pass
  62. class XfrinConnection(asyncore.dispatcher):
  63. '''Do xfrin in this class. '''
  64. def __init__(self,
  65. sock_map, zone_name, rrclass, db_file, shutdown_event,
  66. master_addrinfo, verbose = False, idle_timeout = 60):
  67. ''' idle_timeout: max idle time for read data from socket.
  68. db_file: specify the data source file.
  69. check_soa: when it's true, check soa first before sending xfr query
  70. '''
  71. asyncore.dispatcher.__init__(self, map=sock_map)
  72. self.create_socket(master_addrinfo[0], master_addrinfo[1])
  73. self._zone_name = zone_name
  74. self._sock_map = sock_map
  75. self._rrclass = rrclass
  76. self._db_file = db_file
  77. self._soa_rr_count = 0
  78. self._idle_timeout = idle_timeout
  79. self.setblocking(1)
  80. self._shutdown_event = shutdown_event
  81. self._verbose = verbose
  82. self._master_address = master_addrinfo[4]
  83. def connect_to_master(self):
  84. '''Connect to master in TCP.'''
  85. try:
  86. self.connect(self._master_address)
  87. return True
  88. except socket.error as e:
  89. self.log_msg('Failed to connect:(%s), %s' % (self._master_address,
  90. str(e)))
  91. return False
  92. def _create_query(self, query_type):
  93. '''Create dns query message. '''
  94. msg = Message(Message.RENDER)
  95. query_id = random.randint(0, 0xFFFF)
  96. self._query_id = query_id
  97. msg.set_qid(query_id)
  98. msg.set_opcode(Opcode.QUERY())
  99. msg.set_rcode(Rcode.NOERROR())
  100. query_question = Question(Name(self._zone_name), self._rrclass, query_type)
  101. msg.add_question(query_question)
  102. return msg
  103. def _send_data(self, data):
  104. size = len(data)
  105. total_count = 0
  106. while total_count < size:
  107. count = self.send(data[total_count:])
  108. total_count += count
  109. def _send_query(self, query_type):
  110. '''Send query message over TCP. '''
  111. msg = self._create_query(query_type)
  112. render = MessageRenderer()
  113. msg.to_wire(render)
  114. header_len = struct.pack('H', socket.htons(render.get_length()))
  115. self._send_data(header_len)
  116. self._send_data(render.get_data())
  117. def _asyncore_loop(self):
  118. '''
  119. This method is a trivial wrapper for asyncore.loop(). It's extracted from
  120. _get_request_response so that we can test the rest of the code without
  121. involving actual communication with a remote server.'''
  122. asyncore.loop(self._idle_timeout, map=self._sock_map, count=1)
  123. def _get_request_response(self, size):
  124. recv_size = 0
  125. data = b''
  126. while recv_size < size:
  127. self._recv_time_out = True
  128. self._need_recv_size = size - recv_size
  129. self._asyncore_loop()
  130. if self._recv_time_out:
  131. raise XfrinException('receive data from socket time out.')
  132. recv_size += self._recvd_size
  133. data += self._recvd_data
  134. return data
  135. def _check_soa_serial(self):
  136. ''' Compare the soa serial, if soa serial in master is less than
  137. the soa serial in local, Finish xfrin.
  138. False: soa serial in master is less or equal to the local one.
  139. True: soa serial in master is bigger
  140. '''
  141. self._send_query(RRType("SOA"))
  142. data_len = self._get_request_response(2)
  143. msg_len = socket.htons(struct.unpack('H', data_len)[0])
  144. soa_response = self._get_request_response(msg_len)
  145. msg = Message(Message.PARSE)
  146. msg.from_wire(soa_response)
  147. # perform some minimal level validation. It's an open issue how
  148. # strict we should be (see the comment in _check_response_header())
  149. self._check_response_header(msg)
  150. # TODO, need select soa record from data source then compare the two
  151. # serial, current just return OK, since this function hasn't been used
  152. # now.
  153. return XFRIN_OK
  154. def do_xfrin(self, check_soa, ixfr_first = False):
  155. '''Do xfr by sending xfr request and parsing response. '''
  156. try:
  157. ret = XFRIN_OK
  158. if check_soa:
  159. logstr = 'SOA check for \'%s\' ' % self._zone_name
  160. ret = self._check_soa_serial()
  161. logstr = 'transfer of \'%s\': AXFR ' % self._zone_name
  162. if ret == XFRIN_OK:
  163. self.log_msg(logstr + 'started')
  164. # TODO: .AXFR() RRType.AXFR()
  165. self._send_query(RRType(252))
  166. isc.datasrc.sqlite3_ds.load(self._db_file, self._zone_name,
  167. self._handle_xfrin_response)
  168. self.log_msg(logstr + 'succeeded')
  169. except XfrinException as e:
  170. self.log_msg(e)
  171. self.log_msg(logstr + 'failed')
  172. ret = XFRIN_FAIL
  173. #TODO, recover data source.
  174. except isc.datasrc.sqlite3_ds.Sqlite3DSError as e:
  175. self.log_msg(e)
  176. self.log_msg(logstr + 'failed')
  177. ret = XFRIN_FAIL
  178. except UserWarning as e:
  179. # XXX: this is an exception from our C++ library via the
  180. # Boost.Python binding. It would be better to have more more
  181. # specific exceptions, but at this moment this is the finest
  182. # granularity.
  183. self.log_msg(e)
  184. self.log_msg(logstr + 'failed')
  185. ret = XFRIN_FAIL
  186. finally:
  187. self.close()
  188. return ret
  189. def _check_response_header(self, msg):
  190. '''Perform minimal validation on responses'''
  191. # It's not clear how strict we should be about response validation.
  192. # BIND 9 ignores some cases where it would normally be considered a
  193. # bogus response. For example, it accepts a response even if its
  194. # opcode doesn't match that of the corresponding request.
  195. # According to an original developer of BIND 9 some of the missing
  196. # checks are deliberate to be kind to old implementations that would
  197. # cause interoperability trouble with stricter checks.
  198. msg_rcode = msg.get_rcode()
  199. if msg_rcode != Rcode.NOERROR():
  200. raise XfrinException('error response: %s' % msg_rcode.to_text())
  201. if not msg.get_header_flag(MessageFlag.QR()):
  202. raise XfrinException('response is not a response ')
  203. if msg.get_qid() != self._query_id:
  204. raise XfrinException('bad query id')
  205. def _check_response_status(self, msg):
  206. '''Check validation of xfr response. '''
  207. self._check_response_header(msg)
  208. if msg.get_rr_count(Section.ANSWER()) == 0:
  209. raise XfrinException('answer section is empty')
  210. if msg.get_rr_count(Section.QUESTION()) > 1:
  211. raise XfrinException('query section count greater than 1')
  212. def _handle_answer_section(self, answer_section):
  213. '''Return a generator for the reponse in one tcp package to a zone transfer.'''
  214. for rrset in answer_section:
  215. rrset_name = rrset.get_name().to_text()
  216. rrset_ttl = int(rrset.get_ttl().to_text())
  217. rrset_class = rrset.get_class().to_text()
  218. rrset_type = rrset.get_type().to_text()
  219. for rdata in rrset.get_rdata():
  220. # Count the soa record count
  221. if rrset.get_type() == RRType("SOA"):
  222. self._soa_rr_count += 1
  223. # XXX: the current DNS message parser can't preserve the
  224. # RR order or separete the beginning and ending SOA RRs.
  225. # As a short term workaround, we simply ignore the second
  226. # SOA, and ignore the erroneous case where the transfer
  227. # session doesn't end with an SOA.
  228. if (self._soa_rr_count == 2):
  229. # Avoid inserting soa record twice
  230. break
  231. rdata_text = rdata.to_text()
  232. yield (rrset_name, rrset_ttl, rrset_class, rrset_type,
  233. rdata_text)
  234. def _handle_xfrin_response(self):
  235. '''Return a generator for the response to a zone transfer. '''
  236. while True:
  237. data_len = self._get_request_response(2)
  238. msg_len = socket.htons(struct.unpack('H', data_len)[0])
  239. recvdata = self._get_request_response(msg_len)
  240. msg = Message(Message.PARSE)
  241. msg.from_wire(recvdata)
  242. self._check_response_status(msg)
  243. answer_section = msg.get_section(Section.ANSWER())
  244. for rr in self._handle_answer_section(answer_section):
  245. yield rr
  246. if self._soa_rr_count == 2:
  247. break
  248. if self._shutdown_event.is_set():
  249. raise XfrinException('xfrin is forced to stop')
  250. def handle_read(self):
  251. '''Read query's response from socket. '''
  252. self._recvd_data = self.recv(self._need_recv_size)
  253. self._recvd_size = len(self._recvd_data)
  254. self._recv_time_out = False
  255. def writable(self):
  256. '''Ignore the writable socket. '''
  257. return False
  258. def log_info(self, msg, type='info'):
  259. # Overwrite the log function, log nothing
  260. pass
  261. def log_msg(self, msg):
  262. if self._verbose:
  263. sys.stdout.write('[b10-xfrin] %s\n' % str(msg))
  264. def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
  265. shutdown_event, master_addrinfo, check_soa, verbose):
  266. xfrin_recorder.increment(zone_name)
  267. sock_map = {}
  268. conn = XfrinConnection(sock_map, zone_name, rrclass, db_file,
  269. shutdown_event, master_addrinfo, verbose)
  270. if conn.connect_to_master():
  271. ret = conn.do_xfrin(check_soa)
  272. server.publish_xfrin_news(zone_name, rrclass, ret)
  273. xfrin_recorder.decrement(zone_name)
  274. class XfrinRecorder:
  275. def __init__(self):
  276. self._lock = threading.Lock()
  277. self._zones = []
  278. def increment(self, zone_name):
  279. self._lock.acquire()
  280. self._zones.append(zone_name)
  281. self._lock.release()
  282. def decrement(self, zone_name):
  283. self._lock.acquire()
  284. if zone_name in self._zones:
  285. self._zones.remove(zone_name)
  286. self._lock.release()
  287. def xfrin_in_progress(self, zone_name):
  288. self._lock.acquire()
  289. ret = zone_name in self._zones
  290. self._lock.release()
  291. return ret
  292. def count(self):
  293. self._lock.acquire()
  294. ret = len(self._zones)
  295. self._lock.release()
  296. return ret
  297. class Xfrin:
  298. def __init__(self, verbose = False):
  299. self._max_transfers_in = 10
  300. #TODO, this is the temp way to set the zone's master.
  301. self._master_addr = DEFAULT_MASTER
  302. self._master_port = DEFAULT_MASTER_PORT
  303. self._cc_setup()
  304. self.recorder = XfrinRecorder()
  305. self._shutdown_event = threading.Event()
  306. self._verbose = verbose
  307. def _cc_setup(self):
  308. '''This method is used only as part of initialization, but is
  309. implemented separately for convenience of unit tests; by letting
  310. the test code override this method we can test most of this class
  311. without requiring a command channel.'''
  312. # Create one session for sending command to other modules, because the
  313. # listening session will block the send operation.
  314. self._send_cc_session = isc.cc.Session()
  315. self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
  316. self.config_handler,
  317. self.command_handler)
  318. self._module_cc.start()
  319. config_data = self._module_cc.get_full_config()
  320. self._max_transfers_in = config_data.get("transfers_in")
  321. self._master_addr = config_data.get('master_addr') or self._master_addr
  322. self._master_port = config_data.get('master_port') or self._master_port
  323. def _cc_check_command(self):
  324. '''This is a straightforward wrapper for cc.check_command,
  325. but provided as a separate method for the convenience
  326. of unit tests.'''
  327. self._module_cc.check_command()
  328. def config_handler(self, new_config):
  329. self._max_transfers_in = new_config.get("transfers_in") or self._max_transfers_in
  330. if ('master_addr' in new_config) or ('master_port' in new_config):
  331. # Check if the new master is valid, there should be library for check it.
  332. # and user should change the port and address together.
  333. try:
  334. addr = new_config.get('master_addr') or self._master_addr
  335. port = new_config.get('master_port') or self._master_port
  336. check_addr_port(addr, port)
  337. self._master_addr = addr
  338. self._master_port = port
  339. except:
  340. errmsg = "bad format for zone's master: " + str(new_config)
  341. log_error(errmsg)
  342. return create_answer(1, errmsg)
  343. return create_answer(0)
  344. def shutdown(self):
  345. ''' shutdown the xfrin process. the thread which is doing xfrin should be
  346. terminated.
  347. '''
  348. self._shutdown_event.set()
  349. main_thread = threading.currentThread()
  350. for th in threading.enumerate():
  351. if th is main_thread:
  352. continue
  353. th.join()
  354. def command_handler(self, command, args):
  355. answer = create_answer(0)
  356. try:
  357. if command == 'shutdown':
  358. self._shutdown_event.set()
  359. elif command == 'notify' or command == REFRESH_FROM_ZONEMGR:
  360. # Xfrin receives the refresh/notify command from zone manager.
  361. # notify command maybe has the parameters which
  362. # specify the notifyfrom address and port, according the RFC1996, zone
  363. # transfer should starts first from the notifyfrom, but now, let 'TODO' it.
  364. (zone_name, rrclass) = self._parse_zone_name_and_class(args)
  365. (master_addr) = check_addr_port(self._master_addr, self._master_port)
  366. ret = self.xfrin_start(zone_name,
  367. rrclass,
  368. self._get_db_file(),
  369. master_addr,
  370. True)
  371. answer = create_answer(ret[0], ret[1])
  372. elif command == 'retransfer' or command == 'refresh':
  373. # Xfrin receives the retransfer/refresh from cmdctl(sent by bindctl).
  374. # If the command has specified master address, do transfer from the
  375. # master address, or else do transfer from the configured masters.
  376. (zone_name, rrclass) = self._parse_zone_name_and_class(args)
  377. master_addr = self._parse_master_and_port(args)
  378. db_file = args.get('db_file') or self._get_db_file()
  379. ret = self.xfrin_start(zone_name,
  380. rrclass,
  381. db_file,
  382. master_addr,
  383. (False if command == 'retransfer' else True))
  384. answer = create_answer(ret[0], ret[1])
  385. else:
  386. answer = create_answer(1, 'unknown command: ' + command)
  387. except XfrinException as err:
  388. log_error('error happened for command: %s, %s' % (command, str(err)) )
  389. answer = create_answer(1, str(err))
  390. return answer
  391. def _parse_zone_name_and_class(self, args):
  392. zone_name = args.get('zone_name')
  393. if not zone_name:
  394. raise XfrinException('zone name should be provided')
  395. rrclass = args.get('zone_class')
  396. if not rrclass:
  397. rrclass = RRClass.IN()
  398. else:
  399. try:
  400. rrclass = RRClass(rrclass)
  401. except InvalidRRClass as e:
  402. raise XfrinException('invalid RRClass: ' + rrclass)
  403. return zone_name, rrclass
  404. def _parse_master_and_port(self, args):
  405. port = args.get('port') or self._master_port
  406. master = args.get('master') or self._master_addr
  407. return check_addr_port(master, port)
  408. def _get_db_file(self):
  409. #TODO, the db file path should be got in auth server's configuration
  410. # if we need access to this configuration more often, we
  411. # should add it on start, and not remove it here
  412. # (or, if we have writable ds, we might not need this in
  413. # the first place)
  414. self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
  415. db_file, is_default = self._module_cc.get_remote_config_value("Auth", "database_file")
  416. if is_default and "B10_FROM_BUILD" in os.environ:
  417. # this too should be unnecessary, but currently the
  418. # 'from build' override isn't stored in the config
  419. # (and we don't have writable datasources yet)
  420. db_file = os.environ["B10_FROM_BUILD"] + os.sep + "bind10_zones.sqlite3"
  421. self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
  422. return db_file
  423. def publish_xfrin_news(self, zone_name, zone_class, xfr_result):
  424. '''Send command to xfrout/zone manager module.
  425. If xfrin has finished successfully for one zone, tell the good
  426. news(command: zone_new_data_ready) to zone manager and xfrout.
  427. if xfrin failed, just tell the bad news to zone manager, so that
  428. it can reset the refresh timer for that zone. '''
  429. param = {'zone_name': zone_name, 'zone_class': zone_class.to_text()}
  430. if xfr_result == XFRIN_OK:
  431. msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
  432. # catch the exception, in case msgq has been killed.
  433. try:
  434. self._send_cc_session.group_sendmsg(msg, XFROUT_MODULE_NAME)
  435. self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
  436. except:
  437. pass
  438. else:
  439. msg = create_command(ZONE_XFRIN_FAILED, param)
  440. # catch the exception, in case msgq has been killed.
  441. try:
  442. self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
  443. except:
  444. pass
  445. def startup(self):
  446. while not self._shutdown_event.is_set():
  447. self._cc_check_command()
  448. def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
  449. check_soa = True):
  450. if "pydnspp" not in sys.modules:
  451. return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
  452. # check max_transfer_in, else return quota error
  453. if self.recorder.count() >= self._max_transfers_in:
  454. return (1, 'xfrin quota error')
  455. if self.recorder.xfrin_in_progress(zone_name):
  456. return (1, 'zone xfrin is in progress')
  457. xfrin_thread = threading.Thread(target = process_xfrin,
  458. args = (self,
  459. self.recorder,
  460. zone_name, rrclass,
  461. db_file,
  462. self._shutdown_event,
  463. master_addrinfo, check_soa,
  464. self._verbose))
  465. xfrin_thread.start()
  466. return (0, 'zone xfrin is started')
  467. xfrind = None
  468. def signal_handler(signal, frame):
  469. if xfrind:
  470. xfrind.shutdown()
  471. sys.exit(0)
  472. def set_signal_handler():
  473. signal.signal(signal.SIGTERM, signal_handler)
  474. signal.signal(signal.SIGINT, signal_handler)
  475. def check_addr_port(addrstr, portstr):
  476. # XXX: Linux (glibc)'s getaddrinfo incorrectly accepts numeric port
  477. # string larger than 65535. So we need to explicit validate it separately.
  478. try:
  479. portnum = int(portstr)
  480. if portnum < 0 or portnum > 65535:
  481. raise ValueError("invalid port number (out of range): " + portstr)
  482. except ValueError as err:
  483. raise XfrinException("failed to resolve master address/port=%s/%s: %s" %
  484. (addrstr, portstr, str(err)))
  485. try:
  486. addrinfo = socket.getaddrinfo(addrstr, portstr, socket.AF_UNSPEC,
  487. socket.SOCK_STREAM, socket.IPPROTO_TCP,
  488. socket.AI_NUMERICHOST|
  489. socket.AI_NUMERICSERV)
  490. except socket.gaierror as err:
  491. raise XfrinException("failed to resolve master address/port=%s/%s: %s" %
  492. (addrstr, portstr, str(err)))
  493. if len(addrinfo) != 1:
  494. # with the parameters above the result must be uniquely determined.
  495. errmsg = "unexpected result for address/port resolution for %s:%s"
  496. raise XfrinException(errmsg % (addrstr, portstr))
  497. return addrinfo[0]
  498. def set_cmd_options(parser):
  499. parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
  500. help="display more about what is going on")
  501. def main(xfrin_class, use_signal = True):
  502. """The main loop of the Xfrin daemon.
  503. @param xfrin_class: A class of the Xfrin object. This is normally Xfrin,
  504. but can be a subclass of it for customization.
  505. @param use_signal: True if this process should catch signals. This is
  506. normally True, but may be disabled when this function is called in a
  507. testing context."""
  508. global xfrind
  509. try:
  510. parser = OptionParser(version = __version__)
  511. set_cmd_options(parser)
  512. (options, args) = parser.parse_args()
  513. if use_signal:
  514. set_signal_handler()
  515. xfrind = xfrin_class(verbose = options.verbose)
  516. xfrind.startup()
  517. except KeyboardInterrupt:
  518. log_error("exit b10-xfrin")
  519. except isc.cc.session.SessionError as e:
  520. log_error(str(e))
  521. log_error('Error happened! is the command channel daemon running?')
  522. except Exception as e:
  523. log_error(str(e))
  524. if xfrind:
  525. xfrind.shutdown()
  526. if __name__ == '__main__':
  527. main(Xfrin)