Open Media Library Platform
This commit is contained in:
commit
411ad5b16f
5849 changed files with 1778641 additions and 0 deletions
|
|
@ -0,0 +1 @@
|
|||
"Tests for twisted.names"
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.names.cache}.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
import time
|
||||
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.names import dns, cache
|
||||
from twisted.internet import task, interfaces
|
||||
|
||||
|
||||
class Caching(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{cache.CacheResolver}.
|
||||
"""
|
||||
|
||||
def test_interface(self):
|
||||
"""
|
||||
L{cache.CacheResolver} implements L{interfaces.IResolver}
|
||||
"""
|
||||
verifyClass(interfaces.IResolver, cache.CacheResolver)
|
||||
|
||||
|
||||
def test_lookup(self):
|
||||
c = cache.CacheResolver({
|
||||
dns.Query(name=b'example.com', type=dns.MX, cls=dns.IN):
|
||||
(time.time(), ([], [], []))})
|
||||
return c.lookupMailExchange(b'example.com').addCallback(
|
||||
self.assertEqual, ([], [], []))
|
||||
|
||||
|
||||
def test_constructorExpires(self):
|
||||
"""
|
||||
Cache entries passed into L{cache.CacheResolver.__init__} get
|
||||
cancelled just like entries added with cacheResult
|
||||
"""
|
||||
r = ([dns.RRHeader(b"example.com", dns.A, dns.IN, 60,
|
||||
dns.Record_A("127.0.0.1", 60))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 50,
|
||||
dns.Record_A("127.0.0.1", 50))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 40,
|
||||
dns.Record_A("127.0.0.1", 40))])
|
||||
|
||||
clock = task.Clock()
|
||||
query = dns.Query(name=b"example.com", type=dns.A, cls=dns.IN)
|
||||
|
||||
c = cache.CacheResolver({ query : (clock.seconds(), r)}, reactor=clock)
|
||||
|
||||
# 40 seconds is enough to expire the entry because expiration is based
|
||||
# on the minimum TTL.
|
||||
clock.advance(40)
|
||||
|
||||
self.assertNotIn(query, c.cache)
|
||||
|
||||
return self.assertFailure(
|
||||
c.lookupAddress(b"example.com"), dns.DomainError)
|
||||
|
||||
|
||||
def test_normalLookup(self):
|
||||
"""
|
||||
When a cache lookup finds a cached entry from 1 second ago, it is
|
||||
returned with a TTL of original TTL minus the elapsed 1 second.
|
||||
"""
|
||||
r = ([dns.RRHeader(b"example.com", dns.A, dns.IN, 60,
|
||||
dns.Record_A("127.0.0.1", 60))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 50,
|
||||
dns.Record_A("127.0.0.1", 50))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 40,
|
||||
dns.Record_A("127.0.0.1", 40))])
|
||||
|
||||
clock = task.Clock()
|
||||
|
||||
c = cache.CacheResolver(reactor=clock)
|
||||
c.cacheResult(dns.Query(name=b"example.com", type=dns.A, cls=dns.IN), r)
|
||||
|
||||
clock.advance(1)
|
||||
|
||||
def cbLookup(result):
|
||||
self.assertEqual(result[0][0].ttl, 59)
|
||||
self.assertEqual(result[1][0].ttl, 49)
|
||||
self.assertEqual(result[2][0].ttl, 39)
|
||||
self.assertEqual(result[0][0].name.name, b"example.com")
|
||||
|
||||
return c.lookupAddress(b"example.com").addCallback(cbLookup)
|
||||
|
||||
|
||||
def test_cachedResultExpires(self):
|
||||
"""
|
||||
Once the TTL has been exceeded, the result is removed from the cache.
|
||||
"""
|
||||
r = ([dns.RRHeader(b"example.com", dns.A, dns.IN, 60,
|
||||
dns.Record_A("127.0.0.1", 60))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 50,
|
||||
dns.Record_A("127.0.0.1", 50))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 40,
|
||||
dns.Record_A("127.0.0.1", 40))])
|
||||
|
||||
clock = task.Clock()
|
||||
|
||||
c = cache.CacheResolver(reactor=clock)
|
||||
query = dns.Query(name=b"example.com", type=dns.A, cls=dns.IN)
|
||||
c.cacheResult(query, r)
|
||||
|
||||
clock.advance(40)
|
||||
|
||||
self.assertNotIn(query, c.cache)
|
||||
|
||||
return self.assertFailure(
|
||||
c.lookupAddress(b"example.com"), dns.DomainError)
|
||||
|
||||
|
||||
def test_expiredTTLLookup(self):
|
||||
"""
|
||||
When the cache is queried exactly as the cached entry should expire but
|
||||
before it has actually been cleared, the cache does not return the
|
||||
expired entry.
|
||||
"""
|
||||
r = ([dns.RRHeader(b"example.com", dns.A, dns.IN, 60,
|
||||
dns.Record_A("127.0.0.1", 60))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 50,
|
||||
dns.Record_A("127.0.0.1", 50))],
|
||||
[dns.RRHeader(b"example.com", dns.A, dns.IN, 40,
|
||||
dns.Record_A("127.0.0.1", 40))])
|
||||
|
||||
clock = task.Clock()
|
||||
# Make sure timeouts never happen, so entries won't get cleared:
|
||||
clock.callLater = lambda *args, **kwargs: None
|
||||
|
||||
c = cache.CacheResolver({
|
||||
dns.Query(name=b"example.com", type=dns.A, cls=dns.IN) :
|
||||
(clock.seconds(), r)}, reactor=clock)
|
||||
|
||||
clock.advance(60.1)
|
||||
|
||||
return self.assertFailure(
|
||||
c.lookupAddress(b"example.com"), dns.DomainError)
|
||||
1212
Linux/lib/python2.7/site-packages/twisted/names/test/test_client.py
Normal file
1212
Linux/lib/python2.7/site-packages/twisted/names/test/test_client.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,133 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.names.common}.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
from twisted.internet.interfaces import IResolver
|
||||
from twisted.trial.unittest import SynchronousTestCase
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.names.common import ResolverBase
|
||||
from twisted.names.dns import EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED, Query
|
||||
from twisted.names.error import DNSFormatError, DNSServerError, DNSNameError
|
||||
from twisted.names.error import DNSNotImplementedError, DNSQueryRefusedError
|
||||
from twisted.names.error import DNSUnknownError
|
||||
|
||||
|
||||
class ExceptionForCodeTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{ResolverBase.exceptionForCode}.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.exceptionForCode = ResolverBase().exceptionForCode
|
||||
|
||||
|
||||
def test_eformat(self):
|
||||
"""
|
||||
L{ResolverBase.exceptionForCode} converts L{EFORMAT} to
|
||||
L{DNSFormatError}.
|
||||
"""
|
||||
self.assertIs(self.exceptionForCode(EFORMAT), DNSFormatError)
|
||||
|
||||
|
||||
def test_eserver(self):
|
||||
"""
|
||||
L{ResolverBase.exceptionForCode} converts L{ESERVER} to
|
||||
L{DNSServerError}.
|
||||
"""
|
||||
self.assertIs(self.exceptionForCode(ESERVER), DNSServerError)
|
||||
|
||||
|
||||
def test_ename(self):
|
||||
"""
|
||||
L{ResolverBase.exceptionForCode} converts L{ENAME} to L{DNSNameError}.
|
||||
"""
|
||||
self.assertIs(self.exceptionForCode(ENAME), DNSNameError)
|
||||
|
||||
|
||||
def test_enotimp(self):
|
||||
"""
|
||||
L{ResolverBase.exceptionForCode} converts L{ENOTIMP} to
|
||||
L{DNSNotImplementedError}.
|
||||
"""
|
||||
self.assertIs(self.exceptionForCode(ENOTIMP), DNSNotImplementedError)
|
||||
|
||||
|
||||
def test_erefused(self):
|
||||
"""
|
||||
L{ResolverBase.exceptionForCode} converts L{EREFUSED} to
|
||||
L{DNSQueryRefusedError}.
|
||||
"""
|
||||
self.assertIs(self.exceptionForCode(EREFUSED), DNSQueryRefusedError)
|
||||
|
||||
|
||||
def test_other(self):
|
||||
"""
|
||||
L{ResolverBase.exceptionForCode} converts any other response code to
|
||||
L{DNSUnknownError}.
|
||||
"""
|
||||
self.assertIs(self.exceptionForCode(object()), DNSUnknownError)
|
||||
|
||||
|
||||
|
||||
class QueryTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{ResolverBase.query}.
|
||||
"""
|
||||
|
||||
def test_resolverBaseProvidesIResolver(self):
|
||||
"""
|
||||
L{ResolverBase} provides the L{IResolver} interface.
|
||||
"""
|
||||
verifyClass(IResolver, ResolverBase)
|
||||
|
||||
|
||||
def test_typeToMethodDispatch(self):
|
||||
"""
|
||||
L{ResolverBase.query} looks up a method to invoke using the type of the
|
||||
query passed to it and the C{typeToMethod} mapping on itself.
|
||||
"""
|
||||
results = []
|
||||
resolver = ResolverBase()
|
||||
resolver.typeToMethod = {
|
||||
12345: lambda query, timeout: results.append((query, timeout))}
|
||||
query = Query(name=b"example.com", type=12345)
|
||||
resolver.query(query, 123)
|
||||
self.assertEqual([(b"example.com", 123)], results)
|
||||
|
||||
|
||||
def test_typeToMethodResult(self):
|
||||
"""
|
||||
L{ResolverBase.query} returns a L{Deferred} which fires with the result
|
||||
of the method found in the C{typeToMethod} mapping for the type of the
|
||||
query passed to it.
|
||||
"""
|
||||
expected = object()
|
||||
resolver = ResolverBase()
|
||||
resolver.typeToMethod = {54321: lambda query, timeout: expected}
|
||||
query = Query(name=b"example.com", type=54321)
|
||||
queryDeferred = resolver.query(query, 123)
|
||||
result = []
|
||||
queryDeferred.addBoth(result.append)
|
||||
self.assertEqual(expected, result[0])
|
||||
|
||||
|
||||
def test_unknownQueryType(self):
|
||||
"""
|
||||
L{ResolverBase.query} returns a L{Deferred} which fails with
|
||||
L{NotImplementedError} when called with a query of a type not present in
|
||||
its C{typeToMethod} dictionary.
|
||||
"""
|
||||
resolver = ResolverBase()
|
||||
resolver.typeToMethod = {}
|
||||
query = Query(name=b"example.com", type=12345)
|
||||
queryDeferred = resolver.query(query, 123)
|
||||
result = []
|
||||
queryDeferred.addBoth(result.append)
|
||||
self.assertIsInstance(result[0], Failure)
|
||||
result[0].trap(NotImplementedError)
|
||||
4777
Linux/lib/python2.7/site-packages/twisted/names/test/test_dns.py
Normal file
4777
Linux/lib/python2.7/site-packages/twisted/names/test/test_dns.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,167 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.names} example scripts.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from StringIO import StringIO
|
||||
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.trial.unittest import SkipTest, TestCase
|
||||
|
||||
|
||||
|
||||
class ExampleTestBase(object):
|
||||
"""
|
||||
This is a mixin which adds an example to the path, tests it, and then
|
||||
removes it from the path and unimports the modules which the test loaded.
|
||||
Test cases which test example code and documentation listings should use
|
||||
this.
|
||||
|
||||
This is done this way so that examples can live in isolated path entries,
|
||||
next to the documentation, replete with their own plugin packages and
|
||||
whatever other metadata they need. Also, example code is a rare instance
|
||||
of it being valid to have multiple versions of the same code in the
|
||||
repository at once, rather than relying on version control, because
|
||||
documentation will often show the progression of a single piece of code as
|
||||
features are added to it, and we want to test each one.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Add our example directory to the path and record which modules are
|
||||
currently loaded.
|
||||
"""
|
||||
self.originalPath = sys.path[:]
|
||||
self.originalModules = sys.modules.copy()
|
||||
|
||||
self.fakeErr = StringIO()
|
||||
self.patch(sys, 'stderr', self.fakeErr)
|
||||
self.fakeOut = StringIO()
|
||||
self.patch(sys, 'stdout', self.fakeOut)
|
||||
|
||||
# Get documentation root
|
||||
here = (
|
||||
FilePath(__file__)
|
||||
.parent().parent().parent().parent()
|
||||
.child('docs').child('projects')
|
||||
)
|
||||
|
||||
# Find the example script within this branch
|
||||
for childName in self.exampleRelativePath.split('/'):
|
||||
here = here.child(childName)
|
||||
if not here.exists():
|
||||
raise SkipTest(
|
||||
"Examples (%s) not found - cannot test" % (here.path,))
|
||||
self.examplePath = here
|
||||
|
||||
# Add the example parent folder to the Python path
|
||||
sys.path.append(self.examplePath.parent().path)
|
||||
|
||||
# Import the example as a module
|
||||
moduleName = self.examplePath.basename().split('.')[0]
|
||||
self.example = __import__(moduleName)
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Remove the example directory from the path and remove all
|
||||
modules loaded by the test from sys.modules.
|
||||
"""
|
||||
sys.modules.clear()
|
||||
sys.modules.update(self.originalModules)
|
||||
sys.path[:] = self.originalPath
|
||||
|
||||
|
||||
def test_shebang(self):
|
||||
"""
|
||||
The example scripts start with the standard shebang line.
|
||||
"""
|
||||
self.assertEqual(
|
||||
self.examplePath.open().readline().rstrip(),
|
||||
'#!/usr/bin/env python')
|
||||
|
||||
|
||||
def test_usageConsistency(self):
|
||||
"""
|
||||
The example script prints a usage message to stdout if it is
|
||||
passed a --help option and then exits.
|
||||
|
||||
The first line should contain a USAGE summary, explaining the
|
||||
accepted command arguments.
|
||||
"""
|
||||
# Pass None as first parameter - the reactor - it shouldn't
|
||||
# get as far as calling it.
|
||||
self.assertRaises(
|
||||
SystemExit, self.example.main, None, '--help')
|
||||
|
||||
out = self.fakeOut.getvalue().splitlines()
|
||||
self.assertTrue(
|
||||
out[0].startswith('Usage:'),
|
||||
'Usage message first line should start with "Usage:". '
|
||||
'Actual: %r' % (out[0],))
|
||||
|
||||
|
||||
def test_usageConsistencyOnError(self):
|
||||
"""
|
||||
The example script prints a usage message to stderr if it is
|
||||
passed unrecognized command line arguments.
|
||||
|
||||
The first line should contain a USAGE summary, explaining the
|
||||
accepted command arguments.
|
||||
|
||||
The last line should contain an ERROR summary, explaining that
|
||||
incorrect arguments were supplied.
|
||||
"""
|
||||
# Pass None as first parameter - the reactor - it shouldn't
|
||||
# get as far as calling it.
|
||||
self.assertRaises(
|
||||
SystemExit, self.example.main, None, '--unexpected_argument')
|
||||
|
||||
err = self.fakeErr.getvalue().splitlines()
|
||||
self.assertTrue(
|
||||
err[0].startswith('Usage:'),
|
||||
'Usage message first line should start with "Usage:". '
|
||||
'Actual: %r' % (err[0],))
|
||||
self.assertTrue(
|
||||
err[-1].startswith('ERROR:'),
|
||||
'Usage message last line should start with "ERROR:" '
|
||||
'Actual: %r' % (err[-1],))
|
||||
|
||||
|
||||
|
||||
class TestDnsTests(ExampleTestBase, TestCase):
|
||||
"""
|
||||
Test the testdns.py example script.
|
||||
"""
|
||||
|
||||
exampleRelativePath = 'names/examples/testdns.py'
|
||||
|
||||
|
||||
|
||||
class GetHostByNameTests(ExampleTestBase, TestCase):
|
||||
"""
|
||||
Test the gethostbyname.py example script.
|
||||
"""
|
||||
|
||||
exampleRelativePath = 'names/examples/gethostbyname.py'
|
||||
|
||||
|
||||
|
||||
class DnsServiceTests(ExampleTestBase, TestCase):
|
||||
"""
|
||||
Test the dns-service.py example script.
|
||||
"""
|
||||
|
||||
exampleRelativePath = 'names/examples/dns-service.py'
|
||||
|
||||
|
||||
|
||||
class MultiReverseLookupTests(ExampleTestBase, TestCase):
|
||||
"""
|
||||
Test the multi_reverse_lookup.py example script.
|
||||
"""
|
||||
|
||||
exampleRelativePath = 'names/examples/multi_reverse_lookup.py'
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for the I{hosts(5)}-based resolver, L{twisted.names.hosts}.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
from twisted.trial.unittest import TestCase
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.internet.defer import gatherResults
|
||||
|
||||
from twisted.names.dns import (
|
||||
A, AAAA, IN, DomainError, RRHeader, Query, Record_A, Record_AAAA)
|
||||
from twisted.names.hosts import Resolver, searchFileFor, searchFileForAll
|
||||
|
||||
|
||||
class GoodTempPathMixin(object):
|
||||
def path(self):
|
||||
return FilePath(self.mktemp().encode('utf-8'))
|
||||
|
||||
|
||||
|
||||
class SearchHostsFileTests(TestCase, GoodTempPathMixin):
|
||||
"""
|
||||
Tests for L{searchFileFor}, a helper which finds the first address for a
|
||||
particular hostname in a I{hosts(5)}-style file.
|
||||
"""
|
||||
def test_findAddress(self):
|
||||
"""
|
||||
If there is an IPv4 address for the hostname passed to L{searchFileFor},
|
||||
it is returned.
|
||||
"""
|
||||
hosts = self.path()
|
||||
hosts.setContent(
|
||||
b"10.2.3.4 foo.example.com\n")
|
||||
self.assertEqual(
|
||||
"10.2.3.4", searchFileFor(hosts.path, b"foo.example.com"))
|
||||
|
||||
|
||||
def test_notFoundAddress(self):
|
||||
"""
|
||||
If there is no address information for the hostname passed to
|
||||
L{searchFileFor}, C{None} is returned.
|
||||
"""
|
||||
hosts = self.path()
|
||||
hosts.setContent(
|
||||
b"10.2.3.4 foo.example.com\n")
|
||||
self.assertIs(None, searchFileFor(hosts.path, b"bar.example.com"))
|
||||
|
||||
|
||||
def test_firstAddress(self):
|
||||
"""
|
||||
The first address associated with the given hostname is returned.
|
||||
"""
|
||||
hosts = self.path()
|
||||
hosts.setContent(
|
||||
b"::1 foo.example.com\n"
|
||||
b"10.1.2.3 foo.example.com\n"
|
||||
b"fe80::21b:fcff:feee:5a1d foo.example.com\n")
|
||||
self.assertEqual("::1", searchFileFor(hosts.path, b"foo.example.com"))
|
||||
|
||||
|
||||
def test_searchFileForAliases(self):
|
||||
"""
|
||||
For a host with a canonical name and one or more aliases,
|
||||
L{searchFileFor} can find an address given any of the names.
|
||||
"""
|
||||
hosts = self.path()
|
||||
hosts.setContent(
|
||||
b"127.0.1.1\thelmut.example.org\thelmut\n"
|
||||
b"# a comment\n"
|
||||
b"::1 localhost ip6-localhost ip6-loopback\n")
|
||||
self.assertEqual(searchFileFor(hosts.path, b'helmut'), '127.0.1.1')
|
||||
self.assertEqual(
|
||||
searchFileFor(hosts.path, b'helmut.example.org'), '127.0.1.1')
|
||||
self.assertEqual(searchFileFor(hosts.path, b'ip6-localhost'), '::1')
|
||||
self.assertEqual(searchFileFor(hosts.path, b'ip6-loopback'), '::1')
|
||||
self.assertEqual(searchFileFor(hosts.path, b'localhost'), '::1')
|
||||
|
||||
|
||||
|
||||
class SearchHostsFileForAllTests(TestCase, GoodTempPathMixin):
|
||||
"""
|
||||
Tests for L{searchFileForAll}, a helper which finds all addresses for a
|
||||
particular hostname in a I{hosts(5)}-style file.
|
||||
"""
|
||||
def test_allAddresses(self):
|
||||
"""
|
||||
L{searchFileForAll} returns a list of all addresses associated with the
|
||||
name passed to it.
|
||||
"""
|
||||
hosts = self.path()
|
||||
hosts.setContent(
|
||||
b"127.0.0.1 foobar.example.com\n"
|
||||
b"127.0.0.2 foobar.example.com\n"
|
||||
b"::1 foobar.example.com\n")
|
||||
self.assertEqual(
|
||||
["127.0.0.1", "127.0.0.2", "::1"],
|
||||
searchFileForAll(hosts, b"foobar.example.com"))
|
||||
|
||||
|
||||
def test_caseInsensitively(self):
|
||||
"""
|
||||
L{searchFileForAll} searches for names case-insensitively.
|
||||
"""
|
||||
hosts = self.path()
|
||||
hosts.setContent(b"127.0.0.1 foobar.EXAMPLE.com\n")
|
||||
self.assertEqual(
|
||||
["127.0.0.1"], searchFileForAll(hosts, b"FOOBAR.example.com"))
|
||||
|
||||
|
||||
def test_readError(self):
|
||||
"""
|
||||
If there is an error reading the contents of the hosts file,
|
||||
L{searchFileForAll} returns an empty list.
|
||||
"""
|
||||
self.assertEqual(
|
||||
[], searchFileForAll(self.path(), b"example.com"))
|
||||
|
||||
|
||||
|
||||
class HostsTestCase(TestCase, GoodTempPathMixin):
|
||||
"""
|
||||
Tests for the I{hosts(5)}-based L{twisted.names.hosts.Resolver}.
|
||||
"""
|
||||
def setUp(self):
|
||||
f = self.path()
|
||||
f.setContent(b'''
|
||||
1.1.1.1 EXAMPLE EXAMPLE.EXAMPLETHING
|
||||
::2 mixed
|
||||
1.1.1.2 MIXED
|
||||
::1 ip6thingy
|
||||
1.1.1.3 multiple
|
||||
1.1.1.4 multiple
|
||||
::3 ip6-multiple
|
||||
::4 ip6-multiple
|
||||
''')
|
||||
self.ttl = 4200
|
||||
self.resolver = Resolver(f.path, self.ttl)
|
||||
|
||||
|
||||
def test_defaultPath(self):
|
||||
"""
|
||||
The default hosts file used by L{Resolver} is I{/etc/hosts} if no value
|
||||
is given for the C{file} initializer parameter.
|
||||
"""
|
||||
resolver = Resolver()
|
||||
self.assertEqual(b"/etc/hosts", resolver.file)
|
||||
|
||||
|
||||
def test_getHostByName(self):
|
||||
"""
|
||||
L{hosts.Resolver.getHostByName} returns a L{Deferred} which fires with a
|
||||
string giving the address of the queried name as found in the resolver's
|
||||
hosts file.
|
||||
"""
|
||||
data = [(b'EXAMPLE', '1.1.1.1'),
|
||||
(b'EXAMPLE.EXAMPLETHING', '1.1.1.1'),
|
||||
(b'MIXED', '1.1.1.2'),
|
||||
]
|
||||
ds = [self.resolver.getHostByName(n).addCallback(self.assertEqual, ip)
|
||||
for n, ip in data]
|
||||
return gatherResults(ds)
|
||||
|
||||
|
||||
def test_lookupAddress(self):
|
||||
"""
|
||||
L{hosts.Resolver.lookupAddress} returns a L{Deferred} which fires with A
|
||||
records from the hosts file.
|
||||
"""
|
||||
d = self.resolver.lookupAddress(b'multiple')
|
||||
def resolved(results):
|
||||
answers, authority, additional = results
|
||||
self.assertEqual(
|
||||
(RRHeader(b"multiple", A, IN, self.ttl,
|
||||
Record_A("1.1.1.3", self.ttl)),
|
||||
RRHeader(b"multiple", A, IN, self.ttl,
|
||||
Record_A("1.1.1.4", self.ttl))),
|
||||
answers)
|
||||
d.addCallback(resolved)
|
||||
return d
|
||||
|
||||
|
||||
def test_lookupIPV6Address(self):
|
||||
"""
|
||||
L{hosts.Resolver.lookupIPV6Address} returns a L{Deferred} which fires
|
||||
with AAAA records from the hosts file.
|
||||
"""
|
||||
d = self.resolver.lookupIPV6Address(b'ip6-multiple')
|
||||
def resolved(results):
|
||||
answers, authority, additional = results
|
||||
self.assertEqual(
|
||||
(RRHeader(b"ip6-multiple", AAAA, IN, self.ttl,
|
||||
Record_AAAA("::3", self.ttl)),
|
||||
RRHeader(b"ip6-multiple", AAAA, IN, self.ttl,
|
||||
Record_AAAA("::4", self.ttl))),
|
||||
answers)
|
||||
d.addCallback(resolved)
|
||||
return d
|
||||
|
||||
|
||||
def test_lookupAllRecords(self):
|
||||
"""
|
||||
L{hosts.Resolver.lookupAllRecords} returns a L{Deferred} which fires
|
||||
with A records from the hosts file.
|
||||
"""
|
||||
d = self.resolver.lookupAllRecords(b'mixed')
|
||||
def resolved(results):
|
||||
answers, authority, additional = results
|
||||
self.assertEqual(
|
||||
(RRHeader(b"mixed", A, IN, self.ttl,
|
||||
Record_A("1.1.1.2", self.ttl)),),
|
||||
answers)
|
||||
d.addCallback(resolved)
|
||||
return d
|
||||
|
||||
|
||||
def testNotImplemented(self):
|
||||
return self.assertFailure(self.resolver.lookupMailExchange(b'EXAMPLE'),
|
||||
NotImplementedError)
|
||||
|
||||
|
||||
def testQuery(self):
|
||||
d = self.resolver.query(Query(b'EXAMPLE'))
|
||||
d.addCallback(lambda x: self.assertEqual(x[0][0].payload.dottedQuad(),
|
||||
'1.1.1.1'))
|
||||
return d
|
||||
|
||||
|
||||
def test_lookupAddressNotFound(self):
|
||||
"""
|
||||
L{hosts.Resolver.lookupAddress} returns a L{Deferred} which fires with
|
||||
L{dns.DomainError} if the name passed in has no addresses in the hosts
|
||||
file.
|
||||
"""
|
||||
return self.assertFailure(self.resolver.lookupAddress(b'foueoa'),
|
||||
DomainError)
|
||||
|
||||
|
||||
def test_lookupIPV6AddressNotFound(self):
|
||||
"""
|
||||
Like L{test_lookupAddressNotFound}, but for
|
||||
L{hosts.Resolver.lookupIPV6Address}.
|
||||
"""
|
||||
return self.assertFailure(self.resolver.lookupIPV6Address(b'foueoa'),
|
||||
DomainError)
|
||||
|
||||
|
||||
def test_lookupAllRecordsNotFound(self):
|
||||
"""
|
||||
Like L{test_lookupAddressNotFound}, but for
|
||||
L{hosts.Resolver.lookupAllRecords}.
|
||||
"""
|
||||
return self.assertFailure(self.resolver.lookupAllRecords(b'foueoa'),
|
||||
DomainError)
|
||||
|
|
@ -0,0 +1,974 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for twisted.names.
|
||||
"""
|
||||
|
||||
import socket, operator, copy
|
||||
from StringIO import StringIO
|
||||
from functools import partial, reduce
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.internet import reactor, defer, error
|
||||
from twisted.internet.defer import succeed
|
||||
from twisted.names import client, server, common, authority, dns
|
||||
from twisted.names.dns import Message
|
||||
from twisted.names.error import DomainError
|
||||
from twisted.names.client import Resolver
|
||||
from twisted.names.secondary import (
|
||||
SecondaryAuthorityService, SecondaryAuthority)
|
||||
|
||||
from twisted.test.proto_helpers import StringTransport, MemoryReactorClock
|
||||
|
||||
def justPayload(results):
|
||||
return [r.payload for r in results[0]]
|
||||
|
||||
class NoFileAuthority(authority.FileAuthority):
|
||||
def __init__(self, soa, records):
|
||||
# Yes, skip FileAuthority
|
||||
common.ResolverBase.__init__(self)
|
||||
self.soa, self.records = soa, records
|
||||
|
||||
|
||||
soa_record = dns.Record_SOA(
|
||||
mname = 'test-domain.com',
|
||||
rname = 'root.test-domain.com',
|
||||
serial = 100,
|
||||
refresh = 1234,
|
||||
minimum = 7654,
|
||||
expire = 19283784,
|
||||
retry = 15,
|
||||
ttl=1
|
||||
)
|
||||
|
||||
reverse_soa = dns.Record_SOA(
|
||||
mname = '93.84.28.in-addr.arpa',
|
||||
rname = '93.84.28.in-addr.arpa',
|
||||
serial = 120,
|
||||
refresh = 54321,
|
||||
minimum = 382,
|
||||
expire = 11193983,
|
||||
retry = 30,
|
||||
ttl=3
|
||||
)
|
||||
|
||||
my_soa = dns.Record_SOA(
|
||||
mname = 'my-domain.com',
|
||||
rname = 'postmaster.test-domain.com',
|
||||
serial = 130,
|
||||
refresh = 12345,
|
||||
minimum = 1,
|
||||
expire = 999999,
|
||||
retry = 100,
|
||||
)
|
||||
|
||||
test_domain_com = NoFileAuthority(
|
||||
soa = ('test-domain.com', soa_record),
|
||||
records = {
|
||||
'test-domain.com': [
|
||||
soa_record,
|
||||
dns.Record_A('127.0.0.1'),
|
||||
dns.Record_NS('39.28.189.39'),
|
||||
dns.Record_SPF('v=spf1 mx/30 mx:example.org/30 -all'),
|
||||
dns.Record_SPF('v=spf1 +mx a:\0colo', '.example.com/28 -all not valid'),
|
||||
dns.Record_MX(10, 'host.test-domain.com'),
|
||||
dns.Record_HINFO(os='Linux', cpu='A Fast One, Dontcha know'),
|
||||
dns.Record_CNAME('canonical.name.com'),
|
||||
dns.Record_MB('mailbox.test-domain.com'),
|
||||
dns.Record_MG('mail.group.someplace'),
|
||||
dns.Record_TXT('A First piece of Text', 'a SecoNd piece'),
|
||||
dns.Record_A6(0, 'ABCD::4321', ''),
|
||||
dns.Record_A6(12, '0:0069::0', 'some.network.tld'),
|
||||
dns.Record_A6(8, '0:5634:1294:AFCB:56AC:48EF:34C3:01FF', 'tra.la.la.net'),
|
||||
dns.Record_TXT('Some more text, haha! Yes. \0 Still here?'),
|
||||
dns.Record_MR('mail.redirect.or.whatever'),
|
||||
dns.Record_MINFO(rmailbx='r mail box', emailbx='e mail box'),
|
||||
dns.Record_AFSDB(subtype=1, hostname='afsdb.test-domain.com'),
|
||||
dns.Record_RP(mbox='whatever.i.dunno', txt='some.more.text'),
|
||||
dns.Record_WKS('12.54.78.12', socket.IPPROTO_TCP,
|
||||
'\x12\x01\x16\xfe\xc1\x00\x01'),
|
||||
dns.Record_NAPTR(100, 10, "u", "sip+E2U",
|
||||
"!^.*$!sip:information@domain.tld!"),
|
||||
dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF')],
|
||||
'http.tcp.test-domain.com': [
|
||||
dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool')
|
||||
],
|
||||
'host.test-domain.com': [
|
||||
dns.Record_A('123.242.1.5'),
|
||||
dns.Record_A('0.255.0.255'),
|
||||
],
|
||||
'host-two.test-domain.com': [
|
||||
#
|
||||
# Python bug
|
||||
# dns.Record_A('255.255.255.255'),
|
||||
#
|
||||
dns.Record_A('255.255.255.254'),
|
||||
dns.Record_A('0.0.0.0')
|
||||
],
|
||||
'cname.test-domain.com': [
|
||||
dns.Record_CNAME('test-domain.com')
|
||||
],
|
||||
'anothertest-domain.com': [
|
||||
dns.Record_A('1.2.3.4')],
|
||||
}
|
||||
)
|
||||
|
||||
reverse_domain = NoFileAuthority(
|
||||
soa = ('93.84.28.in-addr.arpa', reverse_soa),
|
||||
records = {
|
||||
'123.93.84.28.in-addr.arpa': [
|
||||
dns.Record_PTR('test.host-reverse.lookup.com'),
|
||||
reverse_soa
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
my_domain_com = NoFileAuthority(
|
||||
soa = ('my-domain.com', my_soa),
|
||||
records = {
|
||||
'my-domain.com': [
|
||||
my_soa,
|
||||
dns.Record_A('1.2.3.4', ttl='1S'),
|
||||
dns.Record_NS('ns1.domain', ttl='2M'),
|
||||
dns.Record_NS('ns2.domain', ttl='3H'),
|
||||
dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl='4D')
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ServerDNSTestCase(unittest.TestCase):
|
||||
"""
|
||||
Test cases for DNS server and client.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = server.DNSServerFactory([
|
||||
test_domain_com, reverse_domain, my_domain_com
|
||||
], verbose=2)
|
||||
|
||||
p = dns.DNSDatagramProtocol(self.factory)
|
||||
|
||||
while 1:
|
||||
listenerTCP = reactor.listenTCP(0, self.factory, interface="127.0.0.1")
|
||||
# It's simpler to do the stop listening with addCleanup,
|
||||
# even though we might not end up using this TCP port in
|
||||
# the test (if the listenUDP below fails). Cleaning up
|
||||
# this TCP port sooner than "cleanup time" would mean
|
||||
# adding more code to keep track of the Deferred returned
|
||||
# by stopListening.
|
||||
self.addCleanup(listenerTCP.stopListening)
|
||||
port = listenerTCP.getHost().port
|
||||
|
||||
try:
|
||||
listenerUDP = reactor.listenUDP(port, p, interface="127.0.0.1")
|
||||
except error.CannotListenError:
|
||||
pass
|
||||
else:
|
||||
self.addCleanup(listenerUDP.stopListening)
|
||||
break
|
||||
|
||||
self.listenerTCP = listenerTCP
|
||||
self.listenerUDP = listenerUDP
|
||||
self.resolver = client.Resolver(servers=[('127.0.0.1', port)])
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Clean up any server connections associated with the
|
||||
L{DNSServerFactory} created in L{setUp}
|
||||
"""
|
||||
# It'd be great if DNSServerFactory had a method that
|
||||
# encapsulated this task. At least the necessary data is
|
||||
# available, though.
|
||||
for conn in self.factory.connections[:]:
|
||||
conn.transport.loseConnection()
|
||||
|
||||
|
||||
def namesTest(self, querying, expectedRecords):
|
||||
"""
|
||||
Assert that the DNS response C{querying} will eventually fire with
|
||||
contains exactly a certain collection of records.
|
||||
|
||||
@param querying: A L{Deferred} returned from one of the DNS client
|
||||
I{lookup} methods.
|
||||
|
||||
@param expectedRecords: A L{list} of L{IRecord} providers which must be
|
||||
in the response or the test will be failed.
|
||||
|
||||
@return: A L{Deferred} that fires when the assertion has been made. It
|
||||
fires with a success result if the assertion succeeds and with a
|
||||
L{Failure} if it fails.
|
||||
"""
|
||||
def checkResults(response):
|
||||
receivedRecords = justPayload(response)
|
||||
self.assertEqual(set(expectedRecords), set(receivedRecords))
|
||||
|
||||
querying.addCallback(checkResults)
|
||||
return querying
|
||||
|
||||
|
||||
def testAddressRecord1(self):
|
||||
"""Test simple DNS 'A' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAddress('test-domain.com'),
|
||||
[dns.Record_A('127.0.0.1', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testAddressRecord2(self):
|
||||
"""Test DNS 'A' record queries with multiple answers"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAddress('host.test-domain.com'),
|
||||
[dns.Record_A('123.242.1.5', ttl=19283784), dns.Record_A('0.255.0.255', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testAddressRecord3(self):
|
||||
"""Test DNS 'A' record queries with edge cases"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAddress('host-two.test-domain.com'),
|
||||
[dns.Record_A('255.255.255.254', ttl=19283784), dns.Record_A('0.0.0.0', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testAuthority(self):
|
||||
"""Test DNS 'SOA' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAuthority('test-domain.com'),
|
||||
[soa_record]
|
||||
)
|
||||
|
||||
|
||||
def test_mailExchangeRecord(self):
|
||||
"""
|
||||
The DNS client can issue an MX query and receive a response including
|
||||
an MX record as well as any A record hints.
|
||||
"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupMailExchange(b"test-domain.com"),
|
||||
[dns.Record_MX(10, b"host.test-domain.com", ttl=19283784),
|
||||
dns.Record_A(b"123.242.1.5", ttl=19283784),
|
||||
dns.Record_A(b"0.255.0.255", ttl=19283784)])
|
||||
|
||||
|
||||
def testNameserver(self):
|
||||
"""Test DNS 'NS' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupNameservers('test-domain.com'),
|
||||
[dns.Record_NS('39.28.189.39', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testHINFO(self):
|
||||
"""Test DNS 'HINFO' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupHostInfo('test-domain.com'),
|
||||
[dns.Record_HINFO(os='Linux', cpu='A Fast One, Dontcha know', ttl=19283784)]
|
||||
)
|
||||
|
||||
def testPTR(self):
|
||||
"""Test DNS 'PTR' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupPointer('123.93.84.28.in-addr.arpa'),
|
||||
[dns.Record_PTR('test.host-reverse.lookup.com', ttl=11193983)]
|
||||
)
|
||||
|
||||
|
||||
def testCNAME(self):
|
||||
"""Test DNS 'CNAME' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupCanonicalName('test-domain.com'),
|
||||
[dns.Record_CNAME('canonical.name.com', ttl=19283784)]
|
||||
)
|
||||
|
||||
def testMB(self):
|
||||
"""Test DNS 'MB' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupMailBox('test-domain.com'),
|
||||
[dns.Record_MB('mailbox.test-domain.com', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testMG(self):
|
||||
"""Test DNS 'MG' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupMailGroup('test-domain.com'),
|
||||
[dns.Record_MG('mail.group.someplace', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testMR(self):
|
||||
"""Test DNS 'MR' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupMailRename('test-domain.com'),
|
||||
[dns.Record_MR('mail.redirect.or.whatever', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testMINFO(self):
|
||||
"""Test DNS 'MINFO' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupMailboxInfo('test-domain.com'),
|
||||
[dns.Record_MINFO(rmailbx='r mail box', emailbx='e mail box', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testSRV(self):
|
||||
"""Test DNS 'SRV' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupService('http.tcp.test-domain.com'),
|
||||
[dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl=19283784)]
|
||||
)
|
||||
|
||||
def testAFSDB(self):
|
||||
"""Test DNS 'AFSDB' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAFSDatabase('test-domain.com'),
|
||||
[dns.Record_AFSDB(subtype=1, hostname='afsdb.test-domain.com', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testRP(self):
|
||||
"""Test DNS 'RP' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupResponsibility('test-domain.com'),
|
||||
[dns.Record_RP(mbox='whatever.i.dunno', txt='some.more.text', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testTXT(self):
|
||||
"""Test DNS 'TXT' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupText('test-domain.com'),
|
||||
[dns.Record_TXT('A First piece of Text', 'a SecoNd piece', ttl=19283784),
|
||||
dns.Record_TXT('Some more text, haha! Yes. \0 Still here?', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def test_spf(self):
|
||||
"""
|
||||
L{DNSServerFactory} can serve I{SPF} resource records.
|
||||
"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupSenderPolicy('test-domain.com'),
|
||||
[dns.Record_SPF('v=spf1 mx/30 mx:example.org/30 -all', ttl=19283784),
|
||||
dns.Record_SPF('v=spf1 +mx a:\0colo', '.example.com/28 -all not valid', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testWKS(self):
|
||||
"""Test DNS 'WKS' record queries"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupWellKnownServices('test-domain.com'),
|
||||
[dns.Record_WKS('12.54.78.12', socket.IPPROTO_TCP, '\x12\x01\x16\xfe\xc1\x00\x01', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def testSomeRecordsWithTTLs(self):
|
||||
result_soa = copy.copy(my_soa)
|
||||
result_soa.ttl = my_soa.expire
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAllRecords('my-domain.com'),
|
||||
[result_soa,
|
||||
dns.Record_A('1.2.3.4', ttl='1S'),
|
||||
dns.Record_NS('ns1.domain', ttl='2M'),
|
||||
dns.Record_NS('ns2.domain', ttl='3H'),
|
||||
dns.Record_SRV(257, 16383, 43690, 'some.other.place.fool', ttl='4D')]
|
||||
)
|
||||
|
||||
|
||||
def testAAAA(self):
|
||||
"""Test DNS 'AAAA' record queries (IPv6)"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupIPV6Address('test-domain.com'),
|
||||
[dns.Record_AAAA('AF43:5634:1294:AFCB:56AC:48EF:34C3:01FF', ttl=19283784)]
|
||||
)
|
||||
|
||||
def testA6(self):
|
||||
"""Test DNS 'A6' record queries (IPv6)"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAddress6('test-domain.com'),
|
||||
[dns.Record_A6(0, 'ABCD::4321', '', ttl=19283784),
|
||||
dns.Record_A6(12, '0:0069::0', 'some.network.tld', ttl=19283784),
|
||||
dns.Record_A6(8, '0:5634:1294:AFCB:56AC:48EF:34C3:01FF', 'tra.la.la.net', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def test_zoneTransfer(self):
|
||||
"""
|
||||
Test DNS 'AXFR' queries (Zone transfer)
|
||||
"""
|
||||
default_ttl = soa_record.expire
|
||||
results = [copy.copy(r) for r in reduce(operator.add, test_domain_com.records.values())]
|
||||
for r in results:
|
||||
if r.ttl is None:
|
||||
r.ttl = default_ttl
|
||||
return self.namesTest(
|
||||
self.resolver.lookupZone('test-domain.com').addCallback(lambda r: (r[0][:-1],)),
|
||||
results
|
||||
)
|
||||
|
||||
|
||||
def testSimilarZonesDontInterfere(self):
|
||||
"""Tests that unrelated zones don't mess with each other."""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupAddress("anothertest-domain.com"),
|
||||
[dns.Record_A('1.2.3.4', ttl=19283784)]
|
||||
)
|
||||
|
||||
|
||||
def test_NAPTR(self):
|
||||
"""
|
||||
Test DNS 'NAPTR' record queries.
|
||||
"""
|
||||
return self.namesTest(
|
||||
self.resolver.lookupNamingAuthorityPointer('test-domain.com'),
|
||||
[dns.Record_NAPTR(100, 10, "u", "sip+E2U",
|
||||
"!^.*$!sip:information@domain.tld!",
|
||||
ttl=19283784)])
|
||||
|
||||
|
||||
|
||||
class HelperTestCase(unittest.TestCase):
|
||||
def testSerialGenerator(self):
|
||||
f = self.mktemp()
|
||||
a = authority.getSerial(f)
|
||||
for i in range(20):
|
||||
b = authority.getSerial(f)
|
||||
self.assertTrue(a < b)
|
||||
a = b
|
||||
|
||||
|
||||
class AXFRTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.results = None
|
||||
self.d = defer.Deferred()
|
||||
self.d.addCallback(self._gotResults)
|
||||
self.controller = client.AXFRController('fooby.com', self.d)
|
||||
|
||||
self.soa = dns.RRHeader(name='fooby.com', type=dns.SOA, cls=dns.IN, ttl=86400, auth=False,
|
||||
payload=dns.Record_SOA(mname='fooby.com',
|
||||
rname='hooj.fooby.com',
|
||||
serial=100,
|
||||
refresh=200,
|
||||
retry=300,
|
||||
expire=400,
|
||||
minimum=500,
|
||||
ttl=600))
|
||||
|
||||
self.records = [
|
||||
self.soa,
|
||||
dns.RRHeader(name='fooby.com', type=dns.NS, cls=dns.IN, ttl=700, auth=False,
|
||||
payload=dns.Record_NS(name='ns.twistedmatrix.com', ttl=700)),
|
||||
|
||||
dns.RRHeader(name='fooby.com', type=dns.MX, cls=dns.IN, ttl=700, auth=False,
|
||||
payload=dns.Record_MX(preference=10, exchange='mail.mv3d.com', ttl=700)),
|
||||
|
||||
dns.RRHeader(name='fooby.com', type=dns.A, cls=dns.IN, ttl=700, auth=False,
|
||||
payload=dns.Record_A(address='64.123.27.105', ttl=700)),
|
||||
self.soa
|
||||
]
|
||||
|
||||
def _makeMessage(self):
|
||||
# hooray they all have the same message format
|
||||
return dns.Message(id=999, answer=1, opCode=0, recDes=0, recAv=1, auth=1, rCode=0, trunc=0, maxSize=0)
|
||||
|
||||
def testBindAndTNamesStyle(self):
|
||||
# Bind style = One big single message
|
||||
m = self._makeMessage()
|
||||
m.queries = [dns.Query('fooby.com', dns.AXFR, dns.IN)]
|
||||
m.answers = self.records
|
||||
self.controller.messageReceived(m, None)
|
||||
self.assertEqual(self.results, self.records)
|
||||
|
||||
def _gotResults(self, result):
|
||||
self.results = result
|
||||
|
||||
def testDJBStyle(self):
|
||||
# DJB style = message per record
|
||||
records = self.records[:]
|
||||
while records:
|
||||
m = self._makeMessage()
|
||||
m.queries = [] # DJB *doesn't* specify any queries.. hmm..
|
||||
m.answers = [records.pop(0)]
|
||||
self.controller.messageReceived(m, None)
|
||||
self.assertEqual(self.results, self.records)
|
||||
|
||||
|
||||
|
||||
class ResolvConfHandling(unittest.TestCase):
|
||||
def testMissing(self):
|
||||
resolvConf = self.mktemp()
|
||||
r = client.Resolver(resolv=resolvConf)
|
||||
self.assertEqual(r.dynServers, [('127.0.0.1', 53)])
|
||||
r._parseCall.cancel()
|
||||
|
||||
def testEmpty(self):
|
||||
resolvConf = self.mktemp()
|
||||
fObj = file(resolvConf, 'w')
|
||||
fObj.close()
|
||||
r = client.Resolver(resolv=resolvConf)
|
||||
self.assertEqual(r.dynServers, [('127.0.0.1', 53)])
|
||||
r._parseCall.cancel()
|
||||
|
||||
|
||||
|
||||
class AuthorityTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for the basic response record selection code in L{FileAuthority}
|
||||
(independent of its fileness).
|
||||
"""
|
||||
|
||||
def test_domainErrorForNameWithCommonSuffix(self):
|
||||
"""
|
||||
L{FileAuthority} lookup methods errback with L{DomainError} if
|
||||
the requested C{name} shares a common suffix with its zone but
|
||||
is not actually a descendant of its zone, in terms of its
|
||||
sequence of DNS name labels. eg www.the-example.com has
|
||||
nothing to do with the zone example.com.
|
||||
"""
|
||||
testDomain = test_domain_com
|
||||
testDomainName = 'nonexistent.prefix-' + testDomain.soa[0]
|
||||
f = self.failureResultOf(testDomain.lookupAddress(testDomainName))
|
||||
self.assertIsInstance(f.value, DomainError)
|
||||
|
||||
|
||||
def test_recordMissing(self):
|
||||
"""
|
||||
If a L{FileAuthority} has a zone which includes an I{NS} record for a
|
||||
particular name and that authority is asked for another record for the
|
||||
same name which does not exist, the I{NS} record is not included in the
|
||||
authority section of the response.
|
||||
"""
|
||||
authority = NoFileAuthority(
|
||||
soa=(str(soa_record.mname), soa_record),
|
||||
records={
|
||||
str(soa_record.mname): [
|
||||
soa_record,
|
||||
dns.Record_NS('1.2.3.4'),
|
||||
]})
|
||||
d = authority.lookupAddress(str(soa_record.mname))
|
||||
result = []
|
||||
d.addCallback(result.append)
|
||||
answer, authority, additional = result[0]
|
||||
self.assertEqual(answer, [])
|
||||
self.assertEqual(
|
||||
authority, [
|
||||
dns.RRHeader(
|
||||
str(soa_record.mname), soa_record.TYPE,
|
||||
ttl=soa_record.expire, payload=soa_record,
|
||||
auth=True)])
|
||||
self.assertEqual(additional, [])
|
||||
|
||||
|
||||
def _referralTest(self, method):
|
||||
"""
|
||||
Create an authority and make a request against it. Then verify that the
|
||||
result is a referral, including no records in the answers or additional
|
||||
sections, but with an I{NS} record in the authority section.
|
||||
"""
|
||||
subdomain = 'example.' + str(soa_record.mname)
|
||||
nameserver = dns.Record_NS('1.2.3.4')
|
||||
authority = NoFileAuthority(
|
||||
soa=(str(soa_record.mname), soa_record),
|
||||
records={
|
||||
subdomain: [
|
||||
nameserver,
|
||||
]})
|
||||
d = getattr(authority, method)(subdomain)
|
||||
answer, authority, additional = self.successResultOf(d)
|
||||
self.assertEqual(answer, [])
|
||||
self.assertEqual(
|
||||
authority, [dns.RRHeader(
|
||||
subdomain, dns.NS, ttl=soa_record.expire,
|
||||
payload=nameserver, auth=False)])
|
||||
self.assertEqual(additional, [])
|
||||
|
||||
|
||||
def test_referral(self):
|
||||
"""
|
||||
When an I{NS} record is found for a child zone, it is included in the
|
||||
authority section of the response. It is marked as non-authoritative if
|
||||
the authority is not also authoritative for the child zone (RFC 2181,
|
||||
section 6.1).
|
||||
"""
|
||||
self._referralTest('lookupAddress')
|
||||
|
||||
|
||||
def test_allRecordsReferral(self):
|
||||
"""
|
||||
A referral is also generated for a request of type C{ALL_RECORDS}.
|
||||
"""
|
||||
self._referralTest('lookupAllRecords')
|
||||
|
||||
|
||||
|
||||
class AdditionalProcessingTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{FileAuthority}'s additional processing for those record types
|
||||
which require it (MX, CNAME, etc).
|
||||
"""
|
||||
_A = dns.Record_A(b"10.0.0.1")
|
||||
_AAAA = dns.Record_AAAA(b"f080::1")
|
||||
|
||||
def _lookupSomeRecords(self, method, soa, makeRecord, target, addresses):
|
||||
"""
|
||||
Perform a DNS lookup against a L{FileAuthority} configured with records
|
||||
as defined by C{makeRecord} and C{addresses}.
|
||||
|
||||
@param method: The name of the lookup method to use; for example,
|
||||
C{"lookupNameservers"}.
|
||||
@type method: L{str}
|
||||
|
||||
@param soa: A L{Record_SOA} for the zone for which the L{FileAuthority}
|
||||
is authoritative.
|
||||
|
||||
@param makeRecord: A one-argument callable which accepts a name and
|
||||
returns an L{IRecord} provider. L{FileAuthority} is constructed
|
||||
with this record. The L{FileAuthority} is queried for a record of
|
||||
the resulting type with the given name.
|
||||
|
||||
@param target: The extra name which the record returned by
|
||||
C{makeRecord} will be pointed at; this is the name which might
|
||||
require extra processing by the server so that all the available,
|
||||
useful information is returned. For example, this is the target of
|
||||
a CNAME record or the mail exchange host pointed to by an MX record.
|
||||
@type target: L{bytes}
|
||||
|
||||
@param addresses: A L{list} of records giving addresses of C{target}.
|
||||
|
||||
@return: A L{Deferred} that fires with the result of the resolver
|
||||
method give by C{method}.
|
||||
"""
|
||||
authority = NoFileAuthority(
|
||||
soa=(soa.mname.name, soa),
|
||||
records={
|
||||
soa.mname.name: [makeRecord(target)],
|
||||
target: addresses,
|
||||
},
|
||||
)
|
||||
return getattr(authority, method)(soa_record.mname.name)
|
||||
|
||||
|
||||
def assertRecordsMatch(self, expected, computed):
|
||||
"""
|
||||
Assert that the L{RRHeader} instances given by C{expected} and
|
||||
C{computed} carry all the same information but without requiring the
|
||||
records appear in the same order.
|
||||
|
||||
@param expected: A L{list} of L{RRHeader} instances giving the expected
|
||||
records.
|
||||
|
||||
@param computed: A L{list} of L{RRHeader} instances giving the records
|
||||
computed by the scenario under test.
|
||||
|
||||
@raise self.failureException: If the two collections of records disagree.
|
||||
"""
|
||||
# RRHeader instances aren't inherently ordered. Impose an ordering
|
||||
# that's good enough for the purposes of these tests - in which we
|
||||
# never have more than one record of a particular type.
|
||||
key = lambda rr: rr.type
|
||||
self.assertEqual(sorted(expected, key=key), sorted(computed, key=key))
|
||||
|
||||
|
||||
def _additionalTest(self, method, makeRecord, addresses):
|
||||
"""
|
||||
Verify that certain address records are included in the I{additional}
|
||||
section of a response generated by L{FileAuthority}.
|
||||
|
||||
@param method: See L{_lookupSomeRecords}
|
||||
|
||||
@param makeRecord: See L{_lookupSomeRecords}
|
||||
|
||||
@param addresses: A L{list} of L{IRecord} providers which the
|
||||
I{additional} section of the response is required to match
|
||||
(ignoring order).
|
||||
|
||||
@raise self.failureException: If the I{additional} section of the
|
||||
response consists of different records than those given by
|
||||
C{addresses}.
|
||||
"""
|
||||
target = b"mail." + soa_record.mname.name
|
||||
d = self._lookupSomeRecords(
|
||||
method, soa_record, makeRecord, target, addresses)
|
||||
answer, authority, additional = self.successResultOf(d)
|
||||
|
||||
self.assertRecordsMatch(
|
||||
[dns.RRHeader(
|
||||
target, address.TYPE, ttl=soa_record.expire, payload=address,
|
||||
auth=True)
|
||||
for address in addresses],
|
||||
additional)
|
||||
|
||||
|
||||
def _additionalMXTest(self, addresses):
|
||||
"""
|
||||
Verify that a response to an MX query has certain records in the
|
||||
I{additional} section.
|
||||
|
||||
@param addresses: See C{_additionalTest}
|
||||
"""
|
||||
self._additionalTest(
|
||||
"lookupMailExchange", partial(dns.Record_MX, 10), addresses)
|
||||
|
||||
|
||||
def test_mailExchangeAdditionalA(self):
|
||||
"""
|
||||
If the name of the MX response has A records, they are included in the
|
||||
additional section of the response.
|
||||
"""
|
||||
self._additionalMXTest([self._A])
|
||||
|
||||
|
||||
def test_mailExchangeAdditionalAAAA(self):
|
||||
"""
|
||||
If the name of the MX response has AAAA records, they are included in
|
||||
the additional section of the response.
|
||||
"""
|
||||
self._additionalMXTest([self._AAAA])
|
||||
|
||||
|
||||
def test_mailExchangeAdditionalBoth(self):
|
||||
"""
|
||||
If the name of the MX response has both A and AAAA records, they are
|
||||
all included in the additional section of the response.
|
||||
"""
|
||||
self._additionalMXTest([self._A, self._AAAA])
|
||||
|
||||
|
||||
def _additionalNSTest(self, addresses):
|
||||
"""
|
||||
Verify that a response to an NS query has certain records in the
|
||||
I{additional} section.
|
||||
|
||||
@param addresses: See C{_additionalTest}
|
||||
"""
|
||||
self._additionalTest(
|
||||
"lookupNameservers", dns.Record_NS, addresses)
|
||||
|
||||
|
||||
def test_nameserverAdditionalA(self):
|
||||
"""
|
||||
If the name of the NS response has A records, they are included in the
|
||||
additional section of the response.
|
||||
"""
|
||||
self._additionalNSTest([self._A])
|
||||
|
||||
|
||||
def test_nameserverAdditionalAAAA(self):
|
||||
"""
|
||||
If the name of the NS response has AAAA records, they are included in
|
||||
the additional section of the response.
|
||||
"""
|
||||
self._additionalNSTest([self._AAAA])
|
||||
|
||||
|
||||
def test_nameserverAdditionalBoth(self):
|
||||
"""
|
||||
If the name of the NS response has both A and AAAA records, they are
|
||||
all included in the additional section of the response.
|
||||
"""
|
||||
self._additionalNSTest([self._A, self._AAAA])
|
||||
|
||||
|
||||
def _answerCNAMETest(self, addresses):
|
||||
"""
|
||||
Verify that a response to a CNAME query has certain records in the
|
||||
I{answer} section.
|
||||
|
||||
@param addresses: See C{_additionalTest}
|
||||
"""
|
||||
target = b"www." + soa_record.mname.name
|
||||
d = self._lookupSomeRecords(
|
||||
"lookupCanonicalName", soa_record, dns.Record_CNAME, target,
|
||||
addresses)
|
||||
answer, authority, additional = self.successResultOf(d)
|
||||
|
||||
alias = dns.RRHeader(
|
||||
soa_record.mname.name, dns.CNAME, ttl=soa_record.expire,
|
||||
payload=dns.Record_CNAME(target), auth=True)
|
||||
self.assertRecordsMatch(
|
||||
[dns.RRHeader(
|
||||
target, address.TYPE, ttl=soa_record.expire, payload=address,
|
||||
auth=True)
|
||||
for address in addresses] + [alias],
|
||||
answer)
|
||||
|
||||
|
||||
def test_canonicalNameAnswerA(self):
|
||||
"""
|
||||
If the name of the CNAME response has A records, they are included in
|
||||
the answer section of the response.
|
||||
"""
|
||||
self._answerCNAMETest([self._A])
|
||||
|
||||
|
||||
def test_canonicalNameAnswerAAAA(self):
|
||||
"""
|
||||
If the name of the CNAME response has AAAA records, they are included
|
||||
in the answer section of the response.
|
||||
"""
|
||||
self._answerCNAMETest([self._AAAA])
|
||||
|
||||
|
||||
def test_canonicalNameAnswerBoth(self):
|
||||
"""
|
||||
If the name of the CNAME response has both A and AAAA records, they are
|
||||
all included in the answer section of the response.
|
||||
"""
|
||||
self._answerCNAMETest([self._A, self._AAAA])
|
||||
|
||||
|
||||
|
||||
class NoInitialResponseTestCase(unittest.TestCase):
|
||||
|
||||
def test_no_answer(self):
|
||||
"""
|
||||
If a request returns a L{dns.NS} response, but we can't connect to the
|
||||
given server, the request fails with the error returned at connection.
|
||||
"""
|
||||
|
||||
def query(self, *args):
|
||||
# Pop from the message list, so that it blows up if more queries
|
||||
# are run than expected.
|
||||
return succeed(messages.pop(0))
|
||||
|
||||
def queryProtocol(self, *args, **kwargs):
|
||||
return defer.fail(socket.gaierror("Couldn't connect"))
|
||||
|
||||
resolver = Resolver(servers=[('0.0.0.0', 0)])
|
||||
resolver._query = query
|
||||
messages = []
|
||||
# Let's patch dns.DNSDatagramProtocol.query, as there is no easy way to
|
||||
# customize it.
|
||||
self.patch(dns.DNSDatagramProtocol, "query", queryProtocol)
|
||||
|
||||
records = [
|
||||
dns.RRHeader(name='fooba.com', type=dns.NS, cls=dns.IN, ttl=700,
|
||||
auth=False,
|
||||
payload=dns.Record_NS(name='ns.twistedmatrix.com',
|
||||
ttl=700))]
|
||||
m = dns.Message(id=999, answer=1, opCode=0, recDes=0, recAv=1, auth=1,
|
||||
rCode=0, trunc=0, maxSize=0)
|
||||
m.answers = records
|
||||
messages.append(m)
|
||||
return self.assertFailure(
|
||||
resolver.getHostByName("fooby.com"), socket.gaierror)
|
||||
|
||||
|
||||
|
||||
class SecondaryAuthorityServiceTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{SecondaryAuthorityService}, a service which keeps one or more
|
||||
authorities up to date by doing zone transfers from a master.
|
||||
"""
|
||||
|
||||
def test_constructAuthorityFromHost(self):
|
||||
"""
|
||||
L{SecondaryAuthorityService} can be constructed with a C{str} giving a
|
||||
master server address and several domains, causing the creation of a
|
||||
secondary authority for each domain and that master server address and
|
||||
the default DNS port.
|
||||
"""
|
||||
primary = '192.168.1.2'
|
||||
service = SecondaryAuthorityService(
|
||||
primary, ['example.com', 'example.org'])
|
||||
self.assertEqual(service.primary, primary)
|
||||
self.assertEqual(service._port, 53)
|
||||
|
||||
self.assertEqual(service.domains[0].primary, primary)
|
||||
self.assertEqual(service.domains[0]._port, 53)
|
||||
self.assertEqual(service.domains[0].domain, 'example.com')
|
||||
|
||||
self.assertEqual(service.domains[1].primary, primary)
|
||||
self.assertEqual(service.domains[1]._port, 53)
|
||||
self.assertEqual(service.domains[1].domain, 'example.org')
|
||||
|
||||
|
||||
def test_constructAuthorityFromHostAndPort(self):
|
||||
"""
|
||||
L{SecondaryAuthorityService.fromServerAddressAndDomains} constructs a
|
||||
new L{SecondaryAuthorityService} from a C{str} giving a master server
|
||||
address and DNS port and several domains, causing the creation of a secondary
|
||||
authority for each domain and that master server address and the given
|
||||
DNS port.
|
||||
"""
|
||||
primary = '192.168.1.3'
|
||||
port = 5335
|
||||
service = SecondaryAuthorityService.fromServerAddressAndDomains(
|
||||
(primary, port), ['example.net', 'example.edu'])
|
||||
self.assertEqual(service.primary, primary)
|
||||
self.assertEqual(service._port, 5335)
|
||||
|
||||
self.assertEqual(service.domains[0].primary, primary)
|
||||
self.assertEqual(service.domains[0]._port, port)
|
||||
self.assertEqual(service.domains[0].domain, 'example.net')
|
||||
|
||||
self.assertEqual(service.domains[1].primary, primary)
|
||||
self.assertEqual(service.domains[1]._port, port)
|
||||
self.assertEqual(service.domains[1].domain, 'example.edu')
|
||||
|
||||
|
||||
|
||||
class SecondaryAuthorityTests(unittest.TestCase):
|
||||
"""
|
||||
L{twisted.names.secondary.SecondaryAuthority} correctly constructs objects
|
||||
with a specified IP address and optionally specified DNS port.
|
||||
"""
|
||||
|
||||
def test_defaultPort(self):
|
||||
"""
|
||||
When constructed using L{SecondaryAuthority.__init__}, the default port
|
||||
of 53 is used.
|
||||
"""
|
||||
secondary = SecondaryAuthority('192.168.1.1', 'inside.com')
|
||||
self.assertEqual(secondary.primary, '192.168.1.1')
|
||||
self.assertEqual(secondary._port, 53)
|
||||
self.assertEqual(secondary.domain, 'inside.com')
|
||||
|
||||
|
||||
def test_explicitPort(self):
|
||||
"""
|
||||
When constructed using L{SecondaryAuthority.fromServerAddressAndDomain},
|
||||
the specified port is used.
|
||||
"""
|
||||
secondary = SecondaryAuthority.fromServerAddressAndDomain(
|
||||
('192.168.1.1', 5353), 'inside.com')
|
||||
self.assertEqual(secondary.primary, '192.168.1.1')
|
||||
self.assertEqual(secondary._port, 5353)
|
||||
self.assertEqual(secondary.domain, 'inside.com')
|
||||
|
||||
|
||||
def test_transfer(self):
|
||||
"""
|
||||
An attempt is made to transfer the zone for the domain the
|
||||
L{SecondaryAuthority} was constructed with from the server address it
|
||||
was constructed with when L{SecondaryAuthority.transfer} is called.
|
||||
"""
|
||||
secondary = SecondaryAuthority.fromServerAddressAndDomain(
|
||||
('192.168.1.2', 1234), 'example.com')
|
||||
secondary._reactor = reactor = MemoryReactorClock()
|
||||
|
||||
secondary.transfer()
|
||||
|
||||
# Verify a connection attempt to the server address above
|
||||
host, port, factory, timeout, bindAddress = reactor.tcpClients.pop(0)
|
||||
self.assertEqual(host, '192.168.1.2')
|
||||
self.assertEqual(port, 1234)
|
||||
|
||||
# See if a zone transfer query is issued.
|
||||
proto = factory.buildProtocol((host, port))
|
||||
transport = StringTransport()
|
||||
proto.makeConnection(transport)
|
||||
|
||||
msg = Message()
|
||||
# DNSProtocol.writeMessage length encodes the message by prepending a
|
||||
# 2 byte message length to the buffered value.
|
||||
msg.decode(StringIO(transport.value()[2:]))
|
||||
|
||||
self.assertEqual(
|
||||
[dns.Query('example.com', dns.AXFR, dns.IN)], msg.queries)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.names.resolve}.
|
||||
"""
|
||||
|
||||
from twisted.trial.unittest import TestCase
|
||||
from twisted.names.error import DomainError
|
||||
from twisted.names.resolve import ResolverChain
|
||||
|
||||
|
||||
|
||||
class ResolverChainTests(TestCase):
|
||||
"""
|
||||
Tests for L{twisted.names.resolve.ResolverChain}
|
||||
"""
|
||||
|
||||
def test_emptyResolversList(self):
|
||||
"""
|
||||
L{ResolverChain._lookup} returns a L{DomainError} failure if
|
||||
its C{resolvers} list is empty.
|
||||
"""
|
||||
r = ResolverChain([])
|
||||
d = r.lookupAddress('www.example.com')
|
||||
f = self.failureResultOf(d)
|
||||
self.assertIs(f.trap(DomainError), DomainError)
|
||||
|
||||
|
||||
def test_emptyResolversListLookupAllRecords(self):
|
||||
"""
|
||||
L{ResolverChain.lookupAllRecords} returns a L{DomainError}
|
||||
failure if its C{resolvers} list is empty.
|
||||
"""
|
||||
r = ResolverChain([])
|
||||
d = r.lookupAllRecords('www.example.com')
|
||||
f = self.failureResultOf(d)
|
||||
self.assertIs(f.trap(DomainError), DomainError)
|
||||
|
|
@ -0,0 +1,444 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for L{twisted.names.rfc1982}.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
import calendar
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
|
||||
from twisted.names._rfc1982 import SerialNumber
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
|
||||
class SerialNumberTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{SerialNumber}.
|
||||
"""
|
||||
|
||||
def test_serialBitsDefault(self):
|
||||
"""
|
||||
L{SerialNumber.serialBits} has default value 32.
|
||||
"""
|
||||
self.assertEqual(SerialNumber(1)._serialBits, 32)
|
||||
|
||||
|
||||
def test_serialBitsOverride(self):
|
||||
"""
|
||||
L{SerialNumber.__init__} accepts a C{serialBits} argument whose value is
|
||||
assigned to L{SerialNumber.serialBits}.
|
||||
"""
|
||||
self.assertEqual(SerialNumber(1, serialBits=8)._serialBits, 8)
|
||||
|
||||
|
||||
def test_repr(self):
|
||||
"""
|
||||
L{SerialNumber.__repr__} returns a string containing number and
|
||||
serialBits.
|
||||
"""
|
||||
self.assertEqual(
|
||||
'<SerialNumber number=123 serialBits=32>',
|
||||
repr(SerialNumber(123, serialBits=32))
|
||||
)
|
||||
|
||||
|
||||
def test_str(self):
|
||||
"""
|
||||
L{SerialNumber.__str__} returns a string representation of the current
|
||||
value.
|
||||
"""
|
||||
self.assertEqual(str(SerialNumber(123)), '123')
|
||||
|
||||
|
||||
def test_int(self):
|
||||
"""
|
||||
L{SerialNumber.__int__} returns an integer representation of the current
|
||||
value.
|
||||
"""
|
||||
self.assertEqual(int(SerialNumber(123)), 123)
|
||||
|
||||
|
||||
def test_hash(self):
|
||||
"""
|
||||
L{SerialNumber.__hash__} allows L{SerialNumber} instances to be hashed
|
||||
for use as dictionary keys.
|
||||
"""
|
||||
self.assertEqual(hash(SerialNumber(1)), hash(SerialNumber(1)))
|
||||
self.assertNotEqual(hash(SerialNumber(1)), hash(SerialNumber(2)))
|
||||
|
||||
|
||||
def test_convertOtherSerialBitsMismatch(self):
|
||||
"""
|
||||
L{SerialNumber._convertOther} raises L{TypeError} if the other
|
||||
SerialNumber instance has a different C{serialBits} value.
|
||||
"""
|
||||
s1 = SerialNumber(0, serialBits=8)
|
||||
s2 = SerialNumber(0, serialBits=16)
|
||||
|
||||
self.assertRaises(
|
||||
TypeError,
|
||||
s1._convertOther,
|
||||
s2
|
||||
)
|
||||
|
||||
|
||||
def test_eq(self):
|
||||
"""
|
||||
L{SerialNumber.__eq__} provides rich equality comparison.
|
||||
"""
|
||||
self.assertEqual(SerialNumber(1), SerialNumber(1))
|
||||
|
||||
|
||||
def test_eqForeignType(self):
|
||||
"""
|
||||
== comparison of L{SerialNumber} with a non-L{SerialNumber} instance
|
||||
raises L{TypeError}.
|
||||
"""
|
||||
self.assertRaises(TypeError, lambda: SerialNumber(1) == object())
|
||||
|
||||
|
||||
def test_ne(self):
|
||||
"""
|
||||
L{SerialNumber.__ne__} provides rich equality comparison.
|
||||
"""
|
||||
self.assertFalse(SerialNumber(1) != SerialNumber(1))
|
||||
self.assertNotEqual(SerialNumber(1), SerialNumber(2))
|
||||
|
||||
|
||||
def test_neForeignType(self):
|
||||
"""
|
||||
!= comparison of L{SerialNumber} with a non-L{SerialNumber} instance
|
||||
raises L{TypeError}.
|
||||
"""
|
||||
self.assertRaises(TypeError, lambda: SerialNumber(1) != object())
|
||||
|
||||
|
||||
def test_le(self):
|
||||
"""
|
||||
L{SerialNumber.__le__} provides rich <= comparison.
|
||||
"""
|
||||
self.assertTrue(SerialNumber(1) <= SerialNumber(1))
|
||||
self.assertTrue(SerialNumber(1) <= SerialNumber(2))
|
||||
|
||||
|
||||
def test_leForeignType(self):
|
||||
"""
|
||||
<= comparison of L{SerialNumber} with a non-L{SerialNumber} instance
|
||||
raises L{TypeError}.
|
||||
"""
|
||||
self.assertRaises(TypeError, lambda: SerialNumber(1) <= object())
|
||||
|
||||
|
||||
def test_ge(self):
|
||||
"""
|
||||
L{SerialNumber.__ge__} provides rich >= comparison.
|
||||
"""
|
||||
self.assertTrue(SerialNumber(1) >= SerialNumber(1))
|
||||
self.assertTrue(SerialNumber(2) >= SerialNumber(1))
|
||||
|
||||
|
||||
def test_geForeignType(self):
|
||||
"""
|
||||
>= comparison of L{SerialNumber} with a non-L{SerialNumber} instance
|
||||
raises L{TypeError}.
|
||||
"""
|
||||
self.assertRaises(TypeError, lambda: SerialNumber(1) >= object())
|
||||
|
||||
|
||||
def test_lt(self):
|
||||
"""
|
||||
L{SerialNumber.__lt__} provides rich < comparison.
|
||||
"""
|
||||
self.assertTrue(SerialNumber(1) < SerialNumber(2))
|
||||
|
||||
|
||||
def test_ltForeignType(self):
|
||||
"""
|
||||
< comparison of L{SerialNumber} with a non-L{SerialNumber} instance
|
||||
raises L{TypeError}.
|
||||
"""
|
||||
self.assertRaises(TypeError, lambda: SerialNumber(1) < object())
|
||||
|
||||
|
||||
def test_gt(self):
|
||||
"""
|
||||
L{SerialNumber.__gt__} provides rich > comparison.
|
||||
"""
|
||||
self.assertTrue(SerialNumber(2) > SerialNumber(1))
|
||||
|
||||
|
||||
def test_gtForeignType(self):
|
||||
"""
|
||||
> comparison of L{SerialNumber} with a non-L{SerialNumber} instance
|
||||
raises L{TypeError}.
|
||||
"""
|
||||
self.assertRaises(TypeError, lambda: SerialNumber(2) > object())
|
||||
|
||||
|
||||
def test_add(self):
|
||||
"""
|
||||
L{SerialNumber.__add__} allows L{SerialNumber} instances to be summed.
|
||||
"""
|
||||
self.assertEqual(SerialNumber(1) + SerialNumber(1), SerialNumber(2))
|
||||
|
||||
|
||||
def test_addForeignType(self):
|
||||
"""
|
||||
Addition of L{SerialNumber} with a non-L{SerialNumber} instance raises
|
||||
L{TypeError}.
|
||||
"""
|
||||
self.assertRaises(TypeError, lambda: SerialNumber(1) + object())
|
||||
|
||||
|
||||
def test_addOutOfRangeHigh(self):
|
||||
"""
|
||||
L{SerialNumber} cannot be added with other SerialNumber values larger
|
||||
than C{_maxAdd}.
|
||||
"""
|
||||
maxAdd = SerialNumber(1)._maxAdd
|
||||
self.assertRaises(
|
||||
ArithmeticError,
|
||||
lambda: SerialNumber(1) + SerialNumber(maxAdd + 1))
|
||||
|
||||
|
||||
def test_maxVal(self):
|
||||
"""
|
||||
L{SerialNumber.__add__} returns a wrapped value when s1 plus the s2
|
||||
would result in a value greater than the C{maxVal}.
|
||||
"""
|
||||
s = SerialNumber(1)
|
||||
maxVal = s._halfRing + s._halfRing - 1
|
||||
maxValPlus1 = maxVal + 1
|
||||
self.assertTrue(SerialNumber(maxValPlus1) > SerialNumber(maxVal))
|
||||
self.assertEqual(SerialNumber(maxValPlus1), SerialNumber(0))
|
||||
|
||||
|
||||
def test_fromRFC4034DateString(self):
|
||||
"""
|
||||
L{SerialNumber.fromRFC4034DateString} accepts a datetime string argument
|
||||
of the form 'YYYYMMDDhhmmss' and returns an L{SerialNumber} instance
|
||||
whose value is the unix timestamp corresponding to that UTC date.
|
||||
"""
|
||||
self.assertEqual(
|
||||
SerialNumber(1325376000),
|
||||
SerialNumber.fromRFC4034DateString('20120101000000')
|
||||
)
|
||||
|
||||
|
||||
def test_toRFC4034DateString(self):
|
||||
"""
|
||||
L{DateSerialNumber.toRFC4034DateString} interprets the current value as
|
||||
a unix timestamp and returns a date string representation of that date.
|
||||
"""
|
||||
self.assertEqual(
|
||||
'20120101000000',
|
||||
SerialNumber(1325376000).toRFC4034DateString()
|
||||
)
|
||||
|
||||
|
||||
def test_unixEpoch(self):
|
||||
"""
|
||||
L{SerialNumber.toRFC4034DateString} stores 32bit timestamps relative to
|
||||
the UNIX epoch.
|
||||
"""
|
||||
self.assertEqual(
|
||||
SerialNumber(0).toRFC4034DateString(),
|
||||
'19700101000000'
|
||||
)
|
||||
|
||||
|
||||
def test_Y2106Problem(self):
|
||||
"""
|
||||
L{SerialNumber} wraps unix timestamps in the year 2106.
|
||||
"""
|
||||
self.assertEqual(
|
||||
SerialNumber(-1).toRFC4034DateString(),
|
||||
'21060207062815'
|
||||
)
|
||||
|
||||
|
||||
def test_Y2038Problem(self):
|
||||
"""
|
||||
L{SerialNumber} raises ArithmeticError when used to add dates more than
|
||||
68 years in the future.
|
||||
"""
|
||||
maxAddTime = calendar.timegm(
|
||||
datetime(2038, 1, 19, 3, 14, 7).utctimetuple())
|
||||
|
||||
self.assertEqual(
|
||||
maxAddTime,
|
||||
SerialNumber(0)._maxAdd,
|
||||
)
|
||||
|
||||
self.assertRaises(
|
||||
ArithmeticError,
|
||||
lambda: SerialNumber(0) + SerialNumber(maxAddTime + 1))
|
||||
|
||||
|
||||
|
||||
def assertUndefinedComparison(testCase, s1, s2):
|
||||
"""
|
||||
A custom assertion for L{SerialNumber} values that cannot be meaningfully
|
||||
compared.
|
||||
|
||||
"Note that there are some pairs of values s1 and s2 for which s1 is not
|
||||
equal to s2, but for which s1 is neither greater than, nor less than, s2.
|
||||
An attempt to use these ordering operators on such pairs of values produces
|
||||
an undefined result."
|
||||
|
||||
@see: U{https://tools.ietf.org/html/rfc1982#section-3.2}
|
||||
|
||||
@param testCase: The L{unittest.TestCase} on which to call assertion
|
||||
methods.
|
||||
@type testCase: L{unittest.TestCase}
|
||||
|
||||
@param s1: The first value to compare.
|
||||
@type s1: L{SerialNumber}
|
||||
|
||||
@param s2: The second value to compare.
|
||||
@type s2: L{SerialNumber}
|
||||
"""
|
||||
testCase.assertFalse(s1 == s2)
|
||||
testCase.assertFalse(s1 <= s2)
|
||||
testCase.assertFalse(s1 < s2)
|
||||
testCase.assertFalse(s1 > s2)
|
||||
testCase.assertFalse(s1 >= s2)
|
||||
|
||||
|
||||
|
||||
serialNumber2 = partial(SerialNumber, serialBits=2)
|
||||
|
||||
|
||||
|
||||
class SerialNumber2BitTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for correct answers to example calculations in RFC1982 5.1.
|
||||
|
||||
The simplest meaningful serial number space has SERIAL_BITS == 2. In this
|
||||
space, the integers that make up the serial number space are 0, 1, 2, and 3.
|
||||
That is, 3 == 2^SERIAL_BITS - 1.
|
||||
|
||||
https://tools.ietf.org/html/rfc1982#section-5.1
|
||||
"""
|
||||
def test_maxadd(self):
|
||||
"""
|
||||
In this space, the largest integer that it is meaningful to add to a
|
||||
sequence number is 2^(SERIAL_BITS - 1) - 1, or 1.
|
||||
"""
|
||||
self.assertEqual(SerialNumber(0, serialBits=2)._maxAdd, 1)
|
||||
|
||||
|
||||
def test_add(self):
|
||||
"""
|
||||
Then, as defined 0+1 == 1, 1+1 == 2, 2+1 == 3, and 3+1 == 0.
|
||||
"""
|
||||
self.assertEqual(serialNumber2(0) + serialNumber2(1), serialNumber2(1))
|
||||
self.assertEqual(serialNumber2(1) + serialNumber2(1), serialNumber2(2))
|
||||
self.assertEqual(serialNumber2(2) + serialNumber2(1), serialNumber2(3))
|
||||
self.assertEqual(serialNumber2(3) + serialNumber2(1), serialNumber2(0))
|
||||
|
||||
|
||||
def test_gt(self):
|
||||
"""
|
||||
Further, 1 > 0, 2 > 1, 3 > 2, and 0 > 3.
|
||||
"""
|
||||
self.assertTrue(serialNumber2(1) > serialNumber2(0))
|
||||
self.assertTrue(serialNumber2(2) > serialNumber2(1))
|
||||
self.assertTrue(serialNumber2(3) > serialNumber2(2))
|
||||
self.assertTrue(serialNumber2(0) > serialNumber2(3))
|
||||
|
||||
|
||||
def test_undefined(self):
|
||||
"""
|
||||
It is undefined whether 2 > 0 or 0 > 2, and whether 1 > 3 or 3 > 1.
|
||||
"""
|
||||
assertUndefinedComparison(self, serialNumber2(2), serialNumber2(0))
|
||||
assertUndefinedComparison(self, serialNumber2(0), serialNumber2(2))
|
||||
assertUndefinedComparison(self, serialNumber2(1), serialNumber2(3))
|
||||
assertUndefinedComparison(self, serialNumber2(3), serialNumber2(1))
|
||||
|
||||
|
||||
|
||||
serialNumber8 = partial(SerialNumber, serialBits=8)
|
||||
|
||||
|
||||
|
||||
class SerialNumber8BitTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for correct answers to example calculations in RFC1982 5.2.
|
||||
|
||||
Consider the case where SERIAL_BITS == 8. In this space the integers that
|
||||
make up the serial number space are 0, 1, 2, ... 254, 255. 255 ==
|
||||
2^SERIAL_BITS - 1.
|
||||
|
||||
https://tools.ietf.org/html/rfc1982#section-5.2
|
||||
"""
|
||||
|
||||
def test_maxadd(self):
|
||||
"""
|
||||
In this space, the largest integer that it is meaningful to add to a
|
||||
sequence number is 2^(SERIAL_BITS - 1) - 1, or 127.
|
||||
"""
|
||||
self.assertEqual(SerialNumber(0, serialBits=8)._maxAdd, 127)
|
||||
|
||||
|
||||
def test_add(self):
|
||||
"""
|
||||
Addition is as expected in this space, for example: 255+1 == 0,
|
||||
100+100 == 200, and 200+100 == 44.
|
||||
"""
|
||||
self.assertEqual(
|
||||
serialNumber8(255) + serialNumber8(1), serialNumber8(0))
|
||||
self.assertEqual(
|
||||
serialNumber8(100) + serialNumber8(100), serialNumber8(200))
|
||||
self.assertEqual(
|
||||
serialNumber8(200) + serialNumber8(100), serialNumber8(44))
|
||||
|
||||
|
||||
def test_gt(self):
|
||||
"""
|
||||
Comparison is more interesting, 1 > 0, 44 > 0, 100 > 0, 100 > 44,
|
||||
200 > 100, 255 > 200, 0 > 255, 100 > 255, 0 > 200, and 44 > 200.
|
||||
"""
|
||||
self.assertTrue(serialNumber8(1) > serialNumber8(0))
|
||||
self.assertTrue(serialNumber8(44) > serialNumber8(0))
|
||||
self.assertTrue(serialNumber8(100) > serialNumber8(0))
|
||||
self.assertTrue(serialNumber8(100) > serialNumber8(44))
|
||||
self.assertTrue(serialNumber8(200) > serialNumber8(100))
|
||||
self.assertTrue(serialNumber8(255) > serialNumber8(200))
|
||||
self.assertTrue(serialNumber8(100) > serialNumber8(255))
|
||||
self.assertTrue(serialNumber8(0) > serialNumber8(200))
|
||||
self.assertTrue(serialNumber8(44) > serialNumber8(200))
|
||||
|
||||
|
||||
def test_surprisingAddition(self):
|
||||
"""
|
||||
Note that 100+100 > 100, but that (100+100)+100 < 100. Incrementing a
|
||||
serial number can cause it to become "smaller". Of course, incrementing
|
||||
by a smaller number will allow many more increments to be made before
|
||||
this occurs. However this is always something to be aware of, it can
|
||||
cause surprising errors, or be useful as it is the only defined way to
|
||||
actually cause a serial number to decrease.
|
||||
"""
|
||||
self.assertTrue(
|
||||
serialNumber8(100) + serialNumber8(100) > serialNumber8(100))
|
||||
self.assertTrue(
|
||||
serialNumber8(100) + serialNumber8(100) + serialNumber8(100)
|
||||
< serialNumber8(100))
|
||||
|
||||
|
||||
def test_undefined(self):
|
||||
"""
|
||||
The pairs of values 0 and 128, 1 and 129, 2 and 130, etc, to 127 and 255
|
||||
are not equal, but in each pair, neither number is defined as being
|
||||
greater than, or less than, the other.
|
||||
"""
|
||||
assertUndefinedComparison(self, serialNumber8(0), serialNumber8(128))
|
||||
assertUndefinedComparison(self, serialNumber8(1), serialNumber8(129))
|
||||
assertUndefinedComparison(self, serialNumber8(2), serialNumber8(130))
|
||||
assertUndefinedComparison(self, serialNumber8(127), serialNumber8(255))
|
||||
|
|
@ -0,0 +1,855 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for Twisted.names' root resolver.
|
||||
"""
|
||||
|
||||
from random import randrange
|
||||
|
||||
from zope.interface import implementer
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
from twisted.python.log import msg
|
||||
from twisted.trial import util
|
||||
from twisted.trial.unittest import SynchronousTestCase, TestCase
|
||||
from twisted.internet.defer import Deferred, succeed, gatherResults, TimeoutError
|
||||
from twisted.internet.task import Clock
|
||||
from twisted.internet.address import IPv4Address
|
||||
from twisted.internet.interfaces import IReactorUDP, IUDPTransport, IResolverSimple
|
||||
from twisted.names import client, root
|
||||
from twisted.names.root import Resolver
|
||||
from twisted.names.dns import (
|
||||
IN, HS, A, NS, CNAME, OK, ENAME, Record_CNAME,
|
||||
Name, Query, Message, RRHeader, Record_A, Record_NS)
|
||||
from twisted.names.error import DNSNameError, ResolverError
|
||||
|
||||
|
||||
def getOnePayload(results):
|
||||
"""
|
||||
From the result of a L{Deferred} returned by L{IResolver.lookupAddress},
|
||||
return the payload of the first record in the answer section.
|
||||
"""
|
||||
ans, auth, add = results
|
||||
return ans[0].payload
|
||||
|
||||
|
||||
def getOneAddress(results):
|
||||
"""
|
||||
From the result of a L{Deferred} returned by L{IResolver.lookupAddress},
|
||||
return the first IPv4 address from the answer section.
|
||||
"""
|
||||
return getOnePayload(results).dottedQuad()
|
||||
|
||||
|
||||
|
||||
@implementer(IUDPTransport)
|
||||
class MemoryDatagramTransport(object):
|
||||
"""
|
||||
This L{IUDPTransport} implementation enforces the usual connection rules
|
||||
and captures sent traffic in a list for later inspection.
|
||||
|
||||
@ivar _host: The host address to which this transport is bound.
|
||||
@ivar _protocol: The protocol connected to this transport.
|
||||
@ivar _sentPackets: A C{list} of two-tuples of the datagrams passed to
|
||||
C{write} and the addresses to which they are destined.
|
||||
|
||||
@ivar _connectedTo: C{None} if this transport is unconnected, otherwise an
|
||||
address to which all traffic is supposedly sent.
|
||||
|
||||
@ivar _maxPacketSize: An C{int} giving the maximum length of a datagram
|
||||
which will be successfully handled by C{write}.
|
||||
"""
|
||||
def __init__(self, host, protocol, maxPacketSize):
|
||||
self._host = host
|
||||
self._protocol = protocol
|
||||
self._sentPackets = []
|
||||
self._connectedTo = None
|
||||
self._maxPacketSize = maxPacketSize
|
||||
|
||||
|
||||
def getHost(self):
|
||||
"""
|
||||
Return the address which this transport is pretending to be bound
|
||||
to.
|
||||
"""
|
||||
return IPv4Address('UDP', *self._host)
|
||||
|
||||
|
||||
def connect(self, host, port):
|
||||
"""
|
||||
Connect this transport to the given address.
|
||||
"""
|
||||
if self._connectedTo is not None:
|
||||
raise ValueError("Already connected")
|
||||
self._connectedTo = (host, port)
|
||||
|
||||
|
||||
def write(self, datagram, addr=None):
|
||||
"""
|
||||
Send the given datagram.
|
||||
"""
|
||||
if addr is None:
|
||||
addr = self._connectedTo
|
||||
if addr is None:
|
||||
raise ValueError("Need an address")
|
||||
if len(datagram) > self._maxPacketSize:
|
||||
raise ValueError("Packet too big")
|
||||
self._sentPackets.append((datagram, addr))
|
||||
|
||||
|
||||
def stopListening(self):
|
||||
"""
|
||||
Shut down this transport.
|
||||
"""
|
||||
self._protocol.stopProtocol()
|
||||
return succeed(None)
|
||||
|
||||
|
||||
def setBroadcastAllowed(self, enabled):
|
||||
"""
|
||||
Dummy implementation to satisfy L{IUDPTransport}.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def getBroadcastAllowed(self):
|
||||
"""
|
||||
Dummy implementation to satisfy L{IUDPTransport}.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
verifyClass(IUDPTransport, MemoryDatagramTransport)
|
||||
|
||||
|
||||
|
||||
@implementer(IReactorUDP)
|
||||
class MemoryReactor(Clock):
|
||||
"""
|
||||
An L{IReactorTime} and L{IReactorUDP} provider.
|
||||
|
||||
Time is controlled deterministically via the base class, L{Clock}. UDP is
|
||||
handled in-memory by connecting protocols to instances of
|
||||
L{MemoryDatagramTransport}.
|
||||
|
||||
@ivar udpPorts: A C{dict} mapping port numbers to instances of
|
||||
L{MemoryDatagramTransport}.
|
||||
"""
|
||||
def __init__(self):
|
||||
Clock.__init__(self)
|
||||
self.udpPorts = {}
|
||||
|
||||
|
||||
def listenUDP(self, port, protocol, interface='', maxPacketSize=8192):
|
||||
"""
|
||||
Pretend to bind a UDP port and connect the given protocol to it.
|
||||
"""
|
||||
if port == 0:
|
||||
while True:
|
||||
port = randrange(1, 2 ** 16)
|
||||
if port not in self.udpPorts:
|
||||
break
|
||||
if port in self.udpPorts:
|
||||
raise ValueError("Address in use")
|
||||
transport = MemoryDatagramTransport(
|
||||
(interface, port), protocol, maxPacketSize)
|
||||
self.udpPorts[port] = transport
|
||||
protocol.makeConnection(transport)
|
||||
return transport
|
||||
|
||||
verifyClass(IReactorUDP, MemoryReactor)
|
||||
|
||||
|
||||
|
||||
class RootResolverTests(TestCase):
|
||||
"""
|
||||
Tests for L{twisted.names.root.Resolver}.
|
||||
"""
|
||||
def _queryTest(self, filter):
|
||||
"""
|
||||
Invoke L{Resolver._query} and verify that it sends the correct DNS
|
||||
query. Deliver a canned response to the query and return whatever the
|
||||
L{Deferred} returned by L{Resolver._query} fires with.
|
||||
|
||||
@param filter: The value to pass for the C{filter} parameter to
|
||||
L{Resolver._query}.
|
||||
"""
|
||||
reactor = MemoryReactor()
|
||||
resolver = Resolver([], reactor=reactor)
|
||||
d = resolver._query(
|
||||
Query(b'foo.example.com', A, IN), [('1.1.2.3', 1053)], (30,),
|
||||
filter)
|
||||
|
||||
# A UDP port should have been started.
|
||||
portNumber, transport = reactor.udpPorts.popitem()
|
||||
|
||||
# And a DNS packet sent.
|
||||
[(packet, address)] = transport._sentPackets
|
||||
|
||||
message = Message()
|
||||
message.fromStr(packet)
|
||||
|
||||
# It should be a query with the parameters used above.
|
||||
self.assertEqual(message.queries, [Query(b'foo.example.com', A, IN)])
|
||||
self.assertEqual(message.answers, [])
|
||||
self.assertEqual(message.authority, [])
|
||||
self.assertEqual(message.additional, [])
|
||||
|
||||
response = []
|
||||
d.addCallback(response.append)
|
||||
self.assertEqual(response, [])
|
||||
|
||||
# Once a reply is received, the Deferred should fire.
|
||||
del message.queries[:]
|
||||
message.answer = 1
|
||||
message.answers.append(RRHeader(
|
||||
b'foo.example.com', payload=Record_A('5.8.13.21')))
|
||||
transport._protocol.datagramReceived(
|
||||
message.toStr(), ('1.1.2.3', 1053))
|
||||
return response[0]
|
||||
|
||||
|
||||
def test_filteredQuery(self):
|
||||
"""
|
||||
L{Resolver._query} accepts a L{Query} instance and an address, issues
|
||||
the query, and returns a L{Deferred} which fires with the response to
|
||||
the query. If a true value is passed for the C{filter} parameter, the
|
||||
result is a three-tuple of lists of records.
|
||||
"""
|
||||
answer, authority, additional = self._queryTest(True)
|
||||
self.assertEqual(
|
||||
answer,
|
||||
[RRHeader(b'foo.example.com', payload=Record_A('5.8.13.21', ttl=0))])
|
||||
self.assertEqual(authority, [])
|
||||
self.assertEqual(additional, [])
|
||||
|
||||
|
||||
def test_unfilteredQuery(self):
|
||||
"""
|
||||
Similar to L{test_filteredQuery}, but for the case where a false value
|
||||
is passed for the C{filter} parameter. In this case, the result is a
|
||||
L{Message} instance.
|
||||
"""
|
||||
message = self._queryTest(False)
|
||||
self.assertIsInstance(message, Message)
|
||||
self.assertEqual(message.queries, [])
|
||||
self.assertEqual(
|
||||
message.answers,
|
||||
[RRHeader(b'foo.example.com', payload=Record_A('5.8.13.21', ttl=0))])
|
||||
self.assertEqual(message.authority, [])
|
||||
self.assertEqual(message.additional, [])
|
||||
|
||||
|
||||
def _respond(self, answers=[], authority=[], additional=[], rCode=OK):
|
||||
"""
|
||||
Create a L{Message} suitable for use as a response to a query.
|
||||
|
||||
@param answers: A C{list} of two-tuples giving data for the answers
|
||||
section of the message. The first element of each tuple is a name
|
||||
for the L{RRHeader}. The second element is the payload.
|
||||
@param authority: A C{list} like C{answers}, but for the authority
|
||||
section of the response.
|
||||
@param additional: A C{list} like C{answers}, but for the
|
||||
additional section of the response.
|
||||
@param rCode: The response code the message will be created with.
|
||||
|
||||
@return: A new L{Message} initialized with the given values.
|
||||
"""
|
||||
response = Message(rCode=rCode)
|
||||
for (section, data) in [(response.answers, answers),
|
||||
(response.authority, authority),
|
||||
(response.additional, additional)]:
|
||||
section.extend([
|
||||
RRHeader(name, record.TYPE, getattr(record, 'CLASS', IN),
|
||||
payload=record)
|
||||
for (name, record) in data])
|
||||
return response
|
||||
|
||||
|
||||
def _getResolver(self, serverResponses, maximumQueries=10):
|
||||
"""
|
||||
Create and return a new L{root.Resolver} modified to resolve queries
|
||||
against the record data represented by C{servers}.
|
||||
|
||||
@param serverResponses: A mapping from dns server addresses to
|
||||
mappings. The inner mappings are from query two-tuples (name,
|
||||
type) to dictionaries suitable for use as **arguments to
|
||||
L{_respond}. See that method for details.
|
||||
"""
|
||||
roots = ['1.1.2.3']
|
||||
resolver = Resolver(roots, maximumQueries)
|
||||
|
||||
def query(query, serverAddresses, timeout, filter):
|
||||
msg("Query for QNAME %s at %r" % (query.name, serverAddresses))
|
||||
for addr in serverAddresses:
|
||||
try:
|
||||
server = serverResponses[addr]
|
||||
except KeyError:
|
||||
continue
|
||||
records = server[query.name.name, query.type]
|
||||
return succeed(self._respond(**records))
|
||||
resolver._query = query
|
||||
return resolver
|
||||
|
||||
|
||||
def test_lookupAddress(self):
|
||||
"""
|
||||
L{root.Resolver.lookupAddress} looks up the I{A} records for the
|
||||
specified hostname by first querying one of the root servers the
|
||||
resolver was created with and then following the authority delegations
|
||||
until a result is received.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
(b'foo.example.com', A): {
|
||||
'authority': [(b'foo.example.com', Record_NS(b'ns1.example.com'))],
|
||||
'additional': [(b'ns1.example.com', Record_A('34.55.89.144'))],
|
||||
},
|
||||
},
|
||||
('34.55.89.144', 53): {
|
||||
(b'foo.example.com', A): {
|
||||
'answers': [(b'foo.example.com', Record_A('10.0.0.1'))],
|
||||
}
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'foo.example.com')
|
||||
d.addCallback(getOneAddress)
|
||||
d.addCallback(self.assertEqual, '10.0.0.1')
|
||||
return d
|
||||
|
||||
|
||||
def test_lookupChecksClass(self):
|
||||
"""
|
||||
If a response includes a record with a class different from the one
|
||||
in the query, it is ignored and lookup continues until a record with
|
||||
the right class is found.
|
||||
"""
|
||||
badClass = Record_A('10.0.0.1')
|
||||
badClass.CLASS = HS
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
('foo.example.com', A): {
|
||||
'answers': [('foo.example.com', badClass)],
|
||||
'authority': [('foo.example.com', Record_NS('ns1.example.com'))],
|
||||
'additional': [('ns1.example.com', Record_A('10.0.0.2'))],
|
||||
},
|
||||
},
|
||||
('10.0.0.2', 53): {
|
||||
('foo.example.com', A): {
|
||||
'answers': [('foo.example.com', Record_A('10.0.0.3'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress('foo.example.com')
|
||||
d.addCallback(getOnePayload)
|
||||
d.addCallback(self.assertEqual, Record_A('10.0.0.3'))
|
||||
return d
|
||||
|
||||
|
||||
def test_missingGlue(self):
|
||||
"""
|
||||
If an intermediate response includes no glue records for the
|
||||
authorities, separate queries are made to find those addresses.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
(b'foo.example.com', A): {
|
||||
'authority': [(b'foo.example.com', Record_NS(b'ns1.example.org'))],
|
||||
# Conspicuous lack of an additional section naming ns1.example.com
|
||||
},
|
||||
(b'ns1.example.org', A): {
|
||||
'answers': [(b'ns1.example.org', Record_A('10.0.0.1'))],
|
||||
},
|
||||
},
|
||||
('10.0.0.1', 53): {
|
||||
(b'foo.example.com', A): {
|
||||
'answers': [(b'foo.example.com', Record_A('10.0.0.2'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'foo.example.com')
|
||||
d.addCallback(getOneAddress)
|
||||
d.addCallback(self.assertEqual, '10.0.0.2')
|
||||
return d
|
||||
|
||||
|
||||
def test_missingName(self):
|
||||
"""
|
||||
If a name is missing, L{Resolver.lookupAddress} returns a L{Deferred}
|
||||
which fails with L{DNSNameError}.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
(b'foo.example.com', A): {
|
||||
'rCode': ENAME,
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'foo.example.com')
|
||||
return self.assertFailure(d, DNSNameError)
|
||||
|
||||
|
||||
def test_answerless(self):
|
||||
"""
|
||||
If a query is responded to with no answers or nameserver records, the
|
||||
L{Deferred} returned by L{Resolver.lookupAddress} fires with
|
||||
L{ResolverError}.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
('example.com', A): {
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress('example.com')
|
||||
return self.assertFailure(d, ResolverError)
|
||||
|
||||
|
||||
def test_delegationLookupError(self):
|
||||
"""
|
||||
If there is an error resolving the nameserver in a delegation response,
|
||||
the L{Deferred} returned by L{Resolver.lookupAddress} fires with that
|
||||
error.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
('example.com', A): {
|
||||
'authority': [('example.com', Record_NS('ns1.example.com'))],
|
||||
},
|
||||
('ns1.example.com', A): {
|
||||
'rCode': ENAME,
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress('example.com')
|
||||
return self.assertFailure(d, DNSNameError)
|
||||
|
||||
|
||||
def test_delegationLookupEmpty(self):
|
||||
"""
|
||||
If there are no records in the response to a lookup of a delegation
|
||||
nameserver, the L{Deferred} returned by L{Resolver.lookupAddress} fires
|
||||
with L{ResolverError}.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
('example.com', A): {
|
||||
'authority': [('example.com', Record_NS('ns1.example.com'))],
|
||||
},
|
||||
('ns1.example.com', A): {
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress('example.com')
|
||||
return self.assertFailure(d, ResolverError)
|
||||
|
||||
|
||||
def test_lookupNameservers(self):
|
||||
"""
|
||||
L{Resolver.lookupNameservers} is like L{Resolver.lookupAddress}, except
|
||||
it queries for I{NS} records instead of I{A} records.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
(b'example.com', A): {
|
||||
'rCode': ENAME,
|
||||
},
|
||||
(b'example.com', NS): {
|
||||
'answers': [(b'example.com', Record_NS(b'ns1.example.com'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupNameservers(b'example.com')
|
||||
def getOneName(results):
|
||||
ans, auth, add = results
|
||||
return ans[0].payload.name
|
||||
d.addCallback(getOneName)
|
||||
d.addCallback(self.assertEqual, Name(b'ns1.example.com'))
|
||||
return d
|
||||
|
||||
|
||||
def test_returnCanonicalName(self):
|
||||
"""
|
||||
If a I{CNAME} record is encountered as the answer to a query for
|
||||
another record type, that record is returned as the answer.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
(b'example.com', A): {
|
||||
'answers': [(b'example.com', Record_CNAME(b'example.net')),
|
||||
(b'example.net', Record_A('10.0.0.7'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'example.com')
|
||||
d.addCallback(lambda results: results[0]) # Get the answer section
|
||||
d.addCallback(
|
||||
self.assertEqual,
|
||||
[RRHeader(b'example.com', CNAME, payload=Record_CNAME(b'example.net')),
|
||||
RRHeader(b'example.net', A, payload=Record_A('10.0.0.7'))])
|
||||
return d
|
||||
|
||||
|
||||
def test_followCanonicalName(self):
|
||||
"""
|
||||
If no record of the requested type is included in a response, but a
|
||||
I{CNAME} record for the query name is included, queries are made to
|
||||
resolve the value of the I{CNAME}.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
('example.com', A): {
|
||||
'answers': [('example.com', Record_CNAME('example.net'))],
|
||||
},
|
||||
('example.net', A): {
|
||||
'answers': [('example.net', Record_A('10.0.0.5'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress('example.com')
|
||||
d.addCallback(lambda results: results[0]) # Get the answer section
|
||||
d.addCallback(
|
||||
self.assertEqual,
|
||||
[RRHeader('example.com', CNAME, payload=Record_CNAME('example.net')),
|
||||
RRHeader('example.net', A, payload=Record_A('10.0.0.5'))])
|
||||
return d
|
||||
|
||||
|
||||
def test_detectCanonicalNameLoop(self):
|
||||
"""
|
||||
If there is a cycle between I{CNAME} records in a response, this is
|
||||
detected and the L{Deferred} returned by the lookup method fails
|
||||
with L{ResolverError}.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
('example.com', A): {
|
||||
'answers': [('example.com', Record_CNAME('example.net')),
|
||||
('example.net', Record_CNAME('example.com'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress('example.com')
|
||||
return self.assertFailure(d, ResolverError)
|
||||
|
||||
|
||||
def test_boundedQueries(self):
|
||||
"""
|
||||
L{Resolver.lookupAddress} won't issue more queries following
|
||||
delegations than the limit passed to its initializer.
|
||||
"""
|
||||
servers = {
|
||||
('1.1.2.3', 53): {
|
||||
# First query - force it to start over with a name lookup of
|
||||
# ns1.example.com
|
||||
('example.com', A): {
|
||||
'authority': [('example.com', Record_NS('ns1.example.com'))],
|
||||
},
|
||||
# Second query - let it resume the original lookup with the
|
||||
# address of the nameserver handling the delegation.
|
||||
('ns1.example.com', A): {
|
||||
'answers': [('ns1.example.com', Record_A('10.0.0.2'))],
|
||||
},
|
||||
},
|
||||
('10.0.0.2', 53): {
|
||||
# Third query - let it jump straight to asking the
|
||||
# delegation server by including its address here (different
|
||||
# case from the first query).
|
||||
('example.com', A): {
|
||||
'authority': [('example.com', Record_NS('ns2.example.com'))],
|
||||
'additional': [('ns2.example.com', Record_A('10.0.0.3'))],
|
||||
},
|
||||
},
|
||||
('10.0.0.3', 53): {
|
||||
# Fourth query - give it the answer, we're done.
|
||||
('example.com', A): {
|
||||
'answers': [('example.com', Record_A('10.0.0.4'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Make two resolvers. One which is allowed to make 3 queries
|
||||
# maximum, and so will fail, and on which may make 4, and so should
|
||||
# succeed.
|
||||
failer = self._getResolver(servers, 3)
|
||||
failD = self.assertFailure(
|
||||
failer.lookupAddress('example.com'), ResolverError)
|
||||
|
||||
succeeder = self._getResolver(servers, 4)
|
||||
succeedD = succeeder.lookupAddress('example.com')
|
||||
succeedD.addCallback(getOnePayload)
|
||||
succeedD.addCallback(self.assertEqual, Record_A('10.0.0.4'))
|
||||
|
||||
return gatherResults([failD, succeedD])
|
||||
|
||||
|
||||
|
||||
class ResolverFactoryArguments(Exception):
|
||||
"""
|
||||
Raised by L{raisingResolverFactory} with the *args and **kwargs passed to
|
||||
that function.
|
||||
"""
|
||||
def __init__(self, args, kwargs):
|
||||
"""
|
||||
Store the supplied args and kwargs as attributes.
|
||||
|
||||
@param args: Positional arguments.
|
||||
@param kwargs: Keyword arguments.
|
||||
"""
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
|
||||
|
||||
def raisingResolverFactory(*args, **kwargs):
|
||||
"""
|
||||
Raise a L{ResolverFactoryArguments} exception containing the
|
||||
positional and keyword arguments passed to resolverFactory.
|
||||
|
||||
@param args: A L{list} of all the positional arguments supplied by
|
||||
the caller.
|
||||
|
||||
@param kwargs: A L{list} of all the keyword arguments supplied by
|
||||
the caller.
|
||||
"""
|
||||
raise ResolverFactoryArguments(args, kwargs)
|
||||
|
||||
|
||||
|
||||
class RootResolverResolverFactoryTests(TestCase):
|
||||
"""
|
||||
Tests for L{root.Resolver._resolverFactory}.
|
||||
"""
|
||||
def test_resolverFactoryArgumentPresent(self):
|
||||
"""
|
||||
L{root.Resolver.__init__} accepts a C{resolverFactory}
|
||||
argument and assigns it to C{self._resolverFactory}.
|
||||
"""
|
||||
r = Resolver(hints=[None], resolverFactory=raisingResolverFactory)
|
||||
self.assertIdentical(r._resolverFactory, raisingResolverFactory)
|
||||
|
||||
|
||||
def test_resolverFactoryArgumentAbsent(self):
|
||||
"""
|
||||
L{root.Resolver.__init__} sets L{client.Resolver} as the
|
||||
C{_resolverFactory} if a C{resolverFactory} argument is not
|
||||
supplied.
|
||||
"""
|
||||
r = Resolver(hints=[None])
|
||||
self.assertIdentical(r._resolverFactory, client.Resolver)
|
||||
|
||||
|
||||
def test_resolverFactoryOnlyExpectedArguments(self):
|
||||
"""
|
||||
L{root.Resolver._resolverFactory} is supplied with C{reactor} and
|
||||
C{servers} keyword arguments.
|
||||
"""
|
||||
dummyReactor = object()
|
||||
r = Resolver(hints=['192.0.2.101'],
|
||||
resolverFactory=raisingResolverFactory,
|
||||
reactor=dummyReactor)
|
||||
|
||||
e = self.assertRaises(ResolverFactoryArguments,
|
||||
r.lookupAddress, 'example.com')
|
||||
|
||||
self.assertEqual(
|
||||
((), {'reactor': dummyReactor, 'servers': [('192.0.2.101', 53)]}),
|
||||
(e.args, e.kwargs)
|
||||
)
|
||||
|
||||
|
||||
|
||||
ROOT_SERVERS = [
|
||||
'a.root-servers.net',
|
||||
'b.root-servers.net',
|
||||
'c.root-servers.net',
|
||||
'd.root-servers.net',
|
||||
'e.root-servers.net',
|
||||
'f.root-servers.net',
|
||||
'g.root-servers.net',
|
||||
'h.root-servers.net',
|
||||
'i.root-servers.net',
|
||||
'j.root-servers.net',
|
||||
'k.root-servers.net',
|
||||
'l.root-servers.net',
|
||||
'm.root-servers.net']
|
||||
|
||||
|
||||
|
||||
@implementer(IResolverSimple)
|
||||
class StubResolver(object):
|
||||
"""
|
||||
An L{IResolverSimple} implementer which traces all getHostByName
|
||||
calls and their deferred results. The deferred results can be
|
||||
accessed and fired synchronously.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""
|
||||
@type calls: L{list} of L{tuple} containing C{args} and
|
||||
C{kwargs} supplied to C{getHostByName} calls.
|
||||
@type pendingResults: L{list} of L{Deferred} returned by
|
||||
C{getHostByName}.
|
||||
"""
|
||||
self.calls = []
|
||||
self.pendingResults = []
|
||||
|
||||
|
||||
def getHostByName(self, *args, **kwargs):
|
||||
"""
|
||||
A fake implementation of L{IResolverSimple.getHostByName}
|
||||
|
||||
@param args: A L{list} of all the positional arguments supplied by
|
||||
the caller.
|
||||
|
||||
@param kwargs: A L{list} of all the keyword arguments supplied by
|
||||
the caller.
|
||||
|
||||
@return: A L{Deferred} which may be fired later from the test
|
||||
fixture.
|
||||
"""
|
||||
self.calls.append((args, kwargs))
|
||||
d = Deferred()
|
||||
self.pendingResults.append(d)
|
||||
return d
|
||||
|
||||
|
||||
|
||||
verifyClass(IResolverSimple, StubResolver)
|
||||
|
||||
|
||||
|
||||
class BootstrapTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{root.bootstrap}
|
||||
"""
|
||||
def test_returnsDeferredResolver(self):
|
||||
"""
|
||||
L{root.bootstrap} returns an object which is initially a
|
||||
L{root.DeferredResolver}.
|
||||
"""
|
||||
deferredResolver = root.bootstrap(StubResolver())
|
||||
self.assertIsInstance(deferredResolver, root.DeferredResolver)
|
||||
|
||||
|
||||
def test_resolves13RootServers(self):
|
||||
"""
|
||||
The L{IResolverSimple} supplied to L{root.bootstrap} is used to lookup
|
||||
the IP addresses of the 13 root name servers.
|
||||
"""
|
||||
stubResolver = StubResolver()
|
||||
root.bootstrap(stubResolver)
|
||||
self.assertEqual(
|
||||
stubResolver.calls,
|
||||
[((s,), {}) for s in ROOT_SERVERS])
|
||||
|
||||
|
||||
def test_becomesResolver(self):
|
||||
"""
|
||||
The L{root.DeferredResolver} initially returned by L{root.bootstrap}
|
||||
becomes a L{root.Resolver} when the supplied resolver has successfully
|
||||
looked up all root hints.
|
||||
"""
|
||||
stubResolver = StubResolver()
|
||||
deferredResolver = root.bootstrap(stubResolver)
|
||||
for d in stubResolver.pendingResults:
|
||||
d.callback('192.0.2.101')
|
||||
self.assertIsInstance(deferredResolver, Resolver)
|
||||
|
||||
|
||||
def test_resolverReceivesRootHints(self):
|
||||
"""
|
||||
The L{root.Resolver} which eventually replaces L{root.DeferredResolver}
|
||||
is supplied with the IP addresses of the 13 root servers.
|
||||
"""
|
||||
stubResolver = StubResolver()
|
||||
deferredResolver = root.bootstrap(stubResolver)
|
||||
for d in stubResolver.pendingResults:
|
||||
d.callback('192.0.2.101')
|
||||
self.assertEqual(deferredResolver.hints, ['192.0.2.101'] * 13)
|
||||
|
||||
|
||||
def test_continuesWhenSomeRootHintsFail(self):
|
||||
"""
|
||||
The L{root.Resolver} is eventually created, even if some of the root
|
||||
hint lookups fail. Only the working root hint IP addresses are supplied
|
||||
to the L{root.Resolver}.
|
||||
"""
|
||||
stubResolver = StubResolver()
|
||||
deferredResolver = root.bootstrap(stubResolver)
|
||||
results = iter(stubResolver.pendingResults)
|
||||
d1 = next(results)
|
||||
for d in results:
|
||||
d.callback('192.0.2.101')
|
||||
d1.errback(TimeoutError())
|
||||
|
||||
def checkHints(res):
|
||||
self.assertEqual(deferredResolver.hints, ['192.0.2.101'] * 12)
|
||||
d1.addBoth(checkHints)
|
||||
|
||||
|
||||
def test_continuesWhenAllRootHintsFail(self):
|
||||
"""
|
||||
The L{root.Resolver} is eventually created, even if all of the root hint
|
||||
lookups fail. Pending and new lookups will then fail with
|
||||
AttributeError.
|
||||
"""
|
||||
stubResolver = StubResolver()
|
||||
deferredResolver = root.bootstrap(stubResolver)
|
||||
results = iter(stubResolver.pendingResults)
|
||||
d1 = next(results)
|
||||
for d in results:
|
||||
d.errback(TimeoutError())
|
||||
d1.errback(TimeoutError())
|
||||
|
||||
def checkHints(res):
|
||||
self.assertEqual(deferredResolver.hints, [])
|
||||
d1.addBoth(checkHints)
|
||||
|
||||
self.addCleanup(self.flushLoggedErrors, TimeoutError)
|
||||
|
||||
|
||||
def test_passesResolverFactory(self):
|
||||
"""
|
||||
L{root.bootstrap} accepts a C{resolverFactory} argument which is passed
|
||||
as an argument to L{root.Resolver} when it has successfully looked up
|
||||
root hints.
|
||||
"""
|
||||
stubResolver = StubResolver()
|
||||
deferredResolver = root.bootstrap(
|
||||
stubResolver, resolverFactory=raisingResolverFactory)
|
||||
|
||||
for d in stubResolver.pendingResults:
|
||||
d.callback('192.0.2.101')
|
||||
|
||||
self.assertIdentical(
|
||||
deferredResolver._resolverFactory, raisingResolverFactory)
|
||||
|
||||
|
||||
|
||||
class StubDNSDatagramProtocol:
|
||||
"""
|
||||
A do-nothing stand-in for L{DNSDatagramProtocol} which can be used to avoid
|
||||
network traffic in tests where that kind of thing doesn't matter.
|
||||
"""
|
||||
def query(self, *a, **kw):
|
||||
return Deferred()
|
||||
|
||||
|
||||
|
||||
_retrySuppression = util.suppress(
|
||||
category=DeprecationWarning,
|
||||
message=(
|
||||
'twisted.names.root.retry is deprecated since Twisted 10.0. Use a '
|
||||
'Resolver object for retry logic.'))
|
||||
1265
Linux/lib/python2.7/site-packages/twisted/names/test/test_server.py
Normal file
1265
Linux/lib/python2.7/site-packages/twisted/names/test/test_server.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,181 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for L{twisted.names.srvconnect}.
|
||||
"""
|
||||
|
||||
from twisted.internet import defer, protocol
|
||||
from twisted.names import client, dns, srvconnect
|
||||
from twisted.names.common import ResolverBase
|
||||
from twisted.names.error import DNSNameError
|
||||
from twisted.internet.error import DNSLookupError, ServiceNameUnknownError
|
||||
from twisted.trial import unittest
|
||||
from twisted.test.proto_helpers import MemoryReactor
|
||||
|
||||
|
||||
class FakeResolver(ResolverBase):
|
||||
"""
|
||||
Resolver that only gives out one given result.
|
||||
|
||||
Either L{results} or L{failure} must be set and will be used for
|
||||
the return value of L{_lookup}
|
||||
|
||||
@ivar results: List of L{dns.RRHeader} for the desired result.
|
||||
@type results: C{list}
|
||||
@ivar failure: Failure with an exception from L{twisted.names.error}.
|
||||
@type failure: L{Failure<twisted.python.failure.Failure>}
|
||||
"""
|
||||
|
||||
def __init__(self, results=None, failure=None):
|
||||
self.results = results
|
||||
self.failure = failure
|
||||
|
||||
def _lookup(self, name, cls, qtype, timeout):
|
||||
"""
|
||||
Return the result or failure on lookup.
|
||||
"""
|
||||
if self.results is not None:
|
||||
return defer.succeed((self.results, [], []))
|
||||
else:
|
||||
return defer.fail(self.failure)
|
||||
|
||||
|
||||
|
||||
class DummyFactory(protocol.ClientFactory):
|
||||
"""
|
||||
Dummy client factory that stores the reason of connection failure.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.reason = None
|
||||
|
||||
def clientConnectionFailed(self, connector, reason):
|
||||
self.reason = reason
|
||||
|
||||
|
||||
|
||||
class SRVConnectorTest(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{srvconnect.SRVConnector}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.patch(client, 'theResolver', FakeResolver())
|
||||
self.reactor = MemoryReactor()
|
||||
self.factory = DummyFactory()
|
||||
self.connector = srvconnect.SRVConnector(self.reactor, 'xmpp-server',
|
||||
'example.org', self.factory)
|
||||
|
||||
|
||||
def test_SRVPresent(self):
|
||||
"""
|
||||
Test connectTCP gets called with the address from the SRV record.
|
||||
"""
|
||||
payload = dns.Record_SRV(port=6269, target='host.example.org', ttl=60)
|
||||
client.theResolver.results = [dns.RRHeader(name='example.org',
|
||||
type=dns.SRV,
|
||||
cls=dns.IN, ttl=60,
|
||||
payload=payload)]
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIs(None, self.factory.reason)
|
||||
self.assertEqual(
|
||||
self.reactor.tcpClients.pop()[:2], ('host.example.org', 6269))
|
||||
|
||||
|
||||
def test_SRVNotPresent(self):
|
||||
"""
|
||||
Test connectTCP gets called with fallback parameters on NXDOMAIN.
|
||||
"""
|
||||
client.theResolver.failure = DNSNameError('example.org')
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIs(None, self.factory.reason)
|
||||
self.assertEqual(
|
||||
self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
|
||||
|
||||
|
||||
def test_SRVNoResult(self):
|
||||
"""
|
||||
Test connectTCP gets called with fallback parameters on empty result.
|
||||
"""
|
||||
client.theResolver.results = []
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIs(None, self.factory.reason)
|
||||
self.assertEqual(
|
||||
self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
|
||||
|
||||
|
||||
def test_SRVNoResultUnknownServiceDefaultPort(self):
|
||||
"""
|
||||
connectTCP gets called with default port if the service is not defined.
|
||||
"""
|
||||
self.connector = srvconnect.SRVConnector(self.reactor,
|
||||
'thisbetternotexist',
|
||||
'example.org', self.factory,
|
||||
defaultPort=5222)
|
||||
|
||||
client.theResolver.failure = ServiceNameUnknownError()
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIs(None, self.factory.reason)
|
||||
self.assertEqual(
|
||||
self.reactor.tcpClients.pop()[:2], ('example.org', 5222))
|
||||
|
||||
|
||||
def test_SRVNoResultUnknownServiceNoDefaultPort(self):
|
||||
"""
|
||||
Connect fails on no result, unknown service and no default port.
|
||||
"""
|
||||
self.connector = srvconnect.SRVConnector(self.reactor,
|
||||
'thisbetternotexist',
|
||||
'example.org', self.factory)
|
||||
|
||||
client.theResolver.failure = ServiceNameUnknownError()
|
||||
self.connector.connect()
|
||||
|
||||
self.assertTrue(self.factory.reason.check(ServiceNameUnknownError))
|
||||
|
||||
|
||||
def test_SRVBadResult(self):
|
||||
"""
|
||||
Test connectTCP gets called with fallback parameters on bad result.
|
||||
"""
|
||||
client.theResolver.results = [dns.RRHeader(name='example.org',
|
||||
type=dns.CNAME,
|
||||
cls=dns.IN, ttl=60,
|
||||
payload=None)]
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIs(None, self.factory.reason)
|
||||
self.assertEqual(
|
||||
self.reactor.tcpClients.pop()[:2], ('example.org', 'xmpp-server'))
|
||||
|
||||
|
||||
def test_SRVNoService(self):
|
||||
"""
|
||||
Test that connecting fails when no service is present.
|
||||
"""
|
||||
payload = dns.Record_SRV(port=5269, target='.', ttl=60)
|
||||
client.theResolver.results = [dns.RRHeader(name='example.org',
|
||||
type=dns.SRV,
|
||||
cls=dns.IN, ttl=60,
|
||||
payload=payload)]
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIsNot(None, self.factory.reason)
|
||||
self.factory.reason.trap(DNSLookupError)
|
||||
self.assertEqual(self.reactor.tcpClients, [])
|
||||
|
||||
|
||||
def test_unicodeDomain(self):
|
||||
"""
|
||||
L{srvconnect.SRVConnector} automatically encodes unicode domain using
|
||||
C{idna} encoding.
|
||||
"""
|
||||
self.connector = srvconnect.SRVConnector(
|
||||
self.reactor, 'xmpp-client', u'\u00e9chec.example.org',
|
||||
self.factory)
|
||||
self.assertIsInstance(self.connector.domain, bytes)
|
||||
self.assertEqual(b'xn--chec-9oa.example.org', self.connector.domain)
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.names.tap}.
|
||||
"""
|
||||
|
||||
from twisted.trial.unittest import TestCase
|
||||
from twisted.python.usage import UsageError
|
||||
from twisted.names.tap import Options, _buildResolvers
|
||||
from twisted.names.dns import PORT
|
||||
from twisted.names.secondary import SecondaryAuthorityService
|
||||
from twisted.names.resolve import ResolverChain
|
||||
from twisted.names.client import Resolver
|
||||
|
||||
class OptionsTests(TestCase):
|
||||
"""
|
||||
Tests for L{Options}, defining how command line arguments for the DNS server
|
||||
are parsed.
|
||||
"""
|
||||
def test_malformedSecondary(self):
|
||||
"""
|
||||
If the value supplied for an I{--secondary} option does not provide a
|
||||
server IP address, optional port number, and domain name,
|
||||
L{Options.parseOptions} raises L{UsageError}.
|
||||
"""
|
||||
options = Options()
|
||||
self.assertRaises(
|
||||
UsageError, options.parseOptions, ['--secondary', ''])
|
||||
self.assertRaises(
|
||||
UsageError, options.parseOptions, ['--secondary', '1.2.3.4'])
|
||||
self.assertRaises(
|
||||
UsageError, options.parseOptions, ['--secondary', '1.2.3.4:hello'])
|
||||
self.assertRaises(
|
||||
UsageError, options.parseOptions,
|
||||
['--secondary', '1.2.3.4:hello/example.com'])
|
||||
|
||||
|
||||
def test_secondary(self):
|
||||
"""
|
||||
An argument of the form C{"ip/domain"} is parsed by L{Options} for the
|
||||
I{--secondary} option and added to its list of secondaries, using the
|
||||
default DNS port number.
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions(['--secondary', '1.2.3.4/example.com'])
|
||||
self.assertEqual(
|
||||
[(('1.2.3.4', PORT), ['example.com'])], options.secondaries)
|
||||
|
||||
|
||||
def test_secondaryExplicitPort(self):
|
||||
"""
|
||||
An argument of the form C{"ip:port/domain"} can be used to specify an
|
||||
alternate port number for for which to act as a secondary.
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions(['--secondary', '1.2.3.4:5353/example.com'])
|
||||
self.assertEqual(
|
||||
[(('1.2.3.4', 5353), ['example.com'])], options.secondaries)
|
||||
|
||||
|
||||
def test_secondaryAuthorityServices(self):
|
||||
"""
|
||||
After parsing I{--secondary} options, L{Options} constructs a
|
||||
L{SecondaryAuthorityService} instance for each configured secondary.
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions(['--secondary', '1.2.3.4:5353/example.com',
|
||||
'--secondary', '1.2.3.5:5354/example.com'])
|
||||
self.assertEqual(len(options.svcs), 2)
|
||||
secondary = options.svcs[0]
|
||||
self.assertIsInstance(options.svcs[0], SecondaryAuthorityService)
|
||||
self.assertEqual(secondary.primary, '1.2.3.4')
|
||||
self.assertEqual(secondary._port, 5353)
|
||||
secondary = options.svcs[1]
|
||||
self.assertIsInstance(options.svcs[1], SecondaryAuthorityService)
|
||||
self.assertEqual(secondary.primary, '1.2.3.5')
|
||||
self.assertEqual(secondary._port, 5354)
|
||||
|
||||
|
||||
def test_recursiveConfiguration(self):
|
||||
"""
|
||||
Recursive DNS lookups, if enabled, should be a last-resort option.
|
||||
Any other lookup method (cache, local lookup, etc.) should take
|
||||
precedence over recursive lookups
|
||||
"""
|
||||
options = Options()
|
||||
options.parseOptions(['--hosts-file', 'hosts.txt', '--recursive'])
|
||||
ca, cl = _buildResolvers(options)
|
||||
|
||||
# Extra cleanup, necessary on POSIX because client.Resolver doesn't know
|
||||
# when to stop parsing resolv.conf. See #NNN for improving this.
|
||||
for x in cl:
|
||||
if isinstance(x, ResolverChain):
|
||||
recurser = x.resolvers[-1]
|
||||
if isinstance(recurser, Resolver):
|
||||
recurser._parseCall.cancel()
|
||||
|
||||
self.assertIsInstance(cl[-1], ResolverChain)
|
||||
Loading…
Add table
Add a link
Reference in a new issue