netutil_test.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. from __future__ import absolute_import, division, print_function
  2. import errno
  3. import os
  4. import signal
  5. import socket
  6. from subprocess import Popen
  7. import sys
  8. import time
  9. from tornado.netutil import (
  10. BlockingResolver, OverrideResolver, ThreadedResolver, is_valid_ip, bind_sockets
  11. )
  12. from tornado.stack_context import ExceptionStackContext
  13. from tornado.testing import AsyncTestCase, gen_test, bind_unused_port
  14. from tornado.test.util import unittest, skipIfNoNetwork, ignore_deprecation
  15. try:
  16. from concurrent import futures
  17. except ImportError:
  18. futures = None
  19. try:
  20. import pycares # type: ignore
  21. except ImportError:
  22. pycares = None
  23. else:
  24. from tornado.platform.caresresolver import CaresResolver
  25. try:
  26. import twisted # type: ignore
  27. import twisted.names # type: ignore
  28. except ImportError:
  29. twisted = None
  30. else:
  31. from tornado.platform.twisted import TwistedResolver
  32. class _ResolverTestMixin(object):
  33. def test_localhost(self):
  34. with ignore_deprecation():
  35. self.resolver.resolve('localhost', 80, callback=self.stop)
  36. result = self.wait()
  37. self.assertIn((socket.AF_INET, ('127.0.0.1', 80)), result)
  38. @gen_test
  39. def test_future_interface(self):
  40. addrinfo = yield self.resolver.resolve('localhost', 80,
  41. socket.AF_UNSPEC)
  42. self.assertIn((socket.AF_INET, ('127.0.0.1', 80)),
  43. addrinfo)
  44. # It is impossible to quickly and consistently generate an error in name
  45. # resolution, so test this case separately, using mocks as needed.
  46. class _ResolverErrorTestMixin(object):
  47. def test_bad_host(self):
  48. def handler(exc_typ, exc_val, exc_tb):
  49. self.stop(exc_val)
  50. return True # Halt propagation.
  51. with ignore_deprecation():
  52. with ExceptionStackContext(handler):
  53. self.resolver.resolve('an invalid domain', 80, callback=self.stop)
  54. result = self.wait()
  55. self.assertIsInstance(result, Exception)
  56. @gen_test
  57. def test_future_interface_bad_host(self):
  58. with self.assertRaises(IOError):
  59. yield self.resolver.resolve('an invalid domain', 80,
  60. socket.AF_UNSPEC)
  61. def _failing_getaddrinfo(*args):
  62. """Dummy implementation of getaddrinfo for use in mocks"""
  63. raise socket.gaierror(errno.EIO, "mock: lookup failed")
  64. @skipIfNoNetwork
  65. class BlockingResolverTest(AsyncTestCase, _ResolverTestMixin):
  66. def setUp(self):
  67. super(BlockingResolverTest, self).setUp()
  68. self.resolver = BlockingResolver()
  69. # getaddrinfo-based tests need mocking to reliably generate errors;
  70. # some configurations are slow to produce errors and take longer than
  71. # our default timeout.
  72. class BlockingResolverErrorTest(AsyncTestCase, _ResolverErrorTestMixin):
  73. def setUp(self):
  74. super(BlockingResolverErrorTest, self).setUp()
  75. self.resolver = BlockingResolver()
  76. self.real_getaddrinfo = socket.getaddrinfo
  77. socket.getaddrinfo = _failing_getaddrinfo
  78. def tearDown(self):
  79. socket.getaddrinfo = self.real_getaddrinfo
  80. super(BlockingResolverErrorTest, self).tearDown()
  81. class OverrideResolverTest(AsyncTestCase, _ResolverTestMixin):
  82. def setUp(self):
  83. super(OverrideResolverTest, self).setUp()
  84. mapping = {
  85. ('google.com', 80): ('1.2.3.4', 80),
  86. ('google.com', 80, socket.AF_INET): ('1.2.3.4', 80),
  87. ('google.com', 80, socket.AF_INET6): ('2a02:6b8:7c:40c:c51e:495f:e23a:3', 80)
  88. }
  89. self.resolver = OverrideResolver(BlockingResolver(), mapping)
  90. @gen_test
  91. def test_resolve_multiaddr(self):
  92. result = yield self.resolver.resolve('google.com', 80, socket.AF_INET)
  93. self.assertIn((socket.AF_INET, ('1.2.3.4', 80)), result)
  94. result = yield self.resolver.resolve('google.com', 80, socket.AF_INET6)
  95. self.assertIn((socket.AF_INET6, ('2a02:6b8:7c:40c:c51e:495f:e23a:3', 80, 0, 0)), result)
  96. @skipIfNoNetwork
  97. @unittest.skipIf(futures is None, "futures module not present")
  98. class ThreadedResolverTest(AsyncTestCase, _ResolverTestMixin):
  99. def setUp(self):
  100. super(ThreadedResolverTest, self).setUp()
  101. self.resolver = ThreadedResolver()
  102. def tearDown(self):
  103. self.resolver.close()
  104. super(ThreadedResolverTest, self).tearDown()
  105. class ThreadedResolverErrorTest(AsyncTestCase, _ResolverErrorTestMixin):
  106. def setUp(self):
  107. super(ThreadedResolverErrorTest, self).setUp()
  108. self.resolver = BlockingResolver()
  109. self.real_getaddrinfo = socket.getaddrinfo
  110. socket.getaddrinfo = _failing_getaddrinfo
  111. def tearDown(self):
  112. socket.getaddrinfo = self.real_getaddrinfo
  113. super(ThreadedResolverErrorTest, self).tearDown()
  114. @skipIfNoNetwork
  115. @unittest.skipIf(futures is None, "futures module not present")
  116. @unittest.skipIf(sys.platform == 'win32', "preexec_fn not available on win32")
  117. class ThreadedResolverImportTest(unittest.TestCase):
  118. def test_import(self):
  119. TIMEOUT = 5
  120. # Test for a deadlock when importing a module that runs the
  121. # ThreadedResolver at import-time. See resolve_test.py for
  122. # full explanation.
  123. command = [
  124. sys.executable,
  125. '-c',
  126. 'import tornado.test.resolve_test_helper']
  127. start = time.time()
  128. popen = Popen(command, preexec_fn=lambda: signal.alarm(TIMEOUT))
  129. while time.time() - start < TIMEOUT:
  130. return_code = popen.poll()
  131. if return_code is not None:
  132. self.assertEqual(0, return_code)
  133. return # Success.
  134. time.sleep(0.05)
  135. self.fail("import timed out")
  136. # We do not test errors with CaresResolver:
  137. # Some DNS-hijacking ISPs (e.g. Time Warner) return non-empty results
  138. # with an NXDOMAIN status code. Most resolvers treat this as an error;
  139. # C-ares returns the results, making the "bad_host" tests unreliable.
  140. # C-ares will try to resolve even malformed names, such as the
  141. # name with spaces used in this test.
  142. @skipIfNoNetwork
  143. @unittest.skipIf(pycares is None, "pycares module not present")
  144. class CaresResolverTest(AsyncTestCase, _ResolverTestMixin):
  145. def setUp(self):
  146. super(CaresResolverTest, self).setUp()
  147. self.resolver = CaresResolver()
  148. # TwistedResolver produces consistent errors in our test cases so we
  149. # could test the regular and error cases in the same class. However,
  150. # in the error cases it appears that cleanup of socket objects is
  151. # handled asynchronously and occasionally results in "unclosed socket"
  152. # warnings if not given time to shut down (and there is no way to
  153. # explicitly shut it down). This makes the test flaky, so we do not
  154. # test error cases here.
  155. @skipIfNoNetwork
  156. @unittest.skipIf(twisted is None, "twisted module not present")
  157. @unittest.skipIf(getattr(twisted, '__version__', '0.0') < "12.1", "old version of twisted")
  158. class TwistedResolverTest(AsyncTestCase, _ResolverTestMixin):
  159. def setUp(self):
  160. super(TwistedResolverTest, self).setUp()
  161. self.resolver = TwistedResolver()
  162. class IsValidIPTest(unittest.TestCase):
  163. def test_is_valid_ip(self):
  164. self.assertTrue(is_valid_ip('127.0.0.1'))
  165. self.assertTrue(is_valid_ip('4.4.4.4'))
  166. self.assertTrue(is_valid_ip('::1'))
  167. self.assertTrue(is_valid_ip('2620:0:1cfe:face:b00c::3'))
  168. self.assertTrue(not is_valid_ip('www.google.com'))
  169. self.assertTrue(not is_valid_ip('localhost'))
  170. self.assertTrue(not is_valid_ip('4.4.4.4<'))
  171. self.assertTrue(not is_valid_ip(' 127.0.0.1'))
  172. self.assertTrue(not is_valid_ip(''))
  173. self.assertTrue(not is_valid_ip(' '))
  174. self.assertTrue(not is_valid_ip('\n'))
  175. self.assertTrue(not is_valid_ip('\x00'))
  176. class TestPortAllocation(unittest.TestCase):
  177. def test_same_port_allocation(self):
  178. if 'TRAVIS' in os.environ:
  179. self.skipTest("dual-stack servers often have port conflicts on travis")
  180. sockets = bind_sockets(None, 'localhost')
  181. try:
  182. port = sockets[0].getsockname()[1]
  183. self.assertTrue(all(s.getsockname()[1] == port
  184. for s in sockets[1:]))
  185. finally:
  186. for sock in sockets:
  187. sock.close()
  188. @unittest.skipIf(not hasattr(socket, "SO_REUSEPORT"), "SO_REUSEPORT is not supported")
  189. def test_reuse_port(self):
  190. sockets = []
  191. socket, port = bind_unused_port(reuse_port=True)
  192. try:
  193. sockets = bind_sockets(port, '127.0.0.1', reuse_port=True)
  194. self.assertTrue(all(s.getsockname()[1] == port for s in sockets))
  195. finally:
  196. socket.close()
  197. for sock in sockets:
  198. sock.close()