xfrin.py.in 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370
  1. #!@PYTHON@
  2. # Copyright (C) 2009-2011 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. import sys; sys.path.append ('@@PYTHONPATH@@')
  17. import os
  18. import signal
  19. import isc
  20. import asyncore
  21. import struct
  22. import threading
  23. import socket
  24. import random
  25. from optparse import OptionParser, OptionValueError
  26. from isc.config.ccsession import *
  27. from isc.notify import notify_out
  28. import isc.util.process
  29. from isc.datasrc import DataSourceClient, ZoneFinder
  30. import isc.net.parse
  31. from isc.xfrin.diff import Diff
  32. from isc.log_messages.xfrin_messages import *
  33. isc.log.init("b10-xfrin")
  34. logger = isc.log.Logger("xfrin")
  35. try:
  36. from pydnspp import *
  37. except ImportError as e:
  38. # C++ loadable module may not be installed; even so the xfrin process
  39. # must keep running, so we warn about it and move forward.
  40. logger.error(XFRIN_IMPORT_DNS, str(e))
  41. isc.util.process.rename()
  42. # If B10_FROM_BUILD is set in the environment, we use data files
  43. # from a directory relative to that, otherwise we use the ones
  44. # installed on the system
  45. if "B10_FROM_BUILD" in os.environ:
  46. SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrin"
  47. AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
  48. else:
  49. PREFIX = "@prefix@"
  50. DATAROOTDIR = "@datarootdir@"
  51. SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
  52. AUTH_SPECFILE_PATH = SPECFILE_PATH
  53. SPECFILE_LOCATION = SPECFILE_PATH + "/xfrin.spec"
  54. AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
  55. XFROUT_MODULE_NAME = 'Xfrout'
  56. ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
  57. REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
  58. ZONE_XFRIN_FAILED = 'zone_xfrin_failed'
  59. # Constants for debug levels.
  60. DBG_XFRIN_TRACE = logger.DBGLVL_TRACE_BASIC
  61. # These two default are currently hard-coded. For config this isn't
  62. # necessary, but we need these defaults for optional command arguments
  63. # (TODO: have similar support to get default values for command
  64. # arguments as we do for config options)
  65. DEFAULT_MASTER_PORT = 53
  66. DEFAULT_ZONE_CLASS = RRClass.IN()
  67. __version__ = 'BIND10'
  68. # define xfrin rcode
  69. XFRIN_OK = 0
  70. XFRIN_FAIL = 1
  71. class XfrinException(Exception):
  72. pass
  73. class XfrinProtocolError(Exception):
  74. '''An exception raised for errors encountered in xfrin protocol handling.
  75. '''
  76. pass
  77. class XfrinZoneInfoException(Exception):
  78. """This exception is raised if there is an error in the given
  79. configuration (part), or when a command does not have a required
  80. argument or has bad arguments, for instance when the zone's master
  81. address is not a valid IP address, when the zone does not
  82. have a name, or when multiple settings are given for the same
  83. zone."""
  84. pass
  85. def _check_zone_name(zone_name_str):
  86. """Checks if the given zone name is a valid domain name, and returns
  87. it as a Name object. Raises an XfrinException if it is not."""
  88. try:
  89. # In the _zones dict, part of the key is the zone name,
  90. # but due to a limitation in the Name class, we
  91. # cannot directly use it as a dict key, and we use to_text()
  92. #
  93. # Downcase the name here for that reason.
  94. return Name(zone_name_str, True)
  95. except (EmptyLabel, TooLongLabel, BadLabelType, BadEscape,
  96. TooLongName, IncompleteName) as ne:
  97. raise XfrinZoneInfoException("bad zone name: " + zone_name_str + " (" + str(ne) + ")")
  98. def _check_zone_class(zone_class_str):
  99. """If the given argument is a string: checks if the given class is
  100. a valid one, and returns an RRClass object if so.
  101. Raises XfrinZoneInfoException if not.
  102. If it is None, this function returns the default RRClass.IN()"""
  103. if zone_class_str is None:
  104. return DEFAULT_ZONE_CLASS
  105. try:
  106. return RRClass(zone_class_str)
  107. except InvalidRRClass as irce:
  108. raise XfrinZoneInfoException("bad zone class: " + zone_class_str + " (" + str(irce) + ")")
  109. def format_zone_str(zone_name, zone_class):
  110. """Helper function to format a zone name and class as a string of
  111. the form '<name>/<class>'.
  112. Parameters:
  113. zone_name (isc.dns.Name) name to format
  114. zone_class (isc.dns.RRClass) class to format
  115. """
  116. return zone_name.to_text(True) + '/' + str(zone_class)
  117. def format_addrinfo(addrinfo):
  118. """Helper function to format the addrinfo as a string of the form
  119. <addr>:<port> (for IPv4) or [<addr>]:port (for IPv6). For unix domain
  120. sockets, and unknown address families, it returns a basic string
  121. conversion of the third element of the passed tuple.
  122. Parameters:
  123. addrinfo: a 3-tuple consisting of address family, socket type, and,
  124. depending on the family, either a 2-tuple with the address
  125. and port, or a filename
  126. """
  127. try:
  128. if addrinfo[0] == socket.AF_INET:
  129. return str(addrinfo[2][0]) + ":" + str(addrinfo[2][1])
  130. elif addrinfo[0] == socket.AF_INET6:
  131. return "[" + str(addrinfo[2][0]) + "]:" + str(addrinfo[2][1])
  132. else:
  133. return str(addrinfo[2])
  134. except IndexError:
  135. raise TypeError("addrinfo argument to format_addrinfo() does not "
  136. "appear to be consisting of (family, socktype, (addr, port))")
  137. def get_soa_serial(soa_rdata):
  138. '''Extract the serial field of an SOA RDATA and returns it as an intger.
  139. We don't have to be very efficient here, so we first dump the entire RDATA
  140. as a string and convert the first corresponding field. This should be
  141. sufficient in practice, but may not always work when the MNAME or RNAME
  142. contains an (escaped) space character in their labels. Ideally there
  143. should be a more direct and convenient way to get access to the SOA
  144. fields.
  145. '''
  146. return int(soa_rdata.to_text().split()[2])
  147. class XfrinState:
  148. '''
  149. The states of the incomding *XFR state machine.
  150. We (will) handle both IXFR and AXFR with a single integrated state
  151. machine because they cannot be distinguished immediately - an AXFR
  152. response to an IXFR request can only be detected when the first two (2)
  153. response RRs have already been received.
  154. The following diagram summarizes the state transition. After sending
  155. the query, xfrin starts the process with the InitialSOA state (all
  156. IXFR/AXFR response begins with an SOA). When it reaches IXFREnd
  157. or AXFREnd, the process successfully completes.
  158. (AXFR or
  159. (recv SOA) AXFR-style IXFR) (SOA, add)
  160. InitialSOA------->FirstData------------->AXFR--------->AXFREnd
  161. | | ^ (post xfr
  162. | | | checks, then
  163. | +--+ commit)
  164. | (non SOA, add)
  165. |
  166. | (non SOA, delete)
  167. (pure IXFR,| +-------+
  168. keep handling)| (Delete SOA) V |
  169. + ->IXFRDeleteSOA------>IXFRDelete--+
  170. ^ |
  171. (see SOA, not end, | (see SOA)|
  172. commit, keep handling) | |
  173. | V
  174. +---------IXFRAdd<----------+IXFRAddSOA
  175. (non SOA, add)| ^ | (Add SOA)
  176. ----------+ |
  177. |(see SOA w/ end serial, commit changes)
  178. V
  179. IXFREnd
  180. Note that changes are committed for every "difference sequence"
  181. (i.e. changes for one SOA update). This means when an IXFR response
  182. contains multiple difference sequences and something goes wrong
  183. after several commits, these changes have been published and visible
  184. to clients even if the IXFR session is subsequently aborted.
  185. It is not clear if this is valid in terms of the protocol specification.
  186. Section 4 of RFC 1995 states:
  187. An IXFR client, should only replace an older version with a newer
  188. version after all the differences have been successfully processed.
  189. If this "replacement" is for the changes of one difference sequence
  190. and "all the differences" mean the changes for that sequence, this
  191. implementation strictly follows what RFC states. If this is for
  192. the entire IXFR response (that may contain multiple sequences),
  193. we should implement it with one big transaction and one final commit
  194. at the very end.
  195. For now, we implement it with multiple smaller commits for two
  196. reasons. First, this is what BIND 9 does, and we generally port
  197. the implementation logic here. BIND 9 has been supporting IXFR
  198. for many years, so the fact that it still behaves this way
  199. probably means it at least doesn't cause a severe operational
  200. problem in practice. Second, especially because BIND 10 would
  201. often uses a database backend, a larger transaction could cause an
  202. undesirable effects, e.g. suspending normal lookups for a longer
  203. period depending on the characteristics of the database. Even if
  204. we find something wrong in a later sequeunce and abort the
  205. session, we can start another incremental update from what has
  206. been validated, or we can switch to AXFR to replace the zone
  207. completely.
  208. This implementation uses the state design pattern, where each state
  209. is represented as a subclass of the base XfrinState class. Each concrete
  210. subclass of XfrinState is assumed to define two methods: handle_rr() and
  211. finish_message(). These methods handle specific part of XFR protocols
  212. and (if necessary) perform the state transition.
  213. Conceptually, XfrinState and its subclasses are a "friend" of
  214. XfrinConnection and are assumed to be allowed to access its internal
  215. information (even though Python does not have a strict access control
  216. between different classes).
  217. The XfrinState and its subclasses are designed to be stateless, and
  218. can be used as singleton objects. For now, however, we always instantiate
  219. a new object for every state transition, partly because the introduction
  220. of singleton will make a code bit complicated, and partly because
  221. the overhead of object instantiotion wouldn't be significant for xfrin.
  222. '''
  223. def set_xfrstate(self, conn, new_state):
  224. '''Set the XfrConnection to a given new state.
  225. As a "friend" class, this method intentionally gets access to the
  226. connection's "private" method.
  227. '''
  228. conn._XfrinConnection__set_xfrstate(new_state)
  229. def handle_rr(self, conn):
  230. '''Handle one RR of an XFR response message.
  231. Depending on the state, the RR is generally added or deleted in the
  232. corresponding data source, or in some special cases indicates
  233. a specifi transition, such as starting a new IXFR difference
  234. sequence or completing the session.
  235. All subclass has their specific behaviors for this method, so
  236. there is no default definition. If the base class version
  237. is called, it's a bug of the caller, and it's notified via
  238. an XfrinException exception.
  239. This method returns a boolean value: True if the given RR was
  240. fully handled and the caller should go to the next RR; False
  241. if the caller needs to call this method with the (possibly) new
  242. state for the same RR again.
  243. '''
  244. raise XfrinException("Internal bug: " +
  245. "XfrinState.handle_rr() called directly")
  246. def finish_message(self, conn):
  247. '''Perform any final processing after handling all RRs of a response.
  248. This method then returns a boolean indicating whether to continue
  249. receiving the message. Unless it's in the end of the entire XFR
  250. session, we should continue, so this default method simply returns
  251. True.
  252. '''
  253. return True
  254. class XfrinInitialSOA(XfrinState):
  255. def handle_rr(self, conn, rr):
  256. if rr.get_type() != RRType.SOA():
  257. raise XfrinProtocolError('First RR in zone transfer must be SOA ('
  258. + rr.get_type().to_text() + ' received)')
  259. conn._end_serial = get_soa_serial(rr.get_rdata()[0])
  260. # FIXME: we need to check the serial is actually greater than ours.
  261. # To do so, however, we need to implement serial number arithmetic.
  262. # Although it wouldn't be a big task, we'll leave it for a separate
  263. # task for now. (Always performing xfr could be inefficient, but
  264. # shouldn't do any harm otherwise)
  265. self.set_xfrstate(conn, XfrinFirstData())
  266. return True
  267. class XfrinFirstData(XfrinState):
  268. def handle_rr(self, conn, rr):
  269. '''Handle the first RR after initial SOA in an XFR session.
  270. This state happens exactly once in an XFR session, where
  271. we decide whether it's incremental update ("real" IXFR) or
  272. non incremental update (AXFR or AXFR-style IXFR).
  273. If we initiated IXFR and the transfer begins with two SOAs
  274. (the serial of the second one being equal to our serial),
  275. it's incremental; otherwise it's non incremental.
  276. This method always return False (unlike many other handle_rr()
  277. methods) because this first RR must be examined again in the
  278. determined update context.
  279. Note that in the non incremental case the RR should normally be
  280. something other SOA, but it's still possible it's an SOA with a
  281. different serial than ours. The only possible interpretation at
  282. this point is that it's non incremental update that only consists
  283. of the SOA RR. It will result in broken zone (for example, it
  284. wouldn't even contain an apex NS) and should be rejected at post
  285. XFR processing, but in terms of the XFR session processing we
  286. accept it and move forward.
  287. Note further that, in the half-broken SOA-only transfer case,
  288. these two SOAs are supposed to be the same as stated in Section 2.2
  289. of RFC 5936. We don't check that condition here, either; we'll
  290. leave whether and how to deal with that situation to the end of
  291. the processing of non incremental update. See also a related
  292. discussion at the IETF dnsext wg:
  293. http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
  294. '''
  295. if conn._request_type == RRType.IXFR() and \
  296. rr.get_type() == RRType.SOA() and \
  297. conn._request_serial == get_soa_serial(rr.get_rdata()[0]):
  298. logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_INCREMENTAL_RESP,
  299. conn.zone_str())
  300. self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
  301. else:
  302. logger.debug(DBG_XFRIN_TRACE, XFRIN_GOT_NONINCREMENTAL_RESP,
  303. conn.zone_str())
  304. # We are now going to add RRs to the new zone. We need create
  305. # a Diff object. It will be used throughtout the XFR session.
  306. # DISABLE FOR DEBUG
  307. conn._diff = Diff(conn._datasrc_client, conn._zone_name, True)
  308. self.set_xfrstate(conn, XfrinAXFR())
  309. return False
  310. class XfrinIXFRDeleteSOA(XfrinState):
  311. def handle_rr(self, conn, rr):
  312. if rr.get_type() != RRType.SOA():
  313. # this shouldn't happen; should this occur it means an internal
  314. # bug.
  315. raise XfrinException(rr.get_type().to_text() +
  316. ' RR is given in IXFRDeleteSOA state')
  317. # This is the beginning state of one difference sequence (changes
  318. # for one SOA update). We need to create a new Diff object now.
  319. # Note also that we (unconditionally) enable journaling here. The
  320. # Diff constructor may internally disable it, however, if the
  321. # underlying data source doesn't support journaling.
  322. conn._diff = Diff(conn._datasrc_client, conn._zone_name, False, True)
  323. conn._diff.delete_data(rr)
  324. self.set_xfrstate(conn, XfrinIXFRDelete())
  325. return True
  326. class XfrinIXFRDelete(XfrinState):
  327. def handle_rr(self, conn, rr):
  328. if rr.get_type() == RRType.SOA():
  329. # This is the only place where current_serial is set
  330. conn._current_serial = get_soa_serial(rr.get_rdata()[0])
  331. self.set_xfrstate(conn, XfrinIXFRAddSOA())
  332. return False
  333. conn._diff.delete_data(rr)
  334. return True
  335. class XfrinIXFRAddSOA(XfrinState):
  336. def handle_rr(self, conn, rr):
  337. if rr.get_type() != RRType.SOA():
  338. # this shouldn't happen; should this occur it means an internal
  339. # bug.
  340. raise XfrinException(rr.get_type().to_text() +
  341. ' RR is given in IXFRAddSOA state')
  342. conn._diff.add_data(rr)
  343. self.set_xfrstate(conn, XfrinIXFRAdd())
  344. return True
  345. class XfrinIXFRAdd(XfrinState):
  346. def handle_rr(self, conn, rr):
  347. if rr.get_type() == RRType.SOA():
  348. soa_serial = get_soa_serial(rr.get_rdata()[0])
  349. if soa_serial == conn._end_serial:
  350. conn._diff.commit()
  351. self.set_xfrstate(conn, XfrinIXFREnd())
  352. return True
  353. elif soa_serial != conn._current_serial:
  354. raise XfrinProtocolError('IXFR out of sync: expected ' +
  355. 'serial ' +
  356. str(conn._current_serial) +
  357. ', got ' + str(soa_serial))
  358. else:
  359. conn._diff.commit()
  360. self.set_xfrstate(conn, XfrinIXFRDeleteSOA())
  361. return False
  362. conn._diff.add_data(rr)
  363. return True
  364. class XfrinIXFREnd(XfrinState):
  365. def handle_rr(self, conn, rr):
  366. raise XfrinProtocolError('Extra data after the end of IXFR diffs: ' +
  367. rr.to_text())
  368. def finish_message(self, conn):
  369. '''Final processing after processing an entire IXFR session.
  370. There will be more actions here, but for now we simply return False,
  371. indicating there will be no more message to receive.
  372. '''
  373. return False
  374. class XfrinAXFR(XfrinState):
  375. def handle_rr(self, conn, rr):
  376. """
  377. Handle the RR by putting it into the zone.
  378. """
  379. conn._diff.add_data(rr)
  380. if rr.get_type() == RRType.SOA():
  381. # SOA means end. Don't commit it yet - we need to perform
  382. # post-transfer checks
  383. soa_serial = get_soa_serial(rr.get_rdata()[0])
  384. if conn._end_serial != soa_serial:
  385. logger.warn(XFRIN_AXFR_INCONSISTENT_SOA, conn.zone_str(),
  386. conn._end_serial, soa_serial)
  387. self.set_xfrstate(conn, XfrinAXFREnd())
  388. # Yes, we've eaten this RR.
  389. return True
  390. class XfrinAXFREnd(XfrinState):
  391. def handle_rr(self, conn, rr):
  392. raise XfrinProtocolError('Extra data after the end of AXFR: ' +
  393. rr.to_text())
  394. def finish_message(self, conn):
  395. """
  396. Final processing after processing an entire AXFR session.
  397. In this process all the AXFR changes are committed to the
  398. data source.
  399. There might be more actions here, but for now we simply return False,
  400. indicating there will be no more message to receive.
  401. """
  402. conn._diff.commit()
  403. return False
  404. class XfrinConnection(asyncore.dispatcher):
  405. '''Do xfrin in this class. '''
  406. def __init__(self,
  407. sock_map, zone_name, rrclass, datasrc_client,
  408. shutdown_event, master_addrinfo, tsig_key=None,
  409. idle_timeout=60):
  410. '''Constructor of the XfirnConnection class.
  411. idle_timeout: max idle time for read data from socket.
  412. datasrc_client: the data source client object used for the XFR session.
  413. This will eventually replace db_file completely.
  414. '''
  415. asyncore.dispatcher.__init__(self, map=sock_map)
  416. # The XFR state. Conceptually this is purely private, so we emphasize
  417. # the fact by the double underscore. Other classes are assumed to
  418. # get access to this via get_xfrstate(), and only XfrinState classes
  419. # are assumed to be allowed to modify it via __set_xfrstate().
  420. self.__state = None
  421. # Requested transfer type (RRType.AXFR or RRType.IXFR). The actual
  422. # transfer type may differ due to IXFR->AXFR fallback:
  423. self._request_type = None
  424. # Zone parameters
  425. self._zone_name = zone_name
  426. self._rrclass = rrclass
  427. # Data source handler
  428. self._datasrc_client = datasrc_client
  429. self._sock_map = sock_map
  430. self._soa_rr_count = 0
  431. self._idle_timeout = idle_timeout
  432. self._shutdown_event = shutdown_event
  433. self._master_addrinfo = master_addrinfo
  434. self._tsig_key = tsig_key
  435. self._tsig_ctx = None
  436. # tsig_ctx_creator is introduced to allow tests to use a mock class for
  437. # easier tests (in normal case we always use the default)
  438. self._tsig_ctx_creator = lambda key : TSIGContext(key)
  439. def init_socket(self):
  440. '''Initialize the underlyig socket.
  441. This is essentially a part of __init__() and is expected to be
  442. called immediately after the constructor. It's separated from
  443. the constructor because otherwise we might not be able to close
  444. it if the constructor raises an exception after opening the socket.
  445. '''
  446. self.create_socket(self._master_addrinfo[0], self._master_addrinfo[1])
  447. self.setblocking(1)
  448. def __set_xfrstate(self, new_state):
  449. self.__state = new_state
  450. def get_xfrstate(self):
  451. return self.__state
  452. def zone_str(self):
  453. '''A convenience function for logging to include zone name and class'''
  454. return format_zone_str(self._zone_name, self._rrclass)
  455. def connect_to_master(self):
  456. '''Connect to master in TCP.'''
  457. try:
  458. self.connect(self._master_addrinfo[2])
  459. return True
  460. except socket.error as e:
  461. logger.error(XFRIN_CONNECT_MASTER, self._master_addrinfo[2],
  462. str(e))
  463. return False
  464. def _get_zone_soa(self):
  465. result, finder = self._datasrc_client.find_zone(self._zone_name)
  466. if result != DataSourceClient.SUCCESS:
  467. raise XfrinException('Zone not found in the given data ' +
  468. 'source: ' + self.zone_str())
  469. result, soa_rrset = finder.find(self._zone_name, RRType.SOA(),
  470. None, ZoneFinder.FIND_DEFAULT)
  471. if result != ZoneFinder.SUCCESS:
  472. raise XfrinException('SOA RR not found in zone: ' +
  473. self.zone_str())
  474. # Especially for database-based zones, a working zone may be in
  475. # a broken state where it has more than one SOA RR. We proactively
  476. # check the condition and abort the xfr attempt if we identify it.
  477. if soa_rrset.get_rdata_count() != 1:
  478. raise XfrinException('Invalid number of SOA RRs for ' +
  479. self.zone_str() + ': ' +
  480. str(soa_rrset.get_rdata_count()))
  481. return soa_rrset
  482. def _create_query(self, query_type):
  483. '''Create an XFR-related query message.
  484. query_type is either SOA, AXFR or IXFR. For type IXFR, it searches
  485. the associated data source for the current SOA record to include
  486. it in the query. If the corresponding zone or the SOA record
  487. cannot be found, it raises an XfrinException exception. Note that
  488. this may not necessarily a broken configuration; for the first attempt
  489. of transfer the secondary may not have any boot-strap zone
  490. information, in which case IXFR simply won't work. The xfrin
  491. should then fall back to AXFR. _request_serial is recorded for
  492. later use.
  493. '''
  494. msg = Message(Message.RENDER)
  495. query_id = random.randint(0, 0xFFFF)
  496. self._query_id = query_id
  497. msg.set_qid(query_id)
  498. msg.set_opcode(Opcode.QUERY())
  499. msg.set_rcode(Rcode.NOERROR())
  500. msg.add_question(Question(self._zone_name, self._rrclass, query_type))
  501. if query_type == RRType.IXFR():
  502. # get the zone finder. this must be SUCCESS (not even
  503. # PARTIALMATCH) because we are specifying the zone origin name.
  504. zone_soa_rr = self._get_zone_soa()
  505. msg.add_rrset(Message.SECTION_AUTHORITY, zone_soa_rr)
  506. self._request_serial = get_soa_serial(zone_soa_rr.get_rdata()[0])
  507. else:
  508. # For AXFR, we temporarily provide backward compatible behavior
  509. # where xfrin is responsible for creating zone in the corresponding
  510. # DB table. Note that the code below uses the old data source
  511. # API and assumes SQLite3 in an ugly manner. We'll have to
  512. # develop a better way of managing zones in a generic way and
  513. # eliminate the code like the one here.
  514. try:
  515. self._get_zone_soa()
  516. except XfrinException:
  517. def empty_rr_generator():
  518. return []
  519. isc.datasrc.sqlite3_ds.load(self._db_file,
  520. self._zone_name.to_text(),
  521. empty_rr_generator)
  522. return msg
  523. def _send_data(self, data):
  524. size = len(data)
  525. total_count = 0
  526. while total_count < size:
  527. count = self.send(data[total_count:])
  528. total_count += count
  529. def _send_query(self, query_type):
  530. '''Send query message over TCP. '''
  531. msg = self._create_query(query_type)
  532. render = MessageRenderer()
  533. # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
  534. # we should remove the if statement and use a universal interface later.
  535. if self._tsig_key is not None:
  536. self._tsig_ctx = self._tsig_ctx_creator(self._tsig_key)
  537. msg.to_wire(render, self._tsig_ctx)
  538. else:
  539. msg.to_wire(render)
  540. header_len = struct.pack('H', socket.htons(render.get_length()))
  541. self._send_data(header_len)
  542. self._send_data(render.get_data())
  543. def _asyncore_loop(self):
  544. '''
  545. This method is a trivial wrapper for asyncore.loop(). It's extracted from
  546. _get_request_response so that we can test the rest of the code without
  547. involving actual communication with a remote server.'''
  548. asyncore.loop(self._idle_timeout, map=self._sock_map, count=1)
  549. def _get_request_response(self, size):
  550. recv_size = 0
  551. data = b''
  552. while recv_size < size:
  553. self._recv_time_out = True
  554. self._need_recv_size = size - recv_size
  555. self._asyncore_loop()
  556. if self._recv_time_out:
  557. raise XfrinException('receive data from socket time out.')
  558. recv_size += self._recvd_size
  559. data += self._recvd_data
  560. return data
  561. def _check_response_tsig(self, msg, response_data):
  562. tsig_record = msg.get_tsig_record()
  563. if self._tsig_ctx is not None:
  564. tsig_error = self._tsig_ctx.verify(tsig_record, response_data)
  565. if tsig_error != TSIGError.NOERROR:
  566. raise XfrinException('TSIG verify fail: %s' % str(tsig_error))
  567. elif tsig_record is not None:
  568. # If the response includes a TSIG while we didn't sign the query,
  569. # we treat it as an error. RFC doesn't say anything about this
  570. # case, but it clearly states the server must not sign a response
  571. # to an unsigned request. Although we could be flexible, no sane
  572. # implementation would return such a response, and since this is
  573. # part of security mechanism, it's probably better to be more
  574. # strict.
  575. raise XfrinException('Unexpected TSIG in response')
  576. def _check_soa_serial(self):
  577. ''' Compare the soa serial, if soa serial in master is less than
  578. the soa serial in local, Finish xfrin.
  579. False: soa serial in master is less or equal to the local one.
  580. True: soa serial in master is bigger
  581. '''
  582. self._send_query(RRType.SOA())
  583. data_len = self._get_request_response(2)
  584. msg_len = socket.htons(struct.unpack('H', data_len)[0])
  585. soa_response = self._get_request_response(msg_len)
  586. msg = Message(Message.PARSE)
  587. msg.from_wire(soa_response)
  588. # TSIG related checks, including an unexpected signed response
  589. self._check_response_tsig(msg, soa_response)
  590. # perform some minimal level validation. It's an open issue how
  591. # strict we should be (see the comment in _check_response_header())
  592. self._check_response_header(msg)
  593. # TODO, need select soa record from data source then compare the two
  594. # serial, current just return OK, since this function hasn't been used
  595. # now.
  596. return XFRIN_OK
  597. def do_xfrin(self, check_soa, request_type=RRType.AXFR()):
  598. '''Do an xfr session by sending xfr request and parsing responses.'''
  599. try:
  600. ret = XFRIN_OK
  601. self._request_type = request_type
  602. # Right now RRType.[IA]XFR().to_text() is 'TYPExxx', so we need
  603. # to hardcode here.
  604. request_str = 'IXFR' if request_type == RRType.IXFR() else 'AXFR'
  605. if check_soa:
  606. ret = self._check_soa_serial()
  607. if ret == XFRIN_OK:
  608. logger.info(XFRIN_XFR_TRANSFER_STARTED, request_str,
  609. self.zone_str())
  610. self._send_query(self._request_type)
  611. self.__state = XfrinInitialSOA()
  612. self._handle_xfrin_responses()
  613. logger.info(XFRIN_XFR_TRANSFER_SUCCESS, request_str,
  614. self.zone_str())
  615. except (XfrinException, XfrinProtocolError) as e:
  616. logger.error(XFRIN_XFR_TRANSFER_FAILURE, request_str,
  617. self.zone_str(), str(e))
  618. ret = XFRIN_FAIL
  619. except Exception as e:
  620. # Catching all possible exceptions like this is generally not a
  621. # good practice, but handling an xfr session could result in
  622. # so many types of exceptions, including ones from the DNS library
  623. # or from the data source library. Eventually we'd introduce a
  624. # hierarchy for exception classes from a base "ISC exception" and
  625. # catch it here, but until then we need broadest coverage so that
  626. # we won't miss anything.
  627. logger.error(XFRIN_XFR_OTHER_FAILURE, request_str,
  628. self.zone_str(), str(e))
  629. ret = XFRIN_FAIL
  630. finally:
  631. # Make sure any remaining transaction in the diff is closed
  632. # (if not yet - possible in case of xfr-level exception) as soon
  633. # as possible
  634. self._diff = None
  635. return ret
  636. def _check_response_header(self, msg):
  637. '''Perform minimal validation on responses'''
  638. # It's not clear how strict we should be about response validation.
  639. # BIND 9 ignores some cases where it would normally be considered a
  640. # bogus response. For example, it accepts a response even if its
  641. # opcode doesn't match that of the corresponding request.
  642. # According to an original developer of BIND 9 some of the missing
  643. # checks are deliberate to be kind to old implementations that would
  644. # cause interoperability trouble with stricter checks.
  645. msg_rcode = msg.get_rcode()
  646. if msg_rcode != Rcode.NOERROR():
  647. raise XfrinException('error response: %s' % msg_rcode.to_text())
  648. if not msg.get_header_flag(Message.HEADERFLAG_QR):
  649. raise XfrinException('response is not a response')
  650. if msg.get_qid() != self._query_id:
  651. raise XfrinException('bad query id')
  652. def _check_response_status(self, msg):
  653. '''Check validation of xfr response. '''
  654. self._check_response_header(msg)
  655. if msg.get_rr_count(Message.SECTION_QUESTION) > 1:
  656. raise XfrinException('query section count greater than 1')
  657. def _handle_xfrin_responses(self):
  658. read_next_msg = True
  659. while read_next_msg:
  660. data_len = self._get_request_response(2)
  661. msg_len = socket.htons(struct.unpack('H', data_len)[0])
  662. recvdata = self._get_request_response(msg_len)
  663. msg = Message(Message.PARSE)
  664. msg.from_wire(recvdata, Message.PRESERVE_ORDER)
  665. # TSIG related checks, including an unexpected signed response
  666. self._check_response_tsig(msg, recvdata)
  667. # Perform response status validation
  668. self._check_response_status(msg)
  669. for rr in msg.get_section(Message.SECTION_ANSWER):
  670. rr_handled = False
  671. while not rr_handled:
  672. rr_handled = self.__state.handle_rr(self, rr)
  673. read_next_msg = self.__state.finish_message(self)
  674. if self._shutdown_event.is_set():
  675. raise XfrinException('xfrin is forced to stop')
  676. def handle_read(self):
  677. '''Read query's response from socket. '''
  678. self._recvd_data = self.recv(self._need_recv_size)
  679. self._recvd_size = len(self._recvd_data)
  680. self._recv_time_out = False
  681. def writable(self):
  682. '''Ignore the writable socket. '''
  683. return False
  684. def __process_xfrin(server, zone_name, rrclass, db_file,
  685. shutdown_event, master_addrinfo, check_soa, tsig_key,
  686. request_type, conn_class):
  687. conn = None
  688. exception = None
  689. ret = XFRIN_FAIL
  690. try:
  691. # Create a data source client used in this XFR session. Right now we
  692. # still assume an sqlite3-based data source, and use both the old and new
  693. # data source APIs. We also need to use a mock client for tests.
  694. # For a temporary workaround to deal with these situations, we skip the
  695. # creation when the given file is none (the test case). Eventually
  696. # this code will be much cleaner.
  697. datasrc_client = None
  698. if db_file is not None:
  699. # temporary hardcoded sqlite initialization. Once we decide on
  700. # the config specification, we need to update this (TODO)
  701. # this may depend on #1207, or any followup ticket created for #1207
  702. datasrc_type = "sqlite3"
  703. datasrc_config = "{ \"database_file\": \"" + db_file + "\"}"
  704. datasrc_client = DataSourceClient(datasrc_type, datasrc_config)
  705. # Create a TCP connection for the XFR session and perform the operation.
  706. sock_map = {}
  707. # In case we were asked to do IXFR and that one fails, we try again with
  708. # AXFR. But only if we could actually connect to the server.
  709. #
  710. # So we start with retry as True, which is set to false on each attempt.
  711. # In the case of connected but failed IXFR, we set it to true once again.
  712. retry = True
  713. while retry:
  714. retry = False
  715. conn = conn_class(sock_map, zone_name, rrclass, datasrc_client,
  716. shutdown_event, master_addrinfo, tsig_key)
  717. conn.init_socket()
  718. # XXX: We still need _db_file for temporary workaround in _create_query().
  719. # This should be removed when we eliminate the need for the workaround.
  720. conn._db_file = db_file
  721. ret = XFRIN_FAIL
  722. if conn.connect_to_master():
  723. ret = conn.do_xfrin(check_soa, request_type)
  724. if ret == XFRIN_FAIL and request_type == RRType.IXFR():
  725. # IXFR failed for some reason. It might mean the server can't
  726. # handle it, or we don't have the zone or we are out of sync or
  727. # whatever else. So we retry with with AXFR, as it may succeed
  728. # in many such cases.
  729. retry = True
  730. request_type = RRType.AXFR()
  731. logger.warn(XFRIN_XFR_TRANSFER_FALLBACK, conn.zone_str())
  732. conn.close()
  733. conn = None
  734. except Exception as ex:
  735. # If exception happens, just remember it here so that we can re-raise
  736. # after cleaning up things. We don't log it here because we want
  737. # eliminate smallest possibility of having an exception in logging
  738. # itself.
  739. exception = ex
  740. # asyncore.dispatcher requires explicit close() unless its lifetime
  741. # from born to destruction is closed within asyncore.loop, which is not
  742. # the case for us. We always close() here, whether or not do_xfrin
  743. # succeeds, and even when we see an unexpected exception.
  744. if conn is not None:
  745. conn.close()
  746. # Publish the zone transfer result news, so zonemgr can reset the
  747. # zone timer, and xfrout can notify the zone's slaves if the result
  748. # is success.
  749. server.publish_xfrin_news(zone_name, rrclass, ret)
  750. if exception is not None:
  751. raise exception
  752. def process_xfrin(server, xfrin_recorder, zone_name, rrclass, db_file,
  753. shutdown_event, master_addrinfo, check_soa, tsig_key,
  754. request_type, conn_class=XfrinConnection):
  755. # Even if it should be rare, the main process of xfrin session can
  756. # raise an exception. In order to make sure the lock in xfrin_recorder
  757. # is released in any cases, we delegate the main part to the helper
  758. # function in the try block, catch any exceptions, then release the lock.
  759. xfrin_recorder.increment(zone_name)
  760. exception = None
  761. try:
  762. __process_xfrin(server, zone_name, rrclass, db_file,
  763. shutdown_event, master_addrinfo, check_soa, tsig_key,
  764. request_type, conn_class)
  765. except Exception as ex:
  766. # don't log it until we complete decrement().
  767. exception = ex
  768. xfrin_recorder.decrement(zone_name)
  769. if exception is not None:
  770. typestr = "AXFR" if request_type == RRType.AXFR() else "IXFR"
  771. logger.error(XFRIN_XFR_PROCESS_FAILURE, typestr, zone_name.to_text(),
  772. str(rrclass), str(exception))
  773. class XfrinRecorder:
  774. def __init__(self):
  775. self._lock = threading.Lock()
  776. self._zones = []
  777. def increment(self, zone_name):
  778. self._lock.acquire()
  779. self._zones.append(zone_name)
  780. self._lock.release()
  781. def decrement(self, zone_name):
  782. self._lock.acquire()
  783. if zone_name in self._zones:
  784. self._zones.remove(zone_name)
  785. self._lock.release()
  786. def xfrin_in_progress(self, zone_name):
  787. self._lock.acquire()
  788. ret = zone_name in self._zones
  789. self._lock.release()
  790. return ret
  791. def count(self):
  792. self._lock.acquire()
  793. ret = len(self._zones)
  794. self._lock.release()
  795. return ret
  796. class ZoneInfo:
  797. def __init__(self, config_data, module_cc):
  798. """Creates a zone_info with the config data element as
  799. specified by the 'zones' list in xfrin.spec. Module_cc is
  800. needed to get the defaults from the specification"""
  801. self._module_cc = module_cc
  802. self.set_name(config_data.get('name'))
  803. self.set_master_addr(config_data.get('master_addr'))
  804. self.set_master_port(config_data.get('master_port'))
  805. self.set_zone_class(config_data.get('class'))
  806. self.set_tsig_key(config_data.get('tsig_key'))
  807. self.set_use_ixfr(config_data.get('use_ixfr'))
  808. def set_name(self, name_str):
  809. """Set the name for this zone given a name string.
  810. Raises XfrinZoneInfoException if name_str is None or if it
  811. cannot be parsed."""
  812. if name_str is None:
  813. raise XfrinZoneInfoException("Configuration zones list "
  814. "element does not contain "
  815. "'name' attribute")
  816. else:
  817. self.name = _check_zone_name(name_str)
  818. def set_master_addr(self, master_addr_str):
  819. """Set the master address for this zone given an IP address
  820. string. Raises XfrinZoneInfoException if master_addr_str is
  821. None or if it cannot be parsed."""
  822. if master_addr_str is None:
  823. raise XfrinZoneInfoException("master address missing from config data")
  824. else:
  825. try:
  826. self.master_addr = isc.net.parse.addr_parse(master_addr_str)
  827. except ValueError:
  828. logger.error(XFRIN_BAD_MASTER_ADDR_FORMAT, master_addr_str)
  829. errmsg = "bad format for zone's master: " + master_addr_str
  830. raise XfrinZoneInfoException(errmsg)
  831. def set_master_port(self, master_port_str):
  832. """Set the master port given a port number string. If
  833. master_port_str is None, the default from the specification
  834. for this module will be used. Raises XfrinZoneInfoException if
  835. the string contains an invalid port number"""
  836. if master_port_str is None:
  837. self.master_port = self._module_cc.get_default_value("zones/master_port")
  838. else:
  839. try:
  840. self.master_port = isc.net.parse.port_parse(master_port_str)
  841. except ValueError:
  842. logger.error(XFRIN_BAD_MASTER_PORT_FORMAT, master_port_str)
  843. errmsg = "bad format for zone's master port: " + master_port_str
  844. raise XfrinZoneInfoException(errmsg)
  845. def set_zone_class(self, zone_class_str):
  846. """Set the zone class given an RR class str (e.g. "IN"). If
  847. zone_class_str is None, it will default to what is specified
  848. in the specification file for this module. Raises
  849. XfrinZoneInfoException if the string cannot be parsed."""
  850. # TODO: remove _str
  851. self.class_str = zone_class_str or self._module_cc.get_default_value("zones/class")
  852. if zone_class_str == None:
  853. #TODO rrclass->zone_class
  854. self.rrclass = RRClass(self._module_cc.get_default_value("zones/class"))
  855. else:
  856. try:
  857. self.rrclass = RRClass(zone_class_str)
  858. except InvalidRRClass:
  859. logger.error(XFRIN_BAD_ZONE_CLASS, zone_class_str)
  860. errmsg = "invalid zone class: " + zone_class_str
  861. raise XfrinZoneInfoException(errmsg)
  862. def set_tsig_key(self, tsig_key_str):
  863. """Set the tsig_key for this zone, given a TSIG key string
  864. representation. If tsig_key_str is None, no TSIG key will
  865. be set. Raises XfrinZoneInfoException if tsig_key_str cannot
  866. be parsed."""
  867. if tsig_key_str is None:
  868. self.tsig_key = None
  869. else:
  870. try:
  871. self.tsig_key = TSIGKey(tsig_key_str)
  872. except InvalidParameter as ipe:
  873. logger.error(XFRIN_BAD_TSIG_KEY_STRING, tsig_key_str)
  874. errmsg = "bad TSIG key string: " + tsig_key_str
  875. raise XfrinZoneInfoException(errmsg)
  876. def set_use_ixfr(self, use_ixfr):
  877. """Set use_ixfr. If set to True, it will use
  878. IXFR for incoming transfers. If set to False, it will use AXFR.
  879. At this moment there is no automatic fallback"""
  880. # TODO: http://bind10.isc.org/ticket/1279
  881. if use_ixfr is None:
  882. self.use_ixfr = \
  883. self._module_cc.get_default_value("zones/use_ixfr")
  884. else:
  885. self.use_ixfr = use_ixfr
  886. def get_master_addr_info(self):
  887. return (self.master_addr.family, socket.SOCK_STREAM,
  888. (str(self.master_addr), self.master_port))
  889. class Xfrin:
  890. def __init__(self):
  891. self._max_transfers_in = 10
  892. self._zones = {}
  893. self._cc_setup()
  894. self.recorder = XfrinRecorder()
  895. self._shutdown_event = threading.Event()
  896. def _cc_setup(self):
  897. '''This method is used only as part of initialization, but is
  898. implemented separately for convenience of unit tests; by letting
  899. the test code override this method we can test most of this class
  900. without requiring a command channel.'''
  901. # Create one session for sending command to other modules, because the
  902. # listening session will block the send operation.
  903. self._send_cc_session = isc.cc.Session()
  904. self._module_cc = isc.config.ModuleCCSession(SPECFILE_LOCATION,
  905. self.config_handler,
  906. self.command_handler)
  907. self._module_cc.start()
  908. config_data = self._module_cc.get_full_config()
  909. self.config_handler(config_data)
  910. def _cc_check_command(self):
  911. '''This is a straightforward wrapper for cc.check_command,
  912. but provided as a separate method for the convenience
  913. of unit tests.'''
  914. self._module_cc.check_command(False)
  915. def _get_zone_info(self, name, rrclass):
  916. """Returns the ZoneInfo object containing the configured data
  917. for the given zone name. If the zone name did not have any
  918. data, returns None"""
  919. return self._zones.get((name.to_text(), rrclass.to_text()))
  920. def _add_zone_info(self, zone_info):
  921. """Add the zone info. Raises a XfrinZoneInfoException if a zone
  922. with the same name and class is already configured"""
  923. key = (zone_info.name.to_text(), zone_info.class_str)
  924. if key in self._zones:
  925. raise XfrinZoneInfoException("zone " + str(key) +
  926. " configured multiple times")
  927. self._zones[key] = zone_info
  928. def _clear_zone_info(self):
  929. self._zones = {}
  930. def config_handler(self, new_config):
  931. # backup all config data (should there be a problem in the new
  932. # data)
  933. old_max_transfers_in = self._max_transfers_in
  934. old_zones = self._zones
  935. self._max_transfers_in = new_config.get("transfers_in") or self._max_transfers_in
  936. if 'zones' in new_config:
  937. self._clear_zone_info()
  938. for zone_config in new_config.get('zones'):
  939. try:
  940. zone_info = ZoneInfo(zone_config, self._module_cc)
  941. self._add_zone_info(zone_info)
  942. except XfrinZoneInfoException as xce:
  943. self._zones = old_zones
  944. self._max_transfers_in = old_max_transfers_in
  945. return create_answer(1, str(xce))
  946. return create_answer(0)
  947. def shutdown(self):
  948. ''' shutdown the xfrin process. the thread which is doing xfrin should be
  949. terminated.
  950. '''
  951. self._shutdown_event.set()
  952. main_thread = threading.currentThread()
  953. for th in threading.enumerate():
  954. if th is main_thread:
  955. continue
  956. th.join()
  957. def command_handler(self, command, args):
  958. answer = create_answer(0)
  959. try:
  960. if command == 'shutdown':
  961. self._shutdown_event.set()
  962. elif command == 'notify' or command == REFRESH_FROM_ZONEMGR:
  963. # Xfrin receives the refresh/notify command from zone manager.
  964. # notify command maybe has the parameters which
  965. # specify the notifyfrom address and port, according the RFC1996, zone
  966. # transfer should starts first from the notifyfrom, but now, let 'TODO' it.
  967. # (using the value now, while we can only set one master address, would be
  968. # a security hole. Once we add the ability to have multiple master addresses,
  969. # we should check if it matches one of them, and then use it.)
  970. (zone_name, rrclass) = self._parse_zone_name_and_class(args)
  971. zone_str = format_zone_str(zone_name, rrclass)
  972. zone_info = self._get_zone_info(zone_name, rrclass)
  973. notify_addr = self._parse_master_and_port(args, zone_name,
  974. rrclass)
  975. if zone_info is None:
  976. # TODO what to do? no info known about zone. defaults?
  977. errmsg = "Got notification to retransfer unknown zone " + zone_str
  978. logger.info(XFRIN_RETRANSFER_UNKNOWN_ZONE, zone_str)
  979. answer = create_answer(1, errmsg)
  980. else:
  981. request_type = RRType.AXFR()
  982. if zone_info.use_ixfr:
  983. request_type = RRType.IXFR()
  984. master_addr = zone_info.get_master_addr_info()
  985. if notify_addr[0] == master_addr[0] and\
  986. notify_addr[2] == master_addr[2]:
  987. ret = self.xfrin_start(zone_name,
  988. rrclass,
  989. self._get_db_file(),
  990. master_addr,
  991. zone_info.tsig_key, request_type,
  992. True)
  993. answer = create_answer(ret[0], ret[1])
  994. else:
  995. notify_addr_str = format_addrinfo(notify_addr)
  996. master_addr_str = format_addrinfo(master_addr)
  997. errmsg = "Got notification for " + zone_str\
  998. + "from unknown address: " + notify_addr_str;
  999. logger.info(XFRIN_NOTIFY_UNKNOWN_MASTER, zone_str,
  1000. notify_addr_str, master_addr_str)
  1001. answer = create_answer(1, errmsg)
  1002. elif command == 'retransfer' or command == 'refresh':
  1003. # Xfrin receives the retransfer/refresh from cmdctl(sent by bindctl).
  1004. # If the command has specified master address, do transfer from the
  1005. # master address, or else do transfer from the configured masters.
  1006. (zone_name, rrclass) = self._parse_zone_name_and_class(args)
  1007. master_addr = self._parse_master_and_port(args, zone_name,
  1008. rrclass)
  1009. zone_info = self._get_zone_info(zone_name, rrclass)
  1010. tsig_key = None
  1011. request_type = RRType.AXFR()
  1012. if zone_info:
  1013. tsig_key = zone_info.tsig_key
  1014. if zone_info.use_ixfr:
  1015. request_type = RRType.IXFR()
  1016. db_file = args.get('db_file') or self._get_db_file()
  1017. ret = self.xfrin_start(zone_name,
  1018. rrclass,
  1019. db_file,
  1020. master_addr,
  1021. tsig_key, request_type,
  1022. (False if command == 'retransfer' else True))
  1023. answer = create_answer(ret[0], ret[1])
  1024. else:
  1025. answer = create_answer(1, 'unknown command: ' + command)
  1026. except XfrinException as err:
  1027. logger.error(XFRIN_COMMAND_ERROR, command, str(err))
  1028. answer = create_answer(1, str(err))
  1029. return answer
  1030. def _parse_zone_name_and_class(self, args):
  1031. zone_name_str = args.get('zone_name')
  1032. if zone_name_str is None:
  1033. raise XfrinException('zone name should be provided')
  1034. return (_check_zone_name(zone_name_str), _check_zone_class(args.get('zone_class')))
  1035. def _parse_master_and_port(self, args, zone_name, zone_class):
  1036. """
  1037. Return tuple (family, socktype, sockaddr) for address and port in given
  1038. args dict.
  1039. IPv4 and IPv6 are the only supported addresses now, so sockaddr will be
  1040. (address, port). The socktype is socket.SOCK_STREAM for now.
  1041. """
  1042. # check if we have configured info about this zone, in case
  1043. # port or master are not specified
  1044. zone_info = self._get_zone_info(zone_name, zone_class)
  1045. addr_str = args.get('master')
  1046. if addr_str is None:
  1047. if zone_info is not None:
  1048. addr = zone_info.master_addr
  1049. else:
  1050. raise XfrinException("Master address not given or "
  1051. "configured for " + zone_name.to_text())
  1052. else:
  1053. try:
  1054. addr = isc.net.parse.addr_parse(addr_str)
  1055. except ValueError as err:
  1056. raise XfrinException("failed to resolve master address %s: %s" %
  1057. (addr_str, str(err)))
  1058. port_str = args.get('port')
  1059. if port_str is None:
  1060. if zone_info is not None:
  1061. port = zone_info.master_port
  1062. else:
  1063. port = DEFAULT_MASTER_PORT
  1064. else:
  1065. try:
  1066. port = isc.net.parse.port_parse(port_str)
  1067. except ValueError as err:
  1068. raise XfrinException("failed to parse port=%s: %s" %
  1069. (port_str, str(err)))
  1070. return (addr.family, socket.SOCK_STREAM, (str(addr), port))
  1071. def _get_db_file(self):
  1072. #TODO, the db file path should be got in auth server's configuration
  1073. # if we need access to this configuration more often, we
  1074. # should add it on start, and not remove it here
  1075. # (or, if we have writable ds, we might not need this in
  1076. # the first place)
  1077. self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION)
  1078. db_file, is_default = self._module_cc.get_remote_config_value("Auth", "database_file")
  1079. if is_default and "B10_FROM_BUILD" in os.environ:
  1080. # this too should be unnecessary, but currently the
  1081. # 'from build' override isn't stored in the config
  1082. # (and we don't have writable datasources yet)
  1083. db_file = os.environ["B10_FROM_BUILD"] + os.sep + "bind10_zones.sqlite3"
  1084. self._module_cc.remove_remote_config(AUTH_SPECFILE_LOCATION)
  1085. return db_file
  1086. def publish_xfrin_news(self, zone_name, zone_class, xfr_result):
  1087. '''Send command to xfrout/zone manager module.
  1088. If xfrin has finished successfully for one zone, tell the good
  1089. news(command: zone_new_data_ready) to zone manager and xfrout.
  1090. if xfrin failed, just tell the bad news to zone manager, so that
  1091. it can reset the refresh timer for that zone. '''
  1092. param = {'zone_name': zone_name.to_text(),
  1093. 'zone_class': zone_class.to_text()}
  1094. if xfr_result == XFRIN_OK:
  1095. msg = create_command(notify_out.ZONE_NEW_DATA_READY_CMD, param)
  1096. # catch the exception, in case msgq has been killed.
  1097. try:
  1098. seq = self._send_cc_session.group_sendmsg(msg,
  1099. XFROUT_MODULE_NAME)
  1100. try:
  1101. answer, env = self._send_cc_session.group_recvmsg(False,
  1102. seq)
  1103. except isc.cc.session.SessionTimeout:
  1104. pass # for now we just ignore the failure
  1105. seq = self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
  1106. try:
  1107. answer, env = self._send_cc_session.group_recvmsg(False,
  1108. seq)
  1109. except isc.cc.session.SessionTimeout:
  1110. pass # for now we just ignore the failure
  1111. except socket.error as err:
  1112. logger.error(XFRIN_MSGQ_SEND_ERROR, XFROUT_MODULE_NAME, ZONE_MANAGER_MODULE_NAME)
  1113. else:
  1114. msg = create_command(ZONE_XFRIN_FAILED, param)
  1115. # catch the exception, in case msgq has been killed.
  1116. try:
  1117. seq = self._send_cc_session.group_sendmsg(msg, ZONE_MANAGER_MODULE_NAME)
  1118. try:
  1119. answer, env = self._send_cc_session.group_recvmsg(False,
  1120. seq)
  1121. except isc.cc.session.SessionTimeout:
  1122. pass # for now we just ignore the failure
  1123. except socket.error as err:
  1124. logger.error(XFRIN_MSGQ_SEND_ERROR_ZONE_MANAGER, ZONE_MANAGER_MODULE_NAME)
  1125. def startup(self):
  1126. while not self._shutdown_event.is_set():
  1127. self._cc_check_command()
  1128. def xfrin_start(self, zone_name, rrclass, db_file, master_addrinfo,
  1129. tsig_key, request_type, check_soa=True):
  1130. if "pydnspp" not in sys.modules:
  1131. return (1, "xfrin failed, can't load dns message python library: 'pydnspp'")
  1132. # check max_transfer_in, else return quota error
  1133. if self.recorder.count() >= self._max_transfers_in:
  1134. return (1, 'xfrin quota error')
  1135. if self.recorder.xfrin_in_progress(zone_name):
  1136. return (1, 'zone xfrin is in progress')
  1137. xfrin_thread = threading.Thread(target = process_xfrin,
  1138. args = (self,
  1139. self.recorder,
  1140. zone_name,
  1141. rrclass,
  1142. db_file,
  1143. self._shutdown_event,
  1144. master_addrinfo, check_soa,
  1145. tsig_key, request_type))
  1146. xfrin_thread.start()
  1147. return (0, 'zone xfrin is started')
  1148. xfrind = None
  1149. def signal_handler(signal, frame):
  1150. if xfrind:
  1151. xfrind.shutdown()
  1152. sys.exit(0)
  1153. def set_signal_handler():
  1154. signal.signal(signal.SIGTERM, signal_handler)
  1155. signal.signal(signal.SIGINT, signal_handler)
  1156. def set_cmd_options(parser):
  1157. parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
  1158. help="This option is obsolete and has no effect.")
  1159. def main(xfrin_class, use_signal=True):
  1160. """The main loop of the Xfrin daemon.
  1161. @param xfrin_class: A class of the Xfrin object. This is normally Xfrin,
  1162. but can be a subclass of it for customization.
  1163. @param use_signal: True if this process should catch signals. This is
  1164. normally True, but may be disabled when this function is called in a
  1165. testing context."""
  1166. global xfrind
  1167. try:
  1168. parser = OptionParser(version = __version__)
  1169. set_cmd_options(parser)
  1170. (options, args) = parser.parse_args()
  1171. if use_signal:
  1172. set_signal_handler()
  1173. xfrind = xfrin_class()
  1174. xfrind.startup()
  1175. except KeyboardInterrupt:
  1176. logger.info(XFRIN_STOPPED_BY_KEYBOARD)
  1177. except isc.cc.session.SessionError as e:
  1178. logger.error(XFRIN_CC_SESSION_ERROR, str(e))
  1179. except Exception as e:
  1180. logger.error(XFRIN_UNKNOWN_ERROR, str(e))
  1181. if xfrind:
  1182. xfrind.shutdown()
  1183. if __name__ == '__main__':
  1184. main(Xfrin)