udp.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. UDP support for IOCP reactor
  5. """
  6. import errno
  7. import socket
  8. import struct
  9. import warnings
  10. from typing import Optional
  11. from zope.interface import implementer
  12. from twisted.internet import address, defer, error, interfaces
  13. from twisted.internet.abstract import isIPAddress, isIPv6Address
  14. from twisted.internet.iocpreactor import abstract, iocpsupport as _iocp
  15. from twisted.internet.iocpreactor.const import (
  16. ERROR_CONNECTION_REFUSED,
  17. ERROR_IO_PENDING,
  18. ERROR_PORT_UNREACHABLE,
  19. )
  20. from twisted.internet.iocpreactor.interfaces import IReadWriteHandle
  21. from twisted.python import failure, log
  22. @implementer(
  23. IReadWriteHandle,
  24. interfaces.IListeningPort,
  25. interfaces.IUDPTransport,
  26. interfaces.ISystemHandle,
  27. )
  28. class Port(abstract.FileHandle):
  29. """
  30. UDP port, listening for packets.
  31. @ivar addressFamily: L{socket.AF_INET} or L{socket.AF_INET6}, depending on
  32. whether this port is listening on an IPv4 address or an IPv6 address.
  33. """
  34. addressFamily = socket.AF_INET
  35. socketType = socket.SOCK_DGRAM
  36. dynamicReadBuffers = False
  37. # Actual port number being listened on, only set to a non-None
  38. # value when we are actually listening.
  39. _realPortNumber: Optional[int] = None
  40. def __init__(self, port, proto, interface="", maxPacketSize=8192, reactor=None):
  41. """
  42. Initialize with a numeric port to listen on.
  43. """
  44. self.port = port
  45. self.protocol = proto
  46. self.readBufferSize = maxPacketSize
  47. self.interface = interface
  48. self.setLogStr()
  49. self._connectedAddr = None
  50. self._setAddressFamily()
  51. abstract.FileHandle.__init__(self, reactor)
  52. skt = socket.socket(self.addressFamily, self.socketType)
  53. addrLen = _iocp.maxAddrLen(skt.fileno())
  54. self.addressBuffer = bytearray(addrLen)
  55. # WSARecvFrom takes an int
  56. self.addressLengthBuffer = bytearray(struct.calcsize("i"))
  57. def _setAddressFamily(self):
  58. """
  59. Resolve address family for the socket.
  60. """
  61. if isIPv6Address(self.interface):
  62. self.addressFamily = socket.AF_INET6
  63. elif isIPAddress(self.interface):
  64. self.addressFamily = socket.AF_INET
  65. elif self.interface:
  66. raise error.InvalidAddressError(
  67. self.interface, "not an IPv4 or IPv6 address"
  68. )
  69. def __repr__(self) -> str:
  70. if self._realPortNumber is not None:
  71. return f"<{self.protocol.__class__} on {self._realPortNumber}>"
  72. else:
  73. return f"<{self.protocol.__class__} not connected>"
  74. def getHandle(self):
  75. """
  76. Return a socket object.
  77. """
  78. return self.socket
  79. def startListening(self):
  80. """
  81. Create and bind my socket, and begin listening on it.
  82. This is called on unserialization, and must be called after creating a
  83. server to begin listening on the specified port.
  84. """
  85. self._bindSocket()
  86. self._connectToProtocol()
  87. def createSocket(self):
  88. return self.reactor.createSocket(self.addressFamily, self.socketType)
  89. def _bindSocket(self):
  90. try:
  91. skt = self.createSocket()
  92. skt.bind((self.interface, self.port))
  93. except OSError as le:
  94. raise error.CannotListenError(self.interface, self.port, le)
  95. # Make sure that if we listened on port 0, we update that to
  96. # reflect what the OS actually assigned us.
  97. self._realPortNumber = skt.getsockname()[1]
  98. log.msg(
  99. "%s starting on %s"
  100. % (self._getLogPrefix(self.protocol), self._realPortNumber)
  101. )
  102. self.connected = True
  103. self.socket = skt
  104. self.getFileHandle = self.socket.fileno
  105. def _connectToProtocol(self):
  106. self.protocol.makeConnection(self)
  107. self.startReading()
  108. self.reactor.addActiveHandle(self)
  109. def cbRead(self, rc, data, evt):
  110. if self.reading:
  111. self.handleRead(rc, data, evt)
  112. self.doRead()
  113. def handleRead(self, rc, data, evt):
  114. if rc in (
  115. errno.WSAECONNREFUSED,
  116. errno.WSAECONNRESET,
  117. ERROR_CONNECTION_REFUSED,
  118. ERROR_PORT_UNREACHABLE,
  119. ):
  120. if self._connectedAddr:
  121. self.protocol.connectionRefused()
  122. elif rc:
  123. log.msg(
  124. "error in recvfrom -- %s (%s)"
  125. % (errno.errorcode.get(rc, "unknown error"), rc)
  126. )
  127. else:
  128. try:
  129. self.protocol.datagramReceived(
  130. bytes(evt.buff[:data]), _iocp.makesockaddr(evt.addr_buff)
  131. )
  132. except BaseException:
  133. log.err()
  134. def doRead(self):
  135. evt = _iocp.Event(self.cbRead, self)
  136. evt.buff = buff = self._readBuffers[0]
  137. evt.addr_buff = addr_buff = self.addressBuffer
  138. evt.addr_len_buff = addr_len_buff = self.addressLengthBuffer
  139. rc, data = _iocp.recvfrom(
  140. self.getFileHandle(), buff, addr_buff, addr_len_buff, evt
  141. )
  142. if rc and rc != ERROR_IO_PENDING:
  143. # If the error was not 0 or IO_PENDING then that means recvfrom() hit a
  144. # failure condition. In this situation recvfrom() gives us our response
  145. # right away and we don't need to wait for Windows to call the callback
  146. # on our event. In fact, windows will not call it for us so we must call it
  147. # ourselves manually
  148. self.reactor.callLater(0, self.cbRead, rc, data, evt)
  149. def write(self, datagram, addr=None):
  150. """
  151. Write a datagram.
  152. @param addr: should be a tuple (ip, port), can be None in connected
  153. mode.
  154. """
  155. if self._connectedAddr:
  156. assert addr in (None, self._connectedAddr)
  157. try:
  158. return self.socket.send(datagram)
  159. except OSError as se:
  160. no = se.args[0]
  161. if no == errno.WSAEINTR:
  162. return self.write(datagram)
  163. elif no == errno.WSAEMSGSIZE:
  164. raise error.MessageLengthError("message too long")
  165. elif no in (
  166. errno.WSAECONNREFUSED,
  167. errno.WSAECONNRESET,
  168. ERROR_CONNECTION_REFUSED,
  169. ERROR_PORT_UNREACHABLE,
  170. ):
  171. self.protocol.connectionRefused()
  172. else:
  173. raise
  174. else:
  175. assert addr != None
  176. if (
  177. not isIPAddress(addr[0])
  178. and not isIPv6Address(addr[0])
  179. and addr[0] != "<broadcast>"
  180. ):
  181. raise error.InvalidAddressError(
  182. addr[0], "write() only accepts IP addresses, not hostnames"
  183. )
  184. if isIPAddress(addr[0]) and self.addressFamily == socket.AF_INET6:
  185. raise error.InvalidAddressError(
  186. addr[0], "IPv6 port write() called with IPv4 address"
  187. )
  188. if isIPv6Address(addr[0]) and self.addressFamily == socket.AF_INET:
  189. raise error.InvalidAddressError(
  190. addr[0], "IPv4 port write() called with IPv6 address"
  191. )
  192. try:
  193. return self.socket.sendto(datagram, addr)
  194. except OSError as se:
  195. no = se.args[0]
  196. if no == errno.WSAEINTR:
  197. return self.write(datagram, addr)
  198. elif no == errno.WSAEMSGSIZE:
  199. raise error.MessageLengthError("message too long")
  200. elif no in (
  201. errno.WSAECONNREFUSED,
  202. errno.WSAECONNRESET,
  203. ERROR_CONNECTION_REFUSED,
  204. ERROR_PORT_UNREACHABLE,
  205. ):
  206. # in non-connected UDP ECONNREFUSED is platform dependent,
  207. # I think and the info is not necessarily useful.
  208. # Nevertheless maybe we should call connectionRefused? XXX
  209. return
  210. else:
  211. raise
  212. def writeSequence(self, seq, addr):
  213. self.write(b"".join(seq), addr)
  214. def connect(self, host, port):
  215. """
  216. 'Connect' to remote server.
  217. """
  218. if self._connectedAddr:
  219. raise RuntimeError(
  220. "already connected, reconnecting is not currently supported "
  221. "(talk to itamar if you want this)"
  222. )
  223. if not isIPAddress(host) and not isIPv6Address(host):
  224. raise error.InvalidAddressError(host, "not an IPv4 or IPv6 address.")
  225. self._connectedAddr = (host, port)
  226. self.socket.connect((host, port))
  227. def _loseConnection(self):
  228. self.stopReading()
  229. self.reactor.removeActiveHandle(self)
  230. if self.connected: # actually means if we are *listening*
  231. self.reactor.callLater(0, self.connectionLost)
  232. def stopListening(self):
  233. if self.connected:
  234. result = self.d = defer.Deferred()
  235. else:
  236. result = None
  237. self._loseConnection()
  238. return result
  239. def loseConnection(self):
  240. warnings.warn(
  241. "Please use stopListening() to disconnect port",
  242. DeprecationWarning,
  243. stacklevel=2,
  244. )
  245. self.stopListening()
  246. def connectionLost(self, reason=None):
  247. """
  248. Cleans up my socket.
  249. """
  250. log.msg("(UDP Port %s Closed)" % self._realPortNumber)
  251. self._realPortNumber = None
  252. abstract.FileHandle.connectionLost(self, reason)
  253. self.protocol.doStop()
  254. self.socket.close()
  255. del self.socket
  256. del self.getFileHandle
  257. if hasattr(self, "d"):
  258. self.d.callback(None)
  259. del self.d
  260. def setLogStr(self):
  261. """
  262. Initialize the C{logstr} attribute to be used by C{logPrefix}.
  263. """
  264. logPrefix = self._getLogPrefix(self.protocol)
  265. self.logstr = "%s (UDP)" % logPrefix
  266. def logPrefix(self):
  267. """
  268. Returns the name of my class, to prefix log entries with.
  269. """
  270. return self.logstr
  271. def getHost(self):
  272. """
  273. Return the local address of the UDP connection
  274. @returns: the local address of the UDP connection
  275. @rtype: L{IPv4Address} or L{IPv6Address}
  276. """
  277. addr = self.socket.getsockname()
  278. if self.addressFamily == socket.AF_INET:
  279. return address.IPv4Address("UDP", *addr)
  280. elif self.addressFamily == socket.AF_INET6:
  281. return address.IPv6Address("UDP", *(addr[:2]))
  282. def setBroadcastAllowed(self, enabled):
  283. """
  284. Set whether this port may broadcast. This is disabled by default.
  285. @param enabled: Whether the port may broadcast.
  286. @type enabled: L{bool}
  287. """
  288. self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, enabled)
  289. def getBroadcastAllowed(self):
  290. """
  291. Checks if broadcast is currently allowed on this port.
  292. @return: Whether this port may broadcast.
  293. @rtype: L{bool}
  294. """
  295. return bool(self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST))
  296. class MulticastMixin:
  297. """
  298. Implement multicast functionality.
  299. """
  300. def getOutgoingInterface(self):
  301. i = self.socket.getsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF)
  302. return socket.inet_ntoa(struct.pack("@i", i))
  303. def setOutgoingInterface(self, addr):
  304. """
  305. Returns Deferred of success.
  306. """
  307. return self.reactor.resolve(addr).addCallback(self._setInterface)
  308. def _setInterface(self, addr):
  309. i = socket.inet_aton(addr)
  310. self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, i)
  311. return 1
  312. def getLoopbackMode(self):
  313. return self.socket.getsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP)
  314. def setLoopbackMode(self, mode):
  315. mode = struct.pack("b", bool(mode))
  316. self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, mode)
  317. def getTTL(self):
  318. return self.socket.getsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL)
  319. def setTTL(self, ttl):
  320. ttl = struct.pack("B", ttl)
  321. self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
  322. def joinGroup(self, addr, interface=""):
  323. """
  324. Join a multicast group. Returns Deferred of success.
  325. """
  326. return self.reactor.resolve(addr).addCallback(self._joinAddr1, interface, 1)
  327. def _joinAddr1(self, addr, interface, join):
  328. return self.reactor.resolve(interface).addCallback(self._joinAddr2, addr, join)
  329. def _joinAddr2(self, interface, addr, join):
  330. addr = socket.inet_aton(addr)
  331. interface = socket.inet_aton(interface)
  332. if join:
  333. cmd = socket.IP_ADD_MEMBERSHIP
  334. else:
  335. cmd = socket.IP_DROP_MEMBERSHIP
  336. try:
  337. self.socket.setsockopt(socket.IPPROTO_IP, cmd, addr + interface)
  338. except OSError as e:
  339. return failure.Failure(error.MulticastJoinError(addr, interface, *e.args))
  340. def leaveGroup(self, addr, interface=""):
  341. """
  342. Leave multicast group, return Deferred of success.
  343. """
  344. return self.reactor.resolve(addr).addCallback(self._joinAddr1, interface, 0)
  345. @implementer(interfaces.IMulticastTransport)
  346. class MulticastPort(MulticastMixin, Port):
  347. """
  348. UDP Port that supports multicasting.
  349. """
  350. def __init__(
  351. self,
  352. port,
  353. proto,
  354. interface="",
  355. maxPacketSize=8192,
  356. reactor=None,
  357. listenMultiple=False,
  358. ):
  359. Port.__init__(self, port, proto, interface, maxPacketSize, reactor)
  360. self.listenMultiple = listenMultiple
  361. def createSocket(self):
  362. skt = Port.createSocket(self)
  363. if self.listenMultiple:
  364. skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  365. if hasattr(socket, "SO_REUSEPORT"):
  366. skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  367. return skt