Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
3ecc777eb9 | |||
1d37debd96 | |||
0a9b362587 | |||
a81525fc5f | |||
3f8db03853 | |||
f58597f702 | |||
133f5c7952 | |||
3e8e1e41d7 | |||
befeb64a41 | |||
b0f1335664 | |||
0648619d2a | |||
151c383cc1 | |||
|
b78ec5516c | ||
d3a1fd2308 | |||
b8dbe54f1a | |||
|
9e153c406d | ||
|
0fe4786bba | ||
9b47a3084a | |||
b1b4f06730 | |||
25c243efa1 | |||
a0c26242ec | |||
e93120da19 | |||
dc7f03eac3 | |||
7390514877 | |||
bbff3b44f6 | |||
d93b87b2a2 | |||
8a9acd9100 | |||
8283f4dbc2 | |||
e3ddfff88e | |||
1b9849c224 | |||
76b843d728 | |||
|
037457f0cc | ||
4c9aaf921b | |||
|
f1807d34ea | ||
9caa006e5b | |||
5a99d58dd1 | |||
c92ea53e57 | |||
47dbc6afe3 | |||
39e564e626 | |||
3df692e6db | |||
2dcf0e7b78 | |||
a0f9cae673 | |||
51e3d252f2 | |||
7db7684c95 | |||
bde3263cfa |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
# virtualenv
|
||||
.venv/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "cymruwhois"]
|
||||
path = cymruwhois
|
||||
url = https://github.com/JustinAzoff/python-cymruwhois.git
|
22
.travis.yml
22
.travis.yml
@ -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"
|
||||
|
@ -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
|
||||
|
11
README.md
11
README.md
@ -1,4 +1,4 @@
|
||||
[](https://travis-ci.org/farrokhi/dnsdiag) [](https://pypi.python.org/pypi/dnsdiag/) []() []() [](https://github.com/farrokhi/dnsdiag/stargazers)
|
||||
[](https://travis-ci.org/farrokhi/dnsdiag) [](https://pypi.python.org/pypi/dnsdiag/) []() [](https://app.fossa.io/projects/git%2Bgithub.com%2Ffarrokhi%2Fdnsdiag?ref=badge_shield) []() [](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.
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Ffarrokhi%2Fdnsdiag?ref=badge_large)
|
||||
|
@ -1 +0,0 @@
|
||||
Subproject commit a34543335cbef02b1b615e774ce5b6187afb0cc2
|
119
dnseval.py
119
dnseval.py
@ -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)
|
||||
-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__))
|
||||
""" % (__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,17 +165,27 @@ 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
|
||||
else:
|
||||
except dns.resolver.NXDOMAIN:
|
||||
etime = time.perf_counter()
|
||||
if force_miss:
|
||||
elapsed = (etime - stime) * 1000 # convert to milliseconds
|
||||
response_times.append(elapsed)
|
||||
else:
|
||||
elapsed = answers.response.time * 1000 # convert to milliseconds
|
||||
response_times.append(elapsed)
|
||||
|
||||
r_sent = i + 1
|
||||
r_received = len(response_times)
|
||||
@ -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:
|
||||
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,
|
||||
(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)
|
||||
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("")
|
||||
|
||||
|
62
dnsping.py
62
dnsping.py
@ -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__':
|
||||
|
@ -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:
|
||||
pkl_file = open(cachefile, 'rb')
|
||||
try:
|
||||
whois = pickle.load(pkl_file)
|
||||
except EOFError:
|
||||
pkl_file.close()
|
||||
except Exception:
|
||||
whois = {}
|
||||
except IOError:
|
||||
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,6 +392,7 @@ def main():
|
||||
|
||||
c = color.N # default
|
||||
if curr_addr != '*':
|
||||
try:
|
||||
IP = ipaddress.ip_address(curr_addr)
|
||||
if IP.is_private:
|
||||
c = color.R
|
||||
@ -386,6 +400,8 @@ def main():
|
||||
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)
|
||||
|
@ -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
36
public-v4.txt
Normal 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
|
@ -1 +1,2 @@
|
||||
dnspython==1.15.0
|
||||
dnspython>=1.15.0
|
||||
cymruwhois>=1.6
|
||||
|
12
setup.py
12
setup.py
@ -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': [
|
||||
|
Loading…
x
Reference in New Issue
Block a user