'''
    icmplib
    ~~~~~~~

    A powerful library for forging ICMP packets and performing ping
    and traceroute.

        https://github.com/ValentinBELYN/icmplib

    :copyright: Copyright 2017-2021 Valentin BELYN.
    :license: GNU LGPLv3, see the LICENSE for details.

    ~~~~~~~

    Example: verbose traceroute (advanced)

    Traceroute to ovh.com (198.27.92.1): 56 data bytes, 30 hops max

        1    192.168.0.254      192.168.0.254                9.86 ms
        2    194.149.164.56     194.149.164.56               4.61 ms
        3    213.186.32.181     be100-159.th2-1-a9.fr.eu     11.97 ms
        4    94.23.122.146      be102.rbx-g1-nc5.fr.eu       15.81 ms
        5    * * *
        6    37.187.231.75      be5.rbx-iplb1-a70.fr.eu      17.12 ms
        7    198.27.92.1        www.ovh.com                  10.87 ms

    Completed.
'''

from socket import getfqdn
from time import sleep

from icmplib import ICMPv4Socket, ICMPv6Socket, ICMPRequest
from icmplib import ICMPLibError, TimeoutExceeded, TimeExceeded
from icmplib import PID, resolve, is_ipv6_address


def verbose_traceroute(address, count=2, interval=0.05, timeout=2,
        id=PID, max_hops=30):
    # We perform a DNS resolution of the address passed in parameters.
    # If the address is already an IP address, no lookup is done. The
    # same address is returned.
    ip_address = resolve(address)

    # A payload of 56 bytes is used by default. You can modify it using
    # the 'payload_size' parameter of your ICMP request.
    print(f'Traceroute to {address} ({ip_address}): '
          f'56 data bytes, {max_hops} hops max\n')

    # We detect the socket to use from the specified IP address
    if is_ipv6_address(ip_address):
        sock = ICMPv6Socket()

    else:
        sock = ICMPv4Socket()

    ttl = 1
    host_reached = False

    while not host_reached and ttl <= max_hops:
        for sequence in range(count):
            # We create an ICMP request
            request = ICMPRequest(
                destination=ip_address,
                id=id,
                sequence=sequence,
                ttl=ttl)

            try:
                # We send the request
                sock.send(request)

                # We are awaiting receipt of an ICMP reply
                reply = sock.receive(request, timeout)

                # We received a reply
                # We display some information
                source_name = getfqdn(reply.source)

                print(f'  {ttl:<2}    {reply.source:15}    '
                      f'{source_name:40}    ', end='')

                # We throw an exception if it is an ICMP error message
                reply.raise_for_status()

                # We reached the destination host
                # We calculate the round-trip time and we display it
                round_trip_time = (reply.time - request.time) * 1000
                print(round(round_trip_time, 2), 'ms')

                # We can stop the search
                host_reached = True
                break

            except TimeExceeded as err:
                # An ICMP Time Exceeded message has been received
                # The message was probably generated by an intermediate
                # gateway
                reply = err.reply

                # We calculate the round-trip time and we display it
                round_trip_time = (reply.time - request.time) * 1000
                print(round(round_trip_time, 2), 'ms')

                sleep(interval)
                break

            except TimeoutExceeded:
                # The timeout has been reached and no host or gateway
                # has responded after multiple attempts
                if sequence >= count - 1:
                    print(f'  {ttl:<2}    * * *')

            except ICMPLibError:
                # Other errors are ignored
                pass

        ttl += 1

    print('\nCompleted.')


# This function supports both FQDNs and IP addresses. See the 'resolve'
# function for details.
verbose_traceroute('ovh.com')
