#!/usr/bin/env python3
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details."""

import argparse
import socket
import sys
import time

import lib.args
import lib.base
import lib.net
from lib.globals import STATE_UNKNOWN, STATE_WARN

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2026040801'

DESCRIPTION = """Performs a DNS lookup and resolves a hostname to one or more IP addresses. Queries the name servers configured on the local machine (e.g. those listed in /etc/resolv.conf). Measures and alerts on the lookup response time. Works with both IPv4 and IPv6."""

DEFAULT_HOSTNAME = 'localhost'
DEFAULT_PORT = 53


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    parser.add_argument(
        '-V',
        '--version',
        action='version',
        version=f'%(prog)s: v{__version__} by {__author__}',
    )

    parser.add_argument(
        '--always-ok',
        help=lib.args.help('--always-ok'),
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '-c',
        '--critical',
        help='CRIT threshold for DNS lookup time in milliseconds. Default: %(default)s',
        dest='CRIT',
        type=float,
        default=None,
    )

    parser.add_argument(
        '-H',
        '--hostname',
        help='Hostname or IP address to resolve. Default: %(default)s',
        dest='HOSTNAME',
        default=DEFAULT_HOSTNAME,
    )

    parser.add_argument(
        '-p',
        '--port',
        help='Port number to query. Default: %(default)s',
        dest='PORT',
        type=int,
        default=DEFAULT_PORT,
    )

    parser.add_argument(
        '--type',
        help='Connection type to narrow the list of returned addresses.',
        dest='TYPE',
        choices=['udp', 'udp6', 'tcp', 'tcp6'],
    )

    parser.add_argument(
        '-w',
        '--warning',
        help='WARN threshold for DNS lookup time in milliseconds. Default: %(default)s',
        dest='WARN',
        type=float,
        default=None,
    )

    args, _ = parser.parse_known_args()
    return args


def main():
    """The main function. This is where the magic happens."""

    # parse the command line
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    # https://docs.python.org/2/library/socket.html#socket.getaddrinfo
    # socket.getaddrinfo(host, port[, family[, socktype[, proto[, flags]]]])

    # The function returns a list of 5-tuples with the following structure:
    # (family, socktype, proto, canonname, sockaddr)
    # family, socktype, proto are all integers
    # sockaddr is a tuple describing a socket address
    #   (address, port) 2-tuple for AF_INET
    #   (address, port, flow info, scope id) for AF_INET6
    # Example results:
    # [(10, 1, 6, '', ('2606:2800:220:1:248:1893:25c8:1946',
    #   53, 0, 0)), ...]
    # [(2, 1, 6, '', ('172.217.168.67', 53)), ...]

    start_time = time.time()
    try:
        if args.TYPE == 'udp':
            result = socket.getaddrinfo(
                args.HOSTNAME,
                args.PORT,
                lib.net.AF_INET,
                lib.net.SOCK_UDP,
                lib.net.PROTO_UDP,
            )
        elif args.TYPE == 'udp6':
            result = socket.getaddrinfo(
                args.HOSTNAME,
                args.PORT,
                lib.net.AF_INET6,
                lib.net.SOCK_UDP,
                lib.net.PROTO_UDP,
            )
        elif args.TYPE == 'tcp':
            result = socket.getaddrinfo(
                args.HOSTNAME,
                args.PORT,
                lib.net.AF_INET,
                lib.net.SOCK_TCP,
                lib.net.PROTO_TCP,
            )
        elif args.TYPE == 'tcp6':
            result = socket.getaddrinfo(
                args.HOSTNAME,
                args.PORT,
                lib.net.AF_INET6,
                lib.net.SOCK_TCP,
                lib.net.PROTO_TCP,
            )
        else:
            result = socket.getaddrinfo(args.HOSTNAME, args.PORT)
    except OSError as e:
        lib.base.oao(
            f'Socket error "{e}" for {args.HOSTNAME}:{args.PORT}/{args.TYPE}.',
            STATE_WARN,
        )
    except socket.herror as e:
        lib.base.oao(
            f'Address related Socket error "{e}" for {args.HOSTNAME}:{args.PORT}/{args.TYPE}.',
            STATE_WARN,
        )
    except socket.gaierror as e:
        lib.base.oao(
            f'Address related Socket error "{e}" for {args.HOSTNAME}:{args.PORT}/{args.TYPE}.',
            STATE_WARN,
        )
    runtime = (time.time() - start_time) * 1000  # we get seconds and want ms

    # build the message
    msg = f'Lookup for {args.HOSTNAME} returns '
    for res in result:
        af, _, proto, _, sa = res
        msg += f'{sa[0]} ({lib.net.PROTOSTR[proto]}{lib.net.FAMILIYSTR[af]}:{sa[1]}), '

    state = lib.base.get_state(runtime, args.WARN, args.CRIT, _operator='ge')
    perfdata = lib.base.get_perfdata(
        'time',
        runtime,
        uom='ms',
        warn=args.WARN,
        crit=args.CRIT,
        _min=0,
    )

    # over and out
    lib.base.oao(msg[:-2], state, perfdata, always_ok=args.ALWAYS_OK)


if __name__ == '__main__':
    try:
        main()
    except Exception:
        lib.base.cu()
