#!/usr/bin/env bash
# x-multi-ping: Multi-protocol ping wrapper in Bash
#
# Description:
# This script pings a list of hostnames using both IPv4 and IPv6 protocols.
# It uses the 'dig' command to resolve the hostnames and then pings each IP
# address found. The script can run once or loop indefinitely with a sleep
# interval between iterations.
#
# This script is based on the original work by Steve Kemp.
# Original work Copyright (c) 2014 by Steve Kemp.
#
# The code in the original repository may be modified and distributed under your choice of:
#   * The Perl Artistic License (http://dev.perl.org/licenses/artistic.html) or
#   * The GNU General Public License, version 2 or later (http://www.gnu.org/licenses/gpl2.txt).
#
# Modifications and enhancements by Ismo Vuorinen on 2025.
#
# Usage:
# x-multi-ping [--loop|--forever] [--sleep=N] hostname1 hostname2 ...
#
# Options:
#   --help             Display this help message.
#   --verbose          Enable verbose output.
#   --loop, --forever  Loop indefinitely.
#   --sleep=N          Sleep N seconds between iterations (default: 1).
#
# Examples:
#   x-multi-ping example.com
#   x-multi-ping --loop --sleep=5 example.com
#   x-multi-ping --forever example.com example.org
#
# Dependencies:
#   - dig (DNS lookup utility)
#   - ping (ICMP ping utility)
#   - ping6 (IPv6 ping utility) or ping -6 (alternative)
#

# Defaults
LOOP=0
SLEEP=1
VERBOSE=0
TIMEOUT=5

usage()
{
  echo "Usage: $0 [--loop|--forever] [--sleep=N] hostname1 hostname2 ..."
  echo "Options:"
  echo "  --help           Display this help message."
  echo "  --verbose        Enable verbose output."
  echo "  --loop, --forever  Loop indefinitely."
  echo "  --sleep=N        Sleep N seconds between iterations (default: 1)."
}

# Parse command-line options
while [[ $# -gt 0 ]]; do
  case "$1" in
    --help)
      usage
      exit 0
      ;;
    --verbose)
      # shellcheck disable=SC2034
      VERBOSE=1
      shift
      ;;
    --loop | --forever)
      LOOP=1
      shift
      ;;
    --sleep=*)
      SLEEP="${1#*=}"
      shift
      ;;
    --sleep)
      if [[ -n "$2" ]]; then
        SLEEP="$2"
        shift 2
      else
        echo "Error: --sleep requires a numeric value."
        exit 1
      fi
      ;;
    --*)
      echo "Unknown option: $1"
      usage
      exit 1
      ;;
    *)
      break
      ;;
  esac
done

# Check for required hostnames
if [[ $# -lt 1 ]]; then
  usage
  exit 1
fi

# Dependency check for dig and ping
if ! command -v dig > /dev/null 2>&1; then
  echo "The required 'dig' command is missing. Aborting."
  exit 1
fi

if ! command -v ping > /dev/null 2>&1; then
  echo "The required 'ping' command is missing. Aborting."
  exit 1
fi

# Determine how to invoke IPv6 ping
if command -v ping6 > /dev/null 2>&1; then
  PING6="ping6"
elif ping -6 -c1 ::1 > /dev/null 2>&1; then
  PING6="ping -6"
else
  echo "The required IPv6 ping command (ping6 or ping -6) is missing. Aborting."
  exit 1
fi

# Function to remove any URI scheme and port from the hostname.
strip_hostname()
{
  local host="$1"
  # Remove leading scheme (e.g., http://) if present.
  if [[ "$host" =~ ^[a-z]+://([^/]+)/? ]]; then
    host="${BASH_REMATCH[1]}"
  fi
  # Remove a port if specified (e.g., example.com:80).
  if [[ "$host" =~ ^([^:]+):[0-9]+$ ]]; then
    host="${BASH_REMATCH[1]}"
  fi
  echo "$host"
}

# Function to ping a given host based on DNS lookups.
pingHost()
{
  local original_host="$1"
  local host
  host=$(strip_hostname "$original_host")

  for type in A AAAA; do
    # Look up the DNS records for the host.
    ips=$(dig +short "$host" "$type")
    if [[ -z "$ips" ]]; then
      echo "WARNING: Failed to resolve $host [$type]"
    else
      # For each IP address found, perform the appropriate ping.
      while IFS= read -r ip; do
        if [[ "$type" == "A" ]]; then
          ping_binary="ping"
        else
          ping_binary="$PING6"
        fi

        # Execute ping with one packet and a timeout.
        $ping_binary -c1 -w"$TIMEOUT" -W"$TIMEOUT" "$host" > /dev/null 2>&1
        # shellcheck disable=SC2181
        if [[ $? -eq 0 ]]; then
          echo "Host $host - $ip - alive"
        else
          echo "Host $host - $ip - FAILED"
        fi
      done <<< "$ips"
    fi
  done
}

# Main loop: run once or forever based on the options.
if [[ $LOOP -eq 1 ]]; then
  while true; do
    for host in "$@"; do
      pingHost "$host"
    done
    sleep "$SLEEP"
  done
else
  for host in "$@"; do
    pingHost "$host"
  done
fi
