Ticket #4362: getaddrinfo-4362-3-1.patch

File getaddrinfo-4362-3-1.patch, 14.4 KB (added by rwall, 3 years ago)

Merged forward and resolved conflicts with trunk - contains broken tests

  • twisted/internet/base.py

    === modified file 'twisted/internet/base.py'
     
    1717
    1818import traceback
    1919
     20from twisted.python.util import FancyEqMixin
    2021from twisted.python.compat import set
    2122from twisted.internet.interfaces import IReactorCore, IReactorTime, IReactorThreads
    2223from twisted.internet.interfaces import IResolverSimple, IReactorPluggableResolver
    2324from twisted.internet.interfaces import IConnector, IDelayedCall
     25from twisted.internet.interfaces import INameResolver
    2426from twisted.internet import fdesc, main, error, abstract, defer, threads
    2527from twisted.python import log, failure, _reflectpy3 as reflect
    2628from twisted.python.runtime import seconds as runtimeSeconds, platform
     
    209211        return "".join(L)
    210212
    211213
     214@implementer(INameResolver)
     215class _ResolverComplexifier(object):
     216    def __init__(self, resolver):
     217        self._resolver = resolver
     218
     219
     220    def getAddressInformation(self, name, service, *args):
     221        d = self._resolver.getHostByName(name)
     222        def cbResolved(address):
     223            family = socket.getaddrinfo(address, 0)[0][0]
     224            return [
     225                AddressInformation(
     226                    family,
     227                    socket.SOCK_STREAM,
     228                    socket.IPPROTO_TCP,
     229                    "",
     230                    (address, service))]
     231        d.addCallback(cbResolved)
     232        return d
     233
     234
    212235
    213236@implementer(IResolverSimple)
    214237class ThreadedResolver(object):
     
    278301
    279302
    280303
     304class AddressInformation(object, FancyEqMixin):
     305    compareAttributes = ('family', 'type', 'protocol', 'canonicalName', 'address')
     306
     307    def __init__(self, family, type, protocol, canonicalName, address):
     308        self.family = family
     309        self.type = type
     310        self.protocol = protocol
     311        self.canonicalName = canonicalName
     312        self.address = address
     313
     314
     315
    281316@implementer(IResolverSimple)
    282317class BlockingResolver:
    283318
     
    507542            reflect.qual(self.__class__) + " did not implement installWaker")
    508543
    509544    def installResolver(self, resolver):
    510         assert IResolverSimple.providedBy(resolver)
     545        resolver = INameResolver(resolver)
    511546        oldResolver = self.resolver
    512547        self.resolver = resolver
    513548        return oldResolver
     
    567602            return defer.succeed('0.0.0.0')
    568603        if abstract.isIPAddress(name):
    569604            return defer.succeed(name)
    570         return self.resolver.getHostByName(name, timeout)
     605        d = self.resolver.getAddressInformation(name, 0)
     606        def cbGotInfo(addresses):
     607            for info in addresses:
     608                if info.family == socket.AF_INET:
     609                    return info.address[0]
     610            # XXX Test me
     611        d.addCallback(cbGotInfo)
     612        return d
     613
    571614
    572615    # Installation.
    573616
  • twisted/internet/interfaces.py

    === modified file 'twisted/internet/interfaces.py'
     
    5454        """
    5555
    5656
     57
     58class INameResolver(Interface):
     59    """
     60    XXX Write me.
     61
     62    RFC 3484.
     63    """
     64    def getAddressInformation(name, service, family=None, socktype=None,
     65                              proto=None, flags=None):
     66        """
     67        Get the address information associated with the given name.
     68
     69        @param name: A hostname to resolve.
     70        @type name: C{str}
     71
     72        @param service: A port number or the name of the service for which to find address
     73            information.  For example, C{22} or C{"ssh"}.
     74        @type service: C{int} or C{str}
     75
     76        @param family: If specified, limit results to addresses from this family.  Must be one
     77            of the address family constants from the socket module.  For example,
     78            L{socket.AF_INET}.
     79
     80        @param socktype: If specified, limit results to addresses for this socket type.  Must be
     81            one of the socket type constants from the socket module.  For example,
     82            L{socket.SOCK_STREAM}.
     83
     84        @param proto: If specified, limit results to addresses for this socket protocol.  Must
     85            be one of the protocol constants from the socket module.  For example,
     86            L{socket.IPPROTO_TCP}.
     87
     88        @param flags: A bitvector specifying zero or more of the following::
     89            - Yea right.  Go read the `getaddrinfo(3)` man page.
     90
     91        @raise ValueError: If one of the specified flags is not supported by the
     92            implementation.  All flags are optional.
     93
     94        @return: A L{Deferred} which will fire when the resolution completes.  If resolution is
     95            successful, the result will be a list of objects with the following attributes:
     96
     97            - C{family}: the family of this address
     98            - C{type}: the type of this address
     99            - C{protocol}: the protocol of this address
     100            - C{canonicalName}: the canonical name associated with this address, or an empty
     101                                string.
     102            - C{address}: The actual address itself, including a port number.  This is suitable
     103                          to be passed directly to L{socket.socket.connect}.
     104        """
     105
     106
    57107class IResolverSimple(Interface):
    58108
    59109    def getHostByName(name, timeout = (1, 3, 11, 45)):
  • twisted/internet/test/test_base.py

    === modified file 'twisted/internet/test/test_base.py'
     
    1212    from queue import Queue
    1313
    1414from zope.interface import implementer
     15from zope.interface.verify import verifyClass
    1516
    1617from twisted.python.threadpool import ThreadPool
    1718from twisted.internet.interfaces import IReactorTime, IReactorThreads
     19from twisted.internet.interfaces import INameResolver
    1820from twisted.internet.error import DNSLookupError
    19 from twisted.internet.base import ThreadedResolver, DelayedCall
     21from twisted.internet.base import (
     22    ThreadedNameResolver, ThreadedResolver, AddressInformation,
     23    _ResolverComplexifier, DelayedCall)
    2024from twisted.internet.task import Clock
    2125from twisted.trial.unittest import TestCase
    2226
     27from twisted.internet.test.test_tcp import FakeResolver
     28
    2329
    2430@implementer(IReactorTime, IReactorThreads)
    2531class FakeReactor(object):
     
    5359
    5460
    5561
     62class NameResolverAdapterTests(TestCase):
     63    """
     64    L{_ResolverComplexifier} adapters an L{IResolverSimple} provider
     65    to L{INameResolver}.
     66    """
     67    def test_interface(self):
     68        """
     69        L{_ResolverComplexifier} implements L{INameResolver}.
     70        """
     71        self.assertTrue(verifyClass(INameResolver, _ResolverComplexifier))
     72
     73
     74    def _successTest(self, address, family):
     75        simple = FakeResolver({'example.com': address})
     76        resolver = _ResolverComplexifier(simple)
     77        d = resolver.getAddressInformation('example.com', 1234)
     78        d.addCallback(
     79            self.assertEquals, [
     80                AddressInformation(
     81                    family,
     82                    socket.SOCK_STREAM,
     83                    socket.IPPROTO_TCP,
     84                    "",
     85                    (address, 1234))])
     86        return d
     87
     88
     89    def test_ipv4Success(self):
     90        """
     91        L{_ResolverComplexifier} calls the wrapped object's
     92        C{getHostByName} method and returns a L{Deferred} which fires
     93        with a list of one element containing an AF_INET element with
     94        the IPv4 address which C{getHostByName}'s L{Deferred} fired
     95        with.
     96        """
     97        return self._successTest('192.168.1.12', socket.AF_INET)
     98
     99
     100    def test_ipv6Success(self):
     101        """
     102        L{_ResolverComplexifier} calls the wrapped object's
     103        C{getHostByName} method and returns a L{Deferred} which fires
     104        with a list of one element containing an AF_INET6 element with
     105        the IPv6 address which C{getHostByName}'s L{Deferred} fired
     106        with.
     107        """
     108        return self._successTest('::1', socket.AF_INET6)
     109
     110
     111    def test_failure(self):
     112        """
     113        The L{Deferred} L{_ResolverComplexifier.getAddressInformation}
     114        returns fails if the wrapped resolver's C{getHostByName}
     115        L{Deferred} fails.
     116        """
     117        error = DNSLookupError("Problems abound")
     118        simple = FakeResolver({'example.com': error})
     119        resolver = _ResolverComplexifier(simple)
     120        d = resolver.getAddressInformation('example.com', 1234)
     121        return self.assertFailure(d, DNSLookupError)
     122
     123
     124
     125class ThreadedNameResolverTests(TestCase):
     126    """
     127    Tests for L{ThreadedNameResolver}.
     128    """
     129    def test_interface(self):
     130        """
     131        L{ThreadedNameResolver} implements L{INameResolver}.
     132        """
     133        self.assertTrue(verifyClass(INameResolver, ThreadedNameResolver))
     134
     135
     136    def test_success(self):
     137        """
     138        If the underlying C{getaddrinfo} library call completes
     139        successfully and returns results, the L{Deferred} returned by
     140        L{ThreadedNameResolver.getAddressInformation} fires with a
     141        list of L{AddressInformation} instances representing those
     142        results.
     143        """
     144
     145
    56146class ThreadedResolverTests(TestCase):
    57147    """
    58148    Tests for L{ThreadedResolver}.
  • twisted/internet/test/test_core.py

    === modified file 'twisted/internet/test/test_core.py'
     
    1212import signal
    1313import time
    1414import inspect
    15 
     15import socket
     16
     17from zope.interface import implementer
     18
     19from twisted.internet.interfaces import INameResolver
    1620from twisted.internet.abstract import FileDescriptor
    1721from twisted.internet.error import ReactorAlreadyRunning, ReactorNotRestartable
    18 from twisted.internet.defer import Deferred
     22from twisted.internet.defer import Deferred, succeed
     23from twisted.internet.base import AddressInformation
    1924from twisted.internet.test.reactormixins import ReactorBuilder
    2025
    2126
     27
    2228class ObjectModelIntegrationMixin(object):
    2329    """
    2430    Helpers for tests about the object model of reactor-related objects.
     
    4147
    4248
    4349
     50@implementer(INameResolver)
     51class MemoryNameResolver(object):
     52    def __init__(self, names):
     53        self._names = names
     54
     55    def getAddressInformation(self, name, service, family=None, type=None,
     56                              protocol=None, flags=None):
     57        return succeed([
     58                address
     59                for address
     60                in self._names[name, service]
     61                if family is None or family == address.family
     62                and type is None or type == address.type
     63                and protocol is None or protocol == address.protocol])
     64
     65
     66
    4467class ObjectModelIntegrationTest(ReactorBuilder, ObjectModelIntegrationMixin):
    4568    """
    4669    Test details of object model integration against all reactors.
     
    328351        self.assertEqual(events, ['tested'])
    329352
    330353
     354    def test_resolve(self):
     355        """
     356        C{reactor.resolve(name)} calls the C{getAddressInformation}
     357        method of the installed resolver and returns a L{Deferred}
     358        which fires with the first C{AF_INET} family element from the
     359        result of C{getAddressInformation}.
     360        """
     361        resolver = MemoryNameResolver({
     362                ('example.com', 0): [
     363                    AddressInformation(
     364                        socket.AF_INET6,
     365                        socket.SOCK_STREAM,
     366                        socket.IPPROTO_TCP,
     367                        "",
     368                        ("::1", 0)),
     369                    AddressInformation(
     370                        socket.AF_INET,
     371                        socket.SOCK_STREAM,
     372                        socket.IPPROTO_TCP,
     373                        "",
     374                        ("127.0.0.1", 22))]})
     375
     376        reactor = self.buildReactor()
     377        reactor.installResolver(resolver)
     378        d = reactor.resolve("example.com")
     379        d.addCallback(self.assertEquals, "127.0.0.1")
     380        return d
     381
    331382
    332383globals().update(SystemEventTestsBuilder.makeTestCaseClasses())
    333384globals().update(ObjectModelIntegrationTest.makeTestCaseClasses())
  • twisted/internet/test/test_tcp.py

    === modified file 'twisted/internet/test/test_tcp.py'
     
    2525    ConnectionLost, UserError, ConnectionRefusedError, ConnectionDone,
    2626    ConnectionAborted)
    2727from twisted.internet.interfaces import (
    28     ILoggingContext, IConnector, IReactorFDSet, IReactorSocket, IReactorTCP)
     28    ILoggingContext, IConnector, IReactorFDSet, IReactorSocket, IReactorTCP, IResolverSimple)
    2929from twisted.internet.address import IPv4Address, IPv6Address
    3030from twisted.internet.defer import (
    31     Deferred, DeferredList, maybeDeferred, gatherResults)
     31    Deferred, DeferredList, maybeDeferred, gatherResults, succeed, fail)
    3232from twisted.internet.endpoints import TCP4ServerEndpoint, TCP4ClientEndpoint
     33from twisted.internet.error import DNSLookupError
    3334from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol
    3435from twisted.internet.interfaces import (
    3536    IPushProducer, IPullProducer, IHalfCloseableProtocol)
     
    108109    client.connect(address)
    109110
    110111
     112class Stop(ClientFactory):
     113    """
     114    A client factory which stops a reactor when a connection attempt fails.
     115    """
     116    def __init__(self, reactor):
     117        self.reactor = reactor
     118
     119
     120    def clientConnectionFailed(self, connector, reason):
     121        self.reactor.stop()
     122
     123
     124@implementer(IResolverSimple)
     125class FakeResolver:
     126    """
     127    A resolver implementation based on a C{dict} mapping names to addresses.
     128    """
     129
     130    def __init__(self, names):
     131        self.names = names
     132
     133
     134    def getHostByName(self, name, timeout=None):
     135        try:
     136            return succeed(self.names[name])
     137        except KeyError:
     138            return fail(DNSLookupError("FakeResolver couldn't find " + name))
     139
     140
     141
     142class TCPClientTestsBuilder(ReactorBuilder):
     143    """
     144    Builder defining tests relating to L{IReactorTCP.connectTCP}.
     145    """
     146    def _freePort(self, interface='127.0.0.1'):
     147        probe = socket.socket()
     148        try:
     149            probe.bind((interface, 0))
     150            return probe.getsockname()
     151        finally:
     152            probe.close()
     153
     154    def test_clientConnectionFailedStopsReactor(self):
     155        """
     156        The reactor can be stopped by a client factory's
     157        C{clientConnectionFailed} method.
     158        """
     159        host, port = self._freePort()
     160        reactor = self.buildReactor()
     161        reactor.connectTCP(host, port, Stop(reactor))
     162
    111163
    112164class FakeSocket(object):
    113165    """