Compare commits

..

No commits in common. "master" and "v1.6.1" have entirely different histories.

13 changed files with 128 additions and 266 deletions

3
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

@ -1,18 +1,10 @@
language: python language: python
sudo: false python:
- "3.3"
- "3.4"
- "3.5"
- "3.5-dev" # 3.5 development branch
- "nightly" # currently points to 3.6-dev
install: "pip install -r requirements.txt" 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 +1,2 @@
include LICENSE README.md TODO.md public-servers.txt public-v4.txt rootservers.txt include LICENSE README.md TODO.md public-servers.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)]() [![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) [![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)
DNS Diagnostics and Performance Measurement Tools DNS Diagnostics and Performance Measurement Tools
================================================== ==================================================
@ -29,6 +29,8 @@ of view.
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/). [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 # installation
@ -41,13 +43,13 @@ There are several ways that you can use this toolset. However using the sourceco
``` ```
git clone https://github.com/farrokhi/dnsdiag.git git clone https://github.com/farrokhi/dnsdiag.git
cd dnsdiag cd dnsdiag
pip3 install -r requirements.txt git submodule update --init
``` ```
2. You can alternatively install the package using pip: 2. You can alternatively install the package using pip:
``` ```
pip3 install dnsdiag pip3 install --process-dependency-links dnsdiag
``` ```
## From Binary ## From Binary
@ -97,7 +99,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 of DNS servers. This script is meant for comparing response time of multiple
DNS servers at once: DNS servers at once:
``` ```
% ./dnseval.py -t AAAA -f public-servers.txt -c10 yahoo.com % ./dnseval.py -t AAAA -f public-v4.txt -c10 yahoo.com
server avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags 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.8.8 270.791 215.599 307.498 40.630 %0 298 QR -- -- RD RA -- --
@ -127,4 +129,3 @@ Babak Farrokhi
dnsdiag is released under a 2 clause BSD license. 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
cymruwhois Submodule

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

View File

@ -33,8 +33,6 @@ 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
@ -42,7 +40,7 @@ import dns.resolver
__author__ = 'Babak Farrokhi (babak@farrokhi.net)' __author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD' __license__ = 'BSD'
__version__ = "1.6.4" __version__ = "1.6.0"
__progname__ = os.path.basename(sys.argv[0]) __progname__ = os.path.basename(sys.argv[0])
shutdown = False shutdown = False
@ -69,11 +67,10 @@ def usage():
print("""%s version %s 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)
-m --cache-miss Force cache miss measurement by prepending a random hostname -w --wait maximum wait time for a reply (default: 2)
-w --wait Maximum wait time for a reply (default: 2)
-t --type DNS request record type (default: A) -t --type DNS request record type (default: A)
-T --tcp Use TCP instead of UDP -T --tcp Use TCP instead of UDP
-e --edns Disable EDNS0 (Default: Enabled) -e --edns Disable EDNS0 (Default: Enabled)
@ -140,13 +137,7 @@ def flags_to_text(flags):
return ' '.join(text_flags) return ' '.join(text_flags)
def random_string(min_length=5, max_length=10): def dnsping(host, server, dnsrecord, timeout, count, use_tcp=False, use_edns=False):
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
@ -165,26 +156,16 @@ def dnsping(host, server, dnsrecord, timeout, count, use_tcp=False, use_edns=Fal
if shutdown: # user pressed CTRL+C if shutdown: # user pressed CTRL+C
break break
try: try:
if force_miss:
fqdn = "_dnsdiag_%s_.%s" % (random_string(), host)
else:
fqdn = host
stime = time.perf_counter() stime = time.perf_counter()
answers = resolver.query(fqdn, dnsrecord, tcp=use_tcp, answers = resolver.query(host, dnsrecord, tcp=use_tcp,
raise_on_no_answer=False) # todo: response validation in future raise_on_no_answer=False) # todo: response validation in future
etime = time.perf_counter()
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 = answers.response.time * 1000 # convert to milliseconds elapsed = (etime - stime) * 1000 # convert to milliseconds
response_times.append(elapsed) response_times.append(elapsed)
r_sent = i + 1 r_sent = i + 1
@ -231,15 +212,13 @@ def main():
fromfile = False fromfile = False
use_tcp = False use_tcp = False
use_edns = True use_edns = True
force_miss = False
verbose = False verbose = False
color_mode = False color_mode = False
hostname = 'wikipedia.org' hostname = 'wikipedia.org'
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:TevCm", opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:TevC",
["help", "file=", "count=", "type=", "wait=", "tcp", "edns", "verbose", "color", ["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()
@ -259,8 +238,6 @@ 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"): elif o in ("-T", "--tcp"):
@ -279,25 +256,14 @@ def main():
try: try:
if fromfile: if fromfile:
if inputfilename == '-':
# read from stdin
with sys.stdin as flist:
f = flist.read().splitlines()
else:
try:
with open(inputfilename, 'rt') as flist: with open(inputfilename, 'rt') as flist:
f = flist.read().splitlines() 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] # remove annoying blanks f = [name.strip() for name in f]
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(%) ttl flags') print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags')
@ -311,29 +277,25 @@ def main():
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:
resolver = socket.getaddrinfo(server, port=None)[1][4][0] s = socket.getaddrinfo(server, port=None)[1][4][0]
except OSError: except OSError:
print('Error: cannot resolve hostname:', server) print('Error: cannot resolve hostname:', server)
resolver = None s = None
except: except:
pass pass
else: else:
resolver = server s = server
if not resolver: if not s:
continue continue
try: try:
(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, flags, ttl, answers) = dnsping(hostname, s,
hostname,
resolver,
dnsrecord, dnsrecord,
waittime, waittime,
count, count,
use_tcp=use_tcp, use_tcp=use_tcp,
use_edns=use_edns, use_edns=use_edns)
force_miss=force_miss
)
except dns.resolver.NXDOMAIN: except dns.resolver.NXDOMAIN:
print('%-15s NXDOMAIN' % server) print('%-15s NXDOMAIN' % server)
continue continue
@ -341,7 +303,7 @@ def main():
print('%s: %s' % (server, e)) print('%s: %s' % (server, e))
continue continue
resolver = server.ljust(width + 1) s = server.ljust(width + 1)
text_flags = flags_to_text(flags) text_flags = flags_to_text(flags)
s_ttl = str(ttl) s_ttl = str(ttl)
@ -353,12 +315,11 @@ def main():
else: else:
l_color = color.N l_color = color.N
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %s%%%-3d%s %-8s %21s" % ( 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), s, r_avg, r_min, r_max, r_stddev, l_color, r_lost_percent, color.N, s_ttl, text_flags), flush=True)
flush=True)
if verbose and hasattr(answers, 'response'): if verbose and hasattr(answers, 'response'):
ans_index = 1 ans_index = 1
for answer in answers.response.answer: for answer in answers.response.answer:
print("Answer %d [ %s%s%s ]" % (ans_index, color.G, answer, color.N)) print("Answer %d [ %s%s%s ]" % (ans_index, color.B, answer, color.N))
ans_index += 1 ans_index += 1
print("") print("")

View File

@ -40,7 +40,7 @@ import dns.resolver
__author__ = 'Babak Farrokhi (babak@farrokhi.net)' __author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD' __license__ = 'BSD'
__version__ = "1.6.4" __version__ = "1.6.0"
__progname__ = os.path.basename(sys.argv[0]) __progname__ = os.path.basename(sys.argv[0])
shutdown = False shutdown = False
@ -58,9 +58,9 @@ usage: %s [-ehqv] [-s server] [-p port] [-P port] [-S address] [-c count] [-t ty
-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, 0 for infinity) -c --count Number of requests to send (default: 10)
-w --wait Maximum wait time for a reply (default: 2 seconds) -w --wait Maximum wait time for a reply (default: 2 seconds)
-i --interval Time between each request (default: 1 seconds) -i --interval Time between each request (default: 0 seconds)
-t --type DNS request record type (default: A) -t --type DNS request record type (default: A)
-e --edns Disable EDNS0 (default: Enabled) -e --edns Disable EDNS0 (default: Enabled)
""" % (__progname__, __version__, __progname__)) """ % (__progname__, __version__, __progname__))
@ -88,7 +88,7 @@ def main():
dnsrecord = 'A' dnsrecord = 'A'
count = 10 count = 10
timeout = 2 timeout = 2
interval = 1 interval = 0
quiet = False quiet = False
verbose = False verbose = False
dnsserver = dns.resolver.get_default_resolver().nameservers[0] dnsserver = dns.resolver.get_default_resolver().nameservers[0]
@ -106,7 +106,7 @@ def main():
"port=", "srcip=", "tcp", "ipv4", "ipv6", "srcport=", "edns"]) "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, file=sys.stderr) # will print something like "option -a not recognized" print(err) # will print something like "option -a not recognized"
usage() usage()
if args and len(args) == 1: if args and len(args) == 1:
@ -118,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 = abs(int(a)) count = 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"):
@ -145,7 +145,7 @@ def main():
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", flush=True) print("WARNING: Source ports below 1024 are only available to superuser")
elif o in ("-S", "--srcip"): elif o in ("-S", "--srcip"):
src_ip = a src_ip = a
else: else:
@ -158,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, file=sys.stderr, flush=True) print('Error: cannot resolve hostname:', dnsserver)
sys.exit(1) sys.exit(1)
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
@ -174,16 +174,11 @@ def main():
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)
while not shutdown: for i in range(count):
if shutdown:
if 0 < count <= i:
break break
else:
i += 1
try: try:
stime = time.perf_counter() stime = time.perf_counter()
answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp, answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp,
@ -191,34 +186,34 @@ def main():
etime = time.perf_counter() 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", file=sys.stderr, flush=True) print("No response to dns request")
if verbose: if verbose:
print("error:", e, file=sys.stderr, flush=True) print("error:", e)
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", file=sys.stderr, flush=True) print("Hostname does not exist")
if verbose: if verbose:
print("Error:", e, file=sys.stderr, flush=True) print("Error:", e)
sys.exit(1) sys.exit(1)
except dns.resolver.Timeout: except dns.resolver.Timeout:
if not quiet: if not quiet:
print("Request timeout", flush=True) print("Request timeout")
pass pass
except dns.resolver.NoAnswer: except dns.resolver.NoAnswer:
if not quiet: if not quiet:
print("No answer", flush=True) print("No answer")
pass pass
else: else:
elapsed = answers.response.time * 1000 # convert to milliseconds elapsed = (etime - stime) * 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=%.3f ms" % ( "%d bytes from %s: seq=%-3d time=%.3f ms" % (
len(str(answers.rrset)), dnsserver, i, elapsed), flush=True) len(str(answers.rrset)), dnsserver, i, elapsed))
if verbose: if verbose:
print(answers.rrset, flush=True) print(answers.rrset)
print("flags:", dns.flags.to_text(answers.response.flags), flush=True) print("flags:", dns.flags.to_text(answers.response.flags))
time_to_next = (stime + interval) - etime time_to_next = (stime + interval) - etime
if time_to_next > 0: if time_to_next > 0:
@ -242,10 +237,9 @@ def main():
r_avg = 0 r_avg = 0
r_stddev = 0 r_stddev = 0
print('\n--- %s dnsping statistics ---' % dnsserver, flush=True) print('\n--- %s dnsping statistics ---' % dnsserver)
print('%d requests transmitted, %d responses received, %.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))
flush=True) print('min=%.3f ms, avg=%.3f ms, max=%.3f ms, stddev=%.3f ms' % (r_min, r_avg, r_max, r_stddev))
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__':

View File

@ -39,20 +39,13 @@ import dns.query
import dns.rdatatype import dns.rdatatype
import dns.resolver import dns.resolver
import cymruwhois from cymruwhois import cymruwhois
# Global Variables
__author__ = 'Babak Farrokhi (babak@farrokhi.net)' __author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD' __license__ = 'BSD'
__version__ = "1.6.4" __version__ = "1.6.0"
_ttl = None _ttl = None
quiet = False quiet = False
whois_cache = {}
shutdown = False
# Constants
__progname__ = os.path.basename(sys.argv[0])
WHOIS_CACHE = 'whois.cache'
class CustomSocket(socket.socket): class CustomSocket(socket.socket):
@ -71,6 +64,11 @@ 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
@ -87,41 +85,35 @@ class Colors(object):
self.B = '' self.B = ''
def whois_lookup(ip): # Globarl Variables
shutdown = False
def whoisrecord(ip):
try: try:
global whois_cache currenttime = time.perf_counter()
currenttime = time.time()
ts = currenttime ts = currenttime
if ip in whois_cache: if ip in whois:
asn, ts = whois_cache[ip] asn, ts = whois[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_cache[ip] = (asn, currenttime) whois[ip] = (asn, currenttime)
return asn return asn
except Exception as e: except Exception as e:
return e return e
def load_whois_cache(cachefile):
try: try:
pkl_file = open(cachefile, 'rb') pkl_file = open(WHOIS_CACHE, 'rb')
try: try:
whois = pickle.load(pkl_file) whois = pickle.load(pkl_file)
pkl_file.close() except EOFError:
except Exception:
whois = {} whois = {}
except IOError: except IOError:
whois = {} 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(): def usage():
@ -133,7 +125,6 @@ def usage():
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: 2)') 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)')
@ -183,7 +174,7 @@ def expert_report(trace_path, color_mode):
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, src_ip, use_edns=False): def ping(resolver, hostname, dnsrecord, ttl, use_edns=False):
global _ttl global _ttl
reached = False reached = False
@ -194,7 +185,7 @@ def ping(resolver, hostname, dnsrecord, ttl, src_ip, use_edns=False):
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO')) resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
try: try:
resolver.query(hostname, dnsrecord, source=src_ip, raise_on_no_answer=False) resolver.query(hostname, dnsrecord, raise_on_no_answer=False)
except dns.resolver.NoNameservers as e: except dns.resolver.NoNameservers as e:
if not quiet: if not quiet:
@ -238,7 +229,6 @@ def main():
timeout = 2 timeout = 2
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
@ -247,9 +237,9 @@ def main():
color_mode = False color_mode = False
try: try:
opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:S:t:w:p:nexC", opts, args = getopt.getopt(sys.argv[1:], "aqhc:s:t:w:p:nexC",
["help", "count=", "server=", "quiet", "type=", "wait=", "asn", "port", "expert", ["help", "count=", "server=", "quiet", "type=", "wait=", "asn", "port", "expert",
"color", "srcip="]) "color"])
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"
@ -271,8 +261,6 @@ def main():
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"):
@ -305,7 +293,6 @@ def main():
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
@ -342,7 +329,7 @@ def main():
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.perf_counter() stime = time.perf_counter()
thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl, src_ip=src_ip, use_edns=use_edns) thr = pool.submit(ping, resolver, hostname, dnsrecord, ttl, use_edns=use_edns)
try: # expect ICMP response try: # expect ICMP response
_, curr_addr = icmp_socket.recvfrom(512) _, curr_addr = icmp_socket.recvfrom(512)
@ -358,8 +345,8 @@ def main():
if reached: if reached:
curr_addr = dnsserver curr_addr = dnsserver
stime = time.perf_counter() # need to recalculate elapsed time for last hop asynchronously stime = time.perf_counter() # need to recalculate elapsed time for last hop without waiting for an icmp error reply
ping(resolver, hostname, dnsrecord, ttl, src_ip=src_ip, use_edns=use_edns) ping(resolver, hostname, dnsrecord, ttl, use_edns=use_edns)
etime = time.perf_counter() etime = time.perf_counter()
elapsed = abs(etime - stime) * 1000 # convert to milliseconds elapsed = abs(etime - stime) * 1000 # convert to milliseconds
@ -380,11 +367,11 @@ def main():
if curr_addr: if curr_addr:
as_name = "" as_name = ""
if as_lookup: if as_lookup:
asn = whois_lookup(curr_addr) asn = whoisrecord(curr_addr)
as_name = '' as_name = ''
try: try:
if asn and asn.asn != "NA": if asn and asn.asn != "NA":
as_name = "[AS%s %s] " % (asn.asn, asn.owner) as_name = "[%s %s] " % (asn.asn, asn.owner)
except AttributeError: except AttributeError:
if shutdown: if shutdown:
sys.exit(0) sys.exit(0)
@ -392,7 +379,6 @@ def main():
c = color.N # default c = color.N # default
if curr_addr != '*': if curr_addr != '*':
try:
IP = ipaddress.ip_address(curr_addr) IP = ipaddress.ip_address(curr_addr)
if IP.is_private: if IP.is_private:
c = color.R c = color.R
@ -400,8 +386,6 @@ def main():
c = color.B c = color.B
if curr_addr == dnsserver: if curr_addr == dnsserver:
c = color.G 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) 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)
@ -420,7 +404,7 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
try: try:
whois_cache = load_whois_cache(WHOIS_CACHE)
main() main()
finally: finally:
save_whois_cache(WHOIS_CACHE, whois_cache) pkl_file = open(WHOIS_CACHE, 'wb')
pickle.dump(whois, pkl_file)

View File

@ -1,42 +1,15 @@
#Cloudflare 8.8.8.8
1.0.0.1 8.8.4.4
1.1.1.1 2001:4860:4860::8888
2606:4700:4700::1001 2001:4860:4860::8844
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
#freenom world 209.244.0.4
80.80.80.80 195.46.39.39
80.80.81.81 195.46.39.40
#Google 216.146.35.35
8.8.4.4 216.146.36.36
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

View File

@ -1,36 +0,0 @@
#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,2 +1 @@
dnspython>=1.15.0 dnspython==1.15.0
cymruwhois>=1.6

View File

@ -2,22 +2,14 @@ from setuptools import setup, find_packages
setup( setup(
name="dnsdiag", name="dnsdiag",
version="1.6.4", version="1.6.0",
packages=find_packages(), packages=find_packages(),
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.3",
"Programming Language :: Python :: 3.4", "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)", "Topic :: Internet :: Name Service (DNS)",
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Operating System :: OS Independent", "Operating System :: OS Independent",
@ -33,7 +25,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. as well as tracing the path your DNS query takes to get to DNS server.
""", """,
license="BSD", license="BSD",
keywords="dns traceroute ping performance", keywords="dns traceroute ping",
url="https://dnsdiag.org/", url="https://dnsdiag.org/",
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [