cft_code.dnslib package¶
Submodules¶
cft_code.dnslib.bimap module¶
Bimap - bidirectional mapping between code/value
-
class
cft_code.dnslib.bimap.
Bimap
(name, forward, error=<type 'exceptions.KeyError'>)¶ Bases:
object
Bi-directional mapping between code/text.
Initialised using:
name: Used for exceptions dict: Dict mapping from code (numeric) to text error: Error type to raise if key not foundThe class provides:
- A ‘forward’ map (code->text) which is accessed through __getitem__ (bimap[code])
- A ‘reverse’ map (code>value) which is accessed through __getattr__ (bimap.text)
- A ‘get’ method which does a forward lookup (code->text) and returns a textual version of code if there is no explicit mapping (or default provided)
>>> class TestError(Exception): ... pass
>>> TEST = Bimap('TEST',{1:'A', 2:'B', 3:'C'},TestError) >>> TEST[1] 'A' >>> TEST.A 1 >>> TEST.X Traceback (most recent call last): ... TestError: TEST: Invalid reverse lookup: [X] >>> TEST[99] Traceback (most recent call last): ... TestError: TEST: Invalid forward lookup: [99] >>> TEST.get(99) '99'
-
get
(k, default=None)¶
-
exception
cft_code.dnslib.bimap.
BimapError
¶ Bases:
exceptions.Exception
cft_code.dnslib.bit module¶
Some basic bit mainpulation utilities
-
cft_code.dnslib.bit.
binary
(n, count=16, reverse=False)¶ Display n in binary (only difference from built-in bin is that this function returns a fixed width string and can optionally be reversed
>>> binary(6789) '0001101010000101' >>> binary(6789,8) '10000101' >>> binary(6789,reverse=True) '1010000101011000'
-
cft_code.dnslib.bit.
get_bits
(data, offset, bits=1)¶ Get specified bits from integer
>>> bin(get_bits(0b0011100,2)) '0b1' >>> bin(get_bits(0b0011100,0,4)) '0b1100'
-
cft_code.dnslib.bit.
hexdump
(src, length=16, prefix='')¶ Print hexdump of string
>>> print(hexdump(b"abcd" * 4)) 0000 61 62 63 64 61 62 63 64 61 62 63 64 61 62 63 64 abcdabcd abcdabcd
>>> print(hexdump(bytearray(range(48)))) 0000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ........ ........ 0010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f ........ ........ 0020 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f !"#$%&' ()*+,-./
-
cft_code.dnslib.bit.
set_bits
(data, value, offset, bits=1)¶ Set specified bits in integer
>>> bin(set_bits(0,0b1010,0,4)) '0b1010' >>> bin(set_bits(0,0b1010,3,4)) '0b1010000'
cft_code.dnslib.buffer module¶
Buffer - simple data buffer
-
class
cft_code.dnslib.buffer.
Buffer
(data='')¶ Bases:
object
A simple data buffer - supports packing/unpacking in struct format
# Needed for Python 2/3 doctest compatibility >>> def p(s): … if not isinstance(s,str): … return s.decode() … return s
>>> b = Buffer() >>> b.pack("!BHI",1,2,3) >>> b.offset 7 >>> b.append(b"0123456789") >>> b.offset 17 >>> p(b.hex()) '0100020000000330313233343536373839' >>> b.offset = 0 >>> b.unpack("!BHI") (1, 2, 3) >>> bytearray(b.get(5)) bytearray(b'01234') >>> bytearray(b.get(5)) bytearray(b'56789') >>> b.update(7,"2s",b"xx") >>> b.offset = 7 >>> bytearray(b.get(5)) bytearray(b'xx234')
-
append
(s)¶ Append s to end of data & increment offset
-
get
(length)¶ Gen len bytes at current offset (& increment offset)
-
hex
()¶ Return data as hex string
-
pack
(fmt, *args)¶ Pack data at end of data according to fmt (from struct) & increment offset
-
remaining
()¶ Return bytes remaining
-
unpack
(fmt)¶ Unpack data at current offset according to fmt (from struct)
-
update
(ptr, fmt, *args)¶ Modify data at offset ptr
-
-
exception
cft_code.dnslib.buffer.
BufferError
¶ Bases:
exceptions.Exception
cft_code.dnslib.client module¶
DNS Client - DiG-like CLI utility.
Mostly useful for testing. Can optionally compare results from two nameservers (–diff) or compare results against DiG (–dig).
Usage: python -m dnslib.client [options|–help]
See –help for usage.
cft_code.dnslib.digparser module¶
digparser¶
Encode/decode DNS packets from DiG textual representation. Parses question (if present: +qr flag) & answer sections and returns list of DNSRecord objects.
Unsupported RR types are skipped (this is different from the packet parser which will store and encode the RDATA as a binary blob)
>>> dig = os.path.join(os.path.dirname(__file__),"test","dig","google.com-A.dig")
>>> with open(dig) as f:
... l = DigParser(f)
... for record in l:
... print('---')
... print(repr(record))
---
<DNS Header: id=0x5c9a type=QUERY opcode=QUERY flags=RD rcode='NOERROR' q=1 a=0 ns=0 ar=0>
<DNS Question: 'google.com.' qtype=A qclass=IN>
---
<DNS Header: id=0x5c9a type=RESPONSE opcode=QUERY flags=RD,RA rcode='NOERROR' q=1 a=16 ns=0 ar=0>
<DNS Question: 'google.com.' qtype=A qclass=IN>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.183'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.152'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.172'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.177'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.157'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.153'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.182'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.168'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.178'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.162'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.187'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.167'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.148'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.173'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.158'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.163'>
>>> dig = os.path.join(os.path.dirname(__file__),"test","dig","google.com-ANY.dig")
>>> with open(dig) as f:
... l = DigParser(f)
... for record in l:
... print('---')
... print(repr(record))
---
<DNS Header: id=0xfc6b type=QUERY opcode=QUERY flags=RD rcode='NOERROR' q=1 a=0 ns=0 ar=0>
<DNS Question: 'google.com.' qtype=ANY qclass=IN>
---
<DNS Header: id=0xa6fc type=QUERY opcode=QUERY flags=RD rcode='NOERROR' q=1 a=0 ns=0 ar=0>
<DNS Question: 'google.com.' qtype=ANY qclass=IN>
---
<DNS Header: id=0xa6fc type=RESPONSE opcode=QUERY flags=RD,RA rcode='NOERROR' q=1 a=28 ns=0 ar=0>
<DNS Question: 'google.com.' qtype=ANY qclass=IN>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.183'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.152'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.172'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.177'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.157'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.153'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.182'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.168'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.178'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.162'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.187'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.167'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.148'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.173'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.158'>
<DNS RR: 'google.com.' rtype=A rclass=IN ttl=299 rdata='62.252.169.163'>
<DNS RR: 'google.com.' rtype=NS rclass=IN ttl=21599 rdata='ns4.google.com.'>
<DNS RR: 'google.com.' rtype=MX rclass=IN ttl=599 rdata='50 alt4.aspmx.l.google.com.'>
<DNS RR: 'google.com.' rtype=NS rclass=IN ttl=21599 rdata='ns2.google.com.'>
<DNS RR: 'google.com.' rtype=MX rclass=IN ttl=599 rdata='10 aspmx.l.google.com.'>
<DNS RR: 'google.com.' rtype=NS rclass=IN ttl=21599 rdata='ns3.google.com.'>
<DNS RR: 'google.com.' rtype=SOA rclass=IN ttl=21599 rdata='ns1.google.com. dns-admin.google.com. 2014021800 7200 1800 1209600 300'>
<DNS RR: 'google.com.' rtype=MX rclass=IN ttl=599 rdata='40 alt3.aspmx.l.google.com.'>
<DNS RR: 'google.com.' rtype=MX rclass=IN ttl=599 rdata='20 alt1.aspmx.l.google.com.'>
<DNS RR: 'google.com.' rtype=TYPE257 rclass=IN ttl=21599 rdata='0005697373756573796d616e7465632e636f6d'>
<DNS RR: 'google.com.' rtype=TXT rclass=IN ttl=3599 rdata='"v=spf1 include:_spf.google.com ip4:216.73.93.70/31 ip4:216.73.93.72/31 ~all"'>
<DNS RR: 'google.com.' rtype=MX rclass=IN ttl=599 rdata='30 alt2.aspmx.l.google.com.'>
<DNS RR: 'google.com.' rtype=NS rclass=IN ttl=21599 rdata='ns1.google.com.'>
cft_code.dnslib.dns module¶
DNS - main dnslib module
Contains core DNS packet handling code
-
class
cft_code.dnslib.dns.
A
(data)¶ Bases:
cft_code.dnslib.dns.RD
-
data
¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
-
class
cft_code.dnslib.dns.
AAAA
(data)¶ Bases:
cft_code.dnslib.dns.RD
Basic support for AAAA record - accepts IPv6 address data as either a tuple of 16 bytes or in text format
-
data
¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
-
class
cft_code.dnslib.dns.
CNAME
(label=None)¶ Bases:
cft_code.dnslib.dns.RD
-
attrs
= ('label',)¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
get_label
()¶
-
label
¶
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
set_label
(label)¶
-
-
exception
cft_code.dnslib.dns.
DNSError
¶ Bases:
exceptions.Exception
-
class
cft_code.dnslib.dns.
DNSHeader
(id=None, bitmap=None, q=0, a=0, auth=0, ar=0, **args)¶ Bases:
object
DNSHeader section
-
a
¶
-
aa
¶
-
ar
¶
-
auth
¶
-
bitmap
¶
-
get_aa
()¶
-
get_opcode
()¶
-
get_qr
()¶
-
get_ra
()¶
-
get_rcode
()¶
-
get_rd
()¶
-
get_tc
()¶
-
id
¶
-
opcode
¶
-
pack
(buffer)¶
-
classmethod
parse
(buffer)¶ Implements parse interface
-
q
¶
-
qr
¶
-
ra
¶
-
rcode
¶
-
rd
¶
-
set_aa
(val)¶
-
set_opcode
(val)¶
-
set_qr
(val)¶
-
set_ra
(val)¶
-
set_rcode
(val)¶
-
set_rd
(val)¶
-
set_tc
(val)¶
-
tc
¶
-
toZone
()¶
-
-
class
cft_code.dnslib.dns.
DNSKEY
(flags, protocol, algorithm, key)¶ Bases:
cft_code.dnslib.dns.RD
-
algorithm
¶
-
attrs
= ('flags', 'protocol', 'algorithm', 'key')¶
-
flags
¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
protocol
¶
-
-
class
cft_code.dnslib.dns.
DNSQuestion
(qname=None, qtype=1, qclass=1)¶ Bases:
object
DNSQuestion section
-
get_qname
()¶
-
pack
(buffer)¶
-
classmethod
parse
(buffer)¶
-
qname
¶
-
set_qname
(qname)¶
-
toZone
()¶
-
-
class
cft_code.dnslib.dns.
DNSRecord
(header=None, questions=None, rr=None, q=None, a=None, auth=None, ar=None)¶ Bases:
object
Main DNS class - corresponds to DNS packet & comprises DNSHeader, DNSQuestion and RR sections (answer,ns,ar)
>>> d = DNSRecord() >>> d.add_question(DNSQuestion("abc.com")) # Or DNSRecord.question("abc.com") >>> d.add_answer(RR("abc.com",QTYPE.CNAME,ttl=60,rdata=CNAME("ns.abc.com"))) >>> d.add_auth(RR("abc.com",QTYPE.SOA,ttl=60,rdata=SOA("ns.abc.com","admin.abc.com",(20140101,3600,3600,3600,3600)))) >>> d.add_ar(RR("ns.abc.com",ttl=60,rdata=A("1.2.3.4"))) >>> print(d) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 60 IN CNAME ns.abc.com. ;; AUTHORITY SECTION: abc.com. 60 IN SOA ns.abc.com. admin.abc.com. 20140101 3600 3600 3600 3600 ;; ADDITIONAL SECTION: ns.abc.com. 60 IN A 1.2.3.4 >>> str(d) == str(DNSRecord.parse(d.pack())) True
-
a
¶
-
add_answer
(*rr)¶ Add answer(s)
>>> q = DNSRecord.question("abc.com") >>> a = q.reply() >>> a.add_answer(*RR.fromZone("abc.com A 1.2.3.4")) >>> print(a) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 0 IN A 1.2.3.4
-
add_ar
(*ar)¶ Add additional records
>>> q = DNSRecord.question("abc.com") >>> a = q.reply() >>> a.add_answer(*RR.fromZone("abc.com 60 CNAME x.abc.com")) >>> a.add_ar(*RR.fromZone("x.abc.com 3600 A 1.2.3.4")) >>> print(a) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 60 IN CNAME x.abc.com. ;; ADDITIONAL SECTION: x.abc.com. 3600 IN A 1.2.3.4
-
add_auth
(*auth)¶ Add authority records
>>> q = DNSRecord.question("abc.com") >>> a = q.reply() >>> a.add_answer(*RR.fromZone("abc.com 60 A 1.2.3.4")) >>> a.add_auth(*RR.fromZone("abc.com 3600 NS nsa.abc.com")) >>> print(a) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 60 IN A 1.2.3.4 ;; AUTHORITY SECTION: abc.com. 3600 IN NS nsa.abc.com.
-
add_question
(*q)¶ Add question(s)
>>> q = DNSRecord() >>> q.add_question(DNSQuestion("abc.com"), ... DNSQuestion("abc.com",QTYPE.MX)) >>> print(q) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: rd; QUERY: 2, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.com. IN A ;abc.com. IN MX
-
diff
(other)¶ Diff records - recursively diff sections (sorting RRs)
-
format
(prefix='', sort=False)¶ Formatted ‘repr’-style representation of record (optionally with prefix and/or sorted RRs)
-
get_a
()¶
-
get_q
()¶
-
pack
()¶ Pack record into binary packet (recursively packs each section into buffer)
>>> q = DNSRecord.question("abc.com") >>> q.header.id = 1234 >>> a = q.replyZone("abc.com A 1.2.3.4") >>> a.header.aa = 0 >>> pkt = a.pack() >>> print(DNSRecord.parse(pkt)) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1234 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 0 IN A 1.2.3.4
-
classmethod
parse
(packet)¶ Parse DNS packet data and return DNSRecord instance Recursively parses sections (calling appropriate parse method)
-
q
¶
-
classmethod
question
(qname, qtype='A', qclass='IN')¶ Shortcut to create question
>>> q = DNSRecord.question("www.google.com") >>> print(q) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.google.com. IN A
>>> q = DNSRecord.question("www.google.com","NS") >>> print(q) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;www.google.com. IN NS
-
reply
(ra=1, aa=1)¶ Create skeleton reply packet
>>> q = DNSRecord.question("abc.com") >>> a = q.reply() >>> a.add_answer(RR("abc.com",QTYPE.A,rdata=A("1.2.3.4"),ttl=60)) >>> print(a) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 60 IN A 1.2.3.4
-
replyZone
(zone, ra=1, aa=1)¶ Create reply with response data in zone-file format >>> q = DNSRecord.question(“abc.com”) >>> a = q.replyZone(“abc.com 60 A 1.2.3.4”) >>> print(a) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: … ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 60 IN A 1.2.3.4
-
send
(dest, port=53, tcp=False, timeout=None, ipv6=False)¶ Send packet to nameserver and return response
-
set_header_qa
()¶ Reset header q/a/auth/ar counts to match numver of records (normally done transparently)
-
short
()¶ Just return RDATA
-
toZone
(prefix='')¶ Formatted ‘DiG’ (zone) style output (with optional prefix)
-
truncate
()¶ Return truncated copy of DNSRecord (with TC flag set) (removes all Questions & RRs and just returns header)
>>> q = DNSRecord.question("abc.com") >>> a = q.reply() >>> a.add_answer(*RR.fromZone('abc.com IN TXT %s' % ('x' * 255))) >>> a.add_answer(*RR.fromZone('abc.com IN TXT %s' % ('x' * 255))) >>> a.add_answer(*RR.fromZone('abc.com IN TXT %s' % ('x' * 255))) >>> len(a.pack()) 829 >>> t = a.truncate() >>> print(t) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa tc rd ra; QUERY: 0, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
-
-
class
cft_code.dnslib.dns.
EDNS0
(rname=None, rtype=41, ext_rcode=0, version=0, flags='', udp_len=0, opts=None)¶ Bases:
cft_code.dnslib.dns.RR
ENDS0 pseudo-record
Wrapper around the ENDS0 support in RR to make it more convenient to create EDNS0 pseudo-record - this just makes it easier to specify the EDNS0 parameters directly
EDNS flags should be passed as a space separated string of options (currently only ‘do’ is supported)
>>> EDNS0("abc.com",flags="do",udp_len=2048,version=1) <DNS OPT: edns_ver=1 do=1 ext_rcode=0 udp_len=2048> >>> print(_) ;OPT PSEUDOSECTION ;EDNS: version: 1, flags: do; udp: 2048 >>> opt = EDNS0("abc.com",flags="do",ext_rcode=1,udp_len=2048,version=1,opts=[EDNSOption(1,b'abcd')]) >>> opt <DNS OPT: edns_ver=1 do=1 ext_rcode=1 udp_len=2048> <EDNS Option: Code=1 Data='61626364'> >>> print(opt) ;OPT PSEUDOSECTION ;EDNS: version: 1, flags: do; udp: 2048 ;EDNS: code: 1; data: 61626364 >>> r = DNSRecord.question("abc.com").replyZone("abc.com A 1.2.3.4") >>> r.add_ar(opt) >>> print(r) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 0 IN A 1.2.3.4 ;; ADDITIONAL SECTION: ;OPT PSEUDOSECTION ;EDNS: version: 1, flags: do; udp: 2048 ;EDNS: code: 1; data: 61626364 >>> DNSRecord.parse(r.pack()) == r True
-
class
cft_code.dnslib.dns.
EDNSOption
(code, data)¶ Bases:
object
EDNSOption pseudo-section
Very rudimentary support for EDNS0 options however this has not been tested due to a lack of data (anyone wanting to improve support or provide test data please raise an issue)
>>> EDNSOption(1,b"1234") <EDNS Option: Code=1 Data='31323334'> >>> EDNSOption(99999,b"1234") Traceback (most recent call last): ... ValueError: Attribute 'code' must be between 0-65535 [99999] >>> EDNSOption(1,None) Traceback (most recent call last): ... ValueError: Attribute 'data' must be instance of ...
-
code
¶
-
data
¶
-
pack
(buffer)¶
-
toZone
()¶
-
-
class
cft_code.dnslib.dns.
MX
(label=None, preference=10)¶ Bases:
cft_code.dnslib.dns.RD
-
attrs
= ('preference', 'label')¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
get_label
()¶
-
label
¶
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
preference
¶
-
set_label
(label)¶
-
-
class
cft_code.dnslib.dns.
NAPTR
(order, preference, flags, service, regexp, replacement=None)¶ Bases:
cft_code.dnslib.dns.RD
-
attrs
= ('order', 'preference', 'flags', 'service', 'regexp', 'replacement')¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
get_replacement
()¶
-
order
¶
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
preference
¶
-
replacement
¶
-
set_replacement
(replacement)¶
-
-
class
cft_code.dnslib.dns.
NS
(label=None)¶ Bases:
cft_code.dnslib.dns.CNAME
-
class
cft_code.dnslib.dns.
PTR
(label=None)¶ Bases:
cft_code.dnslib.dns.CNAME
-
class
cft_code.dnslib.dns.
RD
(data='')¶ Bases:
object
Base RD object - also used as placeholder for unknown RD types
To create a new RD type subclass this and add to RDMAP (below)
Subclass should implement (as a mininum):
parse (parse from packet data) __init__ (create class) __repr__ (return in zone format) fromZone (create from zone format)
(toZone uses __repr__ by default)
Unknown rdata types default to RD and store rdata as a binary blob (this allows round-trip encoding/decoding)
-
attrs
= ('data',)¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
toZone
()¶
-
-
class
cft_code.dnslib.dns.
RR
(rname=None, rtype=1, rclass=1, ttl=0, rdata=None)¶ Bases:
object
DNS Resource Record Contains RR header and RD (resource data) instance
-
classmethod
fromZone
(zone, origin='', ttl=0)¶ Parse RR data from zone file and return list of RRs
-
get_rname
()¶
-
pack
(buffer)¶
-
classmethod
parse
(buffer)¶
-
rclass
¶
-
rdlength
¶
-
rname
¶
-
rtype
¶
-
set_rname
(rname)¶
-
toZone
()¶
-
ttl
¶
-
classmethod
-
class
cft_code.dnslib.dns.
RRSIG
(covered, algorithm, labels, orig_ttl, sig_exp, sig_inc, key_tag, name, sig)¶ Bases:
cft_code.dnslib.dns.RD
-
algorithm
¶
-
attrs
= ('covered', 'algorithm', 'labels', 'orig_ttl', 'sig_exp', 'sig_inc', 'key_tag', 'name', 'sig')¶
-
covered
¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
key_tag
¶
-
labels
¶
-
orig_ttl
¶
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
sig_exp
¶
-
sig_inc
¶
-
-
class
cft_code.dnslib.dns.
SOA
(mname=None, rname=None, times=None)¶ Bases:
cft_code.dnslib.dns.RD
-
attrs
= ('mname', 'rname', 'times')¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
get_mname
()¶
-
get_rname
()¶
-
mname
¶
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
rname
¶
-
set_mname
(mname)¶
-
set_rname
(rname)¶
-
times
¶
-
-
class
cft_code.dnslib.dns.
SRV
(priority=0, weight=0, port=0, target=None)¶ Bases:
cft_code.dnslib.dns.RD
-
attrs
= ('priority', 'weight', 'port', 'target')¶
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
get_target
()¶
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
port
¶
-
priority
¶
-
set_target
(target)¶
-
target
¶
-
weight
¶
-
-
class
cft_code.dnslib.dns.
TXT
(data)¶ Bases:
cft_code.dnslib.dns.RD
DNS TXT record. Pass in either a single string, or a tuple/list of strings.
>>> TXT('txtvers=1') "txtvers=1" >>> TXT(('txtvers=1',)) "txtvers=1" >>> TXT(['txtvers=1',]) "txtvers=1" >>> TXT(['txtvers=1','swver=2.5']) "txtvers=1","swver=2.5" >>> a = DNSRecord() >>> a.add_answer(*RR.fromZone('example.com 60 IN TXT "txtvers=1"')) >>> a.add_answer(*RR.fromZone('example.com 120 IN TXT "txtvers=1" "swver=2.3"')) >>> print(a) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: rd; QUERY: 0, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0 ;; ANSWER SECTION: example.com. 60 IN TXT "txtvers=1" example.com. 120 IN TXT "txtvers=1" "swver=2.3"
-
classmethod
fromZone
(rd, origin=None)¶ Create new record from zone format data RD is a list of strings parsed from DiG output
-
pack
(buffer)¶ Pack record into buffer
-
classmethod
parse
(buffer, length)¶ Unpack from buffer
-
toZone
()¶
-
classmethod
-
class
cft_code.dnslib.dns.
ZoneParser
(zone, origin='', ttl=0)¶ Zone file parser
>>> z = ZoneParser("www.example.com. 60 IN A 1.2.3.4") >>> list(z.parse()) [<DNS RR: 'www.example.com.' rtype=A rclass=IN ttl=60 rdata='1.2.3.4'>]
-
expect
(expect)¶
-
parse
()¶
-
parse_label
(label)¶
-
parse_rr
(rr)¶
-
-
cft_code.dnslib.dns.
label
(label, origin=None)¶
-
cft_code.dnslib.dns.
parse_time
(s)¶ Parse time spec with optional s/m/h/d/w suffix
cft_code.dnslib.fixedresolver module¶
- FixedResolver - example resolver which responds with fixed response
- to all requests
cft_code.dnslib.intercept module¶
- InterceptResolver - proxy requests to upstream server
- (optionally intercepting)
-
class
cft_code.dnslib.intercept.
InterceptResolver
(address, port, ttl, intercept, skip, nxdomain, timeout=0)¶ Bases:
dnslib.server.BaseResolver
Intercepting resolver
Proxy requests to upstream server optionally intercepting requests matching local records
-
resolve
(request, handler)¶ Example resolver - respond to all requests with NXDOMAIN
-
cft_code.dnslib.label module¶
DNSLabel/DNSBuffer - DNS label handling & encoding/decoding
-
class
cft_code.dnslib.label.
DNSBuffer
(data='')¶ Bases:
dnslib.buffer.Buffer
Extends Buffer to provide DNS name encoding/decoding (with caching)
# Needed for Python 2/3 doctest compatibility >>> def p(s): … if not isinstance(s,str): … return s.decode() … return s
>>> b = DNSBuffer() >>> b.encode_name(b'aaa.bbb.ccc.') >>> len(b) 13 >>> b.encode_name(b'aaa.bbb.ccc.') >>> len(b) 15 >>> b.encode_name(b'xxx.yyy.zzz') >>> len(b) 28 >>> b.encode_name(b'zzz.xxx.bbb.ccc.') >>> len(b) 38 >>> b.encode_name(b'aaa.xxx.bbb.ccc') >>> len(b) 44 >>> b.offset = 0 >>> print(b.decode_name()) aaa.bbb.ccc. >>> print(b.decode_name()) aaa.bbb.ccc. >>> print(b.decode_name()) xxx.yyy.zzz. >>> print(b.decode_name()) zzz.xxx.bbb.ccc. >>> print(b.decode_name()) aaa.xxx.bbb.ccc.
>>> b = DNSBuffer() >>> b.encode_name([b'a.aa',b'b.bb',b'c.cc']) >>> b.offset = 0 >>> len(b.decode_name().label) 3
>>> b = DNSBuffer() >>> b.encode_name_nocompress(b'aaa.bbb.ccc.') >>> len(b) 13 >>> b.encode_name_nocompress(b'aaa.bbb.ccc.') >>> len(b) 26 >>> b.offset = 0 >>> print(b.decode_name()) aaa.bbb.ccc. >>> print(b.decode_name()) aaa.bbb.ccc.
-
decode_name
(last=-1)¶ Decode label at current offset in buffer (following pointers to cached elements where necessary)
-
encode_name
(name)¶ Encode label and store at end of buffer (compressing cached elements where needed) and store elements in ‘names’ dict
-
encode_name_nocompress
(name)¶ Encode and store label with no compression (needed for RRSIG)
-
-
class
cft_code.dnslib.label.
DNSLabel
(label)¶ Bases:
object
Container for DNS label
Supports IDNA encoding for unicode domain names
>>> l1 = DNSLabel("aaa.bbb.ccc.") >>> l2 = DNSLabel([b"aaa",b"bbb",b"ccc"]) >>> l1 == l2 True >>> l3 = DNSLabel("AAA.BBB.CCC") >>> l1 == l3 True >>> l1 == 'AAA.BBB.CCC' True >>> x = { l1 : 1 } >>> x[l1] 1 >>> l1 <DNSLabel: 'aaa.bbb.ccc.'> >>> str(l1) 'aaa.bbb.ccc.' >>> l3 = l1.add("xxx.yyy") >>> l3 <DNSLabel: 'xxx.yyy.aaa.bbb.ccc.'> >>> l3.matchSuffix(l1) True >>> l3.matchSuffix("xxx.yyy.") False >>> l3.stripSuffix("bbb.ccc.") <DNSLabel: 'xxx.yyy.aaa.'> >>> l3.matchGlob("*.[abc]aa.BBB.ccc") True >>> l3.matchGlob("*.[abc]xx.bbb.ccc") False
# Too hard to get unicode doctests to work on Python 3.2 # (works on 3.3) # >>> u1 = DNSLabel(u’u2295.com’) # >>> u1.__str__() == u’u2295.com.’ # True # >>> u1.label == ( b”xn–keh”, b”com” ) # True
-
add
(name)¶ Prepend name to label
-
idna
()¶
-
matchGlob
(pattern)¶
-
matchSuffix
(suffix)¶ Return True if label suffix matches
-
stripSuffix
(suffix)¶ Strip suffix from label
-
-
exception
cft_code.dnslib.label.
DNSLabelError
¶ Bases:
exceptions.Exception
cft_code.dnslib.lex module¶
-
class
cft_code.dnslib.lex.
Lexer
(f, debug=False)¶ Bases:
object
Simple Lexer base class. Provides basic lexer framework and helper functionality (read/peek/pushback etc)
Each state is implemented using a method (lexXXXX) which should match a single token and return a (token,lexYYYY) tuple, with lexYYYY representing the next state. If token is None this is not emitted and if lexYYYY is None or the lexer reaches the end of the input stream the lexer exits.
The ‘parse’ method returns a generator that will return tokens (the class also acts as an iterator)
The default start state is ‘lexStart’
Input can either be a string/bytes or file object.
The approach is based loosely on Rob Pike’s Go lexer presentation (using generators rather than channels).
>>> p = Lexer("a bcd efgh") >>> p.read() 'a' >>> p.read() ' ' >>> p.peek(3) 'bcd' >>> p.read(5) 'bcd e' >>> p.pushback('e') >>> p.read(4) 'efgh'
-
escape
= {'n': '\n', 'r': '\r', 't': '\t'}¶
-
escape_chars
= '\\'¶
-
lexStart
()¶
-
next_token
()¶
-
parse
()¶
-
peek
(n=1)¶
-
pushback
(s)¶
-
read
(n=1)¶
-
readescaped
()¶
-
-
class
cft_code.dnslib.lex.
RandomLexer
(f, debug=False)¶ Bases:
cft_code.dnslib.lex.Lexer
Test lexing from infinite stream.
Extract strings of letters/numbers from /dev/urandom
>>> import itertools,sys >>> if sys.version[0] == '2': ... f = open("/dev/urandom") ... else: ... f = open("/dev/urandom",encoding="ascii",errors="replace") >>> r = RandomLexer(f) >>> i = iter(r) >>> len(list(itertools.islice(i,10))) 10
-
lexAlpha
()¶
-
lexDigits
()¶
-
lexRandom
()¶
-
lexStart
()¶
-
minalpha
= 4¶
-
mindigits
= 3¶
-
-
class
cft_code.dnslib.lex.
WordLexer
(f, debug=False)¶ Bases:
cft_code.dnslib.lex.Lexer
Example lexer which will split input stream into words (respecting quotes)
To emit SPACE tokens: self.spacetok = (‘SPACE’,None) To emit NL tokens: self.nltok = (‘NL’,None)
>>> l = WordLexer(r'abc "def@=\. ghi" jkl') >>> list(l) [('ATOM', 'abc'), ('ATOM', 'def@=. ghi'), ('ATOM', 'jkl')] >>> l = WordLexer(r"1 '2 3 4' 5") >>> list(l) [('ATOM', '1'), ('ATOM', '2 3 4'), ('ATOM', '5')] >>> l = WordLexer("abc# a comment") >>> list(l) [('ATOM', 'abc'), ('COMMENT', 'a comment')]
-
commentchars
= set(['#'])¶
-
lexComment
()¶
-
lexNL
()¶
-
lexQuote
()¶
-
lexSpace
()¶
-
lexStart
()¶
-
lexWord
()¶
-
nlchars
= set(['\n', '\r'])¶
-
nltok
= None¶
-
quotechars
= set(['"', "'"])¶
-
spacechars
= set(['\t', '\x0b', '\x0c', ' '])¶
-
spacetok
= None¶
-
wordchars
= set(['!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'])¶
-
cft_code.dnslib.pan_client module¶
DNS Client - DiG-like CLI utility.
Mostly useful for testing. Can optionally compare results from two nameservers (–diff) or compare results against DiG (–dig).
Usage: python -m dnslib.client [options|–help]
See –help for usage.
-
cft_code.dnslib.pan_client.
pan_dig
(domain)¶
cft_code.dnslib.proxy module¶
-
class
cft_code.dnslib.proxy.
PassthroughDNSHandler
(request, client_address, server)¶ Bases:
dnslib.server.DNSHandler
Modify DNSHandler logic (get_reply method) to send directly to upstream DNS server rather then decoding/encoding packet and passing to Resolver (The request/response packets are still parsed and logged but this is not inline)
-
get_reply
(data)¶
-
-
class
cft_code.dnslib.proxy.
ProxyResolver
(address, port, timeout=0)¶ Bases:
dnslib.server.BaseResolver
Proxy resolver - passes all requests to upstream DNS server and returns response
Note that the request/response will be each be decoded/re-encoded twice:
- Request packet received by DNSHandler and parsed into DNSRecord
- DNSRecord passed to ProxyResolver, serialised back into packet and sent to upstream DNS server
- Upstream DNS server returns response packet which is parsed into DNSRecord
- ProxyResolver returns DNSRecord to DNSHandler which re-serialises this into packet and returns to client
In practice this is actually fairly useful for testing but for a ‘real’ transparent proxy option the DNSHandler logic needs to be modified (see PassthroughDNSHandler)
-
resolve
(request, handler)¶ Example resolver - respond to all requests with NXDOMAIN
-
cft_code.dnslib.proxy.
send_tcp
(data, host, port)¶ Helper function to send/receive DNS TCP request (in/out packets will have prepended TCP length header)
-
cft_code.dnslib.proxy.
send_udp
(data, host, port)¶ Helper function to send/receive DNS UDP request
cft_code.dnslib.ranges module¶
Wrapper around property builtin to restrict attribute to defined integer value range (throws ValueError).
Intended to ensure that values packed with struct are in the correct range
>>> class T(object):
... a = range_property('a',-100,100)
... b = B('b')
... c = H('c')
... d = I('d')
... e = instance_property('e',(int,bool))
>>> t = T()
>>> for i in [0,100,-100]:
... t.a = i
... assert t.a == i
>>> t.a = 101
Traceback (most recent call last):
...
ValueError: Attribute 'a' must be between -100-100 [101]
>>> t.a = -101
Traceback (most recent call last):
...
ValueError: Attribute 'a' must be between -100-100 [-101]
>>> t.a = 'blah'
Traceback (most recent call last):
...
ValueError: Attribute 'a' must be between -100-100 [blah]
>>> t.e = 999
>>> t.e = False
>>> t.e = None
Traceback (most recent call last):
...
ValueError: Attribute 'e' must be instance of ...
>>> check_range("test",123,0,255)
>>> check_range("test",999,0,255)
Traceback (most recent call last):
...
ValueError: Attribute 'test' must be between 0-255 [999]
>>> check_instance("test",123,int)
>>> check_instance("test","xxx",int)
Traceback (most recent call last):
...
ValueError: Attribute 'test' must be instance of ...
-
cft_code.dnslib.ranges.
B
(attr)¶ Unsigned Byte
-
cft_code.dnslib.ranges.
BYTES
(attr)¶
-
cft_code.dnslib.ranges.
H
(attr)¶ Unsigned Short
-
cft_code.dnslib.ranges.
I
(attr)¶ Unsigned Long
-
cft_code.dnslib.ranges.
IP4
(attr)¶
-
cft_code.dnslib.ranges.
IP6
(attr)¶
-
cft_code.dnslib.ranges.
check_bytes
(name, val)¶
-
cft_code.dnslib.ranges.
check_instance
(name, val, types)¶
-
cft_code.dnslib.ranges.
check_range
(name, val, min, max)¶
-
cft_code.dnslib.ranges.
instance_property
(attr, types)¶
-
cft_code.dnslib.ranges.
ntuple_range
(attr, n, min, max)¶
-
cft_code.dnslib.ranges.
range_property
(attr, min, max)¶
cft_code.dnslib.server module¶
DNS server framework - intended to simplify creation of custom resolvers.
Comprises the following components:
- DNSServer - socketserver wrapper (in most cases you should just
- need to pass this an appropriate resolver instance and start in either foreground/background)
- DNSHandler - handler instantiated by DNSServer to handle requests
The ‘handle’ method deals with the sending/receiving packets (handling TCP length prefix) and delegates the protocol handling to ‘get_reply’. This decodes packet, hands off a DNSRecord to the Resolver instance, and encodes the returned DNSRecord.
In most cases you dont need to change DNSHandler unless you need to get hold of the raw protocol data in the Resolver
- DNSLogger - The class provides a default set of logging functions for
- the various stages of the request handled by a DNSServer instance which are enabled/disabled by flags in the ‘log’ class variable.
- Resolver - Instance implementing a ‘resolve’ method that receives
the decodes request packet and returns a response.
To implement a custom resolver in most cases all you need is to implement this interface.
Note that there is only a single instance of the Resolver so need to be careful about thread-safety and blocking
The following examples use the server framework:
- fixedresolver.py - Simple resolver which will respond to all
- requests with a fixed response
- zoneresolver.py - Resolver which will take a standard zone
- file input
shellresolver.py - Example of a dynamic resolver proxy.py - DNS proxy intercept.py - Intercepting DNS proxy
>>> resolver = BaseResolver() >>> logger = DNSLogger(prefix=False) >>> server = DNSServer(resolver,port=8053,address="localhost",logger=logger) >>> server.start_thread() >>> q = DNSRecord.question("abc.def") >>> a = q.send("localhost",8053) Request: [...] (udp) / 'abc.def.' (A) Reply: [...] (udp) / 'abc.def.' (A) / NXDOMAIN >>> print(DNSRecord.parse(a)) ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.def. IN A >>> server.stop()>>> class TestResolver: ... def resolve(self,request,handler): ... reply = request.reply() ... reply.add_answer(*RR.fromZone("abc.def. 60 A 1.2.3.4")) ... return reply >>> resolver = TestResolver() >>> server = DNSServer(resolver,port=8053,address="localhost",logger=logger,tcp=True) >>> server.start_thread() >>> a = q.send("localhost",8053,tcp=True) Request: [...] (tcp) / 'abc.def.' (A) Reply: [...] (tcp) / 'abc.def.' (A) / RRs: A >>> print(DNSRecord.parse(a)) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.def. IN A ;; ANSWER SECTION: abc.def. 60 IN A 1.2.3.4 >>> server.stop()
-
class
cft_code.dnslib.server.
BaseResolver
¶ Bases:
object
Base resolver implementation. Provides ‘resolve’ method which is called by DNSHandler with the decode request (DNSRecord instance) and returns a DNSRecord instance as reply.
In most cases you should be able to create a custom resolver by just replacing the resolve method with appropriate resolver code for application (see fixedresolver/zoneresolver/shellresolver for examples)
Note that a single instance is used by all DNSHandler instances so need to consider blocking & thread safety.
-
resolve
(request, handler)¶ Example resolver - respond to all requests with NXDOMAIN
-
-
class
cft_code.dnslib.server.
DNSHandler
(request, client_address, server)¶ Bases:
SocketServer.BaseRequestHandler
Handler for socketserver. Transparently handles both TCP/UDP requests (TCP requests have length prepended) and hands off lookup to resolver instance specified in <SocketServer>.resolver
-
get_reply
(data)¶
-
handle
()¶
-
udplen
= 0¶
-
-
class
cft_code.dnslib.server.
DNSLogger
(log='', prefix=True)¶ The class provides a default set of logging functions for the various stages of the request handled by a DNSServer instance which are enabled/disabled by flags in the ‘log’ class variable.
To customise logging create an object which implements the DNSLogger interface and pass instance to DNSServer.
The methods which the logger instance must implement are:
log_recv - Raw packet received log_send - Raw packet sent log_request - DNS Request log_reply - DNS Response log_truncated - Truncated log_error - Decoding error log_data - Dump full request/response-
log_data
(dnsobj)¶
-
log_error
(handler, e)¶
-
log_pass
(*args)¶
-
log_prefix
(handler)¶
-
log_recv
(handler, data)¶
-
log_reply
(handler, reply)¶
-
log_request
(handler, request)¶
-
log_send
(handler, data)¶
-
log_truncated
(handler, reply)¶
-
-
class
cft_code.dnslib.server.
DNSServer
(resolver, address='', port=53, tcp=False, logger=None, handler=<class cft_code.dnslib.server.DNSHandler>, server=None)¶ Bases:
object
Convenience wrapper for socketserver instance allowing either UDP/TCP server to be started in blocking more or as a background thread.
Processing is delegated to custom resolver (instance) and optionally custom logger (instance), handler (class), and server (class)
In most cases only a custom resolver instance is required (and possibly logger)
-
isAlive
()¶
-
start
()¶
-
start_thread
()¶
-
stop
()¶
-
cft_code.dnslib.shellresolver module¶
-
class
cft_code.dnslib.shellresolver.
ShellResolver
(routes, origin, ttl)¶ Bases:
dnslib.server.BaseResolver
Example dynamic resolver. Maps DNS labels to shell commands and returns result as TXT record (Note: No context is passed to the shell command)
Shell commands are passed in a a list in <label>:<cmd> format - eg:
[ ‘uptime.abc.com.:uptime’, ‘ls:ls’ ]Would respond to requests to ‘uptime.abc.com.’ with the output of the ‘uptime’ command.
For non-absolute labels the ‘origin’ parameter is prepended
-
resolve
(request, handler)¶ Example resolver - respond to all requests with NXDOMAIN
-
cft_code.dnslib.test_decode module¶
Test dnslib packet encoding/decoding
Reads test files from dnslib/test (by default) containing dump of DNS exchange (packet dump & parse output) and test round-trip parsing - specifically:
- Parse packet data and zone format data and compare
- Repack parsed packet data and compare with original
This should test the ‘parse’, ‘fromZone’ and ‘pack’ methods of the associated record types.
The original parsed output is created using dnslib by default so systematic encode/decode errors will not be found. By default the test data is checked against ‘dig’ to ensure that it is correct when generated using the –new option.
By default the module runs in ‘unittest’ mode (and supports unittest –verbose/–failfast options)
The module can also be run in interactive mode (–interactive) and inspect failed tests (–debug)
New test data files can be automatically created using the:
–new <domain> <type>
option. The data is checked against dig output and an error raised if this does not match. This is effectively the same as running:
python -m dnslib.client –query –hex –dig <domain> <type>
It is possible to manually generate test data files using dnslib.client even if the dig data doesn’t match (this is usually due to an unsupported RDATA type which dnslib will output in hex rather then parsing contents). The roundtrip tests will still work in this case (the unknown RDATA is handled as an opaque blob).
In some cases the tests will fail as a result of the zone file parser being more fragile than the packet parser (especially with broken data)
Note - unittests are dynamically generated from the test directory contents (matched against the –glob parameter)
-
class
cft_code.dnslib.test_decode.
TestContainer
(methodName='runTest')¶ Bases:
unittest.case.TestCase
-
cft_code.dnslib.test_decode.
check_decode
(f, debug=False)¶
-
cft_code.dnslib.test_decode.
new_test
(domain, qtype, address='8.8.8.8', port=53, nodig=False)¶
-
cft_code.dnslib.test_decode.
print_errors
(errors)¶
-
cft_code.dnslib.test_decode.
test_generator
(f)¶
cft_code.dnslib.zoneresolver module¶
Module contents¶
dnslib¶
A library to encode/decode DNS wire-format packets supporting both Python 2.7 and Python 3.2+.
The library provides:
- Support for encoding/decoding DNS packets between wire format, python objects, and Zone/DiG textual representation (dnslib.dns)
- A server framework allowing the simple creation of custom DNS resolvers (dnslib.server) and a number of example servers created using this framework
- A number of utilities for testing (dnslib.client, dnslib.proxy, dnslib.intercept)
Python 3 support was added in Version 0.9.0 which represented a fairly major update to the library - the key changes include:
- Python 2.7/3.2+ support (the last version supporting Python 2.6 or earlier was version 0.8.3)
- The ‘Bimap’ interface was changed significantly to explicitly split forward (value->text) lookups via __getitem__ and reverse (text->value) lookups via __getattr__. Applications using the old interface will need to be updated.
- Hostnames are now returned with a trailing dot by default (in line with RFC)
- Most object attributes are now typed in line with the record definitions to make it harder to generate invalid packets
- Support for encoding/decoding resource records in ‘Zone’ (BIND) file format
- Support for encoding/decoding packets in ‘DiG’ format
- Server framework allowing (in most cases) custom resolvers to be created by just subclassing the DNSResolver class and overriding the ‘resolve’ method
- A lot of fixes to error detection/handling which should make the library much more robust to invalid/unsupported data. The library should now either return a valid DNSRecord instance when parsing a packet or raise DNSError (tested via fuzzing)
- Improved utilities (dnslib.client, dnslib.proxy, dnslib.intercept)
- Improvements to encoding/decoding tests including the ability to generate test data automatically in test_decode.py (comparing outputs against DiG)
- Ability to compare and diff DNSRecords
Classes¶
The key DNS packet handling classes are in dnslib.dns and map to the standard DNS packet sections:
- DNSRecord - container for DNS packet. Contains:
- DNSHeader
- Question section containing zero or more DNSQuestion objects
- Answer section containing zero or more RR objects
- Authority section containing zero or more RR objects
- Additional section containing zero or more RR objects
- DNS RRs (resource records) contain an RR header and an RD object)
- Specific RD types are implemented as subclasses of RD
- DNS labels are represented by a DNSLabel class - in most cases this handles conversion to/from textual representation however does support arbitatry labels via a tuple of bytes objects
Usage¶
To decode a DNS packet:
>>> packet = binascii.unhexlify(b'd5ad818000010005000000000377777706676f6f676c6503636f6d0000010001c00c0005000100000005000803777777016cc010c02c0001000100000005000442f95b68c02c0001000100000005000442f95b63c02c0001000100000005000442f95b67c02c0001000100000005000442f95b93')
>>> d = DNSRecord.parse(packet)
>>> d
<DNS Header: id=0xd5ad type=RESPONSE opcode=QUERY flags=RD,RA rcode='NOERROR' q=1 a=5 ns=0 ar=0>
<DNS Question: 'www.google.com.' qtype=A qclass=IN>
<DNS RR: 'www.google.com.' rtype=CNAME rclass=IN ttl=5 rdata='www.l.google.com.'>
<DNS RR: 'www.l.google.com.' rtype=A rclass=IN ttl=5 rdata='66.249.91.104'>
<DNS RR: 'www.l.google.com.' rtype=A rclass=IN ttl=5 rdata='66.249.91.99'>
<DNS RR: 'www.l.google.com.' rtype=A rclass=IN ttl=5 rdata='66.249.91.103'>
<DNS RR: 'www.l.google.com.' rtype=A rclass=IN ttl=5 rdata='66.249.91.147'>
The default text representation of the DNSRecord is in zone file format:
>>> print(d)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54701
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.google.com. IN A
;; ANSWER SECTION:
www.google.com. 5 IN CNAME www.l.google.com.
www.l.google.com. 5 IN A 66.249.91.104
www.l.google.com. 5 IN A 66.249.91.99
www.l.google.com. 5 IN A 66.249.91.103
www.l.google.com. 5 IN A 66.249.91.147
To create a DNS Request Packet:
>>> d = DNSRecord.question("google.com")
(This is equivalent to: d = DNSRecord(q=DNSQuestion(“google.com”) )
>>> d
<DNS Header: id=... type=QUERY opcode=QUERY flags=RD rcode='NOERROR' q=1 a=0 ns=0 ar=0>
<DNS Question: 'google.com.' qtype=A qclass=IN>
>>> str(DNSRecord.parse(d.pack())) == str(d)
True
>>> print(d)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;google.com. IN A
>>> d = DNSRecord.question("google.com","MX")
(This is equivalent to: d = DNSRecord(q=DNSQuestion(“google.com”,QTYPE.MX) )
>>> str(DNSRecord.parse(d.pack())) == str(d)
True
>>> print(d)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;google.com. IN MX
To create a DNS Response Packet:
>>> d = DNSRecord(DNSHeader(qr=1,aa=1,ra=1),
... q=DNSQuestion("abc.com"),
... a=RR("abc.com",rdata=A("1.2.3.4")))
>>> d
<DNS Header: id=... type=RESPONSE opcode=QUERY flags=AA,RD,RA rcode='NOERROR' q=1 a=1 ns=0 ar=0>
<DNS Question: 'abc.com.' qtype=A qclass=IN>
<DNS RR: 'abc.com.' rtype=A rclass=IN ttl=0 rdata='1.2.3.4'>
>>> str(DNSRecord.parse(d.pack())) == str(d)
True
>>> print(d)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;abc.com. IN A
;; ANSWER SECTION:
abc.com. 0 IN A 1.2.3.4
It is also possible to create RRs from a string in zone file format
>>> RR.fromZone("abc.com IN A 1.2.3.4")
[<DNS RR: 'abc.com.' rtype=A rclass=IN ttl=0 rdata='1.2.3.4'>]
- (Note: this produces a list of RRs which should be unpacked if being
passed to add_answer/add_auth/add_ar etc)
>>> q = DNSRecord.question("abc.com") >>> a = q.reply() >>> a.add_answer(*RR.fromZone("abc.com 60 A 1.2.3.4")) >>> print(a) ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ... ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;abc.com. IN A ;; ANSWER SECTION: abc.com. 60 IN A 1.2.3.4
The zone file can contain multiple entries and supports most of the normal format defined in RFC1035 (specifically not $INCLUDE)
>>> z = '''
... $TTL 300
... $ORIGIN abc.com
...
... @ IN MX 10 mail.abc.com.
... www IN A 1.2.3.4
... IN TXT "Some Text"
... mail IN CNAME www.abc.com.
... '''
>>> for rr in RR.fromZone(textwrap.dedent(z)):
... print(rr)
abc.com. 300 IN MX 10 mail.abc.com.
www.abc.com. 300 IN A 1.2.3.4
www.abc.com. 300 IN TXT "Some Text"
mail.abc.com. 300 IN CNAME www.abc.com.
To create a skeleton reply to a DNS query:
>>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.ANY))
>>> a = q.reply()
>>> a.add_answer(RR("abc.com",QTYPE.A,rdata=A("1.2.3.4"),ttl=60))
>>> str(DNSRecord.parse(a.pack())) == str(a)
True
>>> print(a)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;abc.com. IN ANY
;; ANSWER SECTION:
abc.com. 60 IN A 1.2.3.4
Add additional RRs:
>>> a.add_answer(RR("xxx.abc.com",QTYPE.A,rdata=A("1.2.3.4")))
>>> a.add_answer(RR("xxx.abc.com",QTYPE.AAAA,rdata=AAAA("1234:5678::1")))
>>> str(DNSRecord.parse(a.pack())) == str(a)
True
>>> print(a)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;abc.com. IN ANY
;; ANSWER SECTION:
abc.com. 60 IN A 1.2.3.4
xxx.abc.com. 0 IN A 1.2.3.4
xxx.abc.com. 0 IN AAAA 1234:5678::1
It is also possible to create a reply from a string in zone file format:
>>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.ANY))
>>> a = q.replyZone("abc.com 60 IN CNAME xxx.abc.com")
>>> print(a)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;abc.com. IN ANY
;; ANSWER SECTION:
abc.com. 60 IN CNAME xxx.abc.com.
>>> str(DNSRecord.parse(a.pack())) == str(a)
True
>>> q = DNSRecord(q=DNSQuestion("abc.com",QTYPE.ANY))
>>> a = q.replyZone(textwrap.dedent(z))
>>> print(a)
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: ...
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;abc.com. IN ANY
;; ANSWER SECTION:
abc.com. 300 IN MX 10 mail.abc.com.
www.abc.com. 300 IN A 1.2.3.4
www.abc.com. 300 IN TXT "Some Text"
mail.abc.com. 300 IN CNAME www.abc.com.
The library also includes a simple framework for generating custom DNS resolvers in dnslib.server (see module docs). In post cases this just requires implementing a custom ‘resolve’ method which receives a question object and returns a response.
A number of sample resolvers are provided as examples (see CLI –help):
- dnslib.fixedresolver - Respond to all requests with fixed response
- dnslib.zoneresolver - Respond from Zone file
- dnslib.shellresolver - Call shell script to generate response
The library includes a number of client utilities:
DiG like client library
# python -m dnslib.client –help
DNS Proxy Server
# python -m dnslib.proxy –help
Intercepting DNS Proxy Server (replace proxy responses for specified domains)
# python -m dnslib.intercept –help
Changelog:¶
0.1 2010-09-19 Initial Release
0.2 2010-09-22 Minor fixes
0.3 2010-10-02 Add DNSLabel class to support arbitrary labels (embedded ‘.’)
0.4 2012-02-26 Merge with dbslib-circuits
- 0.5 2012-09-13 Add support for RFC2136 DDNS updates
Patch provided by Wesley Shields <wxs@FreeBSD.org> - thanks
0.6 2012-10-20 Basic AAAA support
0.7 2012-10-20 Add initial EDNS0 support (untested)
- 0.8 2012-11-04 Add support for NAPTR, Authority RR and additional RR
Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks
- 0.8.1 2012-11-05 Added NAPTR test case and fixed logic error
Patch provided by Stefan Andersson (https://bitbucket.org/norox) - thanks
- 0.8.2 2012-11-11 Patch to fix IPv6 formatting
Patch provided by Torbjörn Lönnemark (https://bitbucket.org/tobbezz) - thanks
- 0.8.3 2013-04-27 Don’t parse rdata if rdlength is 0
Patch provided by Wesley Shields <wxs@FreeBSD.org> - thanks
0.9.0 2014-05-05 Major update including Py3 support (see docs)
0.9.1 2014-05-05 Minor fixes
- 0.9.2 2014-08-26 Fix Bimap handling of unknown mappings to avoid exception in printing
Add typed attributes to classes Misc fixes from James Mills - thanks
- 0.9.3 2014-08-26 Workaround for argparse bug which raises AssertionError if [] is
present in option text (really?)
- 0.9.4 2015-04-10 Fix to support multiple strings in TXT record
Patch provided by James Cherry (https://bitbucket.org/james_cherry) - thanks NOTE: For consistency this patch changes the ‘repr’ output for
TXT records to always be quoted
0.9.5 2015-10-27 Add threading & timeout handling to DNSServer
- 0.9.6 2015-10-28 Replace strftime in RRSIG formatting to avoid possible locale issues
Identified by Bryan Everly - thanks
License:¶
BSD
Author:¶
- Paul Chakravarti (paul.chakravarti@gmail.com)
Master Repository/Issues:¶
- https://bitbucket.org/paulc/dnslib (Cloned on GitHub: https://github.com/paulchakravarti/dnslib)