"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Friday, 28th Jun, 2024

    OBJECTIVE:

        To provide a way to ping a server and get the traceroute dump.

    REFERENCES:

        1) https://www.geeksforgeeks.org/traceroute-implementation-on-python/

    DOWNLOADS:

        N/A

"""

#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")

# Utils:
from utils_v2.string import json
from utils_v2.string import regex
from utils_v2.date_time import date_time

# For networking:
import socket
from scapy.all import *

# For running the script from the terminal:
import argparse


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


def get_name_and_addr(destination):

    # If the user provided the IP address:
    if (
            regex.match(destination, regex.REGEX_IPV4) or
            regex.match(destination, regex.REGEX_IPV6)
    ):
        try: destination_name = socket.gethostbyaddr(destination)[0]
        except Exception as exception: destination_name = "*"
        destination_ip = destination

    # If the provided destination was the domain name:
    else:
        destination_name = destination
        try: destination_ip = socket.gethostbyname(destination)
        except Exception as exception: destination_ip = "*"

    # Done here:
    return destination_name, destination_ip


# ---------------------------------------------------------------------------------------------------------------------


def tracert(
        destination,
        max_hops = 30,
        timeout = 2.0,
        port = 33434
):

    # Initialize the variables:
    destination_name, destination_ip = get_name_and_addr(destination)
    full_trace = []
    ttl = 1

    # Keep noting hops till the limit is reached:
    while ttl <= max_hops:

        # Create a JSON for this stage:
        this_hop = {
            "destAddr": destination_ip,
            "destName": destination_name,
            "hopNo": ttl - 1,
            "isDest": False,
            "hopAddr": None,
            "hopName": None,
            "ts": None
        }

        # Create the IP and UDP headers and combine them:
        ip_packet = IP(dst = destination, ttl = ttl)
        udp_packet = UDP(dport = port)
        trace_packet = ip_packet / udp_packet

        # Send the packet and receive a reply and note down the timestamp:
        reply = sr1(trace_packet, timeout = timeout, verbose = 0)
        this_hop["ts"] = date_time.get_current_utc_date_time(as_string = True)

        # No response:
        if reply is None: this_hop["hopAddr"] = this_hop["hopName"] = "*"

        # If some response was received, we note the values and break out if this was the destination hop:
        else:
            this_hop["hopName"], this_hop["hopAddr"] = get_name_and_addr(f"{reply.src}")
            if reply.type == 3:
                this_hop["isDest"] = True
                full_trace.append(this_hop)
                break

        # Carry on to the next hop:
        full_trace.append(this_hop)
        ttl += 1

    # Done here:
    return full_trace


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    parser = argparse.ArgumentParser(description = "Traceroute Implementation in Python!")

    parser.add_argument(
        "dest",
        help = "Destination (Name or IP address)."
    )
    parser.add_argument(
        "-m",
        "--max-hops",
        type = int,
        default = 30,
        help =
        "Maximum number of hops (default: 30)."
    )
    parser.add_argument(
        "-t",
        "--timeout",
        type = float,
        default = 2.0,
        help = "Timeout for each packet in seconds (default: 2.0)."
    )
    parser.add_argument(
        "-p",
        "--port",
        type = int,
        default = 33434,
        help = "The port to probe at the destination (default: 33434)."
    )

    args = parser.parse_args()

    trace = tracert(
        destination = args.dest,
        max_hops = args.max_hops,
        timeout = args.timeout,
        port = args.port
    )
    print("TRACE:", json.to_string(trace))
