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