29 Commits

Author SHA1 Message Date
c025a0894c Remove dnspython from external requirements 2016-06-14 17:01:52 +04:30
a820d11e0e Add original dnspython as submodule
This would be in effect until the latest version (>=1.15.0) become
available on pypi.
2016-06-14 17:00:55 +04:30
d8b9de5fcc This should not exist in this branch to make merging easier 2016-06-14 16:58:13 +04:30
d79f5821c5 Refactor and comments 2016-06-14 16:48:35 +04:30
f74b311170 Add relevant category 2016-06-14 16:39:53 +04:30
acd8602891 Refactor and comments 2016-06-14 16:39:44 +04:30
403a583942 Update version information to match setuptools 2016-06-12 12:15:09 +04:30
8725ce0aeb Update requirements to match new dnspython version 2016-06-05 10:04:22 +04:30
c45d408be1 Add requirements.txt, now we depend on dnspython 2016-06-01 13:24:55 +04:30
8a1b74fe0f remove patched dnspython module and use stock version 2016-06-01 13:23:56 +04:30
22ef5331af Remove AF 2016-06-01 13:23:46 +04:30
efeccef2aa Fix text alignment 2016-06-01 13:22:43 +04:30
4092e4e2cb Use new socket_factory to avoid using patched dnspython 2016-06-01 13:09:34 +04:30
db90716837 show dns flags in fixed columns (fix #18) 2016-05-25 20:36:14 +04:30
24adbab4aa test agains various python 3.x builds 2016-05-25 15:46:39 +04:30
76a24b10f2 also ask NS from domain's own DNS server 2016-05-24 16:05:11 +04:30
2609fdd758 organize code in classes and beautify output 2016-05-24 12:37:10 +04:30
440a9f4287 add initial dnsdiag.py tool - the DNS diagnostics swiss army knife 2016-05-23 19:44:37 +04:30
4c2d9d51e1 fix long arguments 2016-05-23 18:54:14 +04:30
920b0a8952 remove unnecessary option 2016-05-23 18:50:40 +04:30
575113ed69 discreetly skip bad hostnames 2016-05-23 18:36:47 +04:30
0164eae199 fix README and code cleanup 2016-05-16 19:05:48 +04:30
c7a602ae4e deal with empty lines in input file (fix #15) 2016-05-16 14:17:39 +04:30
d0db0c62aa fix NoAnswer error when RRSIG requested (fix #14) 2016-05-14 14:12:12 +04:30
1d373df954 add flags in dnseval output and update docs (close #13) 2016-05-14 13:22:02 +04:30
895c80b5bf print flagas in verbose mode (in response to #13) 2016-05-14 13:09:47 +04:30
13751fed51 fix script names for setuptools 2016-05-10 13:16:55 +04:30
a1ee92d8f2 mention binary releases 2016-05-10 10:09:40 +04:30
c745b96e08 bump version 2016-05-09 17:21:24 +04:30
9 changed files with 165 additions and 65 deletions

7
.gitmodules vendored
View File

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

View File

@ -1,5 +1,10 @@
language: python language: python
python: "3.4" python:
- "3.3"
- "3.4"
- "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 --process-dependency-links dnsdiag
script: nosetests dnstraceroute.py script: nosetests dnstraceroute.py

View File

@ -35,6 +35,10 @@ submodule update --init` and project directory to pull the required code.
# installation # installation
There are several ways that you can use this toolset. However using the sourcecode is always recommended.
## From Source Code
1. You can checkout this git repo and its submodules 1. You can checkout this git repo and its submodules
``` ```
@ -49,6 +53,10 @@ git submodule update --init
pip3 install --process-dependency-links dnsdiag pip3 install --process-dependency-links dnsdiag
``` ```
## From Binary
From time to time, binary version will be released for Windows, Mac OS X and Linux platforms. You can grab the latest release from [releases page](https://github.com/farrokhi/dnsdiag/releases).
# dnsping # dnsping
dnsping pings a DNS resolver by sending an arbitrary DNS query for given number dnsping pings a DNS resolver by sending an arbitrary DNS query for given number
of times: of times:
@ -99,17 +107,17 @@ dnseval is a bulk ping utility that sends an arbitrary DNS query to a give list
of DNS servers. This script is meant for comparing response time of multiple of DNS servers. This script is meant for comparing response time of multiple
DNS servers at once: DNS servers at once:
``` ```
% ./dnseval.py wikipedia.org % ./dnseval.py -f public-v4.txt -c3 ripe.net
server avg(ms) min(ms) max(ms) stddev(ms) lost(%) server avg(ms) min(ms) max(ms) stddev(ms) lost(%) flags
-------------------------------------------------------------------------- ----------------------------------------------------------------------------------
4.2.2.1 151.067 131.270 221.742 28.643 %10 8.8.8.8 210.225 109.864 407.420 170.785 %0 QR RD RA
4.2.2.2 142.175 132.921 178.133 13.348 %0 8.8.4.4 107.850 93.134 120.578 13.830 %0 QR RD RA
64.6.64.6 133.047 109.145 162.938 20.609 %0 ns.ripe.net 118.911 114.874 123.389 4.275 %0 QR AA RD
64.6.65.6 377.270 97.669 661.471 172.717 %0 4.2.2.1 104.380 102.449 106.588 2.083 %0 QR RD RA
8.8.4.4 389.048 294.581 511.134 67.953 %0 4.2.2.2 131.056 99.143 193.711 54.264 %0 QR RD RA
8.8.8.8 0.000 0.000 0.000 0.000 %100 4.2.2.3 98.956 97.463 100.310 1.429 %0 QR RD RA
208.67.222.222 179.068 135.975 258.582 50.681 %0 4.2.2.4 223.173 97.418 463.728 208.398 %0 QR RD RA
208.67.220.220 137.817 135.822 140.113 1.504 %0 4.2.2.5 104.290 97.264 117.878 11.770 %0 QR RD RA
``` ```
### Author ### Author

View File

@ -37,7 +37,7 @@ from statistics import stdev
import dns.rdatatype import dns.rdatatype
import dns.resolver import dns.resolver
__VERSION__ = 1.0 __VERSION__ = 1.4
__PROGNAME__ = os.path.basename(sys.argv[0]) __PROGNAME__ = os.path.basename(sys.argv[0])
shutdown = False shutdown = False
@ -69,18 +69,66 @@ def maxlen(names):
return len(sn[-1]) return len(sn[-1])
def _order_flags(table):
return sorted(table.items(), reverse=True)
def flags_to_text(flags):
# Standard DNS flags
QR = 0x8000
AA = 0x0400
TC = 0x0200
RD = 0x0100
RA = 0x0080
AD = 0x0020
CD = 0x0010
# EDNS flags
DO = 0x8000
_by_text = {
'QR': QR,
'AA': AA,
'TC': TC,
'RD': RD,
'RA': RA,
'AD': AD,
'CD': CD
}
_by_value = dict([(y, x) for x, y in _by_text.items()])
_flags_order = _order_flags(_by_value)
_by_value = dict([(y, x) for x, y in _by_text.items()])
order = sorted(_by_value.items(), reverse=True)
text_flags = []
for k, v in order:
if flags & k != 0:
text_flags.append(v)
else:
text_flags.append('--')
return ' '.join(text_flags)
def dnsping(host, server, dnsrecord, timeout, count): def dnsping(host, server, dnsrecord, timeout, count):
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
resolver.nameservers = [server] resolver.nameservers = [server]
resolver.timeout = timeout resolver.timeout = timeout
resolver.lifetime = timeout resolver.lifetime = timeout
resolver.retry_servfail = 0 resolver.retry_servfail = 0
flags = 0
answers = None
resolver.use_edns(edns=True, payload=0, ednsflags=8)
response_time = [] response_times = []
i = 0 i = 0
for i in range(count): for i in range(count):
if shutdown: if shutdown: # user pressed CTRL+C
break break
try: try:
stime = time.time() stime = time.time()
@ -92,18 +140,18 @@ def dnsping(host, server, dnsrecord, timeout, count):
pass pass
else: else:
elapsed = (etime - stime) * 1000 # convert to milliseconds elapsed = (etime - stime) * 1000 # convert to milliseconds
response_time.append(elapsed) response_times.append(elapsed)
r_sent = i + 1 r_sent = i + 1
r_received = len(response_time) r_received = len(response_times)
r_lost = r_sent - r_received r_lost = r_sent - r_received
r_lost_percent = (100 * r_lost) / r_sent r_lost_percent = (100 * r_lost) / r_sent
if response_time: if response_times:
r_min = min(response_time) r_min = min(response_times)
r_max = max(response_time) r_max = max(response_times)
r_avg = sum(response_time) / r_received r_avg = sum(response_times) / r_received
if len(response_time) > 1: if len(response_times) > 1:
r_stddev = stdev(response_time) r_stddev = stdev(response_times)
else: else:
r_stddev = 0 r_stddev = 0
else: else:
@ -112,19 +160,23 @@ def dnsping(host, server, dnsrecord, timeout, count):
r_avg = 0 r_avg = 0
r_stddev = 0 r_stddev = 0
return (server, r_avg, r_min, r_max, r_stddev, r_lost_percent) if answers:
flags = answers.response.flags
return server, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags
def main(): def main():
try: try:
signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z
signal.signal(signal.SIGINT, signal_handler) # ignore CTRL+C signal.signal(signal.SIGINT, signal_handler) # catch CTRL+C
except AttributeError: # Some systems may not support all signals except AttributeError: # Some systems (e.g. Windows) may not support all signals
pass pass
if len(sys.argv) == 1: if len(sys.argv) == 1:
usage() usage()
# defaults
dnsrecord = 'A' dnsrecord = 'A'
count = 10 count = 10
waittime = 5 waittime = 5
@ -172,10 +224,13 @@ def main():
f = [name.strip() for name in f] f = [name.strip() for name in f]
width = maxlen(f) width = maxlen(f)
blanks = (width - 5) * ' ' blanks = (width - 5) * ' '
print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%)') print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%) flags')
print((60 + width) * '-') print((84 + width) * '-')
for server in f: for server in f:
# check if we have a valid dns server address # check if we have a valid dns server address
if server.lstrip() == '': # deal with empty lines
continue
server = server.replace(' ', '')
try: try:
ipaddress.ip_address(server) ipaddress.ip_address(server)
except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name
@ -184,17 +239,20 @@ def main():
except OSError: except OSError:
print('Error: cannot resolve hostname:', server) print('Error: cannot resolve hostname:', server)
s = None s = None
except:
pass
else: else:
s = server s = server
if not s: if not s:
continue continue
(s, r_avg, r_min, r_max, r_stddev, r_lost_percent) = dnsping(hostname, s, dnsrecord, waittime, (s, r_avg, r_min, r_max, r_stddev, r_lost_percent, flags) = dnsping(hostname, s, dnsrecord, waittime,
count) count)
s = server.ljust(width + 1) s = server.ljust(width + 1)
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %%%d" % ( text_flags = flags_to_text(flags)
s, r_avg, r_min, r_max, r_stddev, r_lost_percent), flush=True) 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)
except Exception as e: except Exception as e:
print('error: %s' % e) print('error: %s' % e)

View File

@ -34,10 +34,11 @@ import sys
import time import time
from statistics import stdev from statistics import stdev
import dns.flags
import dns.rdatatype import dns.rdatatype
import dns.resolver import dns.resolver
__VERSION__ = 1.1 __VERSION__ = 1.4
__PROGNAME__ = os.path.basename(sys.argv[0]) __PROGNAME__ = os.path.basename(sys.argv[0])
shutdown = False shutdown = False
@ -95,8 +96,8 @@ def main():
try: try:
opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:vp:P:S:T46", opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:vp:P:S:T46",
["help", "output=", "count=", "server=", "quiet", "type=", "wait=", "verbose", ["help", "count=", "server=", "quiet", "type=", "wait=", "verbose",
"port", "dstport=", "srcip=", "tcp", "ipv4", "ipv6"]) "port=", "srcip=", "tcp", "ipv4", "ipv6", "srcport="])
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"
@ -167,7 +168,8 @@ def main():
break break
try: try:
stime = time.time() stime = time.time()
answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp, af=af) answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp,
raise_on_no_answer=False)
etime = time.time() etime = time.time()
except dns.resolver.NoNameservers as e: except dns.resolver.NoNameservers as e:
if not quiet: if not quiet:
@ -198,6 +200,7 @@ def main():
len(str(answers.rrset)), dnsserver, i, elapsed)) len(str(answers.rrset)), dnsserver, i, elapsed))
if verbose: if verbose:
print(answers.rrset) print(answers.rrset)
print("flags:", dns.flags.to_text(answers.response.flags))
r_sent = i + 1 r_sent = i + 1
r_received = len(response_time) r_received = len(response_time)

View File

@ -35,13 +35,28 @@ import socket
import sys import sys
import time import time
import dns.query
import dns.rdatatype import dns.rdatatype
import dns.resolver import dns.resolver
from cymruwhois import cymruwhois from cymruwhois import cymruwhois
__author__ = 'Babak Farrokhi (babak@farrokhi.net)' __author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__license__ = 'BSD' __license__ = 'BSD'
__version__ = 1.3 __version__ = 1.4
_ttl = None
quiet = False
class CustomSocket(socket.socket):
def __init__(self, *args, **kwargs):
super(CustomSocket, self).__init__(*args, **kwargs)
def sendto(self, *args, **kwargs):
global _ttl
if _ttl:
self.setsockopt(socket.SOL_IP, socket.IP_TTL, _ttl)
super(CustomSocket, self).sendto(*args, **kwargs)
def test_import(): def test_import():
@ -79,14 +94,14 @@ def whoisrecord(ip):
currenttime = time.time() currenttime = time.time()
ts = currenttime ts = currenttime
if ip in whois: if ip in whois:
ASN, ts = whois[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[ip] = (ASN, currenttime) whois[ip] = (asn, currenttime)
return ASN return asn
except Exception as e: except Exception as e:
return e return e
@ -153,15 +168,20 @@ def expert_report(trace_path, color_mode):
print(" %s[*]%s public DNS server is next to a reserved IP address (possible hijacking)" % (color.R, color.N)) print(" %s[*]%s public DNS server is next to a reserved IP address (possible hijacking)" % (color.R, color.N))
return return
## no expert info available # no expert info available
print(" %s[*]%s No expert hint available for this trace" % (color.G, color.N)) print(" %s[*]%s No expert hint available for this trace" % (color.G, color.N))
def ping(resolver, hostname, dnsrecord, ttl): def ping(resolver, hostname, dnsrecord, ttl):
global _ttl
reached = False reached = False
dns.query.socket_factory = CustomSocket
_ttl = ttl
try: try:
resolver.query(hostname, dnsrecord, ipttl=ttl) resolver.query(hostname, dnsrecord)
except dns.resolver.NoNameservers as e: except dns.resolver.NoNameservers as e:
if not quiet: if not quiet:
@ -203,7 +223,6 @@ def main():
dnsrecord = 'A' dnsrecord = 'A'
count = 30 count = 30
timeout = 1 timeout = 1
quiet = False
dnsserver = dns.resolver.get_default_resolver().nameservers[0] dnsserver = dns.resolver.get_default_resolver().nameservers[0]
dest_port = 53 dest_port = 53
hops = 0 hops = 0
@ -330,11 +349,11 @@ def main():
if curr_addr: if curr_addr:
as_name = "" as_name = ""
if as_lookup: if as_lookup:
ASN = whoisrecord(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 = "[%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)

0
requirements.txt Normal file
View File

View File

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