Ausgabe der neuen DB Einträge
This commit is contained in:
parent
bad48e1627
commit
cfbbb9ee3d
2399 changed files with 843193 additions and 43 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 CachingTests(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)
|
||||
1272
venv/lib/python3.9/site-packages/twisted/names/test/test_client.py
Normal file
1272
venv/lib/python3.9/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)
|
||||
4988
venv/lib/python3.9/site-packages/twisted/names/test/test_dns.py
Normal file
4988
venv/lib/python3.9/site-packages/twisted/names/test/test_dns.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,168 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.names} example scripts.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import sys
|
||||
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.trial.unittest import SkipTest, TestCase
|
||||
from twisted.python.compat import NativeStringIO
|
||||
|
||||
|
||||
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()
|
||||
|
||||
# Python usually expects native strs to be written to sys.stdout/stderr
|
||||
self.fakeErr = NativeStringIO()
|
||||
self.patch(sys, 'stderr', self.fakeErr)
|
||||
self.fakeOut = NativeStringIO()
|
||||
self.patch(sys, 'stdout', self.fakeOut)
|
||||
|
||||
# Get documentation root
|
||||
here = (
|
||||
FilePath(__file__)
|
||||
.parent().parent().parent().parent()
|
||||
.child('docs')
|
||||
)
|
||||
|
||||
# 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.
|
||||
"""
|
||||
with self.examplePath.open() as f:
|
||||
self.assertEqual(f.readline().rstrip(), b'#!/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}, L{None} is returned.
|
||||
"""
|
||||
hosts = self.path()
|
||||
hosts.setContent(
|
||||
b"10.2.3.4 foo.example.com\n")
|
||||
self.assertIsNone(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 HostsTests(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 test_notImplemented(self):
|
||||
return self.assertFailure(self.resolver.lookupMailExchange(b'EXAMPLE'),
|
||||
NotImplementedError)
|
||||
|
||||
|
||||
def test_query(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)
|
||||
1256
venv/lib/python3.9/site-packages/twisted/names/test/test_names.py
Normal file
1256
venv/lib/python3.9/site-packages/twisted/names/test/test_names.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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,734 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for Twisted.names' root resolver.
|
||||
"""
|
||||
|
||||
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.interfaces import 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
|
||||
from twisted.names.test.test_util import MemoryReactor
|
||||
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
|
||||
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): {
|
||||
(b'foo.example.com', A): {
|
||||
'answers': [(b'foo.example.com', badClass)],
|
||||
'authority': [(b'foo.example.com', Record_NS(b'ns1.example.com'))],
|
||||
'additional': [(b'ns1.example.com', Record_A('10.0.0.2'))],
|
||||
},
|
||||
},
|
||||
('10.0.0.2', 53): {
|
||||
(b'foo.example.com', A): {
|
||||
'answers': [(b'foo.example.com', Record_A('10.0.0.3'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'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): {
|
||||
(b'example.com', A): {
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'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): {
|
||||
(b'example.com', A): {
|
||||
'authority': [(b'example.com', Record_NS(b'ns1.example.com'))],
|
||||
},
|
||||
(b'ns1.example.com', A): {
|
||||
'rCode': ENAME,
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'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): {
|
||||
(b'example.com', A): {
|
||||
'authority': [(b'example.com', Record_NS(b'ns1.example.com'))],
|
||||
},
|
||||
(b'ns1.example.com', A): {
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'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): {
|
||||
(b'example.com', A): {
|
||||
'answers': [(b'example.com', Record_CNAME(b'example.net'))],
|
||||
},
|
||||
(b'example.net', A): {
|
||||
'answers': [(b'example.net', Record_A('10.0.0.5'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
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.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): {
|
||||
(b'example.com', A): {
|
||||
'answers': [(b'example.com', Record_CNAME(b'example.net')),
|
||||
(b'example.net', Record_CNAME(b'example.com'))],
|
||||
},
|
||||
},
|
||||
}
|
||||
resolver = self._getResolver(servers)
|
||||
d = resolver.lookupAddress(b'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
|
||||
(b'example.com', A): {
|
||||
'authority': [(b'example.com', Record_NS(b'ns1.example.com'))],
|
||||
},
|
||||
# Second query - let it resume the original lookup with the
|
||||
# address of the nameserver handling the delegation.
|
||||
(b'ns1.example.com', A): {
|
||||
'answers': [(b'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).
|
||||
(b'example.com', A): {
|
||||
'authority': [(b'example.com', Record_NS(b'ns2.example.com'))],
|
||||
'additional': [(b'ns2.example.com', Record_A('10.0.0.3'))],
|
||||
},
|
||||
},
|
||||
('10.0.0.3', 53): {
|
||||
# Fourth query - give it the answer, we're done.
|
||||
(b'example.com', A): {
|
||||
'answers': [(b'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(b'example.com'), ResolverError)
|
||||
|
||||
succeeder = self._getResolver(servers, 4)
|
||||
succeedD = succeeder.lookupAddress(b'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.assertIs(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.assertIs(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.assertIs(
|
||||
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.'))
|
||||
1266
venv/lib/python3.9/site-packages/twisted/names/test/test_server.py
Normal file
1266
venv/lib/python3.9/site-packages/twisted/names/test/test_server.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,302 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for L{twisted.names.srvconnect}.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
import random
|
||||
|
||||
from zope.interface.verify import verifyObject
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.internet import defer, protocol
|
||||
from twisted.internet.error import DNSLookupError, ServiceNameUnknownError
|
||||
from twisted.internet.interfaces import IConnector
|
||||
from twisted.names import client, dns, srvconnect
|
||||
from twisted.names.common import ResolverBase
|
||||
from twisted.names.error import DNSNameError
|
||||
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
|
||||
self.lookups = []
|
||||
|
||||
def _lookup(self, name, cls, qtype, timeout):
|
||||
"""
|
||||
Return the result or failure on lookup.
|
||||
"""
|
||||
self.lookups.append((name, cls, qtype, timeout))
|
||||
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 SRVConnectorTests(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)
|
||||
self.randIntArgs = []
|
||||
self.randIntResults = []
|
||||
|
||||
|
||||
def _randint(self, min, max):
|
||||
"""
|
||||
Fake randint.
|
||||
|
||||
Returns the first element of L{randIntResults} and records the
|
||||
arguments passed to it in L{randIntArgs}.
|
||||
|
||||
@param min: Lower bound of the random number.
|
||||
@type min: L{int}
|
||||
|
||||
@param max: Higher bound of the random number.
|
||||
@type max: L{int}
|
||||
|
||||
@return: Fake random number from L{randIntResults}.
|
||||
@rtype: L{int}
|
||||
"""
|
||||
self.randIntArgs.append((min, max))
|
||||
return self.randIntResults.pop(0)
|
||||
|
||||
|
||||
def test_interface(self):
|
||||
"""
|
||||
L{srvconnect.SRVConnector} implements L{IConnector}.
|
||||
"""
|
||||
verifyObject(IConnector, self.connector)
|
||||
|
||||
|
||||
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.assertIsNone(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(b'example.org')
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIsNone(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.assertIsNone(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.assertIsNone(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.assertIsNone(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=b'.', ttl=60)
|
||||
client.theResolver.results = [dns.RRHeader(name='example.org',
|
||||
type=dns.SRV,
|
||||
cls=dns.IN, ttl=60,
|
||||
payload=payload)]
|
||||
self.connector.connect()
|
||||
|
||||
self.assertIsNotNone(self.factory.reason)
|
||||
self.factory.reason.trap(DNSLookupError)
|
||||
self.assertEqual(self.reactor.tcpClients, [])
|
||||
|
||||
|
||||
def test_SRVLookupName(self):
|
||||
"""
|
||||
The lookup name is a native string from service, protocol and domain.
|
||||
"""
|
||||
client.theResolver.results = []
|
||||
self.connector.connect()
|
||||
|
||||
name = client.theResolver.lookups[-1][0]
|
||||
self.assertEqual(b'_xmpp-server._tcp.example.org', name)
|
||||
|
||||
|
||||
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.assertEqual(b'xn--chec-9oa.example.org', self.connector.domain)
|
||||
|
||||
|
||||
def test_pickServerWeights(self):
|
||||
"""
|
||||
pickServer calculates running sum of weights and calls randint.
|
||||
|
||||
This exercises the server selection algorithm specified in RFC 2782 by
|
||||
preparing fake L{random.randint} results and checking the values it was
|
||||
called with.
|
||||
"""
|
||||
record1 = dns.Record_SRV(10, 10, 5222, 'host1.example.org')
|
||||
record2 = dns.Record_SRV(10, 20, 5222, 'host2.example.org')
|
||||
|
||||
self.connector.orderedServers = [record1, record2]
|
||||
self.connector.servers = []
|
||||
self.patch(random, 'randint', self._randint)
|
||||
|
||||
# 1st round
|
||||
self.randIntResults = [11, 0]
|
||||
|
||||
self.connector.pickServer()
|
||||
self.assertEqual(self.randIntArgs[0], (0, 30))
|
||||
|
||||
self.connector.pickServer()
|
||||
self.assertEqual(self.randIntArgs[1], (0, 10))
|
||||
|
||||
# 2nd round
|
||||
self.randIntResults = [10, 0]
|
||||
|
||||
self.connector.pickServer()
|
||||
self.assertEqual(self.randIntArgs[2], (0, 30))
|
||||
|
||||
self.connector.pickServer()
|
||||
self.assertEqual(self.randIntArgs[3], (0, 20))
|
||||
|
||||
|
||||
def test_pickServerSamePriorities(self):
|
||||
"""
|
||||
Two records with equal priorities compare on weight (ascending).
|
||||
"""
|
||||
record1 = dns.Record_SRV(10, 10, 5222, 'host1.example.org')
|
||||
record2 = dns.Record_SRV(10, 20, 5222, 'host2.example.org')
|
||||
|
||||
self.connector.orderedServers = [record2, record1]
|
||||
self.connector.servers = []
|
||||
self.patch(random, 'randint', self._randint)
|
||||
self.randIntResults = [0, 0]
|
||||
|
||||
self.assertEqual(('host1.example.org', 5222),
|
||||
self.connector.pickServer())
|
||||
|
||||
self.assertEqual(('host2.example.org', 5222),
|
||||
self.connector.pickServer())
|
||||
|
||||
|
||||
def test_srvDifferentPriorities(self):
|
||||
"""
|
||||
Two records with differing priorities compare on priority (ascending).
|
||||
"""
|
||||
record1 = dns.Record_SRV(10, 0, 5222, 'host1.example.org')
|
||||
record2 = dns.Record_SRV(20, 0, 5222, 'host2.example.org')
|
||||
|
||||
self.connector.orderedServers = [record2, record1]
|
||||
self.connector.servers = []
|
||||
self.patch(random, 'randint', self._randint)
|
||||
self.randIntResults = [0, 0]
|
||||
|
||||
self.assertEqual(('host1.example.org', 5222),
|
||||
self.connector.pickServer())
|
||||
|
||||
self.assertEqual(('host2.example.org', 5222),
|
||||
self.connector.pickServer())
|
||||
123
venv/lib/python3.9/site-packages/twisted/names/test/test_tap.py
Normal file
123
venv/lib/python3.9/site-packages/twisted/names/test/test_tap.py
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.names.tap}.
|
||||
"""
|
||||
|
||||
from twisted.internet.base import ThreadedResolver
|
||||
from twisted.names.client import Resolver
|
||||
from twisted.names.dns import PORT
|
||||
from twisted.names.resolve import ResolverChain
|
||||
from twisted.names.secondary import SecondaryAuthorityService
|
||||
from twisted.names.tap import Options, _buildResolvers
|
||||
from twisted.python.compat import _PY3
|
||||
from twisted.python.runtime import platform
|
||||
from twisted.python.usage import UsageError
|
||||
from twisted.trial.unittest import SynchronousTestCase
|
||||
|
||||
|
||||
|
||||
class OptionsTests(SynchronousTestCase):
|
||||
"""
|
||||
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 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()
|
||||
|
||||
# On Windows, we need to use a threaded resolver, which leaves trash
|
||||
# lying about that we can't easily clean up without reaching into the
|
||||
# reactor and cancelling them. We only cancel the cleanup functions, as
|
||||
# there should be no others (and it leaving a callLater lying about
|
||||
# should rightly cause the test to fail).
|
||||
if platform.getType() != 'posix':
|
||||
|
||||
# We want the delayed calls on the reactor, which should be all of
|
||||
# ours from the threaded resolver cleanup
|
||||
from twisted.internet import reactor
|
||||
for x in reactor._newTimedCalls:
|
||||
if _PY3:
|
||||
self.assertEqual(x.func.__func__,
|
||||
ThreadedResolver._cleanup)
|
||||
else:
|
||||
self.assertEqual(x.func.__func__,
|
||||
ThreadedResolver._cleanup.__func__)
|
||||
x.cancel()
|
||||
|
||||
self.assertIsInstance(cl[-1], ResolverChain)
|
||||
137
venv/lib/python3.9/site-packages/twisted/names/test/test_util.py
Normal file
137
venv/lib/python3.9/site-packages/twisted/names/test/test_util.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Utilities for Twisted.names tests.
|
||||
"""
|
||||
|
||||
from __future__ import division, absolute_import
|
||||
|
||||
from random import randrange
|
||||
|
||||
from zope.interface import implementer
|
||||
from zope.interface.verify import verifyClass
|
||||
|
||||
from twisted.internet.address import IPv4Address
|
||||
from twisted.internet.defer import succeed
|
||||
from twisted.internet.task import Clock
|
||||
from twisted.internet.interfaces import IReactorUDP, IUDPTransport
|
||||
|
||||
|
||||
|
||||
@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: L{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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue