Ticket #5086: twisted_udpv6_value_error.patch

File twisted_udpv6_value_error.patch, 21.7 KB (added by satis, 3 years ago)

Variation of last patch where InvalidAddress now inherits from ValueError

  • new file doc/core/howto/listings/udp/ipv6_listen.py

    diff --git doc/core/howto/listings/udp/ipv6_listen.py doc/core/howto/listings/udp/ipv6_listen.py
    new file mode 100644
    index 0000000..cac6b85
    - +  
     1from twisted.internet.protocol import DatagramProtocol
     2from twisted.internet import reactor
     3
     4
     5
     6class Echo(DatagramProtocol):
     7    def datagramReceived(self, data, addr):
     8        print "received %r from %s" % (data, addr)
     9        self.transport.write(data, addr)
     10
     11
     12
     13reactor.listenUDP(8006, Echo(), interface='::')
     14reactor.run()
  • doc/core/howto/udp.xhtml

    diff --git doc/core/howto/udp.xhtml doc/core/howto/udp.xhtml
    index 6fb5141..62c236c 100644
    reactor.run() 
    207207    base="twisted.internet.interfaces">IMulticastTransport</code> for more
    208208    information.</p>
    209209
     210    <h2>IPv6</h2>
     211    <p>
     212      UDP sockets can also bind to IPv6 addresses to support sending and receiving
     213      datagrams over IPv6. By passing an IPv6 address to <code class="API"
     214      base="twisted.internet.interfaces.IReactorUDP">listenUDP</code>&apos;s
     215      <code>interface</code> argument, the reactor will start an IPv6 socket that
     216      can be used to send and receive UDP datagrams.
     217    </p>
     218
     219    <a href="listings/udp/ipv6_listen.py" class="py-listing">ipv6_listen.py</a>
     220
    210221</body>
    211222</html>
  • twisted/internet/error.py

    diff --git twisted/internet/error.py twisted/internet/error.py
    index 4e9fb5f..7de2dfb 100644
    class AlreadyListened(Exception): 
    458458    listened on once.
    459459    """
    460460
     461class InvalidAddressError(ValueError):
     462    """
     463    An invalid address was specified (e.g. neither IPv4 or IPv6)
     464
     465    @ivar address: the address that was provided
     466    @ivar message: Additional information provided by the calling context
     467    """
     468    def __init__(self, address, message):
     469        self.address = address
     470        self.message = message
     471
     472    def __str__(self):
     473        return "Invalid address %s: %s" %(self.address, self.message)
    461474
    462475__all__ = [
    463476    'BindError', 'CannotListenError', 'MulticastJoinError',
    __all__ = [ 
    472485    'ProcessTerminated', 'ProcessExitedAlready', 'NotConnectingError',
    473486    'NotListeningError', 'ReactorNotRunning', 'ReactorAlreadyRunning',
    474487    'ReactorAlreadyInstalledError', 'ConnectingCancelledError',
    475     'UnsupportedAddressFamily', 'UnsupportedSocketType']
     488    'UnsupportedAddressFamily', 'UnsupportedSocketType', 'InvalidAddressError']
  • twisted/internet/interfaces.py

    diff --git twisted/internet/interfaces.py twisted/internet/interfaces.py
    index 4021e57..da52c6d 100644
    class IUDPTransport(Interface): 
    22822282
    22832283    def getHost():
    22842284        """
    2285         Returns L{IPv4Address}.
     2285        Returns an L{IPv4Address} or L{IPv6Address}.
    22862286        """
    22872287
    22882288    def stopListening():
  • twisted/internet/iocpreactor/udp.py

    diff --git twisted/internet/iocpreactor/udp.py twisted/internet/iocpreactor/udp.py
    index 4dec51f..69fb6df 100644
    import socket, operator, struct, warnings, errno 
    1010from zope.interface import implements
    1111
    1212from twisted.internet import defer, address, error, interfaces
    13 from twisted.internet.abstract import isIPAddress
     13from twisted.internet.abstract import isIPAddress, isIPv6Address
    1414from twisted.python import log, failure
    1515
    1616from twisted.internet.iocpreactor.const import ERROR_IO_PENDING
    class Port(abstract.FileHandle): 
    4949        self.interface = interface
    5050        self.setLogStr()
    5151        self._connectedAddr = None
     52        self._setAddressFamily()
    5253
    5354        abstract.FileHandle.__init__(self, reactor)
    5455
    class Port(abstract.FileHandle): 
    6061                struct.calcsize('i'))
    6162
    6263
     64    def _setAddressFamily(self):
     65        """
     66        Resolve address family for the socket.
     67        """
     68        if isIPv6Address(self.interface):
     69            self.addressFamily = socket.AF_INET6
     70        elif isIPAddress(self.interface):
     71            self.addressFamily = socket.AF_INET
     72        elif self.interface:
     73            raise error.InvalidAddressError(self.interface, 'not an IPv4 or IPv6 address.')
     74
     75
    6376    def __repr__(self):
    6477        if self._realPortNumber is not None:
    6578            return ("<%s on %s>" %
    class Port(abstract.FileHandle): 
    95108            skt = self.createSocket()
    96109            skt.bind((self.interface, self.port))
    97110        except socket.error, le:
    98             raise error.CannotListenError, (self.interface, self.port, le)
     111            raise error.CannotListenError(self.interface, self.port, le)
    99112
    100113        # Make sure that if we listened on port 0, we update that to
    101114        # reflect what the OS actually assigned us.
    class Port(abstract.FileHandle): 
    166179                if no == errno.WSAEINTR:
    167180                    return self.write(datagram)
    168181                elif no == errno.WSAEMSGSIZE:
    169                     raise error.MessageLengthError, "message too long"
     182                    raise error.MessageLengthError("message too long")
    170183                elif no in (errno.WSAECONNREFUSED, errno.WSAECONNRESET,
    171184                            ERROR_CONNECTION_REFUSED, ERROR_PORT_UNREACHABLE):
    172185                    self.protocol.connectionRefused()
    class Port(abstract.FileHandle): 
    174187                    raise
    175188        else:
    176189            assert addr != None
    177             if not addr[0].replace(".", "").isdigit():
    178                 warnings.warn("Please only pass IPs to write(), not hostnames",
    179                               DeprecationWarning, stacklevel=2)
     190            if not isIPAddress(addr[0]) and not isIPv6Address(addr[0]):
     191                raise error.InvalidAddressError(addr[0], "write() only accepts IP addresses, not hostnames")
     192            if isIPAddress(addr[0]) and self.addressFamily == socket.AF_INET6:
     193                raise error.InvalidAddressError(addr[0], "IPv6 port write() called with IPv4 address")
     194            if isIPv6Address(addr[0]) and self.addressFamily == socket.AF_INET:
     195                raise error.InvalidAddressError(addr[0], "IPv4 port write() called with IPv6 address")
    180196            try:
    181197                return self.socket.sendto(datagram, addr)
    182198            except socket.error, se:
    class Port(abstract.FileHandle): 
    184200                if no == errno.WSAEINTR:
    185201                    return self.write(datagram, addr)
    186202                elif no == errno.WSAEMSGSIZE:
    187                     raise error.MessageLengthError, "message too long"
     203                    raise error.MessageLengthError("message too long")
    188204                elif no in (errno.WSAECONNREFUSED, errno.WSAECONNRESET,
    189205                            ERROR_CONNECTION_REFUSED, ERROR_PORT_UNREACHABLE):
    190206                    # in non-connected UDP ECONNREFUSED is platform dependent,
    class Port(abstract.FileHandle): 
    207223            raise RuntimeError(
    208224                "already connected, reconnecting is not currently supported "
    209225                "(talk to itamar if you want this)")
    210         if not isIPAddress(host):
    211             raise ValueError, "please pass only IP addresses, not domain names"
     226        if not isIPAddress(host) and not isIPv6Address(host):
     227            raise error.InvalidAddressError(host, 'not an IPv4 or IPv6 address.')
    212228        self._connectedAddr = (host, port)
    213229        self.socket.connect((host, port))
    214230
    class Port(abstract.FileHandle): 
    268284
    269285    def getHost(self):
    270286        """
    271         Returns an IPv4Address.
     287        Return the local address of the UDP connection
    272288
    273         This indicates the address from which I am connecting.
     289        @returns: the local address of the UDP connection
     290        @rtype: L{IPv4Address} or L{IPv6Address}
    274291        """
    275         return address.IPv4Address('UDP', *self.socket.getsockname())
     292        addr = self.socket.getsockname()
     293        if self.addressFamily == socket.AF_INET:
     294            return address.IPv4Address('UDP', *addr)
     295        elif self.addressFamily == socket.AF_INET6:
     296            return address.IPv6Address('UDP', *(addr[:2]))
    276297
    277298
    278299
    class MulticastPort(MulticastMixin, Port): 
    378399            if hasattr(socket, "SO_REUSEPORT"):
    379400                skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    380401        return skt
    381 
    382 
  • twisted/internet/test/test_udp.py

    diff --git twisted/internet/test/test_udp.py twisted/internet/test/test_udp.py
    index 4452e2c..6a59760 100644
    from twisted.internet.test.reactormixins import ReactorBuilder 
    2121from twisted.internet.defer import Deferred, maybeDeferred
    2222from twisted.internet.interfaces import (
    2323    ILoggingContext, IListeningPort, IReactorUDP, IReactorSocket)
    24 from twisted.internet.address import IPv4Address
     24from twisted.internet.address import IPv4Address, IPv6Address
    2525from twisted.internet.protocol import DatagramProtocol
    2626
    2727from twisted.internet.test.connectionmixins import (LogObserverMixin,
    2828                                                    findFreePort)
     29from twisted.internet import defer, error
     30from twisted.test.test_udp import Server, GoodClient
    2931from twisted.trial.unittest import SkipTest
    3032
    3133
    class UDPPortTestsMixin(object): 
    139141            port.getHost(), IPv4Address('UDP', host, portNumber))
    140142
    141143
     144    def test_getHostIPv6(self):
     145        """
     146        L{IListeningPort.getHost} returns an L{IPv6Address} when listening on
     147        an IPv6 interface.
     148        """
     149        reactor = self.buildReactor()
     150        port = self.getListeningPort(
     151            reactor, DatagramProtocol(), interface='::1')
     152        addr = port.getHost()
     153        self.assertEqual(addr.host, "::1")
     154        self.assertIsInstance(addr, IPv6Address)
     155
     156
     157    def test_invalidInterface(self):
     158        """
     159        A C{InvalidAddressError} is raised when trying to listen on an address that
     160        isn't a valid IPv4 or IPv6 address.
     161        """
     162        reactor = self.buildReactor()
     163        self.assertRaises(
     164            error.InvalidAddressError, reactor.listenUDP, DatagramProtocol(), 0,
     165            interface='example.com')
     166
     167
    142168    def test_logPrefix(self):
    143169        """
    144170        Datagram transports implement L{ILoggingContext.logPrefix} to return a
    class UDPPortTestsMixin(object): 
    192218        self.assertIn(repr(port.getHost().port), str(port))
    193219
    194220
     221    def test_writeToIPv6Interface(self):
     222        """
     223        Writing to an IPv6 UDP socket on the loopback interface succeeds.
     224        """
     225
     226        reactor = self.buildReactor()
     227        server = Server()
     228        serverStarted = server.startedDeferred = defer.Deferred()
     229        self.getListeningPort(reactor, server, interface="::1")
     230
     231        client = GoodClient()
     232        clientStarted = client.startedDeferred = defer.Deferred()
     233        self.getListeningPort(reactor, client, interface="::1")
     234        cAddr = client.transport.getHost()
     235
     236        def cbClientStarted(ignored):
     237            """
     238            Send a datagram from the client once it's started.
     239
     240            @param ignored: a list of C{[None, None]}, which is ignored
     241            @returns: a deferred which fires when the server has received a
     242                datagram.
     243            """
     244            client.transport.write(
     245                b"spam", ("::1", server.transport.getHost().port))
     246            serverReceived = server.packetReceived = defer.Deferred()
     247            return serverReceived
     248
     249        def cbServerReceived(ignored):
     250            """
     251            Stop the reactor after a datagram is received.
     252
     253            @param ignored: C{None}, which is ignored
     254            @returns: C{None}
     255            """
     256            reactor.stop()
     257
     258        d = defer.gatherResults([serverStarted, clientStarted])
     259        d.addCallback(cbClientStarted)
     260        d.addCallback(cbServerReceived)
     261        d.addErrback(err)
     262        self.runReactor(reactor)
     263
     264        packet = server.packets[0]
     265        self.assertEqual(packet[0], b'spam')
     266        self.assertEqual(packet[1], (cAddr.host, cAddr.port))
     267
     268
     269    def test_connectedWriteToIPv6Interface(self):
     270        """
     271        An IPv6 address can be passed as the C{interface} argument to
     272        L{listenUDP}. The resulting Port accepts IPv6 datagrams.
     273        """
     274
     275        reactor = self.buildReactor()
     276        server = Server()
     277        serverStarted = server.startedDeferred = defer.Deferred()
     278        self.getListeningPort(reactor, server, interface="::1")
     279
     280        client = GoodClient()
     281        clientStarted = client.startedDeferred = defer.Deferred()
     282        self.getListeningPort(reactor, client, interface="::1")
     283        cAddr = client.transport.getHost()
     284
     285        def cbClientStarted(ignored):
     286            """
     287            Send a datagram from the client once it's started.
     288
     289            @param ignored: a list of C{[None, None]}, which is ignored
     290            @returns: a deferred which fires when the server has received a
     291                datagram.
     292            """
     293
     294            client.transport.connect("::1", server.transport.getHost().port)
     295            client.transport.write(b"spam")
     296            serverReceived = server.packetReceived = defer.Deferred()
     297            return serverReceived
     298
     299        def cbServerReceived(ignored):
     300            """
     301            Stop the reactor after a datagram is received.
     302
     303            @param ignored: C{None}, which is ignored
     304            @returns: C{None}
     305            """
     306
     307            reactor.stop()
     308
     309        d = defer.gatherResults([serverStarted, clientStarted])
     310        d.addCallback(cbClientStarted)
     311        d.addCallback(cbServerReceived)
     312        d.addErrback(err)
     313        self.runReactor(reactor)
     314
     315        packet = server.packets[0]
     316        self.assertEqual(packet[0], b'spam')
     317        self.assertEqual(packet[1], (cAddr.host, cAddr.port))
     318
     319
     320    def test_writingToHostnameRaisesAddressError(self):
     321        """
     322        Writing to a hostname instead of an IP address will raise an
     323        C{InvalidAddressError}.
     324        """
     325
     326        reactor = self.buildReactor()
     327        port = self.getListeningPort(reactor, DatagramProtocol())
     328        self.assertRaises(error.InvalidAddressError, port.write, 'spam', ('eggs.com', 1))
     329
     330    def test_writingToIPv6OnIPv4RaisesAddressError(self):
     331        """
     332        Writing to an IPv6 address on an IPv4 socket will raise an
     333        C{InvalidAddressError}.
     334        """
     335        reactor = self.buildReactor()
     336        port = self.getListeningPort(reactor, DatagramProtocol())
     337        self.assertRaises(error.InvalidAddressError, port.write, 'spam', ('::1', 1))
     338
     339    def test_writingToIPv4OnIPv6RaisesAddressError(self):
     340        """
     341        Writing to an IPv6 address on an IPv4 socket will raise an
     342        C{InvalidAddressError}.
     343        """
     344        reactor = self.buildReactor()
     345        port = self.getListeningPort(reactor, DatagramProtocol(), interface="::1")
     346        self.assertRaises(error.InvalidAddressError, port.write, 'spam', ('127.0.0.1', 1))
     347
     348    def test_connectingToHostnameRaisesAddressError(self):
     349        """
     350        Connecting to a hostname instead of an IP address will raise an
     351        C{InvalidAddressError}.
     352        """
     353
     354        reactor = self.buildReactor()
     355        port = self.getListeningPort(reactor, DatagramProtocol())
     356        self.assertRaises(error.InvalidAddressError, port.connect, 'eggs.com', 1)
     357
     358
    195359
    196360class UDPServerTestsBuilder(ReactorBuilder,
    197361                            UDPPortTestsMixin, DatagramTransportTestsMixin):
  • twisted/internet/udp.py

    diff --git twisted/internet/udp.py twisted/internet/udp.py
    index 9dc55df..35eaa75 100644
    class Port(base.BasePort): 
    108108        self.interface = interface
    109109        self.setLogStr()
    110110        self._connectedAddr = None
     111        self._setAddressFamily()
    111112
    112113
    113114    @classmethod
    class Port(base.BasePort): 
    230231                raise
    231232            else:
    232233                read += len(data)
     234                if self.addressFamily == socket.AF_INET6:
     235                    # Remove the flow and scope ID from the address tuple,
     236                    # reducing it to a tuple of just (host, port). This should
     237                    # be amended later to return an object that can unpack to
     238                    # (host, port) but also includes the flow and scope ID.
     239                    addr = addr[:2]
    233240                try:
    234241                    self.protocol.datagramReceived(data, addr)
    235242                except:
    class Port(base.BasePort): 
    245252
    246253        @type addr: C{tuple} containing C{str} as first element and C{int} as
    247254            second element, or C{None}
    248         @param addr: A tuple of (I{stringified dotted-quad IP address},
     255        @param addr: A tuple of (I{stringified IPv4 or IPv6 address},
    249256            I{integer port number}); can be C{None} in connected mode.
    250257        """
    251258        if self._connectedAddr:
    class Port(base.BasePort): 
    264271                    raise
    265272        else:
    266273            assert addr != None
    267             if not addr[0].replace(".", "").isdigit() and addr[0] != "<broadcast>":
    268                 warnings.warn("Please only pass IPs to write(), not hostnames",
    269                               DeprecationWarning, stacklevel=2)
     274            if (not abstract.isIPAddress(addr[0])
     275                    and not abstract.isIPv6Address(addr[0])
     276                    and addr[0] != "<broadcast>"):
     277                raise error.InvalidAddressError(addr[0], "write() only accepts IP addresses, not hostnames")
     278            if (abstract.isIPAddress(addr[0]) or addr[0] == "<broadcast>") and self.addressFamily == socket.AF_INET6:
     279                raise error.InvalidAddressError(addr[0], "IPv6 port write() called with IPv4 or broadcast address")
     280            if abstract.isIPv6Address(addr[0]) and self.addressFamily == socket.AF_INET:
     281                raise error.InvalidAddressError(addr[0], "IPv4 port write() called with IPv6 address")
    270282            try:
    271283                return self.socket.sendto(datagram, addr)
    272284            except socket.error as se:
    class Port(base.BasePort): 
    292304        """
    293305        if self._connectedAddr:
    294306            raise RuntimeError("already connected, reconnecting is not currently supported")
    295         if not abstract.isIPAddress(host):
    296             raise ValueError("please pass only IP addresses, not domain names")
     307        if not abstract.isIPAddress(host) and not abstract.isIPv6Address(host):
     308            raise error.InvalidAddressError(host, 'not an IPv4 or IPv6 address.')
    297309        self._connectedAddr = (host, port)
    298310        self.socket.connect((host, port))
    299311
    class Port(base.BasePort): 
    337349        logPrefix = self._getLogPrefix(self.protocol)
    338350        self.logstr = "%s (UDP)" % logPrefix
    339351
     352    def _setAddressFamily(self):
     353        """
     354        Resolve address family for the socket.
     355        """
     356        if abstract.isIPv6Address(self.interface):
     357            self.addressFamily = socket.AF_INET6
     358        elif abstract.isIPAddress(self.interface):
     359            self.addressFamily = socket.AF_INET
     360        elif self.interface:
     361            raise error.InvalidAddressError(self.interface, 'not an IPv4 or IPv6 address.')
     362
    340363
    341364    def logPrefix(self):
    342365        """
    class Port(base.BasePort): 
    347370
    348371    def getHost(self):
    349372        """
    350         Returns an IPv4Address.
     373        Return the local address of the UDP connection
    351374
    352         This indicates the address from which I am connecting.
     375        @returns: the local address of the UDP connection
     376        @rtype: L{IPv4Address} or L{IPv6Address}
    353377        """
    354         return address.IPv4Address('UDP', *self.socket.getsockname())
     378        addr = self.socket.getsockname()
     379        if self.addressFamily == socket.AF_INET:
     380            return address.IPv4Address('UDP', *addr)
     381        elif self.addressFamily == socket.AF_INET6:
     382            return address.IPv6Address('UDP', *(addr[:2]))
    355383
    356384
    357385
    class MulticastPort(MulticastMixin, Port): 
    417445    UDP Port that supports multicasting.
    418446    """
    419447
    420     def __init__(self, port, proto, interface='', maxPacketSize=8192, reactor=None, listenMultiple=False):
     448    def __init__(self, port, proto, interface='', maxPacketSize=8192,
     449                 reactor=None, listenMultiple=False):
    421450        """
    422451        @see: L{twisted.internet.interfaces.IReactorMulticast.listenMulticast}
    423452        """
  • twisted/test/test_udp.py

    diff --git twisted/test/test_udp.py twisted/test/test_udp.py
    index 21d145c..a12dab7 100644
    class UDPTestCase(unittest.TestCase): 
    267267        return d
    268268
    269269
     270
    270271    def test_connectionRefused(self):
    271272        """
    272273        A L{ConnectionRefusedError} exception is raised when a connection
    class UDPTestCase(unittest.TestCase): 
    312313
    313314    def test_badConnect(self):
    314315        """
    315         A call to the transport's connect method fails with a L{ValueError}
     316        A call to the transport's connect method fails with a L{InvalidAddressError}
    316317        when a non-IP address is passed as the host value.
    317318
    318319        A call to a transport's connect method fails with a L{RuntimeError}
    class UDPTestCase(unittest.TestCase): 
    322323        port = reactor.listenUDP(0, client, interface="127.0.0.1")
    323324        self.assertRaises(ValueError, client.transport.connect,
    324325                          "localhost", 80)
     326        #With introduction of IPv6 the error message is now more specific
     327        self.assertRaises(error.InvalidAddressError, client.transport.connect,
     328                          "localhost", 80)
    325329        client.transport.connect("127.0.0.1", 80)
    326330        self.assertRaises(RuntimeError, client.transport.connect,
    327331                          "127.0.0.1", 80)
  • new file twisted/topfiles/5086.feature

    diff --git twisted/topfiles/5086.feature twisted/topfiles/5086.feature
    new file mode 100644
    index 0000000..d0ea2f0
    - +  
     1IReactorUDP.listenUDP, IUDPTransport.write and IUDPTransport.connect now accept ipv6 address literals.
     2 No newline at end of file