1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
|
# -*- coding: utf-8 -*-
"""
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
* 0.9.7 2017-01-15 Sort out CAA/TYPE257 DiG parsing mismatch
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)
"""
from dnslib.dns import *
version = "0.9.7"
if __name__ == '__main__':
import doctest,textwrap
doctest.testmod(optionflags=doctest.ELLIPSIS)
|