dns_tcp.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. # Copyright (C) 2012 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. """Utility for handling DNS transactions over TCP.
  16. This module defines a few convenient utility classes for handling DNS
  17. transactions via a TCP socket.
  18. """
  19. import isc.log
  20. from isc.server_common.logger import logger
  21. from isc.log_messages.server_common_messages import *
  22. from isc.ddns.logger import ClientFormatter
  23. import errno
  24. import socket
  25. import struct
  26. class DNSTCPSendBuffer:
  27. '''A composite buffer for a DNS message sent over TCP.
  28. This class encapsulates binary data supposed to be a complete DNS
  29. message, taking into account the 2-byte length field preceeding the
  30. actual data.
  31. An object of this class is constructed with a binary object for the
  32. DNS message data (in wire-format), conceptually "appended" to the
  33. 2-byte length field. The length is automatically calculated and
  34. converted to the wire-format data in the network byte order.
  35. Its get_data() method returns a binary object corresponding to the
  36. consecutive region of the conceptual buffer starting from the specified
  37. position. The returned region may not necessarily contain all remaining
  38. data from the specified position; this class can internally hold multiple
  39. separate binary objects to represent the conceptual buffer, and,
  40. in that case, get_data() identifies the object that contains the
  41. specified position of data, and returns the longest consecutive region
  42. from that position. So the caller must call get_data(), incrementing
  43. the position as it transmits the data, until it gets None.
  44. This class is primarily intended to be a private utility for the
  45. DNSTCPContext class, but can be used by other general applications
  46. that need to send DNS messages over TCP in their own way.
  47. '''
  48. def __init__(self, data):
  49. '''Consructor.
  50. Parameter:
  51. data (binary): A binary sequence that is supposed to be a
  52. complete DNS message in the wire format. It must not
  53. exceed 65535 bytes in length; otherwise ValueError will be
  54. raised. This class does not check any further validity on
  55. the data as a DNS message.
  56. '''
  57. self.__data_size = len(data)
  58. self.__len_size = 2 # fixed length
  59. if self.__data_size > 0xffff:
  60. raise ValueError('Too large data for DNS/TCP, size: ' +
  61. str(self.__data_size))
  62. self.__lenbuf = struct.pack('H', socket.htons(self.__data_size))
  63. self.__databuf = data
  64. def get_total_len(self):
  65. '''Return the total length of the buffer, including the length field.
  66. '''
  67. return self.__data_size + self.__len_size
  68. def get_data(self, pos):
  69. '''Return a portion of data from a specified position.
  70. Parameter:
  71. pos (int): The position in the TCP DNS message data (including
  72. the 2-byte length field) from which the data are to be returned.
  73. Return:
  74. A Python binary object that corresponds to a part of the TCP
  75. DNS message data starting at the specified position. It may
  76. or may not contain all remaining data from that position.
  77. If the given position is beyond the end of the enrire data,
  78. None will be returned.
  79. '''
  80. if pos >= self.__len_size:
  81. pos -= self.__len_size
  82. if pos >= self.__data_size:
  83. return None
  84. return self.__databuf[pos:]
  85. return self.__lenbuf[pos:]
  86. class DNSTCPContextError(Exception):
  87. '''An exception raised against logic errors in DNSTCPContext.
  88. This is raised only when the context class is used in an unexpected way,
  89. that is for a caller's bug.
  90. '''
  91. pass
  92. class DNSTCPContext:
  93. '''Context of a TCP connection used for DNS transactions.
  94. This class offers the following services:
  95. - Handle the initial 2-byte length field internally. The user of
  96. this class only has to deal with the bare DNS message (just like
  97. the one transmiited over UDP).
  98. - Asynchronous I/O. It supports the non blocking operation mode,
  99. where method calls never block. The caller is told whether it's
  100. ongoing and it should watch the socket or it's fully completed.
  101. - Socket error handling: it internally catches socket related exceptions
  102. and handle them in an appropriate way. A fatal error will be reported
  103. to the caller in the form of a normal return value. The application
  104. of this class can therefore assume it's basically exception free.
  105. Notes:
  106. - the initial implementation only supports non blocking mode, but
  107. it's intended to be extended so it can work in both blocking or
  108. non blocking mode as we see the need for it.
  109. - the initial implementation only supports send operations on an
  110. already connected socket, but the intent is to extend this class
  111. so it can handle receive or connect operations.
  112. '''
  113. # Result codes used in send()/send_ready() methods.
  114. SEND_DONE = 1
  115. SENDING = 2
  116. CLOSED = 3
  117. def __init__(self, sock):
  118. '''Constructor.
  119. Parameter:
  120. sock (Python socket): the socket to be used for the transaction.
  121. It must represent a TCP socket; otherwise DNSTCPContextError
  122. will be raised. It's also expected to be connected, but it's
  123. not checked on construction; a subsequent send operation would
  124. fail.
  125. '''
  126. if sock.proto != socket.IPPROTO_TCP:
  127. raise DNSTCPContextError('not a TCP socket, proto: ' +
  128. str(sock.proto))
  129. sock.setblocking(False)
  130. self.__sock = sock
  131. self.__send_buffer = None
  132. self.__remote_addr = sock.getpeername() # record it for logging
  133. def send(self, data):
  134. '''Send a DNS message.
  135. In the non blocking mode, it sends as much data as possible via
  136. the underlying TCP socket until it would block or all data are sent
  137. out, and returns the corresponding result code. This method
  138. therefore doesn't block in this mode.
  139. Note: the initial implementation only works in the non blocking
  140. mode.
  141. This method must not be called once an error is detected and
  142. CLOSED is returned or a prior send attempt is ongoing (with
  143. the result code of SENDING); otherwise DNSTCPContextError is
  144. raised.
  145. Parameter:
  146. data (binary): A binary sequence that is supposed to be a
  147. complete DNS message in the wire format. It must meet
  148. the assumption that DNSTCPSendBuffer requires.
  149. Return:
  150. An integer constant representing the result:
  151. - SEND_DONE All data have been sent out successfully.
  152. - SENDING All writable data has been sent out, and further
  153. attempt would block at the moment. The caller is expected
  154. to detect it when the underlying socket is writable again
  155. and call send_ready() to continue the send.
  156. - CLOSED A network error happened before the send operation is
  157. completed. The underlying socket has been closed, and this
  158. context object will be unusable.
  159. '''
  160. if self.__sock is None:
  161. raise DNSTCPContextError('send() called after close')
  162. if self.__send_buffer is not None:
  163. raise DNSTCPContextError('duplicate send()')
  164. self.__send_buffer = DNSTCPSendBuffer(data)
  165. self.__send_marker = 0
  166. return self.__do_send()
  167. def send_ready(self):
  168. '''Resume sending a DNS message.
  169. This method is expected to be called followed by a send() call or
  170. another send_ready() call that resulted in SENDING, when the caller
  171. detects the underlying socket becomes writable. It works as
  172. send() except that it continues the send operation from the suspended
  173. position of the data at the time of the previous call.
  174. This method must not be called once an error is detected and
  175. CLOSED is returned or a send() method hasn't been called to
  176. start the operation; otherwise DNSTCPContextError is raised.
  177. Return: see send().
  178. '''
  179. if self.__sock is None:
  180. raise DNSTCPContextError('send() called after close')
  181. if self.__send_buffer is None:
  182. raise DNSTCPContextError('send_ready() called before send')
  183. return self.__do_send()
  184. def __do_send(self):
  185. while True:
  186. data = self.__send_buffer.get_data(self.__send_marker)
  187. if data is None:
  188. # send complete; clear the internal buffer for next possible
  189. # send.
  190. logger.debug(logger.DBGLVL_TRACE_DETAIL,
  191. PYSERVER_COMMON_DNS_TCP_SEND_DONE,
  192. ClientFormatter(self.__remote_addr),
  193. self.__send_marker)
  194. self.__send_buffer = None
  195. self.__send_marker = 0
  196. return self.SEND_DONE
  197. try:
  198. cc = self.__sock.send(data)
  199. except socket.error as ex:
  200. total_len = self.__send_buffer.get_total_len()
  201. if ex.errno == errno.EAGAIN:
  202. logger.debug(logger.DBGLVL_TRACE_DETAIL,
  203. PYSERVER_COMMON_DNS_TCP_SEND_PENDING,
  204. ClientFormatter(self.__remote_addr),
  205. self.__send_marker, total_len)
  206. return self.SENDING
  207. logger.warn(PYSERVER_COMMON_DNS_TCP_SEND_FAILED,
  208. ClientFormatter(self.__remote_addr),
  209. self.__send_marker, total_len, ex)
  210. self.__sock.close()
  211. self.__sock = None
  212. return self.CLOSED
  213. self.__send_marker += cc
  214. def close(self):
  215. '''Close the socket.
  216. This method closes the underlying socket. Once called, the context
  217. object is effectively useless; any further method call would result
  218. in a DNSTCPContextError exception.
  219. The underlying socket will be automatically (and implicitly) closed
  220. when this object is deallocated, but Python seems to expect socket
  221. objects should be explicitly closed before deallocation. So it's
  222. generally advisable for the user of this object to call this method
  223. explictily when it doesn't need the context.
  224. This method can be called more than once or can be called after
  225. other I/O related methods have returned CLOSED; it's compatible
  226. with the close() method of the Python socket class.
  227. '''
  228. if self.__sock is None:
  229. return
  230. self.__sock.close()
  231. self.__sock = None # prevent furhter operation