Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(59)

Side by Side Diff: third_party/twisted_8_1/twisted/names/dns.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.names.test.test_dns -*-
2 # Copyright (c) 2001-2007 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 DNS protocol implementation.
8
9 Future Plans:
10 - Get rid of some toplevels, maybe.
11 - Put in a better lookupRecordType implementation.
12
13 @author: U{Moshe Zadka<mailto:moshez@twistedmatrix.com>},
14 U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
15 """
16
17 # System imports
18 import warnings
19
20 import struct, random, types, socket
21
22 try:
23 import cStringIO as StringIO
24 except ImportError:
25 import StringIO
26
27 AF_INET6 = socket.AF_INET6
28
29 from zope.interface import implements, Interface
30
31
32 # Twisted imports
33 from twisted.internet import protocol, defer
34 from twisted.internet.error import CannotListenError
35 from twisted.python import log, failure
36 from twisted.python import util as tputil
37 from twisted.python import randbytes
38
39
40 def randomSource():
41 """
42 Wrapper around L{randbytes.secureRandom} to return 2 random chars.
43 """
44 return struct.unpack('H', randbytes.secureRandom(2, fallback=True))[0]
45
46
47 PORT = 53
48
49 (A, NS, MD, MF, CNAME, SOA, MB, MG, MR, NULL, WKS, PTR, HINFO, MINFO, MX, TXT,
50 RP, AFSDB) = range(1, 19)
51 AAAA = 28
52 SRV = 33
53 A6 = 38
54 DNAME = 39
55
56 QUERY_TYPES = {
57 A: 'A',
58 NS: 'NS',
59 MD: 'MD',
60 MF: 'MF',
61 CNAME: 'CNAME',
62 SOA: 'SOA',
63 MB: 'MB',
64 MG: 'MG',
65 MR: 'MR',
66 NULL: 'NULL',
67 WKS: 'WKS',
68 PTR: 'PTR',
69 HINFO: 'HINFO',
70 MINFO: 'MINFO',
71 MX: 'MX',
72 TXT: 'TXT',
73 RP: 'RP',
74 AFSDB: 'AFSDB',
75
76 # 19 through 27? Eh, I'll get to 'em.
77
78 AAAA: 'AAAA',
79 SRV: 'SRV',
80
81 A6: 'A6',
82 DNAME: 'DNAME'
83 }
84
85 IXFR, AXFR, MAILB, MAILA, ALL_RECORDS = range(251, 256)
86
87 # "Extended" queries (Hey, half of these are deprecated, good job)
88 EXT_QUERIES = {
89 IXFR: 'IXFR',
90 AXFR: 'AXFR',
91 MAILB: 'MAILB',
92 MAILA: 'MAILA',
93 ALL_RECORDS: 'ALL_RECORDS'
94 }
95
96 REV_TYPES = dict([
97 (v, k) for (k, v) in QUERY_TYPES.items() + EXT_QUERIES.items()
98 ])
99
100 IN, CS, CH, HS = range(1, 5)
101 ANY = 255
102
103 QUERY_CLASSES = {
104 IN: 'IN',
105 CS: 'CS',
106 CH: 'CH',
107 HS: 'HS',
108 ANY: 'ANY'
109 }
110 REV_CLASSES = dict([
111 (v, k) for (k, v) in QUERY_CLASSES.items()
112 ])
113
114
115 # Opcodes
116 OP_QUERY, OP_INVERSE, OP_STATUS = range(3)
117 OP_NOTIFY = 4 # RFC 1996
118 OP_UPDATE = 5 # RFC 2136
119
120
121 # Response Codes
122 OK, EFORMAT, ESERVER, ENAME, ENOTIMP, EREFUSED = range(6)
123
124 class IRecord(Interface):
125 """
126 An single entry in a zone of authority.
127
128 @cvar TYPE: An indicator of what kind of record this is.
129 """
130
131
132 # Backwards compatibility aliases - these should be deprecated or something I
133 # suppose. -exarkun
134 from twisted.names.error import DomainError, AuthoritativeDomainError
135 from twisted.names.error import DNSQueryTimeoutError
136
137
138 def str2time(s):
139 suffixes = (
140 ('S', 1), ('M', 60), ('H', 60 * 60), ('D', 60 * 60 * 24),
141 ('W', 60 * 60 * 24 * 7), ('Y', 60 * 60 * 24 * 365)
142 )
143 if isinstance(s, types.StringType):
144 s = s.upper().strip()
145 for (suff, mult) in suffixes:
146 if s.endswith(suff):
147 return int(float(s[:-1]) * mult)
148 try:
149 s = int(s)
150 except ValueError:
151 raise ValueError, "Invalid time interval specifier: " + s
152 return s
153
154
155 def readPrecisely(file, l):
156 buff = file.read(l)
157 if len(buff) < l:
158 raise EOFError
159 return buff
160
161
162 class IEncodable(Interface):
163 """
164 Interface for something which can be encoded to and decoded
165 from a file object.
166 """
167 def encode(strio, compDict = None):
168 """
169 Write a representation of this object to the given
170 file object.
171
172 @type strio: File-like object
173 @param strio: The stream to which to write bytes
174
175 @type compDict: C{dict} or C{None}
176 @param compDict: A dictionary of backreference addresses that have
177 have already been written to this stream and that may be used for
178 compression.
179 """
180
181 def decode(strio, length = None):
182 """
183 Reconstruct an object from data read from the given
184 file object.
185
186 @type strio: File-like object
187 @param strio: The stream from which bytes may be read
188
189 @type length: C{int} or C{None}
190 @param length: The number of bytes in this RDATA field. Most
191 implementations can ignore this value. Only in the case of
192 records similar to TXT where the total length is in no way
193 encoded in the data is it necessary.
194 """
195
196
197 class Name:
198 implements(IEncodable)
199
200 def __init__(self, name=''):
201 assert isinstance(name, types.StringTypes), "%r is not a string" % (name ,)
202 self.name = name
203
204 def encode(self, strio, compDict=None):
205 """
206 Encode this Name into the appropriate byte format.
207
208 @type strio: file
209 @param strio: The byte representation of this Name will be written to
210 this file.
211
212 @type compDict: dict
213 @param compDict: dictionary of Names that have already been encoded
214 and whose addresses may be backreferenced by this Name (for the purpose
215 of reducing the message size).
216 """
217 name = self.name
218 while name:
219 if compDict is not None:
220 if name in compDict:
221 strio.write(
222 struct.pack("!H", 0xc000 | compDict[name]))
223 return
224 else:
225 compDict[name] = strio.tell() + Message.headerSize
226 ind = name.find('.')
227 if ind > 0:
228 label, name = name[:ind], name[ind + 1:]
229 else:
230 label, name = name, ''
231 ind = len(label)
232 strio.write(chr(ind))
233 strio.write(label)
234 strio.write(chr(0))
235
236
237 def decode(self, strio, length=None):
238 """
239 Decode a byte string into this Name.
240
241 @type strio: file
242 @param strio: Bytes will be read from this file until the full Name
243 is decoded.
244
245 @raise EOFError: Raised when there are not enough bytes available
246 from C{strio}.
247 """
248 self.name = ''
249 off = 0
250 while 1:
251 l = ord(readPrecisely(strio, 1))
252 if l == 0:
253 if off > 0:
254 strio.seek(off)
255 return
256 if (l >> 6) == 3:
257 new_off = ((l&63) << 8
258 | ord(readPrecisely(strio, 1)))
259 if off == 0:
260 off = strio.tell()
261 strio.seek(new_off)
262 continue
263 label = readPrecisely(strio, l)
264 if self.name == '':
265 self.name = label
266 else:
267 self.name = self.name + '.' + label
268
269 def __eq__(self, other):
270 if isinstance(other, Name):
271 return str(self) == str(other)
272 return 0
273
274
275 def __hash__(self):
276 return hash(str(self))
277
278
279 def __str__(self):
280 return self.name
281
282 class Query:
283 """
284 Represent a single DNS query.
285
286 @ivar name: The name about which this query is requesting information.
287 @ivar type: The query type.
288 @ivar cls: The query class.
289 """
290
291 implements(IEncodable)
292
293 name = None
294 type = None
295 cls = None
296
297 def __init__(self, name='', type=A, cls=IN):
298 """
299 @type name: C{str}
300 @param name: The name about which to request information.
301
302 @type type: C{int}
303 @param type: The query type.
304
305 @type cls: C{int}
306 @param cls: The query class.
307 """
308 self.name = Name(name)
309 self.type = type
310 self.cls = cls
311
312
313 def encode(self, strio, compDict=None):
314 self.name.encode(strio, compDict)
315 strio.write(struct.pack("!HH", self.type, self.cls))
316
317
318 def decode(self, strio, length = None):
319 self.name.decode(strio)
320 buff = readPrecisely(strio, 4)
321 self.type, self.cls = struct.unpack("!HH", buff)
322
323
324 def __hash__(self):
325 return hash((str(self.name).lower(), self.type, self.cls))
326
327
328 def __cmp__(self, other):
329 return isinstance(other, Query) and cmp(
330 (str(self.name).lower(), self.type, self.cls),
331 (str(other.name).lower(), other.type, other.cls)
332 ) or cmp(self.__class__, other.__class__)
333
334
335 def __str__(self):
336 t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
337 c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
338 return '<Query %s %s %s>' % (self.name, t, c)
339
340
341 def __repr__(self):
342 return 'Query(%r, %r, %r)' % (str(self.name), self.type, self.cls)
343
344
345 class RRHeader:
346 """
347 A resource record header.
348
349 @cvar fmt: C{str} specifying the byte format of an RR.
350
351 @ivar name: The name about which this reply contains information.
352 @ivar type: The query type of the original request.
353 @ivar cls: The query class of the original request.
354 @ivar ttl: The time-to-live for this record.
355 @ivar payload: An object that implements the IEncodable interface
356 @ivar auth: Whether this header is authoritative or not.
357 """
358
359 implements(IEncodable)
360
361 fmt = "!HHIH"
362
363 name = None
364 type = None
365 cls = None
366 ttl = None
367 payload = None
368 rdlength = None
369
370 cachedResponse = None
371
372 def __init__(self, name='', type=A, cls=IN, ttl=0, payload=None, auth=False) :
373 """
374 @type name: C{str}
375 @param name: The name about which this reply contains information.
376
377 @type type: C{int}
378 @param type: The query type.
379
380 @type cls: C{int}
381 @param cls: The query class.
382
383 @type ttl: C{int}
384 @param ttl: Time to live for this record.
385
386 @type payload: An object implementing C{IEncodable}
387 @param payload: A Query Type specific data object.
388 """
389 assert (payload is None) or (payload.TYPE == type)
390
391 self.name = Name(name)
392 self.type = type
393 self.cls = cls
394 self.ttl = ttl
395 self.payload = payload
396 self.auth = auth
397
398
399 def encode(self, strio, compDict=None):
400 self.name.encode(strio, compDict)
401 strio.write(struct.pack(self.fmt, self.type, self.cls, self.ttl, 0))
402 if self.payload:
403 prefix = strio.tell()
404 self.payload.encode(strio, compDict)
405 aft = strio.tell()
406 strio.seek(prefix - 2, 0)
407 strio.write(struct.pack('!H', aft - prefix))
408 strio.seek(aft, 0)
409
410
411 def decode(self, strio, length = None):
412 self.name.decode(strio)
413 l = struct.calcsize(self.fmt)
414 buff = readPrecisely(strio, l)
415 r = struct.unpack(self.fmt, buff)
416 self.type, self.cls, self.ttl, self.rdlength = r
417
418
419 def isAuthoritative(self):
420 return self.auth
421
422
423 def __str__(self):
424 t = QUERY_TYPES.get(self.type, EXT_QUERIES.get(self.type, 'UNKNOWN (%d)' % self.type))
425 c = QUERY_CLASSES.get(self.cls, 'UNKNOWN (%d)' % self.cls)
426 return '<RR name=%s type=%s class=%s ttl=%ds auth=%s>' % (self.name, t, c, self.ttl, self.auth and 'True' or 'False')
427
428
429 __repr__ = __str__
430
431
432
433 class SimpleRecord(tputil.FancyStrMixin, tputil.FancyEqMixin):
434 """
435 A Resource Record which consists of a single RFC 1035 domain-name.
436
437 @type name: L{Name}
438 @ivar name: The name associated with this record.
439
440 @type ttl: C{int}
441 @ivar ttl: The maximum number of seconds which this record should be
442 cached.
443 """
444 implements(IEncodable, IRecord)
445
446 showAttributes = (('name', 'name', '%s'), 'ttl')
447 compareAttributes = ('name', 'ttl')
448
449 TYPE = None
450 name = None
451
452 def __init__(self, name='', ttl=None):
453 self.name = Name(name)
454 self.ttl = str2time(ttl)
455
456
457 def encode(self, strio, compDict = None):
458 self.name.encode(strio, compDict)
459
460
461 def decode(self, strio, length = None):
462 self.name = Name()
463 self.name.decode(strio)
464
465
466 def __hash__(self):
467 return hash(self.name)
468
469
470 # Kinds of RRs - oh my!
471 class Record_NS(SimpleRecord):
472 """
473 An authoritative nameserver.
474 """
475 TYPE = NS
476
477
478
479 class Record_MD(SimpleRecord):
480 """
481 A mail destination.
482
483 This record type is obsolete.
484
485 @see: L{Record_MX}
486 """
487 TYPE = MD
488
489
490
491 class Record_MF(SimpleRecord):
492 """
493 A mail forwarder.
494
495 This record type is obsolete.
496
497 @see: L{Record_MX}
498 """
499 TYPE = MF
500
501
502
503 class Record_CNAME(SimpleRecord):
504 """
505 The canonical name for an alias.
506 """
507 TYPE = CNAME
508
509
510
511 class Record_MB(SimpleRecord):
512 """
513 A mailbox domain name.
514
515 This is an experimental record type.
516 """
517 TYPE = MB
518
519
520
521 class Record_MG(SimpleRecord):
522 """
523 A mail group member.
524
525 This is an experimental record type.
526 """
527 TYPE = MG
528
529
530
531 class Record_MR(SimpleRecord):
532 """
533 A mail rename domain name.
534
535 This is an experimental record type.
536 """
537 TYPE = MR
538
539
540
541 class Record_PTR(SimpleRecord):
542 """
543 A domain name pointer.
544 """
545 TYPE = PTR
546
547
548
549 class Record_DNAME(SimpleRecord):
550 """
551 A non-terminal DNS name redirection.
552
553 This record type provides the capability to map an entire subtree of the
554 DNS name space to another domain. It differs from the CNAME record which
555 maps a single node of the name space.
556
557 @see: U{http://www.faqs.org/rfcs/rfc2672.html}
558 """
559 TYPE = DNAME
560
561
562
563 class Record_A(tputil.FancyEqMixin):
564 """
565 An IPv4 host address.
566
567 @type address: C{str}
568 @ivar address: The packed network-order representation of the IPv4 address
569 associated with this record.
570
571 @type ttl: C{int}
572 @ivar ttl: The maximum number of seconds which this record should be
573 cached.
574 """
575 implements(IEncodable, IRecord)
576
577 compareAttributes = ('address', 'ttl')
578
579 TYPE = A
580 address = None
581
582 def __init__(self, address='0.0.0.0', ttl=None):
583 address = socket.inet_aton(address)
584 self.address = address
585 self.ttl = str2time(ttl)
586
587
588 def encode(self, strio, compDict = None):
589 strio.write(self.address)
590
591
592 def decode(self, strio, length = None):
593 self.address = readPrecisely(strio, 4)
594
595
596 def __hash__(self):
597 return hash(self.address)
598
599
600 def __str__(self):
601 return '<A %s ttl=%s>' % (self.dottedQuad(), self.ttl)
602
603
604 def dottedQuad(self):
605 return socket.inet_ntoa(self.address)
606
607
608
609 class Record_SOA(tputil.FancyEqMixin, tputil.FancyStrMixin):
610 """
611 Marks the start of a zone of authority.
612
613 This record describes parameters which are shared by all records within a
614 particular zone.
615
616 @type mname: L{Name}
617 @ivar mname: The domain-name of the name server that was the original or
618 primary source of data for this zone.
619
620 @type rname: L{Name}
621 @ivar rname: A domain-name which specifies the mailbox of the person
622 responsible for this zone.
623
624 @type serial: C{int}
625 @ivar serial: The unsigned 32 bit version number of the original copy of
626 the zone. Zone transfers preserve this value. This value wraps and
627 should be compared using sequence space arithmetic.
628
629 @type refresh: C{int}
630 @ivar refresh: A 32 bit time interval before the zone should be refreshed.
631
632 @type minimum: C{int}
633 @ivar minimum: The unsigned 32 bit minimum TTL field that should be
634 exported with any RR from this zone.
635
636 @type expire: C{int}
637 @ivar expire: A 32 bit time value that specifies the upper limit on the
638 time interval that can elapse before the zone is no longer
639 authoritative.
640
641 @type retry: C{int}
642 @ivar retry: A 32 bit time interval that should elapse before a failed
643 refresh should be retried.
644
645 @type ttl: C{int}
646 @ivar ttl: The default TTL to use for records served from this zone.
647 """
648 implements(IEncodable, IRecord)
649
650 compareAttributes = ('serial', 'mname', 'rname', 'refresh', 'expire', 'retry ', 'ttl')
651 showAttributes = (('mname', 'mname', '%s'), ('rname', 'rname', '%s'), 'seria l', 'refresh', 'retry', 'expire', 'minimum', 'ttl')
652
653 TYPE = SOA
654
655 def __init__(self, mname='', rname='', serial=0, refresh=0, retry=0, expire= 0, minimum=0, ttl=None):
656 self.mname, self.rname = Name(mname), Name(rname)
657 self.serial, self.refresh = str2time(serial), str2time(refresh)
658 self.minimum, self.expire = str2time(minimum), str2time(expire)
659 self.retry = str2time(retry)
660 self.ttl = str2time(ttl)
661
662
663 def encode(self, strio, compDict = None):
664 self.mname.encode(strio, compDict)
665 self.rname.encode(strio, compDict)
666 strio.write(
667 struct.pack(
668 '!LlllL',
669 self.serial, self.refresh, self.retry, self.expire,
670 self.minimum
671 )
672 )
673
674
675 def decode(self, strio, length = None):
676 self.mname, self.rname = Name(), Name()
677 self.mname.decode(strio)
678 self.rname.decode(strio)
679 r = struct.unpack('!LlllL', readPrecisely(strio, 20))
680 self.serial, self.refresh, self.retry, self.expire, self.minimum = r
681
682
683 def __hash__(self):
684 return hash((
685 self.serial, self.mname, self.rname,
686 self.refresh, self.expire, self.retry
687 ))
688
689
690
691 class Record_NULL: # EXPERIMENTAL
692 """
693 A null record.
694
695 This is an experimental record type.
696
697 @type ttl: C{int}
698 @ivar ttl: The maximum number of seconds which this record should be
699 cached.
700 """
701 implements(IEncodable, IRecord)
702
703 TYPE = NULL
704
705 def __init__(self, payload=None, ttl=None):
706 self.payload = payload
707 self.ttl = str2time(ttl)
708
709
710 def encode(self, strio, compDict = None):
711 strio.write(self.payload)
712
713
714 def decode(self, strio, length = None):
715 self.payload = readPrecisely(strio, length)
716
717
718 def __hash__(self):
719 return hash(self.payload)
720
721
722
723 class Record_WKS(tputil.FancyEqMixin, tputil.FancyStrMixin): # OBSOLETE
724 """
725 A well known service description.
726
727 This record type is obsolete. See L{Record_SRV}.
728
729 @type address: C{str}
730 @ivar address: The packed network-order representation of the IPv4 address
731 associated with this record.
732
733 @type protocol: C{int}
734 @ivar protocol: The 8 bit IP protocol number for which this service map is
735 relevant.
736
737 @type map: C{str}
738 @ivar map: A bitvector indicating the services available at the specified
739 address.
740
741 @type ttl: C{int}
742 @ivar ttl: The maximum number of seconds which this record should be
743 cached.
744 """
745 implements(IEncodable, IRecord)
746
747 compareAttributes = ('address', 'protocol', 'map', 'ttl')
748 showAttributes = ('address', 'protocol', 'ttl')
749
750 TYPE = WKS
751
752 def __init__(self, address='0.0.0.0', protocol=0, map='', ttl=None):
753 self.address = socket.inet_aton(address)
754 self.protocol, self.map = protocol, map
755 self.ttl = str2time(ttl)
756
757
758 def encode(self, strio, compDict = None):
759 strio.write(self.address)
760 strio.write(struct.pack('!B', self.protocol))
761 strio.write(self.map)
762
763
764 def decode(self, strio, length = None):
765 self.address = readPrecisely(strio, 4)
766 self.protocol = struct.unpack('!B', readPrecisely(strio, 1))[0]
767 self.map = readPrecisely(strio, length - 5)
768
769
770 def __hash__(self):
771 return hash((self.address, self.protocol, self.map))
772
773
774
775 class Record_AAAA(tputil.FancyEqMixin): # OBSOLETE (or headed ther e)
776 """
777 An IPv6 host address.
778
779 This record type is obsolete.
780
781 @type address: C{str}
782 @ivar address: The packed network-order representation of the IPv6 address
783 associated with this record.
784
785 @type ttl: C{int}
786 @ivar ttl: The maximum number of seconds which this record should be
787 cached.
788
789 @see: L{Record_A6}
790 """
791 implements(IEncodable, IRecord)
792 TYPE = AAAA
793
794 compareAttributes = ('address', 'ttl')
795
796 def __init__(self, address = '::', ttl=None):
797 self.address = socket.inet_pton(AF_INET6, address)
798 self.ttl = str2time(ttl)
799
800
801 def encode(self, strio, compDict = None):
802 strio.write(self.address)
803
804
805 def decode(self, strio, length = None):
806 self.address = readPrecisely(strio, 16)
807
808
809 def __hash__(self):
810 return hash(self.address)
811
812
813 def __str__(self):
814 return '<AAAA %s ttl=%s>' % (socket.inet_ntop(AF_INET6, self.address), s elf.ttl)
815
816
817
818 class Record_A6:
819 """
820 An IPv6 address.
821
822 @type prefixLen: C{int}
823 @ivar prefixLen: The length of the suffix.
824
825 @type suffix: C{str}
826 @ivar suffix: An IPv6 address suffix in network order.
827
828 @type prefix: L{Name}
829 @ivar prefix: If specified, a name which will be used as a prefix for other
830 A6 records.
831
832 @type bytes: C{int}
833 @ivar bytes: The length of the prefix.
834
835 @type ttl: C{int}
836 @ivar ttl: The maximum number of seconds which this record should be
837 cached.
838
839 @see: U{http://www.faqs.org/rfcs/rfc2874.html}
840 """
841 implements(IEncodable, IRecord)
842 TYPE = A6
843
844 def __init__(self, prefixLen=0, suffix='::', prefix='', ttl=None):
845 self.prefixLen = prefixLen
846 self.suffix = socket.inet_pton(AF_INET6, suffix)
847 self.prefix = Name(prefix)
848 self.bytes = int((128 - self.prefixLen) / 8.0)
849 self.ttl = str2time(ttl)
850
851
852 def encode(self, strio, compDict = None):
853 strio.write(struct.pack('!B', self.prefixLen))
854 if self.bytes:
855 strio.write(self.suffix[-self.bytes:])
856 if self.prefixLen:
857 # This may not be compressed
858 self.prefix.encode(strio, None)
859
860
861 def decode(self, strio, length = None):
862 self.prefixLen = struct.unpack('!B', readPrecisely(strio, 1))[0]
863 self.bytes = int((128 - self.prefixLen) / 8.0)
864 if self.bytes:
865 self.suffix = '\x00' * (16 - self.bytes) + readPrecisely(strio, self .bytes)
866 if self.prefixLen:
867 self.prefix.decode(strio)
868
869
870 def __eq__(self, other):
871 if isinstance(other, Record_A6):
872 return (self.prefixLen == other.prefixLen and
873 self.suffix[-self.bytes:] == other.suffix[-self.bytes:] and
874 self.prefix == other.prefix and
875 self.ttl == other.ttl)
876 return 0
877
878
879 def __hash__(self):
880 return hash((self.prefixLen, self.suffix[-self.bytes:], self.prefix))
881
882
883 def __str__(self):
884 return '<A6 %s %s (%d) ttl=%s>' % (
885 self.prefix,
886 socket.inet_ntop(AF_INET6, self.suffix),
887 self.prefixLen, self.ttl
888 )
889
890
891
892 class Record_SRV(tputil.FancyEqMixin, tputil.FancyStrMixin):
893 """
894 The location of the server(s) for a specific protocol and domain.
895
896 This is an experimental record type.
897
898 @type priority: C{int}
899 @ivar priority: The priority of this target host. A client MUST attempt to
900 contact the target host with the lowest-numbered priority it can reach;
901 target hosts with the same priority SHOULD be tried in an order defined
902 by the weight field.
903
904 @type weight: C{int}
905 @ivar weight: Specifies a relative weight for entries with the same
906 priority. Larger weights SHOULD be given a proportionately higher
907 probability of being selected.
908
909 @type port: C{int}
910 @ivar port: The port on this target host of this service.
911
912 @type target: L{Name}
913 @ivar target: The domain name of the target host. There MUST be one or
914 more address records for this name, the name MUST NOT be an alias (in
915 the sense of RFC 1034 or RFC 2181). Implementors are urged, but not
916 required, to return the address record(s) in the Additional Data
917 section. Unless and until permitted by future standards action, name
918 compression is not to be used for this field.
919
920 @type ttl: C{int}
921 @ivar ttl: The maximum number of seconds which this record should be
922 cached.
923
924 @see: U{http://www.faqs.org/rfcs/rfc2782.html}
925 """
926 implements(IEncodable, IRecord)
927 TYPE = SRV
928
929 compareAttributes = ('priority', 'weight', 'target', 'port', 'ttl')
930 showAttributes = ('priority', 'weight', ('target', 'target', '%s'), 'port', 'ttl')
931
932 def __init__(self, priority=0, weight=0, port=0, target='', ttl=None):
933 self.priority = int(priority)
934 self.weight = int(weight)
935 self.port = int(port)
936 self.target = Name(target)
937 self.ttl = str2time(ttl)
938
939
940 def encode(self, strio, compDict = None):
941 strio.write(struct.pack('!HHH', self.priority, self.weight, self.port))
942 # This can't be compressed
943 self.target.encode(strio, None)
944
945
946 def decode(self, strio, length = None):
947 r = struct.unpack('!HHH', readPrecisely(strio, struct.calcsize('!HHH')))
948 self.priority, self.weight, self.port = r
949 self.target = Name()
950 self.target.decode(strio)
951
952
953 def __hash__(self):
954 return hash((self.priority, self.weight, self.port, self.target))
955
956
957
958 class Record_AFSDB(tputil.FancyStrMixin, tputil.FancyEqMixin):
959 """
960 Map from a domain name to the name of an AFS cell database server.
961
962 @type subtype: C{int}
963 @ivar subtype: In the case of subtype 1, the host has an AFS version 3.0
964 Volume Location Server for the named AFS cell. In the case of subtype
965 2, the host has an authenticated name server holding the cell-root
966 directory node for the named DCE/NCA cell.
967
968 @type hostname: L{Name}
969 @ivar hostname: The domain name of a host that has a server for the cell
970 named by this record.
971
972 @type ttl: C{int}
973 @ivar ttl: The maximum number of seconds which this record should be
974 cached.
975
976 @see: U{http://www.faqs.org/rfcs/rfc1183.html}
977 """
978 implements(IEncodable, IRecord)
979 TYPE = AFSDB
980
981 compareAttributes = ('subtype', 'hostname', 'ttl')
982 showAttributes = ('subtype', ('hostname', 'hostname', '%s'), 'ttl')
983
984 def __init__(self, subtype=0, hostname='', ttl=None):
985 self.subtype = int(subtype)
986 self.hostname = Name(hostname)
987 self.ttl = str2time(ttl)
988
989
990 def encode(self, strio, compDict = None):
991 strio.write(struct.pack('!H', self.subtype))
992 self.hostname.encode(strio, compDict)
993
994
995 def decode(self, strio, length = None):
996 r = struct.unpack('!H', readPrecisely(strio, struct.calcsize('!H')))
997 self.subtype, = r
998 self.hostname.decode(strio)
999
1000
1001 def __hash__(self):
1002 return hash((self.subtype, self.hostname))
1003
1004
1005
1006 class Record_RP(tputil.FancyEqMixin, tputil.FancyStrMixin):
1007 """
1008 The responsible person for a domain.
1009
1010 @type mbox: L{Name}
1011 @ivar mbox: A domain name that specifies the mailbox for the responsible
1012 person.
1013
1014 @type txt: L{Name}
1015 @ivar txt: A domain name for which TXT RR's exist (indirection through
1016 which allows information sharing about the contents of this RP record).
1017
1018 @type ttl: C{int}
1019 @ivar ttl: The maximum number of seconds which this record should be
1020 cached.
1021
1022 @see: U{http://www.faqs.org/rfcs/rfc1183.html}
1023 """
1024 implements(IEncodable, IRecord)
1025 TYPE = RP
1026
1027 compareAttributes = ('mbox', 'txt', 'ttl')
1028 showAttributes = (('mbox', 'mbox', '%s'), ('txt', 'txt', '%s'), 'ttl')
1029
1030 def __init__(self, mbox='', txt='', ttl=None):
1031 self.mbox = Name(mbox)
1032 self.txt = Name(txt)
1033 self.ttl = str2time(ttl)
1034
1035
1036 def encode(self, strio, compDict = None):
1037 self.mbox.encode(strio, compDict)
1038 self.txt.encode(strio, compDict)
1039
1040
1041 def decode(self, strio, length = None):
1042 self.mbox = Name()
1043 self.txt = Name()
1044 self.mbox.decode(strio)
1045 self.txt.decode(strio)
1046
1047
1048 def __hash__(self):
1049 return hash((self.mbox, self.txt))
1050
1051
1052
1053 class Record_HINFO(tputil.FancyStrMixin):
1054 """
1055 Host information.
1056
1057 @type cpu: C{str}
1058 @ivar cpu: Specifies the CPU type.
1059
1060 @type os: C{str}
1061 @ivar os: Specifies the OS.
1062
1063 @type ttl: C{int}
1064 @ivar ttl: The maximum number of seconds which this record should be
1065 cached.
1066 """
1067 implements(IEncodable, IRecord)
1068 TYPE = HINFO
1069
1070 showAttributes = ('cpu', 'os', 'ttl')
1071
1072 def __init__(self, cpu='', os='', ttl=None):
1073 self.cpu, self.os = cpu, os
1074 self.ttl = str2time(ttl)
1075
1076
1077 def encode(self, strio, compDict = None):
1078 strio.write(struct.pack('!B', len(self.cpu)) + self.cpu)
1079 strio.write(struct.pack('!B', len(self.os)) + self.os)
1080
1081
1082 def decode(self, strio, length = None):
1083 cpu = struct.unpack('!B', readPrecisely(strio, 1))[0]
1084 self.cpu = readPrecisely(strio, cpu)
1085 os = struct.unpack('!B', readPrecisely(strio, 1))[0]
1086 self.os = readPrecisely(strio, os)
1087
1088
1089 def __eq__(self, other):
1090 if isinstance(other, Record_HINFO):
1091 return (self.os.lower() == other.os.lower() and
1092 self.cpu.lower() == other.cpu.lower() and
1093 self.ttl == other.ttl)
1094 return 0
1095
1096
1097 def __hash__(self):
1098 return hash((self.os.lower(), self.cpu.lower()))
1099
1100
1101
1102 class Record_MINFO(tputil.FancyEqMixin, tputil.FancyStrMixin):
1103 """
1104 Mailbox or mail list information.
1105
1106 This is an experimental record type.
1107
1108 @type rmailbx: L{Name}
1109 @ivar rmailbx: A domain-name which specifies a mailbox which is responsible
1110 for the mailing list or mailbox. If this domain name names the root,
1111 the owner of the MINFO RR is responsible for itself.
1112
1113 @type emailbx: L{Name}
1114 @ivar emailbx: A domain-name which specifies a mailbox which is to receive
1115 error messages related to the mailing list or mailbox specified by the
1116 owner of the MINFO record. If this domain name names the root, errors
1117 should be returned to the sender of the message.
1118
1119 @type ttl: C{int}
1120 @ivar ttl: The maximum number of seconds which this record should be
1121 cached.
1122 """
1123 implements(IEncodable, IRecord)
1124 TYPE = MINFO
1125
1126 rmailbx = None
1127 emailbx = None
1128
1129 compareAttributes = ('rmailbx', 'emailbx', 'ttl')
1130 showAttributes = (('rmailbx', 'responsibility', '%s'),
1131 ('emailbx', 'errors', '%s'),
1132 'ttl')
1133
1134 def __init__(self, rmailbx='', emailbx='', ttl=None):
1135 self.rmailbx, self.emailbx = Name(rmailbx), Name(emailbx)
1136 self.ttl = str2time(ttl)
1137
1138
1139 def encode(self, strio, compDict = None):
1140 self.rmailbx.encode(strio, compDict)
1141 self.emailbx.encode(strio, compDict)
1142
1143
1144 def decode(self, strio, length = None):
1145 self.rmailbx, self.emailbx = Name(), Name()
1146 self.rmailbx.decode(strio)
1147 self.emailbx.decode(strio)
1148
1149
1150 def __hash__(self):
1151 return hash((self.rmailbx, self.emailbx))
1152
1153
1154
1155 class Record_MX(tputil.FancyStrMixin, tputil.FancyEqMixin):
1156 """
1157 Mail exchange.
1158
1159 @type preference: C{int}
1160 @ivar preference: Specifies the preference given to this RR among others at
1161 the same owner. Lower values are preferred.
1162
1163 @type name: L{Name}
1164 @ivar name: A domain-name which specifies a host willing to act as a mail
1165 exchange.
1166
1167 @type ttl: C{int}
1168 @ivar ttl: The maximum number of seconds which this record should be
1169 cached.
1170 """
1171 implements(IEncodable, IRecord)
1172 TYPE = MX
1173
1174 compareAttributes = ('preference', 'name', 'ttl')
1175 showAttributes = ('preference', ('name', 'name', '%s'), 'ttl')
1176
1177 def __init__(self, preference=0, name='', ttl=None, **kwargs):
1178 self.preference, self.name = int(preference), Name(kwargs.get('exchange' , name))
1179 self.ttl = str2time(ttl)
1180
1181 def encode(self, strio, compDict = None):
1182 strio.write(struct.pack('!H', self.preference))
1183 self.name.encode(strio, compDict)
1184
1185
1186 def decode(self, strio, length = None):
1187 self.preference = struct.unpack('!H', readPrecisely(strio, 2))[0]
1188 self.name = Name()
1189 self.name.decode(strio)
1190
1191 def exchange(self):
1192 warnings.warn("use Record_MX.name instead", DeprecationWarning, stacklev el=2)
1193 return self.name
1194
1195 exchange = property(exchange)
1196
1197 def __hash__(self):
1198 return hash((self.preference, self.name))
1199
1200
1201
1202 # Oh god, Record_TXT how I hate thee.
1203 class Record_TXT(tputil.FancyEqMixin, tputil.FancyStrMixin):
1204 """
1205 Freeform text.
1206
1207 @type data: C{list} of C{str}
1208 @ivar data: Freeform text which makes up this record.
1209 """
1210 implements(IEncodable, IRecord)
1211
1212 TYPE = TXT
1213
1214 showAttributes = compareAttributes = ('data', 'ttl')
1215
1216 def __init__(self, *data, **kw):
1217 self.data = list(data)
1218 # arg man python sucks so bad
1219 self.ttl = str2time(kw.get('ttl', None))
1220
1221
1222 def encode(self, strio, compDict = None):
1223 for d in self.data:
1224 strio.write(struct.pack('!B', len(d)) + d)
1225
1226
1227 def decode(self, strio, length = None):
1228 soFar = 0
1229 self.data = []
1230 while soFar < length:
1231 L = struct.unpack('!B', readPrecisely(strio, 1))[0]
1232 self.data.append(readPrecisely(strio, L))
1233 soFar += L + 1
1234 if soFar != length:
1235 log.msg(
1236 "Decoded %d bytes in TXT record, but rdlength is %d" % (
1237 soFar, length
1238 )
1239 )
1240
1241
1242 def __hash__(self):
1243 return hash(tuple(self.data))
1244
1245
1246
1247 class Message:
1248 headerFmt = "!H2B4H"
1249 headerSize = struct.calcsize(headerFmt)
1250
1251 # Question, answer, additional, and nameserver lists
1252 queries = answers = add = ns = None
1253
1254 def __init__(self, id=0, answer=0, opCode=0, recDes=0, recAv=0,
1255 auth=0, rCode=OK, trunc=0, maxSize=512):
1256 self.maxSize = maxSize
1257 self.id = id
1258 self.answer = answer
1259 self.opCode = opCode
1260 self.auth = auth
1261 self.trunc = trunc
1262 self.recDes = recDes
1263 self.recAv = recAv
1264 self.rCode = rCode
1265 self.queries = []
1266 self.answers = []
1267 self.authority = []
1268 self.additional = []
1269
1270
1271 def addQuery(self, name, type=ALL_RECORDS, cls=IN):
1272 """
1273 Add another query to this Message.
1274
1275 @type name: C{str}
1276 @param name: The name to query.
1277
1278 @type type: C{int}
1279 @param type: Query type
1280
1281 @type cls: C{int}
1282 @param cls: Query class
1283 """
1284 self.queries.append(Query(name, type, cls))
1285
1286
1287 def encode(self, strio):
1288 compDict = {}
1289 body_tmp = StringIO.StringIO()
1290 for q in self.queries:
1291 q.encode(body_tmp, compDict)
1292 for q in self.answers:
1293 q.encode(body_tmp, compDict)
1294 for q in self.authority:
1295 q.encode(body_tmp, compDict)
1296 for q in self.additional:
1297 q.encode(body_tmp, compDict)
1298 body = body_tmp.getvalue()
1299 size = len(body) + self.headerSize
1300 if self.maxSize and size > self.maxSize:
1301 self.trunc = 1
1302 body = body[:self.maxSize - self.headerSize]
1303 byte3 = (( ( self.answer & 1 ) << 7 )
1304 | ((self.opCode & 0xf ) << 3 )
1305 | ((self.auth & 1 ) << 2 )
1306 | ((self.trunc & 1 ) << 1 )
1307 | ( self.recDes & 1 ) )
1308 byte4 = ( ( (self.recAv & 1 ) << 7 )
1309 | (self.rCode & 0xf ) )
1310
1311 strio.write(struct.pack(self.headerFmt, self.id, byte3, byte4,
1312 len(self.queries), len(self.answers),
1313 len(self.authority), len(self.additional)))
1314 strio.write(body)
1315
1316
1317 def decode(self, strio, length=None):
1318 self.maxSize = 0
1319 header = readPrecisely(strio, self.headerSize)
1320 r = struct.unpack(self.headerFmt, header)
1321 self.id, byte3, byte4, nqueries, nans, nns, nadd = r
1322 self.answer = ( byte3 >> 7 ) & 1
1323 self.opCode = ( byte3 >> 3 ) & 0xf
1324 self.auth = ( byte3 >> 2 ) & 1
1325 self.trunc = ( byte3 >> 1 ) & 1
1326 self.recDes = byte3 & 1
1327 self.recAv = ( byte4 >> 7 ) & 1
1328 self.rCode = byte4 & 0xf
1329
1330 self.queries = []
1331 for i in range(nqueries):
1332 q = Query()
1333 try:
1334 q.decode(strio)
1335 except EOFError:
1336 return
1337 self.queries.append(q)
1338
1339 items = ((self.answers, nans), (self.authority, nns), (self.additional, nadd))
1340 for (l, n) in items:
1341 self.parseRecords(l, n, strio)
1342
1343
1344 def parseRecords(self, list, num, strio):
1345 for i in range(num):
1346 header = RRHeader()
1347 try:
1348 header.decode(strio)
1349 except EOFError:
1350 return
1351 t = self.lookupRecordType(header.type)
1352 if not t:
1353 continue
1354 header.payload = t(ttl=header.ttl)
1355 try:
1356 header.payload.decode(strio, header.rdlength)
1357 except EOFError:
1358 return
1359 list.append(header)
1360
1361
1362 def lookupRecordType(self, type):
1363 return globals().get('Record_' + QUERY_TYPES.get(type, ''), None)
1364
1365
1366 def toStr(self):
1367 strio = StringIO.StringIO()
1368 self.encode(strio)
1369 return strio.getvalue()
1370
1371
1372 def fromStr(self, str):
1373 strio = StringIO.StringIO(str)
1374 self.decode(strio)
1375
1376 class DNSMixin(object):
1377 """
1378 DNS protocol mixin shared by UDP and TCP implementations.
1379 """
1380 id = None
1381 liveMessages = None
1382
1383 def __init__(self, controller):
1384 self.controller = controller
1385 self.id = random.randrange(2 ** 10, 2 ** 15)
1386
1387 def pickID(self):
1388 """
1389 Return a unique ID for queries.
1390 """
1391 while True:
1392 self.id += randomSource() % (2 ** 10)
1393 self.id %= 2 ** 16
1394 if self.id not in self.liveMessages:
1395 break
1396 return self.id
1397
1398 def callLater(self, period, func, *args):
1399 """
1400 Wrapper around reactor.callLater, mainly for test purpose.
1401 """
1402 from twisted.internet import reactor
1403 return reactor.callLater(period, func, *args)
1404
1405 def _query(self, queries, timeout, id, writeMessage):
1406 """
1407 Send out a message with the given queries.
1408
1409 @type queries: C{list} of C{Query} instances
1410 @param queries: The queries to transmit
1411
1412 @type timeout: C{int} or C{float}
1413 @param timeout: How long to wait before giving up
1414
1415 @type id: C{int}
1416 @param id: Unique key for this request
1417
1418 @type writeMessage: C{callable}
1419 @param writeMessage: One-parameter callback which writes the message
1420
1421 @rtype: C{Deferred}
1422 @return: a C{Deferred} which will be fired with the result of the
1423 query, or errbacked with any errors that could happen (exceptions
1424 during writing of the query, timeout errors, ...).
1425 """
1426 m = Message(id, recDes=1)
1427 m.queries = queries
1428
1429 try:
1430 writeMessage(m)
1431 except:
1432 return defer.fail()
1433
1434 resultDeferred = defer.Deferred()
1435 cancelCall = self.callLater(timeout, self._clearFailed, resultDeferred, id)
1436 self.liveMessages[id] = (resultDeferred, cancelCall)
1437
1438 return resultDeferred
1439
1440 def _clearFailed(self, deferred, id):
1441 """
1442 Clean the Deferred after a timeout.
1443 """
1444 try:
1445 del self.liveMessages[id]
1446 except KeyError:
1447 pass
1448 deferred.errback(failure.Failure(DNSQueryTimeoutError(id)))
1449
1450
1451 class DNSDatagramProtocol(DNSMixin, protocol.DatagramProtocol):
1452 """
1453 DNS protocol over UDP.
1454 """
1455 resends = None
1456
1457 def stopProtocol(self):
1458 """
1459 Stop protocol: reset state variables.
1460 """
1461 self.liveMessages = {}
1462 self.resends = {}
1463 self.transport = None
1464
1465 def startProtocol(self):
1466 """
1467 Upon start, reset internal state.
1468 """
1469 self.liveMessages = {}
1470 self.resends = {}
1471
1472 def writeMessage(self, message, address):
1473 """
1474 Send a message holding DNS queries.
1475
1476 @type message: L{Message}
1477 """
1478 self.transport.write(message.toStr(), address)
1479
1480 def startListening(self):
1481 from twisted.internet import reactor
1482 reactor.listenUDP(0, self, maxPacketSize=512)
1483
1484 def datagramReceived(self, data, addr):
1485 """
1486 Read a datagram, extract the message in it and trigger the associated
1487 Deferred.
1488 """
1489 m = Message()
1490 try:
1491 m.fromStr(data)
1492 except EOFError:
1493 log.msg("Truncated packet (%d bytes) from %s" % (len(data), addr))
1494 return
1495 except:
1496 # Nothing should trigger this, but since we're potentially
1497 # invoking a lot of different decoding methods, we might as well
1498 # be extra cautious. Anything that triggers this is itself
1499 # buggy.
1500 log.err(failure.Failure(), "Unexpected decoding error")
1501 return
1502
1503 if m.id in self.liveMessages:
1504 d, canceller = self.liveMessages[m.id]
1505 del self.liveMessages[m.id]
1506 canceller.cancel()
1507 # XXX we shouldn't need this hack of catching exception on callback( )
1508 try:
1509 d.callback(m)
1510 except:
1511 log.err()
1512 else:
1513 if m.id not in self.resends:
1514 self.controller.messageReceived(m, self, addr)
1515
1516
1517 def removeResend(self, id):
1518 """
1519 Mark message ID as no longer having duplication suppression.
1520 """
1521 try:
1522 del self.resends[id]
1523 except KeyError:
1524 pass
1525
1526 def query(self, address, queries, timeout=10, id=None):
1527 """
1528 Send out a message with the given queries.
1529
1530 @type address: C{tuple} of C{str} and C{int}
1531 @param address: The address to which to send the query
1532
1533 @type queries: C{list} of C{Query} instances
1534 @param queries: The queries to transmit
1535
1536 @rtype: C{Deferred}
1537 """
1538 if not self.transport:
1539 # XXX transport might not get created automatically, use callLater?
1540 try:
1541 self.startListening()
1542 except CannotListenError:
1543 return defer.fail()
1544
1545 if id is None:
1546 id = self.pickID()
1547 else:
1548 self.resends[id] = 1
1549
1550 def writeMessage(m):
1551 self.writeMessage(m, address)
1552
1553 return self._query(queries, timeout, id, writeMessage)
1554
1555
1556 class DNSProtocol(DNSMixin, protocol.Protocol):
1557 """
1558 DNS protocol over TCP.
1559 """
1560 length = None
1561 buffer = ''
1562
1563 def writeMessage(self, message):
1564 """
1565 Send a message holding DNS queries.
1566
1567 @type message: L{Message}
1568 """
1569 s = message.toStr()
1570 self.transport.write(struct.pack('!H', len(s)) + s)
1571
1572 def connectionMade(self):
1573 """
1574 Connection is made: reset internal state, and notify the controller.
1575 """
1576 self.liveMessages = {}
1577 self.controller.connectionMade(self)
1578
1579 def dataReceived(self, data):
1580 self.buffer += data
1581
1582 while self.buffer:
1583 if self.length is None and len(self.buffer) >= 2:
1584 self.length = struct.unpack('!H', self.buffer[:2])[0]
1585 self.buffer = self.buffer[2:]
1586
1587 if len(self.buffer) >= self.length:
1588 myChunk = self.buffer[:self.length]
1589 m = Message()
1590 m.fromStr(myChunk)
1591
1592 try:
1593 d, canceller = self.liveMessages[m.id]
1594 except KeyError:
1595 self.controller.messageReceived(m, self)
1596 else:
1597 del self.liveMessages[m.id]
1598 canceller.cancel()
1599 # XXX we shouldn't need this hack
1600 try:
1601 d.callback(m)
1602 except:
1603 log.err()
1604
1605 self.buffer = self.buffer[self.length:]
1606 self.length = None
1607 else:
1608 break
1609
1610 def query(self, queries, timeout=60):
1611 """
1612 Send out a message with the given queries.
1613
1614 @type queries: C{list} of C{Query} instances
1615 @param queries: The queries to transmit
1616
1617 @rtype: C{Deferred}
1618 """
1619 id = self.pickID()
1620 return self._query(queries, timeout, id, self.writeMessage)
1621
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/names/common.py ('k') | third_party/twisted_8_1/twisted/names/error.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698