├── dnslib ├── server │ ├── __init__.py │ ├── circuits_server.py │ ├── gevent_server.py │ ├── udp_server.py │ └── udp_proxy.py ├── __init__.py ├── bimap.py ├── buffer.py ├── bit.py ├── label.py └── dns.py ├── .hgignore ├── README.github ├── setup.py └── README /dnslib/server/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dnslib/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from dns import * 3 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | *.swp 4 | *.pyc 5 | *~ 6 | *egg-info 7 | .DS_Store 8 | build 9 | dist 10 | MANIFEST 11 | -------------------------------------------------------------------------------- /README.github: -------------------------------------------------------------------------------- 1 | 2 | This repository is a clone of the master repository at: 3 | 4 | https://bitbucket.org/paulc/dnslib 5 | 6 | For amy issues please use the Bitbucket repository 7 | 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | try: 4 | from setuptools import setup, Command 5 | except ImportError: 6 | from distutils.core import Command,setup 7 | 8 | import dnslib.dns 9 | long_description = dnslib.dns.DNSRecord.__doc__.rstrip() + "\n" 10 | version = dnslib.dns.DNSRecord.version 11 | 12 | class GenerateReadme(Command): 13 | description = "Generates README file from long_description" 14 | user_options = [] 15 | def initialize_options(self): pass 16 | def finalize_options(self): pass 17 | def run(self): 18 | open("README","w").write(long_description) 19 | 20 | setup(name='dnslib', 21 | version = version, 22 | description = 'Simple library to encode/decode DNS wire-format packets', 23 | long_description = long_description, 24 | author = 'Paul Chakravarti', 25 | author_email = 'paul.chakravarti@gmail.com', 26 | url = 'http://bitbucket.org/paulc/dnslib/', 27 | cmdclass = { 'readme' : GenerateReadme }, 28 | packages = ['dnslib','dnslib.server'], 29 | license = 'BSD', 30 | classifiers = [ "Topic :: Internet :: Name Service (DNS)" ], 31 | ) 32 | -------------------------------------------------------------------------------- /dnslib/bimap.py: -------------------------------------------------------------------------------- 1 | 2 | class Bimap(object): 3 | 4 | """ 5 | 6 | A simple bi-directional map which returns either forward or 7 | reverse lookup of key through explicit 'lookup' method or 8 | through __getattr__ or __getitem__. If the key is not found 9 | in either the forward/reverse dictionaries it is returned. 10 | 11 | >>> m = Bimap({1:'a',2:'b',3:'c'}) 12 | >>> m[1] 13 | 'a' 14 | >>> m.lookup('a') 15 | 1 16 | >>> m.a 17 | 1 18 | 19 | """ 20 | 21 | def __init__(self,forward): 22 | self.forward = forward 23 | self.reverse = dict([(v,k) for (k,v) in forward.items()]) 24 | 25 | def lookup(self,k,default=None): 26 | try: 27 | try: 28 | return self.forward[k] 29 | except KeyError: 30 | return self.reverse[k] 31 | except KeyError: 32 | if default: 33 | return default 34 | else: 35 | raise 36 | 37 | def __getitem__(self,k): 38 | return self.lookup(k,k) 39 | 40 | def __getattr__(self,k): 41 | return self.lookup(k,k) 42 | 43 | if __name__ == '__main__': 44 | import doctest 45 | doctest.testmod() 46 | -------------------------------------------------------------------------------- /dnslib/server/circuits_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from circuits.net.sockets import UDPServer 4 | 5 | from dnslib import A, CNAME, MX, RR 6 | from dnslib import DNSHeader, DNSRecord, QTYPE 7 | 8 | AF_INET = 2 9 | SOCK_DGRAM = 2 10 | 11 | IP = "127.0.0.1" 12 | TXT = "circuits_server.py" 13 | 14 | 15 | class DNSServer(UDPServer): 16 | 17 | channel = "dns" 18 | 19 | def read(self, sock, data): 20 | request = DNSRecord.parse(data) 21 | id = request.header.id 22 | qname = request.q.qname 23 | qtype = request.q.qtype 24 | print "------ Request (%s): %r (%s)" % (str(sock), 25 | qname.label, QTYPE[qtype]) 26 | 27 | reply = DNSRecord(DNSHeader(id=id, qr=1, aa=1, ra=1), q=request.q) 28 | 29 | if qtype == QTYPE.A: 30 | reply.add_answer(RR(qname, qtype, rdata=A(IP))) 31 | elif qtype == QTYPE['*']: 32 | reply.add_answer(RR(qname, QTYPE.A, rdata=A(IP))) 33 | reply.add_answer(RR(qname, QTYPE.MX, rdata=MX(IP))) 34 | reply.add_answer(RR(qname, QTYPE.TXT, rdata=TXT(TXT))) 35 | else: 36 | reply.add_answer(RR(qname, QTYPE.CNAME, rdata=CNAME(TXT))) 37 | 38 | return reply.pack() 39 | 40 | DNSServer(("0.0.0.0", 53)).run() 41 | -------------------------------------------------------------------------------- /dnslib/server/gevent_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import gevent 4 | from gevent import socket 5 | 6 | from gevent import monkey 7 | monkey.patch_socket() 8 | 9 | from dnslib import A, AAAA, CNAME, MX, RR, TXT 10 | from dnslib import DNSHeader, DNSRecord, QTYPE 11 | 12 | AF_INET = 2 13 | SOCK_DGRAM = 2 14 | 15 | s = socket.socket(AF_INET, SOCK_DGRAM) 16 | s.bind(('', 53)) 17 | 18 | IP = "127.0.0.1" 19 | IPV6 = (0,) * 16 20 | MSG = "gevent_server.py" 21 | 22 | 23 | def dns_handler(s, peer, data): 24 | request = DNSRecord.parse(data) 25 | id = request.header.id 26 | qname = request.q.qname 27 | qtype = request.q.qtype 28 | print "------ Request (%s): %r (%s)" % (str(peer), 29 | qname.label, QTYPE[qtype]) 30 | print request 31 | 32 | reply = DNSRecord(DNSHeader(id=id, qr=1, aa=1, ra=1), q=request.q) 33 | if qtype == QTYPE.A: 34 | reply.add_answer(RR(qname, qtype, rdata=A(IP))) 35 | if qtype == QTYPE.AAAA: 36 | reply.add_answer(RR(qname, qtype, rdata=AAAA(IPV6))) 37 | elif qtype == QTYPE['*']: 38 | reply.add_answer(RR(qname, QTYPE.A, rdata=A(IP))) 39 | reply.add_answer(RR(qname, QTYPE.MX, rdata=MX(IP))) 40 | reply.add_answer(RR(qname, QTYPE.TXT, rdata=TXT(MSG))) 41 | else: 42 | reply.add_answer(RR(qname, QTYPE.CNAME, rdata=CNAME(MSG))) 43 | 44 | print "------ Reply" 45 | print reply 46 | 47 | s.sendto(reply.pack(), peer) 48 | 49 | while True: 50 | data, peer = s.recvfrom(8192) 51 | gevent.spawn(dns_handler, s, peer, data) 52 | -------------------------------------------------------------------------------- /dnslib/server/udp_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import socket 4 | 5 | from dnslib import A, AAAA, CNAME, MX, RR, TXT 6 | from dnslib import DNSHeader, DNSRecord, QTYPE 7 | 8 | AF_INET = 2 9 | SOCK_DGRAM = 2 10 | 11 | IP = "127.0.0.1" 12 | IPV6 = (0,) * 16 13 | MSG = "gevent_server.py" 14 | 15 | 16 | def dns_handler(s, peer, data): 17 | request = DNSRecord.parse(data) 18 | id = request.header.id 19 | qname = request.q.qname 20 | qtype = request.q.qtype 21 | print "------ Request (%s): %r (%s)" % (str(peer), 22 | qname.label, QTYPE[qtype]) 23 | print "\n".join([ " %s" % l for l in str(request).split("\n")]) 24 | 25 | reply = DNSRecord(DNSHeader(id=id, qr=1, aa=1, ra=1), q=request.q) 26 | if qtype == QTYPE.A: 27 | reply.add_answer(RR(qname, qtype, rdata=A(IP))) 28 | if qtype == QTYPE.AAAA: 29 | reply.add_answer(RR(qname, qtype, rdata=AAAA(IPV6))) 30 | elif qtype == QTYPE['*']: 31 | reply.add_answer(RR(qname, QTYPE.A, rdata=A(IP))) 32 | reply.add_answer(RR(qname, QTYPE.MX, rdata=MX(IP))) 33 | reply.add_answer(RR(qname, QTYPE.TXT, rdata=TXT(MSG))) 34 | else: 35 | reply.add_answer(RR(qname, QTYPE.CNAME, rdata=CNAME(MSG))) 36 | 37 | print "------ Reply" 38 | print "\n".join([ " %s" % l for l in str(reply).split("\n")]) 39 | 40 | s.sendto(reply.pack(), peer) 41 | 42 | s = socket.socket(AF_INET, SOCK_DGRAM) 43 | s.bind(('', 53)) 44 | 45 | while True: 46 | print "====== Waiting for connection" 47 | data, peer = s.recvfrom(8192) 48 | dns_handler(s,peer,data) 49 | -------------------------------------------------------------------------------- /dnslib/buffer.py: -------------------------------------------------------------------------------- 1 | 2 | import struct 3 | 4 | class Buffer(object): 5 | 6 | """ 7 | A simple data buffer - supports packing/unpacking in struct format 8 | 9 | >>> b = Buffer() 10 | >>> b.pack("!BHI",1,2,3) 11 | >>> b.offset 12 | 7 13 | >>> b.append("0123456789") 14 | >>> b.offset 15 | 17 16 | >>> b.offset = 0 17 | >>> b.unpack("!BHI") 18 | (1, 2, 3) 19 | >>> b.get(5) 20 | '01234' 21 | >>> b.get(5) 22 | '56789' 23 | >>> b.update(7,"2s","xx") 24 | >>> b.offset = 7 25 | >>> b.get(5) 26 | 'xx234' 27 | """ 28 | 29 | def __init__(self,data=""): 30 | """ 31 | Initialise Buffer from data 32 | """ 33 | self.data = data 34 | self.offset = 0 35 | 36 | def remaining(self): 37 | """ 38 | Return bytes remaining 39 | """ 40 | return len(self.data) - self.offset 41 | 42 | def get(self,len): 43 | """ 44 | Gen len bytes at current offset (& increment offset) 45 | """ 46 | start = self.offset 47 | end = self.offset + len 48 | self.offset += len 49 | return self.data[start:end] 50 | 51 | def pack(self,fmt,*args): 52 | """ 53 | Pack data at end of data according to fmt (from struct) & increment 54 | offset 55 | """ 56 | self.offset += struct.calcsize(fmt) 57 | self.data += struct.pack(fmt,*args) 58 | 59 | def append(self,s): 60 | """ 61 | Append s to end of data & increment offset 62 | """ 63 | self.offset += len(s) 64 | self.data += s 65 | 66 | def update(self,ptr,fmt,*args): 67 | """ 68 | Modify data at offset `ptr` 69 | """ 70 | s = struct.pack(fmt,*args) 71 | self.data = self.data[:ptr] + s + self.data[ptr+len(s):] 72 | 73 | def unpack(self,fmt): 74 | """ 75 | Unpack data at current offset according to fmt (from struct) 76 | """ 77 | return struct.unpack(fmt,self.get(struct.calcsize(fmt))) 78 | 79 | if __name__ == '__main__': 80 | import doctest 81 | doctest.testmod() 82 | -------------------------------------------------------------------------------- /dnslib/bit.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Some basic bit mainpulation utilities 4 | """ 5 | 6 | FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) 7 | 8 | def hexdump(src, length=16, prefix=''): 9 | """ 10 | Print hexdump of string 11 | 12 | >>> print hexdump("abcd\x00" * 4) 13 | 0000 61 62 63 64 00 61 62 63 64 00 61 62 63 64 00 61 abcd.abc d.abcd.a 14 | 0010 62 63 64 00 bcd. 15 | """ 16 | n = 0 17 | left = length / 2 18 | right = length - left 19 | result= [] 20 | while src: 21 | s,src = src[:length],src[length:] 22 | l,r = s[:left],s[left:] 23 | hexa = "%-*s" % (left*3,' '.join(["%02x"%ord(x) for x in l])) 24 | hexb = "%-*s" % (right*3,' '.join(["%02x"%ord(x) for x in r])) 25 | lf = l.translate(FILTER) 26 | rf = r.translate(FILTER) 27 | result.append("%s%04x %s %s %s %s" % (prefix, n, hexa, hexb, lf, rf)) 28 | n += length 29 | return "\n".join(result) 30 | 31 | def get_bits(data,offset,bits=1): 32 | """ 33 | Get specified bits from integer 34 | 35 | >>> bin(get_bits(0b0011100,2) 36 | 0b1 37 | >>> bin(get_bits(0b0011100,0,4)) 38 | 0b1100 39 | 40 | """ 41 | mask = ((1 << bits) - 1) << offset 42 | return (data & mask) >> offset 43 | 44 | def set_bits(data,value,offset,bits=1): 45 | """ 46 | Set specified bits in integer 47 | 48 | >>> bin(set_bits(0,0b1010,0,4)) 49 | 0b1010 50 | >>> bin(set_bits(0,0b1010,3,4)) 51 | 0b1010000 52 | """ 53 | mask = ((1 << bits) - 1) << offset 54 | clear = 0xffff ^ mask 55 | data = (data & clear) | ((value << offset) & mask) 56 | return data 57 | 58 | def binary(n,count=16,reverse=False): 59 | """ 60 | Display n in binary (only difference from built-in `bin` is 61 | that this function returns a fixed width string and can 62 | optionally be reversed 63 | 64 | >>> binary(6789) 65 | 0001101010000101 66 | >>> binary(6789,8) 67 | 10000101 68 | >>> binary(6789,reverse=True) 69 | 1010000101011000 70 | 71 | """ 72 | bits = [str((n >> y) & 1) for y in range(count-1, -1, -1)] 73 | if reverse: 74 | bits.reverse() 75 | return "".join(bits) 76 | 77 | -------------------------------------------------------------------------------- /dnslib/server/udp_proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import optparse,socket,sys 4 | from dnslib import DNSHeader, DNSRecord, QTYPE 5 | 6 | """ 7 | Simple DNS proxy - listens on proxy port and forwards request/reply to real server 8 | passing data through dnslib and printing the raw/parsed data 9 | 10 | This is mostly for testing - normal usage would be to use dig to generate request 11 | and compare outputs 12 | 13 | Options: 14 | 15 | --port=PORT Proxy port (default: 8053) 16 | --bind=BIND Proxy bind address (default: all) 17 | --dns=DNS DNS server (default: 8.8.8.8) 18 | --dns_port=DNS_PORT DNS server port (default: 53) 19 | 20 | Usage: 21 | 22 | # python udp_proxy.py 23 | 24 | (from another window) 25 | 26 | # dig @127.0.0.1 www.google.com -p 8053 27 | 28 | """ 29 | 30 | AF_INET = 2 31 | SOCK_DGRAM = 2 32 | 33 | parser = optparse.OptionParser(usage="Usage: %prog [options]") 34 | parser.add_option("--port",type=int,default=8053,help="Proxy port (default: 8053)") 35 | parser.add_option("--bind",default="127.0.0.1",help="Proxy bind address (default: 127.0.0.1)") 36 | parser.add_option("--dns",default="8.8.8.8",help="DNS server (default: 8.8.8.8)") 37 | parser.add_option("--dns_port",type=int,default=53,help="DNS server port (default: 53)") 38 | options,args = parser.parse_args() 39 | 40 | proxy = socket.socket(AF_INET, SOCK_DGRAM) 41 | proxy.bind((options.bind,options.port)) 42 | 43 | while True: 44 | # Wait for client connection 45 | data,client = proxy.recvfrom(8192) 46 | # Parse and print request 47 | request = DNSRecord.parse(data) 48 | id = request.header.id 49 | qname = request.q.qname 50 | qtype = request.q.qtype 51 | print "------ Request (%s): %r (%s)" % (str(client),qname.label,QTYPE[qtype]) 52 | print data.encode('hex') 53 | print "\n".join([ " %s" % l for l in str(request).split("\n")]) 54 | # Send request to server 55 | s = socket.socket(AF_INET, SOCK_DGRAM) 56 | s.sendto(data,(options.dns,options.dns_port)) 57 | # Wait for reply 58 | data,server = s.recvfrom(8192) 59 | # Parse and print reply 60 | reply = DNSRecord.parse(data) 61 | id = reply.header.id 62 | qname = reply.q.qname 63 | qtype = reply.q.qtype 64 | print "------ Reply (%s): %r (%s)" % (str(server),qname.label,QTYPE[qtype]) 65 | print data.encode('hex') 66 | print "\n".join([ " %s" % l for l in str(reply).split("\n")]) 67 | print 68 | # Send reply to client 69 | proxy.sendto(data,client) 70 | 71 | 72 | -------------------------------------------------------------------------------- /dnslib/label.py: -------------------------------------------------------------------------------- 1 | 2 | import types 3 | from bit import get_bits,set_bits 4 | from buffer import Buffer 5 | 6 | class DNSLabelError(Exception): 7 | pass 8 | 9 | class DNSLabel(object): 10 | 11 | """ 12 | Container for DNS label supporting arbitary label chars (including '.') 13 | 14 | >>> l1 = DNSLabel("aaa.bbb.ccc") 15 | >>> l2 = DNSLabel(["aaa","bbb","ccc"]) 16 | >>> l1 == l2 17 | True 18 | >>> x = { l1 : 1 } 19 | >>> x[l1] 20 | 1 21 | >>> print l1 22 | aaa.bbb.ccc 23 | >>> l1 24 | 'aaa.bbb.ccc' 25 | 26 | """ 27 | def __init__(self,label): 28 | """ 29 | Create label instance from elements in list/tuple. If label 30 | argument is a string split into components (separated by '.') 31 | """ 32 | if type(label) in (types.ListType,types.TupleType): 33 | self.label = tuple(label) 34 | else: 35 | self.label = tuple(label.split(".")) 36 | 37 | def __str__(self): 38 | return ".".join(self.label) 39 | 40 | def __repr__(self): 41 | return "%r" % ".".join(self.label) 42 | 43 | def __hash__(self): 44 | return hash(self.label) 45 | 46 | def __eq__(self,other): 47 | return self.label == other.label 48 | 49 | def __len__(self): 50 | return len(".".join(self.label)) 51 | 52 | class DNSBuffer(Buffer): 53 | 54 | """ 55 | Extends Buffer to provide DNS name encoding/decoding (with caching) 56 | 57 | >>> b = DNSBuffer() 58 | >>> b.encode_name("aaa.bbb.ccc") 59 | >>> b.encode_name("xxx.yyy.zzz") 60 | >>> b.encode_name("zzz.xxx.bbb.ccc") 61 | >>> b.encode_name("aaa.xxx.bbb.ccc") 62 | >>> b.data.encode("hex") 63 | '036161610362626203636363000378787803797979037a7a7a00037a7a7a03787878c00403616161c01e' 64 | >>> b.offset = 0 65 | >>> b.decode_name() 66 | 'aaa.bbb.ccc' 67 | >>> b.decode_name() 68 | 'xxx.yyy.zzz' 69 | >>> b.decode_name() 70 | 'zzz.xxx.bbb.ccc' 71 | >>> b.decode_name() 72 | 'aaa.xxx.bbb.ccc' 73 | 74 | >>> b = DNSBuffer() 75 | >>> b.encode_name(['a.aa','b.bb','c.cc']) 76 | >>> b.offset = 0 77 | >>> len(b.decode_name().label) 78 | 3 79 | """ 80 | 81 | def __init__(self,data=""): 82 | """ 83 | Add 'names' dict to cache stored labels 84 | """ 85 | super(DNSBuffer,self).__init__(data) 86 | self.names = {} 87 | 88 | def decode_name(self): 89 | """ 90 | Decode label at current offset in buffer (following pointers 91 | to cached elements where necessary) 92 | """ 93 | label = [] 94 | done = False 95 | while not done: 96 | (len,) = self.unpack("!B") 97 | if get_bits(len,6,2) == 3: 98 | # Pointer 99 | self.offset -= 1 100 | pointer = get_bits(self.unpack("!H")[0],0,14) 101 | save = self.offset 102 | self.offset = pointer 103 | label.extend(self.decode_name().label) 104 | self.offset = save 105 | done = True 106 | else: 107 | if len > 0: 108 | label.append(self.get(len)) 109 | else: 110 | done = True 111 | return DNSLabel(label) 112 | 113 | def encode_name(self,name): 114 | """ 115 | Encode label and store at end of buffer (compressing 116 | cached elements where needed) and store elements 117 | in 'names' dict 118 | """ 119 | if not isinstance(name,DNSLabel): 120 | name = DNSLabel(name) 121 | if len(name) > 253: 122 | raise DNSLabelError("Domain label too long: %r" % name) 123 | name = list(name.label) 124 | while name: 125 | if self.names.has_key(tuple(name)): 126 | # Cached - set pointer 127 | pointer = self.names[tuple(name)] 128 | pointer = set_bits(pointer,3,14,2) 129 | self.pack("!H",pointer) 130 | return 131 | else: 132 | self.names[tuple(name)] = self.offset 133 | element = name.pop(0) 134 | if len(element) > 63: 135 | raise DNSLabelError("Label component too long: %r" % element) 136 | self.pack("!B",len(element)) 137 | self.append(element) 138 | self.append("\x00") 139 | 140 | if __name__ == '__main__': 141 | import doctest 142 | doctest.testmod() 143 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | dnslib 3 | ------ 4 | 5 | A simple library to encode/decode DNS wire-format packets. This was originally 6 | written for a custom nameserver. 7 | 8 | The key classes are: 9 | 10 | * DNSRecord (contains a DNSHeader and one or more DNSQuestion/DNSRR records) 11 | * DNSHeader 12 | * DNSQuestion 13 | * RR (resource records) 14 | * RD (resource data - superclass for TXT,A,AAAA,MX,CNAME,PRT,SOA,NAPTR) 15 | * DNSLabel (envelope for a DNS label) 16 | 17 | The library has (in theory) very rudimentary support for EDNS0 options 18 | however this has not been tested due to a lack of data (anyone wanting 19 | to improve support or provide test data please raise an issue) 20 | 21 | Note: In version 0.3 the library was modified to use the DNSLabel class to 22 | support arbirary DNS labels (as specified in RFC2181) - and specifically 23 | to allow embedded '.'s. In most cases this is transparent (DNSLabel will 24 | automatically convert a domain label presented as a dot separated string & 25 | convert pack to this format when converted to a string) however to get the 26 | underlying label data (as a tuple) you need to access the DNSLabel.label 27 | attribute. To specifiy a label to the DNSRecord classes you can either pass 28 | a DNSLabel object or pass the elements as a list/tuple. 29 | 30 | To decode a DNS packet: 31 | 32 | >>> packet = 'd5ad818000010005000000000377777706676f6f676c6503636f6d0000010001c00c0005000100000005000803777777016cc010c02c0001000100000005000442f95b68c02c0001000100000005000442f95b63c02c0001000100000005000442f95b67c02c0001000100000005000442f95b93'.decode('hex') 33 | >>> d = DNSRecord.parse(packet) 34 | >>> print d 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | To create a DNS Request Packet: 44 | 45 | >>> d = DNSRecord(q=DNSQuestion("google.com")) 46 | >>> print d 47 | 48 | 49 | >>> d.pack() 50 | '...' 51 | 52 | >>> d = DNSRecord(q=DNSQuestion("google.com",QTYPE.MX)) 53 | >>> print d 54 | 55 | 56 | >>> d.pack() 57 | '...' 58 | 59 | To create a DNS Response Packet: 60 | 61 | >>> d = DNSRecord(DNSHeader(qr=1,aa=1,ra=1), 62 | ... q=DNSQuestion("abc.com"), 63 | ... a=RR("abc.com",rdata=A("1.2.3.4"))) 64 | >>> print d 65 | 66 | 67 | 68 | >>> d.pack() 69 | '...' 70 | 71 | To create a skeleton reply to a DNS query: 72 | 73 | >>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.CNAME)) 74 | >>> a = q.reply(data="xxx.abc.com") 75 | >>> print a 76 | 77 | 78 | 79 | >>> a.pack() 80 | '...' 81 | 82 | Add additional RRs: 83 | 84 | >>> a.add_answer(RR('xxx.abc.com',QTYPE.A,rdata=A("1.2.3.4"))) 85 | >>> print a 86 | 87 | 88 | 89 | 90 | >>> a.pack() 91 | '...' 92 | 93 | Changelog: 94 | 95 | * 0.1 2010-09-19 Initial Release 96 | * 0.2 2010-09-22 Minor fixes 97 | * 0.3 2010-10-02 Add DNSLabel class to support arbitrary labels (embedded '.') 98 | * 0.4 2012-02-26 Merge with dbslib-circuits 99 | * 0.5 2012-09-13 Add support for RFC2136 DDNS updates 100 | Patch provided by Wesley Shields - thanks 101 | * 0.6 2012-10-20 Basic AAAA support 102 | * 0.7 2012-10-20 Add initial EDNS0 support (untested) 103 | * 0.8 2012-11-04 Add support for NAPTR, Authority RR and additional RR 104 | Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks 105 | * 0.8.1 2012-11-05 Added NAPTR test case and fixed logic error 106 | Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks 107 | * 0.8.2 2012-11-11 Patch to fix IPv6 formatting 108 | Patch provided by Torbjörn Lönnemark (https://bitbucket.org/tobbezz) - thanks 109 | * 0.8.3 2013-04-27 Don't parse rdata if rdlength is 0 110 | Patch provided by Wesley Shields - thanks 111 | 112 | License: 113 | 114 | * BSD 115 | 116 | Author: 117 | 118 | * Paul Chakravarti (paul.chakravarti@gmail.com) 119 | 120 | Master Repository/Issues: 121 | 122 | * https://bitbucket.org/paulc/dnslib 123 | -------------------------------------------------------------------------------- /dnslib/dns.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import random,socket,struct 4 | 5 | from bit import get_bits,set_bits 6 | from bimap import Bimap 7 | from buffer import Buffer 8 | from label import DNSLabel,DNSLabelError,DNSBuffer 9 | 10 | QTYPE = Bimap({1:'A', 2:'NS', 5:'CNAME', 6:'SOA', 12:'PTR', 15:'MX', 11 | 16:'TXT', 17:'RP', 18:'AFSDB', 24:'SIG', 25:'KEY', 12 | 28:'AAAA', 29:'LOC', 33:'SRV', 35:'NAPTR', 36:'KX', 13 | 37:'CERT', 39:'DNAME', 41:'OPT', 42:'APL', 43:'DS', 14 | 44:'SSHFP', 45:'IPSECKEY', 46:'RRSIG', 47:'NSEC', 15 | 48:'DNSKEY', 49:'DHCID', 50:'NSEC3', 51:'NSEC3PARAM', 16 | 55:'HIP', 99:'SPF', 249:'TKEY', 250:'TSIG', 251:'IXFR', 17 | 252:'AXFR', 255:'*', 32768:'TA', 32769:'DLV'}) 18 | CLASS = Bimap({ 1:'IN', 2:'CS', 3:'CH', 4:'Hesiod', 254:'None', 255:'*'}) 19 | QR = Bimap({ 0:'QUERY', 1:'RESPONSE' }) 20 | RCODE = Bimap({ 0:'None', 1:'Format Error', 2:'Server failure', 21 | 3:'Name Error', 4:'Not Implemented', 5:'Refused', 6:'YXDOMAIN', 22 | 7:'YXRRSET', 8:'NXRRSET', 9:'NOTAUTH', 10:'NOTZONE'}) 23 | OPCODE = Bimap({ 0:'QUERY', 1:'IQUERY', 2:'STATUS', 5:'UPDATE' }) 24 | 25 | class DNSError(Exception): 26 | pass 27 | 28 | class DNSRecord(object): 29 | 30 | """ 31 | dnslib 32 | ------ 33 | 34 | A simple library to encode/decode DNS wire-format packets. This was originally 35 | written for a custom nameserver. 36 | 37 | The key classes are: 38 | 39 | * DNSRecord (contains a DNSHeader and one or more DNSQuestion/DNSRR records) 40 | * DNSHeader 41 | * DNSQuestion 42 | * RR (resource records) 43 | * RD (resource data - superclass for TXT,A,AAAA,MX,CNAME,PRT,SOA,NAPTR) 44 | * DNSLabel (envelope for a DNS label) 45 | 46 | The library has (in theory) very rudimentary support for EDNS0 options 47 | however this has not been tested due to a lack of data (anyone wanting 48 | to improve support or provide test data please raise an issue) 49 | 50 | Note: In version 0.3 the library was modified to use the DNSLabel class to 51 | support arbirary DNS labels (as specified in RFC2181) - and specifically 52 | to allow embedded '.'s. In most cases this is transparent (DNSLabel will 53 | automatically convert a domain label presented as a dot separated string & 54 | convert pack to this format when converted to a string) however to get the 55 | underlying label data (as a tuple) you need to access the DNSLabel.label 56 | attribute. To specifiy a label to the DNSRecord classes you can either pass 57 | a DNSLabel object or pass the elements as a list/tuple. 58 | 59 | To decode a DNS packet: 60 | 61 | >>> packet = 'd5ad818000010005000000000377777706676f6f676c6503636f6d0000010001c00c0005000100000005000803777777016cc010c02c0001000100000005000442f95b68c02c0001000100000005000442f95b63c02c0001000100000005000442f95b67c02c0001000100000005000442f95b93'.decode('hex') 62 | >>> d = DNSRecord.parse(packet) 63 | >>> print d 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | To create a DNS Request Packet: 73 | 74 | >>> d = DNSRecord(q=DNSQuestion("google.com")) 75 | >>> print d 76 | 77 | 78 | >>> d.pack() 79 | '...' 80 | 81 | >>> d = DNSRecord(q=DNSQuestion("google.com",QTYPE.MX)) 82 | >>> print d 83 | 84 | 85 | >>> d.pack() 86 | '...' 87 | 88 | To create a DNS Response Packet: 89 | 90 | >>> d = DNSRecord(DNSHeader(qr=1,aa=1,ra=1), 91 | ... q=DNSQuestion("abc.com"), 92 | ... a=RR("abc.com",rdata=A("1.2.3.4"))) 93 | >>> print d 94 | 95 | 96 | 97 | >>> d.pack() 98 | '...' 99 | 100 | To create a skeleton reply to a DNS query: 101 | 102 | >>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.CNAME)) 103 | >>> a = q.reply(data="xxx.abc.com") 104 | >>> print a 105 | 106 | 107 | 108 | >>> a.pack() 109 | '...' 110 | 111 | Add additional RRs: 112 | 113 | >>> a.add_answer(RR('xxx.abc.com',QTYPE.A,rdata=A("1.2.3.4"))) 114 | >>> print a 115 | 116 | 117 | 118 | 119 | >>> a.pack() 120 | '...' 121 | 122 | Changelog: 123 | 124 | * 0.1 2010-09-19 Initial Release 125 | * 0.2 2010-09-22 Minor fixes 126 | * 0.3 2010-10-02 Add DNSLabel class to support arbitrary labels (embedded '.') 127 | * 0.4 2012-02-26 Merge with dbslib-circuits 128 | * 0.5 2012-09-13 Add support for RFC2136 DDNS updates 129 | Patch provided by Wesley Shields - thanks 130 | * 0.6 2012-10-20 Basic AAAA support 131 | * 0.7 2012-10-20 Add initial EDNS0 support (untested) 132 | * 0.8 2012-11-04 Add support for NAPTR, Authority RR and additional RR 133 | Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks 134 | * 0.8.1 2012-11-05 Added NAPTR test case and fixed logic error 135 | Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks 136 | * 0.8.2 2012-11-11 Patch to fix IPv6 formatting 137 | Patch provided by Torbjörn Lönnemark (https://bitbucket.org/tobbezz) - thanks 138 | * 0.8.3 2013-04-27 Don't parse rdata if rdlength is 0 139 | Patch provided by Wesley Shields - thanks 140 | 141 | License: 142 | 143 | * BSD 144 | 145 | Author: 146 | 147 | * Paul Chakravarti (paul.chakravarti@gmail.com) 148 | 149 | Master Repository/Issues: 150 | 151 | * https://bitbucket.org/paulc/dnslib 152 | 153 | """ 154 | 155 | version = "0.8.3" 156 | 157 | @classmethod 158 | def parse(cls,packet): 159 | """ 160 | Parse DNS packet data and return DNSRecord instance 161 | """ 162 | buffer = DNSBuffer(packet) 163 | header = DNSHeader.parse(buffer) 164 | questions = [] 165 | rr = [] 166 | ns = [] 167 | ar = [] 168 | for i in range(header.q): 169 | questions.append(DNSQuestion.parse(buffer)) 170 | for i in range(header.a): 171 | rr.append(RR.parse(buffer)) 172 | for i in range(header.ns): 173 | ns.append(RR.parse(buffer)) 174 | for i in range(header.ar): 175 | ar.append(RR.parse(buffer)) 176 | return cls(header,questions,rr,ns=ns,ar=ar) 177 | 178 | def __init__(self,header=None,questions=None,rr=None,q=None,a=None,ns=None,ar=None): 179 | """ 180 | Create DNSRecord 181 | """ 182 | self.header = header or DNSHeader() 183 | self.questions = questions or [] 184 | self.rr = rr or [] 185 | self.ns = ns or [] 186 | self.ar = ar or [] 187 | # Shortcuts to add a single Question/Answer 188 | if q: 189 | self.questions.append(q) 190 | if a: 191 | self.rr.append(a) 192 | self.set_header_qa() 193 | 194 | def reply(self,data="",ra=1,aa=1): 195 | answer = RDMAP.get(QTYPE[self.q.qtype],RD)(data) 196 | return DNSRecord(DNSHeader(id=self.header.id,bitmap=self.header.bitmap,qr=1,ra=ra,aa=aa), 197 | q=self.q, 198 | a=RR(self.q.qname,self.q.qtype,rdata=answer)) 199 | 200 | 201 | def add_question(self,q): 202 | self.questions.append(q) 203 | self.set_header_qa() 204 | 205 | def add_answer(self,rr): 206 | self.rr.append(rr) 207 | self.set_header_qa() 208 | 209 | def add_ns(self,ns): 210 | self.ns.append(ns) 211 | self.set_header_qa() 212 | 213 | def add_ar(self,ar): 214 | self.ar.append(ar) 215 | self.set_header_qa() 216 | 217 | def set_header_qa(self): 218 | self.header.q = len(self.questions) 219 | self.header.a = len(self.rr) 220 | self.header.ns = len(self.ns) 221 | self.header.ar = len(self.ar) 222 | 223 | # Shortcut to get first question 224 | def get_q(self): 225 | return self.questions[0] 226 | q = property(get_q) 227 | 228 | # Shortcut to get first answer 229 | def get_a(self): 230 | return self.rr[0] 231 | a = property(get_a) 232 | 233 | def pack(self): 234 | self.set_header_qa() 235 | buffer = DNSBuffer() 236 | self.header.pack(buffer) 237 | for q in self.questions: 238 | q.pack(buffer) 239 | for rr in self.rr: 240 | rr.pack(buffer) 241 | for ns in self.ns: 242 | ns.pack(buffer) 243 | for ar in self.ar: 244 | ar.pack(buffer) 245 | return buffer.data 246 | 247 | def send(self,dest,port=53): 248 | sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 249 | sock.sendto(self.pack(),(dest,port)) 250 | response,server = sock.recvfrom(8192) 251 | sock.close() 252 | return DNSRecord.parse(response) 253 | 254 | def __str__(self): 255 | sections = [ str(self.header) ] 256 | sections.extend([str(q) for q in self.questions]) 257 | sections.extend([str(rr) for rr in self.rr]) 258 | sections.extend([str(rr) for rr in self.ns]) 259 | sections.extend([str(rr) for rr in self.ar]) 260 | return "\n".join(sections) 261 | 262 | class DNSHeader(object): 263 | 264 | @classmethod 265 | def parse(cls,buffer): 266 | (id,bitmap,q,a,ns,ar) = buffer.unpack("!HHHHHH") 267 | return cls(id,bitmap,q,a,ns,ar) 268 | 269 | def __init__(self,id=None,bitmap=None,q=0,a=0,ns=0,ar=0,**args): 270 | if id is None: 271 | self.id = random.randint(0,65535) 272 | else: 273 | self.id = id 274 | if bitmap is None: 275 | self.bitmap = 0 276 | self.rd = 1 277 | else: 278 | self.bitmap = bitmap 279 | self.q = q 280 | self.a = a 281 | self.ns = ns 282 | self.ar = ar 283 | for k,v in args.items(): 284 | if k.lower() == "qr": 285 | self.qr = v 286 | elif k.lower() == "opcode": 287 | self.opcode = v 288 | elif k.lower() == "aa": 289 | self.aa = v 290 | elif k.lower() == "tc": 291 | self.tc = v 292 | elif k.lower() == "rd": 293 | self.rd = v 294 | elif k.lower() == "ra": 295 | self.ra = v 296 | elif k.lower() == "rcode": 297 | self.rcode = v 298 | 299 | def get_qr(self): 300 | return get_bits(self.bitmap,15) 301 | 302 | def set_qr(self,val): 303 | self.bitmap = set_bits(self.bitmap,val,15) 304 | 305 | qr = property(get_qr,set_qr) 306 | 307 | def get_opcode(self): 308 | return get_bits(self.bitmap,11,4) 309 | 310 | def set_opcode(self,val): 311 | self.bitmap = set_bits(self.bitmap,val,11,4) 312 | 313 | opcode = property(get_opcode,set_opcode) 314 | 315 | def get_aa(self): 316 | return get_bits(self.bitmap,10) 317 | 318 | def set_aa(self,val): 319 | self.bitmap = set_bits(self.bitmap,val,10) 320 | 321 | aa = property(get_aa,set_aa) 322 | 323 | def get_tc(self): 324 | return get_bits(self.bitmap,9) 325 | 326 | def set_tc(self,val): 327 | self.bitmap = set_bits(self.bitmap,val,9) 328 | 329 | tc = property(get_tc,set_tc) 330 | 331 | def get_rd(self): 332 | return get_bits(self.bitmap,8) 333 | 334 | def set_rd(self,val): 335 | self.bitmap = set_bits(self.bitmap,val,8) 336 | 337 | rd = property(get_rd,set_rd) 338 | 339 | def get_ra(self): 340 | return get_bits(self.bitmap,7) 341 | 342 | def set_ra(self,val): 343 | self.bitmap = set_bits(self.bitmap,val,7) 344 | 345 | ra = property(get_ra,set_ra) 346 | 347 | def get_rcode(self): 348 | return get_bits(self.bitmap,0,4) 349 | 350 | def set_rcode(self,val): 351 | self.bitmap = set_bits(self.bitmap,val,0,4) 352 | 353 | rcode = property(get_rcode,set_rcode) 354 | 355 | def pack(self,buffer): 356 | buffer.pack("!HHHHHH",self.id,self.bitmap,self.q,self.a,self.ns,self.ar) 357 | 358 | def __str__(self): 359 | f = [ self.aa and 'AA', 360 | self.tc and 'TC', 361 | self.rd and 'RD', 362 | self.ra and 'RA' ] 363 | if OPCODE[self.opcode] == 'UPDATE': 364 | f1='zo' 365 | f2='pr' 366 | f3='up' 367 | f4='ad' 368 | else: 369 | f1='q' 370 | f2='a' 371 | f3='ns' 372 | f4='ar' 373 | return "" % ( 375 | self.id, 376 | QR[self.qr], 377 | OPCODE[self.opcode], 378 | ",".join(filter(None,f)), 379 | RCODE[self.rcode], 380 | f1, self.q, f2, self.a, f3, self.ns, f4, self.ar ) 381 | 382 | class DNSQuestion(object): 383 | 384 | @classmethod 385 | def parse(cls,buffer): 386 | qname = buffer.decode_name() 387 | qtype,qclass = buffer.unpack("!HH") 388 | return cls(qname,qtype,qclass) 389 | 390 | def __init__(self,qname=[],qtype=1,qclass=1): 391 | self.qname = qname 392 | self.qtype = qtype 393 | self.qclass = qclass 394 | 395 | def set_qname(self,qname): 396 | if isinstance(qname,DNSLabel): 397 | self._qname = qname 398 | else: 399 | self._qname = DNSLabel(qname) 400 | 401 | def get_qname(self): 402 | return self._qname 403 | 404 | qname = property(get_qname,set_qname) 405 | 406 | def pack(self,buffer): 407 | buffer.encode_name(self.qname) 408 | buffer.pack("!HH",self.qtype,self.qclass) 409 | 410 | def __str__(self): 411 | return "" % ( 412 | self.qname, QTYPE[self.qtype], CLASS[self.qclass]) 413 | 414 | class EDNSOption(object): 415 | 416 | def __init__(self,code,data): 417 | self.code = code 418 | self.data = data 419 | 420 | def __str__(self): 421 | return "" % (self.code,self.data) 422 | 423 | class RR(object): 424 | 425 | @classmethod 426 | def parse(cls,buffer): 427 | rname = buffer.decode_name() 428 | rtype,rclass,ttl,rdlength = buffer.unpack("!HHIH") 429 | if rtype == QTYPE.OPT: 430 | options = [] 431 | option_buffer = Buffer(buffer.get(rdlength)) 432 | while option_buffer.remaining() > 4: 433 | code,length = option_buffer.unpack("!HH") 434 | data = option_buffer.get(length) 435 | options.append(EDNSOption(code,data)) 436 | rdata = options 437 | else: 438 | if rdlength: 439 | rdata = RDMAP.get(QTYPE[rtype],RD).parse(buffer,rdlength) 440 | else: 441 | rdata = '' 442 | return cls(rname,rtype,rclass,ttl,rdata) 443 | 444 | def __init__(self,rname=[],rtype=1,rclass=1,ttl=0,rdata=None): 445 | self.rname = rname 446 | self.rtype = rtype 447 | self.rclass = rclass 448 | self.ttl = ttl 449 | self.rdata = rdata 450 | 451 | def set_rname(self,rname): 452 | if isinstance(rname,DNSLabel): 453 | self._rname = rname 454 | else: 455 | self._rname = DNSLabel(rname) 456 | 457 | def get_rname(self): 458 | return self._rname 459 | 460 | rname = property(get_rname,set_rname) 461 | 462 | def pack(self,buffer): 463 | buffer.encode_name(self.rname) 464 | buffer.pack("!HHI",self.rtype,self.rclass,self.ttl) 465 | rdlength_ptr = buffer.offset 466 | buffer.pack("!H",0) 467 | start = buffer.offset 468 | self.rdata.pack(buffer) 469 | end = buffer.offset 470 | buffer.update(rdlength_ptr,"!H",end-start) 471 | 472 | def __str__(self): 473 | return "" % ( 474 | self.rname, QTYPE[self.rtype], CLASS[self.rclass], 475 | self.ttl, self.rdata) 476 | 477 | class RD(object): 478 | 479 | @classmethod 480 | def parse(cls,buffer,length): 481 | data = buffer.get(length) 482 | return cls(data) 483 | 484 | def __init__(self,data=""): 485 | self.data = data 486 | 487 | def pack(self,buffer): 488 | buffer.append(self.data) 489 | 490 | def __str__(self): 491 | return '%s' % self.data 492 | 493 | class TXT(RD): 494 | 495 | @classmethod 496 | def parse(cls,buffer,length): 497 | (txtlength,) = buffer.unpack("!B") 498 | # First byte is TXT length (not in RFC?) 499 | if txtlength < length: 500 | data = buffer.get(txtlength) 501 | else: 502 | raise DNSError("Invalid TXT record: length (%d) > RD length (%d)" % 503 | (txtlength,length)) 504 | return cls(data) 505 | 506 | def pack(self,buffer): 507 | if len(self.data) > 255: 508 | raise DNSError("TXT record too long: %s" % self.data) 509 | buffer.pack("!B",len(self.data)) 510 | buffer.append(self.data) 511 | 512 | class A(RD): 513 | 514 | @classmethod 515 | def parse(cls,buffer,length): 516 | ip = buffer.unpack("!BBBB") 517 | data = "%d.%d.%d.%d" % ip 518 | return cls(data) 519 | 520 | def pack(self,buffer): 521 | buffer.pack("!BBBB",*map(int,self.data.split("."))) 522 | 523 | class AAAA(RD): 524 | 525 | """ 526 | Basic support for AAAA record - assumes IPv6 address data is presented 527 | as a simple tuple of 16 bytes 528 | """ 529 | 530 | @classmethod 531 | def parse(cls,buffer,length): 532 | data = buffer.unpack("!16B") 533 | return cls(data) 534 | 535 | def pack(self,buffer): 536 | buffer.pack("!16B",*self.data) 537 | 538 | def __str__(self): 539 | hexes = map('{:02x}'.format, self.data) 540 | return ':'.join([''.join(hexes[i:i+2]) for i in xrange(0, len(hexes), 2)]) 541 | 542 | class MX(RD): 543 | 544 | @classmethod 545 | def parse(cls,buffer,length): 546 | (preference,) = buffer.unpack("!H") 547 | mx = buffer.decode_name() 548 | return cls(mx,preference) 549 | 550 | def __init__(self,mx=[],preference=10): 551 | self.mx = mx 552 | self.preference = preference 553 | 554 | def set_mx(self,mx): 555 | if isinstance(mx,DNSLabel): 556 | self._mx = mx 557 | else: 558 | self._mx = DNSLabel(mx) 559 | 560 | def get_mx(self): 561 | return self._mx 562 | 563 | mx = property(get_mx,set_mx) 564 | 565 | def pack(self,buffer): 566 | buffer.pack("!H",self.preference) 567 | buffer.encode_name(self.mx) 568 | 569 | def __str__(self): 570 | return "%d:%s" % (self.preference,self.mx) 571 | 572 | class CNAME(RD): 573 | 574 | @classmethod 575 | def parse(cls,buffer,length): 576 | label = buffer.decode_name() 577 | return cls(label) 578 | 579 | def __init__(self,label=[]): 580 | self.label = label 581 | 582 | def set_label(self,label): 583 | if isinstance(label,DNSLabel): 584 | self._label = label 585 | else: 586 | self._label = DNSLabel(label) 587 | 588 | def get_label(self): 589 | return self._label 590 | 591 | label = property(get_label,set_label) 592 | 593 | def pack(self,buffer): 594 | buffer.encode_name(self.label) 595 | 596 | def __str__(self): 597 | return "%s" % (self.label) 598 | 599 | class PTR(CNAME): 600 | pass 601 | 602 | class NS(CNAME): 603 | pass 604 | 605 | class SOA(RD): 606 | 607 | @classmethod 608 | def parse(cls,buffer,length): 609 | mname = buffer.decode_name() 610 | rname = buffer.decode_name() 611 | times = buffer.unpack("!IIIII") 612 | return cls(mname,rname,times) 613 | 614 | def __init__(self,mname=[],rname=[],times=None): 615 | self.mname = mname 616 | self.rname = rname 617 | self.times = times or (0,0,0,0,0) 618 | 619 | def set_mname(self,mname): 620 | if isinstance(mname,DNSLabel): 621 | self._mname = mname 622 | else: 623 | self._mname = DNSLabel(mname) 624 | 625 | def get_mname(self): 626 | return self._mname 627 | 628 | mname = property(get_mname,set_mname) 629 | 630 | def set_rname(self,rname): 631 | if isinstance(rname,DNSLabel): 632 | self._rname = rname 633 | else: 634 | self._rname = DNSLabel(rname) 635 | 636 | def get_rname(self): 637 | return self._rname 638 | 639 | rname = property(get_rname,set_rname) 640 | 641 | def pack(self,buffer): 642 | buffer.encode_name(self.mname) 643 | buffer.encode_name(self.rname) 644 | buffer.pack("!IIIII", *self.times) 645 | 646 | def __str__(self): 647 | return "%s:%s:%s" % (self.mname,self.rname,":".join(map(str,self.times))) 648 | 649 | class NAPTR(RD): 650 | 651 | def __init__(self,order,preference,flags,service,regexp,replacement=None): 652 | self.order = order 653 | self.preference = preference 654 | self.flags = flags 655 | self.service = service 656 | self.regexp = regexp 657 | self.replacement = replacement or DNSLabel([]) 658 | 659 | @classmethod 660 | def parse(cls, buffer, length): 661 | order, preference = buffer.unpack('!HH') 662 | (length,) = buffer.unpack('!B') 663 | flags = buffer.get(length) 664 | (length,) = buffer.unpack('!B') 665 | service = buffer.get(length) 666 | (length,) = buffer.unpack('!B') 667 | regexp = buffer.get(length) 668 | replacement = buffer.decode_name() 669 | return cls(order, preference, flags, service, regexp, replacement) 670 | 671 | def pack(self, buffer): 672 | buffer.pack('!HH', self.order, self.preference) 673 | buffer.pack('!B', len(self.flags)) 674 | buffer.append(self.flags) 675 | buffer.pack('!B', len(self.service)) 676 | buffer.append(self.service) 677 | buffer.pack('!B', len(self.regexp)) 678 | buffer.append(self.regexp) 679 | buffer.encode_name(self.replacement) 680 | 681 | def __str__(self): 682 | return '%d %d "%s" "%s" "%s" %s' %( 683 | self.order,self.preference,self.flags, 684 | self.service,self.regexp,self.replacement or '.' 685 | ) 686 | 687 | RDMAP = { 'CNAME':CNAME, 'A':A, 'AAAA':AAAA, 'TXT':TXT, 'MX':MX, 688 | 'PTR':PTR, 'SOA':SOA, 'NS':NS, 'NAPTR': NAPTR} 689 | 690 | def test_unpack(s): 691 | """ 692 | Test decoding with sample DNS packets captured from Wireshark 693 | 694 | >>> def unpack(s): 695 | ... d = DNSRecord.parse(s.decode('hex')) 696 | ... print d 697 | 698 | Standard query A www.google.com 699 | >>> unpack('d5ad010000010000000000000377777706676f6f676c6503636f6d0000010001') 700 | 701 | 702 | 703 | Standard query response CNAME www.l.google.com A 66.249.91.104 A 66.249.91.99 A 66.249.91.103 A 66.249.91.147 704 | >>> unpack('d5ad818000010005000000000377777706676f6f676c6503636f6d0000010001c00c0005000100000005000803777777016cc010c02c0001000100000005000442f95b68c02c0001000100000005000442f95b63c02c0001000100000005000442f95b67c02c0001000100000005000442f95b93') 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | Standard query MX google.com 714 | >>> unpack('95370100000100000000000006676f6f676c6503636f6d00000f0001') 715 | 716 | 717 | 718 | Standard query response MX 10 smtp2.google.com MX 10 smtp3.google.com MX 10 smtp4.google.com MX 10 smtp1.google.com 719 | >>> unpack('95378180000100040000000006676f6f676c6503636f6d00000f0001c00c000f000100000005000a000a05736d747032c00cc00c000f000100000005000a000a05736d747033c00cc00c000f000100000005000a000a05736d747034c00cc00c000f000100000005000a000a05736d747031c00c') 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | Standard query PTR 103.91.249.66.in-addr.arpa 728 | >>> unpack('b38001000001000000000000033130330239310332343902363607696e2d61646472046172706100000c0001') 729 | 730 | 731 | 732 | Standard query response PTR ik-in-f103.google.com 733 | >>> unpack('b38081800001000100000000033130330239310332343902363607696e2d61646472046172706100000c0001c00c000c00010000000500170a696b2d696e2d6631303306676f6f676c6503636f6d00') 734 | 735 | 736 | 737 | 738 | Standard query TXT google.com 739 | 740 | >>> unpack('c89f0100000100000000000006676f6f676c6503636f6d0000100001') 741 | 742 | 743 | 744 | Standard query response TXT 745 | >>> unpack('c89f8180000100010000000006676f6f676c6503636f6d0000100001c00c0010000100000005002a29763d7370663120696e636c7564653a5f6e6574626c6f636b732e676f6f676c652e636f6d207e616c6c') 746 | 747 | 748 | 749 | 750 | Standard query SOA google.com 751 | >>> unpack('28fb0100000100000000000006676f6f676c6503636f6d0000060001') 752 | 753 | 754 | 755 | Standard query response SOA ns1.google.com 756 | >>> unpack('28fb8180000100010000000006676f6f676c6503636f6d0000060001c00c00060001000000050026036e7331c00c09646e732d61646d696ec00c77b1566d00001c2000000708001275000000012c') 757 | 758 | 759 | 760 | 761 | Standard query response NAPTR sip2sip.info 762 | >>> unpack('740481800001000300000000077369703273697004696e666f0000230001c00c0023000100000c940027001e00640173075349502b44325500045f736970045f756470077369703273697004696e666f00c00c0023000100000c940027000a00640173075349502b44325400045f736970045f746370077369703273697004696e666f00c00c0023000100000c94002900140064017308534950532b44325400055f73697073045f746370077369703273697004696e666f00') 763 | 764 | 765 | 766 | 767 | 768 | 769 | Standard query response NAPTR 0.0.0.0.1.1.1.3.9.3.0.1.8.7.8.e164.org 770 | >>> unpack('aef0818000010001000000000130013001300130013101310131013301390133013001310138013701380465313634036f72670000230001c00c002300010000a6a300320064000a0175074532552b53495022215e5c2b3f282e2a2924217369703a5c5c31406677642e70756c7665722e636f6d2100') 771 | 772 | 773 | 774 | """ 775 | pass 776 | 777 | 778 | if __name__ == '__main__': 779 | import doctest 780 | doctest.testmod(optionflags=doctest.ELLIPSIS) 781 | --------------------------------------------------------------------------------