// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 1985-2000 Microsoft Corporation
//
// This file is part of the Microsoft Research IPv6 Network Protocol Stack.
// You should have received a copy of the Microsoft End-User License Agreement
// for this software along with this release; see the file "license.txt".
// If not, please see http://www.research.microsoft.com/msripv6/license.htm,
// or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
//
// Abstract:
//
// Packet INternet Groper utility for IPv6.
//


#include <windows.h>
#include <devioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <wspiapi.h>
// Need ntddip6 before ws2ip6 to get CopyTDIFromSA6 and CopySAFromTDI6.
#include <ntddip6.h>
#include <ws2ip6.h>

//
// Localization library and MessageIds.
//
#include <iphlpapi.h>
#include <nls.h>
#include "localmsg.h"

#define MAX_BUFFER_SIZE         (sizeof(ICMPV6_ECHO_REPLY) + 0xfff7)
#define DEFAULT_BUFFER_SIZE     (0x2000 - 8)
#define DEFAULT_SEND_SIZE       32
#define DEFAULT_COUNT           4
#define DEFAULT_TIMEOUT         4000L
#define MIN_INTERVAL            1000L

struct sockaddr_in6 dstaddr, srcaddr;

PWCHAR
GetErrorString(int ErrorCode)
{
    DWORD Status;
    DWORD Length;
    static WCHAR ErrorString[2048]; // a 2K static buffer should suffice

    Length = 2048;
    Status = GetIpErrorString(ErrorCode, ErrorString, &Length);

    if (Status == NO_ERROR) {
        return ErrorString;     // success
    }

    return L"";                 // return a null string
}

void
PrintUsage(void)
{
    NlsPutMsg(STDOUT, PING6_MESSAGE_0);
// printf("\nUsage: ping6 [-t] [-a] [-n count] [-l size]"
//        " [-w timeout] [-s srcaddr] dest\n\n"
//        "Options:\n"
//        "-t             Ping the specifed host until interrupted.\n"
//        "-a             Resolve addresses to hostnames.\n"
//        "-n count       Number of echo requests to send.\n"
//        "-l size        Send buffer size.\n"
//        "-w timeout     Timeout in milliseconds to wait for each reply.\n"
//        "-s srcaddr     Source address to use.\n"
//        "-r             Use routing header to test reverse route also.\n");

}

//
// Can only be called once, because
// a) does not call freeaddrinfo
// b) uses a static buffer for some results
//
int
get_pingee(char *ahstr, int dnsreq, struct sockaddr_in6 *address, char **hstr)
{
    struct addrinfo hints;
    struct addrinfo *result;
    char *name = NULL;

    memset(&hints, 0, sizeof hints);
    hints.ai_flags = AI_NUMERICHOST;
    hints.ai_family = PF_INET6;

    if (getaddrinfo(ahstr, NULL, &hints, &result) != 0) {
        //
        // Not a numeric address.
        // Try again with DNS name resolution.
        //
        hints.ai_flags = AI_CANONNAME;
        if (getaddrinfo(ahstr, NULL, &hints, &result) != 0) {
            //
            // Failure - we can not resolve the name.
            //
            return FALSE;
        }

        name = result->ai_canonname;
    }
    else {
        //
        // Should we do a reverse-lookup to get a name?
        //
        if (dnsreq) {
            static char namebuf[NI_MAXHOST];

            if (getnameinfo(result->ai_addr, result->ai_addrlen,
                            namebuf, sizeof namebuf,
                            NULL, 0,
                            NI_NAMEREQD) == 0) {
                //
                // Reverse lookup succeeded.
                //
                name = namebuf;
            }
        }
    }

    *address = * (struct sockaddr_in6 *) result->ai_addr;
    *hstr = name;
    return TRUE;
}

int
get_source(char *astr, struct sockaddr_in6 *address)
{
    struct addrinfo hints;
    struct addrinfo *result;

    memset(&hints, 0, sizeof hints);
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = PF_INET6;

    if (getaddrinfo(astr, NULL, &hints, &result) != 0)
        return FALSE;

    *address = * (struct sockaddr_in6 *) result->ai_addr;
    return TRUE;
}

char *
format_addr(struct sockaddr_in6 *address)
{
    static char buffer[128];

    if (getnameinfo((struct sockaddr *)address, sizeof *address,
                    buffer, sizeof buffer, NULL, 0, NI_NUMERICHOST) != 0)
        strcpy(buffer, "<invalid>");

    return buffer;
}

u_long
param(char **argv, int argc, int current, u_long min, u_long max)
{
    u_long   temp;
    char    *dummy;

    if (current == (argc - 1) ) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_1, argv[current]);
// printf("Value must be supplied for option %s.\n", argv[current]);

        exit(1);
    }

    temp = strtoul(argv[current+1], &dummy, 0);
    if (temp < min || temp > max) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_2, argv[current]);
// printf("Bad value for option %s.\n", argv[current]);

        exit(1);
    }

    return temp;
}

u_int num_send=0, num_recv=0,
    time_min=(u_int)-1, time_max=0, time_total=0;

void
print_statistics(  )
{
    if (num_send > 0) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_3, format_addr(&dstaddr));
// printf("\nPing statistics for %s:\n", format_addr(&dstaddr));


        NlsPutMsg(STDOUT, PING6_MESSAGE_4,
               num_send, num_recv, num_send - num_recv,
               100 * (num_send - num_recv) / num_send);
// printf("    Packets: Sent = %u, Received = %u, Lost = %u (%u%% loss),\n",
//             num_send, num_recv, num_send - num_recv,
//             100 * (num_send - num_recv) / num_send);


        if (num_recv > 0) {
            NlsPutMsg(STDOUT, PING6_MESSAGE_5);
// printf("Approximate round trip times in milli-seconds:\n");

            NlsPutMsg(STDOUT, PING6_MESSAGE_6,
                   time_min, time_max, time_total / num_recv);
// printf("    Minimum = %ums, Maximum = %ums, Average = %ums\n",
//        time_min, time_max, time_total / num_recv);

        }
    }
}

// Press C-c to      print and abort.
// Press C-break to  print and continue.

BOOL
ConsoleControlHandler(DWORD dwCtrlType)
{
    print_statistics();
    switch ( dwCtrlType ) {
    case CTRL_BREAK_EVENT:
        NlsPutMsg(STDOUT, PING6_MESSAGE_7);
// printf("Control-Break\n");

        return TRUE;
    case CTRL_C_EVENT:
        NlsPutMsg(STDOUT, PING6_MESSAGE_8);
// printf("Control-C\n");

    default:
        return FALSE;
    }
}

int __cdecl
main(int argc, char **argv)
{
    char    *arg;
    u_int    i;
    u_int    j;
    int     found_addr = FALSE;
    int     dnsreq = FALSE;
    char    *hostname = NULL;
    u_int    Count = DEFAULT_COUNT;
    u_long   Timeout = DEFAULT_TIMEOUT;
    DWORD   errorCode;
    HANDLE  Handle;
    int     err;
    BOOL    result;
    PICMPV6_ECHO_REQUEST request;
    PICMPV6_ECHO_REPLY  reply;
    char    *SendBuffer, *RcvBuffer;
    u_int    RcvSize;
    u_int    SendSize = DEFAULT_SEND_SIZE;
    u_int    ReplySize;
    DWORD   bytesReturned;
    WSADATA WsaData;
    int     Reverse = FALSE;

    PWCHAR Message;
    
    memset(&srcaddr, 0, sizeof srcaddr);
    memset(&dstaddr, 0, sizeof dstaddr);

    err = WSAStartup(MAKEWORD(2, 0), &WsaData);
    if (err) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_9, GetLastError());
// printf("Unable to initialize Windows Sockets interface, error code %d\n", GetLastError());

        exit(1);
    }

    if (argc < 2) {
        PrintUsage();
        exit(1);
    } else {
        i = 1;
        while (i < (u_int) argc) {
            arg = argv[i];
            if (arg[0] == '-' || arg[0] == '/') {        // Have an option
                switch (arg[1]) {
                case '?':
                    PrintUsage();
                    exit(0);

                case 'l':
                    // Avoid jumbo-grams, we don't support them yet.
                    // Need to allow 8 bytes for the Echo Request header.
                    SendSize = (u_int)param(argv, argc, i++, 0, 0xffff - 8);
                    break;

                case 't':
                    Count = (u_int)-1;
                    break;

                case 'n':
                    Count = (u_int)param(argv, argc, i++, 1, 0xffffffff);
                    break;

                case 'w':
                    Timeout = param(argv, argc, i++, 0, 0xffffffff);
                    break;

                case 'a':
                    dnsreq = TRUE;
                    break;

                case 'r':
                    Reverse = TRUE;
                    break;

                case 's':
                    if (!get_source(argv[++i], &srcaddr)) {
                        NlsPutMsg(STDOUT, PING6_MESSAGE_10, argv[i]);
// printf("Bad IPv6 address %s.\n", argv[i]);

                        exit(1);
                    }
                    break;

                default:
                    NlsPutMsg(STDOUT, PING6_MESSAGE_11, arg);
// printf("Bad option %s.\n\n", arg);

                    PrintUsage();
                    exit(1);
                    break;
                }
                i++;
            } else {  // Not an option, must be an IPv6 address.
                if (found_addr) {
                    NlsPutMsg(STDOUT, PING6_MESSAGE_12, arg);
// printf("Bad parameter %s.\n", arg);

                    exit(1);
                }
                if (get_pingee(arg, dnsreq, &dstaddr, &hostname)) {
                    found_addr = TRUE;
                    i++;
                } else {
                    NlsPutMsg(STDOUT, PING6_MESSAGE_10, arg);
// printf("Bad IPv6 address %s.\n", arg);

                    exit(1);
                }
            }
        }
    }

    if (!found_addr) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_13);
// printf("IPv6 address must be specified.\n");

        exit(1);
    }

    Handle = CreateFileW(WIN_IPV6_DEVICE_NAME,
                         0,          // desired access
                         FILE_SHARE_READ | FILE_SHARE_WRITE,
                         NULL,       // security attributes
                         OPEN_EXISTING,
                         0,          // flags & attributes
                         NULL);      // template file
    if (Handle == INVALID_HANDLE_VALUE) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_14,
               GetLastError() );
// printf("Unable to contact IPv6 driver, error code %d.\n",
//        GetLastError() );

        exit(1);
    }

    if (srcaddr.sin6_family == 0) {
        SOCKET s;
        DWORD BytesReturned;

        //
        // A source address was not specified.
        // Get the preferred source address for this destination.
        //
        // If you want each individual echo request
        // to select a source address, use "-s ::".
        //

        s = socket(AF_INET6, 0, 0);
        if (s == INVALID_SOCKET) {
            NlsPutMsg(STDOUT, PING6_MESSAGE_15,
                   WSAGetLastError());
// printf("Unable to create IPv6 socket, error code %d.\n",
//        WSAGetLastError());

            exit(1);
        }

        //
        // This ioctl will fail if ping6 is run on a system
        // without the proper support in AFD and the stack.
        // In that case, srcaddr will be unchanged.
        //
        (void) WSAIoctl(s, SIO_ROUTING_INTERFACE_QUERY,
                        &dstaddr, sizeof dstaddr,
                        &srcaddr, sizeof srcaddr,
                        &BytesReturned, NULL, NULL);

        closesocket(s);
    }

    request = LocalAlloc(LMEM_FIXED, sizeof *request + SendSize);
    if (request == NULL) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_16);
// printf("Unable to allocate required memory.\n");

        exit(1);
    }

    SendBuffer = (char *)(request + 1);

    //
    // Calculate receive buffer size and try to allocate it.
    //
    if (SendSize <= DEFAULT_SEND_SIZE) {
        RcvSize = DEFAULT_BUFFER_SIZE;
    }
    else {
        RcvSize = MAX_BUFFER_SIZE;
    }

    reply = LocalAlloc(LMEM_FIXED, sizeof *reply + RcvSize);
    if (reply == NULL) {
        NlsPutMsg(STDOUT, PING6_MESSAGE_16);
// printf("Unable to allocate required memory.\n");

        exit(1);
    }

    RcvBuffer = (char *)(reply + 1);

    //
    // Initialize the request buffer.
    //
    CopyTDIFromSA6(&request->DstAddress, &dstaddr);
    CopyTDIFromSA6(&request->SrcAddress, &srcaddr);
    request->Flags = 0;
    if (Reverse)
        request->Flags |= ICMPV6_ECHO_REQUEST_FLAG_REVERSE;
    request->Timeout = Timeout;
    request->TTL = 0; // default TTL

    //
    // Initialize the request data pattern.
    //
    for (i = 0; i < SendSize; i++)
        SendBuffer[i] = 'a' + (i % 23);

    if (hostname)
        NlsPutMsg(STDOUT, PING6_MESSAGE_17, hostname, format_addr(&dstaddr));
// printf("\nPinging %s [%s]", hostname, format_addr(&dstaddr));

    else
        NlsPutMsg(STDOUT, PING6_MESSAGE_18, format_addr(&dstaddr));
// printf("\nPinging %s", format_addr(&dstaddr));

    if (srcaddr.sin6_family != 0)
        NlsPutMsg(STDOUT, PING6_MESSAGE_19, format_addr(&srcaddr));
// printf("\nfrom %s", format_addr(&srcaddr));

    NlsPutMsg(STDOUT, PING6_MESSAGE_20, SendSize);
// printf(" with %u bytes of data:\n\n", SendSize);


    SetConsoleCtrlHandler(&ConsoleControlHandler, TRUE);

    for (i = 0; i < Count; i++) {

        if (! DeviceIoControl(Handle,
                              IOCTL_ICMPV6_ECHO_REQUEST,
                              request,
                              sizeof *request + SendSize,
                              reply, sizeof *reply + RcvSize,
                              &bytesReturned,
                              NULL) ||
            (bytesReturned < sizeof *reply)) {

            errorCode = GetLastError();

            if (errorCode < IP_STATUS_BASE) {
                NlsPutMsg(STDOUT, PING6_MESSAGE_21, errorCode);
// printf("PING: transmit failed, error code %u\n", errorCode);

            }
            else {
                NlsPutMsg(STDOUT, PING6_MESSAGE_22,
                          GetErrorString(errorCode));
// printf("PING: %s\n", GetErrorString(errorCode));

            }
        }
        else {
            struct sockaddr_in6 ReplyAddress;

            ReplySize = bytesReturned - sizeof *reply;

            CopySAFromTDI6(&ReplyAddress, &reply->Address);

            num_send++;

            if (! IN6_IS_ADDR_UNSPECIFIED(&ReplyAddress.sin6_addr))
                NlsPutMsg(STDOUT, PING6_MESSAGE_23, format_addr(&ReplyAddress));
// printf("Reply from %s: ", format_addr(&ReplyAddress));


            if (reply->Status == IP_SUCCESS) {

                NlsPutMsg(STDOUT, PING6_MESSAGE_24, ReplySize);
// printf("bytes=%u ", ReplySize);


                if (ReplySize != SendSize) {
                    NlsPutMsg(STDOUT, PING6_MESSAGE_25, SendSize);
// printf("(sent %u) ", SendSize);

                }
                else {
                    char *sendptr, *recvptr;

                    sendptr = SendBuffer;
                    recvptr = RcvBuffer;

                    for (j = 0; j < SendSize; j++)
                        if (*sendptr++ != *recvptr++) {
                            NlsPutMsg(STDOUT, PING6_MESSAGE_26, j);
// printf("- MISCOMPARE at offset %u - ", j);

                            break;
                        }
                }

                if (reply->RoundTripTime) {
                    NlsPutMsg(STDOUT, PING6_MESSAGE_27, reply->RoundTripTime);
// printf("time=%ums ", reply->RoundTripTime);

                }
                else {
                    NlsPutMsg(STDOUT, PING6_MESSAGE_28);
// printf("time<1ms ");

                }

                NlsPutMsg(STDOUT, PING6_MESSAGE_29);
// printf("\n");


                //
                // Collect statistics.
                //
                num_recv++;
                time_total += reply->RoundTripTime;
                if (reply->RoundTripTime < time_min)
                    time_min = reply->RoundTripTime;
                if (reply->RoundTripTime > time_max)
                    time_max = reply->RoundTripTime;
            }
            else {
                NlsPutMsg(STDOUT, PING6_MESSAGE_30,
                          GetErrorString(reply->Status)); 
// printf("%s\n", GetErrorString(reply->Status));


                if (IN6_IS_ADDR_UNSPECIFIED(&ReplyAddress.sin6_addr)) {
                    //
                    // Diagnose some common errors.
                    //
                    if (reply->Status == IP_DEST_NO_ROUTE) {
                        if (IN6_IS_ADDR_LINKLOCAL(&dstaddr.sin6_addr) ||
                            IN6_IS_ADDR_SITELOCAL(&dstaddr.sin6_addr) ||
                            IN6_IS_ADDR_MULTICAST(&dstaddr.sin6_addr))
                            NlsPutMsg(STDOUT, PING6_MESSAGE_31);
// printf("  Specify correct scope-id or "
//        "use -s to specify source address.\n");

                    }
                    else if (reply->Status == IP_PARAMETER_PROBLEM) {
                        if (IN6_IS_ADDR_UNSPECIFIED(&dstaddr.sin6_addr))
                            NlsPutMsg(STDOUT, PING6_MESSAGE_32);
// printf("  Illegal destination address.\n");

                        else if (dstaddr.sin6_scope_id != 0)
                            NlsPutMsg(STDOUT, PING6_MESSAGE_33);
// printf("  Invalid scope-id specified.\n");

                        else if (IN6_IS_ADDR_LINKLOCAL(&dstaddr.sin6_addr) ||
                                 IN6_IS_ADDR_SITELOCAL(&dstaddr.sin6_addr) ||
                                 IN6_IS_ADDR_MULTICAST(&dstaddr.sin6_addr))
                            NlsPutMsg(STDOUT, PING6_MESSAGE_31);
// printf("  Specify correct scope-id or "
//        "use -s to specify source address.\n");

                    }
                    else if (reply->Status == IP_BAD_ROUTE) {
                        NlsPutMsg(STDOUT, PING6_MESSAGE_34);
// printf("  Problem with source address or scope-id.\n");

                    }
                }
            }

            if (i < (Count - 1)) {

                if (reply->RoundTripTime < MIN_INTERVAL) {
                    Sleep(MIN_INTERVAL - reply->RoundTripTime);
                }
            }
        }
    }

    print_statistics();
    return num_recv == 0;
}