123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Tests for implementations of L{IHostnameResolver} and their interactions with
- reactor implementations.
- """
- from __future__ import division, absolute_import
- __metaclass__ = type
- from collections import defaultdict
- from socket import (
- getaddrinfo, gaierror, EAI_NONAME, AF_INET, AF_INET6, AF_UNSPEC,
- SOCK_STREAM, SOCK_DGRAM, IPPROTO_TCP
- )
- from threading import local, Lock
- from zope.interface import implementer
- from zope.interface.verify import verifyObject
- from twisted.internet.interfaces import (
- IResolutionReceiver, IResolverSimple, IReactorPluggableNameResolver,
- IHostnameResolver,
- )
- from twisted.trial.unittest import (
- SynchronousTestCase as UnitTest
- )
- from twisted.python.threadpool import ThreadPool
- from twisted._threads import createMemoryWorker, Team, LockWorker
- from twisted.internet.address import IPv4Address, IPv6Address
- from twisted.internet._resolver import (
- GAIResolver, SimpleResolverComplexifier, ComplexResolverSimplifier
- )
- from twisted.internet.defer import Deferred
- from twisted.internet.error import DNSLookupError
- from twisted.internet.base import ReactorBase
- class DeterministicThreadPool(ThreadPool, object):
- """
- Create a deterministic L{ThreadPool} object.
- """
- def __init__(self, team):
- """
- Create a L{DeterministicThreadPool} from a L{Team}.
- """
- self.min = 1
- self.max = 1
- self.name = None
- self.threads = []
- self._team = team
- def deterministicPool():
- """
- Create a deterministic threadpool.
- @return: 2-tuple of L{ThreadPool}, 0-argument C{work} callable; when
- C{work} is called, do the work.
- """
- worker, doer = createMemoryWorker()
- return (
- DeterministicThreadPool(Team(LockWorker(Lock(), local()),
- (lambda: worker), lambda: None)),
- doer
- )
- def deterministicReactorThreads():
- """
- Create a deterministic L{IReactorThreads}
- @return: a 2-tuple consisting of an L{IReactorThreads}-like object and a
- 0-argument callable that will perform one unit of work invoked via that
- object's C{callFromThread} method.
- """
- worker, doer = createMemoryWorker()
- class CFT(object):
- def callFromThread(self, f, *a, **k):
- worker.do(lambda: f(*a, **k))
- return CFT(), doer
- class FakeAddrInfoGetter(object):
- """
- Test object implementing getaddrinfo.
- """
- def __init__(self):
- """
- Create a L{FakeAddrInfoGetter}.
- """
- self.calls = []
- self.results = defaultdict(list)
- def getaddrinfo(self, host, port, family=0, socktype=0, proto=0, flags=0):
- """
- Mock for L{socket.getaddrinfo}.
- @param host: see L{socket.getaddrinfo}
- @param port: see L{socket.getaddrinfo}
- @param family: see L{socket.getaddrinfo}
- @param socktype: see L{socket.getaddrinfo}
- @param proto: see L{socket.getaddrinfo}
- @param flags: see L{socket.getaddrinfo}
- @return: L{socket.getaddrinfo}
- """
- self.calls.append((host, port, family, socktype, proto, flags))
- results = self.results[host]
- if results:
- return results
- else:
- raise gaierror(EAI_NONAME,
- 'nodename nor servname provided, or not known')
- def addResultForHost(self, host, sockaddr, family=AF_INET,
- socktype=SOCK_STREAM, proto=IPPROTO_TCP,
- canonname=b""):
- """
- Add a result for a given hostname. When this hostname is resolved, the
- result will be a L{list} of all results C{addResultForHost} has been
- called with using that hostname so far.
- @param host: The hostname to give this result for. This will be the
- next result from L{FakeAddrInfoGetter.getaddrinfo} when passed this
- host.
- @type canonname: native L{str}
- @param sockaddr: The resulting socket address; should be a 2-tuple for
- IPv4 or a 4-tuple for IPv6.
- @param family: An C{AF_*} constant that will be returned from
- C{getaddrinfo}.
- @param socktype: A C{SOCK_*} constant that will be returned from
- C{getaddrinfo}.
- @param proto: An C{IPPROTO_*} constant that will be returned from
- C{getaddrinfo}.
- @param canonname: A canonical name that will be returned from
- C{getaddrinfo}.
- @type canonname: native L{str}
- """
- self.results[host].append(
- (family, socktype, proto, canonname, sockaddr)
- )
- @implementer(IResolutionReceiver)
- class ResultHolder(object):
- """
- A resolution receiver which holds onto the results it received.
- """
- _started = False
- _ended = False
- def __init__(self, testCase):
- """
- Create a L{ResultHolder} with a L{UnitTest}.
- """
- self._testCase = testCase
- def resolutionBegan(self, hostResolution):
- """
- Hostname resolution began.
- @param hostResolution: see L{IResolutionReceiver}
- """
- self._started = True
- self._resolution = hostResolution
- self._addresses = []
- def addressResolved(self, address):
- """
- An address was resolved.
- @param address: see L{IResolutionReceiver}
- """
- self._addresses.append(address)
- def resolutionComplete(self):
- """
- Hostname resolution is complete.
- """
- self._ended = True
- class HelperTests(UnitTest):
- """
- Tests for error cases of helpers used in this module.
- """
- def test_logErrorsInThreads(self):
- """
- L{DeterministicThreadPool} will log any exceptions that its "thread"
- workers encounter.
- """
- self.pool, self.doThreadWork = deterministicPool()
- def divideByZero():
- return 1 / 0
- self.pool.callInThread(divideByZero)
- self.doThreadWork()
- self.assertEqual(len(self.flushLoggedErrors(ZeroDivisionError)), 1)
- class HostnameResolutionTests(UnitTest):
- """
- Tests for hostname resolution.
- """
- def setUp(self):
- """
- Set up a L{GAIResolver}.
- """
- self.pool, self.doThreadWork = deterministicPool()
- self.reactor, self.doReactorWork = deterministicReactorThreads()
- self.getter = FakeAddrInfoGetter()
- self.resolver = GAIResolver(self.reactor, lambda: self.pool,
- self.getter.getaddrinfo)
- def test_resolveOneHost(self):
- """
- Resolving an individual hostname that results in one address from
- getaddrinfo results in a single call each to C{resolutionBegan},
- C{addressResolved}, and C{resolutionComplete}.
- """
- receiver = ResultHolder(self)
- self.getter.addResultForHost(u"sample.example.com", ("4.3.2.1", 0))
- resolution = self.resolver.resolveHostName(receiver,
- u"sample.example.com")
- self.assertIs(receiver._resolution, resolution)
- self.assertEqual(receiver._started, True)
- self.assertEqual(receiver._ended, False)
- self.doThreadWork()
- self.doReactorWork()
- self.assertEqual(receiver._ended, True)
- self.assertEqual(receiver._addresses,
- [IPv4Address('TCP', '4.3.2.1', 0)])
- def test_resolveOneIPv6Host(self):
- """
- Resolving an individual hostname that results in one address from
- getaddrinfo results in a single call each to C{resolutionBegan},
- C{addressResolved}, and C{resolutionComplete}; C{addressResolved} will
- receive an L{IPv6Address}.
- """
- receiver = ResultHolder(self)
- flowInfo = 1
- scopeID = 2
- self.getter.addResultForHost(u"sample.example.com",
- ("::1", 0, flowInfo, scopeID),
- family=AF_INET6)
- resolution = self.resolver.resolveHostName(receiver,
- u"sample.example.com")
- self.assertIs(receiver._resolution, resolution)
- self.assertEqual(receiver._started, True)
- self.assertEqual(receiver._ended, False)
- self.doThreadWork()
- self.doReactorWork()
- self.assertEqual(receiver._ended, True)
- self.assertEqual(receiver._addresses,
- [IPv6Address('TCP', '::1', 0, flowInfo, scopeID)])
- def test_gaierror(self):
- """
- Resolving a hostname that results in C{getaddrinfo} raising a
- L{gaierror} will result in the L{IResolutionReceiver} receiving a call
- to C{resolutionComplete} with no C{addressResolved} calls in between;
- no failure is logged.
- """
- receiver = ResultHolder(self)
- resolution = self.resolver.resolveHostName(receiver,
- u"sample.example.com")
- self.assertIs(receiver._resolution, resolution)
- self.doThreadWork()
- self.doReactorWork()
- self.assertEqual(receiver._started, True)
- self.assertEqual(receiver._ended, True)
- self.assertEqual(receiver._addresses, [])
- def _resolveOnlyTest(self, addrTypes, expectedAF):
- """
- Verify that the given set of address types results in the given C{AF_}
- constant being passed to C{getaddrinfo}.
- @param addrTypes: iterable of L{IAddress} implementers
- @param expectedAF: an C{AF_*} constant
- """
- receiver = ResultHolder(self)
- resolution = self.resolver.resolveHostName(
- receiver, u"sample.example.com", addressTypes=addrTypes
- )
- self.assertIs(receiver._resolution, resolution)
- self.doThreadWork()
- self.doReactorWork()
- host, port, family, socktype, proto, flags = self.getter.calls[0]
- self.assertEqual(family, expectedAF)
- def test_resolveOnlyIPv4(self):
- """
- When passed an C{addressTypes} parameter containing only
- L{IPv4Address}, L{GAIResolver} will pass C{AF_INET} to C{getaddrinfo}.
- """
- self._resolveOnlyTest([IPv4Address], AF_INET)
- def test_resolveOnlyIPv6(self):
- """
- When passed an C{addressTypes} parameter containing only
- L{IPv6Address}, L{GAIResolver} will pass C{AF_INET6} to C{getaddrinfo}.
- """
- self._resolveOnlyTest([IPv6Address], AF_INET6)
- def test_resolveBoth(self):
- """
- When passed an C{addressTypes} parameter containing both L{IPv4Address}
- and L{IPv6Address} (or the default of C{None}, which carries the same
- meaning), L{GAIResolver} will pass C{AF_UNSPEC} to C{getaddrinfo}.
- """
- self._resolveOnlyTest([IPv4Address, IPv6Address], AF_UNSPEC)
- self._resolveOnlyTest(None, AF_UNSPEC)
- def test_transportSemanticsToSocketType(self):
- """
- When passed a C{transportSemantics} paramter, C{'TCP'} (the value
- present in L{IPv4Address.type} to indicate a stream transport) maps to
- C{SOCK_STREAM} and C{'UDP'} maps to C{SOCK_DGRAM}.
- """
- receiver = ResultHolder(self)
- self.resolver.resolveHostName(receiver, u"example.com",
- transportSemantics='TCP')
- receiver2 = ResultHolder(self)
- self.resolver.resolveHostName(receiver2, u"example.com",
- transportSemantics='UDP')
- self.doThreadWork()
- self.doReactorWork()
- self.doThreadWork()
- self.doReactorWork()
- host, port, family, socktypeT, proto, flags = self.getter.calls[0]
- host, port, family, socktypeU, proto, flags = self.getter.calls[1]
- self.assertEqual(socktypeT, SOCK_STREAM)
- self.assertEqual(socktypeU, SOCK_DGRAM)
- def test_socketTypeToAddressType(self):
- """
- When L{GAIResolver} receives a C{SOCK_DGRAM} result from
- C{getaddrinfo}, it returns a C{'TCP'} L{IPv4Address} or L{IPv6Address};
- if it receives C{SOCK_STREAM} then it returns a C{'UDP'} type of same.
- """
- receiver = ResultHolder(self)
- flowInfo = 1
- scopeID = 2
- for socktype in SOCK_STREAM, SOCK_DGRAM:
- self.getter.addResultForHost(
- "example.com", ("::1", 0, flowInfo, scopeID), family=AF_INET6,
- socktype=socktype
- )
- self.getter.addResultForHost(
- "example.com", ("127.0.0.3", 0), family=AF_INET,
- socktype=socktype
- )
- self.resolver.resolveHostName(receiver, u"example.com")
- self.doThreadWork()
- self.doReactorWork()
- stream4, stream6, dgram4, dgram6 = receiver._addresses
- self.assertEqual(stream4.type, 'TCP')
- self.assertEqual(stream6.type, 'TCP')
- self.assertEqual(dgram4.type, 'UDP')
- self.assertEqual(dgram6.type, 'UDP')
- @implementer(IResolverSimple)
- class SillyResolverSimple(object):
- """
- Trivial implementation of L{IResolverSimple}
- """
- def __init__(self):
- """
- Create a L{SillyResolverSimple} with a queue of requests it is working
- on.
- """
- self._requests = []
- def getHostByName(self, name, timeout=()):
- """
- Implement L{IResolverSimple.getHostByName}.
- @param name: see L{IResolverSimple.getHostByName}.
- @param timeout: see L{IResolverSimple.getHostByName}.
- @return: see L{IResolverSimple.getHostByName}.
- """
- self._requests.append(Deferred())
- return self._requests[-1]
- class LegacyCompatibilityTests(UnitTest, object):
- """
- Older applications may supply an object to the reactor via
- C{installResolver} that only provides L{IResolverSimple}.
- L{SimpleResolverComplexifier} is a wrapper for an L{IResolverSimple}.
- """
- def test_success(self):
- """
- L{SimpleResolverComplexifier} translates C{resolveHostName} into
- L{IResolutionReceiver.addressResolved}.
- """
- simple = SillyResolverSimple()
- complex = SimpleResolverComplexifier(simple)
- receiver = ResultHolder(self)
- self.assertEqual(receiver._started, False)
- complex.resolveHostName(receiver, u"example.com")
- self.assertEqual(receiver._started, True)
- self.assertEqual(receiver._ended, False)
- self.assertEqual(receiver._addresses, [])
- simple._requests[0].callback("192.168.1.1")
- self.assertEqual(receiver._addresses,
- [IPv4Address('TCP', '192.168.1.1', 0)])
- self.assertEqual(receiver._ended, True)
- def test_failure(self):
- """
- L{SimpleResolverComplexifier} translates a known error result from
- L{IResolverSimple.resolveHostName} into an empty result.
- """
- simple = SillyResolverSimple()
- complex = SimpleResolverComplexifier(simple)
- receiver = ResultHolder(self)
- self.assertEqual(receiver._started, False)
- complex.resolveHostName(receiver, u"example.com")
- self.assertEqual(receiver._started, True)
- self.assertEqual(receiver._ended, False)
- self.assertEqual(receiver._addresses, [])
- simple._requests[0].errback(DNSLookupError("nope"))
- self.assertEqual(receiver._ended, True)
- self.assertEqual(receiver._addresses, [])
- def test_error(self):
- """
- L{SimpleResolverComplexifier} translates an unknown error result from
- L{IResolverSimple.resolveHostName} into an empty result and a logged
- error.
- """
- simple = SillyResolverSimple()
- complex = SimpleResolverComplexifier(simple)
- receiver = ResultHolder(self)
- self.assertEqual(receiver._started, False)
- complex.resolveHostName(receiver, u"example.com")
- self.assertEqual(receiver._started, True)
- self.assertEqual(receiver._ended, False)
- self.assertEqual(receiver._addresses, [])
- simple._requests[0].errback(ZeroDivisionError("zow"))
- self.assertEqual(len(self.flushLoggedErrors(ZeroDivisionError)), 1)
- self.assertEqual(receiver._ended, True)
- self.assertEqual(receiver._addresses, [])
- def test_simplifier(self):
- """
- L{ComplexResolverSimplifier} translates an L{IHostnameResolver} into an
- L{IResolverSimple} for applications that still expect the old
- interfaces to be in place.
- """
- self.pool, self.doThreadWork = deterministicPool()
- self.reactor, self.doReactorWork = deterministicReactorThreads()
- self.getter = FakeAddrInfoGetter()
- self.resolver = GAIResolver(self.reactor, lambda: self.pool,
- self.getter.getaddrinfo)
- simpleResolver = ComplexResolverSimplifier(self.resolver)
- self.getter.addResultForHost('example.com', ('192.168.3.4', 4321))
- success = simpleResolver.getHostByName('example.com')
- failure = simpleResolver.getHostByName('nx.example.com')
- self.doThreadWork()
- self.doReactorWork()
- self.doThreadWork()
- self.doReactorWork()
- self.assertEqual(self.failureResultOf(failure).type, DNSLookupError)
- self.assertEqual(self.successResultOf(success), '192.168.3.4')
- def test_portNumber(self):
- """
- L{SimpleResolverComplexifier} preserves the C{port} argument passed to
- C{resolveHostName} in its returned addresses.
- """
- simple = SillyResolverSimple()
- complex = SimpleResolverComplexifier(simple)
- receiver = ResultHolder(self)
- complex.resolveHostName(receiver, u"example.com", 4321)
- self.assertEqual(receiver._started, True)
- self.assertEqual(receiver._ended, False)
- self.assertEqual(receiver._addresses, [])
- simple._requests[0].callback("192.168.1.1")
- self.assertEqual(receiver._addresses,
- [IPv4Address('TCP', '192.168.1.1', 4321)])
- self.assertEqual(receiver._ended, True)
- class JustEnoughReactor(ReactorBase, object):
- """
- Just enough subclass implementation to be a valid L{ReactorBase} subclass.
- """
- def installWaker(self):
- """
- Do nothing.
- """
- class ReactorInstallationTests(UnitTest, object):
- """
- Tests for installing old and new resolvers onto a L{ReactorBase} (from
- which all of Twisted's reactor implementations derive).
- """
- def test_interfaceCompliance(self):
- """
- L{ReactorBase} (and its subclasses) provide both
- L{IReactorPluggableNameResolver} and L{IReactorPluggableResolver}.
- """
- reactor = JustEnoughReactor()
- verifyObject(IReactorPluggableNameResolver, reactor)
- verifyObject(IResolverSimple, reactor.resolver)
- verifyObject(IHostnameResolver, reactor.nameResolver)
- def test_defaultToGAIResolver(self):
- """
- L{ReactorBase} defaults to using a L{GAIResolver}.
- """
- reactor = JustEnoughReactor()
- self.assertIsInstance(reactor.nameResolver, GAIResolver)
- self.assertIs(reactor.nameResolver._getaddrinfo, getaddrinfo)
- self.assertIsInstance(reactor.resolver, ComplexResolverSimplifier)
- self.assertIs(reactor.nameResolver._reactor, reactor)
- self.assertIs(reactor.resolver._nameResolver, reactor.nameResolver)
- def test_installingOldStyleResolver(self):
- """
- L{ReactorBase} will wrap an L{IResolverSimple} in a complexifier.
- """
- reactor = JustEnoughReactor()
- it = SillyResolverSimple()
- verifyObject(IResolverSimple, reactor.installResolver(it))
- self.assertIsInstance(reactor.nameResolver, SimpleResolverComplexifier)
- self.assertIs(reactor.nameResolver._simpleResolver, it)
|