udp.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. # -*- test-case-name: twisted.test.test_udp -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Various asynchronous UDP classes.
  6. Please do not use this module directly.
  7. @var _sockErrReadIgnore: list of symbolic error constants (from the C{errno}
  8. module) representing socket errors where the error is temporary and can be
  9. ignored.
  10. @var _sockErrReadRefuse: list of symbolic error constants (from the C{errno}
  11. module) representing socket errors that indicate connection refused.
  12. """
  13. from __future__ import annotations
  14. # System Imports
  15. import socket
  16. import warnings
  17. from typing import Optional
  18. from zope.interface import implementer
  19. from twisted.internet._multicast import MulticastMixin
  20. from twisted.internet.interfaces import IReactorMulticast
  21. from twisted.internet.protocol import AbstractDatagramProtocol
  22. from twisted.python.runtime import platformType
  23. if platformType == "win32":
  24. from errno import WSAEINPROGRESS # type: ignore[attr-defined]
  25. from errno import WSAEWOULDBLOCK # type: ignore[attr-defined]
  26. from errno import ( # type: ignore[attr-defined]
  27. WSAECONNREFUSED,
  28. WSAECONNRESET,
  29. WSAEINTR,
  30. WSAEMSGSIZE,
  31. WSAENETRESET,
  32. WSAENOPROTOOPT as ENOPROTOOPT,
  33. WSAETIMEDOUT,
  34. )
  35. # Classify read and write errors
  36. _sockErrReadIgnore = [WSAEINTR, WSAEWOULDBLOCK, WSAEMSGSIZE, WSAEINPROGRESS]
  37. _sockErrReadRefuse = [WSAECONNREFUSED, WSAECONNRESET, WSAENETRESET, WSAETIMEDOUT]
  38. # POSIX-compatible write errors
  39. EMSGSIZE = WSAEMSGSIZE
  40. ECONNREFUSED = WSAECONNREFUSED
  41. EAGAIN = WSAEWOULDBLOCK
  42. EINTR = WSAEINTR
  43. else:
  44. from errno import EAGAIN, ECONNREFUSED, EINTR, EMSGSIZE, ENOPROTOOPT, EWOULDBLOCK
  45. _sockErrReadIgnore = [EAGAIN, EINTR, EWOULDBLOCK]
  46. _sockErrReadRefuse = [ECONNREFUSED]
  47. # Twisted Imports
  48. from twisted.internet import abstract, address, base, defer, error, interfaces
  49. from twisted.python import log
  50. @implementer(
  51. interfaces.IListeningPort, interfaces.IUDPTransport, interfaces.ISystemHandle
  52. )
  53. class Port(base.BasePort):
  54. """
  55. UDP port, listening for packets.
  56. @ivar maxThroughput: Maximum number of bytes read in one event
  57. loop iteration.
  58. @ivar addressFamily: L{socket.AF_INET} or L{socket.AF_INET6}, depending on
  59. whether this port is listening on an IPv4 address or an IPv6 address.
  60. @ivar _realPortNumber: Actual port number being listened on. The
  61. value will be L{None} until this L{Port} is listening.
  62. @ivar _preexistingSocket: If not L{None}, a L{socket.socket} instance which
  63. was created and initialized outside of the reactor and will be used to
  64. listen for connections (instead of a new socket being created by this
  65. L{Port}).
  66. """
  67. addressFamily: socket.AddressFamily = socket.AF_INET
  68. socketType: socket.SocketKind = socket.SOCK_DGRAM
  69. maxThroughput = 256 * 1024
  70. _realPortNumber: Optional[int] = None
  71. _preexistingSocket = None
  72. def __init__(self, port, proto, interface="", maxPacketSize=8192, reactor=None):
  73. """
  74. @param port: A port number on which to listen.
  75. @type port: L{int}
  76. @param proto: A C{DatagramProtocol} instance which will be
  77. connected to the given C{port}.
  78. @type proto: L{twisted.internet.protocol.DatagramProtocol}
  79. @param interface: The local IPv4 or IPv6 address to which to bind;
  80. defaults to '', ie all IPv4 addresses.
  81. @type interface: L{str}
  82. @param maxPacketSize: The maximum packet size to accept.
  83. @type maxPacketSize: L{int}
  84. @param reactor: A reactor which will notify this C{Port} when
  85. its socket is ready for reading or writing. Defaults to
  86. L{None}, ie the default global reactor.
  87. @type reactor: L{interfaces.IReactorFDSet}
  88. """
  89. base.BasePort.__init__(self, reactor)
  90. self.port = port
  91. self.protocol = proto
  92. self.maxPacketSize = maxPacketSize
  93. self.interface = interface
  94. self.setLogStr()
  95. self._connectedAddr = None
  96. self._setAddressFamily()
  97. @classmethod
  98. def _fromListeningDescriptor(
  99. cls, reactor, fd, addressFamily, protocol, maxPacketSize
  100. ):
  101. """
  102. Create a new L{Port} based on an existing listening
  103. I{SOCK_DGRAM} socket.
  104. @param reactor: A reactor which will notify this L{Port} when
  105. its socket is ready for reading or writing. Defaults to
  106. L{None}, ie the default global reactor.
  107. @type reactor: L{interfaces.IReactorFDSet}
  108. @param fd: An integer file descriptor associated with a listening
  109. socket. The socket must be in non-blocking mode. Any additional
  110. attributes desired, such as I{FD_CLOEXEC}, must also be set already.
  111. @type fd: L{int}
  112. @param addressFamily: The address family (sometimes called I{domain}) of
  113. the existing socket. For example, L{socket.AF_INET}.
  114. @type addressFamily: L{int}
  115. @param protocol: A C{DatagramProtocol} instance which will be
  116. connected to the C{port}.
  117. @type protocol: L{twisted.internet.protocol.DatagramProtocol}
  118. @param maxPacketSize: The maximum packet size to accept.
  119. @type maxPacketSize: L{int}
  120. @return: A new instance of C{cls} wrapping the socket given by C{fd}.
  121. @rtype: L{Port}
  122. """
  123. port = socket.fromfd(fd, addressFamily, cls.socketType)
  124. interface = port.getsockname()[0]
  125. self = cls(
  126. None,
  127. protocol,
  128. interface=interface,
  129. reactor=reactor,
  130. maxPacketSize=maxPacketSize,
  131. )
  132. self._preexistingSocket = port
  133. return self
  134. def __repr__(self) -> str:
  135. if self._realPortNumber is not None:
  136. return f"<{self.protocol.__class__} on {self._realPortNumber}>"
  137. else:
  138. return f"<{self.protocol.__class__} not connected>"
  139. def getHandle(self):
  140. """
  141. Return a socket object.
  142. """
  143. return self.socket
  144. def startListening(self):
  145. """
  146. Create and bind my socket, and begin listening on it.
  147. This is called on unserialization, and must be called after creating a
  148. server to begin listening on the specified port.
  149. """
  150. self._bindSocket()
  151. self._connectToProtocol()
  152. def _bindSocket(self):
  153. """
  154. Prepare and assign a L{socket.socket} instance to
  155. C{self.socket}.
  156. Either creates a new SOCK_DGRAM L{socket.socket} bound to
  157. C{self.interface} and C{self.port} or takes an existing
  158. L{socket.socket} provided via the
  159. L{interfaces.IReactorSocket.adoptDatagramPort} interface.
  160. """
  161. if self._preexistingSocket is None:
  162. # Create a new socket and make it listen
  163. try:
  164. skt = self.createInternetSocket()
  165. skt.bind((self.interface, self.port))
  166. except OSError as le:
  167. raise error.CannotListenError(self.interface, self.port, le)
  168. else:
  169. # Re-use the externally specified socket
  170. skt = self._preexistingSocket
  171. self._preexistingSocket = None
  172. # Make sure that if we listened on port 0, we update that to
  173. # reflect what the OS actually assigned us.
  174. self._realPortNumber = skt.getsockname()[1]
  175. log.msg(
  176. "%s starting on %s"
  177. % (self._getLogPrefix(self.protocol), self._realPortNumber)
  178. )
  179. self.connected = 1
  180. self.socket = skt
  181. self.fileno = self.socket.fileno
  182. def _connectToProtocol(self):
  183. self.protocol.makeConnection(self)
  184. self.startReading()
  185. def doRead(self):
  186. """
  187. Called when my socket is ready for reading.
  188. """
  189. read = 0
  190. while read < self.maxThroughput:
  191. try:
  192. data, addr = self.socket.recvfrom(self.maxPacketSize)
  193. except OSError as se:
  194. no = se.args[0]
  195. if no in _sockErrReadIgnore:
  196. return
  197. if no in _sockErrReadRefuse:
  198. if self._connectedAddr:
  199. self.protocol.connectionRefused()
  200. return
  201. raise
  202. else:
  203. read += len(data)
  204. if self.addressFamily == socket.AF_INET6:
  205. # Remove the flow and scope ID from the address tuple,
  206. # reducing it to a tuple of just (host, port).
  207. #
  208. # TODO: This should be amended to return an object that can
  209. # unpack to (host, port) but also includes the flow info
  210. # and scope ID. See http://tm.tl/6826
  211. addr = addr[:2]
  212. try:
  213. self.protocol.datagramReceived(data, addr)
  214. except BaseException:
  215. log.err()
  216. def write(self, datagram, addr=None):
  217. """
  218. Write a datagram.
  219. @type datagram: L{bytes}
  220. @param datagram: The datagram to be sent.
  221. @type addr: L{tuple} containing L{str} as first element and L{int} as
  222. second element, or L{None}
  223. @param addr: A tuple of (I{stringified IPv4 or IPv6 address},
  224. I{integer port number}); can be L{None} in connected mode.
  225. """
  226. if self._connectedAddr:
  227. assert addr in (None, self._connectedAddr)
  228. try:
  229. return self.socket.send(datagram)
  230. except OSError as se:
  231. no = se.args[0]
  232. if no == EINTR:
  233. return self.write(datagram)
  234. elif no == EMSGSIZE:
  235. raise error.MessageLengthError("message too long")
  236. elif no == ECONNREFUSED:
  237. self.protocol.connectionRefused()
  238. else:
  239. raise
  240. else:
  241. assert addr != None
  242. if (
  243. not abstract.isIPAddress(addr[0])
  244. and not abstract.isIPv6Address(addr[0])
  245. and addr[0] != "<broadcast>"
  246. ):
  247. raise error.InvalidAddressError(
  248. addr[0], "write() only accepts IP addresses, not hostnames"
  249. )
  250. if (
  251. abstract.isIPAddress(addr[0]) or addr[0] == "<broadcast>"
  252. ) and self.addressFamily == socket.AF_INET6:
  253. raise error.InvalidAddressError(
  254. addr[0], "IPv6 port write() called with IPv4 or broadcast address"
  255. )
  256. if abstract.isIPv6Address(addr[0]) and self.addressFamily == socket.AF_INET:
  257. raise error.InvalidAddressError(
  258. addr[0], "IPv4 port write() called with IPv6 address"
  259. )
  260. try:
  261. return self.socket.sendto(datagram, addr)
  262. except OSError as se:
  263. no = se.args[0]
  264. if no == EINTR:
  265. return self.write(datagram, addr)
  266. elif no == EMSGSIZE:
  267. raise error.MessageLengthError("message too long")
  268. elif no == ECONNREFUSED:
  269. # in non-connected UDP ECONNREFUSED is platform dependent, I
  270. # think and the info is not necessarily useful. Nevertheless
  271. # maybe we should call connectionRefused? XXX
  272. return
  273. else:
  274. raise
  275. def writeSequence(self, seq, addr):
  276. """
  277. Write a datagram constructed from an iterable of L{bytes}.
  278. @param seq: The data that will make up the complete datagram to be
  279. written.
  280. @type seq: an iterable of L{bytes}
  281. @type addr: L{tuple} containing L{str} as first element and L{int} as
  282. second element, or L{None}
  283. @param addr: A tuple of (I{stringified IPv4 or IPv6 address},
  284. I{integer port number}); can be L{None} in connected mode.
  285. """
  286. self.write(b"".join(seq), addr)
  287. def connect(self, host, port):
  288. """
  289. 'Connect' to remote server.
  290. """
  291. if self._connectedAddr:
  292. raise RuntimeError(
  293. "already connected, reconnecting is not currently supported"
  294. )
  295. if not abstract.isIPAddress(host) and not abstract.isIPv6Address(host):
  296. raise error.InvalidAddressError(host, "not an IPv4 or IPv6 address.")
  297. self._connectedAddr = (host, port)
  298. self.socket.connect((host, port))
  299. def _loseConnection(self):
  300. self.stopReading()
  301. if self.connected: # actually means if we are *listening*
  302. self.reactor.callLater(0, self.connectionLost)
  303. def stopListening(self):
  304. if self.connected:
  305. result = self.d = defer.Deferred()
  306. else:
  307. result = None
  308. self._loseConnection()
  309. return result
  310. def loseConnection(self):
  311. warnings.warn(
  312. "Please use stopListening() to disconnect port",
  313. DeprecationWarning,
  314. stacklevel=2,
  315. )
  316. self.stopListening()
  317. def connectionLost(self, reason=None):
  318. """
  319. Cleans up my socket.
  320. """
  321. log.msg("(UDP Port %s Closed)" % self._realPortNumber)
  322. self._realPortNumber = None
  323. self.maxThroughput = -1
  324. base.BasePort.connectionLost(self, reason)
  325. self.protocol.doStop()
  326. self.socket.close()
  327. del self.socket
  328. del self.fileno
  329. if hasattr(self, "d"):
  330. self.d.callback(None)
  331. del self.d
  332. def setLogStr(self):
  333. """
  334. Initialize the C{logstr} attribute to be used by C{logPrefix}.
  335. """
  336. logPrefix = self._getLogPrefix(self.protocol)
  337. self.logstr = "%s (UDP)" % logPrefix
  338. def _setAddressFamily(self):
  339. """
  340. Resolve address family for the socket.
  341. """
  342. if abstract.isIPv6Address(self.interface):
  343. self.addressFamily = socket.AF_INET6
  344. elif abstract.isIPAddress(self.interface):
  345. self.addressFamily = socket.AF_INET
  346. elif self.interface:
  347. raise error.InvalidAddressError(
  348. self.interface, "not an IPv4 or IPv6 address."
  349. )
  350. def logPrefix(self):
  351. """
  352. Return the prefix to log with.
  353. """
  354. return self.logstr
  355. def getHost(self):
  356. """
  357. Return the local address of the UDP connection
  358. @returns: the local address of the UDP connection
  359. @rtype: L{IPv4Address} or L{IPv6Address}
  360. """
  361. addr = self.socket.getsockname()
  362. if self.addressFamily == socket.AF_INET:
  363. return address.IPv4Address("UDP", *addr)
  364. elif self.addressFamily == socket.AF_INET6:
  365. return address.IPv6Address("UDP", *(addr[:2]))
  366. def setBroadcastAllowed(self, enabled):
  367. """
  368. Set whether this port may broadcast. This is disabled by default.
  369. @param enabled: Whether the port may broadcast.
  370. @type enabled: L{bool}
  371. """
  372. self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, enabled)
  373. def getBroadcastAllowed(self):
  374. """
  375. Checks if broadcast is currently allowed on this port.
  376. @return: Whether this port may broadcast.
  377. @rtype: L{bool}
  378. """
  379. return bool(self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST))
  380. @implementer(interfaces.IMulticastTransport)
  381. class MulticastPort(MulticastMixin, Port):
  382. """
  383. UDP Port that supports multicasting.
  384. """
  385. def __init__(
  386. self,
  387. port: int,
  388. proto: AbstractDatagramProtocol,
  389. interface: str = "",
  390. maxPacketSize: int = 8192,
  391. reactor: IReactorMulticast | None = None,
  392. listenMultiple: bool = False,
  393. ) -> None:
  394. """
  395. @see: L{twisted.internet.interfaces.IReactorMulticast.listenMulticast}
  396. """
  397. Port.__init__(self, port, proto, interface, maxPacketSize, reactor)
  398. self.listenMultiple = listenMultiple
  399. def createInternetSocket(self) -> socket.socket:
  400. """
  401. Override L{Port.createInternetSocket} to configure the socket to honor
  402. the C{listenMultiple} argument to L{IReactorMulticast.listenMultiple}.
  403. """
  404. skt = Port.createInternetSocket(self)
  405. if self.listenMultiple:
  406. skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  407. if hasattr(socket, "SO_REUSEPORT"):
  408. try:
  409. skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  410. except OSError as le:
  411. # RHEL6 defines SO_REUSEPORT but it doesn't work
  412. if le.errno == ENOPROTOOPT:
  413. pass
  414. else:
  415. raise
  416. return skt