%PDF- %PDF-
Direktori : /usr/lib/python3/dist-packages/twisted/names/test/ |
Current File : //usr/lib/python3/dist-packages/twisted/names/test/test_dns.py |
# 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 typing import cast 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") 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_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 cast( str, 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), )