test_srvconnect.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Test cases for L{twisted.names.srvconnect}.
  5. """
  6. from __future__ import absolute_import, division
  7. import random
  8. from zope.interface.verify import verifyObject
  9. from twisted.trial import unittest
  10. from twisted.internet import defer, protocol
  11. from twisted.internet.error import DNSLookupError, ServiceNameUnknownError
  12. from twisted.internet.interfaces import IConnector
  13. from twisted.names import client, dns, srvconnect
  14. from twisted.names.common import ResolverBase
  15. from twisted.names.error import DNSNameError
  16. from twisted.python.compat import nativeString
  17. from twisted.test.proto_helpers import MemoryReactor
  18. class FakeResolver(ResolverBase):
  19. """
  20. Resolver that only gives out one given result.
  21. Either L{results} or L{failure} must be set and will be used for
  22. the return value of L{_lookup}
  23. @ivar results: List of L{dns.RRHeader} for the desired result.
  24. @type results: C{list}
  25. @ivar failure: Failure with an exception from L{twisted.names.error}.
  26. @type failure: L{Failure<twisted.python.failure.Failure>}
  27. """
  28. def __init__(self, results=None, failure=None):
  29. self.results = results
  30. self.failure = failure
  31. self.lookups = []
  32. def _lookup(self, name, cls, qtype, timeout):
  33. """
  34. Return the result or failure on lookup.
  35. """
  36. self.lookups.append((name, cls, qtype, timeout))
  37. if self.results is not None:
  38. return defer.succeed((self.results, [], []))
  39. else:
  40. return defer.fail(self.failure)
  41. class DummyFactory(protocol.ClientFactory):
  42. """
  43. Dummy client factory that stores the reason of connection failure.
  44. """
  45. def __init__(self):
  46. self.reason = None
  47. def clientConnectionFailed(self, connector, reason):
  48. self.reason = reason
  49. class SRVConnectorTests(unittest.TestCase):
  50. """
  51. Tests for L{srvconnect.SRVConnector}.
  52. """
  53. def setUp(self):
  54. self.patch(client, 'theResolver', FakeResolver())
  55. self.reactor = MemoryReactor()
  56. self.factory = DummyFactory()
  57. self.connector = srvconnect.SRVConnector(self.reactor, 'xmpp-server',
  58. 'example.org', self.factory)
  59. self.randIntArgs = []
  60. self.randIntResults = []
  61. def _randint(self, min, max):
  62. """
  63. Fake randint.
  64. Returns the first element of L{randIntResults} and records the
  65. arguments passed to it in L{randIntArgs}.
  66. @param min: Lower bound of the random number.
  67. @type min: L{int}
  68. @param max: Higher bound of the random number.
  69. @type max: L{int}
  70. @return: Fake random number from L{randIntResults}.
  71. @rtype: L{int}
  72. """
  73. self.randIntArgs.append((min, max))
  74. return self.randIntResults.pop(0)
  75. def test_interface(self):
  76. """
  77. L{srvconnect.SRVConnector} implements L{IConnector}.
  78. """
  79. verifyObject(IConnector, self.connector)
  80. def test_SRVPresent(self):
  81. """
  82. Test connectTCP gets called with the address from the SRV record.
  83. """
  84. payload = dns.Record_SRV(port=6269, target='host.example.org', ttl=60)
  85. client.theResolver.results = [dns.RRHeader(name='example.org',
  86. type=dns.SRV,
  87. cls=dns.IN, ttl=60,
  88. payload=payload)]
  89. self.connector.connect()
  90. self.assertIsNone(self.factory.reason)
  91. self.assertEqual(
  92. self.reactor.tcpClients.pop()[:2], ('host.example.org', 6269))
  93. def test_SRVNotPresent(self):
  94. """
  95. Test connectTCP gets called with fallback parameters on NXDOMAIN.
  96. """
  97. client.theResolver.failure = DNSNameError('example.org')
  98. self.connector.connect()
  99. self.assertIsNone(self.factory.reason)
  100. self.assertEqual(
  101. self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
  102. def test_SRVNoResult(self):
  103. """
  104. Test connectTCP gets called with fallback parameters on empty result.
  105. """
  106. client.theResolver.results = []
  107. self.connector.connect()
  108. self.assertIsNone(self.factory.reason)
  109. self.assertEqual(
  110. self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
  111. def test_SRVNoResultUnknownServiceDefaultPort(self):
  112. """
  113. connectTCP gets called with default port if the service is not defined.
  114. """
  115. self.connector = srvconnect.SRVConnector(self.reactor,
  116. 'thisbetternotexist',
  117. 'example.org', self.factory,
  118. defaultPort=5222)
  119. client.theResolver.failure = ServiceNameUnknownError()
  120. self.connector.connect()
  121. self.assertIsNone(self.factory.reason)
  122. self.assertEqual(
  123. self.reactor.tcpClients.pop()[:2], ('example.org', 5222))
  124. def test_SRVNoResultUnknownServiceNoDefaultPort(self):
  125. """
  126. Connect fails on no result, unknown service and no default port.
  127. """
  128. self.connector = srvconnect.SRVConnector(self.reactor,
  129. 'thisbetternotexist',
  130. 'example.org', self.factory)
  131. client.theResolver.failure = ServiceNameUnknownError()
  132. self.connector.connect()
  133. self.assertTrue(self.factory.reason.check(ServiceNameUnknownError))
  134. def test_SRVBadResult(self):
  135. """
  136. Test connectTCP gets called with fallback parameters on bad result.
  137. """
  138. client.theResolver.results = [dns.RRHeader(name='example.org',
  139. type=dns.CNAME,
  140. cls=dns.IN, ttl=60,
  141. payload=None)]
  142. self.connector.connect()
  143. self.assertIsNone(self.factory.reason)
  144. self.assertEqual(
  145. self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
  146. def test_SRVNoService(self):
  147. """
  148. Test that connecting fails when no service is present.
  149. """
  150. payload = dns.Record_SRV(port=5269, target=b'.', ttl=60)
  151. client.theResolver.results = [dns.RRHeader(name='example.org',
  152. type=dns.SRV,
  153. cls=dns.IN, ttl=60,
  154. payload=payload)]
  155. self.connector.connect()
  156. self.assertIsNotNone(self.factory.reason)
  157. self.factory.reason.trap(DNSLookupError)
  158. self.assertEqual(self.reactor.tcpClients, [])
  159. def test_SRVLookupName(self):
  160. """
  161. The lookup name is a native string from service, protocol and domain.
  162. """
  163. client.theResolver.results = []
  164. self.connector.connect()
  165. name = client.theResolver.lookups[-1][0]
  166. self.assertEqual(nativeString('_xmpp-server._tcp.example.org'), name)
  167. def test_unicodeDomain(self):
  168. """
  169. L{srvconnect.SRVConnector} automatically encodes unicode domain using
  170. C{idna} encoding.
  171. """
  172. self.connector = srvconnect.SRVConnector(
  173. self.reactor, 'xmpp-client', u'\u00e9chec.example.org',
  174. self.factory)
  175. self.assertEqual('xn--chec-9oa.example.org', self.connector.domain)
  176. def test_pickServerWeights(self):
  177. """
  178. pickServer calculates running sum of weights and calls randint.
  179. This exercises the server selection algorithm specified in RFC 2782 by
  180. preparing fake L{random.randint} results and checking the values it was
  181. called with.
  182. """
  183. record1 = dns.Record_SRV(10, 10, 5222, 'host1.example.org')
  184. record2 = dns.Record_SRV(10, 20, 5222, 'host2.example.org')
  185. self.connector.orderedServers = [record1, record2]
  186. self.connector.servers = []
  187. self.patch(random, 'randint', self._randint)
  188. # 1st round
  189. self.randIntResults = [11, 0]
  190. self.connector.pickServer()
  191. self.assertEqual(self.randIntArgs[0], (0, 30))
  192. self.connector.pickServer()
  193. self.assertEqual(self.randIntArgs[1], (0, 10))
  194. # 2nd round
  195. self.randIntResults = [10, 0]
  196. self.connector.pickServer()
  197. self.assertEqual(self.randIntArgs[2], (0, 30))
  198. self.connector.pickServer()
  199. self.assertEqual(self.randIntArgs[3], (0, 20))
  200. def test_pickServerSamePriorities(self):
  201. """
  202. Two records with equal priorities compare on weight (ascending).
  203. """
  204. record1 = dns.Record_SRV(10, 10, 5222, 'host1.example.org')
  205. record2 = dns.Record_SRV(10, 20, 5222, 'host2.example.org')
  206. self.connector.orderedServers = [record2, record1]
  207. self.connector.servers = []
  208. self.patch(random, 'randint', self._randint)
  209. self.randIntResults = [0, 0]
  210. self.assertEqual(('host1.example.org', 5222),
  211. self.connector.pickServer())
  212. self.assertEqual(('host2.example.org', 5222),
  213. self.connector.pickServer())
  214. def test_srvDifferentPriorities(self):
  215. """
  216. Two records with differing priorities compare on priority (ascending).
  217. """
  218. record1 = dns.Record_SRV(10, 0, 5222, 'host1.example.org')
  219. record2 = dns.Record_SRV(20, 0, 5222, 'host2.example.org')
  220. self.connector.orderedServers = [record2, record1]
  221. self.connector.servers = []
  222. self.patch(random, 'randint', self._randint)
  223. self.randIntResults = [0, 0]
  224. self.assertEqual(('host1.example.org', 5222),
  225. self.connector.pickServer())
  226. self.assertEqual(('host2.example.org', 5222),
  227. self.connector.pickServer())