Compare commits

..

45 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
13 changed files with 306 additions and 138 deletions

3
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

@ -1,10 +1,18 @@
language: python
python:
- "3.3"
- "3.4"
- "3.5"
- "3.5-dev" # 3.5 development branch
- "nightly" # currently points to 3.6-dev
sudo: false
install: "pip install -r requirements.txt"
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 cymruwhois/*.py
include LICENSE README.md TODO.md public-servers.txt public-v4.txt rootservers.txt

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
==================================================
@ -29,8 +29,6 @@ of view.
This script requires python3 as well as latest
[dnspython](http://www.dnspython.org/) and
[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
@ -43,13 +41,13 @@ There are several ways that you can use this toolset. However using the sourceco
```
git clone https://github.com/farrokhi/dnsdiag.git
cd dnsdiag
git submodule update --init
pip3 install -r requirements.txt
```
2. You can alternatively install the package using pip:
```
pip3 install --process-dependency-links dnsdiag
pip3 install dnsdiag
```
## From Binary
@ -99,7 +97,7 @@ 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 -t AAAA -f public-v4.txt -c10 yahoo.com
% ./dnseval.py -t AAAA -f public-servers.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 -- --
@ -129,3 +127,4 @@ Babak Farrokhi
dnsdiag is released under a 2 clause BSD license.
[![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)

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

View File

@ -33,31 +33,53 @@ import signal
import socket
import sys
import time
import random
import string
from statistics import stdev
import dns.rdatatype
import dns.resolver
__VERSION__ = 1.6
__PROGNAME__ = os.path.basename(sys.argv[0])
__author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD'
__version__ = "1.6.4"
__progname__ = os.path.basename(sys.argv[0])
shutdown = False
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():
print("""%s version %1.1f
print("""%s version %s
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: 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__))
-h --help Show this help
-f --file DNS server list to use (default: system resolvers)
-c --count Number of requests to send (default: 10)
-m --cache-miss Force cache miss measurement by prepending a random hostname
-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)
-C --color Print colorful output
-v --verbose Print actual dns response
""" % (__progname__, __version__, __progname__))
sys.exit()
@ -118,7 +140,13 @@ def flags_to_text(flags):
return ' '.join(text_flags)
def dnsping(host, server, dnsrecord, timeout, count, use_tcp=False, use_edns=False):
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.nameservers = [server]
resolver.timeout = timeout
@ -137,16 +165,26 @@ def dnsping(host, server, dnsrecord, timeout, count, use_tcp=False, use_edns=Fal
if shutdown: # user pressed CTRL+C
break
try:
if force_miss:
fqdn = "_dnsdiag_%s_.%s" % (random_string(), host)
else:
fqdn = host
stime = time.perf_counter()
answers = resolver.query(host, dnsrecord, tcp=use_tcp,
answers = resolver.query(fqdn, 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:
pass
except dns.resolver.NXDOMAIN:
etime = time.perf_counter()
if force_miss:
elapsed = (etime - stime) * 1000 # convert to milliseconds
response_times.append(elapsed)
else:
elapsed = (etime - stime) * 1000 # convert to milliseconds
elapsed = answers.response.time * 1000 # convert to milliseconds
response_times.append(elapsed)
r_sent = i + 1
@ -193,12 +231,15 @@ def main():
fromfile = False
use_tcp = False
use_edns = True
force_miss = False
verbose = False
color_mode = False
hostname = 'wikipedia.org'
try:
opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:Tev",
["help", "file=", "count=", "type=", "wait=", "tcp", "edns", "verbose"])
opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:TevCm",
["help", "file=", "count=", "type=", "wait=", "tcp", "edns", "verbose", "color",
"force-miss"])
except getopt.GetoptError as err:
print(err)
usage()
@ -218,28 +259,45 @@ def main():
fromfile = True
elif o in ("-w", "--wait"):
waittime = int(a)
elif o in ("-m", "--cache-miss"):
force_miss = True
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 ("-C", "--color"):
color_mode = True
elif o in ("-v", "--verbose"):
verbose = True
else:
print("Invalid option: %s" % o)
usage()
color = Colors(color_mode)
try:
if fromfile:
with open(inputfilename, 'rt') as flist:
f = flist.read().splitlines()
if inputfilename == '-':
# 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:
f = resolvers
if len(f) == 0:
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)
blanks = (width - 5) * ' '
print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags')
@ -253,25 +311,29 @@ def main():
ipaddress.ip_address(server)
except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name
try:
s = socket.getaddrinfo(server, port=None)[1][4][0]
resolver = socket.getaddrinfo(server, port=None)[1][4][0]
except OSError:
print('Error: cannot resolve hostname:', server)
s = None
resolver = None
except:
pass
else:
s = server
resolver = server
if not s:
if not resolver:
continue
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)
(resolver, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags, ttl, answers) = dnsping(
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
@ -279,19 +341,24 @@ def main():
print('%s: %s' % (server, e))
continue
s = server.ljust(width + 1)
resolver = server.ljust(width + 1)
text_flags = flags_to_text(flags)
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:
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 ]" % (ans_index, answer))
print("Answer %d [ %s%s%s ]" % (ans_index, color.G, answer, color.N))
ans_index += 1
print("")

View File

@ -38,13 +38,15 @@ import dns.flags
import dns.rdatatype
import dns.resolver
__VERSION__ = 1.6
__PROGNAME__ = os.path.basename(sys.argv[0])
__author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD'
__version__ = "1.6.4"
__progname__ = os.path.basename(sys.argv[0])
shutdown = False
def usage():
print("""%s version %1.1f
print("""%s version %s
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
@ -56,12 +58,12 @@ usage: %s [-ehqv] [-s server] [-p port] [-P port] [-S address] [-c count] [-t ty
-6 --ipv6 Use IPv6 as default network protocol
-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)
-c --count Number of requests to send (default: 10, 0 for infinity)
-w --wait Maximum wait time for a reply (default: 2 seconds)
-i --interval Time between each request (default: 0 seconds)
-i --interval Time between each request (default: 1 seconds)
-t --type DNS request record type (default: A)
-e --edns Disable EDNS0 (default: Enabled)
""" % (__PROGNAME__, __VERSION__, __PROGNAME__))
""" % (__progname__, __version__, __progname__))
sys.exit(0)
@ -86,7 +88,7 @@ def main():
dnsrecord = 'A'
count = 10
timeout = 2
interval = 0
interval = 1
quiet = False
verbose = False
dnsserver = dns.resolver.get_default_resolver().nameservers[0]
@ -104,7 +106,7 @@ def main():
"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"
print(err, file=sys.stderr) # will print something like "option -a not recognized"
usage()
if args and len(args) == 1:
@ -116,7 +118,7 @@ def main():
if o in ("-h", "--help"):
usage()
elif o in ("-c", "--count"):
count = int(a)
count = abs(int(a))
elif o in ("-v", "--verbose"):
verbose = True
elif o in ("-s", "--server"):
@ -143,7 +145,7 @@ def main():
elif o in ("-P", "--srcport"):
src_port = int(a)
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"):
src_ip = a
else:
@ -156,7 +158,7 @@ def main():
try:
dnsserver = socket.getaddrinfo(dnsserver, port=None, family=af)[1][4][0]
except OSError:
print('Error: cannot resolve hostname:', dnsserver)
print('Error: cannot resolve hostname:', dnsserver, file=sys.stderr, flush=True)
sys.exit(1)
resolver = dns.resolver.Resolver()
@ -172,11 +174,16 @@ def main():
response_time = []
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):
if shutdown:
while not shutdown:
if 0 < count <= i:
break
else:
i += 1
try:
stime = time.perf_counter()
answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp,
@ -184,34 +191,34 @@ def main():
etime = time.perf_counter()
except dns.resolver.NoNameservers as e:
if not quiet:
print("No response to dns request")
print("No response to dns request", file=sys.stderr, flush=True)
if verbose:
print("error:", e)
print("error:", e, file=sys.stderr, flush=True)
sys.exit(1)
except dns.resolver.NXDOMAIN as e:
if not quiet:
print("Hostname does not exist")
print("Hostname does not exist", file=sys.stderr, flush=True)
if verbose:
print("Error:", e)
print("Error:", e, file=sys.stderr, flush=True)
sys.exit(1)
except dns.resolver.Timeout:
if not quiet:
print("Request timeout")
print("Request timeout", flush=True)
pass
except dns.resolver.NoAnswer:
if not quiet:
print("No answer")
print("No answer", flush=True)
pass
else:
elapsed = (etime - stime) * 1000 # convert to milliseconds
elapsed = answers.response.time * 1000 # convert to milliseconds
response_time.append(elapsed)
if not quiet:
print(
"%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:
print(answers.rrset)
print("flags:", dns.flags.to_text(answers.response.flags))
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:
@ -235,9 +242,10 @@ def main():
r_avg = 0
r_stddev = 0
print('\n--- %s dnsping statistics ---' % dnsserver)
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))
print('\n--- %s dnsping statistics ---' % dnsserver, flush=True)
print('%d requests transmitted, %d responses received, %.0f%% lost' % (r_sent, r_received, r_lost_percent),
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__':

View File

@ -39,13 +39,20 @@ import dns.query
import dns.rdatatype
import dns.resolver
from cymruwhois import cymruwhois
import cymruwhois
# Global Variables
__author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD'
__version__ = 1.6
__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):
@ -64,11 +71,6 @@ def test_import():
pass
# Constants
__PROGNAME__ = os.path.basename(sys.argv[0])
WHOIS_CACHE = 'whois.cache'
class Colors(object):
N = '\033[m' # native
R = '\033[31m' # red
@ -85,46 +87,53 @@ class Colors(object):
self.B = ''
# Globarl Variables
shutdown = False
def whoisrecord(ip):
def whois_lookup(ip):
try:
currenttime = time.perf_counter()
global whois_cache
currenttime = time.time()
ts = currenttime
if ip in whois:
asn, ts = whois[ip]
if ip in whois_cache:
asn, ts = whois_cache[ip]
else:
ts = 0
if (currenttime - ts) > 36000:
c = cymruwhois.Client()
asn = c.lookup(ip)
whois[ip] = (asn, currenttime)
whois_cache[ip] = (asn, currenttime)
return asn
except Exception as e:
return e
try:
pkl_file = open(WHOIS_CACHE, 'rb')
def load_whois_cache(cachefile):
try:
whois = pickle.load(pkl_file)
except EOFError:
pkl_file = open(cachefile, 'rb')
try:
whois = pickle.load(pkl_file)
pkl_file.close()
except Exception:
whois = {}
except IOError:
whois = {}
except IOError:
whois = {}
return whois
def save_whois_cache(cachefile, whois_data):
pkl_file = open(cachefile, 'wb')
pickle.dump(whois_data, pkl_file)
pkl_file.close()
def usage():
print('%s version %1.1f\n' % (__PROGNAME__, __version__))
print('usage: %s [-aeqhCx] [-s server] [-p port] [-c count] [-t type] [-w wait] hostname' % __PROGNAME__)
print('%s version %s\n' % (__progname__, __version__))
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(' -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(' -S --srcip Query source IP address (default: default interface address)')
print(' -c --count Maximum number of hops (default: 30)')
print(' -w --wait Maximum wait time for a reply (default: 2)')
print(' -t --type DNS request record type (default: A)')
@ -174,7 +183,7 @@ 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, use_edns=False):
def ping(resolver, hostname, dnsrecord, ttl, src_ip, use_edns=False):
global _ttl
reached = False
@ -185,7 +194,7 @@ def ping(resolver, hostname, dnsrecord, ttl, use_edns=False):
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
try:
resolver.query(hostname, dnsrecord, raise_on_no_answer=False)
resolver.query(hostname, dnsrecord, source=src_ip, raise_on_no_answer=False)
except dns.resolver.NoNameservers as e:
if not quiet:
@ -229,6 +238,7 @@ def main():
timeout = 2
dnsserver = dns.resolver.get_default_resolver().nameservers[0]
dest_port = 53
src_ip = None
hops = 0
as_lookup = False
expert_mode = False
@ -237,9 +247,9 @@ def main():
color_mode = False
try:
opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:t:w:p:nexC",
opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:S:t:w:p:nexC",
["help", "count=", "server=", "quiet", "type=", "wait=", "asn", "port", "expert",
"color"])
"color", "srcip="])
except getopt.GetoptError as err:
# print help information and exit:
print(err) # will print something like "option -a not recognized"
@ -261,6 +271,8 @@ def main():
dnsserver = a
elif o in ("-q", "--quiet"):
quiet = True
elif o in ("-S", "--srcip"):
src_ip = a
elif o in ("-w", "--wait"):
timeout = int(a)
elif o in ("-t", "--type"):
@ -293,6 +305,7 @@ def main():
resolver = dns.resolver.Resolver()
resolver.nameservers = [dnsserver]
resolver.timeout = timeout
resolver.port = dest_port
resolver.lifetime = timeout
resolver.retry_servfail = 0
@ -303,7 +316,7 @@ 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:
@ -329,7 +342,7 @@ def main():
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: # dispatch dns lookup to another thread
stime = time.perf_counter()
thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl, use_edns=use_edns)
thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl, src_ip=src_ip, use_edns=use_edns)
try: # expect ICMP response
_, curr_addr = icmp_socket.recvfrom(512)
@ -345,8 +358,8 @@ def main():
if reached:
curr_addr = dnsserver
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)
stime = time.perf_counter() # need to recalculate elapsed time for last hop asynchronously
ping(resolver, hostname, dnsrecord, ttl, src_ip=src_ip, use_edns=use_edns)
etime = time.perf_counter()
elapsed = abs(etime - stime) * 1000 # convert to milliseconds
@ -367,11 +380,11 @@ def main():
if curr_addr:
as_name = ""
if as_lookup:
asn = whoisrecord(curr_addr)
asn = whois_lookup(curr_addr)
as_name = ''
try:
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:
if shutdown:
sys.exit(0)
@ -379,13 +392,16 @@ def main():
c = color.N # default
if curr_addr != '*':
IP = ipaddress.ip_address(curr_addr)
if IP.is_private:
c = color.R
if IP.is_reserved:
c = color.B
if curr_addr == dnsserver:
c = color.G
try:
IP = ipaddress.ip_address(curr_addr)
if IP.is_private:
c = color.R
if IP.is_reserved:
c = color.B
if curr_addr == dnsserver:
c = color.G
except:
pass
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)
@ -404,7 +420,7 @@ def main():
if __name__ == '__main__':
try:
whois_cache = load_whois_cache(WHOIS_CACHE)
main()
finally:
pkl_file = open(WHOIS_CACHE, 'wb')
pickle.dump(whois, pkl_file)
save_whois_cache(WHOIS_CACHE, whois_cache)

View File

@ -1,15 +1,42 @@
8.8.8.8
8.8.4.4
2001:4860:4860::8888
2001:4860:4860::8844
#Cloudflare
1.0.0.1
1.1.1.1
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.2
4.2.2.3
4.2.2.4
4.2.2.5
209.244.0.3
209.244.0.4
195.46.39.39
195.46.39.40
216.146.35.35
216.146.36.36
#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

View File

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

View File

@ -2,14 +2,22 @@ from setuptools import setup, find_packages
setup(
name="dnsdiag",
version="1.6.0",
version="1.6.4",
packages=find_packages(),
scripts=["dnseval.py", "dnsping.py", "dnstraceroute.py"],
install_requires=['dnspython>=1.15.0', 'cymruwhois>=1.6'],
classifiers=[
"Topic :: System :: Networking",
"Environment :: Console",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"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",
"Operating System :: OS Independent",
@ -25,7 +33,7 @@ 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.
""",
license="BSD",
keywords="dns traceroute ping",
keywords="dns traceroute ping performance",
url="https://dnsdiag.org/",
entry_points={
'console_scripts': [