Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package dnsdiag for openSUSE:Factory checked in at 2025-09-22 16:41:04 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/dnsdiag (Old) and /work/SRC/openSUSE:Factory/.dnsdiag.new.27445 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "dnsdiag" Mon Sep 22 16:41:04 2025 rev:9 rq:1306502 version:2.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/dnsdiag/dnsdiag.changes 2024-10-28 15:24:20.233504000 +0100 +++ /work/SRC/openSUSE:Factory/.dnsdiag.new.27445/dnsdiag.changes 2025-09-22 16:41:56.283104468 +0200 @@ -1,0 +2,24 @@ +Sun Sep 21 10:26:34 UTC 2025 - Martin Hauke <[email protected]> + +- Update to version 2.8.0 + New Features + * DNS over HTTP/3 (DoH3) Support: Added support for RFC 9114 DNS + over HTTP3 protocol using -3 or --doh3 option in dnsping. + * Improved Error Handling: Enhanced error handling for DoH3 + connection failures. + Breaking Changes + * Python 3.9 Support Dropped: Minimum Python version is now 3.10 + due to dnspython 2.8.0 requirements. + Improvements + * DoQ and DoH3 Enhancements: + + Upgraded to dnspython 2.8.0 which provides improved DoQ (DNS + over QUIC) and DoH3 error handling. + * Display Enhancements: + + Fixed RTT display that was broken in previous release. + + Improved response time display. + + Better display of DNS response flags. + + Fixed EDE (Extended DNS Error) payload display to show empty + string instead of "None". + + Added quotes around EDE payload strings for better visibility + +------------------------------------------------------------------- Old: ---- dnsdiag-2.6.0.tar.gz New: ---- dnsdiag-2.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ dnsdiag.spec ++++++ --- /var/tmp/diff_new_pack.V1QPVP/_old 2025-09-22 16:41:56.955132704 +0200 +++ /var/tmp/diff_new_pack.V1QPVP/_new 2025-09-22 16:41:56.955132704 +0200 @@ -1,8 +1,8 @@ # # spec file for package dnsdiag # -# Copyright (c) 2024 SUSE LLC -# Copyright (c) 2017-2024, Martin Hauke <[email protected]> +# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2017-2025, Martin Hauke <[email protected]> # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,23 +19,24 @@ %bcond_without test Name: dnsdiag -Version: 2.6.0 +Version: 2.7.0 Release: 0 Summary: DNS request auditing toolset License: BSD-3-Clause Group: Development/Languages/Python #Git-Clone: https://github.com/farrokhi/dnsdiag.git URL: https://dnsdiag.org/ -Source: https://files.pythonhosted.org/packages/source/d/dnsdiag/dnsdiag-%{version}.tar.gz +Source: https://github.com/farrokhi/dnsdiag/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz Source1: dnseval.1 Source2: dnsping.1 Source3: dnstraceroute.1 BuildRequires: fdupes BuildRequires: python-rpm-macros BuildRequires: python3-setuptools +Requires: python3-aioquic >= 1.2.0 Requires: python3-cryptography >= 42.0.5 Requires: python3-cymruwhois >= 1.6 -Requires: python3-dnspython >= 2.6.1 +Requires: python3-dnspython >= 2.8.0 Requires: python3-h2 >= 4.1.0 Requires: python3-httpx >= 0.27.0 BuildArch: noarch @@ -65,8 +66,8 @@ of a resolver. %prep -%setup -q -n dnsdiag-%{version} -sed -e '/^#!\//, 1d' -i util/*.py +%autosetup -n dnsdiag-%{version} +sed -e '/^#!\//, 1d' -i dnsdiag/*.py %build %python3_build @@ -91,5 +92,5 @@ %{_mandir}/man1/dnseval.1%{?ext_man} %{_mandir}/man1/dnstraceroute.1%{?ext_man} %{_mandir}/man1/dnsping.1%{?ext_man} -%{python3_sitelib}/* +%{python3_sitelib}/dnsdiag* ++++++ dnsdiag-2.6.0.tar.gz -> dnsdiag-2.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/.github/workflows/packages.yml new/dnsdiag-2.7.0/.github/workflows/packages.yml --- old/dnsdiag-2.6.0/.github/workflows/packages.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/.github/workflows/packages.yml 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,33 @@ +name: Package Build + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v5 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pyinstaller + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=60 --max-line-length=127 --statistics + + - name: Build package + run: | + sh build-pkgs.sh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/.gitignore new/dnsdiag-2.7.0/.gitignore --- old/dnsdiag-2.6.0/.gitignore 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/.gitignore 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,71 @@ +# virtualenv +.venv/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class +*.json +.idea/ +.vscode/ +whois.cache +pkg/ + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#Ipython Notebook +.ipynb_checkpoints +results.json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/Dockerfile new/dnsdiag-2.7.0/Dockerfile --- old/dnsdiag-2.6.0/Dockerfile 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/Dockerfile 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,10 @@ +FROM python:3.12-alpine + +WORKDIR /dnsdiag + +ENV PATH "$PATH:/dnsdiag" + +COPY . . + +RUN pip install --no-cache-dir -r requirements.txt + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/LICENSE new/dnsdiag-2.7.0/LICENSE --- old/dnsdiag-2.6.0/LICENSE 2024-10-25 16:53:59.000000000 +0200 +++ new/dnsdiag-2.7.0/LICENSE 2025-09-21 11:55:15.000000000 +0200 @@ -1,4 +1,4 @@ -Copyright (c) 2024, Babak Farrokhi +Copyright (c) 2025, Babak Farrokhi All rights reserved. Redistribution and use in source and binary forms, with or without diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/MANIFEST.in new/dnsdiag-2.7.0/MANIFEST.in --- old/dnsdiag-2.6.0/MANIFEST.in 2019-11-02 13:38:19.000000000 +0100 +++ new/dnsdiag-2.7.0/MANIFEST.in 2025-09-21 11:55:15.000000000 +0200 @@ -1 +1 @@ -include LICENSE README.md TODO.md public-servers.txt public-v4.txt rootservers.txt +include LICENSE README.md public-servers.txt public-v4.txt rootservers.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/PKG-INFO new/dnsdiag-2.7.0/PKG-INFO --- old/dnsdiag-2.6.0/PKG-INFO 2024-10-25 23:57:52.778604300 +0200 +++ new/dnsdiag-2.7.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,35 +0,0 @@ -Metadata-Version: 2.1 -Name: dnsdiag -Version: 2.6.0 -Summary: DNS Measurement, Troubleshooting and Security Auditing Toolset (ping, traceroute) -Home-page: https://dnsdiag.org/ -Author: Babak Farrokhi -Author-email: [email protected] -License: BSD -Keywords: dns traceroute ping performance -Classifier: Topic :: System :: Networking -Classifier: Environment :: Console -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet :: Name Service (DNS) -Classifier: Development Status :: 5 - Production/Stable -Classifier: Operating System :: OS Independent -License-File: LICENSE -Requires-Dist: aioquic>=1.2.0 -Requires-Dist: cryptography>=42.0.5 -Requires-Dist: cymruwhois>=1.6 -Requires-Dist: dnspython>=2.7.0 -Requires-Dist: h2>=4.1.0 -Requires-Dist: httpx>=0.27.0 - - -DNSDiag provides a handful of tools to measure and diagnose your DNS -performance and integrity. Using dnsping, dnstraceroute and dnseval tools -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. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/README.md new/dnsdiag-2.7.0/README.md --- old/dnsdiag-2.6.0/README.md 2024-10-25 16:53:59.000000000 +0200 +++ new/dnsdiag-2.7.0/README.md 2025-09-21 11:55:15.000000000 +0200 @@ -66,9 +66,9 @@ `dnsping` allows you to "ping" a DNS resolver by sending an arbitrary DNS query multiple times. For a full list of supported command-line options, use `--help`. Here are a few key flags: -- Use `--tcp`, `--tls`, or `--doh` to select the transport protocol (default is UDP). +- Use `--tcp`, `--tls`, `--doh`, `doq` or `--http3` to select the transport protocol (default is UDP). - Use `--flags` to display response flags, including EDNS flags, for each response. -- Use `--dnssec` to request DNSSEC validation if available. +- Use `--dnssec` to request DNSSEC validation, if available. - Use `--ede` to display Extended DNS Error messages ([RFC 8914](https://www.rfc-editor.org/rfc/rfc8914)). - Use `--nsid` to display the Name Server Identifier (NSID) if available ([RFC 5001](https://www.rfc-editor.org/rfc/rfc5001)). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/build-pkgs.sh new/dnsdiag-2.7.0/build-pkgs.sh --- old/dnsdiag-2.6.0/build-pkgs.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/build-pkgs.sh 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,81 @@ +#!/bin/sh + +set -e + +## display an error message and exit(1) +die() { + echo "[ERROR] $*" 1>&2 + exit 1 +} + +msg() { + echo "[STATUS] $*" 1>&2 +} + +checkbin() { + which "${1}" > /dev/null 2>&1 || die "${1} is not installed" +} + +## validate required tools +checkbin "python3" + +## constants +if [ "Windows_NT" = "${OS}" ]; then ## windows compatibility shims + PLATFORM='windows' +else + PLATFORM=$(uname -s | tr 'A-Z' 'a-z') +fi +ARCH=$(uname -m) +DDVER=$(grep version util/shared.py | awk -F\' '{print $2}') +PKG_NAME="dnsdiag-${DDVER}.${PLATFORM}-${ARCH}-bin" +PKG_PATH="pkg/${PKG_NAME}" + +msg "Starting to build dnsdiag package version ${DDVER} for ${PLATFORM}-${ARCH}" + +## main + +if [ $# -gt 0 ]; then + if [ "$1" = "--venv" ]; then + msg "Initializing virtualenv" + checkbin "virtualenv" + virtualenv -q --clear .venv + if [ -f .venv/bin/activate ]; then # *nix + . .venv/bin/activate + elif [ -f .venv/Scripts/activate ]; then # windows + . .venv/Scripts/activate + fi + fi +fi + +msg "Installing dependencies" +pip3 install --upgrade pip +pip3 install -q pyinstaller || die "Failed to install pyinstaller" +pip3 install -q -r requirements.txt || die "Failed to install dependencies" + +mkdir -p "${PKG_PATH}" || die "Cannot create dir hierarcy: ${PKG_PATH}" + +for i in dnsping.py dnstraceroute.py dnseval.py; do + msg "Building package for ${i}" + pyinstaller ${i} -y --onefile --clean \ + --log-level=ERROR \ + --distpath="${PKG_PATH}" \ + --hidden-import=dns \ + --hidden-import=httpx +done + +msg "Adding extra files..." +for i in public-servers.txt public-v4.txt rootservers.txt; do + cp ${i} "${PKG_PATH}/" +done + +cd pkg +if [ "${PLATFORM:-}" = "windows" ]; then + msg "Creating archive: ${PKG_NAME}.zip" + powershell Compress-Archive -Force "${PKG_NAME}" "${PKG_NAME}.zip" + else + msg "Creating tarball: ${PKG_NAME}.tar.gz" + tar cf "${PKG_NAME}.tar" "${PKG_NAME}" || die "Failed to build archive (tar)" + gzip -9f "${PKG_NAME}.tar" || die "Failed to build archive (gzip)" +fi + +rm -fr "${PKG_NAME}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag/dns.py new/dnsdiag-2.7.0/dnsdiag/dns.py --- old/dnsdiag-2.6.0/dnsdiag/dns.py 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/dnsdiag/dns.py 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2016-2025, 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 datetime +import random +import signal +import socket +import sys +from statistics import stdev + +import httpx +import dns.flags +import dns.message +import dns.query +import dns.rcode +import dns.rdataclass +import string + +shutdown = False + +# Transport protocols +PROTO_UDP = 0 +PROTO_TCP = 1 +PROTO_TLS = 2 +PROTO_HTTPS = 3 +PROTO_QUIC = 4 +PROTO_HTTP3 = 5 + +_TTL = None + + +class PingResponse: + def __init__(self): + self.r_avg = 0 + self.r_min = 0 + self.r_max = 0 + self.r_stddev = 0 + self.r_lost_percent = 0 + self.flags = 0 + self.ttl = None + self.answer = None + self.rcode = 0 + self.rcode_text = '' + + +def proto_to_text(proto): + _proto_name = { + PROTO_UDP: 'UDP', + PROTO_TCP: 'TCP', + PROTO_TLS: 'TLS', + PROTO_HTTPS: 'HTTPS', + PROTO_QUIC: 'QUIC', + PROTO_HTTP3: 'HTTP3', + } + return _proto_name[proto] + + +def getDefaultPort(proto): + _proto_port = { + PROTO_UDP: 53, + PROTO_TCP: 53, + PROTO_TLS: 853, # RFC 7858, Secion 3.1 + PROTO_HTTPS: 443, + PROTO_QUIC: 853, # RFC 9250, Section 4.1.1 + PROTO_HTTP3: 443, + } + return _proto_port[proto] + + +class CustomSocket(socket.socket): + def __init__(self, *args, **kwargs): + super(CustomSocket, self).__init__(*args, **kwargs) + if _TTL: + self.setsockopt(socket.SOL_IP, socket.IP_TTL, _TTL) + + +def ping(qname, server, dst_port, rdtype, timeout, count, proto, src_ip, use_edns=False, force_miss=False, + want_dnssec=False, socket_ttl=None): + retval = PingResponse() + retval.rcode_text = "No Response" + + response_times = [] + i = 0 + + if socket_ttl: + global _TTL + _TTL = socket_ttl + dns.query.socket_factory = CustomSocket + + for i in range(count): + + if shutdown: # user pressed CTRL+C + raise SystemExit + + if force_miss: + fqdn = "_dnsdiag_%s_.%s" % (random_string(), qname) + else: + fqdn = qname + + if use_edns: + query = dns.message.make_query(fqdn, rdtype, dns.rdataclass.IN, use_edns, want_dnssec, payload=1232) + else: + query = dns.message.make_query(fqdn, rdtype, dns.rdataclass.IN, use_edns=False, want_dnssec=False) + + try: + if proto is PROTO_UDP: + response = dns.query.udp(query, server, timeout=timeout, port=dst_port, source=src_ip, + ignore_unexpected=True) + elif proto is PROTO_TCP: + response = dns.query.tcp(query, server, timeout=timeout, port=dst_port, source=src_ip) + elif proto is PROTO_TLS: + if hasattr(dns.query, 'tls'): + response = dns.query.tls(query, server, timeout, dst_port, src_ip) + else: + unsupported_feature() + elif proto is PROTO_HTTPS: + if hasattr(dns.query, 'https'): + response = dns.query.https(query, server, timeout, dst_port, src_ip) + else: + unsupported_feature() + + except (httpx.ConnectTimeout, httpx.ReadTimeout, + httpx.ConnectError): + raise ConnectionError('Connection failed') + except ValueError: + retval.rcode_text = "Invalid Response" + break + except dns.exception.Timeout: + break + except OSError as e: + if socket_ttl: # this is an acceptable error while doing traceroute + break + print("error: %s" % e.strerror, file=sys.stderr, flush=True) + raise OSError(e) + except Exception as e: + print("error: %s" % e, file=sys.stderr, flush=True) + break + else: + # convert time to milliseconds, considering that + # time property is retruned differently by query.https + if type(response.time) is datetime.timedelta: + elapsed = response.time.total_seconds() * 1000 + else: + elapsed = response.time * 1000 + response_times.append(elapsed) + if response: + retval.flags = response.flags + retval.answer = response.answer + retval.rcode = response.rcode() + retval.rcode_text = dns.rcode.to_text(response.rcode()) + if len(response.answer) > 0: + retval.ttl = response.answer[0].ttl + + r_sent = i + 1 + r_received = len(response_times) + retval.r_lost_count = r_sent - r_received + retval.r_lost_percent = (100 * retval.r_lost_count) / r_sent + if response_times: + retval.r_min = min(response_times) + retval.r_max = max(response_times) + retval.r_avg = sum(response_times) / r_received + if len(response_times) > 1: + retval.r_stddev = stdev(response_times) + else: + retval.r_stddev = 0 + else: + retval.r_min = 0 + retval.r_max = 0 + retval.r_avg = 0 + retval.r_stddev = 0 + + return retval + + +def random_string(min_length=5, max_length=10): + char_set = string.ascii_letters + string.digits + length = random.randint(min_length, max_length) + return ''.join(map(lambda unused: random.choice(char_set), range(length))) + + +def signal_handler(sig, frame): + global shutdown + if shutdown: # pressed twice, so exit immediately + sys.exit(0) + shutdown = True # pressed once, exit gracefully + + +def unsupported_feature(feature=""): + print("Error: You have an unsupported version of Python interpreter dnspython library.") + print(" Some features such as DoT and DoH are not available. You should upgrade") + print(" the Python interpreter to at least 3.10 and reinstall dependencies.") + if feature: + print("Missing Feature: %s" % feature) + sys.exit(127) + + +def valid_rdatatype(rtype): + # validate RR type + try: + _ = dns.rdatatype.from_text(rtype) + except dns.rdatatype.UnknownRdatatype: + return False + return 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 = sorted(_by_value.items(), reverse=True) + + _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 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag/shared.py new/dnsdiag-2.7.0/dnsdiag/shared.py --- old/dnsdiag-2.6.0/dnsdiag/shared.py 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/dnsdiag/shared.py 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2016-2025, 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.7.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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag/whois.py new/dnsdiag-2.7.0/dnsdiag/whois.py --- old/dnsdiag-2.6.0/dnsdiag/whois.py 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/dnsdiag/whois.py 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2016-2025, 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 pickle +import time + +import cymruwhois + +WHOIS_CACHE_FILE = 'whois.cache' + + +def asn_lookup(ip, whois_cache) -> (str, dict): + """ + Look up an ASN given teh IP address from cache. If not in cache, lookup from a whois server and update the cache + :param ip: IP Address (str) + :param whois_cache: whois data cache (dict) + :return: AS Number (str), Updated whois cache (dict) + """ + asn = None + try: + currenttime = time.time() + if ip in whois_cache: + asn, ts = whois_cache[ip] + else: + ts = 0 + if (currenttime - ts) > 36000: + c = cymruwhois.Client() + asn = c.lookup(ip) + whois_cache[ip] = (asn, currenttime) + except Exception: + pass + return asn, whois_cache + + +def restore() -> dict: + """ + Loads whois cache data from a file + :return: whois data dict + """ + try: + pkl_file = open(WHOIS_CACHE_FILE, 'rb') + try: + whois = pickle.load(pkl_file) + pkl_file.close() + except Exception: + whois = {} + except IOError: + whois = {} + return whois + + +def save(whois_data: dict): + """ + Saves whois cache data to a file + :param whois_data: whois data (dict) + :return: None + """ + pkl_file = open(WHOIS_CACHE_FILE, 'wb') + pickle.dump(whois_data, pkl_file) + pkl_file.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag.egg-info/PKG-INFO new/dnsdiag-2.7.0/dnsdiag.egg-info/PKG-INFO --- old/dnsdiag-2.6.0/dnsdiag.egg-info/PKG-INFO 2024-10-25 23:57:52.000000000 +0200 +++ new/dnsdiag-2.7.0/dnsdiag.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 @@ -1,35 +0,0 @@ -Metadata-Version: 2.1 -Name: dnsdiag -Version: 2.6.0 -Summary: DNS Measurement, Troubleshooting and Security Auditing Toolset (ping, traceroute) -Home-page: https://dnsdiag.org/ -Author: Babak Farrokhi -Author-email: [email protected] -License: BSD -Keywords: dns traceroute ping performance -Classifier: Topic :: System :: Networking -Classifier: Environment :: Console -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: 3.12 -Classifier: Programming Language :: Python :: 3.13 -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet :: Name Service (DNS) -Classifier: Development Status :: 5 - Production/Stable -Classifier: Operating System :: OS Independent -License-File: LICENSE -Requires-Dist: aioquic>=1.2.0 -Requires-Dist: cryptography>=42.0.5 -Requires-Dist: cymruwhois>=1.6 -Requires-Dist: dnspython>=2.7.0 -Requires-Dist: h2>=4.1.0 -Requires-Dist: httpx>=0.27.0 - - -DNSDiag provides a handful of tools to measure and diagnose your DNS -performance and integrity. Using dnsping, dnstraceroute and dnseval tools -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. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag.egg-info/SOURCES.txt new/dnsdiag-2.7.0/dnsdiag.egg-info/SOURCES.txt --- old/dnsdiag-2.6.0/dnsdiag.egg-info/SOURCES.txt 2024-10-25 23:57:52.000000000 +0200 +++ new/dnsdiag-2.7.0/dnsdiag.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,20 +0,0 @@ -LICENSE -MANIFEST.in -README.md -dnseval.py -dnsping.py -dnstraceroute.py -public-servers.txt -public-v4.txt -rootservers.txt -setup.py -dnsdiag.egg-info/PKG-INFO -dnsdiag.egg-info/SOURCES.txt -dnsdiag.egg-info/dependency_links.txt -dnsdiag.egg-info/entry_points.txt -dnsdiag.egg-info/requires.txt -dnsdiag.egg-info/top_level.txt -util/__init__.py -util/dns.py -util/shared.py -util/whois.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag.egg-info/dependency_links.txt new/dnsdiag-2.7.0/dnsdiag.egg-info/dependency_links.txt --- old/dnsdiag-2.6.0/dnsdiag.egg-info/dependency_links.txt 2024-10-25 23:57:52.000000000 +0200 +++ new/dnsdiag-2.7.0/dnsdiag.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag.egg-info/entry_points.txt new/dnsdiag-2.7.0/dnsdiag.egg-info/entry_points.txt --- old/dnsdiag-2.6.0/dnsdiag.egg-info/entry_points.txt 2024-10-25 23:57:52.000000000 +0200 +++ new/dnsdiag-2.7.0/dnsdiag.egg-info/entry_points.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -[console_scripts] -dnseval = dnseval:main -dnsping = dnsping:main -dnstraceroute = dnstraceroute:main diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag.egg-info/requires.txt new/dnsdiag-2.7.0/dnsdiag.egg-info/requires.txt --- old/dnsdiag-2.6.0/dnsdiag.egg-info/requires.txt 2024-10-25 23:57:52.000000000 +0200 +++ new/dnsdiag-2.7.0/dnsdiag.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,6 +0,0 @@ -aioquic>=1.2.0 -cryptography>=42.0.5 -cymruwhois>=1.6 -dnspython>=2.7.0 -h2>=4.1.0 -httpx>=0.27.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsdiag.egg-info/top_level.txt new/dnsdiag-2.7.0/dnsdiag.egg-info/top_level.txt --- old/dnsdiag-2.6.0/dnsdiag.egg-info/top_level.txt 2024-10-25 23:57:52.000000000 +0200 +++ new/dnsdiag-2.7.0/dnsdiag.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ -util diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnseval.py new/dnsdiag-2.7.0/dnseval.py --- old/dnsdiag-2.6.0/dnseval.py 2024-10-25 16:53:59.000000000 +0200 +++ new/dnsdiag-2.7.0/dnseval.py 2025-09-21 11:55:15.000000000 +0200 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2024, Babak Farrokhi +# Copyright (c) 2016-2025, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -37,14 +37,14 @@ import dns.rdatatype import dns.resolver -import util.dns +import dnsdiag.dns __author__ = 'Babak Farrokhi ([email protected])' __license__ = 'BSD' __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 -from util.shared import __version__, Colors +from dnsdiag.dns import PROTO_UDP, PROTO_TCP, PROTO_TLS, PROTO_HTTPS, setup_signal_handler, flags_to_text +from dnsdiag.shared import __version__, Colors def usage(): @@ -156,7 +156,7 @@ usage() # validate RR type - if not util.dns.valid_rdatatype(rdatatype): + if not dnsdiag.dns.valid_rdatatype(rdatatype): print('Error: Invalid record type "%s" ' % rdatatype) sys.exit(1) @@ -214,7 +214,7 @@ continue try: - retval = util.dns.ping(qname, resolver, dst_port, rdatatype, waittime, count, proto, src_ip, + retval = dnsdiag.dns.ping(qname, resolver, dst_port, rdatatype, waittime, count, proto, src_ip, use_edns=use_edns, force_miss=force_miss, want_dnssec=want_dnssec) except SystemExit: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnsping.py new/dnsdiag-2.7.0/dnsping.py --- old/dnsdiag-2.6.0/dnsping.py 2024-10-25 23:39:58.000000000 +0200 +++ new/dnsdiag-2.7.0/dnsping.py 2025-09-21 11:55:15.000000000 +0200 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2024, Babak Farrokhi +# Copyright (c) 2016-2025, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -33,15 +33,15 @@ import socket import sys import time -import httpx from statistics import stdev import dns.flags import dns.resolver +import httpx -from util.dns import PROTO_UDP, PROTO_TCP, PROTO_TLS, PROTO_HTTPS, PROTO_QUIC, proto_to_text, unsupported_feature, \ - random_string, getDefaultPort, valid_rdatatype -from util.shared import __version__ +from dnsdiag.dns import PROTO_UDP, PROTO_TCP, PROTO_TLS, PROTO_HTTPS, PROTO_QUIC, PROTO_HTTP3, proto_to_text, \ + unsupported_feature, random_string, getDefaultPort, valid_rdatatype +from dnsdiag.shared import __version__ __author__ = 'Babak Farrokhi ([email protected])' __license__ = 'BSD' @@ -62,6 +62,7 @@ -T, --tcp Use TCP as the transport protocol -X, --tls Use TLS as the transport protocol -H, --doh Use HTTPS as the transport protocol (DoH) + -3, --http3 Use HTTP/3 as the transport protocol (DoH3) -Q, --doq Use QUIC as the transport protocol (DoQ) -4, --ipv4 Use IPv4 as the network protocol -6, --ipv6 Use IPv6 as the network protocol @@ -154,11 +155,11 @@ qname = 'wikipedia.org' try: - opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:i:vp:P:S:TQ46meDFXHrnEC:Lxa", + opts, args = getopt.getopt(sys.argv[1:], "qhc:s:t:w:i:vp:P:S:TQ346meDFXHrnEC:Lxa", ["help", "count=", "server=", "quiet", "type=", "wait=", "interval=", "verbose", "port=", "srcip=", "tcp", "ipv4", "ipv6", "cache-miss", "srcport=", "edns", "dnssec", "flags", "norecurse", "tls", "doh", "nsid", "ede", "class=", "ttl", - "expert", "answer", "quic"]) + "expert", "answer", "quic", "http3"]) except getopt.GetoptError as err: # print help information and exit: print_stderr(err, False) # will print something like "option -a not recognized" @@ -238,6 +239,11 @@ if use_default_dst_port: dst_port = getDefaultPort(proto) + elif o in ("-3", "--http3"): + proto = PROTO_HTTP3 + if use_default_dst_port: + dst_port = getDefaultPort(proto) + elif o in ("-4", "--ipv4"): af = socket.AF_INET @@ -346,6 +352,18 @@ else: unsupported_feature("DNS-over-HTTPS (DoH)") + elif proto is PROTO_HTTP3: + if hasattr(dns.query, 'quic'): + try: + answers = dns.query.https(query, dnsserver, timeout=timeout, port=dst_port, + source=src_ip, source_port=src_port, + http_version=dns.query.HTTPVersion.H3) + except ConnectionRefusedError: + print_stderr(f"The server did not respond to DNS-over-HTTPS/3 on port {dst_port}", + should_die=True) + else: + unsupported_feature("DNS-over-HTTPS/3 (DoH3)") + elif proto is PROTO_QUIC: if hasattr(dns.query, 'quic'): try: @@ -353,6 +371,9 @@ source=src_ip, source_port=src_port) except dns.exception.Timeout: print_stderr(f"The server did not respond to DoQ on port {dst_port}", should_die=True) + except ConnectionRefusedError: + print_stderr(f"The server did not respond to DNS-over-HTTPS/3 on port {dst_port}", + should_die=True) else: unsupported_feature("DNS-over-QUIC (DoQ)") @@ -401,6 +422,8 @@ if show_flags: ans_flags = dns.flags.to_text(answers.flags) edns_flags = dns.flags.edns_to_text(answers.ednsflags) + if want_dnssec and not (answers.flags & dns.flags.AD): + ans_flags += " --" # add padding to printer output when dnssec is requested, but AD flag is not set extras += " [%s]" % " ".join([ans_flags, edns_flags]).rstrip(' ') # show both regular + edns flags if want_nsid: @@ -412,12 +435,11 @@ if show_ede: for ans_opt in answers.options: # EDE response is optional, but print if there is one if ans_opt.otype == dns.edns.EDE: - extras += " [EDE %d: %s]" % (ans_opt.code, ans_opt.text) + extras += " [EDE %d: \"%s\"]" % (ans_opt.code, ans_opt.text or "") if show_answer: # The answer should be displayed at the rightmost for ans in answers.answer: if ans.rdtype == dns.rdatatype.from_text(rdatatype): # is this the answer to our question? - # extras += " [%s]" % ans[0] extras += " [RDATA: %s]" % ans[0] break diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/dnstraceroute.py new/dnsdiag-2.7.0/dnstraceroute.py --- old/dnsdiag-2.6.0/dnstraceroute.py 2024-10-25 16:53:59.000000000 +0200 +++ new/dnsdiag-2.7.0/dnstraceroute.py 2025-09-21 11:55:15.000000000 +0200 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2016-2024, Babak Farrokhi +# Copyright (c) 2016-2025, Babak Farrokhi # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -37,9 +37,9 @@ import dns.rdatatype import dns.resolver -import util.whois -from util.dns import PROTO_UDP, PROTO_TCP, setup_signal_handler -from util.shared import __version__, Colors +import dnsdiag.whois +from dnsdiag.dns import PROTO_UDP, PROTO_TCP, setup_signal_handler +from dnsdiag.shared import __version__, Colors # Global Variables quiet = False @@ -116,7 +116,7 @@ resp_time = None try: - resp = util.dns.ping(qname, server, port, rdtype, timeout, 1, proto, src_ip, use_edns, force_miss=False, + resp = dnsdiag.dns.ping(qname, server, port, rdtype, timeout, 1, proto, src_ip, use_edns, force_miss=False, want_dnssec=False, socket_ttl=ttl) except SystemExit: @@ -205,7 +205,7 @@ color = Colors(color_mode) # validate RR type - if not util.dns.valid_rdatatype(rdatatype): + if not dnsdiag.dns.valid_rdatatype(rdatatype): print('Error: Invalid record type "%s" ' % rdatatype) sys.exit(1) @@ -299,7 +299,7 @@ if curr_addr: as_name = "" if as_lookup: - asn, whois_cache = util.whois.asn_lookup(curr_addr, whois_cache) + asn, whois_cache = dnsdiag.whois.asn_lookup(curr_addr, whois_cache) as_name = '' try: if asn and asn.asn != "NA": @@ -338,7 +338,7 @@ if __name__ == '__main__': try: - whois_cache = util.whois.restore() + whois_cache = dnsdiag.whois.restore() main() finally: - util.whois.save(whois_cache) + dnsdiag.whois.save(whois_cache) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/requirements.txt new/dnsdiag-2.7.0/requirements.txt --- old/dnsdiag-2.6.0/requirements.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/requirements.txt 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,6 @@ +aioquic>=1.2.0 +cryptography>=42.0.5 +cymruwhois>=1.6 +dnspython>=2.8.0 +h2>=4.1.0 +httpx>=0.27.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/setup.cfg new/dnsdiag-2.7.0/setup.cfg --- old/dnsdiag-2.6.0/setup.cfg 2024-10-25 23:57:52.778800200 +0200 +++ new/dnsdiag-2.7.0/setup.cfg 1970-01-01 01:00:00.000000000 +0100 @@ -1,4 +0,0 @@ -[egg_info] -tag_build = -tag_date = 0 - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/setup.py new/dnsdiag-2.7.0/setup.py --- old/dnsdiag-2.6.0/setup.py 2024-10-25 23:44:57.000000000 +0200 +++ new/dnsdiag-2.7.0/setup.py 2025-09-21 11:55:15.000000000 +0200 @@ -1,24 +1,22 @@ from setuptools import setup, find_packages -from util.shared import __version__ +from dnsdiag.shared import __version__ setup( name="dnsdiag", version=__version__, packages=find_packages(), scripts=["dnseval.py", "dnsping.py", "dnstraceroute.py"], - install_requires=['aioquic>=1.2.0', 'cryptography>=42.0.5', 'cymruwhois>=1.6', 'dnspython>=2.7.0', 'h2>=4.1.0', 'httpx>=0.27.0'], + install_requires=['aioquic>=1.2.0', 'cryptography>=42.0.5', 'cymruwhois>=1.6', 'dnspython>=2.8.0', 'h2>=4.1.0', 'httpx>=0.27.0'], classifiers=[ "Topic :: System :: Networking", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: Name Service (DNS)", "Development Status :: 5 - Production/Stable", "Operating System :: OS Independent", @@ -27,6 +25,7 @@ author="Babak Farrokhi", author_email="[email protected]", description="DNS Measurement, Troubleshooting and Security Auditing Toolset (ping, traceroute)", + long_description_content_type="text/plain", long_description=""" DNSDiag provides a handful of tools to measure and diagnose your DNS performance and integrity. Using dnsping, dnstraceroute and dnseval tools diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/tox.ini new/dnsdiag-2.7.0/tox.ini --- old/dnsdiag-2.6.0/tox.ini 1970-01-01 01:00:00.000000000 +0100 +++ new/dnsdiag-2.7.0/tox.ini 2025-09-21 11:55:15.000000000 +0200 @@ -0,0 +1,6 @@ +[pycodestyle] +ignore = E501, E741 + +[flake8] +ignore = E501, E741 +exclude = .venv diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/util/dns.py new/dnsdiag-2.7.0/util/dns.py --- old/dnsdiag-2.6.0/util/dns.py 2024-10-25 23:39:58.000000000 +0200 +++ new/dnsdiag-2.7.0/util/dns.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,271 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2016-2024, 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 datetime -import random -import signal -import socket -import sys -from statistics import stdev - -import httpx -import dns.flags -import dns.message -import dns.query -import dns.rcode -import dns.rdataclass -import string - -shutdown = False - -# Transport protocols -PROTO_UDP = 0 -PROTO_TCP = 1 -PROTO_TLS = 2 -PROTO_HTTPS = 3 -PROTO_QUIC = 4 - -_TTL = None - - -class PingResponse: - def __init__(self): - self.r_avg = 0 - self.r_min = 0 - self.r_max = 0 - self.r_stddev = 0 - self.r_lost_percent = 0 - self.flags = 0 - self.ttl = None - self.answer = None - self.rcode = 0 - self.rcode_text = '' - - -def proto_to_text(proto): - _proto_name = { - PROTO_UDP: 'UDP', - PROTO_TCP: 'TCP', - PROTO_TLS: 'TLS', - PROTO_HTTPS: 'HTTPS', - PROTO_QUIC: 'QUIC', - } - return _proto_name[proto] - - -def getDefaultPort(proto): - _proto_port = { - PROTO_UDP: 53, - PROTO_TCP: 53, - PROTO_TLS: 853, # RFC 7858, Secion 3.1 - PROTO_HTTPS: 443, - PROTO_QUIC: 853, # RFC 9250, Section 4.1.1 - } - return _proto_port[proto] - - -class CustomSocket(socket.socket): - def __init__(self, *args, **kwargs): - super(CustomSocket, self).__init__(*args, **kwargs) - if _TTL: - self.setsockopt(socket.SOL_IP, socket.IP_TTL, _TTL) - - -def ping(qname, server, dst_port, rdtype, timeout, count, proto, src_ip, use_edns=False, force_miss=False, - want_dnssec=False, socket_ttl=None): - retval = PingResponse() - retval.rcode_text = "No Response" - - response_times = [] - i = 0 - - if socket_ttl: - global _TTL - _TTL = socket_ttl - dns.query.socket_factory = CustomSocket - - for i in range(count): - - if shutdown: # user pressed CTRL+C - raise SystemExit - - if force_miss: - fqdn = "_dnsdiag_%s_.%s" % (random_string(), qname) - else: - fqdn = qname - - if use_edns: - query = dns.message.make_query(fqdn, rdtype, dns.rdataclass.IN, use_edns, want_dnssec, payload=1232) - else: - query = dns.message.make_query(fqdn, rdtype, dns.rdataclass.IN, use_edns=False, want_dnssec=False) - - try: - if proto is PROTO_UDP: - response = dns.query.udp(query, server, timeout=timeout, port=dst_port, source=src_ip, - ignore_unexpected=True) - elif proto is PROTO_TCP: - response = dns.query.tcp(query, server, timeout=timeout, port=dst_port, source=src_ip) - elif proto is PROTO_TLS: - if hasattr(dns.query, 'tls'): - response = dns.query.tls(query, server, timeout, dst_port, src_ip) - else: - unsupported_feature() - elif proto is PROTO_HTTPS: - if hasattr(dns.query, 'https'): - response = dns.query.https(query, server, timeout, dst_port, src_ip) - else: - unsupported_feature() - - except (httpx.ConnectTimeout, httpx.ReadTimeout, - httpx.ConnectError): - raise ConnectionError('Connection failed') - except ValueError: - retval.rcode_text = "Invalid Response" - break - except dns.exception.Timeout: - break - except OSError as e: - if socket_ttl: # this is an acceptable error while doing traceroute - break - print("error: %s" % e.strerror, file=sys.stderr, flush=True) - raise OSError(e) - except Exception as e: - print("error: %s" % e, file=sys.stderr, flush=True) - break - else: - # convert time to milliseconds, considering that - # time property is retruned differently by query.https - if type(response.time) is datetime.timedelta: - elapsed = response.time.total_seconds() * 1000 - else: - elapsed = response.time * 1000 - response_times.append(elapsed) - if response: - retval.flags = response.flags - retval.answer = response.answer - retval.rcode = response.rcode() - retval.rcode_text = dns.rcode.to_text(response.rcode()) - if len(response.answer) > 0: - retval.ttl = response.answer[0].ttl - - r_sent = i + 1 - r_received = len(response_times) - retval.r_lost_count = r_sent - r_received - retval.r_lost_percent = (100 * retval.r_lost_count) / r_sent - if response_times: - retval.r_min = min(response_times) - retval.r_max = max(response_times) - retval.r_avg = sum(response_times) / r_received - if len(response_times) > 1: - retval.r_stddev = stdev(response_times) - else: - retval.r_stddev = 0 - else: - retval.r_min = 0 - retval.r_max = 0 - retval.r_avg = 0 - retval.r_stddev = 0 - - return retval - - -def random_string(min_length=5, max_length=10): - char_set = string.ascii_letters + string.digits - length = random.randint(min_length, max_length) - return ''.join(map(lambda unused: random.choice(char_set), range(length))) - - -def signal_handler(sig, frame): - global shutdown - if shutdown: # pressed twice, so exit immediately - sys.exit(0) - shutdown = True # pressed once, exit gracefully - - -def unsupported_feature(feature=""): - print("Error: You have an unsupported version of Python interpreter dnspython library.") - print(" Some features such as DoT and DoH are not available. You should upgrade") - print(" the Python interpreter to at least 3.7 and reinstall dependencies.") - if feature: - print("Missing Feature: %s" % feature) - sys.exit(127) - - -def valid_rdatatype(rtype): - # validate RR type - try: - _ = dns.rdatatype.from_text(rtype) - except dns.rdatatype.UnknownRdatatype: - return False - return 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 = sorted(_by_value.items(), reverse=True) - - _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 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/util/shared.py new/dnsdiag-2.7.0/util/shared.py --- old/dnsdiag-2.6.0/util/shared.py 2024-10-25 23:40:33.000000000 +0200 +++ new/dnsdiag-2.7.0/util/shared.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2016-2024, 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.6.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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/dnsdiag-2.6.0/util/whois.py new/dnsdiag-2.7.0/util/whois.py --- old/dnsdiag-2.6.0/util/whois.py 2024-10-25 16:53:59.000000000 +0200 +++ new/dnsdiag-2.7.0/util/whois.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2016-2024, 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 pickle -import time - -import cymruwhois - -WHOIS_CACHE_FILE = 'whois.cache' - - -def asn_lookup(ip, whois_cache) -> (str, dict): - """ - Look up an ASN given teh IP address from cache. If not in cache, lookup from a whois server and update the cache - :param ip: IP Address (str) - :param whois_cache: whois data cache (dict) - :return: AS Number (str), Updated whois cache (dict) - """ - asn = None - try: - currenttime = time.time() - if ip in whois_cache: - asn, ts = whois_cache[ip] - else: - ts = 0 - if (currenttime - ts) > 36000: - c = cymruwhois.Client() - asn = c.lookup(ip) - whois_cache[ip] = (asn, currenttime) - except Exception: - pass - return asn, whois_cache - - -def restore() -> dict: - """ - Loads whois cache data from a file - :return: whois data dict - """ - try: - pkl_file = open(WHOIS_CACHE_FILE, 'rb') - try: - whois = pickle.load(pkl_file) - pkl_file.close() - except Exception: - whois = {} - except IOError: - whois = {} - return whois - - -def save(whois_data: dict): - """ - Saves whois cache data to a file - :param whois_data: whois data (dict) - :return: None - """ - pkl_file = open(WHOIS_CACHE_FILE, 'wb') - pickle.dump(whois_data, pkl_file) - pkl_file.close()
