Ticket #5085: connecttcp-ipv6-5085-1.patch

File connecttcp-ipv6-5085-1.patch, 43.5 KB (added by ragzilla, 4 years ago)
  • twisted/internet/address.py

    diff --git twisted/internet/address.py twisted/internet/address.py
    index b9192d4..8e5a483 100644
    class _IPAddress(object, util.FancyEqMixin): 
    1919
    2020    @ivar type: A string describing the type of transport, either 'TCP' or
    2121        'UDP'.
    22     @ivar host: A string containing the dotted-quad IP address.
     22    @ivar host: A string containing the native IP address.
    2323    @ivar port: An integer representing the port number.
    2424    """
    2525
    class _IPAddress(object, util.FancyEqMixin): 
    4343        return hash((self.type, self.host, self.port))
    4444
    4545
    46 
     46   
    4747class IPv4Address(_IPAddress):
    4848    """
    4949    Object representing an IPv4 socket endpoint.
     50
     51    @ivar host: A string containing the dotted-quad IPv4 address.
    5052    """
    5153    def __init__(self, type, host, port, _bwHack=None):
    5254        _IPAddress.__init__(self, type, host, port)
    class IPv4Address(_IPAddress): 
    5961class IPv6Address(_IPAddress):
    6062    """
    6163    Object representing an IPv6 socket endpoint.
     64
     65    @ivar host: A string containing the hexadecimal formatted IPv6 address.
    6266    """
     67    pass
    6368
    6469
    6570
    class UNIXAddress(object, util.FancyEqMixin): 
    8590    if getattr(os.path, 'samefile', None) is not None:
    8691        def __eq__(self, other):
    8792            """
    88             overriding L{util.FancyEqMixin} to ensure the os level samefile check
    89             is done if the name attributes do not match.
     93            overriding L{util.FancyEqMixin} to ensure the os level samefile
     94            check is done if the name attributes do not match.
    9095            """
    9196            res = super(UNIXAddress, self).__eq__(other)
    9297            if res == False:
  • twisted/internet/endpoints.py

    diff --git twisted/internet/endpoints.py twisted/internet/endpoints.py
    index 79d7672..e80e1cc 100644
    from twisted.python.filepath import FilePath 
    2525
    2626__all__ = ["clientFromString", "serverFromString",
    2727           "TCP4ServerEndpoint", "TCP4ClientEndpoint",
     28           "TCP6ServerEndpoint", "TCP6ClientEndpoint",
    2829           "UNIXServerEndpoint", "UNIXClientEndpoint",
    2930           "SSL4ServerEndpoint", "SSL4ClientEndpoint"]
    3031
    class _WrappingFactory(ClientFactory): 
    169170
    170171
    171172
    172 class TCP4ServerEndpoint(object):
     173class _TCPServerEndpoint(object):
    173174    """
    174     TCP server endpoint with an IPv4 configuration
     175    TCP server endpoint with a default (IPv4) configuration
    175176
    176177    @ivar _reactor: An L{IReactorTCP} provider.
    177178
    class TCP4ServerEndpoint(object): 
    212213
    213214
    214215
    215 class TCP4ClientEndpoint(object):
     216class TCP4ServerEndpoint(_TCPServerEndpoint):
     217    """
     218    TCP server endpoint with an IPv4 configuration
     219
     220    @type _interface: str
     221    @ivar _interface: the hostname to bind to, defaults to '' (all)
     222    """
     223    def __init__(self, reactor, port, backlog=50, interface=''):
     224        """
     225        @param interface: the hostname to bind to, defaults to '' (all)
     226        """
     227        _TCPServerEndpoint.__init__(self, reactor, port, backlog, interface)
     228
     229
     230
     231class TCP6ServerEndpoint(_TCPServerEndpoint):
     232    """
     233    TCP server endpoint with an IPv6 configuration
     234
     235    @type _interface: str
     236    @ivar _interface: the hostname to bind to, defaults to '::' (all)
     237    """
     238
     239    def __init__(self, reactor, port, backlog=50, interface='::'):
     240        """
     241        @param interface: the hostname to bind to, defaults to '::' (all)
     242        """
     243        _TCPServerEndpoint.__init__(self, reactor, port, backlog, interface)
     244
     245
     246
     247class _TCPClientEndpoint(object):
    216248    """
    217     TCP client endpoint with an IPv4 configuration.
     249    TCP client endpoint with a default (IPv4) configuration.
    218250
    219251    @ivar _reactor: An L{IReactorTCP} provider.
    220252
    class TCP4ClientEndpoint(object): 
    271303
    272304
    273305
     306class TCP4ClientEndpoint(_TCPClientEndpoint):
     307    """
     308    TCP client endpoint with an IPv4 configuration
     309    """
     310    pass
     311
     312
     313
     314class TCP6ClientEndpoint(_TCPClientEndpoint):
     315    """
     316    TCP client endpoint with an IPv6 configuration
     317    """
     318    pass
     319
     320
     321
    274322class SSL4ServerEndpoint(object):
    275323    """
    276324    SSL secured TCP server endpoint with an IPv4 configuration.
  • twisted/internet/tcp.py

    diff --git twisted/internet/tcp.py twisted/internet/tcp.py
    index 1c81697..31b032b 100644
    class BaseClient(_TLSClientMixin, Connection): 
    320320        else:
    321321            reactor.callLater(0, self.failIfNotConnected, error)
    322322
     323
    323324    def stopConnecting(self):
    324325        """Stop attempt to connect."""
    325326        self.failIfNotConnected(error.UserError())
    326327
     328
    327329    def failIfNotConnected(self, err):
    328330        """
    329331        Generic method called when the attemps to connect failed. It basically
    class BaseClient(_TLSClientMixin, Connection): 
    348350        else:
    349351            del self.socket, self.fileno
    350352
     353
    351354    def createInternetSocket(self):
    352355        """(internal) Create a non-blocking socket using
    353356        self.addressFamily, self.socketType.
    class BaseClient(_TLSClientMixin, Connection): 
    357360        fdesc._setCloseOnExec(s.fileno())
    358361        return s
    359362
     363
    360364    def resolveAddress(self):
    361365        if abstract.isIPAddress(self.addr[0]):
    362366            self._setRealAddress(self.addr[0])
    class BaseClient(_TLSClientMixin, Connection): 
    364368            d = self.reactor.resolve(self.addr[0])
    365369            d.addCallbacks(self._setRealAddress, self.failIfNotConnected)
    366370
     371
    367372    def _setRealAddress(self, address):
    368373        self.realAddress = (address, self.addr[1])
    369374        self.doConnect()
    370375
     376
    371377    def doConnect(self):
    372378        """I connect the socket.
    373379
    class BaseClient(_TLSClientMixin, Connection): 
    383389            self.failIfNotConnected(error.getConnectError((err, strerror(err))))
    384390            return
    385391
    386 
    387392        # doConnect gets called twice.  The first time we actually need to
    388393        # start the connection attempt.  The second time we don't really
    389394        # want to (SO_ERROR above will have taken care of any errors, and if
    class BaseClient(_TLSClientMixin, Connection): 
    418423        self.stopWriting()
    419424        self._connectDone()
    420425
     426
    421427    def _connectDone(self):
    422428        self.protocol = self.connector.buildProtocol(self.getPeer())
    423429        self.connected = 1
    class BaseClient(_TLSClientMixin, Connection): 
    426432        self.startReading()
    427433        self.protocol.makeConnection(self)
    428434
     435
    429436    def connectionLost(self, reason):
    430437        if not self.connected:
    431438            self.failIfNotConnected(error.ConnectError(string=reason))
    class BaseClient(_TLSClientMixin, Connection): 
    434441            self.connector.connectionLost(reason)
    435442
    436443
     444
    437445class Client(BaseClient):
    438     """A TCP client."""
     446    """
     447    A TCP client.
     448
     449    @type _addressType: L{IPv4Address} or L{IPv6Address}
     450    @ivar _addressType: The Twisted _IPAddress implementation for this client
     451    """
     452
     453    _addressType = address.IPv4Address
    439454
    440455    def __init__(self, host, port, bindAddress, connector, reactor=None):
    441456        # BaseClient.__init__ is invoked later
    442457        self.connector = connector
    443458        self.addr = (host, port)
     459        self.bindAddress = bindAddress
     460        self.reactor = reactor
     461        # Do outstanding initialization when real address is resolved
     462        self.resolveAddress()
     463
     464
     465    def resolveAddress(self):
     466        if abstract.isIPAddress(self.addr[0]) or abstract.isIPv6Address(self.addr[0]):
     467            self._setRealAddress(self.addr[0])
     468        else:
     469            d = self.reactor.resolve(self.addr[0])
     470            d.addCallbacks(self._setRealAddress, self.failIfNotConnected)
     471   
    444472
    445         whenDone = self.resolveAddress
     473    def _setRealAddress(self, addr):
     474        """
     475        Sets the address family to IPv6 if passed an IPv6 literal.
     476        Sets the real IP address for this client.
     477        Once the IP is set the socket is created with the correct address
     478        family.
     479
     480        @type addr: str
     481        @param addr: A string formatted IPv4 or IPv6 literal.
     482        """
     483        if abstract.isIPv6Address(addr):
     484            self.addressFamily = socket.AF_INET6
     485            self._addressType = address.IPv6Address
     486        self.realAddress = (addr, self.addr[1])
     487        self.initConnection()
     488
     489
     490    def initConnection(self):
     491        """
     492        Initialize connection by creating appropriate socket.
     493        """
    446494        err = None
    447495        skt = None
    448 
     496        result = True
     497     
    449498        try:
    450499            skt = self.createInternetSocket()
    451500        except socket.error, se:
    452501            err = error.ConnectBindError(se.args[0], se.args[1])
    453             whenDone = None
    454         if whenDone and bindAddress is not None:
     502            result = None
     503        if result and self.bindAddress is not None:
    455504            try:
    456                 skt.bind(bindAddress)
     505                skt.bind(self.bindAddress)
    457506            except socket.error, se:
    458507                err = error.ConnectBindError(se.args[0], se.args[1])
    459                 whenDone = None
    460         self._finishInit(whenDone, skt, err, reactor)
     508                result = None
     509        if result:
     510            Connection.__init__(self, skt, None, self.reactor)
     511            self.doWrite = self.doConnect
     512            self.doRead = self.doConnect
     513            self.doConnect()
     514        else:
     515            self.reactor.callLater(0, self.failIfNotConnected, error)
     516
    461517
    462518    def getHost(self):
    463         """Returns an IPv4Address.
     519        """Returns an L{IPv4Address} or L{IPv6Address}.
    464520
    465521        This indicates the address from which I am connecting.
    466522        """
    467         return address.IPv4Address('TCP', *self.socket.getsockname())
     523        return self._addressType('TCP', *self.socket.getsockname()[:2])
     524
    468525
    469526    def getPeer(self):
    470         """Returns an IPv4Address.
     527        """Returns an L{IPv4Address} or L{IPv6Address}.
    471528
    472529        This indicates the address that I am connected to.
    473530        """
    474         return address.IPv4Address('TCP', *self.realAddress)
     531        return self._addressType('TCP', *self.realAddress)
     532
    475533
    476534    def __repr__(self):
    477535        s = '<%s to %s at %x>' % (self.__class__, self.addr, unsignedID(self))
    478536        return s
    479537
    480538
     539
    481540class Server(_TLSServerMixin, Connection):
    482541    """
    483542    Serverside socket-stream connection class.
    class Port(base.BasePort, _SocketCloser): 
    612671
    613672    def __repr__(self):
    614673        if self._realPortNumber is not None:
    615             return "<%s of %s on %s>" % (self.__class__, self.factory.__class__,
    616                                          self._realPortNumber)
     674            return "<%s of %s on %s (%s)>" % (self.__class__,
     675                self.factory.__class__, self._realPortNumber,
     676                self._addressType)
    617677        else:
    618678            return "<%s of %s (not listening)>" % (self.__class__, self.factory.__class__)
    619679
     680
    620681    def createInternetSocket(self):
    621682        s = base.BasePort.createInternetSocket(self)
    622683        if platformType == "posix" and sys.platform != "cygwin":
    class Port(base.BasePort, _SocketCloser): 
    644705        # reflect what the OS actually assigned us.
    645706        self._realPortNumber = skt.getsockname()[1]
    646707
    647         log.msg("%s starting on %s" % (
    648                 self._getLogPrefix(self.factory), self._realPortNumber))
     708        log.msg("%s starting on %s (%s)" % (
     709                self._getLogPrefix(self.factory), self._realPortNumber,
     710                self._addressType))
    649711
    650712        # The order of the next 6 lines is kind of bizarre.  If no one
    651713        # can explain it, perhaps we should re-arrange them.
    class Port(base.BasePort, _SocketCloser): 
    795857
    796858
    797859class Connector(base.BaseConnector):
     860    _addressType = address.IPv4Address
     861
    798862    def __init__(self, host, port, factory, timeout, bindAddress, reactor=None):
    799         self.host = host
    800863        if isinstance(port, types.StringTypes):
    801864            try:
    802865                port = socket.getservbyname(port, 'tcp')
    803866            except socket.error, e:
    804867                raise error.ServiceNameUnknownError(string="%s (%r)" % (e, port))
    805         self.port = port
     868        self.host, self.port = host, port
     869        if abstract.isIPv6Address(host):
     870            self._addressType = address.IPv6Address
    806871        self.bindAddress = bindAddress
    807872        base.BaseConnector.__init__(self, factory, timeout, reactor)
    808873
    class Connector(base.BaseConnector): 
    810875        return Client(self.host, self.port, self.bindAddress, self, self.reactor)
    811876
    812877    def getDestination(self):
    813         return address.IPv4Address('TCP', self.host, self.port)
     878        return self._addressType('TCP', self.host, self.port)
  • twisted/internet/test/connectionmixins.py

    diff --git twisted/internet/test/connectionmixins.py twisted/internet/test/connectionmixins.py
    index 2a592f8..2abb80b 100644
     
    55Various helpers for tests for connection-oriented transports.
    66"""
    77
     8import socket
     9
    810from gc import collect
    911from weakref import ref
    1012
     13from zope.interface import implements
     14from zope.interface.verify import verifyObject
     15
    1116from twisted.python import context, log
     17from twisted.python.failure import Failure
    1218from twisted.python.reflect import fullyQualifiedName
    1319from twisted.python.log import ILogContext, msg, err
    14 from twisted.internet.defer import Deferred, gatherResults
    15 from twisted.internet.protocol import ServerFactory, Protocol
     20from twisted.internet.defer import Deferred, gatherResults, succeed
     21from twisted.internet.interfaces import (
     22    IConnector, IResolverSimple, IReactorFDSet)
     23from twisted.internet.protocol import ClientFactory, Protocol, ServerFactory
     24from twisted.test.test_tcp import ClosingProtocol
    1625
    1726def serverFactoryFor(protocol):
    1827    """
    def serverFactoryFor(protocol): 
    2938# ServerFactory is good enough for client endpoints, too.
    3039factoryFor = serverFactoryFor
    3140
     41
     42
     43def findFreePort(interface='127.0.0.1', family=socket.AF_INET,
     44                 type=socket.SOCK_STREAM):
     45    """
     46    Ask the platform to allocate a free port on the specified interface,
     47    then release the socket and return the address which was allocated.
     48
     49    @param interface: The local address to try to bind the port on.
     50    @type interface: C{str}
     51
     52    @param type: The socket type which will use the resulting port.
     53
     54    @return: A two-tuple of address and port, like that returned by
     55        L{socket.getsockname}.
     56    """
     57    probe = socket.socket(family, type)
     58    try:
     59        probe.bind((interface, 0))
     60        return probe.getsockname()
     61    finally:
     62        probe.close()
     63
     64
     65
     66def _getWriters(reactor):
     67    """
     68    Like L{IReactorFDSet.getWriters}, but with support for IOCP reactor as well.
     69    """
     70    if IReactorFDSet.providedBy(reactor):
     71        return reactor.getWriters()
     72    elif 'IOCP' in reactor.__class__.__name__:
     73        return reactor.handles
     74    else:
     75        # Cannot tell what is going on.
     76        raise Exception("Cannot find writers on %r" % (reactor,))
     77
     78
     79
    3280class _AcceptOneClient(ServerFactory):
    3381    """
    3482    This factory fires a L{Deferred} with a protocol instance shortly after it
    class _AcceptOneClient(ServerFactory): 
    5098
    5199
    52100
     101class _SimplePullProducer(object):
     102    """
     103    A pull producer which writes one byte whenever it is resumed.  For use by
     104    L{test_unregisterProducerAfterDisconnect}.
     105    """
     106    def __init__(self, consumer):
     107        self.consumer = consumer
     108
     109
     110    def stopProducing(self):
     111        pass
     112
     113
     114    def resumeProducing(self):
     115        log.msg("Producer.resumeProducing")
     116        self.consumer.write('x')
     117
     118
     119
     120class Stop(ClientFactory):
     121    """
     122    A client factory which stops a reactor when a connection attempt fails.
     123    """
     124    def __init__(self, reactor):
     125        self.reactor = reactor
     126
     127
     128    def clientConnectionFailed(self, connector, reason):
     129        self.reactor.stop()
     130
     131
     132
     133class FakeResolver:
     134    """
     135    A resolver implementation based on a C{dict} mapping names to addresses.
     136    """
     137    implements(IResolverSimple)
     138
     139    def __init__(self, names):
     140        self.names = names
     141
     142
     143    def getHostByName(self, name, timeout):
     144        try:
     145            return succeed(self.names[name])
     146        except KeyError:
     147            return fail(DNSLookupError("FakeResolver couldn't find " + name))
     148
     149
     150
    53151class ClosingLaterProtocol(Protocol):
    54152    """
    55153    ClosingLaterProtocol exchanges one byte with its peer and then disconnects
    class LogObserverMixin(object): 
    254352        log.addObserver(loggedMessages.append)
    255353        self.addCleanup(log.removeObserver, loggedMessages.append)
    256354        return loggedMessages
     355
     356
     357
     358class TCPClientTestsMixin(object):
     359    """
     360    This mixin defines tests applicable to TCP client implementations.
     361    """
     362    def test_interface(self):
     363        """
     364        L{IReactorTCP.connectTCP} returns an object providing L{IConnector}.
     365        """
     366        reactor = self.buildReactor()
     367        connector = reactor.connectTCP(self.interface, self.port,
     368            ClientFactory())
     369        self.assertTrue(verifyObject(IConnector, connector))
     370
     371   
     372    def test_clientConnectionFailedStopsReactor(self):
     373        """
     374        The reactor can be stopped by a client factory's
     375        C{clientConnectionFailed} method.
     376        """
     377        host, port = findFreePort(self.interface, self.family)[:2]
     378        reactor = self.buildReactor()
     379        reactor.connectTCP(host, port, Stop(reactor))
     380        self.runReactor(reactor)
     381
     382
     383    def test_addresses(self):
     384        """
     385        A client's transport's C{getHost} and C{getPeer} return L{IPv4Address}
     386        instances which give the dotted-quad string form of the local and
     387        remote endpoints of the connection respectively.
     388        """
     389        host, port = findFreePort(self.interface, self.family)[:2]
     390        reactor = self.buildReactor()
     391        reactor.installResolver(FakeResolver({'localhost': self.interface}))
     392
     393        server = reactor.listenTCP(
     394            0, serverFactoryFor(Protocol), interface=host)
     395        serverAddress = server.getHost()
     396
     397        addresses = {'host': None, 'peer': None}
     398        class CheckAddress(Protocol):
     399            def makeConnection(self, transport):
     400                addresses['host'] = transport.getHost()
     401                addresses['peer'] = transport.getPeer()
     402                reactor.stop()
     403
     404        clientFactory = Stop(reactor)
     405        clientFactory.protocol = CheckAddress
     406        reactor.connectTCP(
     407            'localhost', server.getHost().port, clientFactory,
     408            bindAddress=(self.interface, port))
     409
     410        self.runReactor(reactor)
     411
     412        self.assertEqual(
     413            addresses['host'],
     414            self.addressClass('TCP', self.interface, port))
     415        self.assertEqual(
     416            addresses['peer'],
     417            self.addressClass('TCP', self.interface, serverAddress.port))
     418
     419
     420    def test_connectEvent(self):
     421        """
     422        This test checks that we correctly get notifications event for a
     423        client. This ought to prevent a regression under Windows using the GTK2
     424        reactor. See #3925.
     425        """
     426        reactor = self.buildReactor()
     427
     428        server = reactor.listenTCP(0, serverFactoryFor(Protocol),
     429            interface=self.interface)
     430        connected = []
     431
     432        class CheckConnection(Protocol):
     433            def connectionMade(self):
     434                connected.append(self)
     435                reactor.stop()
     436
     437        clientFactory = Stop(reactor)
     438        clientFactory.protocol = CheckConnection
     439        reactor.connectTCP(
     440            self.interface, server.getHost().port, clientFactory)
     441
     442        reactor.run()
     443
     444        self.assertTrue(connected)
     445
     446
     447    def test_unregisterProducerAfterDisconnect(self):
     448        """
     449        If a producer is unregistered from a L{ITCPTransport} provider after the
     450        transport has been disconnected (by the peer) and after
     451        L{ITCPTransport.loseConnection} has been called, the transport is not
     452        re-added to the reactor as a writer as would be necessary if the
     453        transport were still connected.
     454        """
     455        reactor = self.buildReactor()
     456        port = reactor.listenTCP(0, serverFactoryFor(ClosingProtocol),
     457            interface=self.interface)
     458
     459        finished = Deferred()
     460        finished.addErrback(log.err)
     461        finished.addCallback(lambda ign: reactor.stop())
     462
     463        writing = []
     464
     465        class ClientProtocol(Protocol):
     466            """
     467            Protocol to connect, register a producer, try to lose the
     468            connection, wait for the server to disconnect from us, and
     469            then unregister the producer.
     470            """
     471            def connectionMade(self):
     472                log.msg("ClientProtocol.connectionMade")
     473                self.transport.registerProducer(
     474                    _SimplePullProducer(self.transport), False)
     475                self.transport.loseConnection()
     476
     477            def connectionLost(self, reason):
     478                log.msg("ClientProtocol.connectionLost")
     479                self.unregister()
     480                writing.append(self.transport in _getWriters(reactor))
     481                finished.callback(None)
     482
     483            def unregister(self):
     484                log.msg("ClientProtocol unregister")
     485                self.transport.unregisterProducer()
     486
     487        clientFactory = ClientFactory()
     488        clientFactory.protocol = ClientProtocol
     489        reactor.connectTCP(self.interface, port.getHost().port, clientFactory)
     490        self.runReactor(reactor)
     491        self.assertFalse(
     492            writing[0], "Transport was writing after unregisterProducer.")
     493
     494
     495    def test_disconnectWhileProducing(self):
     496        """
     497        If L{ITCPTransport.loseConnection} is called while a producer
     498        is registered with the transport, the connection is closed
     499        after the producer is unregistered.
     500        """
     501        reactor = self.buildReactor()
     502
     503        # For some reason, pyobject/pygtk will not deliver the close
     504        # notification that should happen after the unregisterProducer call in
     505        # this test.  The selectable is in the write notification set, but no
     506        # notification ever arrives.  Probably for the same reason #5233 led
     507        # win32eventreactor to be broken.
     508        skippedReactors = ["Glib2Reactor", "Gtk2Reactor"]
     509        reactorClassName = reactor.__class__.__name__
     510        if reactorClassName in skippedReactors and platform.isWindows():
     511            raise SkipTest(
     512                "A pygobject/pygtk bug disables this functionality on Windows.")
     513
     514        class Producer:
     515            def resumeProducing(self):
     516                log.msg("Producer.resumeProducing")
     517       
     518        port = reactor.listenTCP(0, serverFactoryFor(Protocol),
     519            interface=self.interface)
     520
     521        finished = Deferred()
     522        finished.addErrback(log.err)
     523        finished.addCallback(lambda ign: reactor.stop())
     524
     525        class ClientProtocol(Protocol):
     526            """
     527            Protocol to connect, register a producer, try to lose the
     528            connection, unregister the producer, and wait for the connection to
     529            actually be lost.
     530            """
     531            def connectionMade(self):
     532                log.msg("ClientProtocol.connectionMade")
     533                self.transport.registerProducer(Producer(), False)
     534                self.transport.loseConnection()
     535                # Let the reactor tick over, in case synchronously calling
     536                # loseConnection and then unregisterProducer is the same as
     537                # synchronously calling unregisterProducer and then
     538                # loseConnection (as it is in several reactors).
     539                reactor.callLater(0, reactor.callLater, 0, self.unregister)
     540
     541            def unregister(self):
     542                log.msg("ClientProtocol unregister")
     543                self.transport.unregisterProducer()
     544                # This should all be pretty quick.  Fail the test
     545                # if we don't get a connectionLost event really
     546                # soon.
     547                reactor.callLater(
     548                    1.0, finished.errback,
     549                    Failure(Exception("Connection was not lost")))
     550
     551            def connectionLost(self, reason):
     552                log.msg("ClientProtocol.connectionLost")
     553                finished.callback(None)
     554
     555        clientFactory = ClientFactory()
     556        clientFactory.protocol = ClientProtocol
     557        reactor.connectTCP(self.interface, port.getHost().port, clientFactory)
     558        self.runReactor(reactor)
     559        # If the test failed, we logged an error already and trial
     560        # will catch it.
  • twisted/internet/test/test_tcp.py

    diff --git twisted/internet/test/test_tcp.py twisted/internet/test/test_tcp.py
    index af9ad0e..8f7b6c2 100644
    from twisted.internet.interfaces import ( 
    2525from twisted.internet.address import IPv4Address, IPv6Address
    2626from twisted.internet.defer import (
    2727    Deferred, DeferredList, succeed, fail, maybeDeferred, gatherResults)
    28 from twisted.internet.endpoints import TCP4ServerEndpoint, TCP4ClientEndpoint
     28from twisted.internet.endpoints import (
     29    TCP4ServerEndpoint, TCP4ClientEndpoint,
     30    TCP6ServerEndpoint, TCP6ClientEndpoint)
    2931from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol
    3032from twisted.internet.interfaces import (
    3133    IPushProducer, IPullProducer, IHalfCloseableProtocol)
    from twisted.internet.protocol import ClientCreator 
    3335from twisted.internet.tcp import Connection, Server
    3436
    3537from twisted.internet.test.connectionmixins import (
    36     LogObserverMixin, ConnectionTestsMixin, serverFactoryFor)
     38    LogObserverMixin, ConnectionTestsMixin, serverFactoryFor,
     39    TCPClientTestsMixin, findFreePort, Stop, FakeResolver)
    3740from twisted.internet.test.test_core import ObjectModelIntegrationMixin
    3841from twisted.test.test_tcp import MyClientFactory, MyServerFactory
    3942from twisted.test.test_tcp import ClosingProtocol
    def getLinkLocalIPv6Address(): 
    8285
    8386
    8487
    85 def findFreePort(interface='127.0.0.1', family=socket.AF_INET,
    86                  type=socket.SOCK_STREAM):
    87     """
    88     Ask the platform to allocate a free port on the specified interface,
    89     then release the socket and return the address which was allocated.
    90 
    91     @param interface: The local address to try to bind the port on.
    92     @type interface: C{str}
    93 
    94     @param type: The socket type which will use the resulting port.
    95 
    96     @return: A two-tuple of address and port, like that returned by
    97         L{socket.getsockname}.
    98     """
    99     probe = socket.socket(family, type)
    100     try:
    101         probe.bind((interface, 0))
    102         return probe.getsockname()
    103     finally:
    104         probe.close()
    105 
    106 
    107 
    10888def connect(client, (host, port)):
    109     if '%' in host:
     89    if '%' in host or ':' in host:
    11090        address = socket.getaddrinfo(host, port)[0][4]
    11191    else:
    11292        address = (host, port)
    def connect(client, (host, port)): 
    11494
    11595
    11696
    117 class Stop(ClientFactory):
    118     """
    119     A client factory which stops a reactor when a connection attempt fails.
    120     """
    121     def __init__(self, reactor):
    122         self.reactor = reactor
    123 
    124 
    125     def clientConnectionFailed(self, connector, reason):
    126         self.reactor.stop()
    127 
    128 
    129 
    130 class FakeResolver:
    131     """
    132     A resolver implementation based on a C{dict} mapping names to addresses.
    133     """
    134     implements(IResolverSimple)
    135 
    136     def __init__(self, names):
    137         self.names = names
    138 
    139 
    140     def getHostByName(self, name, timeout):
    141         try:
    142             return succeed(self.names[name])
    143         except KeyError:
    144             return fail(DNSLookupError("FakeResolver couldn't find " + name))
    145 
    146 
    147 
    148 class _SimplePullProducer(object):
    149     """
    150     A pull producer which writes one byte whenever it is resumed.  For use by
    151     L{test_unregisterProducerAfterDisconnect}.
    152     """
    153     def __init__(self, consumer):
    154         self.consumer = consumer
    155 
    156 
    157     def stopProducing(self):
    158         pass
    159 
    160 
    161     def resumeProducing(self):
    162         log.msg("Producer.resumeProducing")
    163         self.consumer.write('x')
    164 
    165 
    166 
    167 def _getWriters(reactor):
    168     """
    169     Like L{IReactorFDSet.getWriters}, but with support for IOCP reactor as well.
    170     """
    171     if IReactorFDSet.providedBy(reactor):
    172         return reactor.getWriters()
    173     elif 'IOCP' in reactor.__class__.__name__:
    174         return reactor.handles
    175     else:
    176         # Cannot tell what is going on.
    177         raise Exception("Cannot find writers on %r" % (reactor,))
    178 
    179 
    180 
    18197class FakeSocket(object):
    18298    """
    18399    A fake for L{socket.socket} objects.
    class TCPConnectionTests(TestCase): 
    399315        test_tlsAfterStartTLS.skip = "No SSL support available"
    400316
    401317
    402 class TCPClientTestsBuilder(ReactorBuilder, ConnectionTestsMixin):
     318class TCP4ClientTestsBuilder(ReactorBuilder, ConnectionTestsMixin,
     319                             TCPClientTestsMixin):
    403320    """
    404321    Builder defining tests relating to L{IReactorTCP.connectTCP}.
    405322    """
     323    def setUp(self):
     324        self.interface = '127.0.0.1'
     325        self.port = 1234
     326        self.family = socket.AF_INET
     327        self.addressClass = IPv4Address
     328
    406329    def serverEndpoint(self, reactor):
    407330        """
    408331        Create a L{TCP4ServerEndpoint} listening on localhost on a
    409332        TCP/IP-selected port.
    410333        """
    411         return TCP4ServerEndpoint(reactor, 0, interface='127.0.0.1')
     334        return TCP4ServerEndpoint(reactor, 0, interface=self.interface)
    412335
    413336
    414337    def clientEndpoint(self, reactor, serverAddress):
    class TCPClientTestsBuilder(ReactorBuilder, ConnectionTestsMixin): 
    419342        @type serverAddress: L{IPv4Address}
    420343        """
    421344        return TCP4ClientEndpoint(
    422             reactor, '127.0.0.1', serverAddress.port)
    423 
    424 
    425     def test_interface(self):
    426         """
    427         L{IReactorTCP.connectTCP} returns an object providing L{IConnector}.
    428         """
    429         reactor = self.buildReactor()
    430         connector = reactor.connectTCP("127.0.0.1", 1234, ClientFactory())
    431         self.assertTrue(verifyObject(IConnector, connector))
    432 
    433 
    434     def test_clientConnectionFailedStopsReactor(self):
    435         """
    436         The reactor can be stopped by a client factory's
    437         C{clientConnectionFailed} method.
    438         """
    439         host, port = findFreePort()
    440         reactor = self.buildReactor()
    441         reactor.connectTCP(host, port, Stop(reactor))
    442         self.runReactor(reactor)
     345            reactor, self.interface, serverAddress.port)
    443346
    444347
    445348    def test_addresses(self):
    class TCPClientTestsBuilder(ReactorBuilder, ConnectionTestsMixin): 
    448351        instances which give the dotted-quad string form of the local and
    449352        remote endpoints of the connection respectively.
    450353        """
    451         host, port = findFreePort()
    452         reactor = self.buildReactor()
    453 
    454         server = reactor.listenTCP(
    455             0, serverFactoryFor(Protocol), interface=host)
    456         serverAddress = server.getHost()
    457 
    458         addresses = {'host': None, 'peer': None}
    459         class CheckAddress(Protocol):
    460             def makeConnection(self, transport):
    461                 addresses['host'] = transport.getHost()
    462                 addresses['peer'] = transport.getPeer()
    463                 reactor.stop()
     354        TCPClientTestsMixin.test_addresses(self)
    464355
    465         clientFactory = Stop(reactor)
    466         clientFactory.protocol = CheckAddress
    467         reactor.connectTCP(
    468             'localhost', server.getHost().port, clientFactory,
    469             bindAddress=('127.0.0.1', port))
    470356
    471         reactor.installResolver(FakeResolver({'localhost': '127.0.0.1'}))
    472         self.runReactor(reactor)
    473 
    474         self.assertEqual(
    475             addresses['host'],
    476             IPv4Address('TCP', '127.0.0.1', port))
    477         self.assertEqual(
    478             addresses['peer'],
    479             IPv4Address('TCP', '127.0.0.1', serverAddress.port))
    480357
     358class TCP6ClientTestsBuilder(ReactorBuilder, ConnectionTestsMixin,
     359                             TCPClientTestsMixin):
     360    """
     361    Builder defining tests relating to L{IReactorTCP.connectTCP}.
     362    """
     363    def setUp(self):
     364        self.interface = '::1'
     365        self.port = 1234
     366        self.family = socket.AF_INET6
     367        self.addressClass = IPv6Address
    481368
    482     def test_connectEvent(self):
     369    def serverEndpoint(self, reactor):
    483370        """
    484         This test checks that we correctly get notifications event for a
    485         client. This ought to prevent a regression under Windows using the GTK2
    486         reactor. See #3925.
     371        Create a L{TCP6ServerEndpoint} listening on localhost on a
     372        TCP/IP-selected port.
    487373        """
    488         reactor = self.buildReactor()
    489 
    490         server = reactor.listenTCP(0, serverFactoryFor(Protocol))
    491         connected = []
    492 
    493         class CheckConnection(Protocol):
    494             def connectionMade(self):
    495                 connected.append(self)
    496                 reactor.stop()
    497 
    498         clientFactory = Stop(reactor)
    499         clientFactory.protocol = CheckConnection
    500         reactor.connectTCP(
    501             '127.0.0.1', server.getHost().port, clientFactory)
    502 
    503         reactor.run()
     374        return TCP6ServerEndpoint(reactor, 0, interface=self.interface)
    504375
    505         self.assertTrue(connected)
    506376
    507 
    508     def test_unregisterProducerAfterDisconnect(self):
    509         """
    510         If a producer is unregistered from a L{ITCPTransport} provider after the
    511         transport has been disconnected (by the peer) and after
    512         L{ITCPTransport.loseConnection} has been called, the transport is not
    513         re-added to the reactor as a writer as would be necessary if the
    514         transport were still connected.
     377    def clientEndpoint(self, reactor, serverAddress):
    515378        """
    516         reactor = self.buildReactor()
    517         port = reactor.listenTCP(0, serverFactoryFor(ClosingProtocol))
    518 
    519         finished = Deferred()
    520         finished.addErrback(log.err)
    521         finished.addCallback(lambda ign: reactor.stop())
    522 
    523         writing = []
    524 
    525         class ClientProtocol(Protocol):
    526             """
    527             Protocol to connect, register a producer, try to lose the
    528             connection, wait for the server to disconnect from us, and
    529             then unregister the producer.
    530             """
    531             def connectionMade(self):
    532                 log.msg("ClientProtocol.connectionMade")
    533                 self.transport.registerProducer(
    534                     _SimplePullProducer(self.transport), False)
    535                 self.transport.loseConnection()
     379        Create a L{TCP6ClientEndpoint} which will connect to localhost
     380        on the port given by C{serverAddress}.
    536381
    537             def connectionLost(self, reason):
    538                 log.msg("ClientProtocol.connectionLost")
    539                 self.unregister()
    540                 writing.append(self.transport in _getWriters(reactor))
    541                 finished.callback(None)
    542 
    543             def unregister(self):
    544                 log.msg("ClientProtocol unregister")
    545                 self.transport.unregisterProducer()
    546 
    547         clientFactory = ClientFactory()
    548         clientFactory.protocol = ClientProtocol
    549         reactor.connectTCP('127.0.0.1', port.getHost().port, clientFactory)
    550         self.runReactor(reactor)
    551         self.assertFalse(
    552             writing[0], "Transport was writing after unregisterProducer.")
     382        @type serverAddress: L{IPv4Address}
     383        """
     384        return TCP6ClientEndpoint(
     385            reactor, self.interface, serverAddress.port)
    553386
    554387
    555     def test_disconnectWhileProducing(self):
     388    def test_addresses(self):
    556389        """
    557         If L{ITCPTransport.loseConnection} is called while a producer
    558         is registered with the transport, the connection is closed
    559         after the producer is unregistered.
     390        A client's transport's C{getHost} and C{getPeer} return L{IPv6Address}
     391        instances which give the hexadecimal string form of the local and
     392        remote endpoints of the connection respectively.
    560393        """
    561         reactor = self.buildReactor()
    562 
    563         # For some reason, pyobject/pygtk will not deliver the close
    564         # notification that should happen after the unregisterProducer call in
    565         # this test.  The selectable is in the write notification set, but no
    566         # notification ever arrives.  Probably for the same reason #5233 led
    567         # win32eventreactor to be broken.
    568         skippedReactors = ["Glib2Reactor", "Gtk2Reactor"]
    569         reactorClassName = reactor.__class__.__name__
    570         if reactorClassName in skippedReactors and platform.isWindows():
    571             raise SkipTest(
    572                 "A pygobject/pygtk bug disables this functionality on Windows.")
    573 
    574         class Producer:
    575             def resumeProducing(self):
    576                 log.msg("Producer.resumeProducing")
    577 
    578         port = reactor.listenTCP(0, serverFactoryFor(Protocol))
    579 
    580         finished = Deferred()
    581         finished.addErrback(log.err)
    582         finished.addCallback(lambda ign: reactor.stop())
    583 
    584         class ClientProtocol(Protocol):
    585             """
    586             Protocol to connect, register a producer, try to lose the
    587             connection, unregister the producer, and wait for the connection to
    588             actually be lost.
    589             """
    590             def connectionMade(self):
    591                 log.msg("ClientProtocol.connectionMade")
    592                 self.transport.registerProducer(Producer(), False)
    593                 self.transport.loseConnection()
    594                 # Let the reactor tick over, in case synchronously calling
    595                 # loseConnection and then unregisterProducer is the same as
    596                 # synchronously calling unregisterProducer and then
    597                 # loseConnection (as it is in several reactors).
    598                 reactor.callLater(0, reactor.callLater, 0, self.unregister)
    599 
    600             def unregister(self):
    601                 log.msg("ClientProtocol unregister")
    602                 self.transport.unregisterProducer()
    603                 # This should all be pretty quick.  Fail the test
    604                 # if we don't get a connectionLost event really
    605                 # soon.
    606                 reactor.callLater(
    607                     1.0, finished.errback,
    608                     Failure(Exception("Connection was not lost")))
    609 
    610             def connectionLost(self, reason):
    611                 log.msg("ClientProtocol.connectionLost")
    612                 finished.callback(None)
    613 
    614         clientFactory = ClientFactory()
    615         clientFactory.protocol = ClientProtocol
    616         reactor.connectTCP('127.0.0.1', port.getHost().port, clientFactory)
    617         self.runReactor(reactor)
    618         # If the test failed, we logged an error already and trial
    619         # will catch it.
     394        TCPClientTestsMixin.test_addresses(self)
    620395
    621396
    622397
    class TCPPortTestsBuilder(ReactorBuilder, ObjectModelIntegrationMixin, 
    699474        """
    700475        Get the message expected to be logged when a TCP port starts listening.
    701476        """
    702         return "%s starting on %d" % (factory, port.getHost().port)
     477        return "%s starting on %d (<class 'twisted.internet.address.IPv4Address'>)" % (
     478            factory, port.getHost().port)
    703479
    704480
    705481    def getExpectedConnectionLostLogMsg(self, port):
    class WriteSequenceTests(ReactorBuilder): 
    13851161        self.assertEquals(producer.actions, ["resume", "resume"])
    13861162
    13871163
    1388 globals().update(TCPClientTestsBuilder.makeTestCaseClasses())
     1164globals().update(TCP4ClientTestsBuilder.makeTestCaseClasses())
     1165globals().update(TCP6ClientTestsBuilder.makeTestCaseClasses())
    13891166globals().update(TCPPortTestsBuilder.makeTestCaseClasses())
    13901167globals().update(TCPConnectionTestsBuilder.makeTestCaseClasses())
    13911168globals().update(WriteSequenceTests.makeTestCaseClasses())
  • twisted/internet/test/test_tls.py

    diff --git twisted/internet/test/test_tls.py twisted/internet/test/test_tls.py
    index f77af0d..a8e3e97 100644
    class TLSPortTestsBuilder(TLSMixin, ContextGeneratingMixin, 
    273273        """
    274274        Get the message expected to be logged when a TLS port starts listening.
    275275        """
    276         return "%s (TLS) starting on %d" % (factory, port.getHost().port)
     276        return "%s (TLS) starting on %d (<class 'twisted.internet.address.IPv4Address'>)" % (factory, port.getHost().port)
    277277
    278278
    279279    def getExpectedConnectionLostLogMsg(self, port):
  • twisted/test/test_tcp.py

    diff --git twisted/test/test_tcp.py twisted/test/test_tcp.py
    index ecc9fcf..9ebe967 100644
    class FactoryTestCase(unittest.TestCase): 
    678678
    679679
    680680class ConnectorTestCase(unittest.TestCase):
     681    """
     682    TCP Connector Test cases
     683
     684    @type interface: str
     685    @ivar interface: A string representing an IPv4 literal used for this test.
     686    """
     687
     688    interface="127.0.0.1"
    681689
    682690    def test_connectorIdentity(self):
    683691        """
    class ConnectorTestCase(unittest.TestCase): 
    688696        C{clientConnectionLost} method.
    689697        """
    690698        serverFactory = ClosingFactory()
    691         tcpPort = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
     699        tcpPort = reactor.listenTCP(0, serverFactory, interface=self.interface)
    692700        serverFactory.port = tcpPort
    693701        self.addCleanup(serverFactory.cleanUp)
    694702        portNumber = tcpPort.getHost().port
    class ConnectorTestCase(unittest.TestCase): 
    702710                                       seenFailures.append(reason)))
    703711        clientFactory.startedConnecting = seenConnectors.append
    704712
    705         connector = reactor.connectTCP("127.0.0.1", portNumber, clientFactory)
     713        connector = reactor.connectTCP(self.interface, portNumber, clientFactory)
    706714        self.assertTrue(interfaces.IConnector.providedBy(connector))
    707715        dest = connector.getDestination()
    708716        self.assertEqual(dest.type, "TCP")
    709         self.assertEqual(dest.host, "127.0.0.1")
     717        self.assertEqual(dest.host, self.interface)
    710718        self.assertEqual(dest.port, portNumber)
    711719
    712720        d = loopUntil(lambda: clientFactory.stopped)
    class ConnectorTestCase(unittest.TestCase): 
    724732        L{error.UserError} as the reason.
    725733        """
    726734        serverFactory = MyServerFactory()
    727         tcpPort = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
     735        tcpPort = reactor.listenTCP(0, serverFactory, interface=self.interface)
    728736        self.addCleanup(tcpPort.stopListening)
    729737        portNumber = tcpPort.getHost().port
    730738
    class ConnectorTestCase(unittest.TestCase): 
    733741
    734742        clientFactory = ClientStartStopFactory()
    735743        clientFactory.startedConnecting = startedConnecting
    736         reactor.connectTCP("127.0.0.1", portNumber, clientFactory)
     744        reactor.connectTCP(self.interface, portNumber, clientFactory)
    737745
    738746        d = loopUntil(lambda: clientFactory.stopped)
    739747        def check(ignored):
    class ConnectorTestCase(unittest.TestCase): 
    748756        a new connection attempt to be made.
    749757        """
    750758        serverFactory = ClosingFactory()
    751         tcpPort = reactor.listenTCP(0, serverFactory, interface="127.0.0.1")
     759        tcpPort = reactor.listenTCP(0, serverFactory, interface=self.interface)
    752760        serverFactory.port = tcpPort
    753761        self.addCleanup(serverFactory.cleanUp)
    754762        portNumber = tcpPort.getHost().port
    class ConnectorTestCase(unittest.TestCase): 
    758766        def clientConnectionLost(connector, reason):
    759767            connector.connect()
    760768        clientFactory.clientConnectionLost = clientConnectionLost
    761         reactor.connectTCP("127.0.0.1", portNumber, clientFactory)
     769        reactor.connectTCP(self.interface, portNumber, clientFactory)
    762770
    763771        d = loopUntil(lambda: clientFactory.failed)
    764772        def reconnectFailed(ignored):
    class ConnectorTestCase(unittest.TestCase): 
    770778
    771779
    772780
     781class Connector6TestCase(ConnectorTestCase):
     782    """
     783    TCPv6 Connector Test cases.
     784
     785    @type interface: str
     786    @ivar interface: A string representing an IPv6 literal used for this test.
     787    """
     788
     789    interface="::1"
     790
     791
     792
    773793class CannotBindTestCase(unittest.TestCase):
    774794    """
    775795    Tests for correct behavior when a reactor cannot bind to the required TCP