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"]
path = cymruwhois
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
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
script: nosetests dnstraceroute.py

View File

@ -35,6 +35,10 @@ submodule update --init` and project directory to pull the required code.
# 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
```
@ -49,6 +53,10 @@ git submodule update --init
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 pings a DNS resolver by sending an arbitrary DNS query for given number
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
DNS servers at once:
```
% ./dnseval.py wikipedia.org
server avg(ms) min(ms) max(ms) stddev(ms) lost(%)
--------------------------------------------------------------------------
4.2.2.1 151.067 131.270 221.742 28.643 %10
4.2.2.2 142.175 132.921 178.133 13.348 %0
64.6.64.6 133.047 109.145 162.938 20.609 %0
64.6.65.6 377.270 97.669 661.471 172.717 %0
8.8.4.4 389.048 294.581 511.134 67.953 %0
8.8.8.8 0.000 0.000 0.000 0.000 %100
208.67.222.222 179.068 135.975 258.582 50.681 %0
208.67.220.220 137.817 135.822 140.113 1.504 %0
% ./dnseval.py -f public-v4.txt -c3 ripe.net
server avg(ms) min(ms) max(ms) stddev(ms) lost(%) flags
----------------------------------------------------------------------------------
8.8.8.8 210.225 109.864 407.420 170.785 %0 QR RD RA
8.8.4.4 107.850 93.134 120.578 13.830 %0 QR RD RA
ns.ripe.net 118.911 114.874 123.389 4.275 %0 QR AA RD
4.2.2.1 104.380 102.449 106.588 2.083 %0 QR RD RA
4.2.2.2 131.056 99.143 193.711 54.264 %0 QR RD RA
4.2.2.3 98.956 97.463 100.310 1.429 %0 QR RD RA
4.2.2.4 223.173 97.418 463.728 208.398 %0 QR RD RA
4.2.2.5 104.290 97.264 117.878 11.770 %0 QR RD RA
```
### Author

View File

@ -37,7 +37,7 @@ from statistics import stdev
import dns.rdatatype
import dns.resolver
__VERSION__ = 1.0
__VERSION__ = 1.4
__PROGNAME__ = os.path.basename(sys.argv[0])
shutdown = False
@ -69,18 +69,66 @@ def maxlen(names):
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):
resolver = dns.resolver.Resolver()
resolver.nameservers = [server]
resolver.timeout = timeout
resolver.lifetime = timeout
resolver.retry_servfail = 0
flags = 0
answers = None
resolver.use_edns(edns=True, payload=0, ednsflags=8)
response_time = []
response_times = []
i = 0
for i in range(count):
if shutdown:
if shutdown: # user pressed CTRL+C
break
try:
stime = time.time()
@ -92,18 +140,18 @@ def dnsping(host, server, dnsrecord, timeout, count):
pass
else:
elapsed = (etime - stime) * 1000 # convert to milliseconds
response_time.append(elapsed)
response_times.append(elapsed)
r_sent = i + 1
r_received = len(response_time)
r_received = len(response_times)
r_lost = r_sent - r_received
r_lost_percent = (100 * r_lost) / r_sent
if response_time:
r_min = min(response_time)
r_max = max(response_time)
r_avg = sum(response_time) / r_received
if len(response_time) > 1:
r_stddev = stdev(response_time)
if response_times:
r_min = min(response_times)
r_max = max(response_times)
r_avg = sum(response_times) / r_received
if len(response_times) > 1:
r_stddev = stdev(response_times)
else:
r_stddev = 0
else:
@ -112,19 +160,23 @@ def dnsping(host, server, dnsrecord, timeout, count):
r_avg = 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():
try:
signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z
signal.signal(signal.SIGINT, signal_handler) # ignore CTRL+C
except AttributeError: # Some systems may not support all signals
signal.signal(signal.SIGINT, signal_handler) # catch CTRL+C
except AttributeError: # Some systems (e.g. Windows) may not support all signals
pass
if len(sys.argv) == 1:
usage()
# defaults
dnsrecord = 'A'
count = 10
waittime = 5
@ -172,10 +224,13 @@ def main():
f = [name.strip() for name in f]
width = maxlen(f)
blanks = (width - 5) * ' '
print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%)')
print((60 + width) * '-')
print('server ', blanks, ' avg(ms) min(ms) max(ms) stddev(ms) lost(%) flags')
print((84 + width) * '-')
for server in f:
# check if we have a valid dns server address
if server.lstrip() == '': # deal with empty lines
continue
server = server.replace(' ', '')
try:
ipaddress.ip_address(server)
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:
print('Error: cannot resolve hostname:', server)
s = None
except:
pass
else:
s = server
if not s:
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)
s = server.ljust(width + 1)
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %%%d" % (
s, r_avg, r_min, r_max, r_stddev, r_lost_percent), flush=True)
text_flags = flags_to_text(flags)
print("%s %-8.3f %-8.3f %-8.3f %-8.3f %%%-3d %25s" % (
s, r_avg, r_min, r_max, r_stddev, r_lost_percent, text_flags), flush=True)
except Exception as e:
print('error: %s' % e)

View File

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

View File

@ -35,13 +35,28 @@ import socket
import sys
import time
import dns.query
import dns.rdatatype
import dns.resolver
from cymruwhois import cymruwhois
__author__ = 'Babak Farrokhi (babak@farrokhi.net)'
__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():
@ -79,14 +94,14 @@ def whoisrecord(ip):
currenttime = time.time()
ts = currenttime
if ip in whois:
ASN, ts = whois[ip]
asn, ts = whois[ip]
else:
ts = 0
if ((currenttime - ts) > 36000):
if (currenttime - ts) > 36000:
c = cymruwhois.Client()
ASN = c.lookup(ip)
whois[ip] = (ASN, currenttime)
return ASN
asn = c.lookup(ip)
whois[ip] = (asn, currenttime)
return asn
except Exception as 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))
return
## no expert info available
# no expert info available
print(" %s[*]%s No expert hint available for this trace" % (color.G, color.N))
def ping(resolver, hostname, dnsrecord, ttl):
global _ttl
reached = False
dns.query.socket_factory = CustomSocket
_ttl = ttl
try:
resolver.query(hostname, dnsrecord, ipttl=ttl)
resolver.query(hostname, dnsrecord)
except dns.resolver.NoNameservers as e:
if not quiet:
@ -203,7 +223,6 @@ def main():
dnsrecord = 'A'
count = 30
timeout = 1
quiet = False
dnsserver = dns.resolver.get_default_resolver().nameservers[0]
dest_port = 53
hops = 0
@ -330,11 +349,11 @@ def main():
if curr_addr:
as_name = ""
if as_lookup:
ASN = whoisrecord(curr_addr)
asn = whoisrecord(curr_addr)
as_name = ''
try:
if ASN and ASN.asn != "NA":
as_name = "[%s %s] " % (ASN.asn, ASN.owner)
if asn and asn.asn != "NA":
as_name = "[%s %s] " % (asn.asn, asn.owner)
except AttributeError:
if shutdown:
sys.exit(0)

0
requirements.txt Normal file
View File

View File

@ -1,15 +1,16 @@
from setuptools import setup, find_packages
setup(
name="dnsdiag",
version = "1.3.3",
version="1.3.5",
packages=find_packages(),
scripts = ['dnsping.py', 'dnstraceroute.py', 'dnseval.py'],
classifiers=[
"Topic :: System :: Networking",
"Environment :: Console",
"Intended Audience :: Developers",
"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",
"Operating System :: OS Independent",
],
@ -26,4 +27,11 @@ as well as tracing the path your DNS query takes to get to DNS server.
license="BSD",
keywords="dns traceroute ping",
url="https://dnsdiag.org/",
entry_points={
'console_scripts': [
'dnsping = dnsping:main',
'dnstraceroute = dnstraceroute:main',
'dnseval = dnseval:main',
]
}
)