Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

udp.py 18KB

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