diff options
author | Colin Watson <cjwatson@debian.org> | 2024-04-30 18:38:47 +0100 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2024-04-30 18:38:47 +0100 |
commit | 3b4aff8e0a40db5429ac298ee37a19652b18c492 (patch) | |
tree | 69e743a257cd20af0494343a07c9cfed11eb97e2 | |
parent | 573c6f69628d9839d86fa53b47ca30577aa220cb (diff) |
New upstream version 2.1.0
-rw-r--r-- | Dockerfile | 4 | ||||
-rw-r--r-- | README.md | 31 | ||||
-rwxr-xr-x | dnseval.py | 21 | ||||
-rwxr-xr-x | dnsping.py | 125 | ||||
-rwxr-xr-x | dnstraceroute.py | 23 | ||||
-rw-r--r-- | requirements.txt | 6 | ||||
-rw-r--r-- | setup.py | 8 | ||||
-rw-r--r-- | tox.ini | 4 | ||||
-rw-r--r-- | util/dns.py | 2 | ||||
-rw-r--r-- | util/shared.py | 44 | ||||
-rw-r--r-- | util/whois.py | 2 |
11 files changed, 158 insertions, 112 deletions
@@ -1,7 +1,9 @@ -FROM python:3.8.2 +FROM python:3.8-alpine WORKDIR /dnsdiag +ENV PATH "$PATH:/dnsdiag" + COPY . . RUN pip install --no-cache-dir -r requirements.txt @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/farrokhi/dnsdiag.svg)](https://travis-ci.org/farrokhi/dnsdiag) [![PyPI](https://img.shields.io/pypi/v/dnsdiag.svg?maxAge=8600)](https://pypi.python.org/pypi/dnsdiag/) [![PyPI](https://img.shields.io/pypi/l/dnsdiag.svg?maxAge=8600)]() [![PyPI](https://img.shields.io/pypi/pyversions/dnsdiag.svg?maxAge=8600)]() [![Docker Pulls](https://img.shields.io/docker/pulls/farrokhi/dnsdiag)](https://hub.docker.com/r/farrokhi/dnsdiag) [![GitHub stars](https://img.shields.io/github/stars/farrokhi/dnsdiag.svg?style=social&label=Star&maxAge=8600)](https://github.com/farrokhi/dnsdiag/stargazers) +[![Build Status](https://travis-ci.org/farrokhi/dnsdiag.svg)](https://travis-ci.org/farrokhi/dnsdiag) [![PyPI](https://img.shields.io/pypi/v/dnsdiag.svg?maxAge=8600)](https://pypi.python.org/pypi/dnsdiag/) [![PyPI](https://img.shields.io/pypi/l/dnsdiag.svg?maxAge=8600)]() [![Downloads](https://static.pepy.tech/personalized-badge/dnsdiag?period=total&units=international_system&left_color=grey&right_color=blue&left_text=PyPi%20Downloads)](https://pepy.tech/project/dnsdiag) [![PyPI](https://img.shields.io/pypi/pyversions/dnsdiag.svg?maxAge=8600)]() [![Docker Pulls](https://img.shields.io/docker/pulls/farrokhi/dnsdiag)](https://hub.docker.com/r/farrokhi/dnsdiag) [![GitHub stars](https://img.shields.io/github/stars/farrokhi/dnsdiag.svg?style=social&label=Star&maxAge=8600)](https://github.com/farrokhi/dnsdiag/stargazers) DNS Measurement, Troubleshooting and Security Auditing Toolset =============================================================== @@ -54,7 +54,7 @@ From time to time, binary packages will be released for Windows, Mac OS X and Li If you don't want to install dnsdiags on your local machine, you may use the docker image and run programs in a container. For example: ``` -docker run -it --rm farrokhi/dnsdiag ./dnsping.py +docker run --network host -it --rm farrokhi/dnsdiag dnsping.py ``` # dnsping @@ -67,18 +67,21 @@ A complete explanation of supported command line flags is shown by using `--help In addition to UDP, you can ping using TCP, DoT (DNS over TLS) and DoH (DNS over HTTPS) using `--tcp`, `--tls` and `--doh` respectively. +```shell +./dnsping.py -c 5 --dnssec --flags --tls -t AAAA -s 9.9.9.9 ripe.net +``` + ``` -% ./dnsping.py -c 5 --dnssec --flags --tls -t AAAA -s 9.9.9.9 ripe.net dnsping.py DNS: 9.9.9.9:853, hostname: ripe.net, proto: TLS, rdatatype: AAAA, flags: RD -233 bytes from 9.9.9.9: seq=1 time=186.202 ms [QR RD RA AD] -233 bytes from 9.9.9.9: seq=2 time=191.233 ms [QR RD RA AD] -233 bytes from 9.9.9.9: seq=3 time=105.455 ms [QR RD RA AD] -233 bytes from 9.9.9.9: seq=4 time=111.053 ms [QR RD RA AD] -233 bytes from 9.9.9.9: seq=5 time=110.329 ms [QR RD RA AD] +169 bytes from 9.9.9.9: seq=1 time=279.805 ms [QR RD RA AD] NOERROR +169 bytes from 9.9.9.9: seq=2 time=107.237 ms [QR RD RA AD] NOERROR +169 bytes from 9.9.9.9: seq=3 time=96.747 ms [QR RD RA AD] NOERROR +169 bytes from 9.9.9.9: seq=4 time=107.782 ms [QR RD RA AD] NOERROR +169 bytes from 9.9.9.9: seq=5 time=94.713 ms [QR RD RA AD] NOERROR --- 9.9.9.9 dnsping statistics --- 5 requests transmitted, 5 responses received, 0% lost -min=105.455 ms, avg=140.854 ms, max=191.233 ms, stddev=43.782 ms +min=94.713 ms, avg=137.257 ms, max=279.805 ms, stddev=79.908 ms ``` It also displays statistics such as minimum, maximum and average response time as well as @@ -98,8 +101,11 @@ routed to any unwanted path. In addition to UDP, it also supports TCP as transport protocol, using `--tcp` flag. +```shell +./dnstraceroute.py --expert --asn -C -t A -s 8.8.4.4 facebook.com +``` + ``` -% ./dnstraceroute.py --expert --asn -C -t A -s 8.8.4.4 facebook.com dnstraceroute.py DNS: 8.8.4.4:53, hostname: facebook.com, rdatatype: A 1 192.168.0.1 (192.168.0.1) 1 ms 2 192.168.28.177 (192.168.28.177) 4 ms @@ -123,8 +129,11 @@ You can use `dnseval` to compare response times using different transport protocols such as UDP (default), TCP, DoT and DoH using `--tcp`, `--tls` and `--doh` respectively. +```shell +./dnseval.py --dnssec -t AAAA -f public-servers.txt -c10 ripe.net +``` + ``` -% ./dnseval.py --dnssec -t AAAA -f public-servers.txt -c10 ripe.net server avg(ms) min(ms) max(ms) stddev(ms) lost(%) ttl flags response ---------------------------------------------------------------------------------------------------------------------------- 1.0.0.1 36.906 7.612 152.866 50.672 %0 300 QR -- -- RD RA AD -- NOERROR @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2021, Babak Farrokhi +# Copyright (c) 2016-2023, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -41,25 +41,10 @@ import util.dns __author__ = 'Babak Farrokhi (babak@farrokhi.net)' __license__ = 'BSD' -__version__ = '2.0.2' __progname__ = os.path.basename(sys.argv[0]) from util.dns import PROTO_UDP, PROTO_TCP, PROTO_TLS, PROTO_HTTPS, setup_signal_handler, flags_to_text - - -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.B = '' +from util.shared import __version__, Colors def usage(): @@ -117,7 +102,7 @@ def main(): try: opts, args = getopt.getopt(sys.argv[1:], "hf:c:t:w:S:TevCmXHDj:", ["help", "file=", "count=", "type=", "wait=", "json=", "tcp", "edns", "verbose", - "color", "force-miss", "srcip=", "tls", "doh", "dnssec"]) + "color", "cache-miss", "srcip=", "tls", "doh", "dnssec"]) except getopt.GetoptError as err: print(err) usage() @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2021, Babak Farrokhi +# Copyright (c) 2016-2023, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -29,7 +29,6 @@ import datetime import getopt import ipaddress import os -import requests import signal import socket import sys @@ -38,12 +37,13 @@ from statistics import stdev import dns.flags import dns.resolver +import requests -from util.dns import PROTO_UDP, PROTO_TCP, PROTO_TLS, PROTO_HTTPS, proto_to_text, unsupported_feature +from util.dns import PROTO_UDP, PROTO_TCP, PROTO_TLS, PROTO_HTTPS, proto_to_text, unsupported_feature, random_string +from util.shared import __version__ __author__ = 'Babak Farrokhi (babak@farrokhi.net)' __license__ = 'BSD' -__version__ = '2.0.2' __progname__ = os.path.basename(sys.argv[0]) shutdown = False @@ -52,29 +52,39 @@ def usage(): print("""%s version %s usage: %s [-46DeFhqTvX] [-i interval] [-s server] [-p port] [-P port] [-S address] [-c count] [-t type] [-w wait] hostname - -h --help Show this help - -q --quiet Quiet - -v --verbose Print actual dns response - -s --server DNS server to use (default: first entry from /etc/resolv.conf) - -p --port DNS server port number (default: 53 for TCP/UDP and 853 for TLS) - -T --tcp Use TCP as transport protocol - -X --tls Use TLS as transport protocol - -H --doh Use HTTPS as transport protols (DoH) - -4 --ipv4 Use IPv4 as default network protocol - -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, 0 for infinity) - -w --wait Maximum wait time for a reply (default: 2 seconds) - -i --interval Time between each request (default: 1 seconds) - -t --type DNS request record type (default: A) - -e --edns Disable EDNS0 (default: Enabled) - -D --dnssec Enable 'DNSSEC desired' flag in requests. Implies EDNS. - -F --flags Display response flags + -h --help Show this help + -q --quiet Quiet + -v --verbose Print actual dns response + -s --server DNS server to use (default: first entry from /etc/resolv.conf) + -p --port DNS server port number (default: 53 for TCP/UDP and 853 for TLS) + -T --tcp Use TCP as transport protocol + -X --tls Use TLS as transport protocol + -H --doh Use HTTPS as transport protols (DoH) + -4 --ipv4 Use IPv4 as default network protocol + -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, 0 for infinity) + -r --norecurse Enforce non-recursive query by clearing the RD (recursion desired) bit in the query + -m --cache-miss Force cache miss measurement by prepending a random hostname + -w --wait Maximum wait time for a reply (default: 2 seconds) + -i --interval Time between each request (default: 1 seconds) + -t --type DNS request record type (default: A) + -e --edns Disable EDNS0 (default: Enabled) + -D --dnssec Enable 'DNSSEC desired' flag in requests. Implies EDNS. + -F --flags Display response flags """ % (__progname__, __version__, __progname__)) sys.exit(0) +def setup_signal_handler(): + try: + signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z + signal.signal(signal.SIGINT, signal_handler) # custom CTRL+C handler + except AttributeError: # not all signals are supported on all platforms + pass + + def signal_handler(sig, frame): global shutdown if shutdown: # pressed twice, so exit immediately @@ -82,12 +92,22 @@ def signal_handler(sig, frame): shutdown = True # pressed once, exit gracefully -def main(): +def validate_server_address(dnsserver, address_family): + """checks if we have a valid dns server address and resolve if it is a hostname""" + try: - signal.signal(signal.SIGTSTP, signal.SIG_IGN) # ignore CTRL+Z - signal.signal(signal.SIGINT, signal_handler) # custom CTRL+C handler - except AttributeError: # OS Does not support some signals, probably windows - pass + ipaddress.ip_address(dnsserver) + except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name + try: + dnsserver = socket.getaddrinfo(dnsserver, port=None, family=address_family)[1][4][0] + except OSError: + print('Error: cannot resolve hostname:', dnsserver, file=sys.stderr, flush=True) + sys.exit(1) + return dnsserver + + +def main(): + setup_signal_handler() if len(sys.argv) == 1: usage() @@ -107,14 +127,16 @@ def main(): proto = PROTO_UDP use_edns = True want_dnssec = False + force_miss = False + request_flags = dns.flags.from_text('RD') af = socket.AF_INET qname = 'wikipedia.org' try: - opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:i:vp:P:S:T46eDFXH", + opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:i:vp:P:S:T46meDFXHr", ["help", "count=", "server=", "quiet", "type=", "wait=", "interval=", "verbose", - "port=", "srcip=", "tcp", "ipv4", "ipv6", "srcport=", "edns", "dnssec", "flags", - "tls", "doh"]) + "port=", "srcip=", "tcp", "ipv4", "ipv6", "cache-miss", "srcport=", "edns", + "dnssec", "flags", "norecurse", "tls", "doh"]) except getopt.GetoptError as err: # print help information and exit: print(err, file=sys.stderr) # will print something like "option -a not recognized" @@ -139,8 +161,10 @@ def main(): verbose = False elif o in ("-w", "--wait"): timeout = int(a) + elif o in ("-m", "--cache-miss"): + force_miss = True elif o in ("-i", "--interval"): - interval = int(a) + interval = float(a) elif o in ("-t", "--type"): rdatatype = a elif o in ("-T", "--tcp"): @@ -157,6 +181,8 @@ def main(): af = socket.AF_INET6 elif o in ("-e", "--edns"): use_edns = False + elif o in ("-r", "--norecurse"): + request_flags = dns.flags.from_text('') elif o in ("-D", "--dnssec"): want_dnssec = True elif o in ("-F", "--flags"): @@ -177,30 +203,14 @@ def main(): if dnsserver is None: dnsserver = dns.resolver.get_default_resolver().nameservers[0] - # check if we have a valid dns server address - try: - ipaddress.ip_address(dnsserver) - except ValueError: # so it is not a valid IPv4 or IPv6 address, so try to resolve host name - try: - dnsserver = socket.getaddrinfo(dnsserver, port=None, family=af)[1][4][0] - except OSError: - print('Error: cannot resolve hostname:', dnsserver, file=sys.stderr, flush=True) - sys.exit(1) - - if use_edns: - query = dns.message.make_query(qname, rdatatype, dns.rdataclass.IN, - use_edns=True, want_dnssec=want_dnssec, - ednsflags=dns.flags.edns_from_text('DO'), payload=8192) - else: - query = dns.message.make_query(qname, rdatatype, dns.rdataclass.IN, - use_edns=False, want_dnssec=want_dnssec) + dnsserver = validate_server_address(dnsserver, af) response_time = [] i = 0 print("%s DNS: %s:%d, hostname: %s, proto: %s, rdatatype: %s, flags: %s" % (__progname__, dnsserver, dst_port, qname, proto_to_text(proto), rdatatype, - dns.flags.to_text(query.flags)), flush=True) + dns.flags.to_text(request_flags)), flush=True) while not shutdown: @@ -209,6 +219,19 @@ def main(): else: i += 1 + if force_miss: + fqdn = "_dnsdiag_%s_.%s" % (random_string(8, 8), qname) + else: + fqdn = qname + + if use_edns: + query = dns.message.make_query(fqdn, rdatatype, dns.rdataclass.IN, flags=request_flags, + use_edns=True, want_dnssec=want_dnssec, + ednsflags=dns.flags.EDNSFlag.DO, payload=8192) + else: + query = dns.message.make_query(fqdn, rdatatype, dns.rdataclass.IN, flags=request_flags, + use_edns=False, want_dnssec=want_dnssec) + try: stime = time.perf_counter() if proto is PROTO_UDP: @@ -261,7 +284,7 @@ def main(): flags = " [%s] %s" % (dns.flags.to_text(answers.flags), dns.rcode.to_text(answers.rcode())) else: flags = "" - print("%d bytes from %s: seq=%-3d time=%.3f ms%s" % ( + print("%d bytes from %s: seq=%-3d time=%-7.3f ms%s" % ( len(answers.to_wire()), dnsserver, i, elapsed, flags), flush=True) if verbose: print(answers.to_text(), flush=True) diff --git a/dnstraceroute.py b/dnstraceroute.py index 2838cc9..2e1c3a8 100755 --- a/dnstraceroute.py +++ b/dnstraceroute.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2021, Babak Farrokhi +# Copyright (c) 2016-2023, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -39,6 +39,7 @@ import dns.resolver import util.whois from util.dns import PROTO_UDP, PROTO_TCP, setup_signal_handler +from util.shared import __version__, Colors # Global Variables quiet = False @@ -47,7 +48,6 @@ whois_cache = {} # Constants __author__ = 'Babak Farrokhi (babak@farrokhi.net)' __license__ = 'BSD' -__version__ = '2.0.2' __progname__ = os.path.basename(sys.argv[0]) @@ -56,26 +56,12 @@ def test_import(): pass -class Colors(object): - N = '\033[m' # native - R = '\033[31m' # red - G = '\033[32m' # green - B = '\033[34m' # blue - - def __init__(self, mode): - if not mode: - self.N = '' - self.R = '' - self.G = '' - self.B = '' - - def usage(): print("""%s version %s usage: %s [-aeqhCx] [-s server] [-p port] [-c count] [-t type] [-w wait] hostname -h --help Show this help - -q --quiet Quiet + -q --quiet Quiet mode: No extra information, only traceroute output. -T --tcp Use TCP as transport protocol -x --expert Print expert hints if available -a --asn Turn on AS# lookups for each hop encountered @@ -291,6 +277,7 @@ def main(): else: elapsed = abs(etime - stime) * 1000 # convert to milliseconds + curr_name = curr_addr if should_resolve: try: if curr_addr: @@ -301,8 +288,6 @@ def main(): pass except Exception: print("unxpected error: ", sys.exc_info()[0]) - else: - curr_name = curr_addr global whois_cache if curr_addr: diff --git a/requirements.txt b/requirements.txt index b5a2f2a..78a643e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -dnspython>=1.16.0 +dnspython>=2.3.0 cymruwhois>=1.6 -requests>=2.21.0 -requests-toolbelt>=0.9.1 +requests==2.28.2 +requests-toolbelt==0.10.1 @@ -1,20 +1,18 @@ from setuptools import setup, find_packages +from util.shared import __version__ setup( name="dnsdiag", - version="2.0.2", + version=__version__, packages=find_packages(), scripts=["dnseval.py", "dnsping.py", "dnstraceroute.py"], - install_requires=['dnspython>=1.16.0', 'cymruwhois>=1.6', 'requests>=2.21.0', 'requests-toolbelt>=0.9.1'], + install_requires=['dnspython>=2.3.0', 'cymruwhois>=1.6', 'requests>=2.28.2', 'requests-toolbelt>=0.9.1'], classifiers=[ "Topic :: System :: Networking", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -1,5 +1,5 @@ [pycodestyle] -ignore = E501 +ignore = E501, E741 [flake8] -ignore = E501 +ignore = E501, E741 diff --git a/util/dns.py b/util/dns.py index 0dea46f..f5cef38 100644 --- a/util/dns.py +++ b/util/dns.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2021, Babak Farrokhi +# Copyright (c) 2016-2023, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/util/shared.py b/util/shared.py new file mode 100644 index 0000000..ebb66be --- /dev/null +++ b/util/shared.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2016-2023, 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. + + +__version__ = '2.1.0' + + +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 = '' diff --git a/util/whois.py b/util/whois.py index f037335..fd6ad6c 100644 --- a/util/whois.py +++ b/util/whois.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2021, Babak Farrokhi +# Copyright (c) 2016-2023, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without |