secondary.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. # -*- test-case-name: twisted.names.test.test_names -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. from __future__ import absolute_import, division
  5. __all__ = ['SecondaryAuthority', 'SecondaryAuthorityService']
  6. from twisted.internet import task, defer
  7. from twisted.names import dns
  8. from twisted.names import common
  9. from twisted.names import client
  10. from twisted.names import resolve
  11. from twisted.names.authority import FileAuthority
  12. from twisted.python import log, failure
  13. from twisted.application import service
  14. class SecondaryAuthorityService(service.Service):
  15. calls = None
  16. _port = 53
  17. def __init__(self, primary, domains):
  18. """
  19. @param primary: The IP address of the server from which to perform
  20. zone transfers.
  21. @type primary: L{str}
  22. @param domains: A sequence of domain names for which to perform
  23. zone transfers.
  24. @type domains: L{list} of L{bytes}
  25. """
  26. self.primary = primary
  27. self.domains = [SecondaryAuthority(primary, d) for d in domains]
  28. @classmethod
  29. def fromServerAddressAndDomains(cls, serverAddress, domains):
  30. """
  31. Construct a new L{SecondaryAuthorityService} from a tuple giving a
  32. server address and a C{str} giving the name of a domain for which this
  33. is an authority.
  34. @param serverAddress: A two-tuple, the first element of which is a
  35. C{str} giving an IP address and the second element of which is a
  36. C{int} giving a port number. Together, these define where zone
  37. transfers will be attempted from.
  38. @param domain: A C{bytes} giving the domain to transfer.
  39. @return: A new instance of L{SecondaryAuthorityService}.
  40. """
  41. service = cls(None, [])
  42. service.primary = serverAddress[0]
  43. service._port = serverAddress[1]
  44. service.domains = [
  45. SecondaryAuthority.fromServerAddressAndDomain(serverAddress, d)
  46. for d in domains]
  47. return service
  48. def getAuthority(self):
  49. return resolve.ResolverChain(self.domains)
  50. def startService(self):
  51. service.Service.startService(self)
  52. self.calls = [task.LoopingCall(d.transfer) for d in self.domains]
  53. i = 0
  54. from twisted.internet import reactor
  55. for c in self.calls:
  56. # XXX Add errbacks, respect proper timeouts
  57. reactor.callLater(i, c.start, 60 * 60)
  58. i += 1
  59. def stopService(self):
  60. service.Service.stopService(self)
  61. for c in self.calls:
  62. c.stop()
  63. class SecondaryAuthority(FileAuthority):
  64. """
  65. An Authority that keeps itself updated by performing zone transfers.
  66. @ivar primary: The IP address of the server from which zone transfers will
  67. be attempted.
  68. @type primary: C{str}
  69. @ivar _port: The port number of the server from which zone transfers will be
  70. attempted.
  71. @type: C{int}
  72. @ivar _reactor: The reactor to use to perform the zone transfers, or L{None}
  73. to use the global reactor.
  74. """
  75. transferring = False
  76. soa = records = None
  77. _port = 53
  78. _reactor = None
  79. def __init__(self, primaryIP, domain):
  80. """
  81. @param domain: The domain for which this will be the secondary
  82. authority.
  83. @type domain: L{bytes}
  84. """
  85. # Yep. Skip over FileAuthority.__init__. This is a hack until we have
  86. # a good composition-based API for the complicated DNS record lookup
  87. # logic we want to share.
  88. common.ResolverBase.__init__(self)
  89. self.primary = primaryIP
  90. self.domain = domain
  91. @classmethod
  92. def fromServerAddressAndDomain(cls, serverAddress, domain):
  93. """
  94. Construct a new L{SecondaryAuthority} from a tuple giving a server
  95. address and a C{bytes} giving the name of a domain for which this is an
  96. authority.
  97. @param serverAddress: A two-tuple, the first element of which is a
  98. C{str} giving an IP address and the second element of which is a
  99. C{int} giving a port number. Together, these define where zone
  100. transfers will be attempted from.
  101. @param domain: A C{bytes} giving the domain to transfer.
  102. @return: A new instance of L{SecondaryAuthority}.
  103. """
  104. secondary = cls(None, None)
  105. secondary.primary = serverAddress[0]
  106. secondary._port = serverAddress[1]
  107. secondary.domain = domain
  108. return secondary
  109. def transfer(self):
  110. if self.transferring:
  111. return
  112. self.transfering = True
  113. reactor = self._reactor
  114. if reactor is None:
  115. from twisted.internet import reactor
  116. resolver = client.Resolver(
  117. servers=[(self.primary, self._port)], reactor=reactor)
  118. return resolver.lookupZone(self.domain
  119. ).addCallback(self._cbZone
  120. ).addErrback(self._ebZone
  121. )
  122. def _lookup(self, name, cls, type, timeout=None):
  123. if not self.soa or not self.records:
  124. return defer.fail(failure.Failure(dns.DomainError(name)))
  125. return FileAuthority._lookup(self, name, cls, type, timeout)
  126. def _cbZone(self, zone):
  127. ans, _, _ = zone
  128. self.records = r = {}
  129. for rec in ans:
  130. if not self.soa and rec.type == dns.SOA:
  131. self.soa = (str(rec.name).lower(), rec.payload)
  132. else:
  133. r.setdefault(str(rec.name).lower(), []).append(rec.payload)
  134. def _ebZone(self, failure):
  135. log.msg("Updating %s from %s failed during zone transfer" % (self.domain, self.primary))
  136. log.err(failure)
  137. def update(self):
  138. self.transfer().addCallbacks(self._cbTransferred, self._ebTransferred)
  139. def _cbTransferred(self, result):
  140. self.transferring = False
  141. def _ebTransferred(self, failure):
  142. self.transferred = False
  143. log.msg("Transferring %s from %s failed after zone transfer" % (self.domain, self.primary))
  144. log.err(failure)