SocketService.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. # -*- coding: utf-8 -*-
  2. # Description:
  3. # Author: Pawel Krupa (paulfantom)
  4. # Author: Ilya Mashchenko (ilyam8)
  5. # SPDX-License-Identifier: GPL-3.0-or-later
  6. import errno
  7. import socket
  8. try:
  9. import ssl
  10. except ImportError:
  11. _TLS_SUPPORT = False
  12. else:
  13. _TLS_SUPPORT = True
  14. if _TLS_SUPPORT:
  15. try:
  16. PROTOCOL_TLS = ssl.PROTOCOL_TLS
  17. except AttributeError:
  18. PROTOCOL_TLS = ssl.PROTOCOL_SSLv23
  19. from bases.FrameworkServices.SimpleService import SimpleService
  20. DEFAULT_CONNECT_TIMEOUT = 2.0
  21. DEFAULT_READ_TIMEOUT = 2.0
  22. DEFAULT_WRITE_TIMEOUT = 2.0
  23. class SocketService(SimpleService):
  24. def __init__(self, configuration=None, name=None):
  25. self._sock = None
  26. self._keep_alive = False
  27. self.host = 'localhost'
  28. self.port = None
  29. self.unix_socket = None
  30. self.dgram_socket = False
  31. self.request = ''
  32. self.tls = False
  33. self.cert = None
  34. self.key = None
  35. self.__socket_config = None
  36. self.__empty_request = "".encode()
  37. SimpleService.__init__(self, configuration=configuration, name=name)
  38. self.connect_timeout = configuration.get('connect_timeout', DEFAULT_CONNECT_TIMEOUT)
  39. self.read_timeout = configuration.get('read_timeout', DEFAULT_READ_TIMEOUT)
  40. self.write_timeout = configuration.get('write_timeout', DEFAULT_WRITE_TIMEOUT)
  41. def _socket_error(self, message=None):
  42. if self.unix_socket is not None:
  43. self.error('unix socket "{socket}": {message}'.format(socket=self.unix_socket,
  44. message=message))
  45. else:
  46. if self.__socket_config is not None:
  47. _, _, _, _, sa = self.__socket_config
  48. self.error('socket to "{address}" port {port}: {message}'.format(address=sa[0],
  49. port=sa[1],
  50. message=message))
  51. else:
  52. self.error('unknown socket: {0}'.format(message))
  53. def _connect2socket(self, res=None):
  54. """
  55. Connect to a socket, passing the result of getaddrinfo()
  56. :return: boolean
  57. """
  58. if res is None:
  59. res = self.__socket_config
  60. if res is None:
  61. self.error("Cannot create socket to 'None':")
  62. return False
  63. af, sock_type, proto, _, sa = res
  64. try:
  65. self.debug('Creating socket to "{address}", port {port}'.format(address=sa[0], port=sa[1]))
  66. self._sock = socket.socket(af, sock_type, proto)
  67. except socket.error as error:
  68. self.error('Failed to create socket "{address}", port {port}, error: {error}'.format(address=sa[0],
  69. port=sa[1],
  70. error=error))
  71. self._sock = None
  72. self.__socket_config = None
  73. return False
  74. if self.tls:
  75. try:
  76. self.debug('Encapsulating socket with TLS')
  77. self.debug('Using keyfile: {0}, certfile: {1}, cert_reqs: {2}, ssl_version: {3}'.format(
  78. self.key, self.cert, ssl.CERT_NONE, PROTOCOL_TLS
  79. ))
  80. self._sock = ssl.wrap_socket(self._sock,
  81. keyfile=self.key,
  82. certfile=self.cert,
  83. server_side=False,
  84. cert_reqs=ssl.CERT_NONE,
  85. ssl_version=PROTOCOL_TLS,
  86. )
  87. except (socket.error, ssl.SSLError, IOError, OSError) as error:
  88. self.error('failed to wrap socket : {0}'.format(repr(error)))
  89. self._disconnect()
  90. self.__socket_config = None
  91. return False
  92. try:
  93. self.debug('connecting socket to "{address}", port {port}'.format(address=sa[0], port=sa[1]))
  94. self._sock.settimeout(self.connect_timeout)
  95. self.debug('set socket connect timeout to: {0}'.format(self._sock.gettimeout()))
  96. self._sock.connect(sa)
  97. except (socket.error, ssl.SSLError) as error:
  98. self.error('Failed to connect to "{address}", port {port}, error: {error}'.format(address=sa[0],
  99. port=sa[1],
  100. error=error))
  101. self._disconnect()
  102. self.__socket_config = None
  103. return False
  104. self.debug('connected to "{address}", port {port}'.format(address=sa[0], port=sa[1]))
  105. self.__socket_config = res
  106. return True
  107. def _connect2unixsocket(self):
  108. """
  109. Connect to a unix socket, given its filename
  110. :return: boolean
  111. """
  112. if self.unix_socket is None:
  113. self.error("cannot connect to unix socket 'None'")
  114. return False
  115. try:
  116. self.debug('attempting DGRAM unix socket "{0}"'.format(self.unix_socket))
  117. self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
  118. self._sock.settimeout(self.connect_timeout)
  119. self.debug('set socket connect timeout to: {0}'.format(self._sock.gettimeout()))
  120. self._sock.connect(self.unix_socket)
  121. self.debug('connected DGRAM unix socket "{0}"'.format(self.unix_socket))
  122. return True
  123. except socket.error as error:
  124. self.debug('Failed to connect DGRAM unix socket "{socket}": {error}'.format(socket=self.unix_socket,
  125. error=error))
  126. try:
  127. self.debug('attempting STREAM unix socket "{0}"'.format(self.unix_socket))
  128. self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  129. self._sock.settimeout(self.connect_timeout)
  130. self.debug('set socket connect timeout to: {0}'.format(self._sock.gettimeout()))
  131. self._sock.connect(self.unix_socket)
  132. self.debug('connected STREAM unix socket "{0}"'.format(self.unix_socket))
  133. return True
  134. except socket.error as error:
  135. self.debug('Failed to connect STREAM unix socket "{socket}": {error}'.format(socket=self.unix_socket,
  136. error=error))
  137. self._sock = None
  138. return False
  139. def _connect(self):
  140. """
  141. Recreate socket and connect to it since sockets cannot be reused after closing
  142. Available configurations are IPv6, IPv4 or UNIX socket
  143. :return:
  144. """
  145. try:
  146. if self.unix_socket is not None:
  147. self._connect2unixsocket()
  148. else:
  149. if self.__socket_config is not None:
  150. self._connect2socket()
  151. else:
  152. if self.dgram_socket:
  153. sock_type = socket.SOCK_DGRAM
  154. else:
  155. sock_type = socket.SOCK_STREAM
  156. for res in socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, sock_type):
  157. if self._connect2socket(res):
  158. break
  159. except Exception as error:
  160. self.error('unhandled exception during connect : {0}'.format(repr(error)))
  161. self._sock = None
  162. self.__socket_config = None
  163. def _disconnect(self):
  164. """
  165. Close socket connection
  166. :return:
  167. """
  168. if self._sock is not None:
  169. try:
  170. self.debug('closing socket')
  171. self._sock.shutdown(2) # 0 - read, 1 - write, 2 - all
  172. self._sock.close()
  173. except Exception as error:
  174. if not (hasattr(error, 'errno') and error.errno == errno.ENOTCONN):
  175. self.error(error)
  176. self._sock = None
  177. def _send(self, request=None):
  178. """
  179. Send request.
  180. :return: boolean
  181. """
  182. # Send request if it is needed
  183. if self.request != self.__empty_request:
  184. try:
  185. self.debug('set socket write timeout to: {0}'.format(self._sock.gettimeout()))
  186. self._sock.settimeout(self.write_timeout)
  187. self.debug('sending request: {0}'.format(request or self.request))
  188. self._sock.send(request or self.request)
  189. except Exception as error:
  190. self._socket_error('error sending request: {0}'.format(error))
  191. self._disconnect()
  192. return False
  193. return True
  194. def _receive(self, raw=False):
  195. """
  196. Receive data from socket
  197. :param raw: set `True` to return bytes
  198. :type raw: bool
  199. :return: decoded str or raw bytes
  200. :rtype: str/bytes
  201. """
  202. data = "" if not raw else b""
  203. while True:
  204. self.debug('receiving response')
  205. try:
  206. self.debug('set socket read timeout to: {0}'.format(self._sock.gettimeout()))
  207. self._sock.settimeout(self.read_timeout)
  208. buf = self._sock.recv(4096)
  209. except Exception as error:
  210. self._socket_error('failed to receive response: {0}'.format(error))
  211. self._disconnect()
  212. break
  213. if buf is None or len(buf) == 0: # handle server disconnect
  214. if data == "" or data == b"":
  215. self._socket_error('unexpectedly disconnected')
  216. else:
  217. self.debug('server closed the connection')
  218. self._disconnect()
  219. break
  220. self.debug('received data')
  221. data += buf.decode('utf-8', 'ignore') if not raw else buf
  222. if self._check_raw_data(data):
  223. break
  224. self.debug(u'final response: {0}'.format(data if not raw else u'binary data'))
  225. return data
  226. def _get_raw_data(self, raw=False, request=None):
  227. """
  228. Get raw data with low-level "socket" module.
  229. :param raw: set `True` to return bytes
  230. :type raw: bool
  231. :return: decoded data (str) or raw data (bytes)
  232. :rtype: str/bytes
  233. """
  234. if self._sock is None:
  235. self._connect()
  236. if self._sock is None:
  237. return None
  238. # Send request if it is needed
  239. if not self._send(request):
  240. return None
  241. data = self._receive(raw)
  242. if not self._keep_alive:
  243. self._disconnect()
  244. return data
  245. @staticmethod
  246. def _check_raw_data(data):
  247. """
  248. Check if all data has been gathered from socket
  249. :param data: str
  250. :return: boolean
  251. """
  252. return bool(data)
  253. def _parse_config(self):
  254. """
  255. Parse configuration data
  256. :return: boolean
  257. """
  258. try:
  259. self.unix_socket = str(self.configuration['socket'])
  260. except (KeyError, TypeError):
  261. self.debug('No unix socket specified. Trying TCP/IP socket.')
  262. self.unix_socket = None
  263. try:
  264. self.host = str(self.configuration['host'])
  265. except (KeyError, TypeError):
  266. self.debug('No host specified. Using: "{0}"'.format(self.host))
  267. try:
  268. self.port = int(self.configuration['port'])
  269. except (KeyError, TypeError):
  270. self.debug('No port specified. Using: "{0}"'.format(self.port))
  271. self.tls = bool(self.configuration.get('tls', self.tls))
  272. if self.tls and not _TLS_SUPPORT:
  273. self.warning('TLS requested but no TLS module found, disabling TLS support.')
  274. self.tls = False
  275. if _TLS_SUPPORT and not self.tls:
  276. self.debug('No TLS preference specified, not using TLS.')
  277. if self.tls and _TLS_SUPPORT:
  278. self.key = self.configuration.get('tls_key_file')
  279. self.cert = self.configuration.get('tls_cert_file')
  280. if not self.cert:
  281. # If there's not a valid certificate, clear the key too.
  282. self.debug('No valid TLS client certificate configuration found.')
  283. self.key = None
  284. self.cert = None
  285. elif not self.key:
  286. # If a key isn't listed, the config may still be
  287. # valid, because there may be a key attached to the
  288. # certificate.
  289. self.info('No TLS client key specified, assuming it\'s attached to the certificate.')
  290. self.key = None
  291. try:
  292. self.request = str(self.configuration['request'])
  293. except (KeyError, TypeError):
  294. self.debug('No request specified. Using: "{0}"'.format(self.request))
  295. self.request = self.request.encode()
  296. def check(self):
  297. self._parse_config()
  298. return SimpleService.check(self)