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