socketsession_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. # Copyright (C) 2011 Internet Systems Consortium.
  2. #
  3. # Permission to use, copy, modify, and distribute this software for any
  4. # purpose with or without fee is hereby granted, provided that the above
  5. # copyright notice and this permission notice appear in all copies.
  6. #
  7. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  8. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  9. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  10. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  11. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  12. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  13. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  14. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. import os, signal, socket, unittest
  16. from socket import AF_INET, AF_INET6, SOCK_STREAM, SOCK_DGRAM, IPPROTO_UDP, \
  17. IPPROTO_TCP
  18. from isc.util.cio.socketsession import *
  19. TESTDATA_OBJDIR = os.getenv("TESTDATAOBJDIR")
  20. TEST_UNIX_FILE = TESTDATA_OBJDIR + '/ssessiontest.unix'
  21. TEST_DATA = b'BIND10 test'
  22. TEST_PORT = 53535
  23. class TestForwarder(unittest.TestCase):
  24. '''In general, this is a straightforward port of the C++ counterpart.
  25. In some cases test cases are simplified or have Python specific cases.
  26. '''
  27. def setUp(self):
  28. self.forwarder = SocketSessionForwarder(TEST_UNIX_FILE)
  29. if os.path.exists(TEST_UNIX_FILE):
  30. os.unlink(TEST_UNIX_FILE)
  31. self.large_text = b'a' * 65535
  32. def tearDown(self):
  33. if os.path.exists(TEST_UNIX_FILE):
  34. os.unlink(TEST_UNIX_FILE)
  35. def start_listen(self):
  36. self.listen_sock = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
  37. self.listen_sock.bind(TEST_UNIX_FILE)
  38. self.listen_sock.listen(10)
  39. def accept_forwarder(self):
  40. self.listen_sock.setblocking(False)
  41. s, _ = self.listen_sock.accept()
  42. s.setblocking(True)
  43. return s
  44. def test_init(self):
  45. # check bad arguments. valid cases will covered in other tests.
  46. self.assertRaises(TypeError, SocketSessionForwarder, 1)
  47. self.assertRaises(TypeError, SocketSessionForwarder,
  48. 'test.unix', 'test.unix')
  49. def test_badpush(self):
  50. # bad numbers of parameters
  51. self.assertRaises(TypeError, self.forwarder.push, 1)
  52. self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
  53. SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
  54. ('192.0.2.1', 5300), TEST_DATA, 0)
  55. # contain a bad type of parameter
  56. self.assertRaises(TypeError, self.forwarder.push, 0, 'AF_INET',
  57. SOCK_DGRAM, IPPROTO_UDP, ('127.0.0.1', 53),
  58. ('192.0.2.1', 5300), TEST_DATA)
  59. # bad local address
  60. self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
  61. SOCK_DGRAM, IPPROTO_UDP, ('127.0.0..1', 53),
  62. ('192.0.2.1', 5300), TEST_DATA)
  63. self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
  64. SOCK_DGRAM, IPPROTO_UDP, '127.0.0.1',
  65. ('192.0.2.1', 5300), TEST_DATA)
  66. # bad remote address
  67. self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET6,
  68. SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53),
  69. ('2001:db8:::3', 5300), TEST_DATA)
  70. # push before connect
  71. self.assertRaises(TypeError, self.forwarder.push, 0, AF_INET,
  72. SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
  73. ('192.0.2.2', 53), TEST_DATA)
  74. # Now connect the forwarder for the rest of tests
  75. self.start_listen()
  76. self.forwarder.connect_to_receiver()
  77. # Inconsistent address family
  78. self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
  79. SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
  80. ('192.0.2.2', 53), TEST_DATA)
  81. self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET6,
  82. SOCK_DGRAM, IPPROTO_UDP, ('2001:db8::1', 53, 0, 1),
  83. ('192.0.2.2', 53), TEST_DATA)
  84. # Empty data: we reject them at least for now
  85. self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
  86. SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
  87. ('192.0.2.2', 53), b'')
  88. # Too big data: we reject them at least for now
  89. self.assertRaises(TypeError, self.forwarder.push, 1, AF_INET,
  90. SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
  91. ('192.0.2.2', 53), b'd' * 65536)
  92. # Close the receptor before push. It will result in SIGPIPE (should be
  93. # ignored) and EPIPE, which will be converted to SocketSessionError.
  94. self.listen_sock.close()
  95. self.assertRaises(SocketSessionError, self.forwarder.push, 1, AF_INET,
  96. SOCK_DGRAM, IPPROTO_UDP, ('192.0.2.1', 53),
  97. ('192.0.2.2', 53), TEST_DATA)
  98. def create_socket(self, family, type, protocol, addr, do_listen):
  99. s = socket.socket(family, type, protocol)
  100. s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  101. s.bind(addr)
  102. if do_listen and protocol == IPPROTO_TCP:
  103. s.listen(1)
  104. return s
  105. def check_push_and_pop(self, family, type, protocol, local, remote,
  106. data, new_connection):
  107. sock = self.create_socket(family, type, protocol, local, True)
  108. fwd_fd = sock.fileno()
  109. if protocol == IPPROTO_TCP:
  110. client_addr = ('::1', 0, 0, 0) if family == AF_INET6 \
  111. else ('127.0.0.1', 0)
  112. client_sock = self.create_socket(family, type, protocol,
  113. client_addr, False)
  114. client_sock.setblocking(False)
  115. try:
  116. client_sock.connect(local)
  117. except socket.error:
  118. pass
  119. server_sock, _ = sock.accept()
  120. fwd_fd = server_sock.fileno()
  121. # If a new connection is required, start the "server", have the
  122. # internal forwarder connect to it, and then internally accept it.
  123. if new_connection:
  124. self.start_listen()
  125. self.forwarder.connect_to_receiver()
  126. self.accept_sock = self.accept_forwarder()
  127. # Then push one socket session via the forwarder.
  128. self.forwarder.push(fwd_fd, family, type, protocol, local, remote,
  129. data)
  130. # Pop the socket session we just pushed from a local receiver, and
  131. # check the content.
  132. receiver = SocketSessionReceiver(self.accept_sock)
  133. signal.alarm(1)
  134. sock_session = receiver.pop()
  135. signal.alarm(0)
  136. passed_sock = sock_session[0]
  137. self.assertNotEqual(fwd_fd, passed_sock.fileno())
  138. self.assertEqual(family, passed_sock.family)
  139. self.assertEqual(type, passed_sock.type)
  140. self.assertEqual(protocol, passed_sock.proto)
  141. self.assertEqual(local, sock_session[1])
  142. self.assertEqual(remote, sock_session[2])
  143. self.assertEqual(data, sock_session[3])
  144. # Check if the passed FD is usable by sending some data from it.
  145. passed_sock.setblocking(True)
  146. if protocol == IPPROTO_UDP:
  147. self.assertEqual(len(TEST_DATA), passed_sock.sendto(TEST_DATA,
  148. local))
  149. sock.settimeout(10)
  150. self.assertEqual(TEST_DATA, sock.recvfrom(len(TEST_DATA))[0])
  151. else:
  152. server_sock.close()
  153. self.assertEqual(len(TEST_DATA), passed_sock.send(TEST_DATA))
  154. client_sock.setblocking(True)
  155. client_sock.settimeout(10)
  156. self.assertEqual(TEST_DATA, client_sock.recv(len(TEST_DATA)))
  157. def test_push_and_pop(self):
  158. # This is a straightforward port of C++ pushAndPop test.
  159. local6 = ('::1', TEST_PORT, 0, 0)
  160. remote6 = ('2001:db8::1', 5300, 0, 0)
  161. self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
  162. local6, remote6, TEST_DATA, True)
  163. self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
  164. local6, remote6, TEST_DATA, False)
  165. local4 = ('127.0.0.1', TEST_PORT)
  166. remote4 = ('192.0.2.2', 5300)
  167. self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
  168. local4, remote4, TEST_DATA, False)
  169. self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
  170. local4, remote4, TEST_DATA, False)
  171. self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
  172. local6, remote6, self.large_text, False)
  173. self.check_push_and_pop(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
  174. local6, remote6, self.large_text, False)
  175. self.check_push_and_pop(AF_INET, SOCK_DGRAM, IPPROTO_UDP,
  176. local4, remote4, self.large_text, False)
  177. self.check_push_and_pop(AF_INET, SOCK_STREAM, IPPROTO_TCP,
  178. local4, remote4, self.large_text, False)
  179. # Python specific: check for an IPv6 scoped address with non 0
  180. # scope (zone) ID
  181. scope6 = ('fe80::1', TEST_PORT, 0, 1)
  182. self.check_push_and_pop(AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
  183. local6, scope6, TEST_DATA, False)
  184. def test_push_too_fast(self):
  185. # A straightforward port of C++ pushTooFast test.
  186. def multi_push(forwarder, addr, data):
  187. for i in range(0, 10):
  188. forwarder.push(1, AF_INET, SOCK_DGRAM, IPPROTO_UDP, addr,
  189. addr, data)
  190. self.start_listen()
  191. self.forwarder.connect_to_receiver()
  192. self.assertRaises(SocketSessionError, multi_push, self.forwarder,
  193. ('192.0.2.1', 53), self.large_text)
  194. def test_bad_pop(self):
  195. # This is a subset of C++ badPop test. We only check pop() raises
  196. # SocketSessionError when it internally fails to get the FD.
  197. # Other cases would require passing a valid FD from the test,
  198. # which would make the test too complicated. As a wrapper checking
  199. # one common failure case should be reasonably sufficient.
  200. self.start_listen()
  201. s = socket.socket(socket.AF_UNIX, SOCK_STREAM, 0)
  202. s.setblocking(False)
  203. s.connect(TEST_UNIX_FILE)
  204. accept_sock = self.accept_forwarder()
  205. receiver = SocketSessionReceiver(accept_sock)
  206. s.close()
  207. self.assertRaises(SocketSessionError, receiver.pop)
  208. class TestReceiver(unittest.TestCase):
  209. # We only check a couple of failure cases on construction. Valid cases
  210. # are covered in TestForwarder.
  211. def test_bad_init(self):
  212. class FakeSocket:
  213. # pretending to be th standard socket class, but its fileno() is
  214. # bogus.
  215. def fileno(self):
  216. return None
  217. self.assertRaises(TypeError, SocketSessionReceiver, 1)
  218. self.assertRaises(TypeError, SocketSessionReceiver, FakeSocket())
  219. if __name__ == '__main__':
  220. unittest.main()