Compare commits

...

114 Commits

Author SHA1 Message Date
3ecc777eb9
Ensure printed messages are flushed immediately (fixes #59)
Also send error messages to stderr
2019-03-15 17:47:48 +01:00
1d37debd96
Add -c0 support for infinite loop (fixes #57) 2019-03-15 17:36:39 +01:00
0a9b362587
Update classifiers to reflect support for newer python 3.x and pypy 2018-08-05 16:59:47 +04:30
a81525fc5f
Fix travis config to build 3.7 and 3.8-dev 2018-08-05 16:47:38 +04:30
3f8db03853
Fixing 3.7 build again 2018-08-05 16:45:55 +04:30
f58597f702
Add 3.8-dev target to test 2018-08-05 16:44:02 +04:30
133f5c7952
Trying to fix python 3.7 build on Travis 2018-08-05 16:41:18 +04:30
3e8e1e41d7
Add build-matrix support 2018-08-05 16:33:50 +04:30
befeb64a41
Test with more recent python versions and pypy 2018-08-05 16:28:12 +04:30
b0f1335664
Fix badges 2018-08-05 11:22:22 +04:30
0648619d2a
Merge branch 'master' of github.com:farrokhi/dnsdiag 2018-08-05 11:20:00 +04:30
151c383cc1
Merge pull request #56 from fossabot/master
Add license scan report and status
2018-08-05 11:16:14 +04:30
fossabot
b78ec5516c Add license scan report and status
Signed-off-by: fossabot <badges@fossa.io>
2018-08-04 23:35:44 -07:00
d3a1fd2308
Merge pull request #54 from Bashar/patch-4
updated with sort and comments
2018-05-15 18:24:47 +04:30
b8dbe54f1a
Merge pull request #55 from Bashar/patch-3
updated freenom and the missing quad9 with comments
2018-05-15 18:22:07 +04:30
Bashar Al-Abdulhadi
9e153c406d
updated with sort and comments 2018-05-15 16:47:35 +03:00
Bashar Al-Abdulhadi
0fe4786bba
updated freenom and the missing quad9 with comments 2018-05-15 16:44:57 +03:00
9b47a3084a
minor description change 2018-05-15 16:45:05 +04:30
b1b4f06730
Include sample server files 2018-04-03 17:08:14 +04:30
25c243efa1
Preparing for v1.6.4
General improvements and fixes
2018-04-03 17:01:50 +04:30
a0c26242ec
Add sample input file with IPv4 address of public resolvers
for people who are still on legacy networks from late 20th century.
2018-04-02 22:38:00 +04:30
e93120da19
Fix sample input filename in README 2018-04-02 22:36:02 +04:30
dc7f03eac3
Add CloudFlare's new resolver (v4/v6) (Fixes #51) 2018-04-02 11:52:29 +04:30
7390514877
Deal with failures to open input file (fixes #50) 2018-02-18 07:36:37 +03:30
bbff3b44f6
Use "dnsdiag" keyword when generating random hostnames 2018-01-23 17:36:02 +03:30
d93b87b2a2
Remove a leftover debug message 2018-01-23 17:14:23 +03:30
8a9acd9100
Add -m to force cache-miss measurement in dnseval (Closes #41)
Using `-m` causes dnseval to add a random hostname prefix to the given
domain name (format is "_dnseval_RANDOM_."). This will cause NXDOMAIN
and the query fails, but we do measure the response time anyway.
2018-01-23 15:20:36 +03:30
8283f4dbc2
Ability to have comments in resolvers list (Closes #43) 2018-01-23 12:25:46 +03:30
e3ddfff88e
User can specify source address (Fixes #46)
- also respect resolver port number (if specified by user)
2018-01-23 12:13:50 +03:30
1b9849c224
Use more accurate response time measurement method (fixes #44) 2018-01-23 11:32:40 +03:30
76b843d728
Merge pull request #42 from webernetz/patch-1
Update public-servers.txt
2017-11-27 23:03:21 +03:30
Johannes Weber
037457f0cc
Update public-servers.txt
Added the open DNS resolvers from Quad9 and OpenDNS. Both for IPv6 and legacy IP.
2017-11-27 20:31:46 +01:00
4c9aaf921b
Merge pull request #38 from will-h/master
Allow '-f -' to denote stdin
2017-11-04 20:12:25 +03:30
Will Hargrave
f1807d34ea Allow '-f -' to denote stdin 2017-11-01 21:34:56 +00:00
9caa006e5b
Change default ping interval to 1 second 2017-10-06 10:46:16 +03:30
5a99d58dd1
Ignore virtualenv directory 2017-10-06 10:38:00 +03:30
c92ea53e57
Use a more readable color in verbose mode 2017-05-02 14:45:03 +04:30
47dbc6afe3
Update installation notes to reflect recent changes 2017-05-02 14:18:52 +04:30
39e564e626
Use cymruwhois from pypi and remove submodule
- cymruwhois maintainer recovered his access to pypi and
  uploaded latest package. There is no need to use it as
  submodule anymore. So we added an external dependency.

- Refactor whois data caching in dnstraceroute and unbreak
  caching mechanism which was broken since previous commit
  due to a bug in time delta calculation.
2017-05-02 14:16:17 +04:30
3df692e6db
No more needed 2017-04-30 21:18:45 +04:30
2dcf0e7b78
Fix setuptools 2017-04-30 21:12:08 +04:30
a0f9cae673
Fix setuptool installation and dependecies 2017-04-30 19:35:36 +04:30
51e3d252f2
Add color mode to dnseval ("-C" option) 2017-04-26 12:53:05 +04:30
7db7684c95 Fix display in case of no answer (fix #34) 2017-04-25 22:28:22 +04:30
bde3263cfa
Use semantic versioning
and more standard constant names
2017-04-24 17:43:47 +04:30
21944596fb
Bump version 2017-04-24 15:39:23 +04:30
896484244c
Fix string formatting 2017-04-24 15:37:24 +04:30
62f66a9b3c
Add verbose mode to print actual response(s) (FIX #28) 2017-04-24 15:17:22 +04:30
6c1d7313fe
Display correct TTL in case of multiple responses like CNAME (FIX #29) 2017-04-24 14:42:34 +04:30
0c01d0ebb2
Improve help message 2017-04-24 14:41:53 +04:30
8267ce55b2
Update list of public resolvers 2017-04-23 14:06:21 +04:30
87cea8c04c
Improve query time measurement accuracy and display 2017-04-23 13:23:17 +04:30
6a0b0c2ff5
Fix build with Travis CI 2016-11-14 16:26:36 +03:30
c9f3fe6f0d
We don't need a _modified_ dnspython anymore. Fix docs. 2016-11-14 15:44:49 +03:30
77aec5958f
Improve expert hints (fix #30)
Now we assume a radius for local network (persumably 4 hops). Now if a DNS server appears next to a private or reserved IP address, and it happens within 4 hops, then it is your local network and this is not necessarily a bad thing. But if it happens beyond 4 hops, we assume it is public network, and you are not supposed to see a probe response from a private IP address on internet (fishy).
2016-11-14 15:40:07 +03:30
d78c67dc79
Update README 2016-10-20 14:12:30 +03:30
66e5d5acef Remove local dnspython submodule since the latest dnspython (as of 1.15.0) supports all the requirements 2016-10-14 14:19:43 +03:30
2715f42722
Change default timeout value to 2 (was 5) (fix #24) 2016-08-22 16:02:25 +04:30
50934cbe91
Fix handling invalid TTL and some output string justifications (fix #26, #27) 2016-08-22 15:58:58 +04:30
93b0d6ec51 Show TTL in dnseval output and update README (fix #23) 2016-08-16 15:08:19 +04:30
cc5dffc5d5
Do not exit if a resolver cannot resolve a name (Fix #22) 2016-08-16 14:36:08 +04:30
3220c1b65d
Remove -e from examples 2016-08-06 16:41:56 +04:30
af6e32aa3f
Update README to reflect new changes in flags (-e -> -x) 2016-08-06 16:41:01 +04:30
c110b19266
Reverse behavior of -e flag. EDNS0 is now enabled by default (fix #21) 2016-08-06 16:40:05 +04:30
6c4a88b819
Fix conflicting -e switch (--expert and --edns).
- Also reverse behavior of -e flag. EDNS0 is now enabled by default.
2016-08-06 16:38:47 +04:30
a576293bc0
Enable EDNS0 by default. -e disables EDNS0 (fix #21) 2016-08-06 16:36:56 +04:30
b0e73e1b9d Merge pull request #20 from hamishcoleman/master
Add option to pause between each dnsping request
2016-08-04 13:15:28 +04:30
Hamish Coleman
df366d5934 Add option to pause between each dnsping request 2016-08-04 14:13:33 +10:00
ec6e93e2b2
Update readme with fixed example output 2016-06-26 17:49:05 +04:30
52b89212f0
Use ASCII character for separator 2016-06-26 17:48:03 +04:30
94fa0508eb
Bump version 2016-06-26 17:17:24 +04:30
ff52245007
Add EDNS0 support and update docs 2016-06-26 17:15:54 +04:30
5b8b94a2c0
Update example for dnstraceroute 2016-06-26 16:59:05 +04:30
744d492c3d
Update example for dnsping 2016-06-26 16:57:11 +04:30
26fdd00647
Add support for EDNS0 flag 2016-06-26 16:54:22 +04:30
40052f008d
Using EDNS0 is now optional and disabled by default
Also update documentation with new example
2016-06-26 16:42:19 +04:30
8535bb2aba Bump version 2016-06-15 17:15:29 +04:30
b23a3ce081
Fix looking up NS record from root server
- also resolve hostname to IP if name is given as dns server
- raise timeout to 2 seconds
2016-06-15 17:14:10 +04:30
364312cbac
Add list of root servers as example for dnseval 2016-06-15 16:29:53 +04:30
adb4f26f8a Bump version 2016-06-15 16:27:27 +04:30
eeb647b99f
Fix bug in dealing with root servers 2016-06-15 16:26:58 +04:30
758b7b8ccd
Change separator line 2016-06-14 18:46:45 +04:30
2fb6cdb300
Add --tcp/-T option (fix #19) 2016-06-14 18:31:58 +04:30
1fa14ded48
Correctly show AD flag (fix #13) 2016-06-14 18:27:20 +04:30
597b801b7b
Bump version 2016-06-14 17:44:46 +04:30
c025a0894c
Remove dnspython from external requirements 2016-06-14 17:01:52 +04:30
a820d11e0e
Add original dnspython as submodule
This would be in effect until the latest version (>=1.15.0) become
available on pypi.
2016-06-14 17:00:55 +04:30
d8b9de5fcc
This should not exist in this branch to make merging easier 2016-06-14 16:58:13 +04:30
d79f5821c5
Refactor and comments 2016-06-14 16:48:35 +04:30
f74b311170
Add relevant category 2016-06-14 16:39:53 +04:30
acd8602891
Refactor and comments 2016-06-14 16:39:44 +04:30
403a583942
Update version information to match setuptools 2016-06-12 12:15:09 +04:30
8725ce0aeb
Update requirements to match new dnspython version 2016-06-05 10:04:22 +04:30
c45d408be1 Add requirements.txt, now we depend on dnspython 2016-06-01 13:24:55 +04:30
8a1b74fe0f
remove patched dnspython module and use stock version 2016-06-01 13:23:56 +04:30
22ef5331af
Remove AF 2016-06-01 13:23:46 +04:30
efeccef2aa
Fix text alignment 2016-06-01 13:22:43 +04:30
4092e4e2cb Use new socket_factory to avoid using patched dnspython 2016-06-01 13:09:34 +04:30
db90716837
show dns flags in fixed columns (fix #18) 2016-05-25 20:36:14 +04:30
24adbab4aa
test agains various python 3.x builds 2016-05-25 15:46:39 +04:30
76a24b10f2
also ask NS from domain's own DNS server 2016-05-24 16:05:11 +04:30
2609fdd758
organize code in classes and beautify output 2016-05-24 12:37:10 +04:30
440a9f4287
add initial dnsdiag.py tool - the DNS diagnostics swiss army knife 2016-05-23 19:44:37 +04:30
4c2d9d51e1
fix long arguments 2016-05-23 18:54:14 +04:30
920b0a8952
remove unnecessary option 2016-05-23 18:50:40 +04:30
575113ed69
discreetly skip bad hostnames 2016-05-23 18:36:47 +04:30
0164eae199
fix README and code cleanup 2016-05-16 19:05:48 +04:30
c7a602ae4e
deal with empty lines in input file (fix #15) 2016-05-16 14:17:39 +04:30
d0db0c62aa
fix NoAnswer error when RRSIG requested (fix #14) 2016-05-14 14:12:12 +04:30
1d373df954
add flags in dnseval output and update docs (close #13) 2016-05-14 13:22:02 +04:30
895c80b5bf
print flagas in verbose mode (in response to #13) 2016-05-14 13:09:47 +04:30
13751fed51
fix script names for setuptools 2016-05-10 13:16:55 +04:30
a1ee92d8f2
mention binary releases 2016-05-10 10:09:40 +04:30
c745b96e08
bump version 2016-05-09 17:21:24 +04:30
17 changed files with 566 additions and 226 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
# virtualenv
.venv/
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

7
.gitmodules vendored
View File

@ -1,7 +0,0 @@
[submodule "dnspython"]
path = dnspython
url = https://github.com/farrokhi/dnspython.git
branch = python3
[submodule "cymruwhois"]
path = cymruwhois
url = https://github.com/JustinAzoff/python-cymruwhois.git

View File

@ -1,5 +1,18 @@
language: python language: python
python: "3.4" sudo: false
install: pip install --process-dependency-links dnsdiag install: "pip install -r requirements.txt"
script: nosetests dnstraceroute.py script: nosetests dnstraceroute.py
matrix:
fast_finish: true
include:
- python: "3.3"
- python: "3.4"
- python: "3.5"
- python: "3.6"
- python: "3.7"
dist: xenial
sudo: true
- python: "3.8-dev"
dist: xenial
sudo: true
- python: "pypy3"

View File

@ -1,2 +1 @@
include LICENSE README.md TODO.md public-servers.txt include LICENSE README.md TODO.md public-servers.txt public-v4.txt rootservers.txt
include cymruwhois/*.py

View File

@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.org/farrokhi/dnsdiag.svg)](https://travis-ci.org/farrokhi/dnsdiag) [![PyPI](https://img.shields.io/pypi/v/dnsdiag.svg?maxAge=8600)](https://pypi.python.org/pypi/dnsdiag/) [![PyPI](https://img.shields.io/pypi/l/dnsdiag.svg?maxAge=8600)]() [![PyPI](https://img.shields.io/pypi/pyversions/dnsdiag.svg?maxAge=8600)]() [![GitHub stars](https://img.shields.io/github/stars/farrokhi/dnsdiag.svg?style=social&label=Star&maxAge=8600)](https://github.com/farrokhi/dnsdiag/stargazers) [![Build Status](https://travis-ci.org/farrokhi/dnsdiag.svg)](https://travis-ci.org/farrokhi/dnsdiag) [![PyPI](https://img.shields.io/pypi/v/dnsdiag.svg?maxAge=8600)](https://pypi.python.org/pypi/dnsdiag/) [![PyPI](https://img.shields.io/pypi/l/dnsdiag.svg?maxAge=8600)]() [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Ffarrokhi%2Fdnsdiag.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Ffarrokhi%2Fdnsdiag?ref=badge_shield) [![PyPI](https://img.shields.io/pypi/pyversions/dnsdiag.svg?maxAge=8600)]() [![GitHub stars](https://img.shields.io/github/stars/farrokhi/dnsdiag.svg?style=social&label=Star&maxAge=8600)](https://github.com/farrokhi/dnsdiag/stargazers)
DNS Diagnostics and Performance Measurement Tools DNS Diagnostics and Performance Measurement Tools
================================================== ==================================================
@ -28,40 +28,45 @@ of view.
# prerequisites # prerequisites
This script requires python3 as well as latest This script requires python3 as well as latest
[dnspython](http://www.dnspython.org/) and [dnspython](http://www.dnspython.org/) and
[cymruwhois](https://pythonhosted.org/cymruwhois/). Please note that [cymruwhois](https://pythonhosted.org/cymruwhois/).
"dnstraceroute" requires a modified version of dnspython module. All required
third-party modules are included as GIT submodules. You just need to run `git
submodule update --init` and project directory to pull the required code.
# installation # installation
There are several ways that you can use this toolset. However using the sourcecode is always recommended.
## From Source Code
1. You can checkout this git repo and its submodules 1. You can checkout this git repo and its submodules
``` ```
git clone https://github.com/farrokhi/dnsdiag.git git clone https://github.com/farrokhi/dnsdiag.git
cd dnsdiag cd dnsdiag
git submodule update --init pip3 install -r requirements.txt
``` ```
2. You can alternatively install the package using pip: 2. You can alternatively install the package using pip:
``` ```
pip3 install --process-dependency-links dnsdiag pip3 install dnsdiag
``` ```
## From Binary
From time to time, binary version will be released for Windows, Mac OS X and Linux platforms. You can grab the latest release from [releases page](https://github.com/farrokhi/dnsdiag/releases).
# dnsping # dnsping
dnsping pings a DNS resolver by sending an arbitrary DNS query for given number dnsping pings a DNS resolver by sending an arbitrary DNS query for given number
of times: of times:
``` ```
% ./dnsping.py -c 3 -s 8.8.8.8 -t MX wikipedia.org % ./dnsping.py -c 3 -t AAAA -s 8.8.8.8 dnsdiag.org
dnsping.py DNS: 8.8.8.8:53, hostname: wikipedia.org, rdatatype: MX dnsping.py DNS: 8.8.8.8:53, hostname: dnsdiag.org, rdatatype: AAAA
101 bytes from 8.8.8.8: seq=0 time=262.896 ms 4 bytes from 8.8.8.8: seq=0 time=123.509 ms
101 bytes from 8.8.8.8: seq=1 time=305.608 ms 4 bytes from 8.8.8.8: seq=1 time=115.726 ms
101 bytes from 8.8.8.8: seq=2 time=307.221 ms 4 bytes from 8.8.8.8: seq=2 time=117.351 ms
--- 8.8.8.8 dnsping statistics --- --- 8.8.8.8 dnsping statistics ---
3 requests transmitted, 3 responses received, 0% lost 3 requests transmitted, 3 responses received, 0% lost
min=262.896 ms, avg=291.908 ms, max=307.221 ms, stddev=25.138 ms min=115.726 ms, avg=118.862 ms, max=123.509 ms, stddev=4.105 ms
``` ```
This script calculates minimum, maximum and average response time as well as This script calculates minimum, maximum and average response time as well as
jitter (stddev) jitter (stddev)
@ -73,23 +78,16 @@ it to your actual network traceroute and make sure your DNS traffic is not
routed to any unwanted path. routed to any unwanted path.
``` ```
% ./dnstraceroute.py --expert -s 8.8.4.4 yahoo.com % ./dnstraceroute.py --expert -C -t A -s 8.8.4.4 facebook.com
dnstraceroute.py DNS: 8.8.4.4:53, hostname: yahoo.com, rdatatype: A dnstraceroute.py DNS: 8.8.4.4:53, hostname: facebook.com, rdatatype: A
1 204.109.58.53 (204.109.58.53) 1 ms 1 192.168.0.1 (192.168.0.1) 1 ms
2 208.79.80.5 (208.79.80.5) 1 ms 2 192.168.28.177 (192.168.28.177) 4 ms
3 162.223.13.177 (162.223.13.177) 1 ms 3 192.168.0.1 (192.168.0.1) 693 ms
4 208.79.80.254 (208.79.80.254) 7 ms 4 172.19.4.17 (172.19.4.17) 3 ms
5 eqixva-google-gige.google.com (206.126.236.21) 7 ms 5 google-public-dns-b.google.com (8.8.4.4) 8 ms
6 209.85.242.142 (209.85.242.142) 7 ms
7 72.14.236.148 (72.14.236.148) 8 ms
8 209.85.250.70 (209.85.250.70) 16 ms
9 74.125.37.222 (74.125.37.222) 16 ms
10 *
11 google-public-dns-b.google.com (8.8.4.4) 15 ms
=== Expert Hints ===
[*] public DNS server is next to an invisible hop (probably a firewall)
=== Expert Hints ===
[*] public DNS server is next to a private IP address (possible hijacking)
``` ```
Using `--expert` will instruct dnstraceroute to print expert hints (such as warnings of possible DNS traffic hijacking). Using `--expert` will instruct dnstraceroute to print expert hints (such as warnings of possible DNS traffic hijacking).
@ -99,17 +97,21 @@ dnseval is a bulk ping utility that sends an arbitrary DNS query to a give list
of DNS servers. This script is meant for comparing response time of multiple of DNS servers. This script is meant for comparing response time of multiple
DNS servers at once: DNS servers at once:
``` ```
% ./dnseval.py wikipedia.org % ./dnseval.py -t AAAA -f public-servers.txt -c10 yahoo.com
server avg(ms) min(ms) max(ms) stddev(ms) lost(%) server avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags
-------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------
4.2.2.1 151.067 131.270 221.742 28.643 %10 8.8.8.8 270.791 215.599 307.498 40.630 %0 298 QR -- -- RD RA -- --
4.2.2.2 142.175 132.921 178.133 13.348 %0 8.8.4.4 222.955 171.753 307.251 60.481 %10 291 QR -- -- RD RA -- --
64.6.64.6 133.047 109.145 162.938 20.609 %0 ns.ripe.net 174.855 160.949 187.458 10.099 %0 289 QR -- -- RD RA -- --
64.6.65.6 377.270 97.669 661.471 172.717 %0 4.2.2.1 172.798 163.892 189.918 7.823 %0 287 QR -- -- RD RA -- --
8.8.4.4 389.048 294.581 511.134 67.953 %0 4.2.2.2 178.594 169.158 184.696 5.067 %0 285 QR -- -- RD RA -- --
8.8.8.8 0.000 0.000 0.000 0.000 %100 4.2.2.3 153.574 138.509 173.439 12.015 %0 284 QR -- -- RD RA -- --
208.67.222.222 179.068 135.975 258.582 50.681 %0 4.2.2.4 153.182 141.023 162.323 6.700 %0 282 QR -- -- RD RA -- --
208.67.220.220 137.817 135.822 140.113 1.504 %0 4.2.2.5 154.840 141.557 163.889 7.195 %0 281 QR -- -- RD RA -- --
209.244.0.3 156.270 147.320 161.365 3.958 %0 279 QR -- -- RD RA -- --
209.244.0.4 159.329 151.283 163.726 3.958 %0 278 QR -- -- RD RA -- --
195.46.39.39 171.098 163.612 181.147 5.067 %0 276 QR -- -- RD RA -- --
195.46.39.40 175.335 160.920 185.618 8.726 %0 274 QR -- -- RD RA -- --
``` ```
### Author ### Author
@ -117,7 +119,7 @@ server avg(ms) min(ms) max(ms) stddev(ms) lost(%)
Babak Farrokhi Babak Farrokhi
- twitter: [@farrokhi](https://twitter.com/farrokhi) - twitter: [@farrokhi](https://twitter.com/farrokhi)
- github: [/farrokhi](https://github.com/farrokhi/) - github: [github.com/farrokhi](https://github.com/farrokhi/)
- website: [farrokhi.net](https://farrokhi.net/) - website: [farrokhi.net](https://farrokhi.net/)
@ -125,8 +127,4 @@ Babak Farrokhi
dnsdiag is released under a 2 clause BSD license. dnsdiag is released under a 2 clause BSD license.
### Credits [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Ffarrokhi%2Fdnsdiag.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Ffarrokhi%2Fdnsdiag?ref=badge_large)
- [@rthalley](https://github.com/rthalley) for invaluable [dnspython](https://github.com/rthalley/dnspython) library
- [@JustinAzoff](https://github.com/JustinAzoff) for [python-cymruwhois](https://github.com/JustinAzoff/python-cymruwhois) library
- [@bortzmeyer](https://github.com/bortzmeyer) for his feedback and patches

View File

@ -1,6 +1,3 @@
# TODO # TODO
- implement basic DNS client functionality into dnstraceroute to eliminate need
of modified dnspython module
- add support for python 2.x for the conservatives and faint-hearted ;-)
- dnsfingerprint.py tool to fingerprint DNS servers - dnsfingerprint.py tool to fingerprint DNS servers

@ -1 +0,0 @@
Subproject commit a34543335cbef02b1b615e774ce5b6187afb0cc2

1
dns
View File

@ -1 +0,0 @@
dnspython/dns

View File

@ -25,35 +25,61 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import getopt import getopt
import ipaddress import ipaddress
import os
import signal import signal
import socket import socket
import sys import sys
import time import time
import random
import string
from statistics import stdev from statistics import stdev
import dns.rdatatype import dns.rdatatype
import dns.resolver import dns.resolver
__VERSION__ = 1.0 __author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__PROGNAME__ = os.path.basename(sys.argv[0]) __license__ = 'BSD'
__version__ = "1.6.4"
__progname__ = os.path.basename(sys.argv[0])
shutdown = False shutdown = False
resolvers = dns.resolver.get_default_resolver().nameservers resolvers = dns.resolver.get_default_resolver().nameservers
class Colors(object):
N = '\033[m' # native
R = '\033[31m' # red
G = '\033[32m' # green
O = '\033[33m' # orange
B = '\033[34m' # blue
def __init__(self, mode):
if not mode:
self.N = ''
self.R = ''
self.G = ''
self.O = ''
self.B = ''
def usage(): def usage():
print("""%s version %1.1f print("""%s version %s
usage: %s [-h] [-f server-list] [-c count] [-t type] [-w wait] hostname usage: %s [-h] [-f server-list] [-c count] [-t type] [-w wait] hostname
-h --help show this help -h --help Show this help
-f --file dns server list to use (default: system resolvers) -f --file DNS server list to use (default: system resolvers)
-c --count number of requests to send (default: 10) -c --count Number of requests to send (default: 10)
-w --wait maximum wait time for a reply (default: 5) -m --cache-miss Force cache miss measurement by prepending a random hostname
-t --type DNS request record type (default: A) -w --wait Maximum wait time for a reply (default: 2)
""" % (__PROGNAME__, __VERSION__, __PROGNAME__)) -t --type DNS request record type (default: A)
-T --tcp Use TCP instead of UDP
-e --edns Disable EDNS0 (Default: Enabled)
-C --color Print colorful output
-v --verbose Print actual dns response
""" % (__progname__, __version__, __progname__))
sys.exit() sys.exit()
@ -69,41 +95,108 @@ def maxlen(names):
return len(sn[-1]) return len(sn[-1])
def dnsping(host, server, dnsrecord, timeout, count): def _order_flags(table):
return sorted(table.items(), reverse=True)
def flags_to_text(flags):
# Standard DNS flags
QR = 0x8000
AA = 0x0400
TC = 0x0200
RD = 0x0100
RA = 0x0080
AD = 0x0020
CD = 0x0010
# EDNS flags
DO = 0x8000
_by_text = {
'QR': QR,
'AA': AA,
'TC': TC,
'RD': RD,
'RA': RA,
'AD': AD,
'CD': CD
}
_by_value = dict([(y, x) for x, y in _by_text.items()])
_flags_order = _order_flags(_by_value)
_by_value = dict([(y, x) for x, y in _by_text.items()])
order = sorted(_by_value.items(), reverse=True)
text_flags = []
for k, v in order:
if flags & k != 0:
text_flags.append(v)
else:
text_flags.append('--')
return ' '.join(text_flags)
def random_string(min_length=5, max_length=10):
char_set = string.ascii_letters + string.digits
length = random.randint(min_length, max_length)
return ''.join(map(lambda unused: random.choice(char_set), range(length)))
def dnsping(host, server, dnsrecord, timeout, count, use_tcp=False, use_edns=False, force_miss=False):
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
resolver.nameservers = [server] resolver.nameservers = [server]
resolver.timeout = timeout resolver.timeout = timeout
resolver.lifetime = timeout resolver.lifetime = timeout
resolver.retry_servfail = 0 resolver.retry_servfail = 0
flags = 0
ttl = None
answers = None
if use_edns:
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
response_time = [] response_times = []
i = 0 i = 0
for i in range(count): for i in range(count):
if shutdown: if shutdown: # user pressed CTRL+C
break break
try: try:
stime = time.time() if force_miss:
answers = resolver.query(host, dnsrecord) # todo: response validation in future fqdn = "_dnsdiag_%s_.%s" % (random_string(), host)
etime = time.time() else:
fqdn = host
stime = time.perf_counter()
answers = resolver.query(fqdn, dnsrecord, tcp=use_tcp,
raise_on_no_answer=False) # todo: response validation in future
except (dns.resolver.NoNameservers, dns.resolver.NoAnswer): except (dns.resolver.NoNameservers, dns.resolver.NoAnswer):
break break
except dns.resolver.Timeout: except dns.resolver.Timeout:
pass pass
except dns.resolver.NXDOMAIN:
etime = time.perf_counter()
if force_miss:
elapsed = (etime - stime) * 1000 # convert to milliseconds
response_times.append(elapsed)
else: else:
elapsed = (etime - stime) * 1000 # convert to milliseconds elapsed = answers.response.time * 1000 # convert to milliseconds
response_time.append(elapsed) response_times.append(elapsed)
r_sent = i + 1 r_sent = i + 1
r_received = len(response_time) r_received = len(response_times)
r_lost = r_sent - r_received r_lost = r_sent - r_received
r_lost_percent = (100 * r_lost) / r_sent r_lost_percent = (100 * r_lost) / r_sent
if response_time: if response_times:
r_min = min(response_time) r_min = min(response_times)
r_max = max(response_time) r_max = max(response_times)
r_avg = sum(response_time) / r_received r_avg = sum(response_times) / r_received
if len(response_time) > 1: if len(response_times) > 1:
r_stddev = stdev(response_time) r_stddev = stdev(response_times)
else: else:
r_stddev = 0 r_stddev = 0
else: else:
@ -112,29 +205,41 @@ def dnsping(host, server, dnsrecord, timeout, count):
r_avg = 0 r_avg = 0
r_stddev = 0 r_stddev = 0
return (server, r_avg, r_min, r_max, r_stddev, r_lost_percent) if answers is not None:
flags = answers.response.flags
if len(answers.response.answer) > 0:
ttl = answers.response.answer[0].ttl
return server, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags, ttl, answers
def main(): def main():
try: try:
signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z
signal.signal(signal.SIGINT, signal_handler) # ignore CTRL+C signal.signal(signal.SIGINT, signal_handler) # catch CTRL+C
except AttributeError: # Some systems may not support all signals except AttributeError: # Some systems (e.g. Windows) may not support all signals
pass pass
if len(sys.argv) == 1: if len(sys.argv) == 1:
usage() usage()
# defaults
dnsrecord = 'A' dnsrecord = 'A'
count = 10 count = 10
waittime = 5 waittime = 2
inputfilename = None inputfilename = None
fromfile = False fromfile = False
use_tcp = False
use_edns = True
force_miss = False
verbose = False
color_mode = False
hostname = 'wikipedia.org' hostname = 'wikipedia.org'
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:", opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:TevCm",
["help", "file=", "count=", "type=", "wait="]) ["help", "file=", "count=", "type=", "wait=", "tcp", "edns", "verbose", "color",
"force-miss"])
except getopt.GetoptError as err: except getopt.GetoptError as err:
print(err) print(err)
usage() usage()
@ -154,50 +259,111 @@ def main():
fromfile = True fromfile = True
elif o in ("-w", "--wait"): elif o in ("-w", "--wait"):
waittime = int(a) waittime = int(a)
elif o in ("-m", "--cache-miss"):
force_miss = True
elif o in ("-t", "--type"): elif o in ("-t", "--type"):
dnsrecord = a dnsrecord = a
elif o in ("-T", "--tcp"):
use_tcp = True
elif o in ("-e", "--edns"):
use_edns = False
elif o in ("-C", "--color"):
color_mode = True
elif o in ("-v", "--verbose"):
verbose = True
else: else:
print("Invalid option: %s" % o) print("Invalid option: %s" % o)
usage() usage()
color = Colors(color_mode)
try: try:
if fromfile: if fromfile:
with open(inputfilename, 'rt') as flist: if inputfilename == '-':
f = flist.read().splitlines() # read from stdin
with sys.stdin as flist:
f = flist.read().splitlines()
else:
try:
with open(inputfilename, 'rt') as flist:
f = flist.read().splitlines()
except Exception as e:
print(e)
sys.exit(1)
else: else:
f = resolvers f = resolvers
if len(f) == 0: if len(f) == 0:
print("No nameserver specified") print("No nameserver specified")
f = [name.strip() for name in f] f = [name.strip() for name in f] # remove annoying blanks
f = [x for x in f if not x.startswith('#') and len(x)] # remove comments and empty entries
width = maxlen(f) width = maxlen(f)
blanks = (width - 5) * ' ' blanks = (width - 5) * ' '
print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%)') print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags')
print((60 + width) * '-') print((93 + width) * '-')
for server in f: for server in f:
# check if we have a valid dns server address # check if we have a valid dns server address
if server.lstrip() == '': # deal with empty lines
continue
server = server.replace(' ', '')
try: try:
ipaddress.ip_address(server) ipaddress.ip_address(server)
except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name
try: try:
s = socket.getaddrinfo(server, port=None)[1][4][0] resolver = socket.getaddrinfo(server, port=None)[1][4][0]
except OSError: except OSError:
print('Error: cannot resolve hostname:', server) print('Error: cannot resolve hostname:', server)
s = None resolver = None
except:
pass
else: else:
s = server resolver = server
if not s: if not resolver:
continue continue
(s, r_avg, r_min, r_max, r_stddev, r_lost_percent) = dnsping(hostname, s, dnsrecord, waittime,
count)
s = server.ljust(width + 1) try:
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %%%d" % ( (resolver, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags, ttl, answers) = dnsping(
s, r_avg, r_min, r_max, r_stddev, r_lost_percent), flush=True) hostname,
resolver,
dnsrecord,
waittime,
count,
use_tcp=use_tcp,
use_edns=use_edns,
force_miss=force_miss
)
except dns.resolver.NXDOMAIN:
print('%-15s NXDOMAIN' % server)
continue
except Exception as e:
print('%s: %s' % (server, e))
continue
resolver = server.ljust(width + 1)
text_flags = flags_to_text(flags)
s_ttl = str(ttl)
if s_ttl == "None":
s_ttl = "N/A"
if r_lost_percent > 0:
l_color = color.O
else:
l_color = color.N
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %s%%%-3d%s %-8s %21s" % (
resolver, r_avg, r_min, r_max, r_stddev, l_color, r_lost_percent, color.N, s_ttl, text_flags),
flush=True)
if verbose and hasattr(answers, 'response'):
ans_index = 1
for answer in answers.response.answer:
print("Answer %d [ %s%s%s ]" % (ans_index, color.G, answer, color.N))
ans_index += 1
print("")
except Exception as e: except Exception as e:
print('error: %s' % e) print('%s: %s' % (server, e))
sys.exit(1) sys.exit(1)

View File

@ -34,17 +34,20 @@ import sys
import time import time
from statistics import stdev from statistics import stdev
import dns.flags
import dns.rdatatype import dns.rdatatype
import dns.resolver import dns.resolver
__VERSION__ = 1.1 __author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__PROGNAME__ = os.path.basename(sys.argv[0]) __license__ = 'BSD'
__version__ = "1.6.4"
__progname__ = os.path.basename(sys.argv[0])
shutdown = False shutdown = False
def usage(): def usage():
print("""%s version %1.1f print("""%s version %s
usage: %s [-h] [-q] [-v] [-s server] [-p port] [-P port] [-S address] [-c count] [-t type] [-w wait] hostname usage: %s [-ehqv] [-s server] [-p port] [-P port] [-S address] [-c count] [-t type] [-w wait] hostname
-h --help Show this help -h --help Show this help
-q --quiet Quiet -q --quiet Quiet
-v --verbose Print actual dns response -v --verbose Print actual dns response
@ -55,10 +58,12 @@ usage: %s [-h] [-q] [-v] [-s server] [-p port] [-P port] [-S address] [-c count]
-6 --ipv6 Use IPv6 as default network protocol -6 --ipv6 Use IPv6 as default network protocol
-P --srcport Query source port number (default: 0) -P --srcport Query source port number (default: 0)
-S --srcip Query source IP address (default: default interface address) -S --srcip Query source IP address (default: default interface address)
-c --count Number of requests to send (default: 10) -c --count Number of requests to send (default: 10, 0 for infinity)
-w --wait Maximum wait time for a reply (default: 5) -w --wait Maximum wait time for a reply (default: 2 seconds)
-i --interval Time between each request (default: 1 seconds)
-t --type DNS request record type (default: A) -t --type DNS request record type (default: A)
""" % (__PROGNAME__, __VERSION__, __PROGNAME__)) -e --edns Disable EDNS0 (default: Enabled)
""" % (__progname__, __version__, __progname__))
sys.exit(0) sys.exit(0)
@ -82,7 +87,8 @@ def main():
# defaults # defaults
dnsrecord = 'A' dnsrecord = 'A'
count = 10 count = 10
timeout = 5 timeout = 2
interval = 1
quiet = False quiet = False
verbose = False verbose = False
dnsserver = dns.resolver.get_default_resolver().nameservers[0] dnsserver = dns.resolver.get_default_resolver().nameservers[0]
@ -90,16 +96,17 @@ def main():
src_port = 0 src_port = 0
src_ip = None src_ip = None
use_tcp = False use_tcp = False
use_edns = True
af = socket.AF_INET af = socket.AF_INET
hostname = 'wikipedia.org' hostname = 'wikipedia.org'
try: try:
opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:vp:P:S:T46", opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:i:vp:P:S:T46e",
["help", "output=", "count=", "server=", "quiet", "type=", "wait=", "verbose", ["help", "count=", "server=", "quiet", "type=", "wait=", "interval=", "verbose",
"port", "dstport=", "srcip=", "tcp", "ipv4", "ipv6"]) "port=", "srcip=", "tcp", "ipv4", "ipv6", "srcport=", "edns"])
except getopt.GetoptError as err: except getopt.GetoptError as err:
# print help information and exit: # print help information and exit:
print(err) # will print something like "option -a not recognized" print(err, file=sys.stderr) # will print something like "option -a not recognized"
usage() usage()
if args and len(args) == 1: if args and len(args) == 1:
@ -111,7 +118,7 @@ def main():
if o in ("-h", "--help"): if o in ("-h", "--help"):
usage() usage()
elif o in ("-c", "--count"): elif o in ("-c", "--count"):
count = int(a) count = abs(int(a))
elif o in ("-v", "--verbose"): elif o in ("-v", "--verbose"):
verbose = True verbose = True
elif o in ("-s", "--server"): elif o in ("-s", "--server"):
@ -123,6 +130,8 @@ def main():
verbose = False verbose = False
elif o in ("-w", "--wait"): elif o in ("-w", "--wait"):
timeout = int(a) timeout = int(a)
elif o in ("-i", "--interval"):
interval = int(a)
elif o in ("-t", "--type"): elif o in ("-t", "--type"):
dnsrecord = a dnsrecord = a
elif o in ("-T", "--tcp"): elif o in ("-T", "--tcp"):
@ -131,10 +140,12 @@ def main():
af = socket.AF_INET af = socket.AF_INET
elif o in ("-6", "--ipv6"): elif o in ("-6", "--ipv6"):
af = socket.AF_INET6 af = socket.AF_INET6
elif o in ("-e", "--edns"):
use_edns = False
elif o in ("-P", "--srcport"): elif o in ("-P", "--srcport"):
src_port = int(a) src_port = int(a)
if src_port < 1024: if src_port < 1024:
print("WARNING: Source ports below 1024 are only available to superuser") print("WARNING: Source ports below 1024 are only available to superuser", flush=True)
elif o in ("-S", "--srcip"): elif o in ("-S", "--srcip"):
src_ip = a src_ip = a
else: else:
@ -147,7 +158,7 @@ def main():
try: try:
dnsserver = socket.getaddrinfo(dnsserver, port=None, family=af)[1][4][0] dnsserver = socket.getaddrinfo(dnsserver, port=None, family=af)[1][4][0]
except OSError: except OSError:
print('Error: cannot resolve hostname:', dnsserver) print('Error: cannot resolve hostname:', dnsserver, file=sys.stderr, flush=True)
sys.exit(1) sys.exit(1)
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
@ -157,47 +168,61 @@ def main():
resolver.port = dst_port resolver.port = dst_port
resolver.retry_servfail = 0 resolver.retry_servfail = 0
if use_edns:
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
response_time = [] response_time = []
i = 0 i = 0
print("%s DNS: %s:%d, hostname: %s, rdatatype: %s" % (__PROGNAME__, dnsserver, dst_port, hostname, dnsrecord)) print("%s DNS: %s:%d, hostname: %s, rdatatype: %s" % (__progname__, dnsserver, dst_port, hostname, dnsrecord),
flush=True)
for i in range(count): while not shutdown:
if shutdown:
if 0 < count <= i:
break break
else:
i += 1
try: try:
stime = time.time() stime = time.perf_counter()
answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp, af=af) answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp,
etime = time.time() raise_on_no_answer=False)
etime = time.perf_counter()
except dns.resolver.NoNameservers as e: except dns.resolver.NoNameservers as e:
if not quiet: if not quiet:
print("No response to dns request") print("No response to dns request", file=sys.stderr, flush=True)
if verbose: if verbose:
print("error:", e) print("error:", e, file=sys.stderr, flush=True)
sys.exit(1) sys.exit(1)
except dns.resolver.NXDOMAIN as e: except dns.resolver.NXDOMAIN as e:
if not quiet: if not quiet:
print("Hostname does not exist") print("Hostname does not exist", file=sys.stderr, flush=True)
if verbose: if verbose:
print("Error:", e) print("Error:", e, file=sys.stderr, flush=True)
sys.exit(1) sys.exit(1)
except dns.resolver.Timeout: except dns.resolver.Timeout:
if not quiet: if not quiet:
print("Request timeout") print("Request timeout", flush=True)
pass pass
except dns.resolver.NoAnswer: except dns.resolver.NoAnswer:
if not quiet: if not quiet:
print("No answer") print("No answer", flush=True)
pass pass
else: else:
elapsed = (etime - stime) * 1000 # convert to milliseconds elapsed = answers.response.time * 1000 # convert to milliseconds
response_time.append(elapsed) response_time.append(elapsed)
if not quiet: if not quiet:
print( print(
"%d bytes from %s: seq=%-3d time=%3.3f ms" % ( "%d bytes from %s: seq=%-3d time=%.3f ms" % (
len(str(answers.rrset)), dnsserver, i, elapsed)) len(str(answers.rrset)), dnsserver, i, elapsed), flush=True)
if verbose: if verbose:
print(answers.rrset) print(answers.rrset, flush=True)
print("flags:", dns.flags.to_text(answers.response.flags), flush=True)
time_to_next = (stime + interval) - etime
if time_to_next > 0:
time.sleep(time_to_next)
r_sent = i + 1 r_sent = i + 1
r_received = len(response_time) r_received = len(response_time)
@ -217,9 +242,10 @@ def main():
r_avg = 0 r_avg = 0
r_stddev = 0 r_stddev = 0
print('\n--- %s dnsping statistics ---' % dnsserver) print('\n--- %s dnsping statistics ---' % dnsserver, flush=True)
print('%d requests transmitted, %d responses received, %3.0f%% lost' % (r_sent, r_received, r_lost_percent)) print('%d requests transmitted, %d responses received, %.0f%% lost' % (r_sent, r_received, r_lost_percent),
print('min=%3.3f ms, avg=%3.3f ms, max=%3.3f ms, stddev=%3.3f ms' % (r_min, r_avg, r_max, r_stddev)) flush=True)
print('min=%.3f ms, avg=%.3f ms, max=%.3f ms, stddev=%.3f ms' % (r_min, r_avg, r_max, r_stddev), flush=True)
if __name__ == '__main__': if __name__ == '__main__':

@ -1 +0,0 @@
Subproject commit bc7445dcd5fe3917f96cac07c7b1f2f645699204

View File

@ -35,13 +35,35 @@ import socket
import sys import sys
import time import time
import dns.query
import dns.rdatatype import dns.rdatatype
import dns.resolver import dns.resolver
from cymruwhois import cymruwhois
import cymruwhois
# Global Variables
__author__ = 'Babak Farrokhi (babak@farrokhi.net)' __author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD' __license__ = 'BSD'
__version__ = 1.3 __version__ = "1.6.4"
_ttl = None
quiet = False
whois_cache = {}
shutdown = False
# Constants
__progname__ = os.path.basename(sys.argv[0])
WHOIS_CACHE = 'whois.cache'
class CustomSocket(socket.socket):
def __init__(self, *args, **kwargs):
super(CustomSocket, self).__init__(*args, **kwargs)
def sendto(self, *args, **kwargs):
global _ttl
if _ttl:
self.setsockopt(socket.SOL_IP, socket.IP_TTL, _ttl)
super(CustomSocket, self).sendto(*args, **kwargs)
def test_import(): def test_import():
@ -49,11 +71,6 @@ def test_import():
pass pass
# Constants
__PROGNAME__ = os.path.basename(sys.argv[0])
WHOIS_CACHE = 'whois.cache'
class Colors(object): class Colors(object):
N = '\033[m' # native N = '\033[m' # native
R = '\033[31m' # red R = '\033[31m' # red
@ -70,50 +87,58 @@ class Colors(object):
self.B = '' self.B = ''
# Globarl Variables def whois_lookup(ip):
shutdown = False
def whoisrecord(ip):
try: try:
global whois_cache
currenttime = time.time() currenttime = time.time()
ts = currenttime ts = currenttime
if ip in whois: if ip in whois_cache:
ASN, ts = whois[ip] asn, ts = whois_cache[ip]
else: else:
ts = 0 ts = 0
if ((currenttime - ts) > 36000): if (currenttime - ts) > 36000:
c = cymruwhois.Client() c = cymruwhois.Client()
ASN = c.lookup(ip) asn = c.lookup(ip)
whois[ip] = (ASN, currenttime) whois_cache[ip] = (asn, currenttime)
return ASN return asn
except Exception as e: except Exception as e:
return e return e
try: def load_whois_cache(cachefile):
pkl_file = open(WHOIS_CACHE, 'rb')
try: try:
whois = pickle.load(pkl_file) pkl_file = open(cachefile, 'rb')
except EOFError: try:
whois = pickle.load(pkl_file)
pkl_file.close()
except Exception:
whois = {}
except IOError:
whois = {} whois = {}
except IOError: return whois
whois = {}
def save_whois_cache(cachefile, whois_data):
pkl_file = open(cachefile, 'wb')
pickle.dump(whois_data, pkl_file)
pkl_file.close()
def usage(): def usage():
print('%s version %1.1f\n' % (__PROGNAME__, __version__)) print('%s version %s\n' % (__progname__, __version__))
print('usage: %s [-h] [-q] [-a] [-s server] [-p port] [-c count] [-t type] [-w wait] hostname' % __PROGNAME__) print('usage: %s [-aeqhCx] [-s server] [-p port] [-c count] [-t type] [-w wait] hostname' % __progname__)
print(' -h --help Show this help') print(' -h --help Show this help')
print(' -q --quiet Quiet') print(' -q --quiet Quiet')
print(' -e --expert Print expert hints if available') print(' -x --expert Print expert hints if available')
print(' -a --asn Turn on AS# lookups for each hop encountered') print(' -a --asn Turn on AS# lookups for each hop encountered')
print(' -s --server DNS server to use (default: first system resolver)') print(' -s --server DNS server to use (default: first system resolver)')
print(' -p --port DNS server port number (default: 53)') print(' -p --port DNS server port number (default: 53)')
print(' -S --srcip Query source IP address (default: default interface address)')
print(' -c --count Maximum number of hops (default: 30)') print(' -c --count Maximum number of hops (default: 30)')
print(' -w --wait Maximum wait time for a reply (default: 5)') print(' -w --wait Maximum wait time for a reply (default: 2)')
print(' -t --type DNS request record type (default: A)') print(' -t --type DNS request record type (default: A)')
print(' -C --color Print colorful output') print(' -C --color Print colorful output')
print(' -e --edns Disable EDNS0 (Default: Enabled)')
print(' ') print(' ')
sys.exit() sys.exit()
@ -132,6 +157,7 @@ def expert_report(trace_path, color_mode):
print(" [*] empty trace - should not happen") print(" [*] empty trace - should not happen")
return return
private_network_radius = 4 # number of hops we assume we are still inside our local network
prev_hop = None prev_hop = None
if len(trace_path) > 1: if len(trace_path) > 1:
prev_hop = trace_path[-2] prev_hop = trace_path[-2]
@ -141,27 +167,34 @@ def expert_report(trace_path, color_mode):
" %s[*]%s path too short (possible DNS hijacking, unless it is a local DNS resolver)" % (color.R, color.N)) " %s[*]%s path too short (possible DNS hijacking, unless it is a local DNS resolver)" % (color.R, color.N))
return return
if prev_hop == '*': if prev_hop == '*' and len(trace_path) > private_network_radius:
print(" %s[*]%s public DNS server is next to an invisible hop (probably a firewall)" % (color.R, color.N)) print(" %s[*]%s public DNS server is next to an invisible hop (probably a firewall)" % (color.R, color.N))
return return
if prev_hop and ipaddress.ip_address(prev_hop).is_private: if prev_hop and len(trace_path) > private_network_radius and ipaddress.ip_address(prev_hop).is_private:
print(" %s[*]%s public DNS server is next to a private IP address (possible hijacking)" % (color.R, color.N)) print(" %s[*]%s public DNS server is next to a private IP address (possible hijacking)" % (color.R, color.N))
return return
if prev_hop and ipaddress.ip_address(prev_hop).is_reserved: if prev_hop and len(trace_path) > private_network_radius and ipaddress.ip_address(prev_hop).is_reserved:
print(" %s[*]%s public DNS server is next to a reserved IP address (possible hijacking)" % (color.R, color.N)) print(" %s[*]%s public DNS server is next to a reserved IP address (possible hijacking)" % (color.R, color.N))
return return
## no expert info available # no expert info available
print(" %s[*]%s No expert hint available for this trace" % (color.G, color.N)) print(" %s[*]%s No expert hint available for this trace" % (color.G, color.N))
def ping(resolver, hostname, dnsrecord, ttl): def ping(resolver, hostname, dnsrecord, ttl, src_ip, use_edns=False):
global _ttl
reached = False reached = False
dns.query.socket_factory = CustomSocket
_ttl = ttl
if use_edns:
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
try: try:
resolver.query(hostname, dnsrecord, ipttl=ttl) resolver.query(hostname, dnsrecord, source=src_ip, raise_on_no_answer=False)
except dns.resolver.NoNameservers as e: except dns.resolver.NoNameservers as e:
if not quiet: if not quiet:
@ -179,8 +212,8 @@ def ping(resolver, hostname, dnsrecord, ttl):
pass pass
except SystemExit: except SystemExit:
pass pass
except: except Exception as e:
print("unxpected error: ", sys.exc_info()[0]) print("unxpected error: ", e)
sys.exit(1) sys.exit(1)
else: else:
reached = True reached = True
@ -202,20 +235,21 @@ def main():
dnsrecord = 'A' dnsrecord = 'A'
count = 30 count = 30
timeout = 1 timeout = 2
quiet = False
dnsserver = dns.resolver.get_default_resolver().nameservers[0] dnsserver = dns.resolver.get_default_resolver().nameservers[0]
dest_port = 53 dest_port = 53
src_ip = None
hops = 0 hops = 0
as_lookup = False as_lookup = False
expert_mode = False expert_mode = False
should_resolve = True should_resolve = True
use_edns = True
color_mode = False color_mode = False
try: try:
opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:t:w:p:neC", opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:S:t:w:p:nexC",
["help", "count=", "server=", "quiet", "type=", "wait=", "asn", "port", "expert", ["help", "count=", "server=", "quiet", "type=", "wait=", "asn", "port", "expert",
"color"]) "color", "srcip="])
except getopt.GetoptError as err: except getopt.GetoptError as err:
# print help information and exit: # print help information and exit:
print(err) # will print something like "option -a not recognized" print(err) # will print something like "option -a not recognized"
@ -231,12 +265,14 @@ def main():
usage() usage()
elif o in ("-c", "--count"): elif o in ("-c", "--count"):
count = int(a) count = int(a)
elif o in ("-e", "--expert"): elif o in ("-x", "--expert"):
expert_mode = True expert_mode = True
elif o in ("-s", "--server"): elif o in ("-s", "--server"):
dnsserver = a dnsserver = a
elif o in ("-q", "--quiet"): elif o in ("-q", "--quiet"):
quiet = True quiet = True
elif o in ("-S", "--srcip"):
src_ip = a
elif o in ("-w", "--wait"): elif o in ("-w", "--wait"):
timeout = int(a) timeout = int(a)
elif o in ("-t", "--type"): elif o in ("-t", "--type"):
@ -249,14 +285,27 @@ def main():
should_resolve = False should_resolve = False
elif o in ("-a", "--asn"): elif o in ("-a", "--asn"):
as_lookup = True as_lookup = True
elif o in ("-e", "--edns"):
use_edns = False
else: else:
usage() usage()
color = Colors(color_mode) color = Colors(color_mode)
# check if we have a valid dns server address
try:
ipaddress.ip_address(dnsserver)
except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name
try:
dnsserver = socket.getaddrinfo(dnsserver, port=None, family=socket.AF_INET)[1][4][0]
except OSError:
print('Error: cannot resolve hostname:', dnsserver)
sys.exit(1)
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
resolver.nameservers = [dnsserver] resolver.nameservers = [dnsserver]
resolver.timeout = timeout resolver.timeout = timeout
resolver.port = dest_port
resolver.lifetime = timeout resolver.lifetime = timeout
resolver.retry_servfail = 0 resolver.retry_servfail = 0
@ -267,7 +316,8 @@ def main():
trace_path = [] trace_path = []
if not quiet: if not quiet:
print("%s DNS: %s:%d, hostname: %s, rdatatype: %s" % (__PROGNAME__, dnsserver, dest_port, hostname, dnsrecord)) print("%s DNS: %s:%d, hostname: %s, rdatatype: %s" % (__progname__, dnsserver, dest_port, hostname, dnsrecord),
flush=True)
while True: while True:
if shutdown: if shutdown:
@ -291,26 +341,26 @@ def main():
curr_host = None curr_host = None
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: # dispatch dns lookup to another thread with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: # dispatch dns lookup to another thread
stime = time.time() stime = time.perf_counter()
thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl) thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl, src_ip=src_ip, use_edns=use_edns)
try: # expect ICMP response try: # expect ICMP response
_, curr_addr = icmp_socket.recvfrom(512) _, curr_addr = icmp_socket.recvfrom(512)
curr_addr = curr_addr[0] curr_addr = curr_addr[0]
except socket.error: except socket.error:
etime = time.time() etime = time.perf_counter()
pass pass
finally: finally:
etime = time.time() etime = time.perf_counter()
icmp_socket.close() icmp_socket.close()
reached = thr.result() reached = thr.result()
if reached: if reached:
curr_addr = dnsserver curr_addr = dnsserver
stime = time.time() # need to recalculate elapsed time for last hop without waiting for an icmp error reply stime = time.perf_counter() # need to recalculate elapsed time for last hop asynchronously
ping(resolver, hostname, dnsrecord, ttl) ping(resolver, hostname, dnsrecord, ttl, src_ip=src_ip, use_edns=use_edns)
etime = time.time() etime = time.perf_counter()
elapsed = abs(etime - stime) * 1000 # convert to milliseconds elapsed = abs(etime - stime) * 1000 # convert to milliseconds
@ -330,11 +380,11 @@ def main():
if curr_addr: if curr_addr:
as_name = "" as_name = ""
if as_lookup: if as_lookup:
ASN = whoisrecord(curr_addr) asn = whois_lookup(curr_addr)
as_name = '' as_name = ''
try: try:
if ASN and ASN.asn != "NA": if asn and asn.asn != "NA":
as_name = "[%s %s] " % (ASN.asn, ASN.owner) as_name = "[AS%s %s] " % (asn.asn, asn.owner)
except AttributeError: except AttributeError:
if shutdown: if shutdown:
sys.exit(0) sys.exit(0)
@ -342,15 +392,18 @@ def main():
c = color.N # default c = color.N # default
if curr_addr != '*': if curr_addr != '*':
IP = ipaddress.ip_address(curr_addr) try:
if IP.is_private: IP = ipaddress.ip_address(curr_addr)
c = color.R if IP.is_private:
if IP.is_reserved: c = color.R
c = color.B if IP.is_reserved:
if curr_addr == dnsserver: c = color.B
c = color.G if curr_addr == dnsserver:
c = color.G
except:
pass
print("%d\t%s (%s%s%s) %s%d ms" % (ttl, curr_name, c, curr_addr, color.N, as_name, elapsed), flush=True) print("%d\t%s (%s%s%s) %s%.3f ms" % (ttl, curr_name, c, curr_addr, color.N, as_name, elapsed), flush=True)
trace_path.append(curr_addr) trace_path.append(curr_addr)
else: else:
print("%d\t *" % ttl, flush=True) print("%d\t *" % ttl, flush=True)
@ -367,7 +420,7 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
try: try:
whois_cache = load_whois_cache(WHOIS_CACHE)
main() main()
finally: finally:
pkl_file = open(WHOIS_CACHE, 'wb') save_whois_cache(WHOIS_CACHE, whois_cache)
pickle.dump(whois, pkl_file)

View File

@ -1,14 +1,42 @@
8.8.8.8 #Cloudflare
8.8.4.4 1.0.0.1
2001:4860:4860::8888 1.1.1.1
2001:4860:4860::8844 2606:4700:4700::1001
2606:4700:4700::1111
#SafeDNS
195.46.39.39
195.46.39.40
#OpenDNS
208.67.220.220
208.67.222.222
2620:0:ccc::2
2620:0:ccd::2
#DYN DNS
216.146.35.35
216.146.36.36
#Level3
209.244.0.3
209.244.0.4
4.2.2.1 4.2.2.1
4.2.2.2 4.2.2.2
4.2.2.3 4.2.2.3
4.2.2.4 4.2.2.4
4.2.2.5 4.2.2.5
209.244.0.3
209.244.0.4
195.46.39.39
195.46.39.40
#freenom world
80.80.80.80
80.80.81.81
#Google
8.8.4.4
8.8.8.8
2001:4860:4860::8844
2001:4860:4860::8888
#PCH's Quad9
9.9.9.9
2620:fe::fe
149.112.112.112

36
public-v4.txt Normal file
View File

@ -0,0 +1,36 @@
#Cloudflare
1.0.0.1
1.1.1.1
#SafeDNS
195.46.39.39
195.46.39.40
#OpenDNS
208.67.220.220
208.67.222.222
#DYN DNS
216.146.35.35
216.146.36.36
#Level3
209.244.0.3
209.244.0.4
4.2.2.1
4.2.2.2
4.2.2.3
4.2.2.4
4.2.2.5
#freenom world
80.80.80.80
80.80.81.81
#Google
8.8.4.4
8.8.8.8
#PCH's Quad9
9.9.9.9
149.112.112.112

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
dnspython>=1.15.0
cymruwhois>=1.6

13
rootservers.txt Normal file
View File

@ -0,0 +1,13 @@
a.root-servers.net
b.root-servers.net
c.root-servers.net
d.root-servers.net
e.root-servers.net
f.root-servers.net
g.root-servers.net
h.root-servers.net
i.root-servers.net
j.root-servers.net
k.root-servers.net
l.root-servers.net
m.root-servers.net

View File

@ -1,29 +1,45 @@
from setuptools import setup, find_packages from setuptools import setup, find_packages
setup( setup(
name = "dnsdiag", name="dnsdiag",
version = "1.3.3", version="1.6.4",
packages = find_packages(), packages=find_packages(),
scripts = ['dnsping.py', 'dnstraceroute.py', 'dnseval.py'], scripts=["dnseval.py", "dnsping.py", "dnstraceroute.py"],
install_requires=['dnspython>=1.15.0', 'cymruwhois>=1.6'],
classifiers=[ classifiers=[
"Topic :: System :: Networking", "Topic :: System :: Networking",
"Environment :: Console", "Environment :: Console",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: BSD License", "License :: OSI Approved :: BSD License",
'Programming Language :: Python :: 3.4', "Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: Name Service (DNS)",
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Operating System :: OS Independent", "Operating System :: OS Independent",
], ],
author = "Babak Farrokhi", author="Babak Farrokhi",
author_email = "babak@farrokhi.net", author_email="babak@farrokhi.net",
description = "DNS Diagnostics and measurement tools (ping, traceroute)", description="DNS Diagnostics and measurement tools (ping, traceroute)",
long_description = """ long_description="""
DNSDiag provides a handful of tools to measure and diagnose your DNS DNSDiag provides a handful of tools to measure and diagnose your DNS
performance and integrity. Using dnsping, dnstraceroute and dnseval tools performance and integrity. Using dnsping, dnstraceroute and dnseval tools
you can measure your DNS response quality from delay and loss perspective you can measure your DNS response quality from delay and loss perspective
as well as tracing the path your DNS query takes to get to DNS server. as well as tracing the path your DNS query takes to get to DNS server.
""", """,
license = "BSD", license="BSD",
keywords = "dns traceroute ping", keywords="dns traceroute ping performance",
url = "https://dnsdiag.org/", url="https://dnsdiag.org/",
entry_points={
'console_scripts': [
'dnsping = dnsping:main',
'dnstraceroute = dnstraceroute:main',
'dnseval = dnseval:main',
]
}
) )