12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356 |
- # -*- test-case-name: twisted.names.test.test_dns -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
- """
- DNS protocol implementation.
-
- Future Plans:
- - Get rid of some toplevels, maybe.
- """
-
- # System imports
- import inspect
- import random
- import socket
- import struct
- from io import BytesIO
- from itertools import chain
- from typing import Optional, SupportsInt, Union
-
- from zope.interface import Attribute, Interface, implementer
-
- # Twisted imports
- from twisted.internet import defer, protocol
- from twisted.internet.error import CannotListenError
- from twisted.python import failure, log, randbytes, util as tputil
- from twisted.python.compat import cmp, comparable, nativeString
-
- __all__ = [
- "IEncodable",
- "IRecord",
- "IEncodableRecord",
- "A",
- "A6",
- "AAAA",
- "AFSDB",
- "CNAME",
- "DNAME",
- "HINFO",
- "MAILA",
- "MAILB",
- "MB",
- "MD",
- "MF",
- "MG",
- "MINFO",
- "MR",
- "MX",
- "NAPTR",
- "NS",
- "NULL",
- "OPT",
- "PTR",
- "RP",
- "SOA",
- "SPF",
- "SRV",
- "TXT",
- "SSHFP",
- "TSIG",
- "WKS",
- "ANY",
- "CH",
- "CS",
- "HS",
- "IN",
- "ALL_RECORDS",
- "AXFR",
- "IXFR",
- "EFORMAT",
- "ENAME",
- "ENOTIMP",
- "EREFUSED",
- "ESERVER",
- "EBADVERSION",
- "EBADSIG",
- "EBADKEY",
- "EBADTIME",
- "Record_A",
- "Record_A6",
- "Record_AAAA",
- "Record_AFSDB",
- "Record_CNAME",
- "Record_DNAME",
- "Record_HINFO",
- "Record_MB",
- "Record_MD",
- "Record_MF",
- "Record_MG",
- "Record_MINFO",
- "Record_MR",
- "Record_MX",
- "Record_NAPTR",
- "Record_NS",
- "Record_NULL",
- "Record_PTR",
- "Record_RP",
- "Record_SOA",
- "Record_SPF",
- "Record_SRV",
- "Record_SSHFP",
- "Record_TSIG",
- "Record_TXT",
- "Record_WKS",
- "UnknownRecord",
- "QUERY_CLASSES",
- "QUERY_TYPES",
- "REV_CLASSES",
- "REV_TYPES",
- "EXT_QUERIES",
- "Charstr",
- "Message",
- "Name",
- "Query",
- "RRHeader",
- "SimpleRecord",
- "DNSDatagramProtocol",
- "DNSMixin",
- "DNSProtocol",
- "OK",
- "OP_INVERSE",
- "OP_NOTIFY",
- "OP_QUERY",
- "OP_STATUS",
- "OP_UPDATE",
- "PORT",
- "AuthoritativeDomainError",
- "DNSQueryTimeoutError",
- "DomainError",
- ]
-
-
- AF_INET6 = socket.AF_INET6
-
-
- def _ord2bytes(ordinal):
- """
- Construct a bytes object representing a single byte with the given
- ordinal value.
-
- @type ordinal: L{int}
- @rtype: L{bytes}
- """
- return bytes([ordinal])
-
-
- def _nicebytes(bytes):
- """
- Represent a mostly textful bytes object in a way suitable for
- presentation to an end user.
-
- @param bytes: The bytes to represent.
- @rtype: L{str}
- """
- return repr(bytes)[1:]
-
-
- def _nicebyteslist(list):
- """
- Represent a list of mostly textful bytes objects in a way suitable for
- presentation to an end user.
-
- @param list: The list of bytes to represent.
- @rtype: L{str}
- """
- return "[{}]".format(", ".join([_nicebytes(b) for b in list]))
-
-
- def randomSource():
- """
- Wrapper around L{twisted.python.randbytes.RandomFactory.secureRandom} to
- return 2 random bytes.
-
- @rtype: L{bytes}
- """
- return struct.unpack("H", randbytes.secureRandom(2, fallback=True))[0]
-
-
- PORT = 53
-
- (
- A,
- NS,
- MD,
- MF,
- CNAME,
- SOA,
- MB,
- MG,
- MR,
- NULL,
- WKS,
- PTR,
- HINFO,
- MINFO,
- MX,
- TXT,
- RP,
- AFSDB,
- ) = range(1, 19)
- AAAA = 28
- SRV = 33
- NAPTR = 35
- A6 = 38
- DNAME = 39
- OPT = 41
- SSHFP = 44
- SPF = 99
-
- # These record types do not exist in zones, but are transferred in
- # messages the same way normal RRs are.
- TKEY = 249
- TSIG = 250
-
- QUERY_TYPES = {
- A: "A",
- NS: "NS",
- MD: "MD",
- MF: "MF",
- CNAME: "CNAME",
- SOA: "SOA",
- MB: "MB",
- MG: "MG",
- MR: "MR",
- NULL: "NULL",
- WKS: "WKS",
- PTR: "PTR",
- HINFO: "HINFO",
- MINFO: "MINFO",
- MX: "MX",
- TXT: "TXT",
- RP: "RP",
- AFSDB: "AFSDB",
- # 19 through 27? Eh, I'll get to 'em.
- AAAA: "AAAA",
- SRV: "SRV",
- NAPTR: "NAPTR",
- A6: "A6",
- DNAME: "DNAME",
- OPT: "OPT",
- SSHFP: "SSHFP",
- SPF: "SPF",
- TKEY: "TKEY",
- TSIG: "TSIG",
- }
-
- IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
-
- # "Extended" queries (Hey, half of these are deprecated, good job)
- EXT_QUERIES = {
- IXFR: "IXFR",
- AXFR: "AXFR",
- MAILB: "MAILB",
- MAILA: "MAILA",
- ALL_RECORDS: "ALL_RECORDS",
- }
-
- REV_TYPES = {v: k for (k, v) in chain(QUERY_TYPES.items(), EXT_QUERIES.items())}
-
- IN, CS, CH, HS = range(1, 5)
- ANY = 255
-
- QUERY_CLASSES = {IN: "IN", CS: "CS", CH: "CH", HS: "HS", ANY: "ANY"}
- REV_CLASSES = {v: k for (k, v) in QUERY_CLASSES.items()}
-
-
- # Opcodes
- OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
- OP_NOTIFY = 4 # RFC 1996
- OP_UPDATE = 5 # RFC 2136
-
-
- # Response Codes
- OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
- # https://tools.ietf.org/html/rfc6891#section-9
- EBADVERSION = 16
- # RFC 2845
- EBADSIG, EBADKEY, EBADTIME = range(16, 19)
-
-
- class IRecord(Interface):
- """
- A single entry in a zone of authority.
- """
-
- TYPE = Attribute("An indicator of what kind of record this is.")
-
-
- # Backwards compatibility aliases - these should be deprecated or something I
- # suppose. -exarkun
- from twisted.names.error import (
- AuthoritativeDomainError,
- DNSQueryTimeoutError,
- DomainError,
- )
-
-
- def _nameToLabels(name):
- """
- Split a domain name into its constituent labels.
-
- @type name: L{bytes}
- @param name: A fully qualified domain name (with or without a
- trailing dot).
-
- @return: A L{list} of labels ending with an empty label
- representing the DNS root zone.
- @rtype: L{list} of L{bytes}
- """
- if name in (b"", b"."):
- return [b""]
- labels = name.split(b".")
- if labels[-1] != b"":
- labels.append(b"")
- return labels
-
-
- def domainString(domain):
- """
- Coerce a domain name string to bytes.
-
- L{twisted.names} represents domain names as L{bytes}, but many interfaces
- accept L{bytes} or a text string (L{unicode} on Python 2, L{str} on Python
- 3). This function coerces text strings using IDNA encoding --- see
- L{encodings.idna}.
-
- Note that DNS is I{case insensitive} but I{case preserving}. This function
- doesn't normalize case, so you'll still need to do that whenever comparing
- the strings it returns.
-
- @param domain: A domain name. If passed as a text string it will be
- C{idna} encoded.
- @type domain: L{bytes} or L{str}
-
- @returns: L{bytes} suitable for network transmission.
- @rtype: L{bytes}
-
- @since: Twisted 20.3.0
- """
- if isinstance(domain, str):
- domain = domain.encode("idna")
- if not isinstance(domain, bytes):
- raise TypeError(
- "Expected {} or {} but found {!r} of type {}".format(
- bytes.__name__, str.__name__, domain, type(domain)
- )
- )
- return domain
-
-
- def _isSubdomainOf(descendantName, ancestorName):
- """
- Test whether C{descendantName} is equal to or is a I{subdomain} of
- C{ancestorName}.
-
- The names are compared case-insensitively.
-
- The names are treated as byte strings containing one or more
- DNS labels separated by B{.}.
-
- C{descendantName} is considered equal if its sequence of labels
- exactly matches the labels of C{ancestorName}.
-
- C{descendantName} is considered a I{subdomain} if its sequence of
- labels ends with the labels of C{ancestorName}.
-
- @type descendantName: L{bytes}
- @param descendantName: The DNS subdomain name.
-
- @type ancestorName: L{bytes}
- @param ancestorName: The DNS parent or ancestor domain name.
-
- @return: C{True} if C{descendantName} is equal to or if it is a
- subdomain of C{ancestorName}. Otherwise returns C{False}.
- """
- descendantLabels = _nameToLabels(descendantName.lower())
- ancestorLabels = _nameToLabels(ancestorName.lower())
- return descendantLabels[-len(ancestorLabels) :] == ancestorLabels
-
-
- def str2time(s):
- """
- Parse a string description of an interval into an integer number of seconds.
-
- @param s: An interval definition constructed as an interval duration
- followed by an interval unit. An interval duration is a base ten
- representation of an integer. An interval unit is one of the following
- letters: S (seconds), M (minutes), H (hours), D (days), W (weeks), or Y
- (years). For example: C{"3S"} indicates an interval of three seconds;
- C{"5D"} indicates an interval of five days. Alternatively, C{s} may be
- any non-string and it will be returned unmodified.
- @type s: text string (L{bytes} or L{str}) for parsing; anything else
- for passthrough.
-
- @return: an L{int} giving the interval represented by the string C{s}, or
- whatever C{s} is if it is not a string.
- """
- suffixes = (
- ("S", 1),
- ("M", 60),
- ("H", 60 * 60),
- ("D", 60 * 60 * 24),
- ("W", 60 * 60 * 24 * 7),
- ("Y", 60 * 60 * 24 * 365),
- )
- if isinstance(s, bytes):
- s = s.decode("ascii")
-
- if isinstance(s, str):
- s = s.upper().strip()
- for (suff, mult) in suffixes:
- if s.endswith(suff):
- return int(float(s[:-1]) * mult)
- try:
- s = int(s)
- except ValueError:
- raise ValueError("Invalid time interval specifier: " + s)
- return s
-
-
- def readPrecisely(file, l):
- buff = file.read(l)
- if len(buff) < l:
- raise EOFError
- return buff
-
-
- class IEncodable(Interface):
- """
- Interface for something which can be encoded to and decoded
- to the DNS wire format.
-
- A binary-mode file object (such as L{io.BytesIO}) is used as a buffer when
- encoding or decoding.
- """
-
- def encode(strio, compDict=None):
- """
- Write a representation of this object to the given
- file object.
-
- @type strio: File-like object
- @param strio: The buffer to write to. It must have a C{tell()} method.
-
- @type compDict: L{dict} of L{bytes} to L{int} r L{None}
- @param compDict: A mapping of names to byte offsets that have already
- been written to the buffer, which may be used for compression (see RFC
- 1035 section 4.1.4). When L{None}, encode without compression.
- """
-
- def decode(strio, length=None):
- """
- Reconstruct an object from data read from the given
- file object.
-
- @type strio: File-like object
- @param strio: A seekable buffer from which bytes may be read.
-
- @type length: L{int} or L{None}
- @param length: The number of bytes in this RDATA field. Most
- implementations can ignore this value. Only in the case of
- records similar to TXT where the total length is in no way
- encoded in the data is it necessary.
- """
-
-
- class IEncodableRecord(IEncodable, IRecord):
- """
- Interface for DNS records that can be encoded and decoded.
-
- @since: Twisted 21.2.0
- """
-
-
- @implementer(IEncodable)
- class Charstr:
- def __init__(self, string=b""):
- if not isinstance(string, bytes):
- raise ValueError(f"{string!r} is not a byte string")
- self.string = string
-
- def encode(self, strio, compDict=None):
- """
- Encode this Character string into the appropriate byte format.
-
- @type strio: file
- @param strio: The byte representation of this Charstr will be written
- to this file.
- """
- string = self.string
- ind = len(string)
- strio.write(_ord2bytes(ind))
- strio.write(string)
-
- def decode(self, strio, length=None):
- """
- Decode a byte string into this Charstr.
-
- @type strio: file
- @param strio: Bytes will be read from this file until the full string
- is decoded.
-
- @raise EOFError: Raised when there are not enough bytes available from
- C{strio}.
- """
- self.string = b""
- l = ord(readPrecisely(strio, 1))
- self.string = readPrecisely(strio, l)
-
- def __eq__(self, other: object) -> bool:
- if isinstance(other, Charstr):
- return self.string == other.string
- return NotImplemented
-
- def __hash__(self):
- return hash(self.string)
-
- def __str__(self) -> str:
- """
- Represent this L{Charstr} instance by its string value.
- """
- return nativeString(self.string)
-
-
- @implementer(IEncodable)
- class Name:
- """
- A name in the domain name system, made up of multiple labels. For example,
- I{twistedmatrix.com}.
-
- @ivar name: A byte string giving the name.
- @type name: L{bytes}
- """
-
- def __init__(self, name=b""):
- """
- @param name: A name.
- @type name: L{bytes} or L{str}
- """
- self.name = domainString(name)
-
- def encode(self, strio, compDict=None):
- """
- Encode this Name into the appropriate byte format.
-
- @type strio: file
- @param strio: The byte representation of this Name will be written to
- this file.
-
- @type compDict: dict
- @param compDict: dictionary of Names that have already been encoded
- and whose addresses may be backreferenced by this Name (for the purpose
- of reducing the message size).
- """
- name = self.name
- while name:
- if compDict is not None:
- if name in compDict:
- strio.write(struct.pack("!H", 0xC000 | compDict[name]))
- return
- else:
- compDict[name] = strio.tell() + Message.headerSize
- ind = name.find(b".")
- if ind > 0:
- label, name = name[:ind], name[ind + 1 :]
- else:
- # This is the last label, end the loop after handling it.
- label = name
- name = None
- ind = len(label)
- strio.write(_ord2bytes(ind))
- strio.write(label)
- strio.write(b"\x00")
-
- def decode(self, strio, length=None):
- """
- Decode a byte string into this Name.
-
- @type strio: file
- @param strio: Bytes will be read from this file until the full Name
- is decoded.
-
- @raise EOFError: Raised when there are not enough bytes available
- from C{strio}.
-
- @raise ValueError: Raised when the name cannot be decoded (for example,
- because it contains a loop).
- """
- visited = set()
- self.name = b""
- off = 0
- while 1:
- l = ord(readPrecisely(strio, 1))
- if l == 0:
- if off > 0:
- strio.seek(off)
- return
- if (l >> 6) == 3:
- new_off = (l & 63) << 8 | ord(readPrecisely(strio, 1))
- if new_off in visited:
- raise ValueError("Compression loop in encoded name")
- visited.add(new_off)
- if off == 0:
- off = strio.tell()
- strio.seek(new_off)
- continue
- label = readPrecisely(strio, l)
- if self.name == b"":
- self.name = label
- else:
- self.name = self.name + b"." + label
-
- def __eq__(self, other: object) -> bool:
- if isinstance(other, Name):
- return self.name.lower() == other.name.lower()
- return NotImplemented
-
- def __hash__(self):
- return hash(self.name)
-
- def __str__(self) -> str:
- """
- Represent this L{Name} instance by its string name.
- """
- return nativeString(self.name)
-
-
- @comparable
- @implementer(IEncodable)
- class Query:
- """
- Represent a single DNS query.
-
- @ivar name: The name about which this query is requesting information.
- @type name: L{Name}
-
- @ivar type: The query type.
- @type type: L{int}
-
- @ivar cls: The query class.
- @type cls: L{int}
- """
-
- def __init__(self, name: Union[bytes, str] = b"", type: int = A, cls: int = IN):
- """
- @type name: L{bytes} or L{str}
- @param name: See L{Query.name}
-
- @type type: L{int}
- @param type: The query type.
-
- @type cls: L{int}
- @param cls: The query class.
- """
- self.name = Name(name)
- self.type = type
- self.cls = cls
-
- def encode(self, strio, compDict=None):
- self.name.encode(strio, compDict)
- strio.write(struct.pack("!HH", self.type, self.cls))
-
- def decode(self, strio, length=None):
- self.name.decode(strio)
- buff = readPrecisely(strio, 4)
- self.type, self.cls = struct.unpack("!HH", buff)
-
- def __hash__(self):
- return hash((self.name.name.lower(), self.type, self.cls))
-
- def __cmp__(self, other):
- if isinstance(other, Query):
- return cmp(
- (self.name.name.lower(), self.type, self.cls),
- (other.name.name.lower(), other.type, other.cls),
- )
- return NotImplemented
-
- def __str__(self) -> str:
- t = QUERY_TYPES.get(
- self.type, EXT_QUERIES.get(self.type, "UNKNOWN (%d)" % self.type)
- )
- c = QUERY_CLASSES.get(self.cls, "UNKNOWN (%d)" % self.cls)
- return f"<Query {self.name} {t} {c}>"
-
- def __repr__(self) -> str:
- return f"Query({self.name.name!r}, {self.type!r}, {self.cls!r})"
-
-
- @implementer(IEncodable)
- class _OPTHeader(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- An OPT record header.
-
- @ivar name: The DNS name associated with this record. Since this
- is a pseudo record, the name is always an L{Name} instance
- with value b'', which represents the DNS root zone. This
- attribute is a readonly property.
-
- @ivar type: The DNS record type. This is a fixed value of 41
- C{dns.OPT} for OPT Record. This attribute is a readonly
- property.
-
- @see: L{_OPTHeader.__init__} for documentation of other public
- instance attributes.
-
- @see: U{https://tools.ietf.org/html/rfc6891#section-6.1.2}
-
- @since: 13.2
- """
-
- showAttributes = (
- ("name", lambda n: nativeString(n.name)),
- "type",
- "udpPayloadSize",
- "extendedRCODE",
- "version",
- "dnssecOK",
- "options",
- )
-
- compareAttributes = (
- "name",
- "type",
- "udpPayloadSize",
- "extendedRCODE",
- "version",
- "dnssecOK",
- "options",
- )
-
- def __init__(
- self,
- udpPayloadSize=4096,
- extendedRCODE=0,
- version=0,
- dnssecOK=False,
- options=None,
- ):
- """
- @type udpPayloadSize: L{int}
- @param udpPayloadSize: The number of octets of the largest UDP
- payload that can be reassembled and delivered in the
- requestor's network stack.
-
- @type extendedRCODE: L{int}
- @param extendedRCODE: Forms the upper 8 bits of extended
- 12-bit RCODE (together with the 4 bits defined in
- [RFC1035]. Note that EXTENDED-RCODE value 0 indicates
- that an unextended RCODE is in use (values 0 through 15).
-
- @type version: L{int}
- @param version: Indicates the implementation level of the
- setter. Full conformance with this specification is
- indicated by version C{0}.
-
- @type dnssecOK: L{bool}
- @param dnssecOK: DNSSEC OK bit as defined by [RFC3225].
-
- @type options: L{list}
- @param options: A L{list} of 0 or more L{_OPTVariableOption}
- instances.
- """
- self.udpPayloadSize = udpPayloadSize
- self.extendedRCODE = extendedRCODE
- self.version = version
- self.dnssecOK = dnssecOK
-
- if options is None:
- options = []
- self.options = options
-
- @property
- def name(self):
- """
- A readonly property for accessing the C{name} attribute of
- this record.
-
- @return: The DNS name associated with this record. Since this
- is a pseudo record, the name is always an L{Name} instance
- with value b'', which represents the DNS root zone.
- """
- return Name(b"")
-
- @property
- def type(self):
- """
- A readonly property for accessing the C{type} attribute of
- this record.
-
- @return: The DNS record type. This is a fixed value of 41
- (C{dns.OPT} for OPT Record.
- """
- return OPT
-
- def encode(self, strio, compDict=None):
- """
- Encode this L{_OPTHeader} instance to bytes.
-
- @type strio: file
- @param strio: the byte representation of this L{_OPTHeader}
- will be written to this file.
-
- @type compDict: L{dict} or L{None}
- @param compDict: A dictionary of backreference addresses that
- have already been written to this stream and that may
- be used for DNS name compression.
- """
- b = BytesIO()
- for o in self.options:
- o.encode(b)
- optionBytes = b.getvalue()
-
- RRHeader(
- name=self.name.name,
- type=self.type,
- cls=self.udpPayloadSize,
- ttl=(self.extendedRCODE << 24 | self.version << 16 | self.dnssecOK << 15),
- payload=UnknownRecord(optionBytes),
- ).encode(strio, compDict)
-
- def decode(self, strio, length=None):
- """
- Decode bytes into an L{_OPTHeader} instance.
-
- @type strio: file
- @param strio: Bytes will be read from this file until the full
- L{_OPTHeader} is decoded.
-
- @type length: L{int} or L{None}
- @param length: Not used.
- """
-
- h = RRHeader()
- h.decode(strio, length)
- h.payload = UnknownRecord(readPrecisely(strio, h.rdlength))
-
- newOptHeader = self.fromRRHeader(h)
-
- for attrName in self.compareAttributes:
- if attrName not in ("name", "type"):
- setattr(self, attrName, getattr(newOptHeader, attrName))
-
- @classmethod
- def fromRRHeader(cls, rrHeader):
- """
- A classmethod for constructing a new L{_OPTHeader} from the
- attributes and payload of an existing L{RRHeader} instance.
-
- @type rrHeader: L{RRHeader}
- @param rrHeader: An L{RRHeader} instance containing an
- L{UnknownRecord} payload.
-
- @return: An instance of L{_OPTHeader}.
- @rtype: L{_OPTHeader}
- """
- options = None
- if rrHeader.payload is not None:
- options = []
- optionsBytes = BytesIO(rrHeader.payload.data)
- optionsBytesLength = len(rrHeader.payload.data)
- while optionsBytes.tell() < optionsBytesLength:
- o = _OPTVariableOption()
- o.decode(optionsBytes)
- options.append(o)
-
- # Decode variable options if present
- return cls(
- udpPayloadSize=rrHeader.cls,
- extendedRCODE=rrHeader.ttl >> 24,
- version=rrHeader.ttl >> 16 & 0xFF,
- dnssecOK=(rrHeader.ttl & 0xFFFF) >> 15,
- options=options,
- )
-
-
- @implementer(IEncodable)
- class _OPTVariableOption(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- A class to represent OPT record variable options.
-
- @see: L{_OPTVariableOption.__init__} for documentation of public
- instance attributes.
-
- @see: U{https://tools.ietf.org/html/rfc6891#section-6.1.2}
-
- @since: 13.2
- """
-
- showAttributes = ("code", ("data", nativeString))
- compareAttributes = ("code", "data")
-
- _fmt = "!HH"
-
- def __init__(self, code=0, data=b""):
- """
- @type code: L{int}
- @param code: The option code
-
- @type data: L{bytes}
- @param data: The option data
- """
- self.code = code
- self.data = data
-
- def encode(self, strio, compDict=None):
- """
- Encode this L{_OPTVariableOption} to bytes.
-
- @type strio: file
- @param strio: the byte representation of this
- L{_OPTVariableOption} will be written to this file.
-
- @type compDict: L{dict} or L{None}
- @param compDict: A dictionary of backreference addresses that
- have already been written to this stream and that may
- be used for DNS name compression.
- """
- strio.write(struct.pack(self._fmt, self.code, len(self.data)) + self.data)
-
- def decode(self, strio, length=None):
- """
- Decode bytes into an L{_OPTVariableOption} instance.
-
- @type strio: file
- @param strio: Bytes will be read from this file until the full
- L{_OPTVariableOption} is decoded.
-
- @type length: L{int} or L{None}
- @param length: Not used.
- """
- l = struct.calcsize(self._fmt)
- buff = readPrecisely(strio, l)
- self.code, length = struct.unpack(self._fmt, buff)
- self.data = readPrecisely(strio, length)
-
-
- @implementer(IEncodable)
- class RRHeader(tputil.FancyEqMixin):
- """
- A resource record header.
-
- @cvar fmt: L{str} specifying the byte format of an RR.
-
- @ivar name: The name about which this reply contains information.
- @type name: L{Name}
-
- @ivar type: The query type of the original request.
- @type type: L{int}
-
- @ivar cls: The query class of the original request.
-
- @ivar ttl: The time-to-live for this record.
- @type ttl: L{int}
-
- @ivar payload: The record described by this header.
- @type payload: L{IEncodableRecord} or L{None}
-
- @ivar auth: A L{bool} indicating whether this C{RRHeader} was parsed from
- an authoritative message.
- """
-
- compareAttributes = ("name", "type", "cls", "ttl", "payload", "auth")
-
- fmt = "!HHIH"
-
- rdlength = None
-
- cachedResponse = None
-
- def __init__(
- self,
- name: Union[bytes, str] = b"",
- type: int = A,
- cls: int = IN,
- ttl: SupportsInt = 0,
- payload: Optional[IEncodableRecord] = None,
- auth: bool = False,
- ):
- """
- @type name: L{bytes} or L{str}
- @param name: See L{RRHeader.name}
-
- @type type: L{int}
- @param type: The query type.
-
- @type cls: L{int}
- @param cls: The query class.
-
- @type ttl: L{int}
- @param ttl: Time to live for this record. This will be
- converted to an L{int}.
-
- @type payload: L{IEncodableRecord} or L{None}
- @param payload: An optional Query Type specific data object.
-
- @raises TypeError: if the ttl cannot be converted to an L{int}.
- @raises ValueError: if the ttl is negative.
- @raises ValueError: if the payload type is not equal to the C{type}
- argument.
- """
- payloadType = None if payload is None else payload.TYPE
- if payloadType is not None and payloadType != type:
- raise ValueError(
- "Payload type (%s) does not match given type (%s)"
- % (
- QUERY_TYPES.get(payloadType, payloadType),
- QUERY_TYPES.get(type, type),
- )
- )
-
- integralTTL = int(ttl)
-
- if integralTTL < 0:
- raise ValueError("TTL cannot be negative")
-
- self.name = Name(name)
- self.type = type
- self.cls = cls
- self.ttl = integralTTL
- self.payload = payload
- self.auth = auth
-
- def encode(self, strio, compDict=None):
- self.name.encode(strio, compDict)
- strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
- if self.payload:
- prefix = strio.tell()
- self.payload.encode(strio, compDict)
- aft = strio.tell()
- strio.seek(prefix - 2, 0)
- strio.write(struct.pack("!H", aft - prefix))
- strio.seek(aft, 0)
-
- def decode(self, strio, length=None):
- self.name.decode(strio)
- l = struct.calcsize(self.fmt)
- buff = readPrecisely(strio, l)
- r = struct.unpack(self.fmt, buff)
- self.type, self.cls, self.ttl, self.rdlength = r
-
- def isAuthoritative(self):
- return self.auth
-
- def __str__(self) -> str:
- t = QUERY_TYPES.get(
- self.type, EXT_QUERIES.get(self.type, "UNKNOWN (%d)" % self.type)
- )
- c = QUERY_CLASSES.get(self.cls, "UNKNOWN (%d)" % self.cls)
- return "<RR name=%s type=%s class=%s ttl=%ds auth=%s>" % (
- self.name,
- t,
- c,
- self.ttl,
- self.auth and "True" or "False",
- )
-
- __repr__ = __str__
-
-
- @implementer(IEncodableRecord)
- class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- A Resource Record which consists of a single RFC 1035 domain-name.
-
- @type name: L{Name}
- @ivar name: The name associated with this record.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
- """
-
- showAttributes = (("name", "name", "%s"), "ttl")
- compareAttributes = ("name", "ttl")
-
- TYPE: Optional[int] = None
- name = None
-
- def __init__(self, name=b"", ttl=None):
- """
- @param name: See L{SimpleRecord.name}
- @type name: L{bytes} or L{str}
- """
- self.name = Name(name)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- self.name.encode(strio, compDict)
-
- def decode(self, strio, length=None):
- self.name = Name()
- self.name.decode(strio)
-
- def __hash__(self):
- return hash(self.name)
-
-
- # Kinds of RRs - oh my!
- class Record_NS(SimpleRecord):
- """
- An authoritative nameserver.
- """
-
- TYPE = NS
- fancybasename = "NS"
-
-
- class Record_MD(SimpleRecord):
- """
- A mail destination.
-
- This record type is obsolete.
-
- @see: L{Record_MX}
- """
-
- TYPE = MD
- fancybasename = "MD"
-
-
- class Record_MF(SimpleRecord):
- """
- A mail forwarder.
-
- This record type is obsolete.
-
- @see: L{Record_MX}
- """
-
- TYPE = MF
- fancybasename = "MF"
-
-
- class Record_CNAME(SimpleRecord):
- """
- The canonical name for an alias.
- """
-
- TYPE = CNAME
- fancybasename = "CNAME"
-
-
- class Record_MB(SimpleRecord):
- """
- A mailbox domain name.
-
- This is an experimental record type.
- """
-
- TYPE = MB
- fancybasename = "MB"
-
-
- class Record_MG(SimpleRecord):
- """
- A mail group member.
-
- This is an experimental record type.
- """
-
- TYPE = MG
- fancybasename = "MG"
-
-
- class Record_MR(SimpleRecord):
- """
- A mail rename domain name.
-
- This is an experimental record type.
- """
-
- TYPE = MR
- fancybasename = "MR"
-
-
- class Record_PTR(SimpleRecord):
- """
- A domain name pointer.
- """
-
- TYPE = PTR
- fancybasename = "PTR"
-
-
- class Record_DNAME(SimpleRecord):
- """
- A non-terminal DNS name redirection.
-
- This record type provides the capability to map an entire subtree of the
- DNS name space to another domain. It differs from the CNAME record which
- maps a single node of the name space.
-
- @see: U{http://www.faqs.org/rfcs/rfc2672.html}
- @see: U{http://www.faqs.org/rfcs/rfc3363.html}
- """
-
- TYPE = DNAME
- fancybasename = "DNAME"
-
-
- @implementer(IEncodableRecord)
- class Record_A(tputil.FancyEqMixin):
- """
- An IPv4 host address.
-
- @type address: L{bytes}
- @ivar address: The packed network-order representation of the IPv4 address
- associated with this record.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
- """
-
- compareAttributes = ("address", "ttl")
-
- TYPE = A
- address = None
-
- def __init__(self, address="0.0.0.0", ttl=None):
- """
- @type address: L{bytes} or L{str}
- @param address: The IPv4 address associated with this record, in
- quad-dotted notation.
- """
- if isinstance(address, bytes):
- address = address.decode("ascii")
-
- address = socket.inet_aton(address)
- self.address = address
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(self.address)
-
- def decode(self, strio, length=None):
- self.address = readPrecisely(strio, 4)
-
- def __hash__(self):
- return hash(self.address)
-
- def __str__(self) -> str:
- return f"<A address={self.dottedQuad()} ttl={self.ttl}>"
-
- __repr__ = __str__
-
- def dottedQuad(self):
- return socket.inet_ntoa(self.address)
-
-
- @implementer(IEncodableRecord)
- class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- Marks the start of a zone of authority.
-
- This record describes parameters which are shared by all records within a
- particular zone.
-
- @type mname: L{Name}
- @ivar mname: The domain-name of the name server that was the original or
- primary source of data for this zone.
-
- @type rname: L{Name}
- @ivar rname: A domain-name which specifies the mailbox of the person
- responsible for this zone.
-
- @type serial: L{int}
- @ivar serial: The unsigned 32 bit version number of the original copy of
- the zone. Zone transfers preserve this value. This value wraps and
- should be compared using sequence space arithmetic.
-
- @type refresh: L{int}
- @ivar refresh: A 32 bit time interval before the zone should be refreshed.
-
- @type minimum: L{int}
- @ivar minimum: The unsigned 32 bit minimum TTL field that should be
- exported with any RR from this zone.
-
- @type expire: L{int}
- @ivar expire: A 32 bit time value that specifies the upper limit on the
- time interval that can elapse before the zone is no longer
- authoritative.
-
- @type retry: L{int}
- @ivar retry: A 32 bit time interval that should elapse before a failed
- refresh should be retried.
-
- @type ttl: L{int}
- @ivar ttl: The default TTL to use for records served from this zone.
- """
-
- fancybasename = "SOA"
- compareAttributes = (
- "serial",
- "mname",
- "rname",
- "refresh",
- "expire",
- "retry",
- "minimum",
- "ttl",
- )
- showAttributes = (
- ("mname", "mname", "%s"),
- ("rname", "rname", "%s"),
- "serial",
- "refresh",
- "retry",
- "expire",
- "minimum",
- "ttl",
- )
-
- TYPE = SOA
-
- def __init__(
- self,
- mname=b"",
- rname=b"",
- serial=0,
- refresh=0,
- retry=0,
- expire=0,
- minimum=0,
- ttl=None,
- ):
- """
- @param mname: See L{Record_SOA.mname}
- @type mname: L{bytes} or L{str}
-
- @param rname: See L{Record_SOA.rname}
- @type rname: L{bytes} or L{str}
- """
- self.mname, self.rname = Name(mname), Name(rname)
- self.serial, self.refresh = str2time(serial), str2time(refresh)
- self.minimum, self.expire = str2time(minimum), str2time(expire)
- self.retry = str2time(retry)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- self.mname.encode(strio, compDict)
- self.rname.encode(strio, compDict)
- strio.write(
- struct.pack(
- "!LlllL",
- self.serial,
- self.refresh,
- self.retry,
- self.expire,
- self.minimum,
- )
- )
-
- def decode(self, strio, length=None):
- self.mname, self.rname = Name(), Name()
- self.mname.decode(strio)
- self.rname.decode(strio)
- r = struct.unpack("!LlllL", readPrecisely(strio, 20))
- self.serial, self.refresh, self.retry, self.expire, self.minimum = r
-
- def __hash__(self):
- return hash(
- (self.serial, self.mname, self.rname, self.refresh, self.expire, self.retry)
- )
-
-
- @implementer(IEncodableRecord)
- class Record_NULL(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- A null record.
-
- This is an experimental record type.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
- """
-
- fancybasename = "NULL"
- showAttributes = (("payload", _nicebytes), "ttl")
- compareAttributes = ("payload", "ttl")
-
- TYPE = NULL
-
- def __init__(self, payload=None, ttl=None):
- self.payload = payload
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(self.payload)
-
- def decode(self, strio, length=None):
- self.payload = readPrecisely(strio, length)
-
- def __hash__(self):
- return hash(self.payload)
-
-
- @implementer(IEncodableRecord)
- class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- A well known service description.
-
- This record type is obsolete. See L{Record_SRV}.
-
- @type address: L{bytes}
- @ivar address: The packed network-order representation of the IPv4 address
- associated with this record.
-
- @type protocol: L{int}
- @ivar protocol: The 8 bit IP protocol number for which this service map is
- relevant.
-
- @type map: L{bytes}
- @ivar map: A bitvector indicating the services available at the specified
- address.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
- """
-
- fancybasename = "WKS"
- compareAttributes = ("address", "protocol", "map", "ttl")
- showAttributes = [("_address", "address", "%s"), "protocol", "ttl"]
-
- TYPE = WKS
-
- @property
- def _address(self):
- return socket.inet_ntoa(self.address)
-
- def __init__(self, address="0.0.0.0", protocol=0, map=b"", ttl=None):
- """
- @type address: L{bytes} or L{str}
- @param address: The IPv4 address associated with this record, in
- quad-dotted notation.
- """
- if isinstance(address, bytes):
- address = address.decode("idna")
-
- self.address = socket.inet_aton(address)
- self.protocol, self.map = protocol, map
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(self.address)
- strio.write(struct.pack("!B", self.protocol))
- strio.write(self.map)
-
- def decode(self, strio, length=None):
- self.address = readPrecisely(strio, 4)
- self.protocol = struct.unpack("!B", readPrecisely(strio, 1))[0]
- self.map = readPrecisely(strio, length - 5)
-
- def __hash__(self):
- return hash((self.address, self.protocol, self.map))
-
-
- @implementer(IEncodableRecord)
- class Record_AAAA(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- An IPv6 host address.
-
- @type address: L{bytes}
- @ivar address: The packed network-order representation of the IPv6 address
- associated with this record.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
-
- @see: U{http://www.faqs.org/rfcs/rfc1886.html}
- """
-
- TYPE = AAAA
-
- fancybasename = "AAAA"
- showAttributes = (("_address", "address", "%s"), "ttl")
- compareAttributes = ("address", "ttl")
-
- @property
- def _address(self):
- return socket.inet_ntop(AF_INET6, self.address)
-
- def __init__(self, address="::", ttl=None):
- """
- @type address: L{bytes} or L{str}
- @param address: The IPv6 address for this host, in RFC 2373 format.
- """
- if isinstance(address, bytes):
- address = address.decode("idna")
-
- self.address = socket.inet_pton(AF_INET6, address)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(self.address)
-
- def decode(self, strio, length=None):
- self.address = readPrecisely(strio, 16)
-
- def __hash__(self):
- return hash(self.address)
-
-
- @implementer(IEncodableRecord)
- class Record_A6(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- An IPv6 address.
-
- This is an experimental record type.
-
- @type prefixLen: L{int}
- @ivar prefixLen: The length of the suffix.
-
- @type suffix: L{bytes}
- @ivar suffix: An IPv6 address suffix in network order.
-
- @type prefix: L{Name}
- @ivar prefix: If specified, a name which will be used as a prefix for other
- A6 records.
-
- @type bytes: L{int}
- @ivar bytes: The length of the prefix.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
-
- @see: U{http://www.faqs.org/rfcs/rfc2874.html}
- @see: U{http://www.faqs.org/rfcs/rfc3363.html}
- @see: U{http://www.faqs.org/rfcs/rfc3364.html}
- """
-
- TYPE = A6
-
- fancybasename = "A6"
- showAttributes = (("_suffix", "suffix", "%s"), ("prefix", "prefix", "%s"), "ttl")
- compareAttributes = ("prefixLen", "prefix", "suffix", "ttl")
-
- @property
- def _suffix(self):
- return socket.inet_ntop(AF_INET6, self.suffix)
-
- def __init__(self, prefixLen=0, suffix="::", prefix=b"", ttl=None):
- """
- @param suffix: An IPv6 address suffix in in RFC 2373 format.
- @type suffix: L{bytes} or L{str}
-
- @param prefix: An IPv6 address prefix for other A6 records.
- @type prefix: L{bytes} or L{str}
- """
- if isinstance(suffix, bytes):
- suffix = suffix.decode("idna")
-
- self.prefixLen = prefixLen
- self.suffix = socket.inet_pton(AF_INET6, suffix)
- self.prefix = Name(prefix)
- self.bytes = int((128 - self.prefixLen) / 8.0)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(struct.pack("!B", self.prefixLen))
- if self.bytes:
- strio.write(self.suffix[-self.bytes :])
- if self.prefixLen:
- # This may not be compressed
- self.prefix.encode(strio, None)
-
- def decode(self, strio, length=None):
- self.prefixLen = struct.unpack("!B", readPrecisely(strio, 1))[0]
- self.bytes = int((128 - self.prefixLen) / 8.0)
- if self.bytes:
- self.suffix = b"\x00" * (16 - self.bytes) + readPrecisely(strio, self.bytes)
- if self.prefixLen:
- self.prefix.decode(strio)
-
- def __eq__(self, other: object) -> bool:
- if isinstance(other, Record_A6):
- return (
- self.prefixLen == other.prefixLen
- and self.suffix[-self.bytes :] == other.suffix[-self.bytes :]
- and self.prefix == other.prefix
- and self.ttl == other.ttl
- )
- return NotImplemented
-
- def __hash__(self):
- return hash((self.prefixLen, self.suffix[-self.bytes :], self.prefix))
-
- def __str__(self) -> str:
- return "<A6 %s %s (%d) ttl=%s>" % (
- self.prefix,
- socket.inet_ntop(AF_INET6, self.suffix),
- self.prefixLen,
- self.ttl,
- )
-
-
- @implementer(IEncodableRecord)
- class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- The location of the server(s) for a specific protocol and domain.
-
- This is an experimental record type.
-
- @type priority: L{int}
- @ivar priority: The priority of this target host. A client MUST attempt to
- contact the target host with the lowest-numbered priority it can reach;
- target hosts with the same priority SHOULD be tried in an order defined
- by the weight field.
-
- @type weight: L{int}
- @ivar weight: Specifies a relative weight for entries with the same
- priority. Larger weights SHOULD be given a proportionately higher
- probability of being selected.
-
- @type port: L{int}
- @ivar port: The port on this target host of this service.
-
- @type target: L{Name}
- @ivar target: The domain name of the target host. There MUST be one or
- more address records for this name, the name MUST NOT be an alias (in
- the sense of RFC 1034 or RFC 2181). Implementors are urged, but not
- required, to return the address record(s) in the Additional Data
- section. Unless and until permitted by future standards action, name
- compression is not to be used for this field.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
-
- @see: U{http://www.faqs.org/rfcs/rfc2782.html}
- """
-
- TYPE = SRV
-
- fancybasename = "SRV"
- compareAttributes = ("priority", "weight", "target", "port", "ttl")
- showAttributes = ("priority", "weight", ("target", "target", "%s"), "port", "ttl")
-
- def __init__(self, priority=0, weight=0, port=0, target=b"", ttl=None):
- """
- @param target: See L{Record_SRV.target}
- @type target: L{bytes} or L{str}
- """
- self.priority = int(priority)
- self.weight = int(weight)
- self.port = int(port)
- self.target = Name(target)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(struct.pack("!HHH", self.priority, self.weight, self.port))
- # This can't be compressed
- self.target.encode(strio, None)
-
- def decode(self, strio, length=None):
- r = struct.unpack("!HHH", readPrecisely(strio, struct.calcsize("!HHH")))
- self.priority, self.weight, self.port = r
- self.target = Name()
- self.target.decode(strio)
-
- def __hash__(self):
- return hash((self.priority, self.weight, self.port, self.target))
-
-
- @implementer(IEncodableRecord)
- class Record_NAPTR(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- The location of the server(s) for a specific protocol and domain.
-
- @type order: L{int}
- @ivar order: An integer specifying the order in which the NAPTR records
- MUST be processed to ensure the correct ordering of rules. Low numbers
- are processed before high numbers.
-
- @type preference: L{int}
- @ivar preference: An integer that specifies the order in which NAPTR
- records with equal "order" values SHOULD be processed, low numbers
- being processed before high numbers.
-
- @type flag: L{Charstr}
- @ivar flag: A <character-string> containing flags to control aspects of the
- rewriting and interpretation of the fields in the record. Flags
- are single characters from the set [A-Z0-9]. The case of the alphabetic
- characters is not significant.
-
- At this time only four flags, "S", "A", "U", and "P", are defined.
-
- @type service: L{Charstr}
- @ivar service: Specifies the service(s) available down this rewrite path.
- It may also specify the particular protocol that is used to talk with a
- service. A protocol MUST be specified if the flags field states that
- the NAPTR is terminal.
-
- @type regexp: L{Charstr}
- @ivar regexp: A STRING containing a substitution expression that is applied
- to the original string held by the client in order to construct the
- next domain name to lookup.
-
- @type replacement: L{Name}
- @ivar replacement: The next NAME to query for NAPTR, SRV, or address
- records depending on the value of the flags field. This MUST be a
- fully qualified domain-name.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
-
- @see: U{http://www.faqs.org/rfcs/rfc2915.html}
- """
-
- TYPE = NAPTR
-
- compareAttributes = (
- "order",
- "preference",
- "flags",
- "service",
- "regexp",
- "replacement",
- )
- fancybasename = "NAPTR"
-
- showAttributes = (
- "order",
- "preference",
- ("flags", "flags", "%s"),
- ("service", "service", "%s"),
- ("regexp", "regexp", "%s"),
- ("replacement", "replacement", "%s"),
- "ttl",
- )
-
- def __init__(
- self,
- order=0,
- preference=0,
- flags=b"",
- service=b"",
- regexp=b"",
- replacement=b"",
- ttl=None,
- ):
- """
- @param replacement: See L{Record_NAPTR.replacement}
- @type replacement: L{bytes} or L{str}
- """
- self.order = int(order)
- self.preference = int(preference)
- self.flags = Charstr(flags)
- self.service = Charstr(service)
- self.regexp = Charstr(regexp)
- self.replacement = Name(replacement)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(struct.pack("!HH", self.order, self.preference))
- # This can't be compressed
- self.flags.encode(strio, None)
- self.service.encode(strio, None)
- self.regexp.encode(strio, None)
- self.replacement.encode(strio, None)
-
- def decode(self, strio, length=None):
- r = struct.unpack("!HH", readPrecisely(strio, struct.calcsize("!HH")))
- self.order, self.preference = r
- self.flags = Charstr()
- self.service = Charstr()
- self.regexp = Charstr()
- self.replacement = Name()
- self.flags.decode(strio)
- self.service.decode(strio)
- self.regexp.decode(strio)
- self.replacement.decode(strio)
-
- def __hash__(self):
- return hash(
- (
- self.order,
- self.preference,
- self.flags,
- self.service,
- self.regexp,
- self.replacement,
- )
- )
-
-
- @implementer(IEncodableRecord)
- class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- Map from a domain name to the name of an AFS cell database server.
-
- @type subtype: L{int}
- @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
- Volume Location Server for the named AFS cell. In the case of subtype
- 2, the host has an authenticated name server holding the cell-root
- directory node for the named DCE/NCA cell.
-
- @type hostname: L{Name}
- @ivar hostname: The domain name of a host that has a server for the cell
- named by this record.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
-
- @see: U{http://www.faqs.org/rfcs/rfc1183.html}
- """
-
- TYPE = AFSDB
-
- fancybasename = "AFSDB"
- compareAttributes = ("subtype", "hostname", "ttl")
- showAttributes = ("subtype", ("hostname", "hostname", "%s"), "ttl")
-
- def __init__(self, subtype=0, hostname=b"", ttl=None):
- """
- @param hostname: See L{Record_AFSDB.hostname}
- @type hostname: L{bytes} or L{str}
- """
- self.subtype = int(subtype)
- self.hostname = Name(hostname)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(struct.pack("!H", self.subtype))
- self.hostname.encode(strio, compDict)
-
- def decode(self, strio, length=None):
- r = struct.unpack("!H", readPrecisely(strio, struct.calcsize("!H")))
- (self.subtype,) = r
- self.hostname.decode(strio)
-
- def __hash__(self):
- return hash((self.subtype, self.hostname))
-
-
- @implementer(IEncodableRecord)
- class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- The responsible person for a domain.
-
- @type mbox: L{Name}
- @ivar mbox: A domain name that specifies the mailbox for the responsible
- person.
-
- @type txt: L{Name}
- @ivar txt: A domain name for which TXT RR's exist (indirection through
- which allows information sharing about the contents of this RP record).
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
-
- @see: U{http://www.faqs.org/rfcs/rfc1183.html}
- """
-
- TYPE = RP
-
- fancybasename = "RP"
- compareAttributes = ("mbox", "txt", "ttl")
- showAttributes = (("mbox", "mbox", "%s"), ("txt", "txt", "%s"), "ttl")
-
- def __init__(self, mbox=b"", txt=b"", ttl=None):
- """
- @param mbox: See L{Record_RP.mbox}.
- @type mbox: L{bytes} or L{str}
-
- @param txt: See L{Record_RP.txt}
- @type txt: L{bytes} or L{str}
- """
- self.mbox = Name(mbox)
- self.txt = Name(txt)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- self.mbox.encode(strio, compDict)
- self.txt.encode(strio, compDict)
-
- def decode(self, strio, length=None):
- self.mbox = Name()
- self.txt = Name()
- self.mbox.decode(strio)
- self.txt.decode(strio)
-
- def __hash__(self):
- return hash((self.mbox, self.txt))
-
-
- @implementer(IEncodableRecord)
- class Record_HINFO(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- Host information.
-
- @type cpu: L{bytes}
- @ivar cpu: Specifies the CPU type.
-
- @type os: L{bytes}
- @ivar os: Specifies the OS.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
- """
-
- TYPE = HINFO
-
- fancybasename = "HINFO"
- showAttributes = (("cpu", _nicebytes), ("os", _nicebytes), "ttl")
- compareAttributes = ("cpu", "os", "ttl")
-
- def __init__(self, cpu=b"", os=b"", ttl=None):
- self.cpu, self.os = cpu, os
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(struct.pack("!B", len(self.cpu)) + self.cpu)
- strio.write(struct.pack("!B", len(self.os)) + self.os)
-
- def decode(self, strio, length=None):
- cpu = struct.unpack("!B", readPrecisely(strio, 1))[0]
- self.cpu = readPrecisely(strio, cpu)
- os = struct.unpack("!B", readPrecisely(strio, 1))[0]
- self.os = readPrecisely(strio, os)
-
- def __eq__(self, other: object) -> bool:
- if isinstance(other, Record_HINFO):
- return (
- self.os.lower() == other.os.lower()
- and self.cpu.lower() == other.cpu.lower()
- and self.ttl == other.ttl
- )
- return NotImplemented
-
- def __hash__(self):
- return hash((self.os.lower(), self.cpu.lower()))
-
-
- @implementer(IEncodableRecord)
- class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- Mailbox or mail list information.
-
- This is an experimental record type.
-
- @type rmailbx: L{Name}
- @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
- for the mailing list or mailbox. If this domain name names the root,
- the owner of the MINFO RR is responsible for itself.
-
- @type emailbx: L{Name}
- @ivar emailbx: A domain-name which specifies a mailbox which is to receive
- error messages related to the mailing list or mailbox specified by the
- owner of the MINFO record. If this domain name names the root, errors
- should be returned to the sender of the message.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
- """
-
- TYPE = MINFO
-
- rmailbx = None
- emailbx = None
-
- fancybasename = "MINFO"
- compareAttributes = ("rmailbx", "emailbx", "ttl")
- showAttributes = (
- ("rmailbx", "responsibility", "%s"),
- ("emailbx", "errors", "%s"),
- "ttl",
- )
-
- def __init__(self, rmailbx=b"", emailbx=b"", ttl=None):
- """
- @param rmailbx: See L{Record_MINFO.rmailbx}.
- @type rmailbx: L{bytes} or L{str}
-
- @param emailbx: See L{Record_MINFO.rmailbx}.
- @type emailbx: L{bytes} or L{str}
- """
- self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- self.rmailbx.encode(strio, compDict)
- self.emailbx.encode(strio, compDict)
-
- def decode(self, strio, length=None):
- self.rmailbx, self.emailbx = Name(), Name()
- self.rmailbx.decode(strio)
- self.emailbx.decode(strio)
-
- def __hash__(self):
- return hash((self.rmailbx, self.emailbx))
-
-
- @implementer(IEncodableRecord)
- class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
- """
- Mail exchange.
-
- @type preference: L{int}
- @ivar preference: Specifies the preference given to this RR among others at
- the same owner. Lower values are preferred.
-
- @type name: L{Name}
- @ivar name: A domain-name which specifies a host willing to act as a mail
- exchange.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be
- cached.
- """
-
- TYPE = MX
-
- fancybasename = "MX"
- compareAttributes = ("preference", "name", "ttl")
- showAttributes = ("preference", ("name", "name", "%s"), "ttl")
-
- def __init__(self, preference=0, name=b"", ttl=None, **kwargs):
- """
- @param name: See L{Record_MX.name}.
- @type name: L{bytes} or L{str}
- """
- self.preference = int(preference)
- self.name = Name(kwargs.get("exchange", name))
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- strio.write(struct.pack("!H", self.preference))
- self.name.encode(strio, compDict)
-
- def decode(self, strio, length=None):
- self.preference = struct.unpack("!H", readPrecisely(strio, 2))[0]
- self.name = Name()
- self.name.decode(strio)
-
- def __hash__(self):
- return hash((self.preference, self.name))
-
-
- @implementer(IEncodableRecord)
- class Record_SSHFP(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- A record containing the fingerprint of an SSH key.
-
- @type algorithm: L{int}
- @ivar algorithm: The SSH key's algorithm, such as L{ALGORITHM_RSA}.
- Note that the numbering used for SSH key algorithms is specific
- to the SSHFP record, and is not the same as the numbering
- used for KEY or SIG records.
-
- @type fingerprintType: L{int}
- @ivar fingerprintType: The fingerprint type,
- such as L{FINGERPRINT_TYPE_SHA256}.
-
- @type fingerprint: L{bytes}
- @ivar fingerprint: The key's fingerprint, e.g. a 32-byte SHA-256 digest.
-
- @cvar ALGORITHM_RSA: The algorithm value for C{ssh-rsa} keys.
- @cvar ALGORITHM_DSS: The algorithm value for C{ssh-dss} keys.
- @cvar ALGORITHM_ECDSA: The algorithm value for C{ecdsa-sha2-*} keys.
- @cvar ALGORITHM_Ed25519: The algorithm value for C{ed25519} keys.
-
- @cvar FINGERPRINT_TYPE_SHA1: The type for SHA-1 fingerprints.
- @cvar FINGERPRINT_TYPE_SHA256: The type for SHA-256 fingerprints.
-
- @see: U{RFC 4255 <https://tools.ietf.org/html/rfc4255>}
- and
- U{RFC 6594 <https://tools.ietf.org/html/rfc6594>}
- """
-
- fancybasename = "SSHFP"
- compareAttributes = ("algorithm", "fingerprintType", "fingerprint", "ttl")
- showAttributes = ("algorithm", "fingerprintType", "fingerprint")
-
- TYPE = SSHFP
-
- ALGORITHM_RSA = 1
- ALGORITHM_DSS = 2
- ALGORITHM_ECDSA = 3
- ALGORITHM_Ed25519 = 4
-
- FINGERPRINT_TYPE_SHA1 = 1
- FINGERPRINT_TYPE_SHA256 = 2
-
- def __init__(self, algorithm=0, fingerprintType=0, fingerprint=b"", ttl=0):
- self.algorithm = algorithm
- self.fingerprintType = fingerprintType
- self.fingerprint = fingerprint
- self.ttl = ttl
-
- def encode(self, strio, compDict=None):
- strio.write(struct.pack("!BB", self.algorithm, self.fingerprintType))
- strio.write(self.fingerprint)
-
- def decode(self, strio, length=None):
- r = struct.unpack("!BB", readPrecisely(strio, 2))
- (self.algorithm, self.fingerprintType) = r
- self.fingerprint = readPrecisely(strio, length - 2)
-
- def __hash__(self):
- return hash((self.algorithm, self.fingerprintType, self.fingerprint))
-
-
- @implementer(IEncodableRecord)
- class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- Freeform text.
-
- @type data: L{list} of L{bytes}
- @ivar data: Freeform text which makes up this record.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be cached.
- """
-
- TYPE = TXT
-
- fancybasename = "TXT"
- showAttributes = (("data", _nicebyteslist), "ttl")
- compareAttributes = ("data", "ttl")
-
- def __init__(self, *data, **kw):
- self.data = list(data)
- # arg man python sucks so bad
- self.ttl = str2time(kw.get("ttl", None))
-
- def encode(self, strio, compDict=None):
- for d in self.data:
- strio.write(struct.pack("!B", len(d)) + d)
-
- def decode(self, strio, length=None):
- soFar = 0
- self.data = []
- while soFar < length:
- L = struct.unpack("!B", readPrecisely(strio, 1))[0]
- self.data.append(readPrecisely(strio, L))
- soFar += L + 1
- if soFar != length:
- log.msg(
- "Decoded %d bytes in %s record, but rdlength is %d"
- % (soFar, self.fancybasename, length)
- )
-
- def __hash__(self):
- return hash(tuple(self.data))
-
-
- @implementer(IEncodableRecord)
- class UnknownRecord(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- Encapsulate the wire data for unknown record types so that they can
- pass through the system unchanged.
-
- @type data: L{bytes}
- @ivar data: Wire data which makes up this record.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds which this record should be cached.
-
- @since: 11.1
- """
-
- TYPE = None
-
- fancybasename = "UNKNOWN"
- compareAttributes = ("data", "ttl")
- showAttributes = (("data", _nicebytes), "ttl")
-
- def __init__(self, data=b"", ttl=None):
- self.data = data
- self.ttl = str2time(ttl)
-
- def encode(self, strio, compDict=None):
- """
- Write the raw bytes corresponding to this record's payload to the
- stream.
- """
- strio.write(self.data)
-
- def decode(self, strio, length=None):
- """
- Load the bytes which are part of this record from the stream and store
- them unparsed and unmodified.
- """
- if length is None:
- raise Exception("must know length for unknown record types")
- self.data = readPrecisely(strio, length)
-
- def __hash__(self):
- return hash((self.data, self.ttl))
-
-
- class Record_SPF(Record_TXT):
- """
- Structurally, freeform text. Semantically, a policy definition, formatted
- as defined in U{rfc 4408<http://www.faqs.org/rfcs/rfc4408.html>}.
-
- @type data: L{list} of L{bytes}
- @ivar data: Freeform text which makes up this record.
-
- @type ttl: L{int}
- @ivar ttl: The maximum number of seconds
- which this record should be cached.
- """
-
- TYPE = SPF
- fancybasename = "SPF"
-
-
- @implementer(IEncodableRecord)
- class Record_TSIG(tputil.FancyEqMixin, tputil.FancyStrMixin):
- """
- A transaction signature, encapsulated in a RR, as described
- in U{RFC 2845 <https://tools.ietf.org/html/rfc2845>}.
-
- @type algorithm: L{Name}
- @ivar algorithm: The name of the signature or MAC algorithm.
-
- @type timeSigned: L{int}
- @ivar timeSigned: Signing time, as seconds from the POSIX epoch.
-
- @type fudge: L{int}
- @ivar fudge: Allowable time skew, in seconds.
-
- @type MAC: L{bytes}
- @ivar MAC: The message digest or signature.
-
- @type originalID: L{int}
- @ivar originalID: A message ID.
-
- @type error: L{int}
- @ivar error: An error code (extended C{RCODE}) carried
- in exceptional cases.
-
- @type otherData: L{bytes}
- @ivar otherData: Other data carried in exceptional cases.
-
- """
-
- fancybasename = "TSIG"
- compareAttributes = (
- "algorithm",
- "timeSigned",
- "fudge",
- "MAC",
- "originalID",
- "error",
- "otherData",
- "ttl",
- )
- showAttributes = ["algorithm", "timeSigned", "MAC", "error", "otherData"]
-
- TYPE = TSIG
-
- def __init__(
- self,
- algorithm=None,
- timeSigned=None,
- fudge=5,
- MAC=None,
- originalID=0,
- error=OK,
- otherData=b"",
- ttl=0,
- ):
- # All of our init arguments have to have defaults, because of
- # the way IEncodable and Message.parseRecords() work, but for
- # some of our arguments there is no reasonable default; we use
- # invalid values here to prevent a user of this class from
- # relying on what's really an internal implementation detail.
- self.algorithm = None if algorithm is None else Name(algorithm)
- self.timeSigned = timeSigned
- self.fudge = str2time(fudge)
- self.MAC = MAC
- self.originalID = originalID
- self.error = error
- self.otherData = otherData
- self.ttl = ttl
-
- def encode(self, strio, compDict=None):
- self.algorithm.encode(strio, compDict)
- strio.write(struct.pack("!Q", self.timeSigned)[2:]) # 48-bit number
- strio.write(struct.pack("!HH", self.fudge, len(self.MAC)))
- strio.write(self.MAC)
- strio.write(
- struct.pack("!HHH", self.originalID, self.error, len(self.otherData))
- )
- strio.write(self.otherData)
-
- def decode(self, strio, length=None):
- algorithm = Name()
- algorithm.decode(strio)
- self.algorithm = algorithm
- fields = struct.unpack("!QHH", b"\x00\x00" + readPrecisely(strio, 10))
- self.timeSigned, self.fudge, macLength = fields
- self.MAC = readPrecisely(strio, macLength)
- fields = struct.unpack("!HHH", readPrecisely(strio, 6))
- self.originalID, self.error, otherLength = fields
- self.otherData = readPrecisely(strio, otherLength)
-
- def __hash__(self):
- return hash((self.algorithm, self.timeSigned, self.MAC, self.originalID))
-
-
- def _responseFromMessage(responseConstructor, message, **kwargs):
- """
- Generate a L{Message} like instance suitable for use as the response to
- C{message}.
-
- The C{queries}, C{id} attributes will be copied from C{message} and the
- C{answer} flag will be set to L{True}.
-
- @param responseConstructor: A response message constructor with an
- initializer signature matching L{dns.Message.__init__}.
- @type responseConstructor: C{callable}
-
- @param message: A request message.
- @type message: L{Message}
-
- @param kwargs: Keyword arguments which will be passed to the initialiser
- of the response message.
- @type kwargs: L{dict}
-
- @return: A L{Message} like response instance.
- @rtype: C{responseConstructor}
- """
- response = responseConstructor(id=message.id, answer=True, **kwargs)
- response.queries = message.queries[:]
- return response
-
-
- def _getDisplayableArguments(obj, alwaysShow, fieldNames):
- """
- Inspect the function signature of C{obj}'s constructor,
- and get a list of which arguments should be displayed.
- This is a helper function for C{_compactRepr}.
-
- @param obj: The instance whose repr is being generated.
- @param alwaysShow: A L{list} of field names which should always be shown.
- @param fieldNames: A L{list} of field attribute names which should be shown
- if they have non-default values.
- @return: A L{list} of displayable arguments.
- """
- displayableArgs = []
- # Get the argument names and values from the constructor.
- signature = inspect.signature(obj.__class__.__init__)
- for name in fieldNames:
- defaultValue = signature.parameters[name].default
- fieldValue = getattr(obj, name, defaultValue)
- if (name in alwaysShow) or (fieldValue != defaultValue):
- displayableArgs.append(f" {name}={fieldValue!r}")
-
- return displayableArgs
-
-
- def _compactRepr(
- obj, alwaysShow=None, flagNames=None, fieldNames=None, sectionNames=None
- ):
- """
- Return a L{str} representation of C{obj} which only shows fields with
- non-default values, flags which are True and sections which have been
- explicitly set.
-
- @param obj: The instance whose repr is being generated.
- @param alwaysShow: A L{list} of field names which should always be shown.
- @param flagNames: A L{list} of flag attribute names which should be shown if
- they are L{True}.
- @param fieldNames: A L{list} of field attribute names which should be shown
- if they have non-default values.
- @param sectionNames: A L{list} of section attribute names which should be
- shown if they have been assigned a value.
-
- @return: A L{str} representation of C{obj}.
- """
- if alwaysShow is None:
- alwaysShow = []
-
- if flagNames is None:
- flagNames = []
-
- if fieldNames is None:
- fieldNames = []
-
- if sectionNames is None:
- sectionNames = []
-
- setFlags = []
- for name in flagNames:
- if name in alwaysShow or getattr(obj, name, False) == True:
- setFlags.append(name)
-
- displayableArgs = _getDisplayableArguments(obj, alwaysShow, fieldNames)
- out = ["<", obj.__class__.__name__] + displayableArgs
-
- if setFlags:
- out.append(" flags={}".format(",".join(setFlags)))
-
- for name in sectionNames:
- section = getattr(obj, name, [])
- if section:
- out.append(f" {name}={section!r}")
-
- out.append(">")
-
- return "".join(out)
-
-
- class Message(tputil.FancyEqMixin):
- """
- L{Message} contains all the information represented by a single
- DNS request or response.
-
- @ivar id: See L{__init__}
- @ivar answer: See L{__init__}
- @ivar opCode: See L{__init__}
- @ivar recDes: See L{__init__}
- @ivar recAv: See L{__init__}
- @ivar auth: See L{__init__}
- @ivar rCode: See L{__init__}
- @ivar trunc: See L{__init__}
- @ivar maxSize: See L{__init__}
- @ivar authenticData: See L{__init__}
- @ivar checkingDisabled: See L{__init__}
-
- @ivar queries: The queries which are being asked of or answered by
- DNS server.
- @type queries: L{list} of L{Query}
-
- @ivar answers: Records containing the answers to C{queries} if
- this is a response message.
- @type answers: L{list} of L{RRHeader}
-
- @ivar authority: Records containing information about the
- authoritative DNS servers for the names in C{queries}.
- @type authority: L{list} of L{RRHeader}
-
- @ivar additional: Records containing IP addresses of host names
- in C{answers} and C{authority}.
- @type additional: L{list} of L{RRHeader}
-
- @ivar _flagNames: The names of attributes representing the flag header
- fields.
- @ivar _fieldNames: The names of attributes representing non-flag fixed
- header fields.
- @ivar _sectionNames: The names of attributes representing the record
- sections of this message.
- """
-
- compareAttributes = (
- "id",
- "answer",
- "opCode",
- "recDes",
- "recAv",
- "auth",
- "rCode",
- "trunc",
- "maxSize",
- "authenticData",
- "checkingDisabled",
- "queries",
- "answers",
- "authority",
- "additional",
- )
-
- headerFmt = "!H2B4H"
- headerSize = struct.calcsize(headerFmt)
-
- # Question, answer, additional, and nameserver lists
- queries = answers = add = ns = None
-
- def __init__(
- self,
- id=0,
- answer=0,
- opCode=0,
- recDes=0,
- recAv=0,
- auth=0,
- rCode=OK,
- trunc=0,
- maxSize=512,
- authenticData=0,
- checkingDisabled=0,
- ):
- """
- @param id: A 16 bit identifier assigned by the program that
- generates any kind of query. This identifier is copied to
- the corresponding reply and can be used by the requester
- to match up replies to outstanding queries.
- @type id: L{int}
-
- @param answer: A one bit field that specifies whether this
- message is a query (0), or a response (1).
- @type answer: L{int}
-
- @param opCode: A four bit field that specifies kind of query in
- this message. This value is set by the originator of a query
- and copied into the response.
- @type opCode: L{int}
-
- @param recDes: Recursion Desired - this bit may be set in a
- query and is copied into the response. If RD is set, it
- directs the name server to pursue the query recursively.
- Recursive query support is optional.
- @type recDes: L{int}
-
- @param recAv: Recursion Available - this bit is set or cleared
- in a response and denotes whether recursive query support
- is available in the name server.
- @type recAv: L{int}
-
- @param auth: Authoritative Answer - this bit is valid in
- responses and specifies that the responding name server
- is an authority for the domain name in question section.
- @type auth: L{int}
-
- @ivar rCode: A response code, used to indicate success or failure in a
- message which is a response from a server to a client request.
- @type rCode: C{0 <= int < 16}
-
- @param trunc: A flag indicating that this message was
- truncated due to length greater than that permitted on the
- transmission channel.
- @type trunc: L{int}
-
- @param maxSize: The requestor's UDP payload size is the number
- of octets of the largest UDP payload that can be
- reassembled and delivered in the requestor's network
- stack.
- @type maxSize: L{int}
-
- @param authenticData: A flag indicating in a response that all
- the data included in the answer and authority portion of
- the response has been authenticated by the server
- according to the policies of that server.
- See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
- @type authenticData: L{int}
-
- @param checkingDisabled: A flag indicating in a query that
- pending (non-authenticated) data is acceptable to the
- resolver sending the query.
- See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
- @type authenticData: L{int}
- """
- self.maxSize = maxSize
- self.id = id
- self.answer = answer
- self.opCode = opCode
- self.auth = auth
- self.trunc = trunc
- self.recDes = recDes
- self.recAv = recAv
- self.rCode = rCode
- self.authenticData = authenticData
- self.checkingDisabled = checkingDisabled
-
- self.queries = []
- self.answers = []
- self.authority = []
- self.additional = []
-
- def __repr__(self) -> str:
- """
- Generate a repr of this L{Message}.
-
- Only includes the non-default fields and sections and only includes
- flags which are set. The C{id} is always shown.
-
- @return: The native string repr.
- """
- return _compactRepr(
- self,
- flagNames=(
- "answer",
- "auth",
- "trunc",
- "recDes",
- "recAv",
- "authenticData",
- "checkingDisabled",
- ),
- fieldNames=("id", "opCode", "rCode", "maxSize"),
- sectionNames=("queries", "answers", "authority", "additional"),
- alwaysShow=("id",),
- )
-
- def addQuery(self, name, type=ALL_RECORDS, cls=IN):
- """
- Add another query to this Message.
-
- @type name: L{bytes}
- @param name: The name to query.
-
- @type type: L{int}
- @param type: Query type
-
- @type cls: L{int}
- @param cls: Query class
- """
- self.queries.append(Query(name, type, cls))
-
- def encode(self, strio):
- compDict = {}
- body_tmp = BytesIO()
- for q in self.queries:
- q.encode(body_tmp, compDict)
- for q in self.answers:
- q.encode(body_tmp, compDict)
- for q in self.authority:
- q.encode(body_tmp, compDict)
- for q in self.additional:
- q.encode(body_tmp, compDict)
- body = body_tmp.getvalue()
- size = len(body) + self.headerSize
- if self.maxSize and size > self.maxSize:
- self.trunc = 1
- body = body[: self.maxSize - self.headerSize]
- byte3 = (
- ((self.answer & 1) << 7)
- | ((self.opCode & 0xF) << 3)
- | ((self.auth & 1) << 2)
- | ((self.trunc & 1) << 1)
- | (self.recDes & 1)
- )
- byte4 = (
- ((self.recAv & 1) << 7)
- | ((self.authenticData & 1) << 5)
- | ((self.checkingDisabled & 1) << 4)
- | (self.rCode & 0xF)
- )
-
- strio.write(
- struct.pack(
- self.headerFmt,
- self.id,
- byte3,
- byte4,
- len(self.queries),
- len(self.answers),
- len(self.authority),
- len(self.additional),
- )
- )
- strio.write(body)
-
- def decode(self, strio, length=None):
- self.maxSize = 0
- header = readPrecisely(strio, self.headerSize)
- r = struct.unpack(self.headerFmt, header)
- self.id, byte3, byte4, nqueries, nans, nns, nadd = r
- self.answer = (byte3 >> 7) & 1
- self.opCode = (byte3 >> 3) & 0xF
- self.auth = (byte3 >> 2) & 1
- self.trunc = (byte3 >> 1) & 1
- self.recDes = byte3 & 1
- self.recAv = (byte4 >> 7) & 1
- self.authenticData = (byte4 >> 5) & 1
- self.checkingDisabled = (byte4 >> 4) & 1
- self.rCode = byte4 & 0xF
-
- self.queries = []
- for i in range(nqueries):
- q = Query()
- try:
- q.decode(strio)
- except EOFError:
- return
- self.queries.append(q)
-
- items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
-
- for (l, n) in items:
- self.parseRecords(l, n, strio)
-
- def parseRecords(self, list, num, strio):
- for i in range(num):
- header = RRHeader(auth=self.auth)
- try:
- header.decode(strio)
- except EOFError:
- return
- t = self.lookupRecordType(header.type)
- if not t:
- continue
- header.payload = t(ttl=header.ttl)
- try:
- header.payload.decode(strio, header.rdlength)
- except EOFError:
- return
- list.append(header)
-
- # Create a mapping from record types to their corresponding Record_*
- # classes. This relies on the global state which has been created so
- # far in initializing this module (so don't define Record classes after
- # this).
- _recordTypes = {}
- for name in globals():
- if name.startswith("Record_"):
- _recordTypes[globals()[name].TYPE] = globals()[name]
-
- # Clear the iteration variable out of the class namespace so it
- # doesn't become an attribute.
- del name
-
- def lookupRecordType(self, type):
- """
- Retrieve the L{IRecord} implementation for the given record type.
-
- @param type: A record type, such as C{A} or L{NS}.
- @type type: L{int}
-
- @return: An object which implements L{IRecord} or L{None} if none
- can be found for the given type.
- @rtype: C{Type[IRecord]}
- """
- return self._recordTypes.get(type, UnknownRecord)
-
- def toStr(self):
- """
- Encode this L{Message} into a byte string in the format described by RFC
- 1035.
-
- @rtype: L{bytes}
- """
- strio = BytesIO()
- self.encode(strio)
- return strio.getvalue()
-
- def fromStr(self, str):
- """
- Decode a byte string in the format described by RFC 1035 into this
- L{Message}.
-
- @param str: L{bytes}
- """
- strio = BytesIO(str)
- self.decode(strio)
-
-
- class _EDNSMessage(tputil.FancyEqMixin):
- """
- An I{EDNS} message.
-
- Designed for compatibility with L{Message} but with a narrower public
- interface.
-
- Most importantly, L{_EDNSMessage.fromStr} will interpret and remove I{OPT}
- records that are present in the additional records section.
-
- The I{OPT} records are used to populate certain I{EDNS} specific attributes.
-
- L{_EDNSMessage.toStr} will add suitable I{OPT} records to the additional
- section to represent the extended EDNS information.
-
- @see: U{https://tools.ietf.org/html/rfc6891}
-
- @ivar id: See L{__init__}
- @ivar answer: See L{__init__}
- @ivar opCode: See L{__init__}
- @ivar auth: See L{__init__}
- @ivar trunc: See L{__init__}
- @ivar recDes: See L{__init__}
- @ivar recAv: See L{__init__}
- @ivar rCode: See L{__init__}
- @ivar ednsVersion: See L{__init__}
- @ivar dnssecOK: See L{__init__}
- @ivar authenticData: See L{__init__}
- @ivar checkingDisabled: See L{__init__}
- @ivar maxSize: See L{__init__}
-
- @ivar queries: See L{__init__}
- @ivar answers: See L{__init__}
- @ivar authority: See L{__init__}
- @ivar additional: See L{__init__}
-
- @ivar _messageFactory: A constructor of L{Message} instances. Called by
- C{_toMessage} and C{_fromMessage}.
- """
-
- compareAttributes = (
- "id",
- "answer",
- "opCode",
- "auth",
- "trunc",
- "recDes",
- "recAv",
- "rCode",
- "ednsVersion",
- "dnssecOK",
- "authenticData",
- "checkingDisabled",
- "maxSize",
- "queries",
- "answers",
- "authority",
- "additional",
- )
-
- _messageFactory = Message
-
- def __init__(
- self,
- id=0,
- answer=False,
- opCode=OP_QUERY,
- auth=False,
- trunc=False,
- recDes=False,
- recAv=False,
- rCode=0,
- ednsVersion=0,
- dnssecOK=False,
- authenticData=False,
- checkingDisabled=False,
- maxSize=512,
- queries=None,
- answers=None,
- authority=None,
- additional=None,
- ):
- """
- Construct a new L{_EDNSMessage}
-
- @see: U{RFC1035 section-4.1.1<https://tools.ietf.org/html/rfc1035#section-4.1.1>}
- @see: U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}
- @see: U{RFC3225 section-3<https://tools.ietf.org/html/rfc3225#section-3>}
- @see: U{RFC6891 section-6.1.3<https://tools.ietf.org/html/rfc6891#section-6.1.3>}
-
- @param id: A 16 bit identifier assigned by the program that generates
- any kind of query. This identifier is copied the corresponding
- reply and can be used by the requester to match up replies to
- outstanding queries.
- @type id: L{int}
-
- @param answer: A one bit field that specifies whether this message is a
- query (0), or a response (1).
- @type answer: L{bool}
-
- @param opCode: A four bit field that specifies kind of query in this
- message. This value is set by the originator of a query and copied
- into the response.
- @type opCode: L{int}
-
- @param auth: Authoritative Answer - this bit is valid in responses, and
- specifies that the responding name server is an authority for the
- domain name in question section.
- @type auth: L{bool}
-
- @param trunc: Truncation - specifies that this message was truncated due
- to length greater than that permitted on the transmission channel.
- @type trunc: L{bool}
-
- @param recDes: Recursion Desired - this bit may be set in a query and is
- copied into the response. If set, it directs the name server to
- pursue the query recursively. Recursive query support is optional.
- @type recDes: L{bool}
-
- @param recAv: Recursion Available - this bit is set or cleared in a
- response, and denotes whether recursive query support is available
- in the name server.
- @type recAv: L{bool}
-
- @param rCode: Extended 12-bit RCODE. Derived from the 4 bits defined in
- U{RFC1035 4.1.1<https://tools.ietf.org/html/rfc1035#section-4.1.1>}
- and the upper 8bits defined in U{RFC6891
- 6.1.3<https://tools.ietf.org/html/rfc6891#section-6.1.3>}.
- @type rCode: L{int}
-
- @param ednsVersion: Indicates the EDNS implementation level. Set to
- L{None} to prevent any EDNS attributes and options being added to
- the encoded byte string.
- @type ednsVersion: L{int} or L{None}
-
- @param dnssecOK: DNSSEC OK bit as defined by
- U{RFC3225 3<https://tools.ietf.org/html/rfc3225#section-3>}.
- @type dnssecOK: L{bool}
-
- @param authenticData: A flag indicating in a response that all the data
- included in the answer and authority portion of the response has
- been authenticated by the server according to the policies of that
- server.
- See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
- @type authenticData: L{bool}
-
- @param checkingDisabled: A flag indicating in a query that pending
- (non-authenticated) data is acceptable to the resolver sending the
- query.
- See U{RFC2535 section-6.1<https://tools.ietf.org/html/rfc2535#section-6.1>}.
- @type authenticData: L{bool}
-
- @param maxSize: The requestor's UDP payload size is the number of octets
- of the largest UDP payload that can be reassembled and delivered in
- the requestor's network stack.
- @type maxSize: L{int}
-
- @param queries: The L{list} of L{Query} associated with this message.
- @type queries: L{list} of L{Query}
-
- @param answers: The L{list} of answers associated with this message.
- @type answers: L{list} of L{RRHeader}
-
- @param authority: The L{list} of authority records associated with this
- message.
- @type authority: L{list} of L{RRHeader}
-
- @param additional: The L{list} of additional records associated with
- this message.
- @type additional: L{list} of L{RRHeader}
- """
- self.id = id
- self.answer = answer
- self.opCode = opCode
- self.auth = auth
- self.trunc = trunc
- self.recDes = recDes
- self.recAv = recAv
- self.rCode = rCode
- self.ednsVersion = ednsVersion
- self.dnssecOK = dnssecOK
- self.authenticData = authenticData
- self.checkingDisabled = checkingDisabled
- self.maxSize = maxSize
-
- if queries is None:
- queries = []
- self.queries = queries
-
- if answers is None:
- answers = []
- self.answers = answers
-
- if authority is None:
- authority = []
- self.authority = authority
-
- if additional is None:
- additional = []
- self.additional = additional
-
- def __repr__(self) -> str:
- return _compactRepr(
- self,
- flagNames=(
- "answer",
- "auth",
- "trunc",
- "recDes",
- "recAv",
- "authenticData",
- "checkingDisabled",
- "dnssecOK",
- ),
- fieldNames=("id", "opCode", "rCode", "maxSize", "ednsVersion"),
- sectionNames=("queries", "answers", "authority", "additional"),
- alwaysShow=("id",),
- )
-
- def _toMessage(self):
- """
- Convert to a standard L{dns.Message}.
-
- If C{ednsVersion} is not None, an L{_OPTHeader} instance containing all
- the I{EDNS} specific attributes and options will be appended to the list
- of C{additional} records.
-
- @return: A L{dns.Message}
- @rtype: L{dns.Message}
- """
- m = self._messageFactory(
- id=self.id,
- answer=self.answer,
- opCode=self.opCode,
- auth=self.auth,
- trunc=self.trunc,
- recDes=self.recDes,
- recAv=self.recAv,
- # Assign the lower 4 bits to the message
- rCode=self.rCode & 0xF,
- authenticData=self.authenticData,
- checkingDisabled=self.checkingDisabled,
- )
-
- m.queries = self.queries[:]
- m.answers = self.answers[:]
- m.authority = self.authority[:]
- m.additional = self.additional[:]
-
- if self.ednsVersion is not None:
- o = _OPTHeader(
- version=self.ednsVersion,
- dnssecOK=self.dnssecOK,
- udpPayloadSize=self.maxSize,
- # Assign the upper 8 bits to the OPT record
- extendedRCODE=self.rCode >> 4,
- )
- m.additional.append(o)
-
- return m
-
- def toStr(self):
- """
- Encode to wire format by first converting to a standard L{dns.Message}.
-
- @return: A L{bytes} string.
- """
- return self._toMessage().toStr()
-
- @classmethod
- def _fromMessage(cls, message):
- """
- Construct and return a new L{_EDNSMessage} whose attributes and records
- are derived from the attributes and records of C{message} (a L{Message}
- instance).
-
- If present, an C{OPT} record will be extracted from the C{additional}
- section and its attributes and options will be used to set the EDNS
- specific attributes C{extendedRCODE}, C{ednsVersion}, C{dnssecOK},
- C{ednsOptions}.
-
- The C{extendedRCODE} will be combined with C{message.rCode} and assigned
- to C{self.rCode}.
-
- @param message: The source L{Message}.
- @type message: L{Message}
-
- @return: A new L{_EDNSMessage}
- @rtype: L{_EDNSMessage}
- """
- additional = []
- optRecords = []
- for r in message.additional:
- if r.type == OPT:
- optRecords.append(_OPTHeader.fromRRHeader(r))
- else:
- additional.append(r)
-
- newMessage = cls(
- id=message.id,
- answer=message.answer,
- opCode=message.opCode,
- auth=message.auth,
- trunc=message.trunc,
- recDes=message.recDes,
- recAv=message.recAv,
- rCode=message.rCode,
- authenticData=message.authenticData,
- checkingDisabled=message.checkingDisabled,
- # Default to None, it will be updated later when the OPT records are
- # parsed.
- ednsVersion=None,
- dnssecOK=False,
- queries=message.queries[:],
- answers=message.answers[:],
- authority=message.authority[:],
- additional=additional,
- )
-
- if len(optRecords) == 1:
- # XXX: If multiple OPT records are received, an EDNS server should
- # respond with FORMERR. See ticket:5669#comment:1.
- opt = optRecords[0]
- newMessage.ednsVersion = opt.version
- newMessage.dnssecOK = opt.dnssecOK
- newMessage.maxSize = opt.udpPayloadSize
- newMessage.rCode = opt.extendedRCODE << 4 | message.rCode
-
- return newMessage
-
- def fromStr(self, bytes):
- """
- Decode from wire format, saving flags, values and records to this
- L{_EDNSMessage} instance in place.
-
- @param bytes: The full byte string to be decoded.
- @type bytes: L{bytes}
- """
- m = self._messageFactory()
- m.fromStr(bytes)
-
- ednsMessage = self._fromMessage(m)
- for attrName in self.compareAttributes:
- setattr(self, attrName, getattr(ednsMessage, attrName))
-
-
- class DNSMixin:
- """
- DNS protocol mixin shared by UDP and TCP implementations.
-
- @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider which will
- be used to issue DNS queries and manage request timeouts.
- """
-
- id = None
- liveMessages = None
-
- def __init__(self, controller, reactor=None):
- self.controller = controller
- self.id = random.randrange(2 ** 10, 2 ** 15)
- if reactor is None:
- from twisted.internet import reactor
- self._reactor = reactor
-
- def pickID(self):
- """
- Return a unique ID for queries.
- """
- while True:
- id = randomSource()
- if id not in self.liveMessages:
- return id
-
- def callLater(self, period, func, *args):
- """
- Wrapper around reactor.callLater, mainly for test purpose.
- """
- return self._reactor.callLater(period, func, *args)
-
- def _query(self, queries, timeout, id, writeMessage):
- """
- Send out a message with the given queries.
-
- @type queries: L{list} of C{Query} instances
- @param queries: The queries to transmit
-
- @type timeout: L{int} or C{float}
- @param timeout: How long to wait before giving up
-
- @type id: L{int}
- @param id: Unique key for this request
-
- @type writeMessage: C{callable}
- @param writeMessage: One-parameter callback which writes the message
-
- @rtype: C{Deferred}
- @return: a C{Deferred} which will be fired with the result of the
- query, or errbacked with any errors that could happen (exceptions
- during writing of the query, timeout errors, ...).
- """
- m = Message(id, recDes=1)
- m.queries = queries
-
- try:
- writeMessage(m)
- except BaseException:
- return defer.fail()
-
- resultDeferred = defer.Deferred()
- cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
- self.liveMessages[id] = (resultDeferred, cancelCall)
-
- return resultDeferred
-
- def _clearFailed(self, deferred, id):
- """
- Clean the Deferred after a timeout.
- """
- try:
- del self.liveMessages[id]
- except KeyError:
- pass
- deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
-
-
- class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
- """
- DNS protocol over UDP.
- """
-
- resends = None
-
- def stopProtocol(self):
- """
- Stop protocol: reset state variables.
- """
- self.liveMessages = {}
- self.resends = {}
- self.transport = None
-
- def startProtocol(self):
- """
- Upon start, reset internal state.
- """
- self.liveMessages = {}
- self.resends = {}
-
- def writeMessage(self, message, address):
- """
- Send a message holding DNS queries.
-
- @type message: L{Message}
- """
- self.transport.write(message.toStr(), address)
-
- def startListening(self):
- self._reactor.listenUDP(0, self, maxPacketSize=512)
-
- def datagramReceived(self, data, addr):
- """
- Read a datagram, extract the message in it and trigger the associated
- Deferred.
- """
- m = Message()
- try:
- m.fromStr(data)
- except EOFError:
- log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
- return
- except ValueError as ex:
- log.msg(f"Invalid packet ({ex}) from {addr}")
- return
- except BaseException:
- # Nothing should trigger this, but since we're potentially
- # invoking a lot of different decoding methods, we might as well
- # be extra cautious. Anything that triggers this is itself
- # buggy.
- log.err(failure.Failure(), "Unexpected decoding error")
- return
-
- if m.id in self.liveMessages:
- d, canceller = self.liveMessages[m.id]
- del self.liveMessages[m.id]
- canceller.cancel()
- # XXX we shouldn't need this hack of catching exception on callback()
- try:
- d.callback(m)
- except BaseException:
- log.err()
- else:
- if m.id not in self.resends:
- self.controller.messageReceived(m, self, addr)
-
- def removeResend(self, id):
- """
- Mark message ID as no longer having duplication suppression.
- """
- try:
- del self.resends[id]
- except KeyError:
- pass
-
- def query(self, address, queries, timeout=10, id=None):
- """
- Send out a message with the given queries.
-
- @type address: L{tuple} of L{str} and L{int}
- @param address: The address to which to send the query
-
- @type queries: L{list} of C{Query} instances
- @param queries: The queries to transmit
-
- @rtype: C{Deferred}
- """
- if not self.transport:
- # XXX transport might not get created automatically, use callLater?
- try:
- self.startListening()
- except CannotListenError:
- return defer.fail()
-
- if id is None:
- id = self.pickID()
- else:
- self.resends[id] = 1
-
- def writeMessage(m):
- self.writeMessage(m, address)
-
- return self._query(queries, timeout, id, writeMessage)
-
-
- class DNSProtocol(DNSMixin, protocol.Protocol):
- """
- DNS protocol over TCP.
- """
-
- length = None
- buffer = b""
-
- def writeMessage(self, message):
- """
- Send a message holding DNS queries.
-
- @type message: L{Message}
- """
- s = message.toStr()
- self.transport.write(struct.pack("!H", len(s)) + s)
-
- def connectionMade(self):
- """
- Connection is made: reset internal state, and notify the controller.
- """
- self.liveMessages = {}
- self.controller.connectionMade(self)
-
- def connectionLost(self, reason):
- """
- Notify the controller that this protocol is no longer
- connected.
- """
- self.controller.connectionLost(self)
-
- def dataReceived(self, data):
- self.buffer += data
-
- while self.buffer:
- if self.length is None and len(self.buffer) >= 2:
- self.length = struct.unpack("!H", self.buffer[:2])[0]
- self.buffer = self.buffer[2:]
-
- if len(self.buffer) >= self.length:
- myChunk = self.buffer[: self.length]
- m = Message()
- m.fromStr(myChunk)
-
- try:
- d, canceller = self.liveMessages[m.id]
- except KeyError:
- self.controller.messageReceived(m, self)
- else:
- del self.liveMessages[m.id]
- canceller.cancel()
- # XXX we shouldn't need this hack
- try:
- d.callback(m)
- except BaseException:
- log.err()
-
- self.buffer = self.buffer[self.length :]
- self.length = None
- else:
- break
-
- def query(self, queries, timeout=60):
- """
- Send out a message with the given queries.
-
- @type queries: L{list} of C{Query} instances
- @param queries: The queries to transmit
-
- @rtype: C{Deferred}
- """
- id = self.pickID()
- return self._query(queries, timeout, id, self.writeMessage)
|