sockcreator_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. # Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
  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. """
  16. Tests for the bind10.sockcreator module.
  17. """
  18. import unittest
  19. import struct
  20. import socket
  21. from isc.net.addr import IPAddr
  22. import isc.log
  23. from libutil_io_python import send_fd
  24. from isc.bind10.sockcreator import Parser, CreatorError, WrappedSocket
  25. class FakeCreator:
  26. """
  27. Class emulating the socket to the socket creator. It can be given expected
  28. data to receive (and check) and responses to give to the Parser class
  29. during testing.
  30. """
  31. class InvalidPlan(Exception):
  32. """
  33. Raised when someone wants to recv when sending is planned or vice
  34. versa.
  35. """
  36. pass
  37. class InvalidData(Exception):
  38. """
  39. Raises when the data passed to sendall are not the same as expected.
  40. """
  41. pass
  42. def __init__(self, plan):
  43. """
  44. Create the object. The plan variable contains list of expected actions,
  45. in form:
  46. [('r', 'Data to return from recv'), ('s', 'Data expected on sendall'),
  47. , ('d', 'File descriptor number to return from read_sock'), ('e',
  48. None), ...]
  49. It modifies the array as it goes.
  50. """
  51. self.__plan = plan
  52. def __get_plan(self, expected):
  53. if len(self.__plan) == 0:
  54. raise InvalidPlan('Nothing more planned')
  55. (kind, data) = self.__plan[0]
  56. if kind == 'e':
  57. self.__plan.pop(0)
  58. raise socket.error('False socket error')
  59. if kind != expected:
  60. raise InvalidPlan('Planned ' + kind + ', but ' + expected +
  61. 'requested')
  62. return data
  63. def recv(self, maxsize):
  64. """
  65. Emulate recv. Returs maxsize bytes from the current recv plan. If
  66. there are data left from previous recv call, it is used first.
  67. If no recv is planned, raises InvalidPlan.
  68. """
  69. data = self.__get_plan('r')
  70. result, rest = data[:maxsize], data[maxsize:]
  71. if len(rest) > 0:
  72. self.__plan[0] = ('r', rest)
  73. else:
  74. self.__plan.pop(0)
  75. return result
  76. def read_fd(self):
  77. """
  78. Emulate the reading of file descriptor. Returns one from a plan.
  79. It raises InvalidPlan if no socket is planned now.
  80. """
  81. fd = self.__get_plan('f')
  82. self.__plan.pop(0)
  83. return fd
  84. def sendall(self, data):
  85. """
  86. Checks that the data passed are correct according to plan. It raises
  87. InvalidData if the data differs or InvalidPlan when sendall is not
  88. expected.
  89. """
  90. planned = self.__get_plan('s')
  91. dlen = len(data)
  92. prefix, rest = planned[:dlen], planned[dlen:]
  93. if prefix != data:
  94. raise InvalidData('Expected "' + str(prefix)+ '", got "' +
  95. str(data) + '"')
  96. if len(rest) > 0:
  97. self.__plan[0] = ('s', rest)
  98. else:
  99. self.__plan.pop(0)
  100. def all_used(self):
  101. """
  102. Returns if the whole plan was consumed.
  103. """
  104. return len(self.__plan) == 0
  105. class ParserTests(unittest.TestCase):
  106. """
  107. Testcases for the Parser class.
  108. A lot of these test could be done by
  109. `with self.assertRaises(CreatorError) as cm`. But some versions of python
  110. take the scope wrong and don't work, so we use the primitive way of
  111. try-except.
  112. """
  113. def __terminate(self):
  114. creator = FakeCreator([('s', b'T'), ('r', b'')])
  115. parser = Parser(creator)
  116. self.assertEqual(None, parser.terminate())
  117. self.assertTrue(creator.all_used())
  118. return parser
  119. def test_terminate(self):
  120. """
  121. Test if the command to terminate is correct and it waits for reading the
  122. EOF.
  123. """
  124. self.__terminate()
  125. def __terminate_raises(self, parser):
  126. """
  127. Check that terminate() raises a fatal exception.
  128. """
  129. try:
  130. parser.terminate()
  131. self.fail("Not raised")
  132. except CreatorError as ce:
  133. self.assertTrue(ce.fatal)
  134. self.assertEqual(None, ce.errno)
  135. def test_terminate_error1(self):
  136. """
  137. Test it reports an exception when there's error terminating the creator.
  138. This one raises an error when receiving the EOF.
  139. """
  140. creator = FakeCreator([('s', b'T'), ('e', None)])
  141. parser = Parser(creator)
  142. self.__terminate_raises(parser)
  143. def test_terminate_error2(self):
  144. """
  145. Test it reports an exception when there's error terminating the creator.
  146. This one raises an error when sending data.
  147. """
  148. creator = FakeCreator([('e', None)])
  149. parser = Parser(creator)
  150. self.__terminate_raises(parser)
  151. def test_terminate_error3(self):
  152. """
  153. Test it reports an exception when there's error terminating the creator.
  154. This one sends data when it should have terminated.
  155. """
  156. creator = FakeCreator([('s', b'T'), ('r', b'Extra data')])
  157. parser = Parser(creator)
  158. self.__terminate_raises(parser)
  159. def test_terminate_twice(self):
  160. """
  161. Test we can't terminate twice.
  162. """
  163. parser = self.__terminate()
  164. self.__terminate_raises(parser)
  165. def test_crash(self):
  166. """
  167. Tests that the parser correctly raises exception when it crashes
  168. unexpectedly.
  169. """
  170. creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'')])
  171. parser = Parser(creator)
  172. try:
  173. parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
  174. self.fail("Not raised")
  175. except CreatorError as ce:
  176. self.assertTrue(creator.all_used())
  177. # Is the exception correct?
  178. self.assertTrue(ce.fatal)
  179. self.assertEqual(None, ce.errno)
  180. def test_error(self):
  181. """
  182. Tests that the parser correctly raises non-fatal exception when
  183. the socket can not be created.
  184. """
  185. # We split the int to see if it can cope with data coming in
  186. # different packets
  187. intpart = struct.pack('@i', 42)
  188. creator = FakeCreator([('s', b'SU4\0\0\0\0\0\0'), ('r', b'ES' +
  189. intpart[:1]), ('r', intpart[1:])])
  190. parser = Parser(creator)
  191. try:
  192. parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
  193. self.fail("Not raised")
  194. except CreatorError as ce:
  195. self.assertTrue(creator.all_used())
  196. # Is the exception correct?
  197. self.assertFalse(ce.fatal)
  198. self.assertEqual(42, ce.errno)
  199. def __error(self, plan):
  200. creator = FakeCreator(plan)
  201. parser = Parser(creator)
  202. try:
  203. parser.get_socket(IPAddr('0.0.0.0'), 0, socket.SOCK_DGRAM)
  204. self.fail("Not raised")
  205. except CreatorError as ce:
  206. self.assertTrue(creator.all_used())
  207. self.assertTrue(ce.fatal)
  208. def test_error_send(self):
  209. self.__error([('e', None)])
  210. def test_error_recv(self):
  211. self.__error([('s', b'SU4\0\0\0\0\0\0'), ('e', None)])
  212. def test_error_read_fd(self):
  213. self.__error([('s', b'SU4\0\0\0\0\0\0'), ('r', b'S'), ('e', None)])
  214. def __create(self, addr, socktype, encoded):
  215. creator = FakeCreator([('s', b'S' + encoded), ('r', b'S'), ('f', 42)])
  216. parser = Parser(creator)
  217. self.assertEqual(42, parser.get_socket(IPAddr(addr), 42, socktype))
  218. def test_create1(self):
  219. self.__create('192.0.2.0', 'UDP', b'U4\0\x2A\xC0\0\x02\0')
  220. def test_create2(self):
  221. self.__create('2001:db8::', socket.SOCK_STREAM,
  222. b'T6\0\x2A\x20\x01\x0d\xb8\0\0\0\0\0\0\0\0\0\0\0\0')
  223. def test_create_terminated(self):
  224. """
  225. Test we can't request sockets after it was terminated.
  226. """
  227. parser = self.__terminate()
  228. try:
  229. parser.get_socket(IPAddr('0.0.0.0'), 0, 'UDP')
  230. self.fail("Not raised")
  231. except CreatorError as ce:
  232. self.assertTrue(ce.fatal)
  233. self.assertEqual(None, ce.errno)
  234. def test_invalid_socktype(self):
  235. """
  236. Test invalid socket type is rejected
  237. """
  238. self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
  239. IPAddr('0.0.0.0'), 42, 'RAW')
  240. def test_invalid_family(self):
  241. """
  242. Test it rejects invalid address family.
  243. """
  244. # Note: this produces a bad logger output, since this address
  245. # can not be converted to string, so the original message with
  246. # placeholders is output. This should not happen in practice, so
  247. # it is harmless.
  248. addr = IPAddr('0.0.0.0')
  249. addr.family = 42
  250. self.assertRaises(ValueError, Parser(FakeCreator([])).get_socket,
  251. addr, 42, socket.SOCK_DGRAM)
  252. class WrapTests(unittest.TestCase):
  253. """
  254. Tests for the wrap_socket function.
  255. """
  256. def test_wrap(self):
  257. # We construct two pairs of socket. The receiving side of one pair will
  258. # be wrapped. Then we send one of the other pair through this pair and
  259. # check the received one can be used as a socket
  260. # The transport socket
  261. (t1, t2) = socket.socketpair()
  262. # The payload socket
  263. (p1, p2) = socket.socketpair()
  264. t2 = WrappedSocket(t2)
  265. # Transfer the descriptor
  266. send_fd(t1.fileno(), p1.fileno())
  267. p1 = socket.fromfd(t2.read_fd(), socket.AF_UNIX, socket.SOCK_STREAM)
  268. # Now, pass some data trough the socket
  269. p1.send(b'A')
  270. data = p2.recv(1)
  271. self.assertEqual(b'A', data)
  272. # Test the wrapping didn't hurt the socket's usual methods
  273. t1.send(b'B')
  274. data = t2.recv(1)
  275. self.assertEqual(b'B', data)
  276. t2.send(b'C')
  277. data = t1.recv(1)
  278. self.assertEqual(b'C', data)
  279. if __name__ == '__main__':
  280. isc.log.init("bind10") # FIXME Should this be needed?
  281. isc.log.resetUnitTestRootLogger()
  282. unittest.main()