sockcreator.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  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. import socket
  16. import struct
  17. import os
  18. import errno
  19. import copy
  20. import subprocess
  21. import copy
  22. from isc.log_messages.init_messages import *
  23. from libutil_io_python import recv_fd
  24. logger = isc.log.Logger("init")
  25. """
  26. Module that comunicates with the privileged socket creator (b10-sockcreator).
  27. """
  28. class CreatorError(Exception):
  29. """
  30. Exception for socket creator related errors.
  31. It has two members: fatal and errno and they are just holding the values
  32. passed to the __init__ function.
  33. """
  34. def __init__(self, message, fatal, error_num=None):
  35. """
  36. Creates the exception. The message argument is the usual string.
  37. The fatal one tells if the error is fatal (eg. the creator crashed)
  38. and error_num is the errno value returned from socket creator, if
  39. applicable.
  40. """
  41. Exception.__init__(self, message)
  42. self.fatal = fatal
  43. self.errno = error_num
  44. class Parser:
  45. """
  46. This class knows the sockcreator language. It creates commands, sends them
  47. and receives the answers and parses them.
  48. It does not start it, the communication channel must be provided.
  49. In theory, anything here can throw a fatal CreatorError exception, but it
  50. happens only in case something like the creator process crashes. Any other
  51. occasions are mentioned explicitly.
  52. """
  53. def __init__(self, creator_socket):
  54. """
  55. Creates the parser. The creator_socket is socket to the socket creator
  56. process that will be used for communication. However, the object must
  57. have a read_fd() method to read the file descriptor. This slightly
  58. unusual trick with modifying an object is used to easy up testing.
  59. You can use WrappedSocket in production code to add the method to any
  60. ordinary socket.
  61. """
  62. self.__socket = creator_socket
  63. logger.info(BIND10_SOCKCREATOR_INIT)
  64. def terminate(self):
  65. """
  66. Asks the creator process to terminate and waits for it to close the
  67. socket. Does not return anything. Raises a CreatorError if there is
  68. still data on the socket, if there is an error closing the socket,
  69. or if the socket had already been closed.
  70. """
  71. if self.__socket is None:
  72. raise CreatorError('Terminated already', True)
  73. logger.info(BIND10_SOCKCREATOR_TERMINATE)
  74. try:
  75. self.__socket.sendall(b'T')
  76. # Wait for an EOF - it will return empty data
  77. eof = self.__socket.recv(1)
  78. if len(eof) != 0:
  79. raise CreatorError('Protocol error - data after terminated',
  80. True)
  81. self.__socket = None
  82. except socket.error as se:
  83. self.__socket = None
  84. raise CreatorError(str(se), True)
  85. def __addrport_str(self, address, port):
  86. '''Convert a pair of IP address and port to common form for logging.'''
  87. if address.family == socket.AF_INET:
  88. return str(address) + ':' + str(port)
  89. else:
  90. return '[' + str(address) + ']:' + str(port)
  91. def get_socket(self, address, port, socktype):
  92. """
  93. Asks the socket creator process to create a socket. Pass an address
  94. (the isc.net.IPaddr object), port number and socket type (either
  95. string "UDP", "TCP" or constant socket.SOCK_DGRAM or
  96. socket.SOCK_STREAM.
  97. Blocks until it is provided by the socket creator process (which
  98. should be fast, as it is on localhost) and returns the file descriptor
  99. number. It raises a CreatorError exception if the creation fails.
  100. """
  101. if self.__socket is None:
  102. raise CreatorError('Socket requested on terminated creator', True)
  103. # First, assemble the request from parts
  104. logger.info(BIND10_SOCKET_GET, address, port, socktype)
  105. data = b'S'
  106. if socktype == 'UDP' or socktype == socket.SOCK_DGRAM:
  107. data += b'U'
  108. elif socktype == 'TCP' or socktype == socket.SOCK_STREAM:
  109. data += b'T'
  110. else:
  111. raise ValueError('Unknown socket type: ' + str(socktype))
  112. if address.family == socket.AF_INET:
  113. data += b'4'
  114. elif address.family == socket.AF_INET6:
  115. data += b'6'
  116. else:
  117. raise ValueError('Unknown address family in address')
  118. data += struct.pack('!H', port)
  119. data += address.addr
  120. try:
  121. # Send the request
  122. self.__socket.sendall(data)
  123. answer = self.__socket.recv(1)
  124. if answer == b'S':
  125. # Success!
  126. result = self.__socket.read_fd()
  127. logger.info(BIND10_SOCKET_CREATED, result)
  128. return result
  129. elif answer == b'E':
  130. # There was an error, read the error as well
  131. error = self.__socket.recv(1)
  132. rcv_errno = struct.unpack('i',
  133. self.__read_all(len(struct.pack('i',
  134. 0))))
  135. if error == b'S':
  136. cause = 'socket'
  137. elif error == b'B':
  138. cause = 'bind'
  139. else:
  140. self.__socket = None
  141. logger.fatal(BIND10_SOCKCREATOR_BAD_CAUSE, error)
  142. raise CreatorError('Unknown error cause' + str(answer), True)
  143. logger.error(BIND10_SOCKET_ERROR, cause, rcv_errno[0],
  144. os.strerror(rcv_errno[0]))
  145. # Provide as detailed information as possible on the error,
  146. # as error related to socket creation is a common operation
  147. # trouble. In particular, we are intentionally very verbose
  148. # if it fails due to "permission denied" so the administrator
  149. # can easily identify what is wrong and how to fix it.
  150. addrport = self.__addrport_str(address, port)
  151. error_text = 'Error creating socket on ' + cause + \
  152. ' to be bound to ' + addrport + ': ' + \
  153. os.strerror(rcv_errno[0])
  154. if rcv_errno[0] == errno.EACCES:
  155. error_text += ' - probably need to restart BIND 10 ' + \
  156. 'as a super user'
  157. raise CreatorError(error_text, False, rcv_errno[0])
  158. else:
  159. self.__socket = None
  160. logger.fatal(BIND10_SOCKCREATOR_BAD_RESPONSE, answer)
  161. raise CreatorError('Unknown response ' + str(answer), True)
  162. except socket.error as se:
  163. self.__socket = None
  164. logger.fatal(BIND10_SOCKCREATOR_TRANSPORT_ERROR, str(se))
  165. raise CreatorError(str(se), True)
  166. def __read_all(self, length):
  167. """
  168. Keeps reading until length data is read or EOF or error happens.
  169. EOF is considered error as well and throws a CreatorError.
  170. """
  171. result = b''
  172. while len(result) < length:
  173. data = self.__socket.recv(length - len(result))
  174. if len(data) == 0:
  175. self.__socket = None
  176. logger.fatal(BIND10_SOCKCREATOR_EOF)
  177. raise CreatorError('Unexpected EOF', True)
  178. result += data
  179. return result
  180. class WrappedSocket:
  181. """
  182. This class wraps a socket and adds a read_fd method, so it can be used
  183. for the Parser class conveniently. It simply copies all its guts into
  184. itself and implements the method.
  185. """
  186. def __init__(self, socket):
  187. # Copy whatever can be copied from the socket
  188. for name in dir(socket):
  189. if name not in ['__class__', '__weakref__']:
  190. setattr(self, name, getattr(socket, name))
  191. # Keep the socket, so we can prevent it from being garbage-collected
  192. # and closed before we are removed ourself
  193. self.__orig_socket = socket
  194. def read_fd(self):
  195. """
  196. Read the file descriptor from the socket.
  197. """
  198. return recv_fd(self.fileno())
  199. # FIXME: Any idea how to test this? Starting an external process doesn't sound
  200. # OK
  201. class Creator(Parser):
  202. """
  203. This starts the socket creator and allows asking for the sockets.
  204. Note: __process shouldn't be reset once created. See the note
  205. of the SockCreator class for details.
  206. """
  207. def __init__(self, path):
  208. (local, remote) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
  209. # Popen does not like, for some reason, having the same socket for
  210. # stdin as well as stdout, so we dup it before passing it there.
  211. remote2 = socket.fromfd(remote.fileno(), socket.AF_UNIX,
  212. socket.SOCK_STREAM)
  213. env = copy.deepcopy(os.environ)
  214. env['PATH'] = path
  215. # We explicitly set close_fs to True; it's False by default before
  216. # Python 3.2. If we don't close the remaining FDs, the copy of
  217. # 'local' will prevent the child process from terminating when
  218. # the parent process died abruptly.
  219. self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
  220. stdin=remote.fileno(),
  221. stdout=remote2.fileno(),
  222. close_fds=True,
  223. preexec_fn=self.__preexec_work)
  224. remote.close()
  225. remote2.close()
  226. Parser.__init__(self, WrappedSocket(local))
  227. def __preexec_work(self):
  228. """Function used before running a program that needs to run as a
  229. different user."""
  230. # Put us into a separate process group so we don't get
  231. # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
  232. # other means).
  233. os.setpgrp()
  234. def pid(self):
  235. return self.__process.pid
  236. def kill(self):
  237. logger.warn(BIND10_SOCKCREATOR_KILL)
  238. if self.__process is not None:
  239. self.__process.kill()