// Need to link with Ws2_32.lib and Iphlpapi.lib
#include <cstdio>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>

#pragma warning(disable : 4996)

#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "ws2_32.lib")

#define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
#define FREE(x)   HeapFree(GetProcessHeap(), 0, (x))

/* Note: could also use malloc() and free() */

int ShowRouteTable() {

    // Declare and initialize variables.

    /* variables used for GetIfForwardTable */
    PMIB_IPFORWARDTABLE pIpForwardTable;
    DWORD               dwSize   = 0;
    DWORD               dwRetVal = 0;

    char szDestIp[128];
    char szMaskIp[128];
    char szGatewayIp[128];

    struct in_addr IpAddr;

    int i;

    pIpForwardTable = (MIB_IPFORWARDTABLE *)MALLOC(sizeof(MIB_IPFORWARDTABLE));
    if (pIpForwardTable == nullptr) {
        printf("Error allocating memory\n");
        return 1;
    }

    if (GetIpForwardTable(pIpForwardTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) {
        FREE(pIpForwardTable);
        pIpForwardTable = (MIB_IPFORWARDTABLE *)MALLOC(dwSize);
        if (pIpForwardTable == nullptr) {
            printf("Error allocating memory\n");
            return 1;
        }
    }

    /* Note that the IPv4 addresses returned in 
     * GetIpForwardTable entries are in network byte order 
     */
    if ((dwRetVal = GetIpForwardTable(pIpForwardTable, &dwSize, 0)) == NO_ERROR) {
        printf("\tNumber of entries: %d\n", (int)pIpForwardTable->dwNumEntries);
        for (i = 0; i < (int)pIpForwardTable->dwNumEntries; i++) {
            /* Convert IPv4 addresses to strings */
            IpAddr.S_un.S_addr = (u_long)pIpForwardTable->table[i].dwForwardDest;
            strcpy_s(szDestIp, sizeof(szDestIp), inet_ntoa(IpAddr));
            IpAddr.S_un.S_addr = (u_long)pIpForwardTable->table[i].dwForwardMask;
            strcpy_s(szMaskIp, sizeof(szMaskIp), inet_ntoa(IpAddr));
            IpAddr.S_un.S_addr = (u_long)pIpForwardTable->table[i].dwForwardNextHop;
            strcpy_s(szGatewayIp, sizeof(szGatewayIp), inet_ntoa(IpAddr));

            printf("\n\tRoute[%d] Dest IP: %s\n", i, szDestIp);
            printf("\tRoute[%d] Subnet Mask: %s\n", i, szMaskIp);
            printf("\tRoute[%d] Next Hop: %s\n", i, szGatewayIp);
            printf("\tRoute[%d] If Index: %ld\n", i, pIpForwardTable->table[i].dwForwardIfIndex);
            printf("\tRoute[%d] Type: %ld - ", i, pIpForwardTable->table[i].dwForwardType);
            switch (pIpForwardTable->table[i].dwForwardType) {
                case MIB_IPROUTE_TYPE_OTHER:
                    printf("other\n");
                    break;
                case MIB_IPROUTE_TYPE_INVALID:
                    printf("invalid route\n");
                    break;
                case MIB_IPROUTE_TYPE_DIRECT:
                    printf("local route where next hop is final destination\n");
                    break;
                case MIB_IPROUTE_TYPE_INDIRECT:
                    printf("remote route where next hop is not final destination\n");
                    break;
                default:
                    printf("UNKNOWN Type value\n");
                    break;
            }
            printf("\tRoute[%d] Proto: %ld - ", i, pIpForwardTable->table[i].dwForwardProto);
            switch (pIpForwardTable->table[i].dwForwardProto) {
                case MIB_IPPROTO_OTHER:
                    printf("other\n");
                    break;
                case MIB_IPPROTO_LOCAL:
                    printf("local interface\n");
                    break;
                case MIB_IPPROTO_NETMGMT:
                    printf("static route set through network management \n");
                    break;
                case MIB_IPPROTO_ICMP:
                    printf("result of ICMP redirect\n");
                    break;
                case MIB_IPPROTO_EGP:
                    printf("Exterior Gateway Protocol (EGP)\n");
                    break;
                case MIB_IPPROTO_GGP:
                    printf("Gateway-to-Gateway Protocol (GGP)\n");
                    break;
                case MIB_IPPROTO_HELLO:
                    printf("Hello protocol\n");
                    break;
                case MIB_IPPROTO_RIP:
                    printf("Routing Information Protocol (RIP)\n");
                    break;
                case MIB_IPPROTO_IS_IS:
                    printf("Intermediate System-to-Intermediate System (IS-IS) protocol\n");
                    break;
                case MIB_IPPROTO_ES_IS:
                    printf("End System-to-Intermediate System (ES-IS) protocol\n");
                    break;
                case MIB_IPPROTO_CISCO:
                    printf("Cisco Interior Gateway Routing Protocol (IGRP)\n");
                    break;
                case MIB_IPPROTO_BBN:
                    printf("BBN Internet Gateway Protocol (IGP) using SPF\n");
                    break;
                case MIB_IPPROTO_OSPF:
                    printf("Open Shortest Path First (OSPF) protocol\n");
                    break;
                case MIB_IPPROTO_BGP:
                    printf("Border Gateway Protocol (BGP)\n");
                    break;
                case MIB_IPPROTO_NT_AUTOSTATIC:
                    printf("special Windows auto static route\n");
                    break;
                case MIB_IPPROTO_NT_STATIC:
                    printf("special Windows static route\n");
                    break;
                case MIB_IPPROTO_NT_STATIC_NON_DOD:
                    printf("special Windows static route not based on Internet standards\n");
                    break;
                default:
                    printf("UNKNOWN Proto value\n");
                    break;
            }

            printf("\tRoute[%d] Age: %ld\n", i, pIpForwardTable->table[i].dwForwardAge);
            printf("\tRoute[%d] Metric1: %ld\n", i, pIpForwardTable->table[i].dwForwardMetric1);
        }
        FREE(pIpForwardTable);
        return 0;
    } else {
        printf("\tGetIpForwardTable failed.\n");
        FREE(pIpForwardTable);
        return 1;
    }
}

int SetRouteTable() {

    // Declare and initialize variables.

    /* variables used for SetIfForwardEntry */

    PMIB_IPFORWARDTABLE pIpForwardTable = NULL;
    PMIB_IPFORWARDROW   pRow            = NULL;
    DWORD               dwSize          = 0;
    BOOL                bOrder          = FALSE;
    DWORD               dwStatus        = 0;
    DWORD               NewGateway      = 0xDDBBCCAA;    // this is in host order Ip Address AA.BB.CC.DD is DDCCBBAA
    DWORD               i;

    // Find out how big our buffer needs to be.
    dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder);
    if (dwStatus == ERROR_INSUFFICIENT_BUFFER) {
        // Allocate the memory for the table
        pIpForwardTable = (PMIB_IPFORWARDTABLE)malloc(dwSize);
        if (pIpForwardTable == NULL) {
            printf("Unable to allocate memory for the IPFORWARDTALE\n");
            exit(1);
        }
        // Now get the table.
        dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder);
    }

    if (dwStatus != ERROR_SUCCESS) {
        printf("getIpForwardTable failed.\n");
        if (pIpForwardTable) {
            free(pIpForwardTable);
        }
        exit(1);
    }
    // Search for the row in the table we want. The default gateway has a destination
    // of 0.0.0.0. Notice that we continue looking through the table, but copy only
    // one row. This is so that if there happen to be multiple default gateways, we can
    // be sure to delete them all.
    for (i = 0; i < pIpForwardTable->dwNumEntries; i++) {
        if (pIpForwardTable->table[i].dwForwardDest == 0) {
            // We have found the default gateway.
            if (!pRow) {
                // Allocate some memory to store the row in. This is easier than filling
                // in the row structure ourselves, and we can be sure to change only the
                // gateway address.
                pRow = (PMIB_IPFORWARDROW)malloc(sizeof(MIB_IPFORWARDROW));
                if (!pRow) {
                    printf("Malloc failed. Out of memory.\n");
                    exit(1);
                }
                // Copy the row.
                memcpy(pRow, &(pIpForwardTable->table[i]), sizeof(MIB_IPFORWARDROW));
            }
            // Delete the old default gateway entry.
            dwStatus = DeleteIpForwardEntry(&(pIpForwardTable->table[i]));

            if (dwStatus != ERROR_SUCCESS) {
                printf("Could not delete old gateway\n");
                exit(1);
            }
        }
    }

    // Set the nexthop field to our new gateway. All the other properties of the route will
    // be the same as they were previously.
    pRow->dwForwardNextHop = NewGateway;

    // Create a new route entry for the default gateway.
    dwStatus = SetIpForwardEntry(pRow);

    if (dwStatus == NO_ERROR) {
        printf("Gateway changed successfully\n");
    } else if (dwStatus == ERROR_INVALID_PARAMETER) {
        printf("Invalid parameter.\n");
    } else {
        printf("Error: %d\n", dwStatus);
    }

    // Free resources.
    if (pIpForwardTable) {
        free(pIpForwardTable);
    }
    if (pRow) {
        free(pRow);
    }

    return 0;
}