2016-02-03 15:12:27 +03:30
|
|
|
#!/usr/bin/env python3
|
2016-02-06 10:35:46 +03:30
|
|
|
#
|
2016-02-03 15:12:27 +03:30
|
|
|
# Copyright (c) 2016, Babak Farrokhi
|
|
|
|
# All rights reserved.
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
|
|
# modification, are permitted provided that the following conditions are met:
|
|
|
|
#
|
|
|
|
# * Redistributions of source code must retain the above copyright notice, this
|
|
|
|
# list of conditions and the following disclaimer.
|
|
|
|
#
|
|
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
|
|
# and/or other materials provided with the distribution.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
|
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
|
|
|
|
|
|
|
import getopt
|
2016-04-10 16:43:09 +04:30
|
|
|
import ipaddress
|
2016-04-09 11:37:42 +04:30
|
|
|
import os
|
2016-02-10 09:23:06 +03:30
|
|
|
import signal
|
2016-04-10 16:43:09 +04:30
|
|
|
import socket
|
2016-02-10 09:23:06 +03:30
|
|
|
import sys
|
2016-02-03 15:12:27 +03:30
|
|
|
import time
|
|
|
|
from statistics import stdev
|
|
|
|
|
2016-05-16 19:05:48 +04:30
|
|
|
import dns.flags
|
2016-04-10 16:43:09 +04:30
|
|
|
import dns.rdatatype
|
|
|
|
import dns.resolver
|
|
|
|
|
2017-04-24 17:43:47 +04:30
|
|
|
__author__ = 'Babak Farrokhi (babak@farrokhi.net)'
|
|
|
|
__license__ = 'BSD'
|
2018-04-03 17:01:50 +04:30
|
|
|
__version__ = "1.6.4"
|
2017-04-24 17:43:47 +04:30
|
|
|
__progname__ = os.path.basename(sys.argv[0])
|
2016-05-07 14:47:15 +04:30
|
|
|
shutdown = False
|
2016-02-03 15:12:27 +03:30
|
|
|
|
|
|
|
|
|
|
|
def usage():
|
2017-04-24 17:43:47 +04:30
|
|
|
print("""%s version %s
|
2016-08-06 16:36:56 +04:30
|
|
|
usage: %s [-ehqv] [-s server] [-p port] [-P port] [-S address] [-c count] [-t type] [-w wait] hostname
|
2016-04-16 14:11:38 +04:30
|
|
|
-h --help Show this help
|
|
|
|
-q --quiet Quiet
|
|
|
|
-v --verbose Print actual dns response
|
2016-05-04 13:51:43 +04:30
|
|
|
-s --server DNS server to use (default: first entry from /etc/resolv.conf)
|
2016-04-16 14:11:38 +04:30
|
|
|
-p --port DNS server port number (default: 53)
|
2016-05-04 12:49:34 +04:30
|
|
|
-T --tcp Use TCP instead of UDP
|
2016-05-04 13:05:02 +04:30
|
|
|
-4 --ipv4 Use IPv4 as default network protocol
|
|
|
|
-6 --ipv6 Use IPv6 as default network protocol
|
2016-04-16 14:11:38 +04:30
|
|
|
-P --srcport Query source port number (default: 0)
|
|
|
|
-S --srcip Query source IP address (default: default interface address)
|
2019-03-15 17:36:39 +01:00
|
|
|
-c --count Number of requests to send (default: 10, 0 for infinity)
|
2017-04-24 14:41:53 +04:30
|
|
|
-w --wait Maximum wait time for a reply (default: 2 seconds)
|
2017-10-06 10:46:16 +03:30
|
|
|
-i --interval Time between each request (default: 1 seconds)
|
2016-04-16 14:11:38 +04:30
|
|
|
-t --type DNS request record type (default: A)
|
2016-08-06 16:36:56 +04:30
|
|
|
-e --edns Disable EDNS0 (default: Enabled)
|
2017-04-24 17:43:47 +04:30
|
|
|
""" % (__progname__, __version__, __progname__))
|
2016-05-09 13:32:45 +04:30
|
|
|
sys.exit(0)
|
2016-02-03 15:12:27 +03:30
|
|
|
|
|
|
|
|
2016-02-06 10:15:08 +03:30
|
|
|
def signal_handler(sig, frame):
|
2016-05-07 14:47:15 +04:30
|
|
|
global shutdown
|
2016-05-09 11:24:22 +04:30
|
|
|
if shutdown: # pressed twice, so exit immediately
|
2016-05-09 13:32:45 +04:30
|
|
|
sys.exit(0)
|
2016-05-09 11:24:22 +04:30
|
|
|
shutdown = True # pressed once, exit gracefully
|
2016-02-06 09:36:01 +03:30
|
|
|
|
|
|
|
|
2016-02-03 15:12:27 +03:30
|
|
|
def main():
|
2016-04-14 16:51:07 +04:30
|
|
|
try:
|
|
|
|
signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z
|
|
|
|
signal.signal(signal.SIGINT, signal_handler) # custom CTRL+C handler
|
2016-04-16 14:11:38 +04:30
|
|
|
except AttributeError: # OS Does not support some signals, probably windows
|
2016-04-14 16:51:07 +04:30
|
|
|
pass
|
2016-02-03 15:12:27 +03:30
|
|
|
|
2016-02-03 16:08:11 +03:30
|
|
|
if len(sys.argv) == 1:
|
|
|
|
usage()
|
|
|
|
|
2016-04-12 12:57:26 +04:30
|
|
|
# defaults
|
2016-02-03 15:12:27 +03:30
|
|
|
dnsrecord = 'A'
|
|
|
|
count = 10
|
2016-08-22 16:02:25 +04:30
|
|
|
timeout = 2
|
2017-10-06 10:46:16 +03:30
|
|
|
interval = 1
|
2016-02-03 16:08:11 +03:30
|
|
|
quiet = False
|
2016-02-03 15:12:27 +03:30
|
|
|
verbose = False
|
2016-05-04 13:51:43 +04:30
|
|
|
dnsserver = dns.resolver.get_default_resolver().nameservers[0]
|
2016-04-30 14:42:29 +04:30
|
|
|
dst_port = 53
|
2016-04-16 14:11:38 +04:30
|
|
|
src_port = 0
|
|
|
|
src_ip = None
|
2016-05-04 12:49:34 +04:30
|
|
|
use_tcp = False
|
2016-08-06 16:36:56 +04:30
|
|
|
use_edns = True
|
2016-05-09 16:43:07 +04:30
|
|
|
af = socket.AF_INET
|
2016-02-06 10:15:08 +03:30
|
|
|
hostname = 'wikipedia.org'
|
2016-02-03 15:12:27 +03:30
|
|
|
|
|
|
|
try:
|
2016-08-04 14:13:33 +10:00
|
|
|
opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:i:vp:P:S:T46e",
|
|
|
|
["help", "count=", "server=", "quiet", "type=", "wait=", "interval=", "verbose",
|
2016-06-26 16:54:22 +04:30
|
|
|
"port=", "srcip=", "tcp", "ipv4", "ipv6", "srcport=", "edns"])
|
2016-02-03 15:12:27 +03:30
|
|
|
except getopt.GetoptError as err:
|
|
|
|
# print help information and exit:
|
2019-03-15 17:47:48 +01:00
|
|
|
print(err, file=sys.stderr) # will print something like "option -a not recognized"
|
2016-02-03 15:12:27 +03:30
|
|
|
usage()
|
|
|
|
|
2016-02-03 16:08:11 +03:30
|
|
|
if args and len(args) == 1:
|
|
|
|
hostname = args[0]
|
|
|
|
else:
|
|
|
|
usage()
|
2016-02-03 15:12:27 +03:30
|
|
|
|
|
|
|
for o, a in opts:
|
|
|
|
if o in ("-h", "--help"):
|
|
|
|
usage()
|
|
|
|
elif o in ("-c", "--count"):
|
2019-03-15 17:36:39 +01:00
|
|
|
count = abs(int(a))
|
2016-02-03 15:12:27 +03:30
|
|
|
elif o in ("-v", "--verbose"):
|
|
|
|
verbose = True
|
|
|
|
elif o in ("-s", "--server"):
|
|
|
|
dnsserver = a
|
2016-04-12 12:57:26 +04:30
|
|
|
elif o in ("-p", "--port"):
|
2016-04-30 14:42:29 +04:30
|
|
|
dst_port = int(a)
|
2016-02-03 15:12:27 +03:30
|
|
|
elif o in ("-q", "--quiet"):
|
|
|
|
quiet = True
|
|
|
|
verbose = False
|
|
|
|
elif o in ("-w", "--wait"):
|
|
|
|
timeout = int(a)
|
2016-08-04 14:13:33 +10:00
|
|
|
elif o in ("-i", "--interval"):
|
|
|
|
interval = int(a)
|
2016-02-03 15:12:27 +03:30
|
|
|
elif o in ("-t", "--type"):
|
|
|
|
dnsrecord = a
|
2016-05-04 12:49:34 +04:30
|
|
|
elif o in ("-T", "--tcp"):
|
|
|
|
use_tcp = True
|
2016-05-04 13:05:02 +04:30
|
|
|
elif o in ("-4", "--ipv4"):
|
|
|
|
af = socket.AF_INET
|
|
|
|
elif o in ("-6", "--ipv6"):
|
|
|
|
af = socket.AF_INET6
|
2016-06-26 16:54:22 +04:30
|
|
|
elif o in ("-e", "--edns"):
|
2016-08-06 16:36:56 +04:30
|
|
|
use_edns = False
|
2016-04-16 14:11:38 +04:30
|
|
|
elif o in ("-P", "--srcport"):
|
|
|
|
src_port = int(a)
|
|
|
|
if src_port < 1024:
|
2019-03-15 17:47:48 +01:00
|
|
|
print("WARNING: Source ports below 1024 are only available to superuser", flush=True)
|
2016-04-16 14:11:38 +04:30
|
|
|
elif o in ("-S", "--srcip"):
|
|
|
|
src_ip = a
|
2016-02-03 15:12:27 +03:30
|
|
|
else:
|
2016-02-04 12:15:21 +03:30
|
|
|
usage()
|
2016-02-03 15:12:27 +03:30
|
|
|
|
2016-04-10 16:43:09 +04:30
|
|
|
# check if we have a valid dns server address
|
|
|
|
try:
|
|
|
|
ipaddress.ip_address(dnsserver)
|
2016-04-10 19:23:20 +04:30
|
|
|
except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name
|
2016-04-10 16:43:09 +04:30
|
|
|
try:
|
2016-05-09 13:56:35 +04:30
|
|
|
dnsserver = socket.getaddrinfo(dnsserver, port=None, family=af)[1][4][0]
|
2016-04-10 16:43:09 +04:30
|
|
|
except OSError:
|
2019-03-15 17:47:48 +01:00
|
|
|
print('Error: cannot resolve hostname:', dnsserver, file=sys.stderr, flush=True)
|
2016-05-09 13:32:45 +04:30
|
|
|
sys.exit(1)
|
2016-04-10 16:43:09 +04:30
|
|
|
|
2016-02-03 15:12:27 +03:30
|
|
|
resolver = dns.resolver.Resolver()
|
2016-02-06 09:36:01 +03:30
|
|
|
resolver.nameservers = [dnsserver]
|
2016-02-03 15:12:27 +03:30
|
|
|
resolver.timeout = timeout
|
|
|
|
resolver.lifetime = timeout
|
2016-04-30 14:42:29 +04:30
|
|
|
resolver.port = dst_port
|
2016-02-03 15:12:27 +03:30
|
|
|
resolver.retry_servfail = 0
|
|
|
|
|
2016-06-26 16:54:22 +04:30
|
|
|
if use_edns:
|
|
|
|
resolver.use_edns(edns=0, payload=8192, ednsflags=dns.flags.edns_from_text('DO'))
|
|
|
|
|
2016-02-03 15:12:27 +03:30
|
|
|
response_time = []
|
2016-02-06 10:15:08 +03:30
|
|
|
i = 0
|
2016-02-03 15:12:27 +03:30
|
|
|
|
2019-03-15 17:47:48 +01:00
|
|
|
print("%s DNS: %s:%d, hostname: %s, rdatatype: %s" % (__progname__, dnsserver, dst_port, hostname, dnsrecord),
|
|
|
|
flush=True)
|
2016-02-03 15:12:27 +03:30
|
|
|
|
2019-03-15 17:36:39 +01:00
|
|
|
while not shutdown:
|
|
|
|
|
|
|
|
if 0 < count <= i:
|
2016-02-06 09:36:01 +03:30
|
|
|
break
|
2019-03-15 17:36:39 +01:00
|
|
|
else:
|
|
|
|
i += 1
|
|
|
|
|
2016-02-03 15:12:27 +03:30
|
|
|
try:
|
2017-04-23 13:23:17 +04:30
|
|
|
stime = time.perf_counter()
|
2016-06-01 13:23:46 +04:30
|
|
|
answers = resolver.query(hostname, dnsrecord, source_port=src_port, source=src_ip, tcp=use_tcp,
|
2016-05-16 19:05:48 +04:30
|
|
|
raise_on_no_answer=False)
|
2017-04-23 13:23:17 +04:30
|
|
|
etime = time.perf_counter()
|
2016-04-07 11:57:48 +04:30
|
|
|
except dns.resolver.NoNameservers as e:
|
2016-02-03 15:12:27 +03:30
|
|
|
if not quiet:
|
2019-03-15 17:47:48 +01:00
|
|
|
print("No response to dns request", file=sys.stderr, flush=True)
|
2016-04-07 11:57:48 +04:30
|
|
|
if verbose:
|
2019-03-15 17:47:48 +01:00
|
|
|
print("error:", e, file=sys.stderr, flush=True)
|
2016-05-09 13:32:45 +04:30
|
|
|
sys.exit(1)
|
2016-04-07 11:57:48 +04:30
|
|
|
except dns.resolver.NXDOMAIN as e:
|
|
|
|
if not quiet:
|
2019-03-15 17:47:48 +01:00
|
|
|
print("Hostname does not exist", file=sys.stderr, flush=True)
|
2016-04-07 11:57:48 +04:30
|
|
|
if verbose:
|
2019-03-15 17:47:48 +01:00
|
|
|
print("Error:", e, file=sys.stderr, flush=True)
|
2016-05-09 13:32:45 +04:30
|
|
|
sys.exit(1)
|
2016-02-03 15:12:27 +03:30
|
|
|
except dns.resolver.Timeout:
|
|
|
|
if not quiet:
|
2019-03-15 17:47:48 +01:00
|
|
|
print("Request timeout", flush=True)
|
2016-02-03 15:12:27 +03:30
|
|
|
pass
|
|
|
|
except dns.resolver.NoAnswer:
|
|
|
|
if not quiet:
|
2019-03-15 17:47:48 +01:00
|
|
|
print("No answer", flush=True)
|
2016-02-03 15:12:27 +03:30
|
|
|
pass
|
|
|
|
else:
|
2018-01-23 11:32:40 +03:30
|
|
|
elapsed = answers.response.time * 1000 # convert to milliseconds
|
2016-02-03 15:12:27 +03:30
|
|
|
response_time.append(elapsed)
|
|
|
|
if not quiet:
|
|
|
|
print(
|
2017-04-23 13:23:17 +04:30
|
|
|
"%d bytes from %s: seq=%-3d time=%.3f ms" % (
|
2019-03-15 17:47:48 +01:00
|
|
|
len(str(answers.rrset)), dnsserver, i, elapsed), flush=True)
|
2016-02-03 15:12:27 +03:30
|
|
|
if verbose:
|
2019-03-15 17:47:48 +01:00
|
|
|
print(answers.rrset, flush=True)
|
|
|
|
print("flags:", dns.flags.to_text(answers.response.flags), flush=True)
|
2016-02-03 15:12:27 +03:30
|
|
|
|
2016-08-04 14:13:33 +10:00
|
|
|
time_to_next = (stime + interval) - etime
|
|
|
|
if time_to_next > 0:
|
|
|
|
time.sleep(time_to_next)
|
|
|
|
|
2016-02-03 15:12:27 +03:30
|
|
|
r_sent = i + 1
|
|
|
|
r_received = len(response_time)
|
|
|
|
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
|
2016-05-04 12:40:05 +04:30
|
|
|
if len(response_time) > 1:
|
|
|
|
r_stddev = stdev(response_time)
|
|
|
|
else:
|
|
|
|
r_stddev = 0
|
2016-02-03 15:12:27 +03:30
|
|
|
else:
|
|
|
|
r_min = 0
|
|
|
|
r_max = 0
|
|
|
|
r_avg = 0
|
|
|
|
r_stddev = 0
|
|
|
|
|
2019-03-15 17:47:48 +01:00
|
|
|
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)
|
2016-02-03 15:12:27 +03:30
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|