PageRenderTime 37ms CodeModel.GetById 2ms app.highlight 29ms RepoModel.GetById 1ms app.codeStats 0ms

/twisted/internet/udp.py

https://github.com/adaschevici/twisted
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