diff --git a/.gitignore b/.gitignore index 1dbc687..a39d774 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__/ *.py[cod] *$py.class +.idea/ # C extensions *.so diff --git a/dnsping.py b/dnsping.py new file mode 100755 index 0000000..66c1b1e --- /dev/null +++ b/dnsping.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# 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 +import time +from statistics import stdev +import sys +import signal + +import dns.rdatatype +import dns.resolver + +__VERSION__ = 1.0 + + +def usage(): + print('dnsping version %1.1f\n' % __VERSION__) + print('syntax: dnsping [-h] [-q] [-s server] [-c count] [-t type] [-w wait] hostname') + print(' -h --help show this help') + print(' -q --quiet quiet') + print(' -v --verbose print actual dns response') + print(' -s --server dns server to use (default: 8.8.8.8)') + print(' -c --count number of requests to send (default: 10)') + print(' -w --wait maximum wait time for a reply (default: 5)') + print(' -t --type DNS request record type (default: A)') + print(' ') + exit() + + +def main(): + # if not __debug__: + # signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z + # signal.signal(signal.SIGINT, signal.SIG_IGN) # ignore CTRL+C + + hostname = 'wikipedia.org' + dnsrecord = 'A' + count = 10 + timeout = 5 + verbose = False + dnsserver = '8.8.8.8' + + try: + opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:v", + ["help", "output=", "count=", "server=", "quiet", "type=", "wait=", "verbose"]) + except getopt.GetoptError as err: + # print help information and exit: + print(err) # will print something like "option -a not recognized" + usage() + exit(2) + + quiet = False + hn = [h for h in sys.argv[-1:] if not h.startswith('-')] + hostname = hn[0] + + for o, a in opts: + if o in ("-h", "--help"): + usage() + elif o in ("-c", "--count"): + count = int(a) + elif o in ("-v", "--verbose"): + verbose = True + elif o in ("-s", "--server"): + dnsserver = a + elif o in ("-q", "--quiet"): + quiet = True + verbose = False + elif o in ("-w", "--wait"): + timeout = int(a) + elif o in ("-t", "--type"): + dnsrecord = a + else: + if a.startswith('-'): + print("unknown syntax: %s" % a) + usage() + else: + hostname = a + + resolver = dns.resolver.Resolver() + resolver.nameservers.append(dnsserver) + resolver.timeout = timeout + resolver.lifetime = timeout + resolver.retry_servfail = 0 + + response_time = [] + + print("DNSPING %s: hostname=%s rdatatype=%s" % (dnsserver, hostname, dnsrecord)) + + for i in range(count): + try: + stime = time.time() + answers = resolver.query(hostname, dnsrecord) + etime = time.time() + except dns.resolver.NoNameservers: + if not quiet: + print("no response to dns request") + exit(1) + except dns.resolver.Timeout: + if not quiet: + print("request timeout") + pass + except dns.resolver.NoAnswer: + if not quiet: + print("invalid answer") + pass + else: + elapsed = (etime - stime) * 1000 # convert to milliseconds + response_time.append(elapsed) + if not quiet: + print( + "%d bytes from %s: seq=%-3d time=%3.3f ms" % ( + len(str(answers.rrset)), dnsserver, i, elapsed)) + if verbose: + print(answers.rrset) + + 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 + r_stddev = stdev(response_time) + else: + r_min = 0 + r_max = 0 + r_avg = 0 + r_stddev = 0 + + print('\n--- %s dnsping statistics ---' % dnsserver) + print('%d requests transmitted, %d responses received, %3.0f%% lost' % (r_sent, r_received, r_lost_percent)) + print('min=%3.3f ms, avg=%3.3f ms, max=%3.3f ms, stddev=%3.3f ms' % (r_min, r_avg, r_max, r_stddev)) + + +if __name__ == '__main__': + main()