40 Commits

Author SHA1 Message Date
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
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
13 changed files with 182 additions and 110 deletions

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "cymruwhois"]
path = cymruwhois
url = https://github.com/JustinAzoff/python-cymruwhois.git
[submodule "dnspython-orig"]
path = dnspython
url = https://github.com/rthalley/dnspython/

View File

@ -5,6 +5,6 @@ python:
- "3.5"
- "3.5-dev" # 3.5 development branch
- "nightly" # currently points to 3.6-dev
install: pip install --process-dependency-links dnsdiag
install: "pip install -r requirements.txt"
script: nosetests dnstraceroute.py

View File

@ -28,10 +28,9 @@ of view.
# prerequisites
This script requires python3 as well as latest
[dnspython](http://www.dnspython.org/) and
[cymruwhois](https://pythonhosted.org/cymruwhois/). Please note that
"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.
[cymruwhois](https://pythonhosted.org/cymruwhois/).
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
@ -61,15 +60,15 @@ From time to time, binary version will be released for Windows, Mac OS X and Lin
dnsping pings a DNS resolver by sending an arbitrary DNS query for given number
of times:
```
% ./dnsping.py -c 3 -s 8.8.8.8 -t MX wikipedia.org
dnsping.py DNS: 8.8.8.8:53, hostname: wikipedia.org, rdatatype: MX
101 bytes from 8.8.8.8: seq=0 time=262.896 ms
101 bytes from 8.8.8.8: seq=1 time=305.608 ms
101 bytes from 8.8.8.8: seq=2 time=307.221 ms
% ./dnsping.py -c 3 -t AAAA -s 8.8.8.8 dnsdiag.org
dnsping.py DNS: 8.8.8.8:53, hostname: dnsdiag.org, rdatatype: AAAA
4 bytes from 8.8.8.8: seq=0 time=123.509 ms
4 bytes from 8.8.8.8: seq=1 time=115.726 ms
4 bytes from 8.8.8.8: seq=2 time=117.351 ms
--- 8.8.8.8 dnsping statistics ---
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
jitter (stddev)
@ -81,23 +80,16 @@ it to your actual network traceroute and make sure your DNS traffic is not
routed to any unwanted path.
```
% ./dnstraceroute.py --expert -s 8.8.4.4 yahoo.com
dnstraceroute.py DNS: 8.8.4.4:53, hostname: yahoo.com, rdatatype: A
1 204.109.58.53 (204.109.58.53) 1 ms
2 208.79.80.5 (208.79.80.5) 1 ms
3 162.223.13.177 (162.223.13.177) 1 ms
4 208.79.80.254 (208.79.80.254) 7 ms
5 eqixva-google-gige.google.com (206.126.236.21) 7 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)
% ./dnstraceroute.py --expert -C -t A -s 8.8.4.4 facebook.com
dnstraceroute.py DNS: 8.8.4.4:53, hostname: facebook.com, rdatatype: A
1 192.168.0.1 (192.168.0.1) 1 ms
2 192.168.28.177 (192.168.28.177) 4 ms
3 192.168.0.1 (192.168.0.1) 693 ms
4 172.19.4.17 (172.19.4.17) 3 ms
5 google-public-dns-b.google.com (8.8.4.4) 8 ms
=== 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).
@ -107,17 +99,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
DNS servers at once:
```
% ./dnseval.py -f public-v4.txt -c3 ripe.net
server avg(ms) min(ms) max(ms) stddev(ms) lost(%) flags
----------------------------------------------------------------------------------
8.8.8.8 210.225 109.864 407.420 170.785 %0 QR RD RA
8.8.4.4 107.850 93.134 120.578 13.830 %0 QR RD RA
ns.ripe.net 118.911 114.874 123.389 4.275 %0 QR AA RD
4.2.2.1 104.380 102.449 106.588 2.083 %0 QR RD RA
4.2.2.2 131.056 99.143 193.711 54.264 %0 QR RD RA
4.2.2.3 98.956 97.463 100.310 1.429 %0 QR RD RA
4.2.2.4 223.173 97.418 463.728 208.398 %0 QR RD RA
4.2.2.5 104.290 97.264 117.878 11.770 %0 QR RD RA
% ./dnseval.py -t AAAA -f public-v4.txt -c10 yahoo.com
server avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags
------------------------------------------------------------------------------------------------------
8.8.8.8 270.791 215.599 307.498 40.630 %0 298 QR -- -- RD RA -- --
8.8.4.4 222.955 171.753 307.251 60.481 %10 291 QR -- -- RD RA -- --
ns.ripe.net 174.855 160.949 187.458 10.099 %0 289 QR -- -- RD RA -- --
4.2.2.1 172.798 163.892 189.918 7.823 %0 287 QR -- -- RD RA -- --
4.2.2.2 178.594 169.158 184.696 5.067 %0 285 QR -- -- RD RA -- --
4.2.2.3 153.574 138.509 173.439 12.015 %0 284 QR -- -- RD RA -- --
4.2.2.4 153.182 141.023 162.323 6.700 %0 282 QR -- -- RD RA -- --
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
@ -125,7 +121,7 @@ ns.ripe.net 118.911 114.874 123.389 4.275 %0 QR AA
Babak 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/)
@ -133,8 +129,3 @@ Babak Farrokhi
dnsdiag is released under a 2 clause BSD license.
### Credits
- [@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
- 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

1
dns
View File

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

View File

@ -25,9 +25,10 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import getopt
import ipaddress
import os
import signal
import socket
import sys
@ -37,7 +38,7 @@ from statistics import stdev
import dns.rdatatype
import dns.resolver
__VERSION__ = 1.4
__VERSION__ = 1.6
__PROGNAME__ = os.path.basename(sys.argv[0])
shutdown = False
@ -51,8 +52,11 @@ usage: %s [-h] [-f server-list] [-c count] [-t type] [-w wait] hostname
-h --help show this help
-f --file dns server list to use (default: system resolvers)
-c --count number of requests to send (default: 10)
-w --wait maximum wait time for a reply (default: 5)
-w --wait maximum wait time for a reply (default: 2)
-t --type DNS request record type (default: A)
-T --tcp Use TCP instead of UDP
-e --edns Disable EDNS0 (Default: Enabled)
-v --verbose Print actual dns response
""" % (__PROGNAME__, __VERSION__, __PROGNAME__))
sys.exit()
@ -114,15 +118,17 @@ def flags_to_text(flags):
return ' '.join(text_flags)
def dnsping(host, server, dnsrecord, timeout, count):
def dnsping(host, server, dnsrecord, timeout, count, use_tcp=False, use_edns=False):
resolver = dns.resolver.Resolver()
resolver.nameservers = [server]
resolver.timeout = timeout
resolver.lifetime = timeout
resolver.retry_servfail = 0
flags = 0
ttl = None
answers = None
resolver.use_edns(edns=True, payload=0, ednsflags=8)
if use_edns:
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
response_times = []
i = 0
@ -131,9 +137,10 @@ def dnsping(host, server, dnsrecord, timeout, count):
if shutdown: # user pressed CTRL+C
break
try:
stime = time.time()
answers = resolver.query(host, dnsrecord) # todo: response validation in future
etime = time.time()
stime = time.perf_counter()
answers = resolver.query(host, dnsrecord, tcp=use_tcp,
raise_on_no_answer=False) # todo: response validation in future
etime = time.perf_counter()
except (dns.resolver.NoNameservers, dns.resolver.NoAnswer):
break
except dns.resolver.Timeout:
@ -160,10 +167,12 @@ def dnsping(host, server, dnsrecord, timeout, count):
r_avg = 0
r_stddev = 0
if answers:
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
return server, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags, ttl, answers
def main():
@ -179,14 +188,17 @@ def main():
# defaults
dnsrecord = 'A'
count = 10
waittime = 5
waittime = 2
inputfilename = None
fromfile = False
use_tcp = False
use_edns = True
verbose = False
hostname = 'wikipedia.org'
try:
opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:",
["help", "file=", "count=", "type=", "wait="])
opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:Tev",
["help", "file=", "count=", "type=", "wait=", "tcp", "edns", "verbose"])
except getopt.GetoptError as err:
print(err)
usage()
@ -208,6 +220,12 @@ def main():
waittime = int(a)
elif o in ("-t", "--type"):
dnsrecord = a
elif o in ("-T", "--tcp"):
use_tcp = True
elif o in ("-e", "--edns"):
use_edns = False
elif o in ("-v", "--verbose"):
verbose = True
else:
print("Invalid option: %s" % o)
usage()
@ -224,8 +242,8 @@ def main():
f = [name.strip() for name in f]
width = maxlen(f)
blanks = (width - 5) * ' '
print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%) flags')
print((84 + width) * '-')
print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags')
print((93 + width) * '-')
for server in f:
# check if we have a valid dns server address
if server.lstrip() == '': # deal with empty lines
@ -246,16 +264,39 @@ def main():
if not s:
continue
(s, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags) = dnsping(hostname, s, dnsrecord, waittime,
count)
try:
(s, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags, ttl, answers) = dnsping(hostname, s,
dnsrecord,
waittime,
count,
use_tcp=use_tcp,
use_edns=use_edns)
except dns.resolver.NXDOMAIN:
print('%-15s NXDOMAIN' % server)
continue
except Exception as e:
print('%s: %s' % (server, e))
continue
s = server.ljust(width + 1)
text_flags = flags_to_text(flags)
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %%%-3d %25s" % (
s, r_avg, r_min, r_max, r_stddev, r_lost_percent, text_flags), flush=True)
s_ttl = str(ttl)
if s_ttl == "None":
s_ttl = "N/A"
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %%%-3d %-8s %21s" % (
s, r_avg, r_min, r_max, r_stddev, r_lost_percent, s_ttl, text_flags), flush=True)
if verbose:
ans_index = 1
for answer in answers.response.answer:
print("Answer %d [ %s ]" % (ans_index, answer))
ans_index += 1
print("")
except Exception as e:
print('error: %s' % e)
print('%s: %s' % (server, e))
sys.exit(1)

View File

@ -38,14 +38,14 @@ import dns.flags
import dns.rdatatype
import dns.resolver
__VERSION__ = 1.4
__VERSION__ = 1.6
__PROGNAME__ = os.path.basename(sys.argv[0])
shutdown = False
def usage():
print("""%s version %1.1f
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
-q --quiet Quiet
-v --verbose Print actual dns response
@ -57,8 +57,10 @@ usage: %s [-h] [-q] [-v] [-s server] [-p port] [-P port] [-S address] [-c count]
-P --srcport Query source port number (default: 0)
-S --srcip Query source IP address (default: default interface address)
-c --count Number of requests to send (default: 10)
-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: 0 seconds)
-t --type DNS request record type (default: A)
-e --edns Disable EDNS0 (default: Enabled)
""" % (__PROGNAME__, __VERSION__, __PROGNAME__))
sys.exit(0)
@ -83,7 +85,8 @@ def main():
# defaults
dnsrecord = 'A'
count = 10
timeout = 5
timeout = 2
interval = 0
quiet = False
verbose = False
dnsserver = dns.resolver.get_default_resolver().nameservers[0]
@ -91,13 +94,14 @@ def main():
src_port = 0
src_ip = None
use_tcp = False
use_edns = True
af = socket.AF_INET
hostname = 'wikipedia.org'
try:
opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:vp:P:S:T46",
["help", "count=", "server=", "quiet", "type=", "wait=", "verbose",
"port=", "srcip=", "tcp", "ipv4", "ipv6", "srcport="])
opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:i:vp:P:S:T46e",
["help", "count=", "server=", "quiet", "type=", "wait=", "interval=", "verbose",
"port=", "srcip=", "tcp", "ipv4", "ipv6", "srcport=", "edns"])
except getopt.GetoptError as err:
# print help information and exit:
print(err) # will print something like "option -a not recognized"
@ -124,6 +128,8 @@ def main():
verbose = False
elif o in ("-w", "--wait"):
timeout = int(a)
elif o in ("-i", "--interval"):
interval = int(a)
elif o in ("-t", "--type"):
dnsrecord = a
elif o in ("-T", "--tcp"):
@ -132,6 +138,8 @@ def main():
af = socket.AF_INET
elif o in ("-6", "--ipv6"):
af = socket.AF_INET6
elif o in ("-e", "--edns"):
use_edns = False
elif o in ("-P", "--srcport"):
src_port = int(a)
if src_port < 1024:
@ -158,6 +166,9 @@ def main():
resolver.port = dst_port
resolver.retry_servfail = 0
if use_edns:
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
response_time = []
i = 0
@ -167,10 +178,10 @@ def main():
if shutdown:
break
try:
stime = time.time()
stime = time.perf_counter()
answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp,
raise_on_no_answer=False)
etime = time.time()
etime = time.perf_counter()
except dns.resolver.NoNameservers as e:
if not quiet:
print("No response to dns request")
@ -196,12 +207,16 @@ def main():
response_time.append(elapsed)
if not quiet:
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))
if verbose:
print(answers.rrset)
print("flags:", dns.flags.to_text(answers.response.flags))
time_to_next = (stime + interval) - etime
if time_to_next > 0:
time.sleep(time_to_next)
r_sent = i + 1
r_received = len(response_time)
r_lost = r_sent - r_received
@ -221,8 +236,8 @@ def main():
r_stddev = 0
print('\n--- %s dnsping statistics ---' % dnsserver)
print('%d requests transmitted, %d responses received, %3.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))
print('%d requests transmitted, %d responses received, %.0f%% lost' % (r_sent, r_received, r_lost_percent))
print('min=%.3f ms, avg=%.3f ms, max=%.3f ms, stddev=%.3f ms' % (r_min, r_avg, r_max, r_stddev))
if __name__ == '__main__':

Submodule dnspython deleted from a55e23fbc4

View File

@ -43,7 +43,7 @@ from cymruwhois import cymruwhois
__author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD'
__version__ = 1.4
__version__ = 1.6
_ttl = None
quiet = False
@ -91,7 +91,7 @@ shutdown = False
def whoisrecord(ip):
try:
currenttime = time.time()
currenttime = time.perf_counter()
ts = currenttime
if ip in whois:
asn, ts = whois[ip]
@ -118,17 +118,18 @@ except IOError:
def usage():
print('%s version %1.1f\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(' -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(' -s --server DNS server to use (default: first system resolver)')
print(' -p --port DNS server port number (default: 53)')
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(' -C --color Print colorful output')
print(' -e --edns Disable EDNS0 (Default: Enabled)')
print(' ')
sys.exit()
@ -147,6 +148,7 @@ def expert_report(trace_path, color_mode):
print(" [*] empty trace - should not happen")
return
private_network_radius = 4 # number of hops we assume we are still inside our local network
prev_hop = None
if len(trace_path) > 1:
prev_hop = trace_path[-2]
@ -156,15 +158,15 @@ 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))
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))
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))
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))
return
@ -172,16 +174,18 @@ def expert_report(trace_path, color_mode):
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, use_edns=False):
global _ttl
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:
resolver.query(hostname, dnsrecord)
resolver.query(hostname, dnsrecord, raise_on_no_answer=False)
except dns.resolver.NoNameservers as e:
if not quiet:
@ -199,8 +203,8 @@ def ping(resolver, hostname, dnsrecord, ttl):
pass
except SystemExit:
pass
except:
print("unxpected error: ", sys.exc_info()[0])
except Exception as e:
print("unxpected error: ", e)
sys.exit(1)
else:
reached = True
@ -222,17 +226,18 @@ def main():
dnsrecord = 'A'
count = 30
timeout = 1
timeout = 2
dnsserver = dns.resolver.get_default_resolver().nameservers[0]
dest_port = 53
hops = 0
as_lookup = False
expert_mode = False
should_resolve = True
use_edns = True
color_mode = False
try:
opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:t:w:p:neC",
opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:t:w:p:nexC",
["help", "count=", "server=", "quiet", "type=", "wait=", "asn", "port", "expert",
"color"])
except getopt.GetoptError as err:
@ -250,7 +255,7 @@ def main():
usage()
elif o in ("-c", "--count"):
count = int(a)
elif o in ("-e", "--expert"):
elif o in ("-x", "--expert"):
expert_mode = True
elif o in ("-s", "--server"):
dnsserver = a
@ -268,11 +273,23 @@ def main():
should_resolve = False
elif o in ("-a", "--asn"):
as_lookup = True
elif o in ("-e", "--edns"):
use_edns = False
else:
usage()
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.nameservers = [dnsserver]
resolver.timeout = timeout
@ -286,7 +303,8 @@ def main():
trace_path = []
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:
if shutdown:
@ -310,26 +328,26 @@ def main():
curr_host = None
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: # dispatch dns lookup to another thread
stime = time.time()
thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl)
stime = time.perf_counter()
thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl, use_edns=use_edns)
try: # expect ICMP response
_, curr_addr = icmp_socket.recvfrom(512)
curr_addr = curr_addr[0]
except socket.error:
etime = time.time()
etime = time.perf_counter()
pass
finally:
etime = time.time()
etime = time.perf_counter()
icmp_socket.close()
reached = thr.result()
if reached:
curr_addr = dnsserver
stime = time.time() # need to recalculate elapsed time for last hop without waiting for an icmp error reply
ping(resolver, hostname, dnsrecord, ttl)
etime = time.time()
stime = time.perf_counter() # need to recalculate elapsed time for last hop without waiting for an icmp error reply
ping(resolver, hostname, dnsrecord, ttl, use_edns=use_edns)
etime = time.perf_counter()
elapsed = abs(etime - stime) * 1000 # convert to milliseconds
@ -369,7 +387,7 @@ def main():
if curr_addr == dnsserver:
c = color.G
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)
else:
print("%d\t *" % ttl, flush=True)

View File

@ -11,4 +11,5 @@
209.244.0.4
195.46.39.39
195.46.39.40
216.146.35.35
216.146.36.36

View File

@ -0,0 +1 @@
dnspython==1.15.0

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

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="dnsdiag",
version="1.3.5",
version="1.6.0",
packages=find_packages(),
classifiers=[
"Topic :: System :: Networking",