4935 lines
158 KiB
Python
4935 lines
158 KiB
Python
# test-case-name: twisted.names.test.test_dns
|
|
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Tests for twisted.names.dns.
|
|
"""
|
|
|
|
|
|
import struct
|
|
from io import BytesIO
|
|
|
|
from zope.interface.verify import verifyClass
|
|
|
|
from twisted.internet import address, task
|
|
from twisted.internet.error import CannotListenError, ConnectionDone
|
|
from twisted.names import dns
|
|
from twisted.python.failure import Failure
|
|
from twisted.python.util import FancyEqMixin, FancyStrMixin
|
|
from twisted.test import proto_helpers
|
|
from twisted.test.testutils import ComparisonTestsMixin
|
|
from twisted.trial import unittest
|
|
|
|
RECORD_TYPES = [
|
|
dns.Record_NS,
|
|
dns.Record_MD,
|
|
dns.Record_MF,
|
|
dns.Record_CNAME,
|
|
dns.Record_MB,
|
|
dns.Record_MG,
|
|
dns.Record_MR,
|
|
dns.Record_PTR,
|
|
dns.Record_DNAME,
|
|
dns.Record_A,
|
|
dns.Record_SOA,
|
|
dns.Record_NULL,
|
|
dns.Record_WKS,
|
|
dns.Record_SRV,
|
|
dns.Record_AFSDB,
|
|
dns.Record_RP,
|
|
dns.Record_HINFO,
|
|
dns.Record_MINFO,
|
|
dns.Record_MX,
|
|
dns.Record_TXT,
|
|
dns.Record_AAAA,
|
|
dns.Record_A6,
|
|
dns.Record_NAPTR,
|
|
dns.Record_SSHFP,
|
|
dns.Record_TSIG,
|
|
dns.UnknownRecord,
|
|
]
|
|
|
|
|
|
class DomainStringTests(unittest.SynchronousTestCase):
|
|
def test_bytes(self):
|
|
"""
|
|
L{dns.domainString} returns L{bytes} unchanged.
|
|
"""
|
|
self.assertEqual(
|
|
b"twistedmatrix.com",
|
|
dns.domainString(b"twistedmatrix.com"),
|
|
)
|
|
|
|
def test_native(self):
|
|
"""
|
|
L{dns.domainString} converts a native string to L{bytes}
|
|
if necessary.
|
|
"""
|
|
self.assertEqual(b"example.com", dns.domainString("example.com"))
|
|
|
|
def test_text(self):
|
|
"""
|
|
L{dns.domainString} always converts a unicode string to L{bytes}.
|
|
"""
|
|
self.assertEqual(b"foo.example", dns.domainString("foo.example"))
|
|
|
|
def test_idna(self):
|
|
"""
|
|
L{dns.domainString} encodes Unicode using IDNA.
|
|
"""
|
|
self.assertEqual(b"xn--fwg.test", dns.domainString("\u203D.test"))
|
|
|
|
def test_nonsense(self):
|
|
"""
|
|
L{dns.domainString} encodes Unicode using IDNA.
|
|
"""
|
|
self.assertRaises(TypeError, dns.domainString, 9000)
|
|
self.assertRaises(TypeError, dns.domainString, dns.Name("bar.example"))
|
|
|
|
|
|
class Ord2ByteTests(unittest.TestCase):
|
|
"""
|
|
Tests for L{dns._ord2bytes}.
|
|
"""
|
|
|
|
def test_ord2byte(self):
|
|
"""
|
|
L{dns._ord2byte} accepts an integer and returns a byte string of length
|
|
one with an ordinal value equal to the given integer.
|
|
"""
|
|
self.assertEqual(b"\x10", dns._ord2bytes(0x10))
|
|
|
|
|
|
class Str2TimeTests(unittest.TestCase):
|
|
"""
|
|
Tests for L{dns.str2name}.
|
|
"""
|
|
|
|
def test_nonString(self):
|
|
"""
|
|
When passed a non-string object, L{dns.str2name} returns it unmodified.
|
|
"""
|
|
time = object()
|
|
self.assertIs(time, dns.str2time(time))
|
|
|
|
def test_seconds(self):
|
|
"""
|
|
Passed a string giving a number of seconds, L{dns.str2time} returns the
|
|
number of seconds represented. For example, C{"10S"} represents C{10}
|
|
seconds.
|
|
"""
|
|
self.assertEqual(10, dns.str2time("10S"))
|
|
|
|
def test_minutes(self):
|
|
"""
|
|
Like C{test_seconds}, but for the C{"M"} suffix which multiplies the
|
|
time value by C{60} (the number of seconds in a minute!).
|
|
"""
|
|
self.assertEqual(2 * 60, dns.str2time("2M"))
|
|
|
|
def test_hours(self):
|
|
"""
|
|
Like C{test_seconds}, but for the C{"H"} suffix which multiplies the
|
|
time value by C{3600}, the number of seconds in an hour.
|
|
"""
|
|
self.assertEqual(3 * 3600, dns.str2time("3H"))
|
|
|
|
def test_days(self):
|
|
"""
|
|
Like L{test_seconds}, but for the C{"D"} suffix which multiplies the
|
|
time value by C{86400}, the number of seconds in a day.
|
|
"""
|
|
self.assertEqual(4 * 86400, dns.str2time("4D"))
|
|
|
|
def test_weeks(self):
|
|
"""
|
|
Like L{test_seconds}, but for the C{"W"} suffix which multiplies the
|
|
time value by C{604800}, the number of seconds in a week.
|
|
"""
|
|
self.assertEqual(5 * 604800, dns.str2time("5W"))
|
|
|
|
def test_years(self):
|
|
"""
|
|
Like L{test_seconds}, but for the C{"Y"} suffix which multiplies the
|
|
time value by C{31536000}, the number of seconds in a year.
|
|
"""
|
|
self.assertEqual(6 * 31536000, dns.str2time("6Y"))
|
|
|
|
def test_invalidPrefix(self):
|
|
"""
|
|
If a non-integer prefix is given, L{dns.str2time} raises L{ValueError}.
|
|
"""
|
|
self.assertRaises(ValueError, dns.str2time, "fooS")
|
|
|
|
def test_invalidSuffix(self) -> None:
|
|
"""
|
|
If an invalid suffix is given, L{dns.str2time} raises L{ValueError}.
|
|
"""
|
|
self.assertRaises(ValueError, dns.str2time, "1Q")
|
|
|
|
|
|
class NameTests(unittest.TestCase):
|
|
"""
|
|
Tests for L{Name}, the representation of a single domain name with support
|
|
for encoding into and decoding from DNS message format.
|
|
"""
|
|
|
|
def test_nonStringName(self):
|
|
"""
|
|
When constructed with a name which is neither C{bytes} nor C{str},
|
|
L{Name} raises L{TypeError}.
|
|
"""
|
|
self.assertRaises(TypeError, dns.Name, 123)
|
|
self.assertRaises(TypeError, dns.Name, object())
|
|
self.assertRaises(TypeError, dns.Name, [])
|
|
|
|
def test_unicodeName(self):
|
|
"""
|
|
L{dns.Name} automatically encodes unicode domain name using C{idna}
|
|
encoding.
|
|
"""
|
|
name = dns.Name("\u00e9chec.example.org")
|
|
self.assertIsInstance(name.name, bytes)
|
|
self.assertEqual(b"xn--chec-9oa.example.org", name.name)
|
|
|
|
def test_decode(self):
|
|
"""
|
|
L{Name.decode} populates the L{Name} instance with name information read
|
|
from the file-like object passed to it.
|
|
"""
|
|
n = dns.Name()
|
|
n.decode(BytesIO(b"\x07example\x03com\x00"))
|
|
self.assertEqual(n.name, b"example.com")
|
|
|
|
def test_encode(self):
|
|
"""
|
|
L{Name.encode} encodes its name information and writes it to the
|
|
file-like object passed to it.
|
|
"""
|
|
name = dns.Name(b"foo.example.com")
|
|
stream = BytesIO()
|
|
name.encode(stream)
|
|
self.assertEqual(stream.getvalue(), b"\x03foo\x07example\x03com\x00")
|
|
|
|
def test_encodeWithCompression(self):
|
|
"""
|
|
If a compression dictionary is passed to it, L{Name.encode} uses offset
|
|
information from it to encode its name with references to existing
|
|
labels in the stream instead of including another copy of them in the
|
|
output. It also updates the compression dictionary with the location of
|
|
the name it writes to the stream.
|
|
"""
|
|
name = dns.Name(b"foo.example.com")
|
|
compression = {b"example.com": 0x17}
|
|
|
|
# Some bytes already encoded into the stream for this message
|
|
previous = b"some prefix to change .tell()"
|
|
stream = BytesIO()
|
|
stream.write(previous)
|
|
|
|
# The position at which the encoded form of this new name will appear in
|
|
# the stream.
|
|
expected = len(previous) + dns.Message.headerSize
|
|
name.encode(stream, compression)
|
|
self.assertEqual(b"\x03foo\xc0\x17", stream.getvalue()[len(previous) :])
|
|
self.assertEqual(
|
|
{b"example.com": 0x17, b"foo.example.com": expected}, compression
|
|
)
|
|
|
|
def test_unknown(self):
|
|
"""
|
|
A resource record of unknown type and class is parsed into an
|
|
L{UnknownRecord} instance with its data preserved, and an
|
|
L{UnknownRecord} instance is serialized to a string equal to the one it
|
|
was parsed from.
|
|
"""
|
|
wire = (
|
|
b"\x01\x00" # Message ID
|
|
b"\x00" # answer bit, opCode nibble, auth bit, trunc bit, recursive
|
|
# bit
|
|
b"\x00" # recursion bit, empty bit, authenticData bit,
|
|
# checkingDisabled bit, response code nibble
|
|
b"\x00\x01" # number of queries
|
|
b"\x00\x01" # number of answers
|
|
b"\x00\x00" # number of authorities
|
|
b"\x00\x01" # number of additionals
|
|
# query
|
|
b"\x03foo\x03bar\x00" # foo.bar
|
|
b"\xde\xad" # type=0xdead
|
|
b"\xbe\xef" # cls=0xbeef
|
|
# 1st answer
|
|
b"\xc0\x0c" # foo.bar - compressed
|
|
b"\xde\xad" # type=0xdead
|
|
b"\xbe\xef" # cls=0xbeef
|
|
b"\x00\x00\x01\x01" # ttl=257
|
|
b"\x00\x08somedata" # some payload data
|
|
# 1st additional
|
|
b"\x03baz\x03ban\x00" # baz.ban
|
|
b"\x00\x01" # type=A
|
|
b"\x00\x01" # cls=IN
|
|
b"\x00\x00\x01\x01" # ttl=257
|
|
b"\x00\x04" # len=4
|
|
b"\x01\x02\x03\x04" # 1.2.3.4
|
|
)
|
|
|
|
msg = dns.Message()
|
|
msg.fromStr(wire)
|
|
|
|
self.assertEqual(
|
|
msg.queries,
|
|
[
|
|
dns.Query(b"foo.bar", type=0xDEAD, cls=0xBEEF),
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
msg.answers,
|
|
[
|
|
dns.RRHeader(
|
|
b"foo.bar",
|
|
type=0xDEAD,
|
|
cls=0xBEEF,
|
|
ttl=257,
|
|
payload=dns.UnknownRecord(b"somedata", ttl=257),
|
|
),
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
msg.additional,
|
|
[
|
|
dns.RRHeader(
|
|
b"baz.ban",
|
|
type=dns.A,
|
|
cls=dns.IN,
|
|
ttl=257,
|
|
payload=dns.Record_A("1.2.3.4", ttl=257),
|
|
),
|
|
],
|
|
)
|
|
|
|
enc = msg.toStr()
|
|
|
|
self.assertEqual(enc, wire)
|
|
|
|
def test_decodeWithCompression(self):
|
|
"""
|
|
If the leading byte of an encoded label (in bytes read from a stream
|
|
passed to L{Name.decode}) has its two high bits set, the next byte is
|
|
treated as a pointer to another label in the stream and that label is
|
|
included in the name being decoded.
|
|
"""
|
|
# Slightly modified version of the example from RFC 1035, section 4.1.4.
|
|
stream = BytesIO(
|
|
b"x" * 20 + b"\x01f\x03isi\x04arpa\x00"
|
|
b"\x03foo\xc0\x14"
|
|
b"\x03bar\xc0\x20"
|
|
)
|
|
stream.seek(20)
|
|
name = dns.Name()
|
|
name.decode(stream)
|
|
# Verify we found the first name in the stream and that the stream
|
|
# position is left at the first byte after the decoded name.
|
|
self.assertEqual(b"f.isi.arpa", name.name)
|
|
self.assertEqual(32, stream.tell())
|
|
|
|
# Get the second name from the stream and make the same assertions.
|
|
name.decode(stream)
|
|
self.assertEqual(name.name, b"foo.f.isi.arpa")
|
|
self.assertEqual(38, stream.tell())
|
|
|
|
# Get the third and final name
|
|
name.decode(stream)
|
|
self.assertEqual(name.name, b"bar.foo.f.isi.arpa")
|
|
self.assertEqual(44, stream.tell())
|
|
|
|
def test_rejectCompressionLoop(self):
|
|
"""
|
|
L{Name.decode} raises L{ValueError} if the stream passed to it includes
|
|
a compression pointer which forms a loop, causing the name to be
|
|
undecodable.
|
|
"""
|
|
name = dns.Name()
|
|
stream = BytesIO(b"\xc0\x00")
|
|
self.assertRaises(ValueError, name.decode, stream)
|
|
|
|
def test_equality(self):
|
|
"""
|
|
L{Name} instances are equal as long as they have the same value for
|
|
L{Name.name}, regardless of the case.
|
|
"""
|
|
name1 = dns.Name(b"foo.bar")
|
|
name2 = dns.Name(b"foo.bar")
|
|
self.assertEqual(name1, name2)
|
|
|
|
name3 = dns.Name(b"fOO.bar")
|
|
self.assertEqual(name1, name3)
|
|
|
|
def test_inequality(self):
|
|
"""
|
|
L{Name} instances are not equal as long as they have different
|
|
L{Name.name} attributes.
|
|
"""
|
|
name1 = dns.Name(b"foo.bar")
|
|
name2 = dns.Name(b"bar.foo")
|
|
self.assertNotEqual(name1, name2)
|
|
|
|
|
|
class RoundtripDNSTests(unittest.TestCase):
|
|
"""
|
|
Encoding and then decoding various objects.
|
|
"""
|
|
|
|
names = [b"example.org", b"go-away.fish.tv", b"23strikesback.net"]
|
|
|
|
def test_name(self):
|
|
for n in self.names:
|
|
# encode the name
|
|
f = BytesIO()
|
|
dns.Name(n).encode(f)
|
|
|
|
# decode the name
|
|
f.seek(0, 0)
|
|
result = dns.Name()
|
|
result.decode(f)
|
|
self.assertEqual(result.name, n)
|
|
|
|
def test_query(self):
|
|
"""
|
|
L{dns.Query.encode} returns a byte string representing the fields of the
|
|
query which can be decoded into a new L{dns.Query} instance using
|
|
L{dns.Query.decode}.
|
|
"""
|
|
for n in self.names:
|
|
for dnstype in range(1, 17):
|
|
for dnscls in range(1, 5):
|
|
# encode the query
|
|
f = BytesIO()
|
|
dns.Query(n, dnstype, dnscls).encode(f)
|
|
|
|
# decode the result
|
|
f.seek(0, 0)
|
|
result = dns.Query()
|
|
result.decode(f)
|
|
self.assertEqual(result.name.name, n)
|
|
self.assertEqual(result.type, dnstype)
|
|
self.assertEqual(result.cls, dnscls)
|
|
|
|
def test_resourceRecordHeader(self):
|
|
"""
|
|
L{dns.RRHeader.encode} encodes the record header's information and
|
|
writes it to the file-like object passed to it and
|
|
L{dns.RRHeader.decode} reads from a file-like object to re-construct a
|
|
L{dns.RRHeader} instance.
|
|
"""
|
|
# encode the RR
|
|
f = BytesIO()
|
|
dns.RRHeader(b"test.org", 3, 4, 17).encode(f)
|
|
|
|
# decode the result
|
|
f.seek(0, 0)
|
|
result = dns.RRHeader()
|
|
result.decode(f)
|
|
self.assertEqual(result.name, dns.Name(b"test.org"))
|
|
self.assertEqual(result.type, 3)
|
|
self.assertEqual(result.cls, 4)
|
|
self.assertEqual(result.ttl, 17)
|
|
|
|
def test_resourceRecordHeaderTypeMismatch(self):
|
|
"""
|
|
L{RRHeader()} raises L{ValueError} when the given type and the type
|
|
of the payload don't match.
|
|
"""
|
|
|
|
with self.assertRaisesRegex(ValueError, r"Payload type \(AAAA\) .* type \(A\)"):
|
|
dns.RRHeader(type=dns.A, payload=dns.Record_AAAA())
|
|
|
|
def test_resources(self):
|
|
"""
|
|
L{dns.SimpleRecord.encode} encodes the record's name information and
|
|
writes it to the file-like object passed to it and
|
|
L{dns.SimpleRecord.decode} reads from a file-like object to re-construct
|
|
a L{dns.SimpleRecord} instance.
|
|
"""
|
|
names = (
|
|
b"this.are.test.name",
|
|
b"will.compress.will.this.will.name.will.hopefully",
|
|
b"test.CASE.preSErVatIOn.YeAH",
|
|
b"a.s.h.o.r.t.c.a.s.e.t.o.t.e.s.t",
|
|
b"singleton",
|
|
)
|
|
for s in names:
|
|
f = BytesIO()
|
|
dns.SimpleRecord(s).encode(f)
|
|
f.seek(0, 0)
|
|
result = dns.SimpleRecord()
|
|
result.decode(f)
|
|
self.assertEqual(result.name, dns.Name(s))
|
|
|
|
def test_hashable(self):
|
|
"""
|
|
Instances of all record types are hashable.
|
|
"""
|
|
for k in RECORD_TYPES:
|
|
k1, k2 = k(), k()
|
|
hk1 = hash(k1)
|
|
hk2 = hash(k2)
|
|
self.assertEqual(hk1, hk2, f"{hk1} != {hk2} (for {k})")
|
|
|
|
def test_Charstr(self):
|
|
"""
|
|
Test L{dns.Charstr} encode and decode.
|
|
"""
|
|
for n in self.names:
|
|
# encode the name
|
|
f = BytesIO()
|
|
dns.Charstr(n).encode(f)
|
|
|
|
# decode the name
|
|
f.seek(0, 0)
|
|
result = dns.Charstr()
|
|
result.decode(f)
|
|
self.assertEqual(result.string, n)
|
|
|
|
def _recordRoundtripTest(self, record):
|
|
"""
|
|
Assert that encoding C{record} and then decoding the resulting bytes
|
|
creates a record which compares equal to C{record}.
|
|
|
|
@type record: L{dns.IEncodable}
|
|
@param record: A record instance to encode
|
|
"""
|
|
stream = BytesIO()
|
|
record.encode(stream)
|
|
|
|
length = stream.tell()
|
|
stream.seek(0, 0)
|
|
replica = record.__class__()
|
|
replica.decode(stream, length)
|
|
self.assertEqual(record, replica)
|
|
|
|
def assertEncodedFormat(self, expectedEncoding, record):
|
|
"""
|
|
Assert that encoding C{record} produces the expected bytes.
|
|
|
|
@type record: L{dns.IEncodable}
|
|
@param record: A record instance to encode
|
|
|
|
@type expectedEncoding: L{bytes}
|
|
@param expectedEncoding: The value which C{record.encode()}
|
|
should produce.
|
|
"""
|
|
stream = BytesIO()
|
|
record.encode(stream)
|
|
|
|
self.assertEqual(stream.getvalue(), expectedEncoding)
|
|
|
|
def test_SOA(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_SOA.encode} can be used by
|
|
L{dns.Record_SOA.decode} to reconstruct the state of the original
|
|
L{dns.Record_SOA} instance.
|
|
"""
|
|
self._recordRoundtripTest(
|
|
dns.Record_SOA(
|
|
mname=b"foo",
|
|
rname=b"bar",
|
|
serial=12,
|
|
refresh=34,
|
|
retry=56,
|
|
expire=78,
|
|
minimum=90,
|
|
)
|
|
)
|
|
|
|
def test_A(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_A.encode} can be used by
|
|
L{dns.Record_A.decode} to reconstruct the state of the original
|
|
L{dns.Record_A} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_A("1.2.3.4"))
|
|
|
|
def test_NULL(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_NULL.encode} can be used by
|
|
L{dns.Record_NULL.decode} to reconstruct the state of the original
|
|
L{dns.Record_NULL} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_NULL(b"foo bar"))
|
|
|
|
def test_WKS(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_WKS.encode} can be used by
|
|
L{dns.Record_WKS.decode} to reconstruct the state of the original
|
|
L{dns.Record_WKS} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_WKS("1.2.3.4", 3, b"xyz"))
|
|
|
|
def test_AAAA(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_AAAA.encode} can be used by
|
|
L{dns.Record_AAAA.decode} to reconstruct the state of the original
|
|
L{dns.Record_AAAA} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_AAAA("::1"))
|
|
|
|
def test_A6(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_A6.encode} can be used by
|
|
L{dns.Record_A6.decode} to reconstruct the state of the original
|
|
L{dns.Record_A6} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_A6(8, "::1:2", b"foo"))
|
|
|
|
def test_SRV(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_SRV.encode} can be used by
|
|
L{dns.Record_SRV.decode} to reconstruct the state of the original
|
|
L{dns.Record_SRV} instance.
|
|
"""
|
|
self._recordRoundtripTest(
|
|
dns.Record_SRV(priority=1, weight=2, port=3, target=b"example.com")
|
|
)
|
|
|
|
def test_SSHFP(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_SSHFP.encode} can be used by
|
|
L{dns.Record_SSHFP.decode} to reconstruct the state of the original
|
|
L{dns.Record_SSHFP} instance.
|
|
"""
|
|
|
|
fp = (
|
|
b"\xda\x39\xa3\xee\x5e\x6b\x4b\x0d"
|
|
+ b"\x32\x55\xbf\xef\x95\x60\x18\x90\xaf\xd8\x07\x09"
|
|
)
|
|
rr = dns.Record_SSHFP(
|
|
algorithm=dns.Record_SSHFP.ALGORITHM_DSS,
|
|
fingerprintType=dns.Record_SSHFP.FINGERPRINT_TYPE_SHA1,
|
|
fingerprint=fp,
|
|
)
|
|
self._recordRoundtripTest(rr)
|
|
self.assertEncodedFormat(b"\x02\x01" + fp, rr)
|
|
|
|
def test_NAPTR(self):
|
|
"""
|
|
Test L{dns.Record_NAPTR} encode and decode.
|
|
"""
|
|
naptrs = [
|
|
(100, 10, b"u", b"sip+E2U", b"!^.*$!sip:information@domain.tld!", b""),
|
|
(100, 50, b"s", b"http+I2L+I2C+I2R", b"", b"_http._tcp.gatech.edu"),
|
|
]
|
|
|
|
for order, preference, flags, service, regexp, replacement in naptrs:
|
|
rin = dns.Record_NAPTR(
|
|
order, preference, flags, service, regexp, replacement
|
|
)
|
|
e = BytesIO()
|
|
rin.encode(e)
|
|
e.seek(0, 0)
|
|
rout = dns.Record_NAPTR()
|
|
rout.decode(e)
|
|
self.assertEqual(rin.order, rout.order)
|
|
self.assertEqual(rin.preference, rout.preference)
|
|
self.assertEqual(rin.flags, rout.flags)
|
|
self.assertEqual(rin.service, rout.service)
|
|
self.assertEqual(rin.regexp, rout.regexp)
|
|
self.assertEqual(rin.replacement.name, rout.replacement.name)
|
|
self.assertEqual(rin.ttl, rout.ttl)
|
|
|
|
def test_AFSDB(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_AFSDB.encode} can be used by
|
|
L{dns.Record_AFSDB.decode} to reconstruct the state of the original
|
|
L{dns.Record_AFSDB} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_AFSDB(subtype=3, hostname=b"example.com"))
|
|
|
|
def test_RP(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_RP.encode} can be used by
|
|
L{dns.Record_RP.decode} to reconstruct the state of the original
|
|
L{dns.Record_RP} instance.
|
|
"""
|
|
self._recordRoundtripTest(
|
|
dns.Record_RP(mbox=b"alice.example.com", txt=b"example.com")
|
|
)
|
|
|
|
def test_HINFO(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_HINFO.encode} can be used by
|
|
L{dns.Record_HINFO.decode} to reconstruct the state of the original
|
|
L{dns.Record_HINFO} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_HINFO(cpu=b"fast", os=b"great"))
|
|
|
|
def test_MINFO(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_MINFO.encode} can be used by
|
|
L{dns.Record_MINFO.decode} to reconstruct the state of the original
|
|
L{dns.Record_MINFO} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_MINFO(rmailbx=b"foo", emailbx=b"bar"))
|
|
|
|
def test_MX(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_MX.encode} can be used by
|
|
L{dns.Record_MX.decode} to reconstruct the state of the original
|
|
L{dns.Record_MX} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_MX(preference=1, name=b"example.com"))
|
|
|
|
def test_TSIG(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_TSIG.encode} can be used by
|
|
L{dns.Record_TSIG.decode} to reconstruct the state of the original
|
|
L{dns.Record_TSIG} instance.
|
|
"""
|
|
mac = b"\x00\x01\x02\x03\x10\x11\x12\x13" b"\x20\x21\x22\x23\x30\x31\x32\x33"
|
|
rr = dns.Record_TSIG(
|
|
algorithm="hmac-md5.sig-alg.reg.int",
|
|
timeSigned=1515548975,
|
|
originalID=42,
|
|
fudge=5,
|
|
MAC=mac,
|
|
)
|
|
self._recordRoundtripTest(rr)
|
|
rdata = (
|
|
b"\x08hmac-md5\x07sig-alg\x03reg\x03int\x00"
|
|
b"\x00\x00\x5a\x55\x71\x2f\x00\x05\x00\x10"
|
|
+ mac
|
|
+ b"\x00\x2A\x00\x00\x00\x00"
|
|
)
|
|
self.assertEncodedFormat(rdata, rr)
|
|
|
|
rr = dns.Record_TSIG(
|
|
algorithm="hmac-sha256",
|
|
timeSigned=4511798055, # More than 32 bits
|
|
originalID=65535,
|
|
error=dns.EBADTIME,
|
|
otherData=b"\x80\x00\x00\x00\x00\x08",
|
|
MAC=mac,
|
|
)
|
|
self._recordRoundtripTest(rr)
|
|
rdata = (
|
|
b"\x0Bhmac-sha256\x00"
|
|
b"\x00\x01\x0c\xec\x93\x27\x00\x05\x00\x10"
|
|
+ mac
|
|
+ b"\xff\xff\x00\x12\x00\x06"
|
|
b"\x80\x00\x00\x00\x00\x08"
|
|
)
|
|
self.assertEncodedFormat(rdata, rr)
|
|
|
|
def test_TXT(self):
|
|
"""
|
|
The byte stream written by L{dns.Record_TXT.encode} can be used by
|
|
L{dns.Record_TXT.decode} to reconstruct the state of the original
|
|
L{dns.Record_TXT} instance.
|
|
"""
|
|
self._recordRoundtripTest(dns.Record_TXT(b"foo", b"bar"))
|
|
|
|
|
|
MESSAGE_AUTHENTIC_DATA_BYTES = (
|
|
b"\x00\x00" # ID
|
|
b"\x00" #
|
|
b"\x20" # RA, Z, AD=1, CD, RCODE
|
|
b"\x00\x00" # Query count
|
|
b"\x00\x00" # Answer count
|
|
b"\x00\x00" # Authority count
|
|
b"\x00\x00" # Additional count
|
|
)
|
|
|
|
|
|
MESSAGE_CHECKING_DISABLED_BYTES = (
|
|
b"\x00\x00" # ID
|
|
b"\x00" #
|
|
b"\x10" # RA, Z, AD, CD=1, RCODE
|
|
b"\x00\x00" # Query count
|
|
b"\x00\x00" # Answer count
|
|
b"\x00\x00" # Authority count
|
|
b"\x00\x00" # Additional count
|
|
)
|
|
|
|
|
|
class MessageTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{twisted.names.dns.Message}.
|
|
"""
|
|
|
|
def test_authenticDataDefault(self):
|
|
"""
|
|
L{dns.Message.authenticData} has default value 0.
|
|
"""
|
|
self.assertEqual(dns.Message().authenticData, 0)
|
|
|
|
def test_authenticDataOverride(self):
|
|
"""
|
|
L{dns.Message.__init__} accepts a C{authenticData} argument which
|
|
is assigned to L{dns.Message.authenticData}.
|
|
"""
|
|
self.assertEqual(dns.Message(authenticData=1).authenticData, 1)
|
|
|
|
def test_authenticDataEncode(self):
|
|
"""
|
|
L{dns.Message.toStr} encodes L{dns.Message.authenticData} into
|
|
byte4 of the byte string.
|
|
"""
|
|
self.assertEqual(
|
|
dns.Message(authenticData=1).toStr(), MESSAGE_AUTHENTIC_DATA_BYTES
|
|
)
|
|
|
|
def test_authenticDataDecode(self):
|
|
"""
|
|
L{dns.Message.fromStr} decodes byte4 and assigns bit3 to
|
|
L{dns.Message.authenticData}.
|
|
"""
|
|
m = dns.Message()
|
|
m.fromStr(MESSAGE_AUTHENTIC_DATA_BYTES)
|
|
|
|
self.assertEqual(m.authenticData, 1)
|
|
|
|
def test_checkingDisabledDefault(self):
|
|
"""
|
|
L{dns.Message.checkingDisabled} has default value 0.
|
|
"""
|
|
self.assertEqual(dns.Message().checkingDisabled, 0)
|
|
|
|
def test_checkingDisabledOverride(self):
|
|
"""
|
|
L{dns.Message.__init__} accepts a C{checkingDisabled} argument which
|
|
is assigned to L{dns.Message.checkingDisabled}.
|
|
"""
|
|
self.assertEqual(dns.Message(checkingDisabled=1).checkingDisabled, 1)
|
|
|
|
def test_checkingDisabledEncode(self):
|
|
"""
|
|
L{dns.Message.toStr} encodes L{dns.Message.checkingDisabled} into
|
|
byte4 of the byte string.
|
|
"""
|
|
self.assertEqual(
|
|
dns.Message(checkingDisabled=1).toStr(), MESSAGE_CHECKING_DISABLED_BYTES
|
|
)
|
|
|
|
def test_checkingDisabledDecode(self):
|
|
"""
|
|
L{dns.Message.fromStr} decodes byte4 and assigns bit4 to
|
|
L{dns.Message.checkingDisabled}.
|
|
"""
|
|
m = dns.Message()
|
|
m.fromStr(MESSAGE_CHECKING_DISABLED_BYTES)
|
|
|
|
self.assertEqual(m.checkingDisabled, 1)
|
|
|
|
def test_reprDefaults(self):
|
|
"""
|
|
L{dns.Message.__repr__} omits field values and sections which are
|
|
identical to their defaults. The id field value is always shown.
|
|
"""
|
|
self.assertEqual("<Message id=0>", repr(dns.Message()))
|
|
|
|
def test_reprFlagsIfSet(self):
|
|
"""
|
|
L{dns.Message.__repr__} displays flags if they are L{True}.
|
|
"""
|
|
m = dns.Message(
|
|
answer=True,
|
|
auth=True,
|
|
trunc=True,
|
|
recDes=True,
|
|
recAv=True,
|
|
authenticData=True,
|
|
checkingDisabled=True,
|
|
)
|
|
self.assertEqual(
|
|
"<Message "
|
|
"id=0 "
|
|
"flags=answer,auth,trunc,recDes,recAv,authenticData,"
|
|
"checkingDisabled"
|
|
">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_reprNonDefautFields(self):
|
|
"""
|
|
L{dns.Message.__repr__} displays field values if they differ from their
|
|
defaults.
|
|
"""
|
|
m = dns.Message(id=10, opCode=20, rCode=30, maxSize=40)
|
|
self.assertEqual(
|
|
"<Message " "id=10 " "opCode=20 " "rCode=30 " "maxSize=40" ">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_reprNonDefaultSections(self):
|
|
"""
|
|
L{dns.Message.__repr__} displays sections which differ from their
|
|
defaults.
|
|
"""
|
|
m = dns.Message()
|
|
m.queries = [1, 2, 3]
|
|
m.answers = [4, 5, 6]
|
|
m.authority = [7, 8, 9]
|
|
m.additional = [10, 11, 12]
|
|
self.assertEqual(
|
|
"<Message "
|
|
"id=0 "
|
|
"queries=[1, 2, 3] "
|
|
"answers=[4, 5, 6] "
|
|
"authority=[7, 8, 9] "
|
|
"additional=[10, 11, 12]"
|
|
">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_emptyMessage(self):
|
|
"""
|
|
Test that a message which has been truncated causes an EOFError to
|
|
be raised when it is parsed.
|
|
"""
|
|
msg = dns.Message()
|
|
self.assertRaises(EOFError, msg.fromStr, b"")
|
|
|
|
def test_emptyQuery(self):
|
|
"""
|
|
Test that bytes representing an empty query message can be decoded
|
|
as such.
|
|
"""
|
|
msg = dns.Message()
|
|
msg.fromStr(
|
|
b"\x01\x00" # Message ID
|
|
b"\x00" # answer bit, opCode nibble, auth bit, trunc bit, recursive bit
|
|
b"\x00" # recursion bit, empty bit, authenticData bit,
|
|
# checkingDisabled bit, response code nibble
|
|
b"\x00\x00" # number of queries
|
|
b"\x00\x00" # number of answers
|
|
b"\x00\x00" # number of authorities
|
|
b"\x00\x00" # number of additionals
|
|
)
|
|
self.assertEqual(msg.id, 256)
|
|
self.assertFalse(msg.answer, "Message was not supposed to be an answer.")
|
|
self.assertEqual(msg.opCode, dns.OP_QUERY)
|
|
self.assertFalse(msg.auth, "Message was not supposed to be authoritative.")
|
|
self.assertFalse(msg.trunc, "Message was not supposed to be truncated.")
|
|
self.assertEqual(msg.queries, [])
|
|
self.assertEqual(msg.answers, [])
|
|
self.assertEqual(msg.authority, [])
|
|
self.assertEqual(msg.additional, [])
|
|
|
|
def test_NULL(self):
|
|
"""
|
|
A I{NULL} record with an arbitrary payload can be encoded and decoded as
|
|
part of a L{dns.Message}.
|
|
"""
|
|
bytes = b"".join([dns._ord2bytes(i) for i in range(256)])
|
|
rec = dns.Record_NULL(bytes)
|
|
rr = dns.RRHeader(b"testname", dns.NULL, payload=rec)
|
|
msg1 = dns.Message()
|
|
msg1.answers.append(rr)
|
|
s = BytesIO()
|
|
msg1.encode(s)
|
|
s.seek(0, 0)
|
|
msg2 = dns.Message()
|
|
msg2.decode(s)
|
|
|
|
self.assertIsInstance(msg2.answers[0].payload, dns.Record_NULL)
|
|
self.assertEqual(msg2.answers[0].payload.payload, bytes)
|
|
|
|
def test_lookupRecordTypeDefault(self):
|
|
"""
|
|
L{Message.lookupRecordType} returns C{dns.UnknownRecord} if it is
|
|
called with an integer which doesn't correspond to any known record
|
|
type.
|
|
"""
|
|
# 65280 is the first value in the range reserved for private
|
|
# use, so it shouldn't ever conflict with an officially
|
|
# allocated value.
|
|
self.assertIs(dns.Message().lookupRecordType(65280), dns.UnknownRecord)
|
|
|
|
def test_nonAuthoritativeMessage(self):
|
|
"""
|
|
The L{RRHeader} instances created by L{Message} from a non-authoritative
|
|
message are marked as not authoritative.
|
|
"""
|
|
buf = BytesIO()
|
|
answer = dns.RRHeader(payload=dns.Record_A("1.2.3.4", ttl=0))
|
|
answer.encode(buf)
|
|
message = dns.Message()
|
|
message.fromStr(
|
|
b"\x01\x00" # Message ID
|
|
# answer bit, opCode nibble, auth bit, trunc bit, recursive bit
|
|
b"\x00"
|
|
# recursion bit, empty bit, authenticData bit,
|
|
# checkingDisabled bit, response code nibble
|
|
b"\x00"
|
|
b"\x00\x00" # number of queries
|
|
b"\x00\x01" # number of answers
|
|
b"\x00\x00" # number of authorities
|
|
b"\x00\x00" + buf.getvalue() # number of additionals
|
|
)
|
|
self.assertEqual(message.answers, [answer])
|
|
self.assertFalse(message.answers[0].auth)
|
|
|
|
def test_authoritativeMessage(self):
|
|
"""
|
|
The L{RRHeader} instances created by L{Message} from an authoritative
|
|
message are marked as authoritative.
|
|
"""
|
|
buf = BytesIO()
|
|
answer = dns.RRHeader(payload=dns.Record_A("1.2.3.4", ttl=0))
|
|
answer.encode(buf)
|
|
message = dns.Message()
|
|
message.fromStr(
|
|
b"\x01\x00" # Message ID
|
|
# answer bit, opCode nibble, auth bit, trunc bit, recursive bit
|
|
b"\x04"
|
|
# recursion bit, empty bit, authenticData bit,
|
|
# checkingDisabled bit, response code nibble
|
|
b"\x00"
|
|
b"\x00\x00" # number of queries
|
|
b"\x00\x01" # number of answers
|
|
b"\x00\x00" # number of authorities
|
|
b"\x00\x00" + buf.getvalue() # number of additionals
|
|
)
|
|
answer.auth = True
|
|
self.assertEqual(message.answers, [answer])
|
|
self.assertTrue(message.answers[0].auth)
|
|
|
|
|
|
class MessageComparisonTests(ComparisonTestsMixin, unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for the rich comparison of L{dns.Message} instances.
|
|
"""
|
|
|
|
def messageFactory(self, *args, **kwargs):
|
|
"""
|
|
Create a L{dns.Message}.
|
|
|
|
The L{dns.Message} constructor doesn't accept C{queries}, C{answers},
|
|
C{authority}, C{additional} arguments, so we extract them from the
|
|
kwargs supplied to this factory function and assign them to the message.
|
|
|
|
@param args: Positional arguments.
|
|
@param kwargs: Keyword arguments.
|
|
@return: A L{dns.Message} instance.
|
|
"""
|
|
queries = kwargs.pop("queries", [])
|
|
answers = kwargs.pop("answers", [])
|
|
authority = kwargs.pop("authority", [])
|
|
additional = kwargs.pop("additional", [])
|
|
m = dns.Message(**kwargs)
|
|
if queries:
|
|
m.queries = queries
|
|
if answers:
|
|
m.answers = answers
|
|
if authority:
|
|
m.authority = authority
|
|
if additional:
|
|
m.additional = additional
|
|
return m
|
|
|
|
def test_id(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same id
|
|
value.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(id=10),
|
|
self.messageFactory(id=10),
|
|
self.messageFactory(id=20),
|
|
)
|
|
|
|
def test_answer(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same answer
|
|
flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(answer=1),
|
|
self.messageFactory(answer=1),
|
|
self.messageFactory(answer=0),
|
|
)
|
|
|
|
def test_opCode(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same opCode
|
|
value.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(opCode=10),
|
|
self.messageFactory(opCode=10),
|
|
self.messageFactory(opCode=20),
|
|
)
|
|
|
|
def test_recDes(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same recDes
|
|
flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(recDes=1),
|
|
self.messageFactory(recDes=1),
|
|
self.messageFactory(recDes=0),
|
|
)
|
|
|
|
def test_recAv(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same recAv
|
|
flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(recAv=1),
|
|
self.messageFactory(recAv=1),
|
|
self.messageFactory(recAv=0),
|
|
)
|
|
|
|
def test_auth(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same auth
|
|
flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(auth=1),
|
|
self.messageFactory(auth=1),
|
|
self.messageFactory(auth=0),
|
|
)
|
|
|
|
def test_rCode(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same rCode
|
|
value.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(rCode=10),
|
|
self.messageFactory(rCode=10),
|
|
self.messageFactory(rCode=20),
|
|
)
|
|
|
|
def test_trunc(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same trunc
|
|
flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(trunc=1),
|
|
self.messageFactory(trunc=1),
|
|
self.messageFactory(trunc=0),
|
|
)
|
|
|
|
def test_maxSize(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same
|
|
maxSize value.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(maxSize=10),
|
|
self.messageFactory(maxSize=10),
|
|
self.messageFactory(maxSize=20),
|
|
)
|
|
|
|
def test_authenticData(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same
|
|
authenticData flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(authenticData=1),
|
|
self.messageFactory(authenticData=1),
|
|
self.messageFactory(authenticData=0),
|
|
)
|
|
|
|
def test_checkingDisabled(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same
|
|
checkingDisabled flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(checkingDisabled=1),
|
|
self.messageFactory(checkingDisabled=1),
|
|
self.messageFactory(checkingDisabled=0),
|
|
)
|
|
|
|
def test_queries(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same
|
|
queries.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(queries=[dns.Query(b"example.com")]),
|
|
self.messageFactory(queries=[dns.Query(b"example.com")]),
|
|
self.messageFactory(queries=[dns.Query(b"example.org")]),
|
|
)
|
|
|
|
def test_answers(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same
|
|
answers.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(
|
|
answers=[dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))]
|
|
),
|
|
self.messageFactory(
|
|
answers=[dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))]
|
|
),
|
|
self.messageFactory(
|
|
answers=[dns.RRHeader(b"example.org", payload=dns.Record_A("4.3.2.1"))]
|
|
),
|
|
)
|
|
|
|
def test_authority(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same
|
|
authority records.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(
|
|
authority=[
|
|
dns.RRHeader(b"example.com", type=dns.SOA, payload=dns.Record_SOA())
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
authority=[
|
|
dns.RRHeader(b"example.com", type=dns.SOA, payload=dns.Record_SOA())
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
authority=[
|
|
dns.RRHeader(b"example.org", type=dns.SOA, payload=dns.Record_SOA())
|
|
]
|
|
),
|
|
)
|
|
|
|
def test_additional(self):
|
|
"""
|
|
Two L{dns.Message} instances compare equal if they have the same
|
|
additional records.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(
|
|
additional=[
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
additional=[
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
additional=[
|
|
dns.RRHeader(b"example.org", payload=dns.Record_A("1.2.3.4"))
|
|
]
|
|
),
|
|
)
|
|
|
|
|
|
class TestController:
|
|
"""
|
|
Pretend to be a DNS query processor for a DNSDatagramProtocol.
|
|
|
|
@ivar messages: the list of received messages.
|
|
@type messages: C{list} of (msg, protocol, address)
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initialize the controller: create a list of messages.
|
|
"""
|
|
self.messages = []
|
|
|
|
def messageReceived(self, msg, proto, addr=None):
|
|
"""
|
|
Save the message so that it can be checked during the tests.
|
|
"""
|
|
self.messages.append((msg, proto, addr))
|
|
|
|
|
|
class DatagramProtocolTests(unittest.TestCase):
|
|
"""
|
|
Test various aspects of L{dns.DNSDatagramProtocol}.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Create a L{dns.DNSDatagramProtocol} with a deterministic clock.
|
|
"""
|
|
self.clock = task.Clock()
|
|
self.controller = TestController()
|
|
self.proto = dns.DNSDatagramProtocol(self.controller)
|
|
transport = proto_helpers.FakeDatagramTransport()
|
|
self.proto.makeConnection(transport)
|
|
self.proto.callLater = self.clock.callLater
|
|
|
|
def test_truncatedPacket(self):
|
|
"""
|
|
Test that when a short datagram is received, datagramReceived does
|
|
not raise an exception while processing it.
|
|
"""
|
|
self.proto.datagramReceived(b"", address.IPv4Address("UDP", "127.0.0.1", 12345))
|
|
self.assertEqual(self.controller.messages, [])
|
|
|
|
def test_malformedMessage(self):
|
|
"""
|
|
Test that when an unparsable message is received, datagramReceived does
|
|
not raise an exception while processing it.
|
|
"""
|
|
# message with a reference loop - captured in the field.
|
|
unparsable = (
|
|
b"\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x02\x11WWWW"
|
|
b"WWWWWW-XXXXXX\x08_arduino\x04_tcp\x05local\x00\x00\xff\x80\x01\xc0"
|
|
b"7\x00\x0c\x00\x01\x00\x00\x11\x94\x00\x02\xc0V\xc0V\x00!\x00\x01\x00"
|
|
b"\x00\x11\x94\x00\x08\x00\x00\x00\x00 J\xc0\x8f\xc0V\x00\x10\x00\x01"
|
|
b'\x00\x00\x11\x94\x00K\x0eauth_upload=no board="ESP8266_WEMOS_D1MINIL'
|
|
b'ITE"\rssh_upload=no\x0ctcp_check=no\xc0\x8f\x00\x01\x00\x01\x00\x00'
|
|
b"\x00x\x00\x04\xc0\xa8\x01)"
|
|
)
|
|
self.proto.datagramReceived(
|
|
unparsable, address.IPv4Address("UDP", "127.0.0.1", 12345)
|
|
)
|
|
self.assertEqual(self.controller.messages, [])
|
|
|
|
def test_simpleQuery(self):
|
|
"""
|
|
Test content received after a query.
|
|
"""
|
|
d = self.proto.query(("127.0.0.1", 21345), [dns.Query(b"foo")])
|
|
self.assertEqual(len(self.proto.liveMessages.keys()), 1)
|
|
m = dns.Message()
|
|
m.id = next(iter(self.proto.liveMessages.keys()))
|
|
m.answers = [dns.RRHeader(payload=dns.Record_A(address="1.2.3.4"))]
|
|
|
|
def cb(result):
|
|
self.assertEqual(result.answers[0].payload.dottedQuad(), "1.2.3.4")
|
|
|
|
d.addCallback(cb)
|
|
self.proto.datagramReceived(m.toStr(), ("127.0.0.1", 21345))
|
|
return d
|
|
|
|
def test_queryTimeout(self):
|
|
"""
|
|
Test that query timeouts after some seconds.
|
|
"""
|
|
d = self.proto.query(("127.0.0.1", 21345), [dns.Query(b"foo")])
|
|
self.assertEqual(len(self.proto.liveMessages), 1)
|
|
self.clock.advance(10)
|
|
self.assertFailure(d, dns.DNSQueryTimeoutError)
|
|
self.assertEqual(len(self.proto.liveMessages), 0)
|
|
return d
|
|
|
|
def test_writeError(self):
|
|
"""
|
|
Exceptions raised by the transport's write method should be turned into
|
|
C{Failure}s passed to errbacks of the C{Deferred} returned by
|
|
L{DNSDatagramProtocol.query}.
|
|
"""
|
|
|
|
def writeError(message, addr):
|
|
raise RuntimeError("bar")
|
|
|
|
self.proto.transport.write = writeError
|
|
|
|
d = self.proto.query(("127.0.0.1", 21345), [dns.Query(b"foo")])
|
|
return self.assertFailure(d, RuntimeError)
|
|
|
|
def test_listenError(self):
|
|
"""
|
|
Exception L{CannotListenError} raised by C{listenUDP} should be turned
|
|
into a C{Failure} passed to errback of the C{Deferred} returned by
|
|
L{DNSDatagramProtocol.query}.
|
|
"""
|
|
|
|
def startListeningError():
|
|
raise CannotListenError(None, None, None)
|
|
|
|
self.proto.startListening = startListeningError
|
|
# Clean up transport so that the protocol calls startListening again
|
|
self.proto.transport = None
|
|
|
|
d = self.proto.query(("127.0.0.1", 21345), [dns.Query(b"foo")])
|
|
return self.assertFailure(d, CannotListenError)
|
|
|
|
def test_receiveMessageNotInLiveMessages(self):
|
|
"""
|
|
When receiving a message whose id is not in
|
|
L{DNSDatagramProtocol.liveMessages} or L{DNSDatagramProtocol.resends},
|
|
the message will be received by L{DNSDatagramProtocol.controller}.
|
|
"""
|
|
message = dns.Message()
|
|
message.id = 1
|
|
message.answers = [dns.RRHeader(payload=dns.Record_A(address="1.2.3.4"))]
|
|
self.proto.datagramReceived(message.toStr(), ("127.0.0.1", 21345))
|
|
self.assertEqual(self.controller.messages[-1][0].toStr(), message.toStr())
|
|
|
|
|
|
class TestTCPController(TestController):
|
|
"""
|
|
Pretend to be a DNS query processor for a DNSProtocol.
|
|
|
|
@ivar connections: A list of L{DNSProtocol} instances which have
|
|
notified this controller that they are connected and have not
|
|
yet notified it that their connection has been lost.
|
|
"""
|
|
|
|
def __init__(self):
|
|
TestController.__init__(self)
|
|
self.connections = []
|
|
|
|
def connectionMade(self, proto):
|
|
self.connections.append(proto)
|
|
|
|
def connectionLost(self, proto):
|
|
self.connections.remove(proto)
|
|
|
|
|
|
class DNSProtocolTests(unittest.TestCase):
|
|
"""
|
|
Test various aspects of L{dns.DNSProtocol}.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""
|
|
Create a L{dns.DNSProtocol} with a deterministic clock.
|
|
"""
|
|
self.clock = task.Clock()
|
|
self.controller = TestTCPController()
|
|
self.proto = dns.DNSProtocol(self.controller)
|
|
self.proto.makeConnection(proto_helpers.StringTransport())
|
|
self.proto.callLater = self.clock.callLater
|
|
|
|
def test_connectionTracking(self):
|
|
"""
|
|
L{dns.DNSProtocol} calls its controller's C{connectionMade}
|
|
method with itself when it is connected to a transport and its
|
|
controller's C{connectionLost} method when it is disconnected.
|
|
"""
|
|
self.assertEqual(self.controller.connections, [self.proto])
|
|
self.proto.connectionLost(Failure(ConnectionDone("Fake Connection Done")))
|
|
self.assertEqual(self.controller.connections, [])
|
|
|
|
def test_queryTimeout(self):
|
|
"""
|
|
Test that query timeouts after some seconds.
|
|
"""
|
|
d = self.proto.query([dns.Query(b"foo")])
|
|
self.assertEqual(len(self.proto.liveMessages), 1)
|
|
self.clock.advance(60)
|
|
self.assertFailure(d, dns.DNSQueryTimeoutError)
|
|
self.assertEqual(len(self.proto.liveMessages), 0)
|
|
return d
|
|
|
|
def test_simpleQuery(self):
|
|
"""
|
|
Test content received after a query.
|
|
"""
|
|
d = self.proto.query([dns.Query(b"foo")])
|
|
self.assertEqual(len(self.proto.liveMessages.keys()), 1)
|
|
m = dns.Message()
|
|
m.id = next(iter(self.proto.liveMessages.keys()))
|
|
m.answers = [dns.RRHeader(payload=dns.Record_A(address="1.2.3.4"))]
|
|
|
|
def cb(result):
|
|
self.assertEqual(result.answers[0].payload.dottedQuad(), "1.2.3.4")
|
|
|
|
d.addCallback(cb)
|
|
s = m.toStr()
|
|
s = struct.pack("!H", len(s)) + s
|
|
self.proto.dataReceived(s)
|
|
return d
|
|
|
|
def test_writeError(self):
|
|
"""
|
|
Exceptions raised by the transport's write method should be turned into
|
|
C{Failure}s passed to errbacks of the C{Deferred} returned by
|
|
L{DNSProtocol.query}.
|
|
"""
|
|
|
|
def writeError(message):
|
|
raise RuntimeError("bar")
|
|
|
|
self.proto.transport.write = writeError
|
|
|
|
d = self.proto.query([dns.Query(b"foo")])
|
|
return self.assertFailure(d, RuntimeError)
|
|
|
|
def test_receiveMessageNotInLiveMessages(self):
|
|
"""
|
|
When receiving a message whose id is not in L{DNSProtocol.liveMessages}
|
|
the message will be received by L{DNSProtocol.controller}.
|
|
"""
|
|
message = dns.Message()
|
|
message.id = 1
|
|
message.answers = [dns.RRHeader(payload=dns.Record_A(address="1.2.3.4"))]
|
|
string = message.toStr()
|
|
string = struct.pack("!H", len(string)) + string
|
|
self.proto.dataReceived(string)
|
|
self.assertEqual(self.controller.messages[-1][0].toStr(), message.toStr())
|
|
|
|
|
|
class ReprTests(unittest.TestCase):
|
|
"""
|
|
Tests for the C{__repr__} implementation of record classes.
|
|
"""
|
|
|
|
def test_ns(self):
|
|
"""
|
|
The repr of a L{dns.Record_NS} instance includes the name of the
|
|
nameserver and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_NS(b"example.com", 4321)), "<NS name=example.com ttl=4321>"
|
|
)
|
|
|
|
def test_md(self):
|
|
"""
|
|
The repr of a L{dns.Record_MD} instance includes the name of the
|
|
mail destination and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_MD(b"example.com", 4321)), "<MD name=example.com ttl=4321>"
|
|
)
|
|
|
|
def test_mf(self):
|
|
"""
|
|
The repr of a L{dns.Record_MF} instance includes the name of the
|
|
mail forwarder and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_MF(b"example.com", 4321)), "<MF name=example.com ttl=4321>"
|
|
)
|
|
|
|
def test_cname(self):
|
|
"""
|
|
The repr of a L{dns.Record_CNAME} instance includes the name of the
|
|
mail forwarder and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_CNAME(b"example.com", 4321)),
|
|
"<CNAME name=example.com ttl=4321>",
|
|
)
|
|
|
|
def test_mb(self):
|
|
"""
|
|
The repr of a L{dns.Record_MB} instance includes the name of the
|
|
mailbox and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_MB(b"example.com", 4321)), "<MB name=example.com ttl=4321>"
|
|
)
|
|
|
|
def test_mg(self):
|
|
"""
|
|
The repr of a L{dns.Record_MG} instance includes the name of the
|
|
mail group member and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_MG(b"example.com", 4321)), "<MG name=example.com ttl=4321>"
|
|
)
|
|
|
|
def test_mr(self):
|
|
"""
|
|
The repr of a L{dns.Record_MR} instance includes the name of the
|
|
mail rename domain and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_MR(b"example.com", 4321)), "<MR name=example.com ttl=4321>"
|
|
)
|
|
|
|
def test_ptr(self):
|
|
"""
|
|
The repr of a L{dns.Record_PTR} instance includes the name of the
|
|
pointer and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_PTR(b"example.com", 4321)),
|
|
"<PTR name=example.com ttl=4321>",
|
|
)
|
|
|
|
def test_dname(self):
|
|
"""
|
|
The repr of a L{dns.Record_DNAME} instance includes the name of the
|
|
non-terminal DNS name redirection and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_DNAME(b"example.com", 4321)),
|
|
"<DNAME name=example.com ttl=4321>",
|
|
)
|
|
|
|
def test_a(self):
|
|
"""
|
|
The repr of a L{dns.Record_A} instance includes the dotted-quad
|
|
string representation of the address it is for and the TTL of the
|
|
record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_A("1.2.3.4", 567)), "<A address=1.2.3.4 ttl=567>"
|
|
)
|
|
|
|
def test_soa(self):
|
|
"""
|
|
The repr of a L{dns.Record_SOA} instance includes all of the
|
|
authority fields.
|
|
"""
|
|
self.assertEqual(
|
|
repr(
|
|
dns.Record_SOA(
|
|
mname=b"mName",
|
|
rname=b"rName",
|
|
serial=123,
|
|
refresh=456,
|
|
retry=789,
|
|
expire=10,
|
|
minimum=11,
|
|
ttl=12,
|
|
)
|
|
),
|
|
"<SOA mname=mName rname=rName serial=123 refresh=456 "
|
|
"retry=789 expire=10 minimum=11 ttl=12>",
|
|
)
|
|
|
|
def test_null(self):
|
|
"""
|
|
The repr of a L{dns.Record_NULL} instance includes the repr of its
|
|
payload and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_NULL(b"abcd", 123)), "<NULL payload='abcd' ttl=123>"
|
|
)
|
|
|
|
def test_wks(self):
|
|
"""
|
|
The repr of a L{dns.Record_WKS} instance includes the dotted-quad
|
|
string representation of the address it is for, the IP protocol
|
|
number it is for, and the TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_WKS("2.3.4.5", 7, ttl=8)),
|
|
"<WKS address=2.3.4.5 protocol=7 ttl=8>",
|
|
)
|
|
|
|
def test_aaaa(self):
|
|
"""
|
|
The repr of a L{dns.Record_AAAA} instance includes the colon-separated
|
|
hex string representation of the address it is for and the TTL of the
|
|
record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_AAAA("8765::1234", ttl=10)),
|
|
"<AAAA address=8765::1234 ttl=10>",
|
|
)
|
|
|
|
def test_a6(self):
|
|
"""
|
|
The repr of a L{dns.Record_A6} instance includes the colon-separated
|
|
hex string representation of the address it is for and the TTL of the
|
|
record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_A6(0, "1234::5678", b"foo.bar", ttl=10)),
|
|
"<A6 suffix=1234::5678 prefix=foo.bar ttl=10>",
|
|
)
|
|
|
|
def test_srv(self):
|
|
"""
|
|
The repr of a L{dns.Record_SRV} instance includes the name and port of
|
|
the target and the priority, weight, and TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_SRV(1, 2, 3, b"example.org", 4)),
|
|
"<SRV priority=1 weight=2 target=example.org port=3 ttl=4>",
|
|
)
|
|
|
|
def test_naptr(self):
|
|
"""
|
|
The repr of a L{dns.Record_NAPTR} instance includes the order,
|
|
preference, flags, service, regular expression, replacement, and TTL of
|
|
the record.
|
|
"""
|
|
record = dns.Record_NAPTR(5, 9, b"S", b"http", b"/foo/bar/i", b"baz", 3)
|
|
self.assertEqual(
|
|
repr(record),
|
|
"<NAPTR order=5 preference=9 flags=S service=http "
|
|
"regexp=/foo/bar/i replacement=baz ttl=3>",
|
|
)
|
|
|
|
def test_afsdb(self):
|
|
"""
|
|
The repr of a L{dns.Record_AFSDB} instance includes the subtype,
|
|
hostname, and TTL of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_AFSDB(3, b"example.org", 5)),
|
|
"<AFSDB subtype=3 hostname=example.org ttl=5>",
|
|
)
|
|
|
|
def test_rp(self):
|
|
"""
|
|
The repr of a L{dns.Record_RP} instance includes the mbox, txt, and TTL
|
|
fields of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_RP(b"alice.example.com", b"admin.example.com", 3)),
|
|
"<RP mbox=alice.example.com txt=admin.example.com ttl=3>",
|
|
)
|
|
|
|
def test_hinfo(self):
|
|
"""
|
|
The repr of a L{dns.Record_HINFO} instance includes the cpu, os, and
|
|
TTL fields of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_HINFO(b"sparc", b"minix", 12)),
|
|
"<HINFO cpu='sparc' os='minix' ttl=12>",
|
|
)
|
|
|
|
def test_minfo(self):
|
|
"""
|
|
The repr of a L{dns.Record_MINFO} instance includes the rmailbx,
|
|
emailbx, and TTL fields of the record.
|
|
"""
|
|
record = dns.Record_MINFO(b"alice.example.com", b"bob.example.com", 15)
|
|
self.assertEqual(
|
|
repr(record),
|
|
"<MINFO responsibility=alice.example.com " "errors=bob.example.com ttl=15>",
|
|
)
|
|
|
|
def test_mx(self):
|
|
"""
|
|
The repr of a L{dns.Record_MX} instance includes the preference, name,
|
|
and TTL fields of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_MX(13, b"mx.example.com", 2)),
|
|
"<MX preference=13 name=mx.example.com ttl=2>",
|
|
)
|
|
|
|
def test_txt(self):
|
|
"""
|
|
The repr of a L{dns.Record_TXT} instance includes the data and ttl
|
|
fields of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_TXT(b"foo", b"bar", ttl=15)),
|
|
"<TXT data=['foo', 'bar'] ttl=15>",
|
|
)
|
|
|
|
def test_spf(self):
|
|
"""
|
|
The repr of a L{dns.Record_SPF} instance includes the data and ttl
|
|
fields of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.Record_SPF(b"foo", b"bar", ttl=15)),
|
|
"<SPF data=['foo', 'bar'] ttl=15>",
|
|
)
|
|
|
|
def test_unknown(self):
|
|
"""
|
|
The repr of a L{dns.UnknownRecord} instance includes the data and ttl
|
|
fields of the record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns.UnknownRecord(b"foo\x1fbar", 12)),
|
|
"<UNKNOWN data='foo\\x1fbar' ttl=12>",
|
|
)
|
|
|
|
|
|
class EqualityTests(ComparisonTestsMixin, unittest.TestCase):
|
|
"""
|
|
Tests for the equality and non-equality behavior of record classes.
|
|
"""
|
|
|
|
def _equalityTest(self, firstValueOne, secondValueOne, valueTwo):
|
|
return self.assertNormalEqualityImplementation(
|
|
firstValueOne, secondValueOne, valueTwo
|
|
)
|
|
|
|
def test_charstr(self):
|
|
"""
|
|
Two L{dns.Charstr} instances compare equal if and only if they have the
|
|
same string value.
|
|
"""
|
|
self._equalityTest(
|
|
dns.Charstr(b"abc"), dns.Charstr(b"abc"), dns.Charstr(b"def")
|
|
)
|
|
|
|
def test_name(self):
|
|
"""
|
|
Two L{dns.Name} instances compare equal if and only if they have the
|
|
same name value.
|
|
"""
|
|
self._equalityTest(dns.Name(b"abc"), dns.Name(b"abc"), dns.Name(b"def"))
|
|
|
|
def _simpleEqualityTest(self, cls):
|
|
"""
|
|
Assert that instances of C{cls} with the same attributes compare equal
|
|
to each other and instances with different attributes compare as not
|
|
equal.
|
|
|
|
@param cls: A L{dns.SimpleRecord} subclass.
|
|
"""
|
|
# Vary the TTL
|
|
self._equalityTest(
|
|
cls(b"example.com", 123), cls(b"example.com", 123), cls(b"example.com", 321)
|
|
)
|
|
# Vary the name
|
|
self._equalityTest(
|
|
cls(b"example.com", 123), cls(b"example.com", 123), cls(b"example.org", 123)
|
|
)
|
|
|
|
def test_rrheader(self):
|
|
"""
|
|
Two L{dns.RRHeader} instances compare equal if and only if they have
|
|
the same name, type, class, time to live, payload, and authoritative
|
|
bit.
|
|
"""
|
|
# Vary the name
|
|
self._equalityTest(
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.org", payload=dns.Record_A("1.2.3.4")),
|
|
)
|
|
|
|
# Vary the payload
|
|
self._equalityTest(
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.5")),
|
|
)
|
|
|
|
# Vary the type. Leave the payload as None so that we don't have to
|
|
# provide non-equal values.
|
|
self._equalityTest(
|
|
dns.RRHeader(b"example.com", dns.A),
|
|
dns.RRHeader(b"example.com", dns.A),
|
|
dns.RRHeader(b"example.com", dns.MX),
|
|
)
|
|
|
|
# Probably not likely to come up. Most people use the internet.
|
|
self._equalityTest(
|
|
dns.RRHeader(b"example.com", cls=dns.IN, payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", cls=dns.IN, payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", cls=dns.CS, payload=dns.Record_A("1.2.3.4")),
|
|
)
|
|
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.RRHeader(b"example.com", ttl=60, payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", ttl=60, payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", ttl=120, payload=dns.Record_A("1.2.3.4")),
|
|
)
|
|
|
|
# Vary the auth bit
|
|
self._equalityTest(
|
|
dns.RRHeader(b"example.com", auth=1, payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", auth=1, payload=dns.Record_A("1.2.3.4")),
|
|
dns.RRHeader(b"example.com", auth=0, payload=dns.Record_A("1.2.3.4")),
|
|
)
|
|
|
|
def test_ns(self):
|
|
"""
|
|
Two L{dns.Record_NS} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_NS)
|
|
|
|
def test_md(self):
|
|
"""
|
|
Two L{dns.Record_MD} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_MD)
|
|
|
|
def test_mf(self):
|
|
"""
|
|
Two L{dns.Record_MF} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_MF)
|
|
|
|
def test_cname(self):
|
|
"""
|
|
Two L{dns.Record_CNAME} instances compare equal if and only if they
|
|
have the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_CNAME)
|
|
|
|
def test_mb(self):
|
|
"""
|
|
Two L{dns.Record_MB} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_MB)
|
|
|
|
def test_mg(self):
|
|
"""
|
|
Two L{dns.Record_MG} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_MG)
|
|
|
|
def test_mr(self):
|
|
"""
|
|
Two L{dns.Record_MR} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_MR)
|
|
|
|
def test_ptr(self):
|
|
"""
|
|
Two L{dns.Record_PTR} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_PTR)
|
|
|
|
def test_dname(self):
|
|
"""
|
|
Two L{dns.Record_MD} instances compare equal if and only if they have
|
|
the same name and TTL.
|
|
"""
|
|
self._simpleEqualityTest(dns.Record_DNAME)
|
|
|
|
def test_a(self):
|
|
"""
|
|
Two L{dns.Record_A} instances compare equal if and only if they have
|
|
the same address and TTL.
|
|
"""
|
|
# Vary the TTL
|
|
self._equalityTest(
|
|
dns.Record_A("1.2.3.4", 5),
|
|
dns.Record_A("1.2.3.4", 5),
|
|
dns.Record_A("1.2.3.4", 6),
|
|
)
|
|
# Vary the address
|
|
self._equalityTest(
|
|
dns.Record_A("1.2.3.4", 5),
|
|
dns.Record_A("1.2.3.4", 5),
|
|
dns.Record_A("1.2.3.5", 5),
|
|
)
|
|
|
|
def test_soa(self):
|
|
"""
|
|
Two L{dns.Record_SOA} instances compare equal if and only if they have
|
|
the same mname, rname, serial, refresh, minimum, expire, retry, and
|
|
ttl.
|
|
"""
|
|
# Vary the mname
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"xname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
)
|
|
# Vary the rname
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"xname", 123, 456, 789, 10, 20, 30),
|
|
)
|
|
# Vary the serial
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 1, 456, 789, 10, 20, 30),
|
|
)
|
|
# Vary the refresh
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 1, 789, 10, 20, 30),
|
|
)
|
|
# Vary the minimum
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 1, 10, 20, 30),
|
|
)
|
|
# Vary the expire
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 1, 20, 30),
|
|
)
|
|
# Vary the retry
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 1, 30),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"rname", 123, 456, 789, 10, 20, 30),
|
|
dns.Record_SOA(b"mname", b"xname", 123, 456, 789, 10, 20, 1),
|
|
)
|
|
|
|
def test_null(self):
|
|
"""
|
|
Two L{dns.Record_NULL} instances compare equal if and only if they have
|
|
the same payload and ttl.
|
|
"""
|
|
# Vary the payload
|
|
self._equalityTest(
|
|
dns.Record_NULL("foo bar", 10),
|
|
dns.Record_NULL("foo bar", 10),
|
|
dns.Record_NULL("bar foo", 10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_NULL("foo bar", 10),
|
|
dns.Record_NULL("foo bar", 10),
|
|
dns.Record_NULL("foo bar", 100),
|
|
)
|
|
|
|
def test_wks(self):
|
|
"""
|
|
Two L{dns.Record_WKS} instances compare equal if and only if they have
|
|
the same address, protocol, map, and ttl.
|
|
"""
|
|
# Vary the address
|
|
self._equalityTest(
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("4.3.2.1", 1, "foo", 2),
|
|
)
|
|
# Vary the protocol
|
|
self._equalityTest(
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("1.2.3.4", 100, "foo", 2),
|
|
)
|
|
# Vary the map
|
|
self._equalityTest(
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("1.2.3.4", 1, "bar", 2),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 2),
|
|
dns.Record_WKS("1.2.3.4", 1, "foo", 200),
|
|
)
|
|
|
|
def test_aaaa(self):
|
|
"""
|
|
Two L{dns.Record_AAAA} instances compare equal if and only if they have
|
|
the same address and ttl.
|
|
"""
|
|
# Vary the address
|
|
self._equalityTest(
|
|
dns.Record_AAAA("1::2", 1),
|
|
dns.Record_AAAA("1::2", 1),
|
|
dns.Record_AAAA("2::1", 1),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_AAAA("1::2", 1),
|
|
dns.Record_AAAA("1::2", 1),
|
|
dns.Record_AAAA("1::2", 10),
|
|
)
|
|
|
|
def test_a6(self):
|
|
"""
|
|
Two L{dns.Record_A6} instances compare equal if and only if they have
|
|
the same prefix, prefix length, suffix, and ttl.
|
|
"""
|
|
# Note, A6 is crazy, I'm not sure these values are actually legal.
|
|
# Hopefully that doesn't matter for this test. -exarkun
|
|
|
|
# Vary the prefix length
|
|
self._equalityTest(
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(32, "::abcd", b"example.com", 10),
|
|
)
|
|
# Vary the suffix
|
|
self._equalityTest(
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(16, "::abcd:0", b"example.com", 10),
|
|
)
|
|
# Vary the prefix
|
|
self._equalityTest(
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(16, "::abcd", b"example.org", 10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(16, "::abcd", b"example.com", 10),
|
|
dns.Record_A6(16, "::abcd", b"example.com", 100),
|
|
)
|
|
|
|
def test_srv(self):
|
|
"""
|
|
Two L{dns.Record_SRV} instances compare equal if and only if they have
|
|
the same priority, weight, port, target, and ttl.
|
|
"""
|
|
# Vary the priority
|
|
self._equalityTest(
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(100, 20, 30, b"example.com", 40),
|
|
)
|
|
# Vary the weight
|
|
self._equalityTest(
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 200, 30, b"example.com", 40),
|
|
)
|
|
# Vary the port
|
|
self._equalityTest(
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 300, b"example.com", 40),
|
|
)
|
|
# Vary the target
|
|
self._equalityTest(
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 30, b"example.org", 40),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 40),
|
|
dns.Record_SRV(10, 20, 30, b"example.com", 400),
|
|
)
|
|
|
|
def test_sshfp(self):
|
|
"""
|
|
Two L{dns.Record_SSHFP} instances compare equal if and only if
|
|
they have the same key type, fingerprint type, fingerprint, and ttl.
|
|
"""
|
|
# Vary the key type.
|
|
self._equalityTest(
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(2, 2, b"happyday", 40),
|
|
)
|
|
# Vary the fingerprint type.
|
|
self._equalityTest(
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(1, 1, b"happyday", 40),
|
|
)
|
|
# Vary the fingerprint itself.
|
|
self._equalityTest(
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(1, 2, b"happxday", 40),
|
|
)
|
|
# Vary the ttl.
|
|
self._equalityTest(
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(1, 2, b"happyday", 40),
|
|
dns.Record_SSHFP(1, 2, b"happyday", 45),
|
|
)
|
|
|
|
def test_naptr(self):
|
|
"""
|
|
Two L{dns.Record_NAPTR} instances compare equal if and only if they
|
|
have the same order, preference, flags, service, regexp, replacement,
|
|
and ttl.
|
|
"""
|
|
# Vary the order
|
|
self._equalityTest(
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(2, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
)
|
|
# Vary the preference
|
|
self._equalityTest(
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 3, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
)
|
|
# Vary the flags
|
|
self._equalityTest(
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"p", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
)
|
|
# Vary the service
|
|
self._equalityTest(
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"http", b"/foo/bar/", b"baz", 12),
|
|
)
|
|
# Vary the regexp
|
|
self._equalityTest(
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/bar/foo/", b"baz", 12),
|
|
)
|
|
# Vary the replacement
|
|
self._equalityTest(
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/bar/foo/", b"quux", 12),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/foo/bar/", b"baz", 12),
|
|
dns.Record_NAPTR(1, 2, b"u", b"sip+E2U", b"/bar/foo/", b"baz", 5),
|
|
)
|
|
|
|
def test_afsdb(self):
|
|
"""
|
|
Two L{dns.Record_AFSDB} instances compare equal if and only if they
|
|
have the same subtype, hostname, and ttl.
|
|
"""
|
|
# Vary the subtype
|
|
self._equalityTest(
|
|
dns.Record_AFSDB(1, b"example.com", 2),
|
|
dns.Record_AFSDB(1, b"example.com", 2),
|
|
dns.Record_AFSDB(2, b"example.com", 2),
|
|
)
|
|
# Vary the hostname
|
|
self._equalityTest(
|
|
dns.Record_AFSDB(1, b"example.com", 2),
|
|
dns.Record_AFSDB(1, b"example.com", 2),
|
|
dns.Record_AFSDB(1, b"example.org", 2),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_AFSDB(1, b"example.com", 2),
|
|
dns.Record_AFSDB(1, b"example.com", 2),
|
|
dns.Record_AFSDB(1, b"example.com", 3),
|
|
)
|
|
|
|
def test_rp(self):
|
|
"""
|
|
Two L{Record_RP} instances compare equal if and only if they have the
|
|
same mbox, txt, and ttl.
|
|
"""
|
|
# Vary the mbox
|
|
self._equalityTest(
|
|
dns.Record_RP(b"alice.example.com", b"alice is nice", 10),
|
|
dns.Record_RP(b"alice.example.com", b"alice is nice", 10),
|
|
dns.Record_RP(b"bob.example.com", b"alice is nice", 10),
|
|
)
|
|
# Vary the txt
|
|
self._equalityTest(
|
|
dns.Record_RP(b"alice.example.com", b"alice is nice", 10),
|
|
dns.Record_RP(b"alice.example.com", b"alice is nice", 10),
|
|
dns.Record_RP(b"alice.example.com", b"alice is not nice", 10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_RP(b"alice.example.com", b"alice is nice", 10),
|
|
dns.Record_RP(b"alice.example.com", b"alice is nice", 10),
|
|
dns.Record_RP(b"alice.example.com", b"alice is nice", 100),
|
|
)
|
|
|
|
def test_hinfo(self):
|
|
"""
|
|
Two L{dns.Record_HINFO} instances compare equal if and only if they
|
|
have the same cpu, os, and ttl.
|
|
"""
|
|
# Vary the cpu
|
|
self._equalityTest(
|
|
dns.Record_HINFO("x86-64", "plan9", 10),
|
|
dns.Record_HINFO("x86-64", "plan9", 10),
|
|
dns.Record_HINFO("i386", "plan9", 10),
|
|
)
|
|
# Vary the os
|
|
self._equalityTest(
|
|
dns.Record_HINFO("x86-64", "plan9", 10),
|
|
dns.Record_HINFO("x86-64", "plan9", 10),
|
|
dns.Record_HINFO("x86-64", "plan11", 10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_HINFO("x86-64", "plan9", 10),
|
|
dns.Record_HINFO("x86-64", "plan9", 10),
|
|
dns.Record_HINFO("x86-64", "plan9", 100),
|
|
)
|
|
|
|
def test_minfo(self):
|
|
"""
|
|
Two L{dns.Record_MINFO} instances compare equal if and only if they
|
|
have the same rmailbx, emailbx, and ttl.
|
|
"""
|
|
# Vary the rmailbx
|
|
self._equalityTest(
|
|
dns.Record_MINFO(b"rmailbox", b"emailbox", 10),
|
|
dns.Record_MINFO(b"rmailbox", b"emailbox", 10),
|
|
dns.Record_MINFO(b"someplace", b"emailbox", 10),
|
|
)
|
|
# Vary the emailbx
|
|
self._equalityTest(
|
|
dns.Record_MINFO(b"rmailbox", b"emailbox", 10),
|
|
dns.Record_MINFO(b"rmailbox", b"emailbox", 10),
|
|
dns.Record_MINFO(b"rmailbox", b"something", 10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_MINFO(b"rmailbox", b"emailbox", 10),
|
|
dns.Record_MINFO(b"rmailbox", b"emailbox", 10),
|
|
dns.Record_MINFO(b"rmailbox", b"emailbox", 100),
|
|
)
|
|
|
|
def test_mx(self):
|
|
"""
|
|
Two L{dns.Record_MX} instances compare equal if and only if they have
|
|
the same preference, name, and ttl.
|
|
"""
|
|
# Vary the preference
|
|
self._equalityTest(
|
|
dns.Record_MX(10, b"example.org", 20),
|
|
dns.Record_MX(10, b"example.org", 20),
|
|
dns.Record_MX(100, b"example.org", 20),
|
|
)
|
|
# Vary the name
|
|
self._equalityTest(
|
|
dns.Record_MX(10, b"example.org", 20),
|
|
dns.Record_MX(10, b"example.org", 20),
|
|
dns.Record_MX(10, b"example.net", 20),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_MX(10, b"example.org", 20),
|
|
dns.Record_MX(10, b"example.org", 20),
|
|
dns.Record_MX(10, b"example.org", 200),
|
|
)
|
|
|
|
def test_txt(self):
|
|
"""
|
|
Two L{dns.Record_TXT} instances compare equal if and only if they have
|
|
the same data and ttl.
|
|
"""
|
|
# Vary the length of the data
|
|
self._equalityTest(
|
|
dns.Record_TXT("foo", "bar", ttl=10),
|
|
dns.Record_TXT("foo", "bar", ttl=10),
|
|
dns.Record_TXT("foo", "bar", "baz", ttl=10),
|
|
)
|
|
# Vary the value of the data
|
|
self._equalityTest(
|
|
dns.Record_TXT("foo", "bar", ttl=10),
|
|
dns.Record_TXT("foo", "bar", ttl=10),
|
|
dns.Record_TXT("bar", "foo", ttl=10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_TXT("foo", "bar", ttl=10),
|
|
dns.Record_TXT("foo", "bar", ttl=10),
|
|
dns.Record_TXT("foo", "bar", ttl=100),
|
|
)
|
|
|
|
def test_spf(self):
|
|
"""
|
|
L{dns.Record_SPF} instances compare equal if and only if they have the
|
|
same data and ttl.
|
|
"""
|
|
# Vary the length of the data
|
|
self._equalityTest(
|
|
dns.Record_SPF("foo", "bar", ttl=10),
|
|
dns.Record_SPF("foo", "bar", ttl=10),
|
|
dns.Record_SPF("foo", "bar", "baz", ttl=10),
|
|
)
|
|
# Vary the value of the data
|
|
self._equalityTest(
|
|
dns.Record_SPF("foo", "bar", ttl=10),
|
|
dns.Record_SPF("foo", "bar", ttl=10),
|
|
dns.Record_SPF("bar", "foo", ttl=10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.Record_SPF("foo", "bar", ttl=10),
|
|
dns.Record_SPF("foo", "bar", ttl=10),
|
|
dns.Record_SPF("foo", "bar", ttl=100),
|
|
)
|
|
|
|
def test_tsig(self):
|
|
"""
|
|
L{dns.Record_TSIG} instances compare equal if and only if they have the
|
|
same RDATA (algorithm, timestamp, MAC, etc.) and ttl.
|
|
"""
|
|
baseargs = {
|
|
"algorithm": "hmac-sha224",
|
|
"timeSigned": 1515548975,
|
|
"fudge": 5,
|
|
"MAC": b"\x01\x02\x03\x04\x05",
|
|
"originalID": 99,
|
|
"error": dns.OK,
|
|
"otherData": b"",
|
|
"ttl": 40,
|
|
}
|
|
altargs = {
|
|
"algorithm": "hmac-sha512",
|
|
"timeSigned": 1515548875,
|
|
"fudge": 0,
|
|
"MAC": b"\x05\x04\x03\x02\x01",
|
|
"originalID": 65437,
|
|
"error": dns.EBADTIME,
|
|
"otherData": b"\x00\x00",
|
|
"ttl": 400,
|
|
}
|
|
for kw in baseargs.keys():
|
|
altered = baseargs.copy()
|
|
altered[kw] = altargs[kw]
|
|
self._equalityTest(
|
|
dns.Record_TSIG(**altered),
|
|
dns.Record_TSIG(**altered),
|
|
dns.Record_TSIG(**baseargs),
|
|
)
|
|
|
|
def test_unknown(self):
|
|
"""
|
|
L{dns.UnknownRecord} instances compare equal if and only if they have
|
|
the same data and ttl.
|
|
"""
|
|
# Vary the length of the data
|
|
self._equalityTest(
|
|
dns.UnknownRecord("foo", ttl=10),
|
|
dns.UnknownRecord("foo", ttl=10),
|
|
dns.UnknownRecord("foobar", ttl=10),
|
|
)
|
|
# Vary the value of the data
|
|
self._equalityTest(
|
|
dns.UnknownRecord("foo", ttl=10),
|
|
dns.UnknownRecord("foo", ttl=10),
|
|
dns.UnknownRecord("bar", ttl=10),
|
|
)
|
|
# Vary the ttl
|
|
self._equalityTest(
|
|
dns.UnknownRecord("foo", ttl=10),
|
|
dns.UnknownRecord("foo", ttl=10),
|
|
dns.UnknownRecord("foo", ttl=100),
|
|
)
|
|
|
|
|
|
class RRHeaderTests(unittest.TestCase):
|
|
"""
|
|
Tests for L{twisted.names.dns.RRHeader}.
|
|
"""
|
|
|
|
def test_negativeTTL(self):
|
|
"""
|
|
Attempting to create a L{dns.RRHeader} instance with a negative TTL
|
|
causes L{ValueError} to be raised.
|
|
"""
|
|
self.assertRaises(
|
|
ValueError,
|
|
dns.RRHeader,
|
|
"example.com",
|
|
dns.A,
|
|
dns.IN,
|
|
-1,
|
|
dns.Record_A("127.0.0.1"),
|
|
)
|
|
|
|
def test_nonIntegralTTL(self):
|
|
"""
|
|
L{dns.RRHeader} converts TTLs to integers.
|
|
"""
|
|
ttlAsFloat = 123.45
|
|
header = dns.RRHeader(
|
|
"example.com", dns.A, dns.IN, ttlAsFloat, dns.Record_A("127.0.0.1")
|
|
)
|
|
self.assertEqual(header.ttl, int(ttlAsFloat))
|
|
|
|
def test_nonNumericTTLRaisesTypeError(self):
|
|
"""
|
|
Attempting to create a L{dns.RRHeader} instance with a TTL
|
|
that L{int} cannot convert to an integer raises a L{TypeError}.
|
|
"""
|
|
self.assertRaises(
|
|
ValueError,
|
|
dns.RRHeader,
|
|
"example.com",
|
|
dns.A,
|
|
dns.IN,
|
|
"this is not a number",
|
|
dns.Record_A("127.0.0.1"),
|
|
)
|
|
|
|
|
|
class NameToLabelsTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{twisted.names.dns._nameToLabels}.
|
|
"""
|
|
|
|
def test_empty(self):
|
|
"""
|
|
L{dns._nameToLabels} returns a list containing a single
|
|
empty label for an empty name.
|
|
"""
|
|
self.assertEqual(dns._nameToLabels(b""), [b""])
|
|
|
|
def test_onlyDot(self):
|
|
"""
|
|
L{dns._nameToLabels} returns a list containing a single
|
|
empty label for a name containing only a dot.
|
|
"""
|
|
self.assertEqual(dns._nameToLabels(b"."), [b""])
|
|
|
|
def test_withoutTrailingDot(self):
|
|
"""
|
|
L{dns._nameToLabels} returns a list ending with an empty
|
|
label for a name without a trailing dot.
|
|
"""
|
|
self.assertEqual(dns._nameToLabels(b"com"), [b"com", b""])
|
|
|
|
def test_withTrailingDot(self):
|
|
"""
|
|
L{dns._nameToLabels} returns a list ending with an empty
|
|
label for a name with a trailing dot.
|
|
"""
|
|
self.assertEqual(dns._nameToLabels(b"com."), [b"com", b""])
|
|
|
|
def test_subdomain(self):
|
|
"""
|
|
L{dns._nameToLabels} returns a list containing entries
|
|
for all labels in a subdomain name.
|
|
"""
|
|
self.assertEqual(
|
|
dns._nameToLabels(b"foo.bar.baz.example.com."),
|
|
[b"foo", b"bar", b"baz", b"example", b"com", b""],
|
|
)
|
|
|
|
def test_casePreservation(self):
|
|
"""
|
|
L{dns._nameToLabels} preserves the case of ascii
|
|
characters in labels.
|
|
"""
|
|
self.assertEqual(dns._nameToLabels(b"EXAMPLE.COM"), [b"EXAMPLE", b"COM", b""])
|
|
|
|
|
|
def assertIsSubdomainOf(testCase, descendant, ancestor):
|
|
"""
|
|
Assert that C{descendant} *is* a subdomain of C{ancestor}.
|
|
|
|
@type testCase: L{unittest.SynchronousTestCase}
|
|
@param testCase: The test case on which to run the assertions.
|
|
|
|
@type descendant: C{str}
|
|
@param descendant: The subdomain name to test.
|
|
|
|
@type ancestor: C{str}
|
|
@param ancestor: The superdomain name to test.
|
|
"""
|
|
testCase.assertTrue(
|
|
dns._isSubdomainOf(descendant, ancestor),
|
|
f"{descendant!r} is not a subdomain of {ancestor!r}",
|
|
)
|
|
|
|
|
|
def assertIsNotSubdomainOf(testCase, descendant, ancestor):
|
|
"""
|
|
Assert that C{descendant} *is not* a subdomain of C{ancestor}.
|
|
|
|
@type testCase: L{unittest.SynchronousTestCase}
|
|
@param testCase: The test case on which to run the assertions.
|
|
|
|
@type descendant: C{str}
|
|
@param descendant: The subdomain name to test.
|
|
|
|
@type ancestor: C{str}
|
|
@param ancestor: The superdomain name to test.
|
|
"""
|
|
testCase.assertFalse(
|
|
dns._isSubdomainOf(descendant, ancestor),
|
|
f"{descendant!r} is a subdomain of {ancestor!r}",
|
|
)
|
|
|
|
|
|
class IsSubdomainOfTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{twisted.names.dns._isSubdomainOf}.
|
|
"""
|
|
|
|
def test_identical(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{True} for identical
|
|
domain names.
|
|
"""
|
|
assertIsSubdomainOf(self, b"example.com", b"example.com")
|
|
|
|
def test_parent(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{True} when the first
|
|
name is an immediate descendant of the second name.
|
|
"""
|
|
assertIsSubdomainOf(self, b"foo.example.com", b"example.com")
|
|
|
|
def test_distantAncestor(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{True} when the first
|
|
name is a distant descendant of the second name.
|
|
"""
|
|
assertIsSubdomainOf(self, b"foo.bar.baz.example.com", b"com")
|
|
|
|
def test_superdomain(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{False} when the first
|
|
name is an ancestor of the second name.
|
|
"""
|
|
assertIsNotSubdomainOf(self, b"example.com", b"foo.example.com")
|
|
|
|
def test_sibling(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{False} if the first name
|
|
is a sibling of the second name.
|
|
"""
|
|
assertIsNotSubdomainOf(self, b"foo.example.com", b"bar.example.com")
|
|
|
|
def test_unrelatedCommonSuffix(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{False} even when domain
|
|
names happen to share a common suffix.
|
|
"""
|
|
assertIsNotSubdomainOf(self, b"foo.myexample.com", b"example.com")
|
|
|
|
def test_subdomainWithTrailingDot(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{True} if the first name
|
|
is a subdomain of the second name but the first name has a
|
|
trailing ".".
|
|
"""
|
|
assertIsSubdomainOf(self, b"foo.example.com.", b"example.com")
|
|
|
|
def test_superdomainWithTrailingDot(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{True} if the first name
|
|
is a subdomain of the second name but the second name has a
|
|
trailing ".".
|
|
"""
|
|
assertIsSubdomainOf(self, b"foo.example.com", b"example.com.")
|
|
|
|
def test_bothWithTrailingDot(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{True} if the first name
|
|
is a subdomain of the second name and both names have a
|
|
trailing ".".
|
|
"""
|
|
assertIsSubdomainOf(self, b"foo.example.com.", b"example.com.")
|
|
|
|
def test_emptySubdomain(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{False} if the first name
|
|
is empty and the second name is not.
|
|
"""
|
|
assertIsNotSubdomainOf(self, b"", b"example.com")
|
|
|
|
def test_emptySuperdomain(self):
|
|
"""
|
|
L{dns._isSubdomainOf} returns C{True} if the second name
|
|
is empty and the first name is not.
|
|
"""
|
|
assertIsSubdomainOf(self, b"foo.example.com", b"")
|
|
|
|
def test_caseInsensitiveComparison(self):
|
|
"""
|
|
L{dns._isSubdomainOf} does case-insensitive comparison
|
|
of name labels.
|
|
"""
|
|
assertIsSubdomainOf(self, b"foo.example.com", b"EXAMPLE.COM")
|
|
|
|
assertIsSubdomainOf(self, b"FOO.EXAMPLE.COM", b"example.com")
|
|
|
|
|
|
class OPTNonStandardAttributes:
|
|
"""
|
|
Generate byte and instance representations of an L{dns._OPTHeader}
|
|
where all attributes are set to non-default values.
|
|
|
|
For testing whether attributes have really been read from the byte
|
|
string during decoding.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls, excludeName=False, excludeOptions=False):
|
|
"""
|
|
Return L{bytes} representing an encoded OPT record.
|
|
|
|
@param excludeName: A flag that controls whether to exclude
|
|
the name field. This allows a non-standard name to be
|
|
prepended during the test.
|
|
@type excludeName: L{bool}
|
|
|
|
@param excludeOptions: A flag that controls whether to exclude
|
|
the RDLEN field. This allows encoded variable options to be
|
|
appended during the test.
|
|
@type excludeOptions: L{bool}
|
|
|
|
@return: L{bytes} representing the encoded OPT record returned
|
|
by L{object}.
|
|
"""
|
|
rdlen = b"\x00\x00" # RDLEN 0
|
|
if excludeOptions:
|
|
rdlen = b""
|
|
|
|
return (
|
|
b"\x00" # 0 root zone
|
|
b"\x00\x29" # type 41
|
|
b"\x02\x00" # udpPayloadsize 512
|
|
b"\x03" # extendedRCODE 3
|
|
b"\x04" # version 4
|
|
b"\x80\x00" # DNSSEC OK 1 + Z
|
|
) + rdlen
|
|
|
|
@classmethod
|
|
def object(cls):
|
|
"""
|
|
Return a new L{dns._OPTHeader} instance.
|
|
|
|
@return: A L{dns._OPTHeader} instance with attributes that
|
|
match the encoded record returned by L{bytes}.
|
|
"""
|
|
return dns._OPTHeader(
|
|
udpPayloadSize=512, extendedRCODE=3, version=4, dnssecOK=True
|
|
)
|
|
|
|
|
|
class OPTHeaderTests(ComparisonTestsMixin, unittest.TestCase):
|
|
"""
|
|
Tests for L{twisted.names.dns._OPTHeader}.
|
|
"""
|
|
|
|
def test_interface(self):
|
|
"""
|
|
L{dns._OPTHeader} implements L{dns.IEncodable}.
|
|
"""
|
|
verifyClass(dns.IEncodable, dns._OPTHeader)
|
|
|
|
def test_name(self):
|
|
"""
|
|
L{dns._OPTHeader.name} is an instance attribute whose value is
|
|
fixed as the root domain
|
|
"""
|
|
self.assertEqual(dns._OPTHeader().name, dns.Name(b""))
|
|
|
|
def test_nameReadonly(self):
|
|
"""
|
|
L{dns._OPTHeader.name} is readonly.
|
|
"""
|
|
h = dns._OPTHeader()
|
|
self.assertRaises(AttributeError, setattr, h, "name", dns.Name(b"example.com"))
|
|
|
|
def test_type(self):
|
|
"""
|
|
L{dns._OPTHeader.type} is an instance attribute with fixed value
|
|
41.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader().type, 41)
|
|
|
|
def test_typeReadonly(self):
|
|
"""
|
|
L{dns._OPTHeader.type} is readonly.
|
|
"""
|
|
h = dns._OPTHeader()
|
|
self.assertRaises(AttributeError, setattr, h, "type", dns.A)
|
|
|
|
def test_udpPayloadSize(self):
|
|
"""
|
|
L{dns._OPTHeader.udpPayloadSize} defaults to 4096 as
|
|
recommended in rfc6891 section-6.2.5.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader().udpPayloadSize, 4096)
|
|
|
|
def test_udpPayloadSizeOverride(self):
|
|
"""
|
|
L{dns._OPTHeader.udpPayloadSize} can be overridden in the
|
|
constructor.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader(udpPayloadSize=512).udpPayloadSize, 512)
|
|
|
|
def test_extendedRCODE(self):
|
|
"""
|
|
L{dns._OPTHeader.extendedRCODE} defaults to 0.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader().extendedRCODE, 0)
|
|
|
|
def test_extendedRCODEOverride(self):
|
|
"""
|
|
L{dns._OPTHeader.extendedRCODE} can be overridden in the
|
|
constructor.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader(extendedRCODE=1).extendedRCODE, 1)
|
|
|
|
def test_version(self):
|
|
"""
|
|
L{dns._OPTHeader.version} defaults to 0.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader().version, 0)
|
|
|
|
def test_versionOverride(self):
|
|
"""
|
|
L{dns._OPTHeader.version} can be overridden in the
|
|
constructor.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader(version=1).version, 1)
|
|
|
|
def test_dnssecOK(self):
|
|
"""
|
|
L{dns._OPTHeader.dnssecOK} defaults to False.
|
|
"""
|
|
self.assertFalse(dns._OPTHeader().dnssecOK)
|
|
|
|
def test_dnssecOKOverride(self):
|
|
"""
|
|
L{dns._OPTHeader.dnssecOK} can be overridden in the
|
|
constructor.
|
|
"""
|
|
self.assertTrue(dns._OPTHeader(dnssecOK=True).dnssecOK)
|
|
|
|
def test_options(self):
|
|
"""
|
|
L{dns._OPTHeader.options} defaults to empty list.
|
|
"""
|
|
self.assertEqual(dns._OPTHeader().options, [])
|
|
|
|
def test_optionsOverride(self):
|
|
"""
|
|
L{dns._OPTHeader.options} can be overridden in the
|
|
constructor.
|
|
"""
|
|
h = dns._OPTHeader(options=[(1, 1, b"\x00")])
|
|
self.assertEqual(h.options, [(1, 1, b"\x00")])
|
|
|
|
def test_encode(self):
|
|
"""
|
|
L{dns._OPTHeader.encode} packs the header fields and writes
|
|
them to a file like object passed in as an argument.
|
|
"""
|
|
b = BytesIO()
|
|
|
|
OPTNonStandardAttributes.object().encode(b)
|
|
self.assertEqual(b.getvalue(), OPTNonStandardAttributes.bytes())
|
|
|
|
def test_encodeWithOptions(self):
|
|
"""
|
|
L{dns._OPTHeader.options} is a list of L{dns._OPTVariableOption}
|
|
instances which are packed into the rdata area of the header.
|
|
"""
|
|
h = OPTNonStandardAttributes.object()
|
|
h.options = [
|
|
dns._OPTVariableOption(1, b"foobarbaz"),
|
|
dns._OPTVariableOption(2, b"qux"),
|
|
]
|
|
b = BytesIO()
|
|
|
|
h.encode(b)
|
|
self.assertEqual(
|
|
b.getvalue(),
|
|
OPTNonStandardAttributes.bytes(excludeOptions=True)
|
|
+ (
|
|
b"\x00\x14" # RDLEN 20
|
|
b"\x00\x01" # OPTION-CODE
|
|
b"\x00\x09" # OPTION-LENGTH
|
|
b"foobarbaz" # OPTION-DATA
|
|
b"\x00\x02" # OPTION-CODE
|
|
b"\x00\x03" # OPTION-LENGTH
|
|
b"qux" # OPTION-DATA
|
|
),
|
|
)
|
|
|
|
def test_decode(self):
|
|
"""
|
|
L{dns._OPTHeader.decode} unpacks the header fields from a file
|
|
like object and populates the attributes of an existing
|
|
L{dns._OPTHeader} instance.
|
|
"""
|
|
decodedHeader = dns._OPTHeader()
|
|
decodedHeader.decode(BytesIO(OPTNonStandardAttributes.bytes()))
|
|
|
|
self.assertEqual(decodedHeader, OPTNonStandardAttributes.object())
|
|
|
|
def test_decodeAllExpectedBytes(self):
|
|
"""
|
|
L{dns._OPTHeader.decode} reads all the bytes of the record
|
|
that is being decoded.
|
|
"""
|
|
# Check that all the input data has been consumed.
|
|
b = BytesIO(OPTNonStandardAttributes.bytes())
|
|
|
|
decodedHeader = dns._OPTHeader()
|
|
decodedHeader.decode(b)
|
|
|
|
self.assertEqual(b.tell(), len(b.getvalue()))
|
|
|
|
def test_decodeOnlyExpectedBytes(self):
|
|
"""
|
|
L{dns._OPTHeader.decode} reads only the bytes from the current
|
|
file position to the end of the record that is being
|
|
decoded. Trailing bytes are not consumed.
|
|
"""
|
|
b = BytesIO(OPTNonStandardAttributes.bytes() + b"xxxx") # Trailing bytes
|
|
|
|
decodedHeader = dns._OPTHeader()
|
|
decodedHeader.decode(b)
|
|
|
|
self.assertEqual(b.tell(), len(b.getvalue()) - len(b"xxxx"))
|
|
|
|
def test_decodeDiscardsName(self):
|
|
"""
|
|
L{dns._OPTHeader.decode} discards the name which is encoded in
|
|
the supplied bytes. The name attribute of the resulting
|
|
L{dns._OPTHeader} instance will always be L{dns.Name(b'')}.
|
|
"""
|
|
b = BytesIO(
|
|
OPTNonStandardAttributes.bytes(excludeName=True) + b"\x07example\x03com\x00"
|
|
)
|
|
|
|
h = dns._OPTHeader()
|
|
h.decode(b)
|
|
self.assertEqual(h.name, dns.Name(b""))
|
|
|
|
def test_decodeRdlengthTooShort(self):
|
|
"""
|
|
L{dns._OPTHeader.decode} raises an exception if the supplied
|
|
RDLEN is too short.
|
|
"""
|
|
b = BytesIO(
|
|
OPTNonStandardAttributes.bytes(excludeOptions=True)
|
|
+ (
|
|
b"\x00\x05" # RDLEN 5 Too short - should be 6
|
|
b"\x00\x01" # OPTION-CODE
|
|
b"\x00\x02" # OPTION-LENGTH
|
|
b"\x00\x00" # OPTION-DATA
|
|
)
|
|
)
|
|
h = dns._OPTHeader()
|
|
self.assertRaises(EOFError, h.decode, b)
|
|
|
|
def test_decodeRdlengthTooLong(self):
|
|
"""
|
|
L{dns._OPTHeader.decode} raises an exception if the supplied
|
|
RDLEN is too long.
|
|
"""
|
|
b = BytesIO(
|
|
OPTNonStandardAttributes.bytes(excludeOptions=True)
|
|
+ (
|
|
b"\x00\x07" # RDLEN 7 Too long - should be 6
|
|
b"\x00\x01" # OPTION-CODE
|
|
b"\x00\x02" # OPTION-LENGTH
|
|
b"\x00\x00" # OPTION-DATA
|
|
)
|
|
)
|
|
h = dns._OPTHeader()
|
|
self.assertRaises(EOFError, h.decode, b)
|
|
|
|
def test_decodeWithOptions(self):
|
|
"""
|
|
If the OPT bytes contain variable options,
|
|
L{dns._OPTHeader.decode} will populate a list
|
|
L{dns._OPTHeader.options} with L{dns._OPTVariableOption}
|
|
instances.
|
|
"""
|
|
|
|
b = BytesIO(
|
|
OPTNonStandardAttributes.bytes(excludeOptions=True)
|
|
+ (
|
|
b"\x00\x14" # RDLEN 20
|
|
b"\x00\x01" # OPTION-CODE
|
|
b"\x00\x09" # OPTION-LENGTH
|
|
b"foobarbaz" # OPTION-DATA
|
|
b"\x00\x02" # OPTION-CODE
|
|
b"\x00\x03" # OPTION-LENGTH
|
|
b"qux" # OPTION-DATA
|
|
)
|
|
)
|
|
|
|
h = dns._OPTHeader()
|
|
h.decode(b)
|
|
self.assertEqual(
|
|
h.options,
|
|
[
|
|
dns._OPTVariableOption(1, b"foobarbaz"),
|
|
dns._OPTVariableOption(2, b"qux"),
|
|
],
|
|
)
|
|
|
|
def test_fromRRHeader(self):
|
|
"""
|
|
L{_OPTHeader.fromRRHeader} accepts an L{RRHeader} instance and
|
|
returns an L{_OPTHeader} instance whose attribute values have
|
|
been derived from the C{cls}, C{ttl} and C{payload} attributes
|
|
of the original header.
|
|
"""
|
|
genericHeader = dns.RRHeader(
|
|
b"example.com",
|
|
type=dns.OPT,
|
|
cls=0xFFFF,
|
|
ttl=(0xFE << 24 | 0xFD << 16 | True << 15),
|
|
payload=dns.UnknownRecord(b"\xff\xff\x00\x03abc"),
|
|
)
|
|
|
|
decodedOptHeader = dns._OPTHeader.fromRRHeader(genericHeader)
|
|
|
|
expectedOptHeader = dns._OPTHeader(
|
|
udpPayloadSize=0xFFFF,
|
|
extendedRCODE=0xFE,
|
|
version=0xFD,
|
|
dnssecOK=True,
|
|
options=[dns._OPTVariableOption(code=0xFFFF, data=b"abc")],
|
|
)
|
|
|
|
self.assertEqual(decodedOptHeader, expectedOptHeader)
|
|
|
|
def test_repr(self):
|
|
"""
|
|
L{dns._OPTHeader.__repr__} displays the name and type and all
|
|
the fixed and extended header values of the OPT record.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns._OPTHeader()),
|
|
"<_OPTHeader "
|
|
"name= "
|
|
"type=41 "
|
|
"udpPayloadSize=4096 "
|
|
"extendedRCODE=0 "
|
|
"version=0 "
|
|
"dnssecOK=False "
|
|
"options=[]>",
|
|
)
|
|
|
|
def test_equalityUdpPayloadSize(self):
|
|
"""
|
|
Two L{OPTHeader} instances compare equal if they have the same
|
|
udpPayloadSize.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
dns._OPTHeader(udpPayloadSize=512),
|
|
dns._OPTHeader(udpPayloadSize=512),
|
|
dns._OPTHeader(udpPayloadSize=4096),
|
|
)
|
|
|
|
def test_equalityExtendedRCODE(self):
|
|
"""
|
|
Two L{OPTHeader} instances compare equal if they have the same
|
|
extendedRCODE.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
dns._OPTHeader(extendedRCODE=1),
|
|
dns._OPTHeader(extendedRCODE=1),
|
|
dns._OPTHeader(extendedRCODE=2),
|
|
)
|
|
|
|
def test_equalityVersion(self):
|
|
"""
|
|
Two L{OPTHeader} instances compare equal if they have the same
|
|
version.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
dns._OPTHeader(version=1),
|
|
dns._OPTHeader(version=1),
|
|
dns._OPTHeader(version=2),
|
|
)
|
|
|
|
def test_equalityDnssecOK(self):
|
|
"""
|
|
Two L{OPTHeader} instances compare equal if they have the same
|
|
dnssecOK flags.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
dns._OPTHeader(dnssecOK=True),
|
|
dns._OPTHeader(dnssecOK=True),
|
|
dns._OPTHeader(dnssecOK=False),
|
|
)
|
|
|
|
def test_equalityOptions(self):
|
|
"""
|
|
Two L{OPTHeader} instances compare equal if they have the same
|
|
options.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
dns._OPTHeader(options=[dns._OPTVariableOption(1, b"x")]),
|
|
dns._OPTHeader(options=[dns._OPTVariableOption(1, b"x")]),
|
|
dns._OPTHeader(options=[dns._OPTVariableOption(2, b"y")]),
|
|
)
|
|
|
|
|
|
class OPTVariableOptionTests(ComparisonTestsMixin, unittest.TestCase):
|
|
"""
|
|
Tests for L{dns._OPTVariableOption}.
|
|
"""
|
|
|
|
def test_interface(self):
|
|
"""
|
|
L{dns._OPTVariableOption} implements L{dns.IEncodable}.
|
|
"""
|
|
verifyClass(dns.IEncodable, dns._OPTVariableOption)
|
|
|
|
def test_constructorArguments(self):
|
|
"""
|
|
L{dns._OPTVariableOption.__init__} requires code and data
|
|
arguments which are saved as public instance attributes.
|
|
"""
|
|
h = dns._OPTVariableOption(1, b"x")
|
|
self.assertEqual(h.code, 1)
|
|
self.assertEqual(h.data, b"x")
|
|
|
|
def test_repr(self):
|
|
"""
|
|
L{dns._OPTVariableOption.__repr__} displays the code and data
|
|
of the option.
|
|
"""
|
|
self.assertEqual(
|
|
repr(dns._OPTVariableOption(1, b"x")),
|
|
"<_OPTVariableOption " "code=1 " "data=x" ">",
|
|
)
|
|
|
|
def test_equality(self):
|
|
"""
|
|
Two OPTVariableOption instances compare equal if they have the same
|
|
code and data values.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
dns._OPTVariableOption(1, b"x"),
|
|
dns._OPTVariableOption(1, b"x"),
|
|
dns._OPTVariableOption(2, b"x"),
|
|
)
|
|
|
|
self.assertNormalEqualityImplementation(
|
|
dns._OPTVariableOption(1, b"x"),
|
|
dns._OPTVariableOption(1, b"x"),
|
|
dns._OPTVariableOption(1, b"y"),
|
|
)
|
|
|
|
def test_encode(self):
|
|
"""
|
|
L{dns._OPTVariableOption.encode} encodes the code and data
|
|
instance attributes to a byte string which also includes the
|
|
data length.
|
|
"""
|
|
o = dns._OPTVariableOption(1, b"foobar")
|
|
b = BytesIO()
|
|
o.encode(b)
|
|
self.assertEqual(
|
|
b.getvalue(),
|
|
b"\x00\x01" # OPTION-CODE 1
|
|
b"\x00\x06" # OPTION-LENGTH 6
|
|
b"foobar", # OPTION-DATA
|
|
)
|
|
|
|
def test_decode(self):
|
|
"""
|
|
L{dns._OPTVariableOption.decode} is a classmethod that decodes
|
|
a byte string and returns a L{dns._OPTVariableOption} instance.
|
|
"""
|
|
b = BytesIO(
|
|
b"\x00\x01" # OPTION-CODE 1
|
|
b"\x00\x06" # OPTION-LENGTH 6
|
|
b"foobar" # OPTION-DATA
|
|
)
|
|
|
|
o = dns._OPTVariableOption()
|
|
o.decode(b)
|
|
self.assertEqual(o.code, 1)
|
|
self.assertEqual(o.data, b"foobar")
|
|
|
|
|
|
class RaisedArgs(Exception):
|
|
"""
|
|
An exception which can be raised by fakes to test that the fake is called
|
|
with expected arguments.
|
|
"""
|
|
|
|
def __init__(self, args, kwargs):
|
|
"""
|
|
Store the positional and keyword arguments as attributes.
|
|
|
|
@param args: The positional args.
|
|
@param kwargs: The keyword args.
|
|
"""
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
|
|
class MessageEmpty:
|
|
"""
|
|
Generate byte string and constructor arguments for an empty
|
|
L{dns._EDNSMessage}.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x01\x00" # id: 256
|
|
b"\x97" # QR: 1, OPCODE: 2, AA: 0, TC: 0, RD: 1
|
|
b"\x8f" # RA: 1, Z, RCODE: 15
|
|
b"\x00\x00" # number of queries
|
|
b"\x00\x00" # number of answers
|
|
b"\x00\x00" # number of authorities
|
|
b"\x00\x00" # number of additionals
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=256,
|
|
answer=True,
|
|
opCode=dns.OP_STATUS,
|
|
auth=True,
|
|
trunc=True,
|
|
recDes=True,
|
|
recAv=True,
|
|
rCode=15,
|
|
ednsVersion=None,
|
|
)
|
|
|
|
|
|
class MessageTruncated:
|
|
"""
|
|
An empty response message whose TR bit is set to 1.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x01\x00" # ID: 256
|
|
b"\x82" # QR: 1, OPCODE: 0, AA: 0, TC: 1, RD: 0
|
|
b"\x00" # RA: 0, Z, RCODE: 0
|
|
b"\x00\x00" # Number of queries
|
|
b"\x00\x00" # Number of answers
|
|
b"\x00\x00" # Number of authorities
|
|
b"\x00\x00" # Number of additionals
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=256,
|
|
answer=1,
|
|
opCode=0,
|
|
auth=0,
|
|
trunc=1,
|
|
recDes=0,
|
|
recAv=0,
|
|
rCode=0,
|
|
ednsVersion=None,
|
|
)
|
|
|
|
|
|
class MessageNonAuthoritative:
|
|
"""
|
|
A minimal non-authoritative message.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x01\x00" # ID 256
|
|
b"\x00" # QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0
|
|
b"\x00" # RA: 0, Z, RCODE: 0
|
|
b"\x00\x00" # Query count
|
|
b"\x00\x01" # Answer count
|
|
b"\x00\x00" # Authorities count
|
|
b"\x00\x00" # Additionals count
|
|
# Answer
|
|
b"\x00" # RR NAME (root)
|
|
b"\x00\x01" # RR TYPE 1 (A)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\x00\x00\x00\x00" # RR TTL
|
|
b"\x00\x04" # RDLENGTH 4
|
|
b"\x01\x02\x03\x04" # IPv4 1.2.3.4
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=256,
|
|
auth=0,
|
|
ednsVersion=None,
|
|
answers=[
|
|
dns.RRHeader(b"", payload=dns.Record_A("1.2.3.4", ttl=0), auth=False)
|
|
],
|
|
)
|
|
|
|
|
|
class MessageAuthoritative:
|
|
"""
|
|
A minimal authoritative message.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x01\x00" # ID: 256
|
|
b"\x04" # QR: 0, OPCODE: 0, AA: 1, TC: 0, RD: 0
|
|
b"\x00" # RA: 0, Z, RCODE: 0
|
|
b"\x00\x00" # Query count
|
|
b"\x00\x01" # Answer count
|
|
b"\x00\x00" # Authorities count
|
|
b"\x00\x00" # Additionals count
|
|
# Answer
|
|
b"\x00" # RR NAME (root)
|
|
b"\x00\x01" # RR TYPE 1 (A)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\x00\x00\x00\x00" # RR TTL
|
|
b"\x00\x04" # RDLENGTH 4
|
|
b"\x01\x02\x03\x04" # IPv4 1.2.3.4
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=256,
|
|
auth=1,
|
|
ednsVersion=None,
|
|
answers=[
|
|
dns.RRHeader(b"", payload=dns.Record_A("1.2.3.4", ttl=0), auth=True)
|
|
],
|
|
)
|
|
|
|
|
|
class MessageComplete:
|
|
"""
|
|
An example of a fully populated non-edns response message.
|
|
|
|
Contains name compression, answers, authority, and additional records.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x01\x00" # ID: 256
|
|
b"\x95" # QR: 1, OPCODE: 2, AA: 1, TC: 0, RD: 1
|
|
b"\x8f" # RA: 1, Z, RCODE: 15
|
|
b"\x00\x01" # Query count
|
|
b"\x00\x01" # Answer count
|
|
b"\x00\x01" # Authorities count
|
|
b"\x00\x01" # Additionals count
|
|
# Query begins at Byte 12
|
|
b"\x07example\x03com\x00" # QNAME
|
|
b"\x00\x06" # QTYPE 6 (SOA)
|
|
b"\x00\x01" # QCLASS 1 (IN)
|
|
# Answers
|
|
b"\xc0\x0c" # RR NAME (compression ref b12)
|
|
b"\x00\x06" # RR TYPE 6 (SOA)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\xff\xff\xff\xff" # RR TTL
|
|
b"\x00\x27" # RDLENGTH 39
|
|
b"\x03ns1\xc0\x0c" # Mname (ns1.example.com (compression ref b15)
|
|
b"\x0ahostmaster\xc0\x0c" # rname (hostmaster.example.com)
|
|
b"\xff\xff\xff\xfe" # Serial
|
|
b"\x7f\xff\xff\xfd" # Refresh
|
|
b"\x7f\xff\xff\xfc" # Retry
|
|
b"\x7f\xff\xff\xfb" # Expire
|
|
b"\xff\xff\xff\xfa" # Minimum
|
|
# Authority
|
|
b"\xc0\x0c" # RR NAME (example.com compression ref b12)
|
|
b"\x00\x02" # RR TYPE 2 (NS)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\xff\xff\xff\xff" # RR TTL
|
|
b"\x00\x02" # RDLENGTH
|
|
b"\xc0\x29" # RDATA (ns1.example.com (compression ref b41)
|
|
# Additional
|
|
b"\xc0\x29" # RR NAME (ns1.example.com compression ref b41)
|
|
b"\x00\x01" # RR TYPE 1 (A)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\xff\xff\xff\xff" # RR TTL
|
|
b"\x00\x04" # RDLENGTH
|
|
b"\x05\x06\x07\x08" # RDATA 5.6.7.8
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=256,
|
|
answer=1,
|
|
opCode=dns.OP_STATUS,
|
|
auth=1,
|
|
recDes=1,
|
|
recAv=1,
|
|
rCode=15,
|
|
ednsVersion=None,
|
|
queries=[dns.Query(b"example.com", dns.SOA)],
|
|
answers=[
|
|
dns.RRHeader(
|
|
b"example.com",
|
|
type=dns.SOA,
|
|
ttl=0xFFFFFFFF,
|
|
auth=True,
|
|
payload=dns.Record_SOA(
|
|
ttl=0xFFFFFFFF,
|
|
mname=b"ns1.example.com",
|
|
rname=b"hostmaster.example.com",
|
|
serial=0xFFFFFFFE,
|
|
refresh=0x7FFFFFFD,
|
|
retry=0x7FFFFFFC,
|
|
expire=0x7FFFFFFB,
|
|
minimum=0xFFFFFFFA,
|
|
),
|
|
)
|
|
],
|
|
authority=[
|
|
dns.RRHeader(
|
|
b"example.com",
|
|
type=dns.NS,
|
|
ttl=0xFFFFFFFF,
|
|
auth=True,
|
|
payload=dns.Record_NS("ns1.example.com", ttl=0xFFFFFFFF),
|
|
)
|
|
],
|
|
additional=[
|
|
dns.RRHeader(
|
|
b"ns1.example.com",
|
|
type=dns.A,
|
|
ttl=0xFFFFFFFF,
|
|
auth=True,
|
|
payload=dns.Record_A("5.6.7.8", ttl=0xFFFFFFFF),
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
class MessageEDNSQuery:
|
|
"""
|
|
A minimal EDNS query message.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x00\x00" # ID: 0
|
|
b"\x00" # QR: 0, OPCODE: 0, AA: 0, TC: 0, RD: 0
|
|
b"\x00" # RA: 0, Z, RCODE: 0
|
|
b"\x00\x01" # Queries count
|
|
b"\x00\x00" # Anwers count
|
|
b"\x00\x00" # Authority count
|
|
b"\x00\x01" # Additionals count
|
|
# Queries
|
|
b"\x03www\x07example\x03com\x00" # QNAME
|
|
b"\x00\x01" # QTYPE (A)
|
|
b"\x00\x01" # QCLASS (IN)
|
|
# Additional OPT record
|
|
b"\x00" # NAME (.)
|
|
b"\x00\x29" # TYPE (OPT 41)
|
|
b"\x10\x00" # UDP Payload Size (4096)
|
|
b"\x00" # Extended RCODE
|
|
b"\x03" # EDNS version
|
|
b"\x00\x00" # DO: False + Z
|
|
b"\x00\x00" # RDLENGTH
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=0,
|
|
answer=0,
|
|
opCode=dns.OP_QUERY,
|
|
auth=0,
|
|
recDes=0,
|
|
recAv=0,
|
|
rCode=0,
|
|
ednsVersion=3,
|
|
dnssecOK=False,
|
|
queries=[dns.Query(b"www.example.com", dns.A)],
|
|
additional=[],
|
|
)
|
|
|
|
|
|
class MessageEDNSComplete:
|
|
"""
|
|
An example of a fully populated edns response message.
|
|
|
|
Contains name compression, answers, authority, and additional records.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x01\x00" # ID: 256
|
|
b"\x95" # QR: 1, OPCODE: 2, AA: 1, TC: 0, RD: 1
|
|
b"\xbf" # RA: 1, AD: 1, RCODE: 15
|
|
b"\x00\x01" # Query count
|
|
b"\x00\x01" # Answer count
|
|
b"\x00\x01" # Authorities count
|
|
b"\x00\x02" # Additionals count
|
|
# Query begins at Byte 12
|
|
b"\x07example\x03com\x00" # QNAME
|
|
b"\x00\x06" # QTYPE 6 (SOA)
|
|
b"\x00\x01" # QCLASS 1 (IN)
|
|
# Answers
|
|
b"\xc0\x0c" # RR NAME (compression ref b12)
|
|
b"\x00\x06" # RR TYPE 6 (SOA)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\xff\xff\xff\xff" # RR TTL
|
|
b"\x00\x27" # RDLENGTH 39
|
|
b"\x03ns1\xc0\x0c" # mname (ns1.example.com (compression ref b15)
|
|
b"\x0ahostmaster\xc0\x0c" # rname (hostmaster.example.com)
|
|
b"\xff\xff\xff\xfe" # Serial
|
|
b"\x7f\xff\xff\xfd" # Refresh
|
|
b"\x7f\xff\xff\xfc" # Retry
|
|
b"\x7f\xff\xff\xfb" # Expire
|
|
b"\xff\xff\xff\xfa" # Minimum
|
|
# Authority
|
|
b"\xc0\x0c" # RR NAME (example.com compression ref b12)
|
|
b"\x00\x02" # RR TYPE 2 (NS)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\xff\xff\xff\xff" # RR TTL
|
|
b"\x00\x02" # RDLENGTH
|
|
b"\xc0\x29" # RDATA (ns1.example.com (compression ref b41)
|
|
# Additional
|
|
b"\xc0\x29" # RR NAME (ns1.example.com compression ref b41)
|
|
b"\x00\x01" # RR TYPE 1 (A)
|
|
b"\x00\x01" # RR CLASS 1 (IN)
|
|
b"\xff\xff\xff\xff" # RR TTL
|
|
b"\x00\x04" # RDLENGTH
|
|
b"\x05\x06\x07\x08" # RDATA 5.6.7.8
|
|
# Additional OPT record
|
|
b"\x00" # NAME (.)
|
|
b"\x00\x29" # TYPE (OPT 41)
|
|
b"\x04\x00" # UDP Payload Size (1024)
|
|
b"\x00" # Extended RCODE
|
|
b"\x03" # EDNS version
|
|
b"\x80\x00" # DO: True + Z
|
|
b"\x00\x00" # RDLENGTH
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=256,
|
|
answer=1,
|
|
opCode=dns.OP_STATUS,
|
|
auth=1,
|
|
trunc=0,
|
|
recDes=1,
|
|
recAv=1,
|
|
rCode=15,
|
|
ednsVersion=3,
|
|
dnssecOK=True,
|
|
authenticData=True,
|
|
checkingDisabled=True,
|
|
maxSize=1024,
|
|
queries=[dns.Query(b"example.com", dns.SOA)],
|
|
answers=[
|
|
dns.RRHeader(
|
|
b"example.com",
|
|
type=dns.SOA,
|
|
ttl=0xFFFFFFFF,
|
|
auth=True,
|
|
payload=dns.Record_SOA(
|
|
ttl=0xFFFFFFFF,
|
|
mname=b"ns1.example.com",
|
|
rname=b"hostmaster.example.com",
|
|
serial=0xFFFFFFFE,
|
|
refresh=0x7FFFFFFD,
|
|
retry=0x7FFFFFFC,
|
|
expire=0x7FFFFFFB,
|
|
minimum=0xFFFFFFFA,
|
|
),
|
|
)
|
|
],
|
|
authority=[
|
|
dns.RRHeader(
|
|
b"example.com",
|
|
type=dns.NS,
|
|
ttl=0xFFFFFFFF,
|
|
auth=True,
|
|
payload=dns.Record_NS("ns1.example.com", ttl=0xFFFFFFFF),
|
|
)
|
|
],
|
|
additional=[
|
|
dns.RRHeader(
|
|
b"ns1.example.com",
|
|
type=dns.A,
|
|
ttl=0xFFFFFFFF,
|
|
auth=True,
|
|
payload=dns.Record_A("5.6.7.8", ttl=0xFFFFFFFF),
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
class MessageEDNSExtendedRCODE:
|
|
"""
|
|
An example of an EDNS message with an extended RCODE.
|
|
"""
|
|
|
|
@classmethod
|
|
def bytes(cls):
|
|
"""
|
|
Bytes which are expected when encoding an instance constructed using
|
|
C{kwargs} and which are expected to result in an identical instance when
|
|
decoded.
|
|
|
|
@return: The L{bytes} of a wire encoded message.
|
|
"""
|
|
return (
|
|
b"\x00\x00"
|
|
b"\x00"
|
|
b"\x0c" # RA: 0, Z, RCODE: 12
|
|
b"\x00\x00"
|
|
b"\x00\x00"
|
|
b"\x00\x00"
|
|
b"\x00\x01" # 1 additionals
|
|
# Additional OPT record
|
|
b"\x00"
|
|
b"\x00\x29"
|
|
b"\x10\x00"
|
|
b"\xab" # Extended RCODE: 171
|
|
b"\x00"
|
|
b"\x00\x00"
|
|
b"\x00\x00"
|
|
)
|
|
|
|
@classmethod
|
|
def kwargs(cls):
|
|
"""
|
|
Keyword constructor arguments which are expected to result in an
|
|
instance which returns C{bytes} when encoded.
|
|
|
|
@return: A L{dict} of keyword arguments.
|
|
"""
|
|
return dict(
|
|
id=0,
|
|
answer=False,
|
|
opCode=dns.OP_QUERY,
|
|
auth=False,
|
|
trunc=False,
|
|
recDes=False,
|
|
recAv=False,
|
|
rCode=0xABC, # Combined OPT extended RCODE + Message RCODE
|
|
ednsVersion=0,
|
|
dnssecOK=False,
|
|
maxSize=4096,
|
|
queries=[],
|
|
answers=[],
|
|
authority=[],
|
|
additional=[],
|
|
)
|
|
|
|
|
|
class MessageComparable(FancyEqMixin, FancyStrMixin):
|
|
"""
|
|
A wrapper around L{dns.Message} which is comparable so that it can be tested
|
|
using some of the L{dns._EDNSMessage} tests.
|
|
"""
|
|
|
|
showAttributes = compareAttributes = (
|
|
"id",
|
|
"answer",
|
|
"opCode",
|
|
"auth",
|
|
"trunc",
|
|
"recDes",
|
|
"recAv",
|
|
"rCode",
|
|
"queries",
|
|
"answers",
|
|
"authority",
|
|
"additional",
|
|
)
|
|
|
|
def __init__(self, original):
|
|
self.original = original
|
|
|
|
def __getattr__(self, key):
|
|
return getattr(self.original, key)
|
|
|
|
|
|
def verifyConstructorArgument(
|
|
testCase, cls, argName, defaultVal, altVal, attrName=None
|
|
):
|
|
"""
|
|
Verify that an attribute has the expected default value and that a
|
|
corresponding argument passed to a constructor is assigned to that
|
|
attribute.
|
|
|
|
@param testCase: The L{TestCase} whose assert methods will be
|
|
called.
|
|
@type testCase: L{unittest.TestCase}
|
|
|
|
@param cls: The constructor under test.
|
|
@type cls: L{type}
|
|
|
|
@param argName: The name of the constructor argument under test.
|
|
@type argName: L{str}
|
|
|
|
@param defaultVal: The expected default value of C{attrName} /
|
|
C{argName}
|
|
@type defaultVal: L{object}
|
|
|
|
@param altVal: A value which is different from the default. Used to
|
|
test that supplied constructor arguments are actually assigned to the
|
|
correct attribute.
|
|
@type altVal: L{object}
|
|
|
|
@param attrName: The name of the attribute under test if different
|
|
from C{argName}. Defaults to C{argName}
|
|
@type attrName: L{str}
|
|
"""
|
|
if attrName is None:
|
|
attrName = argName
|
|
|
|
actual = {}
|
|
expected = {"defaultVal": defaultVal, "altVal": altVal}
|
|
|
|
o = cls()
|
|
actual["defaultVal"] = getattr(o, attrName)
|
|
|
|
o = cls(**{argName: altVal})
|
|
actual["altVal"] = getattr(o, attrName)
|
|
|
|
testCase.assertEqual(expected, actual)
|
|
|
|
|
|
class ConstructorTestsMixin:
|
|
"""
|
|
Helper methods for verifying default attribute values and corresponding
|
|
constructor arguments.
|
|
"""
|
|
|
|
def _verifyConstructorArgument(self, argName, defaultVal, altVal):
|
|
"""
|
|
Wrap L{verifyConstructorArgument} to provide simpler interface for
|
|
testing Message and _EDNSMessage constructor arguments.
|
|
|
|
@param argName: The name of the constructor argument.
|
|
@param defaultVal: The expected default value.
|
|
@param altVal: An alternative value which is expected to be assigned to
|
|
a correspondingly named attribute.
|
|
"""
|
|
verifyConstructorArgument(
|
|
testCase=self,
|
|
cls=self.messageFactory,
|
|
argName=argName,
|
|
defaultVal=defaultVal,
|
|
altVal=altVal,
|
|
)
|
|
|
|
def _verifyConstructorFlag(self, argName, defaultVal):
|
|
"""
|
|
Wrap L{verifyConstructorArgument} to provide simpler interface for
|
|
testing _EDNSMessage constructor flags.
|
|
|
|
@param argName: The name of the constructor flag argument
|
|
@param defaultVal: The expected default value of the flag
|
|
"""
|
|
assert defaultVal in (True, False)
|
|
verifyConstructorArgument(
|
|
testCase=self,
|
|
cls=self.messageFactory,
|
|
argName=argName,
|
|
defaultVal=defaultVal,
|
|
altVal=not defaultVal,
|
|
)
|
|
|
|
|
|
class CommonConstructorTestsMixin:
|
|
"""
|
|
Tests for constructor arguments and their associated attributes that are
|
|
common to both L{twisted.names.dns._EDNSMessage} and L{dns.Message}.
|
|
|
|
TestCase classes that use this mixin must provide a C{messageFactory} method
|
|
which accepts any argment supported by L{dns.Message.__init__}.
|
|
|
|
TestCases must also mixin ConstructorTestsMixin which provides some custom
|
|
assertions for testing constructor arguments.
|
|
"""
|
|
|
|
def test_id(self):
|
|
"""
|
|
L{dns._EDNSMessage.id} defaults to C{0} and can be overridden in
|
|
the constructor.
|
|
"""
|
|
self._verifyConstructorArgument("id", defaultVal=0, altVal=1)
|
|
|
|
def test_answer(self):
|
|
"""
|
|
L{dns._EDNSMessage.answer} defaults to C{False} and can be overridden in
|
|
the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("answer", defaultVal=False)
|
|
|
|
def test_opCode(self):
|
|
"""
|
|
L{dns._EDNSMessage.opCode} defaults to L{dns.OP_QUERY} and can be
|
|
overridden in the constructor.
|
|
"""
|
|
self._verifyConstructorArgument(
|
|
"opCode", defaultVal=dns.OP_QUERY, altVal=dns.OP_STATUS
|
|
)
|
|
|
|
def test_auth(self):
|
|
"""
|
|
L{dns._EDNSMessage.auth} defaults to C{False} and can be overridden in
|
|
the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("auth", defaultVal=False)
|
|
|
|
def test_trunc(self):
|
|
"""
|
|
L{dns._EDNSMessage.trunc} defaults to C{False} and can be overridden in
|
|
the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("trunc", defaultVal=False)
|
|
|
|
def test_recDes(self):
|
|
"""
|
|
L{dns._EDNSMessage.recDes} defaults to C{False} and can be overridden in
|
|
the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("recDes", defaultVal=False)
|
|
|
|
def test_recAv(self):
|
|
"""
|
|
L{dns._EDNSMessage.recAv} defaults to C{False} and can be overridden in
|
|
the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("recAv", defaultVal=False)
|
|
|
|
def test_rCode(self):
|
|
"""
|
|
L{dns._EDNSMessage.rCode} defaults to C{0} and can be overridden in the
|
|
constructor.
|
|
"""
|
|
self._verifyConstructorArgument("rCode", defaultVal=0, altVal=123)
|
|
|
|
def test_maxSize(self):
|
|
"""
|
|
L{dns._EDNSMessage.maxSize} defaults to C{512} and can be overridden in
|
|
the constructor.
|
|
"""
|
|
self._verifyConstructorArgument("maxSize", defaultVal=512, altVal=1024)
|
|
|
|
def test_queries(self):
|
|
"""
|
|
L{dns._EDNSMessage.queries} defaults to C{[]}.
|
|
"""
|
|
self.assertEqual(self.messageFactory().queries, [])
|
|
|
|
def test_answers(self):
|
|
"""
|
|
L{dns._EDNSMessage.answers} defaults to C{[]}.
|
|
"""
|
|
self.assertEqual(self.messageFactory().answers, [])
|
|
|
|
def test_authority(self):
|
|
"""
|
|
L{dns._EDNSMessage.authority} defaults to C{[]}.
|
|
"""
|
|
self.assertEqual(self.messageFactory().authority, [])
|
|
|
|
def test_additional(self):
|
|
"""
|
|
L{dns._EDNSMessage.additional} defaults to C{[]}.
|
|
"""
|
|
self.assertEqual(self.messageFactory().additional, [])
|
|
|
|
|
|
class EDNSMessageConstructorTests(
|
|
ConstructorTestsMixin, CommonConstructorTestsMixin, unittest.SynchronousTestCase
|
|
):
|
|
"""
|
|
Tests for L{twisted.names.dns._EDNSMessage} constructor arguments that are
|
|
shared with L{dns.Message}.
|
|
"""
|
|
|
|
messageFactory = dns._EDNSMessage
|
|
|
|
|
|
class MessageConstructorTests(
|
|
ConstructorTestsMixin, CommonConstructorTestsMixin, unittest.SynchronousTestCase
|
|
):
|
|
"""
|
|
Tests for L{twisted.names.dns.Message} constructor arguments that are shared
|
|
with L{dns._EDNSMessage}.
|
|
"""
|
|
|
|
messageFactory = dns.Message
|
|
|
|
|
|
class EDNSMessageSpecificsTests(ConstructorTestsMixin, unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{dns._EDNSMessage}.
|
|
|
|
These tests are for L{dns._EDNSMessage} APIs which are not shared with
|
|
L{dns.Message}.
|
|
"""
|
|
|
|
messageFactory = dns._EDNSMessage
|
|
|
|
def test_ednsVersion(self):
|
|
"""
|
|
L{dns._EDNSMessage.ednsVersion} defaults to C{0} and can be overridden
|
|
in the constructor.
|
|
"""
|
|
self._verifyConstructorArgument("ednsVersion", defaultVal=0, altVal=None)
|
|
|
|
def test_dnssecOK(self):
|
|
"""
|
|
L{dns._EDNSMessage.dnssecOK} defaults to C{False} and can be overridden
|
|
in the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("dnssecOK", defaultVal=False)
|
|
|
|
def test_authenticData(self):
|
|
"""
|
|
L{dns._EDNSMessage.authenticData} defaults to C{False} and can be
|
|
overridden in the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("authenticData", defaultVal=False)
|
|
|
|
def test_checkingDisabled(self):
|
|
"""
|
|
L{dns._EDNSMessage.checkingDisabled} defaults to C{False} and can be
|
|
overridden in the constructor.
|
|
"""
|
|
self._verifyConstructorFlag("checkingDisabled", defaultVal=False)
|
|
|
|
def test_queriesOverride(self):
|
|
"""
|
|
L{dns._EDNSMessage.queries} can be overridden in the constructor.
|
|
"""
|
|
msg = self.messageFactory(queries=[dns.Query(b"example.com")])
|
|
|
|
self.assertEqual(msg.queries, [dns.Query(b"example.com")])
|
|
|
|
def test_answersOverride(self):
|
|
"""
|
|
L{dns._EDNSMessage.answers} can be overridden in the constructor.
|
|
"""
|
|
msg = self.messageFactory(
|
|
answers=[dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))]
|
|
)
|
|
|
|
self.assertEqual(
|
|
msg.answers, [dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))]
|
|
)
|
|
|
|
def test_authorityOverride(self):
|
|
"""
|
|
L{dns._EDNSMessage.authority} can be overridden in the constructor.
|
|
"""
|
|
msg = self.messageFactory(
|
|
authority=[
|
|
dns.RRHeader(b"example.com", type=dns.SOA, payload=dns.Record_SOA())
|
|
]
|
|
)
|
|
|
|
self.assertEqual(
|
|
msg.authority,
|
|
[dns.RRHeader(b"example.com", type=dns.SOA, payload=dns.Record_SOA())],
|
|
)
|
|
|
|
def test_additionalOverride(self):
|
|
"""
|
|
L{dns._EDNSMessage.authority} can be overridden in the constructor.
|
|
"""
|
|
msg = self.messageFactory(
|
|
additional=[dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))]
|
|
)
|
|
|
|
self.assertEqual(
|
|
msg.additional,
|
|
[dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))],
|
|
)
|
|
|
|
def test_reprDefaults(self):
|
|
"""
|
|
L{dns._EDNSMessage.__repr__} omits field values and sections which are
|
|
identical to their defaults. The id field value is always shown.
|
|
"""
|
|
self.assertEqual("<_EDNSMessage id=0>", repr(self.messageFactory()))
|
|
|
|
def test_reprFlagsIfSet(self):
|
|
"""
|
|
L{dns._EDNSMessage.__repr__} displays flags if they are L{True}.
|
|
"""
|
|
m = self.messageFactory(
|
|
answer=True,
|
|
auth=True,
|
|
trunc=True,
|
|
recDes=True,
|
|
recAv=True,
|
|
authenticData=True,
|
|
checkingDisabled=True,
|
|
dnssecOK=True,
|
|
)
|
|
self.assertEqual(
|
|
"<_EDNSMessage "
|
|
"id=0 "
|
|
"flags=answer,auth,trunc,recDes,recAv,authenticData,"
|
|
"checkingDisabled,dnssecOK"
|
|
">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_reprNonDefautFields(self):
|
|
"""
|
|
L{dns._EDNSMessage.__repr__} displays field values if they differ from
|
|
their defaults.
|
|
"""
|
|
m = self.messageFactory(id=10, opCode=20, rCode=30, maxSize=40, ednsVersion=50)
|
|
self.assertEqual(
|
|
"<_EDNSMessage "
|
|
"id=10 "
|
|
"opCode=20 "
|
|
"rCode=30 "
|
|
"maxSize=40 "
|
|
"ednsVersion=50"
|
|
">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_reprNonDefaultSections(self):
|
|
"""
|
|
L{dns.Message.__repr__} displays sections which differ from their
|
|
defaults.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.queries = [1, 2, 3]
|
|
m.answers = [4, 5, 6]
|
|
m.authority = [7, 8, 9]
|
|
m.additional = [10, 11, 12]
|
|
self.assertEqual(
|
|
"<_EDNSMessage "
|
|
"id=0 "
|
|
"queries=[1, 2, 3] "
|
|
"answers=[4, 5, 6] "
|
|
"authority=[7, 8, 9] "
|
|
"additional=[10, 11, 12]"
|
|
">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_fromStrCallsMessageFactory(self):
|
|
"""
|
|
L{dns._EDNSMessage.fromString} calls L{dns._EDNSMessage._messageFactory}
|
|
to create a new L{dns.Message} instance which is used to decode the
|
|
supplied bytes.
|
|
"""
|
|
|
|
class FakeMessageFactory:
|
|
"""
|
|
Fake message factory.
|
|
"""
|
|
|
|
def fromStr(self, *args, **kwargs):
|
|
"""
|
|
Fake fromStr method which raises the arguments it was passed.
|
|
|
|
@param args: positional arguments
|
|
@param kwargs: keyword arguments
|
|
"""
|
|
raise RaisedArgs(args, kwargs)
|
|
|
|
m = dns._EDNSMessage()
|
|
m._messageFactory = FakeMessageFactory
|
|
dummyBytes = object()
|
|
e = self.assertRaises(RaisedArgs, m.fromStr, dummyBytes)
|
|
self.assertEqual(((dummyBytes,), {}), (e.args, e.kwargs))
|
|
|
|
def test_fromStrCallsFromMessage(self):
|
|
"""
|
|
L{dns._EDNSMessage.fromString} calls L{dns._EDNSMessage._fromMessage}
|
|
with a L{dns.Message} instance
|
|
"""
|
|
m = dns._EDNSMessage()
|
|
|
|
class FakeMessageFactory:
|
|
"""
|
|
Fake message factory.
|
|
"""
|
|
|
|
def fromStr(self, bytes):
|
|
"""
|
|
A noop fake version of fromStr
|
|
|
|
@param bytes: the bytes to be decoded
|
|
"""
|
|
|
|
fakeMessage = FakeMessageFactory()
|
|
m._messageFactory = lambda: fakeMessage
|
|
|
|
def fakeFromMessage(*args, **kwargs):
|
|
raise RaisedArgs(args, kwargs)
|
|
|
|
m._fromMessage = fakeFromMessage
|
|
e = self.assertRaises(RaisedArgs, m.fromStr, b"")
|
|
self.assertEqual(((fakeMessage,), {}), (e.args, e.kwargs))
|
|
|
|
def test_toStrCallsToMessage(self):
|
|
"""
|
|
L{dns._EDNSMessage.toStr} calls L{dns._EDNSMessage._toMessage}
|
|
"""
|
|
m = dns._EDNSMessage()
|
|
|
|
def fakeToMessage(*args, **kwargs):
|
|
raise RaisedArgs(args, kwargs)
|
|
|
|
m._toMessage = fakeToMessage
|
|
e = self.assertRaises(RaisedArgs, m.toStr)
|
|
self.assertEqual(((), {}), (e.args, e.kwargs))
|
|
|
|
def test_toStrCallsToMessageToStr(self):
|
|
"""
|
|
L{dns._EDNSMessage.toStr} calls C{toStr} on the message returned by
|
|
L{dns._EDNSMessage._toMessage}.
|
|
"""
|
|
m = dns._EDNSMessage()
|
|
dummyBytes = object()
|
|
|
|
class FakeMessage:
|
|
"""
|
|
Fake Message
|
|
"""
|
|
|
|
def toStr(self):
|
|
"""
|
|
Fake toStr which returns dummyBytes.
|
|
|
|
@return: dummyBytes
|
|
"""
|
|
return dummyBytes
|
|
|
|
def fakeToMessage(*args, **kwargs):
|
|
return FakeMessage()
|
|
|
|
m._toMessage = fakeToMessage
|
|
|
|
self.assertEqual(dummyBytes, m.toStr())
|
|
|
|
|
|
class EDNSMessageEqualityTests(ComparisonTestsMixin, unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for equality between L{dns._EDNSMessage} instances.
|
|
|
|
These tests will not work with L{dns.Message} because it does not use
|
|
L{twisted.python.util.FancyEqMixin}.
|
|
"""
|
|
|
|
messageFactory = dns._EDNSMessage
|
|
|
|
def test_id(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
id.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(id=1),
|
|
self.messageFactory(id=1),
|
|
self.messageFactory(id=2),
|
|
)
|
|
|
|
def test_answer(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
answer flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(answer=True),
|
|
self.messageFactory(answer=True),
|
|
self.messageFactory(answer=False),
|
|
)
|
|
|
|
def test_opCode(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
opCode.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(opCode=dns.OP_STATUS),
|
|
self.messageFactory(opCode=dns.OP_STATUS),
|
|
self.messageFactory(opCode=dns.OP_INVERSE),
|
|
)
|
|
|
|
def test_auth(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
auth flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(auth=True),
|
|
self.messageFactory(auth=True),
|
|
self.messageFactory(auth=False),
|
|
)
|
|
|
|
def test_trunc(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
trunc flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(trunc=True),
|
|
self.messageFactory(trunc=True),
|
|
self.messageFactory(trunc=False),
|
|
)
|
|
|
|
def test_recDes(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
recDes flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(recDes=True),
|
|
self.messageFactory(recDes=True),
|
|
self.messageFactory(recDes=False),
|
|
)
|
|
|
|
def test_recAv(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
recAv flag.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(recAv=True),
|
|
self.messageFactory(recAv=True),
|
|
self.messageFactory(recAv=False),
|
|
)
|
|
|
|
def test_rCode(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
rCode.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(rCode=16),
|
|
self.messageFactory(rCode=16),
|
|
self.messageFactory(rCode=15),
|
|
)
|
|
|
|
def test_ednsVersion(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
ednsVersion.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(ednsVersion=1),
|
|
self.messageFactory(ednsVersion=1),
|
|
self.messageFactory(ednsVersion=None),
|
|
)
|
|
|
|
def test_dnssecOK(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
dnssecOK.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(dnssecOK=True),
|
|
self.messageFactory(dnssecOK=True),
|
|
self.messageFactory(dnssecOK=False),
|
|
)
|
|
|
|
def test_authenticData(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
authenticData flags.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(authenticData=True),
|
|
self.messageFactory(authenticData=True),
|
|
self.messageFactory(authenticData=False),
|
|
)
|
|
|
|
def test_checkingDisabled(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
checkingDisabled flags.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(checkingDisabled=True),
|
|
self.messageFactory(checkingDisabled=True),
|
|
self.messageFactory(checkingDisabled=False),
|
|
)
|
|
|
|
def test_maxSize(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
maxSize.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(maxSize=2048),
|
|
self.messageFactory(maxSize=2048),
|
|
self.messageFactory(maxSize=1024),
|
|
)
|
|
|
|
def test_queries(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
queries.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(queries=[dns.Query(b"example.com")]),
|
|
self.messageFactory(queries=[dns.Query(b"example.com")]),
|
|
self.messageFactory(queries=[dns.Query(b"example.org")]),
|
|
)
|
|
|
|
def test_answers(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
answers.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(
|
|
answers=[dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))]
|
|
),
|
|
self.messageFactory(
|
|
answers=[dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))]
|
|
),
|
|
self.messageFactory(
|
|
answers=[dns.RRHeader(b"example.org", payload=dns.Record_A("4.3.2.1"))]
|
|
),
|
|
)
|
|
|
|
def test_authority(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
authority records.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(
|
|
authority=[
|
|
dns.RRHeader(b"example.com", type=dns.SOA, payload=dns.Record_SOA())
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
authority=[
|
|
dns.RRHeader(b"example.com", type=dns.SOA, payload=dns.Record_SOA())
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
authority=[
|
|
dns.RRHeader(b"example.org", type=dns.SOA, payload=dns.Record_SOA())
|
|
]
|
|
),
|
|
)
|
|
|
|
def test_additional(self):
|
|
"""
|
|
Two L{dns._EDNSMessage} instances compare equal if they have the same
|
|
additional records.
|
|
"""
|
|
self.assertNormalEqualityImplementation(
|
|
self.messageFactory(
|
|
additional=[
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
additional=[
|
|
dns.RRHeader(b"example.com", payload=dns.Record_A("1.2.3.4"))
|
|
]
|
|
),
|
|
self.messageFactory(
|
|
additional=[
|
|
dns.RRHeader(b"example.org", payload=dns.Record_A("1.2.3.4"))
|
|
]
|
|
),
|
|
)
|
|
|
|
|
|
class StandardEncodingTestsMixin:
|
|
"""
|
|
Tests for the encoding and decoding of various standard (not EDNS) messages.
|
|
|
|
These tests should work with both L{dns._EDNSMessage} and L{dns.Message}.
|
|
|
|
TestCase classes that use this mixin must provide a C{messageFactory} method
|
|
which accepts any argment supported by L{dns._EDNSMessage.__init__}.
|
|
|
|
EDNS specific arguments may be discarded if not supported by the message
|
|
class under construction.
|
|
"""
|
|
|
|
def test_emptyMessageEncode(self):
|
|
"""
|
|
An empty message can be encoded.
|
|
"""
|
|
self.assertEqual(
|
|
self.messageFactory(**MessageEmpty.kwargs()).toStr(), MessageEmpty.bytes()
|
|
)
|
|
|
|
def test_emptyMessageDecode(self):
|
|
"""
|
|
An empty message byte sequence can be decoded.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageEmpty.bytes())
|
|
|
|
self.assertEqual(m, self.messageFactory(**MessageEmpty.kwargs()))
|
|
|
|
def test_completeQueryEncode(self):
|
|
"""
|
|
A fully populated query message can be encoded.
|
|
"""
|
|
self.assertEqual(
|
|
self.messageFactory(**MessageComplete.kwargs()).toStr(),
|
|
MessageComplete.bytes(),
|
|
)
|
|
|
|
def test_completeQueryDecode(self):
|
|
"""
|
|
A fully populated message byte string can be decoded.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageComplete.bytes()),
|
|
|
|
self.assertEqual(m, self.messageFactory(**MessageComplete.kwargs()))
|
|
|
|
def test_NULL(self):
|
|
"""
|
|
A I{NULL} record with an arbitrary payload can be encoded and decoded as
|
|
part of a message.
|
|
"""
|
|
bytes = b"".join([dns._ord2bytes(i) for i in range(256)])
|
|
rec = dns.Record_NULL(bytes)
|
|
rr = dns.RRHeader(b"testname", dns.NULL, payload=rec)
|
|
msg1 = self.messageFactory()
|
|
msg1.answers.append(rr)
|
|
s = msg1.toStr()
|
|
|
|
msg2 = self.messageFactory()
|
|
msg2.fromStr(s)
|
|
|
|
self.assertIsInstance(msg2.answers[0].payload, dns.Record_NULL)
|
|
self.assertEqual(msg2.answers[0].payload.payload, bytes)
|
|
|
|
def test_nonAuthoritativeMessageEncode(self):
|
|
"""
|
|
If the message C{authoritative} attribute is set to 0, the encoded bytes
|
|
will have AA bit 0.
|
|
"""
|
|
self.assertEqual(
|
|
self.messageFactory(**MessageNonAuthoritative.kwargs()).toStr(),
|
|
MessageNonAuthoritative.bytes(),
|
|
)
|
|
|
|
def test_nonAuthoritativeMessageDecode(self):
|
|
"""
|
|
The L{dns.RRHeader} instances created by a message from a
|
|
non-authoritative message byte string are marked as not authoritative.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageNonAuthoritative.bytes())
|
|
|
|
self.assertEqual(m, self.messageFactory(**MessageNonAuthoritative.kwargs()))
|
|
|
|
def test_authoritativeMessageEncode(self):
|
|
"""
|
|
If the message C{authoritative} attribute is set to 1, the encoded bytes
|
|
will have AA bit 1.
|
|
"""
|
|
self.assertEqual(
|
|
self.messageFactory(**MessageAuthoritative.kwargs()).toStr(),
|
|
MessageAuthoritative.bytes(),
|
|
)
|
|
|
|
def test_authoritativeMessageDecode(self):
|
|
"""
|
|
The message and its L{dns.RRHeader} instances created by C{decode} from
|
|
an authoritative message byte string, are marked as authoritative.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageAuthoritative.bytes())
|
|
|
|
self.assertEqual(m, self.messageFactory(**MessageAuthoritative.kwargs()))
|
|
|
|
def test_truncatedMessageEncode(self):
|
|
"""
|
|
If the message C{trunc} attribute is set to 1 the encoded bytes will
|
|
have TR bit 1.
|
|
"""
|
|
self.assertEqual(
|
|
self.messageFactory(**MessageTruncated.kwargs()).toStr(),
|
|
MessageTruncated.bytes(),
|
|
)
|
|
|
|
def test_truncatedMessageDecode(self):
|
|
"""
|
|
The message instance created by decoding a truncated message is marked
|
|
as truncated.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageTruncated.bytes())
|
|
|
|
self.assertEqual(m, self.messageFactory(**MessageTruncated.kwargs()))
|
|
|
|
|
|
class EDNSMessageStandardEncodingTests(
|
|
StandardEncodingTestsMixin, unittest.SynchronousTestCase
|
|
):
|
|
"""
|
|
Tests for the encoding and decoding of various standard (non-EDNS) messages
|
|
by L{dns._EDNSMessage}.
|
|
"""
|
|
|
|
messageFactory = dns._EDNSMessage
|
|
|
|
|
|
class MessageStandardEncodingTests(
|
|
StandardEncodingTestsMixin, unittest.SynchronousTestCase
|
|
):
|
|
"""
|
|
Tests for the encoding and decoding of various standard (non-EDNS) messages
|
|
by L{dns.Message}.
|
|
"""
|
|
|
|
@staticmethod
|
|
def messageFactory(**kwargs):
|
|
"""
|
|
This function adapts constructor arguments expected by
|
|
_EDNSMessage.__init__ to arguments suitable for use with the
|
|
Message.__init__.
|
|
|
|
Also handles the fact that unlike L{dns._EDNSMessage},
|
|
L{dns.Message.__init__} does not accept queries, answers etc as
|
|
arguments.
|
|
|
|
Also removes any L{dns._EDNSMessage} specific arguments.
|
|
|
|
@param args: The positional arguments which will be passed to
|
|
L{dns.Message.__init__}.
|
|
|
|
@param kwargs: The keyword arguments which will be stripped of EDNS
|
|
specific arguments before being passed to L{dns.Message.__init__}.
|
|
|
|
@return: An L{dns.Message} instance.
|
|
"""
|
|
queries = kwargs.pop("queries", [])
|
|
answers = kwargs.pop("answers", [])
|
|
authority = kwargs.pop("authority", [])
|
|
additional = kwargs.pop("additional", [])
|
|
|
|
kwargs.pop("ednsVersion", None)
|
|
|
|
m = dns.Message(**kwargs)
|
|
m.queries = queries
|
|
m.answers = answers
|
|
m.authority = authority
|
|
m.additional = additional
|
|
return MessageComparable(m)
|
|
|
|
|
|
class EDNSMessageEDNSEncodingTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for the encoding and decoding of various EDNS messages.
|
|
|
|
These test will not work with L{dns.Message}.
|
|
"""
|
|
|
|
messageFactory = dns._EDNSMessage
|
|
|
|
def test_ednsMessageDecodeStripsOptRecords(self):
|
|
"""
|
|
The L(_EDNSMessage} instance created by L{dns._EDNSMessage.decode} from
|
|
an EDNS query never includes OPT records in the additional section.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageEDNSQuery.bytes())
|
|
|
|
self.assertEqual(m.additional, [])
|
|
|
|
def test_ednsMessageDecodeMultipleOptRecords(self):
|
|
"""
|
|
An L(_EDNSMessage} instance created from a byte string containing
|
|
multiple I{OPT} records will discard all the C{OPT} records.
|
|
|
|
C{ednsVersion} will be set to L{None}.
|
|
|
|
@see: U{https://tools.ietf.org/html/rfc6891#section-6.1.1}
|
|
"""
|
|
m = dns.Message()
|
|
m.additional = [dns._OPTHeader(version=2), dns._OPTHeader(version=3)]
|
|
|
|
ednsMessage = dns._EDNSMessage()
|
|
ednsMessage.fromStr(m.toStr())
|
|
|
|
self.assertIsNone(ednsMessage.ednsVersion)
|
|
|
|
def test_fromMessageCopiesSections(self):
|
|
"""
|
|
L{dns._EDNSMessage._fromMessage} returns an L{_EDNSMessage} instance
|
|
whose queries, answers, authority and additional lists are copies (not
|
|
references to) the original message lists.
|
|
"""
|
|
standardMessage = dns.Message()
|
|
standardMessage.fromStr(MessageEDNSQuery.bytes())
|
|
|
|
ednsMessage = dns._EDNSMessage._fromMessage(standardMessage)
|
|
|
|
duplicates = []
|
|
for attrName in ("queries", "answers", "authority", "additional"):
|
|
if getattr(standardMessage, attrName) is getattr(ednsMessage, attrName):
|
|
duplicates.append(attrName)
|
|
|
|
if duplicates:
|
|
self.fail(
|
|
"Message and _EDNSMessage shared references to the following "
|
|
"section lists after decoding: %s" % (duplicates,)
|
|
)
|
|
|
|
def test_toMessageCopiesSections(self):
|
|
"""
|
|
L{dns._EDNSMessage.toStr} makes no in place changes to the message
|
|
instance.
|
|
"""
|
|
ednsMessage = dns._EDNSMessage(ednsVersion=1)
|
|
ednsMessage.toStr()
|
|
self.assertEqual(ednsMessage.additional, [])
|
|
|
|
def test_optHeaderPosition(self):
|
|
"""
|
|
L{dns._EDNSMessage} can decode OPT records, regardless of their position
|
|
in the additional records section.
|
|
|
|
"The OPT RR MAY be placed anywhere within the additional data section."
|
|
|
|
@see: U{https://tools.ietf.org/html/rfc6891#section-6.1.1}
|
|
"""
|
|
# XXX: We need an _OPTHeader.toRRHeader method. See #6779.
|
|
b = BytesIO()
|
|
optRecord = dns._OPTHeader(version=1)
|
|
optRecord.encode(b)
|
|
optRRHeader = dns.RRHeader()
|
|
b.seek(0)
|
|
optRRHeader.decode(b)
|
|
m = dns.Message()
|
|
m.additional = [optRRHeader]
|
|
|
|
actualMessages = []
|
|
actualMessages.append(dns._EDNSMessage._fromMessage(m).ednsVersion)
|
|
|
|
m.additional.append(dns.RRHeader(type=dns.A))
|
|
actualMessages.append(dns._EDNSMessage._fromMessage(m).ednsVersion)
|
|
|
|
m.additional.insert(0, dns.RRHeader(type=dns.A))
|
|
actualMessages.append(dns._EDNSMessage._fromMessage(m).ednsVersion)
|
|
|
|
self.assertEqual([1] * 3, actualMessages)
|
|
|
|
def test_ednsDecode(self):
|
|
"""
|
|
The L(_EDNSMessage} instance created by L{dns._EDNSMessage.fromStr}
|
|
derives its edns specific values (C{ednsVersion}, etc) from the supplied
|
|
OPT record.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageEDNSComplete.bytes())
|
|
|
|
self.assertEqual(m, self.messageFactory(**MessageEDNSComplete.kwargs()))
|
|
|
|
def test_ednsEncode(self):
|
|
"""
|
|
The L(_EDNSMessage} instance created by L{dns._EDNSMessage.toStr}
|
|
encodes its edns specific values (C{ednsVersion}, etc) into an OPT
|
|
record added to the additional section.
|
|
"""
|
|
self.assertEqual(
|
|
self.messageFactory(**MessageEDNSComplete.kwargs()).toStr(),
|
|
MessageEDNSComplete.bytes(),
|
|
)
|
|
|
|
def test_extendedRcodeEncode(self):
|
|
"""
|
|
The L(_EDNSMessage.toStr} encodes the extended I{RCODE} (>=16) by
|
|
assigning the lower 4bits to the message RCODE field and the upper 4bits
|
|
to the OPT pseudo record.
|
|
"""
|
|
self.assertEqual(
|
|
self.messageFactory(**MessageEDNSExtendedRCODE.kwargs()).toStr(),
|
|
MessageEDNSExtendedRCODE.bytes(),
|
|
)
|
|
|
|
def test_extendedRcodeDecode(self):
|
|
"""
|
|
The L(_EDNSMessage} instance created by L{dns._EDNSMessage.fromStr}
|
|
derives RCODE from the supplied OPT record.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.fromStr(MessageEDNSExtendedRCODE.bytes())
|
|
|
|
self.assertEqual(m, self.messageFactory(**MessageEDNSExtendedRCODE.kwargs()))
|
|
|
|
def test_extendedRcodeZero(self):
|
|
"""
|
|
Note that EXTENDED-RCODE value 0 indicates that an unextended RCODE is
|
|
in use (values 0 through 15).
|
|
|
|
https://tools.ietf.org/html/rfc6891#section-6.1.3
|
|
"""
|
|
ednsMessage = self.messageFactory(rCode=15, ednsVersion=0)
|
|
standardMessage = ednsMessage._toMessage()
|
|
|
|
self.assertEqual(
|
|
(15, 0),
|
|
(standardMessage.rCode, standardMessage.additional[0].extendedRCODE),
|
|
)
|
|
|
|
|
|
class ResponseFromMessageTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{dns._responseFromMessage}.
|
|
"""
|
|
|
|
def test_responseFromMessageResponseType(self):
|
|
"""
|
|
L{dns.Message._responseFromMessage} is a constructor function which
|
|
generates a new I{answer} message from an existing L{dns.Message} like
|
|
instance.
|
|
"""
|
|
request = dns.Message()
|
|
response = dns._responseFromMessage(
|
|
responseConstructor=dns.Message, message=request
|
|
)
|
|
self.assertIsNot(request, response)
|
|
|
|
def test_responseType(self):
|
|
"""
|
|
L{dns._responseFromMessage} returns a new instance of C{cls}
|
|
"""
|
|
|
|
class SuppliedClass:
|
|
id = 1
|
|
queries = []
|
|
|
|
expectedClass = dns.Message
|
|
|
|
self.assertIsInstance(
|
|
dns._responseFromMessage(
|
|
responseConstructor=expectedClass, message=SuppliedClass()
|
|
),
|
|
expectedClass,
|
|
)
|
|
|
|
def test_responseId(self):
|
|
"""
|
|
L{dns._responseFromMessage} copies the C{id} attribute of the original
|
|
message.
|
|
"""
|
|
self.assertEqual(
|
|
1234,
|
|
dns._responseFromMessage(
|
|
responseConstructor=dns.Message, message=dns.Message(id=1234)
|
|
).id,
|
|
)
|
|
|
|
def test_responseAnswer(self):
|
|
"""
|
|
L{dns._responseFromMessage} sets the C{answer} flag to L{True}
|
|
"""
|
|
request = dns.Message()
|
|
response = dns._responseFromMessage(
|
|
responseConstructor=dns.Message, message=request
|
|
)
|
|
self.assertEqual((False, True), (request.answer, response.answer))
|
|
|
|
def test_responseQueries(self):
|
|
"""
|
|
L{dns._responseFromMessage} copies the C{queries} attribute of the
|
|
original message.
|
|
"""
|
|
request = dns.Message()
|
|
expectedQueries = [object(), object(), object()]
|
|
request.queries = expectedQueries[:]
|
|
|
|
self.assertEqual(
|
|
expectedQueries,
|
|
dns._responseFromMessage(
|
|
responseConstructor=dns.Message, message=request
|
|
).queries,
|
|
)
|
|
|
|
def test_responseKwargs(self):
|
|
"""
|
|
L{dns._responseFromMessage} accepts other C{kwargs} which are assigned
|
|
to the new message before it is returned.
|
|
"""
|
|
self.assertEqual(
|
|
123,
|
|
dns._responseFromMessage(
|
|
responseConstructor=dns.Message, message=dns.Message(), rCode=123
|
|
).rCode,
|
|
)
|
|
|
|
|
|
class Foo:
|
|
"""
|
|
An example class for use in L{dns._compactRepr} tests.
|
|
It follows the pattern of initialiser settable flags, fields and sections
|
|
found in L{dns.Message} and L{dns._EDNSMessage}.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
field1=1,
|
|
field2=2,
|
|
alwaysShowField="AS",
|
|
flagTrue=True,
|
|
flagFalse=False,
|
|
section1=None,
|
|
):
|
|
"""
|
|
Set some flags, fields and sections as public attributes.
|
|
"""
|
|
self.field1 = field1
|
|
self.field2 = field2
|
|
self.alwaysShowField = alwaysShowField
|
|
self.flagTrue = flagTrue
|
|
self.flagFalse = flagFalse
|
|
|
|
if section1 is None:
|
|
section1 = []
|
|
self.section1 = section1
|
|
|
|
def __repr__(self) -> str:
|
|
"""
|
|
Call L{dns._compactRepr} to generate a string representation.
|
|
"""
|
|
return dns._compactRepr(
|
|
self,
|
|
alwaysShow="alwaysShowField".split(),
|
|
fieldNames="field1 field2 alwaysShowField".split(),
|
|
flagNames="flagTrue flagFalse".split(),
|
|
sectionNames="section1 section2".split(),
|
|
)
|
|
|
|
|
|
class CompactReprTests(unittest.SynchronousTestCase):
|
|
"""
|
|
Tests for L{dns._compactRepr}.
|
|
"""
|
|
|
|
messageFactory = Foo
|
|
|
|
def test_defaults(self):
|
|
"""
|
|
L{dns._compactRepr} omits field values and sections which have the
|
|
default value. Flags which are True are always shown.
|
|
"""
|
|
self.assertEqual(
|
|
"<Foo alwaysShowField='AS' flags=flagTrue>", repr(self.messageFactory())
|
|
)
|
|
|
|
def test_flagsIfSet(self):
|
|
"""
|
|
L{dns._compactRepr} displays flags if they have a non-default value.
|
|
"""
|
|
m = self.messageFactory(flagTrue=True, flagFalse=True)
|
|
self.assertEqual(
|
|
"<Foo " "alwaysShowField='AS' " "flags=flagTrue,flagFalse" ">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_nonDefautFields(self):
|
|
"""
|
|
L{dns._compactRepr} displays field values if they differ from their
|
|
defaults.
|
|
"""
|
|
m = self.messageFactory(field1=10, field2=20)
|
|
self.assertEqual(
|
|
"<Foo "
|
|
"field1=10 "
|
|
"field2=20 "
|
|
"alwaysShowField='AS' "
|
|
"flags=flagTrue"
|
|
">",
|
|
repr(m),
|
|
)
|
|
|
|
def test_nonDefaultSections(self):
|
|
"""
|
|
L{dns._compactRepr} displays sections which differ from their defaults.
|
|
"""
|
|
m = self.messageFactory()
|
|
m.section1 = [1, 1, 1]
|
|
m.section2 = [2, 2, 2]
|
|
self.assertEqual(
|
|
"<Foo "
|
|
"alwaysShowField='AS' "
|
|
"flags=flagTrue "
|
|
"section1=[1, 1, 1] "
|
|
"section2=[2, 2, 2]"
|
|
">",
|
|
repr(m),
|
|
)
|