#!/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  # pylint: disable=C0413
import socket  # pylint: disable=C0413
import sys  # pylint: disable=C0413
import time  # pylint: disable=C0413

import lib.base  # pylint: disable=C0413
import lib.net  # pylint: disable=C0413
from lib.globals import (STATE_OK, STATE_UNKNOWN,  # pylint: disable=C0413
                          STATE_WARN)

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

DESCRIPTION = """Performs a DNS lookup and converts a hostname to one or more IP addresses. Only the
                name servers configured on the machine running this check plugin (for example those
                visible in `/etc/resolv.conf`) will be queried - you can't query other DNS servers.
                This command 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='{0}: v{1} by {2}'.format('%(prog)s', __version__, __author__)
    )

    parser.add_argument(
        '--always-ok',
        help='Always returns OK.',
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '-c', '--critical',
        help='Return critical if elapsed time in ms exceeds value. Default: %(default)s',
        dest='CRIT',
        type=float,
        default=None,
    )

    parser.add_argument(
        '-H', '--hostname',
        help='The host or ip address to check. Default: %(default)s',
        dest='HOSTNAME',
        default=DEFAULT_HOSTNAME,
    )

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

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

    parser.add_argument(
        '-w', '--warning',
        help='Return warning if elapsed time in ms exceeds value. Default: %(default)s',
        dest='WARN',
        type=float,
        default=None,
    )

    return parser.parse_args()


def main():
    """The main function. Hier spielt die Musik.
    """

    # parse the command line, exit with UNKNOWN if it fails
    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
    # [(10, 1, 6, '', ('2606:2800:220:1:248:1893:25c8:1946', 53, 0, 0)), (10, 2, 17, '', ('2606:2800:220:1:248:1893:25c8:1946', 53, 0, 0)), (10, 3, 0, '', ('2606:2800:220:1:248:1893:25c8:1946', 53, 0, 0)), (2, 1, 6, '', ('93.184.216.34', 53)), (2, 2, 17, '', ('93.184.216.34', 53)), (2, 3, 0, '', ('93.184.216.34', 53))]
    # [(2, 1, 6, '', ('172.217.168.67', 53)), (2, 2, 17, '', ('172.217.168.67', 53)), (2, 3, 0, '', ('172.217.168.67', 53)), (10, 1, 6, '', ('2a00:1450:400a:803::2003', 53, 0, 0)), (10, 2, 17, '', ('2a00:1450:400a:803::2003', 53, 0, 0)), (10, 3, 0, '', ('2a00:1450:400a:803::2003', 53, 0, 0))]

    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 socket.error as e:
        lib.base.oao('Socket error "{}" for {}:{}/{}.'.format(e, args.HOSTNAME, args.PORT, args.TYPE), STATE_WARN)
    except socket.herror as e:
        lib.base.oao('Address related Socket error "{}" for {}:{}/{}.'.format(e, args.HOSTNAME, args.PORT, args.TYPE), STATE_WARN)
    except socket.gaierror as e:
        lib.base.oao('Address related Socket error "{}" for {}:{}/{}.'.format(e, args.HOSTNAME, args.PORT, args.TYPE), STATE_WARN)
    runtime = (time.time() - start_time) * 1000     # we get seconds and want ms

    msg = 'Lookup for {} returns '.format(args.HOSTNAME)
    for res in result:
        af, socktype, proto, canonname, sa = res
        msg += '{} ({}{}:{}), '.format(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, 'ms', args.WARN, args.CRIT, 0, None)

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


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