/*
 * Some issues:
 *
 * 1. libwinet_dump_ipv6_route_table
 *
 *    Before Windows Vista, we have to use command "netsh" to get ipv6
 *    route table. But the output misses the value of route protocol.
 *
 * 2. libwinet_edit_route_entry
 * 
 *    What should be the value of protocol? MIB_IPPROTO_NETMGMT or
 *    RTPROT_BABEL_LOCAL
 *    
 */

/* The Win32 select only worked on socket handles. The Cygwin
 * implementation allows select to function normally when given
 * different types of file descriptors (sockets, pipes, handles,
 * etc.).
 */
#if !defined(__INSIDE_CYGWIN__)
  #define __INSIDE_CYGWIN__
  #define USE_SYS_TYPES_FD_SET
#endif
#include <sys/select.h>
#include <sys/fcntl.h>

/* Headers in the /usr/include/w32api */
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <wlanapi.h>

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <wchar.h>

#define INSIDE_CYGINET
#include "cyginet.h"
#undef INSIDE_CYGINET

static HRESULT (WINAPI *ws_guidfromstring)(LPCTSTR psz, LPGUID pguid) = NULL;
static HANDLE event_notify_monitor_thread = WSA_INVALID_EVENT;

static void
plen2mask(int n, struct in_addr *dest)
{
    unsigned char *p;
    int i;

    static const int pl2m[9] = {
        0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
    };

    memset(dest, 0, sizeof(struct in_addr));
    p = (u_char *)dest;
    for (i = 0; i < 4; i++, p++, n -= 8) {
        if (n >= 8) {
            *p = 0xff;
            continue;
        }
        *p = pl2m[n];
        break;
    }
    return;
}

static int
mask2len(const unsigned char *p, const int size)
{
    int i = 0, j;

    for(j = 0; j < size; j++, p++) {
        if(*p != 0xff)
            break;
        i += 8;
    }
    if(j < size) {
        switch(*p) {
#define	MASKLEN(m, l)	case m: do { i += l; break; } while (0)
            MASKLEN(0xfe, 7); break;
            MASKLEN(0xfc, 6); break;
            MASKLEN(0xf8, 5); break;
            MASKLEN(0xf0, 4); break;
            MASKLEN(0xe0, 3); break;
            MASKLEN(0xc0, 2); break;
            MASKLEN(0x80, 1); break;
#undef	MASKLEN
        }
    }
    return i;
}

static int
libwinet_get_interface_info(const char *ifname,
                            int family,
                            int ifindex,
                            PLIBWINET_INTERFACE pinfo)
{
  IP_ADAPTER_ADDRESSES *pAdaptAddr = NULL;
  IP_ADAPTER_ADDRESSES *pTmpAdaptAddr = NULL;
  DWORD dwRet = 0;
  DWORD dwSize = 0x10000;
  DWORD dwReturn = 0;

  dwRet = GetAdaptersAddresses(family,
                               GAA_FLAG_SKIP_ANYCAST            \
                               | GAA_FLAG_SKIP_MULTICAST        \
                               | GAA_FLAG_SKIP_DNS_SERVER       \
                               | GAA_FLAG_SKIP_FRIENDLY_NAME,
                               NULL,
                               pAdaptAddr,
                               &dwSize
                               );
  if (ERROR_BUFFER_OVERFLOW == dwRet) {
    FREE(pAdaptAddr);
    if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize)))
      return -1;
    dwRet = GetAdaptersAddresses(family,
                                 GAA_FLAG_SKIP_ANYCAST            \
                                 | GAA_FLAG_SKIP_MULTICAST        \
                                 | GAA_FLAG_SKIP_DNS_SERVER       \
                                 | GAA_FLAG_SKIP_FRIENDLY_NAME,
                                 NULL,
                                 pAdaptAddr,
                                 &dwSize
                                 );
  }
  if (NO_ERROR == dwRet) {

    pTmpAdaptAddr = pAdaptAddr;

    while (pTmpAdaptAddr) {
      /* For ipv4, two contidions:
         AdapterName equals ifname and IfIndex is nonzero.
         For ipv6, only judge Ipv6IfIndex
         */
      if (family == AF_INET6 ? pTmpAdaptAddr -> Ipv6IfIndex == ifindex  \
          : (pTmpAdaptAddr -> IfIndex != 0)                             \
          && (strcmp(ifname, pTmpAdaptAddr -> AdapterName) == 0)
          ) {
        memset(pinfo, 0, sizeof(pinfo));
        pinfo -> IfType = pTmpAdaptAddr -> IfType;
        pinfo -> Mtu = pTmpAdaptAddr -> Mtu;
        pinfo -> OperStatus = pTmpAdaptAddr -> OperStatus;
        /* Copy first unicast address */
        if (pTmpAdaptAddr -> FirstUnicastAddress)
          memcpy(&(pinfo -> Address),
                 (pTmpAdaptAddr -> FirstUnicastAddress -> Address).lpSockaddr,
                 sizeof(pinfo -> Address)
                 );
        dwReturn = 1;
        break;
      }

      pTmpAdaptAddr = pTmpAdaptAddr->Next;
    }

  }

  FREE(pAdaptAddr);
  return dwReturn;
}

static int
libwinet_run_command(const char *command)
{
  FILE *output;

  output = popen (command, "r");
  if (!output)
    return -1;
  /* Waiting for subprocess exit and return exit code */
  return pclose (output);
}

#if _WIN32_WINNT < _WIN32_WINNT_VISTA

/*
 * Before Windows Vista, use netsh command to get ipv6 route table
 *
 *   C:\> netsh interface ipv6 show routes verbose
 *
 *   It will print the following route entries:
 *
 *       Prefix            : fe80::1/128
 *       Interface 1       : Loopback Pseudo-Interface
 *       Gateway           : fe80::1
 *       Metric            : 4
 *       Publish           : no
 *       Type              : System
 *       Valid Lifetime    : infinite
 *       Preferred Lifetime: infinite
 *       Site Prefix Length: 0
 *
 *       ....
 *
 *   Type System means that routes used for loopback.
 *
 *   Gateway could be an address or interface name.
 *
 */
static int
libwinet_dump_ipv6_route_table(struct cyginet_route *routes,
                               int maxroutes)
{
  #define MAX_LINE_SIZE 80
  const char * command = "netsh interface ipv6 show routes verbose";

  FILE *output;
  char buffer[MAX_LINE_SIZE];
  char *s, *p;
  int count = 0;
  int ignored = 0;

  struct sockaddr_in6 *dest;
  struct sockaddr_in6 *gate;

  struct cyginet_route route;
  struct cyginet_route * proute = routes;

  output = popen (command, "r");
  if (!output)
    return -1;

  dest = (struct sockaddr_in6*)&(route.prefix);
  gate = (struct sockaddr_in6*)&(route.gateway);

  /* Ignore the first line */
  fgets(buffer, MAX_LINE_SIZE, output);

  /* Read the output until EOF */
  while (fgets(buffer, MAX_LINE_SIZE, output)) {

    if (('\n' == buffer[0]) || ('\r' == buffer[0]))
      continue;

    if (NULL == (s = strchr(buffer, ':')))
      break;

    *s ++ = 0;                  /* Split the string */
    s ++;                       /* Skip space */

    /* The first field of route entry */
    if (strncmp(buffer, "Prefix", 6) == 0) {
      
      memset(&route, 0, sizeof(struct cyginet_route));
      if (NULL == (p = strchr(s, '/')))
        break;
      *p ++ = 0;
      /*
       * Maybe it will be "fe80::5efe:10.85.0.127", ignore it
       */
      if (inet_pton(AF_INET6, s, &(dest -> sin6_addr)) != 1)
        ignored = 1;
      dest -> sin6_family = AF_INET6;
      route.plen = strtol(p, NULL, 10);
    }

    else if (strncmp(buffer, "Interface", 9) == 0)
      route.ifindex = strtol(buffer + 9, NULL, 10);

    else if (strncmp(buffer, "Gateway", 7) == 0) {
      if (inet_pton(AF_INET6, s, &(gate -> sin6_addr)) == 1)
        gate -> sin6_family = AF_INET6;
    }

    else if (strncmp(buffer, "Metric", 6) == 0)
      route.metric = strtol(s, NULL, 10);

    /* Last field of the route entry */
    else if (strncmp(buffer, "Site Prefix Length", 18) == 0) {
      if (ignored)
        ignored = 0;
      else if (!ignored) {
        route.proto = MIB_IPPROTO_OTHER; /* ?? */
        if ((maxroutes > count) && (proute != NULL)) {
          memcpy(proute, &route, sizeof(struct cyginet_route));
          proute ++;
        }
        count ++;
        
      }
    }

    else {
      /* Immortal: persistent entry */
      /* Age */
      /* ... */
    }
  }

  pclose (output);
  return count;
}

/* Tell the interface is wireless or not. First we list all wireless
 * interfaces in the machine, then search the designated one.
 */
static int
libwinet_is_wireless_interface(const char *ifname)
{
  GUID ifguid;

  HANDLE hClient = NULL;
  DWORD dwMaxClient = 1;
  /* 1 Client version for Windows XP with SP3 and Wireless LAN API for
     Windows XP with SP2. */
  /* 2 Client version for Windows Vista and Windows Server 2008 */
  DWORD dwCurVersion = 1;
  DWORD dwResult = 0;
  int iRet = 0;
  int i;

  if (NULL == ws_guidfromstring) {
    HMODULE lib;
    if ((lib = LoadLibraryW(L"shell32.dll"))) {
      ws_guidfromstring = (HRESULT (WINAPI *)(LPCTSTR, LPGUID))
        GetProcAddress(lib, (LPCSTR)703); /* GUIDFromStringA */
      FreeLibrary(lib);
    }
  }
  if (NULL == ws_guidfromstring)
    return -1;

  if (!(*ws_guidfromstring)(ifname, &ifguid))
    return -1;

  /* variables used for WlanEnumInterfaces  */
  PWLAN_INTERFACE_INFO_LIST pIfList = NULL;
  PWLAN_INTERFACE_INFO pIfInfo = NULL;

  dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &hClient);
  if (dwResult != ERROR_SUCCESS)
    return -1;

  dwResult = WlanEnumInterfaces(hClient, NULL, &pIfList);
  if (dwResult != ERROR_SUCCESS)
    return -1;

  for (i = 0; i < (int) pIfList->dwNumberOfItems; i++) {
    pIfInfo = (WLAN_INTERFACE_INFO *) &pIfList->InterfaceInfo[i];
    if (0 == memcmp(&pIfInfo->InterfaceGuid, &ifguid, sizeof(GUID))) {
      iRet = 1;
      break;
    }
  }

  if (pIfList != NULL) {
    WlanFreeMemory(pIfList);
    pIfList = NULL;
  }

  return iRet;
}

#endif  /* _WIN32_WINNT < _WIN32_WINNT_VISTA */

static DWORD WINAPI
libwinet_monitor_route_thread_proc(LPVOID lpParam)
{
  #define EVENT_COUNT 4
  DWORD dwBytesReturned = 0;
  DWORD dwReturn = 0;

  SOCKET s[2] = {INVALID_SOCKET, INVALID_SOCKET};
  WSAOVERLAPPED hOverLappeds[EVENT_COUNT];
  WSAEVENT hEvents[EVENT_COUNT + 1];
  SOCKADDR_IN6 IPv6Addr = {
      AF_INET6,
      0,
      0,
      {{IN6ADDR_ANY_INIT}}
  };
  SOCKADDR_IN IPv4Addr = {
    AF_INET,
    0,
    {{{INADDR_ANY}}},
    {0}
  };

  int mypipe = (int)lpParam;
  BOOL bResult = TRUE;
  int i;

  memset(hOverLappeds, 0, sizeof(WSAOVERLAPPED) * EVENT_COUNT);
  memset(hEvents, 0, sizeof(WSAEVENT) * EVENT_COUNT);

  hEvents[EVENT_COUNT] = event_notify_monitor_thread;

  if (bResult) {
    s[0] = socket(AF_INET, SOCK_DGRAM, 0);
    s[1] = socket(AF_INET6, SOCK_DGRAM, 0);

    if ((INVALID_SOCKET == s[0]) || (INVALID_SOCKET == s[1])) {
      SOCKETERR("socket");
      bResult = FALSE;
    }
  }

  if (bResult)
    for (i = 0; i < EVENT_COUNT; i++)
      if (WSA_INVALID_EVENT == (hEvents[i] = WSACreateEvent())) {
        SOCKETERR("WSACreateEvent");
        bResult = FALSE;
        break;
      }

  /* DestAddrs[0].sa_family = AF_INET; */
  /* ((SOCKADDR_IN*)&DestAddrs[0])->sin_addr.S_un.S_addr =
     htonl(INADDR_ANY); */
  /* DestAddrs[1].sa_family = AF_INET6; */
  /* ((SOCKADDR_IN6*)&DestAddrs[1])->sin6_addr = IN6ADDR_ANY_INIT; */

  /* Waiting for route and interface changed */
  DWORD dwWaitResult;
  while (bResult) {

    memset(hOverLappeds, 0, sizeof(WSAOVERLAPPED) * EVENT_COUNT);
    for (i = 0; i < EVENT_COUNT; i++) 
      hOverLappeds[i].hEvent = hEvents[i];
    for (i = 0; i < 2; i++) {

      if (bResult) {
        if (SOCKET_ERROR == WSAIoctl(s[i],
                                     SIO_ROUTING_INTERFACE_CHANGE,
                                     i ? (LPVOID)&IPv6Addr:(LPVOID)&IPv4Addr,
                                     i ? sizeof(SOCKADDR_IN6):sizeof(SOCKADDR_IN),
                                     NULL,
                                     0,
                                     &dwBytesReturned,
                                     &hOverLappeds[i * 2],
                                     NULL
                                     )) {
          if (WSA_IO_PENDING != WSAGetLastError()) {
            SOCKETERR("WSAIoctl");
            bResult = FALSE;
          }
        }
      }

      if (bResult) {
        if (SOCKET_ERROR == WSAIoctl(s[i],
                                     SIO_ADDRESS_LIST_CHANGE,
                                     NULL,
                                     0,
                                     NULL,
                                     0,
                                     &dwBytesReturned,
                                     &hOverLappeds[i * 2 + 1],
                                     NULL
                                     )) {
          if (WSA_IO_PENDING != WSAGetLastError()) {
            SOCKETERR("WSAIoctl");
            bResult = FALSE;
          }
        }
      }
    }
    if (bResult) {
      dwWaitResult = WSAWaitForMultipleEvents(EVENT_COUNT + 1,
                                              hEvents,
                                              FALSE,
                                              WSA_INFINITE,
                                              FALSE
                                              );
      switch (dwWaitResult) {
      case WSA_WAIT_TIMEOUT:
        break;
      case WSA_WAIT_EVENT_0:
        WSAResetEvent(hEvents[0]);
        if (write(mypipe, &"0", 1) == -1)
          bResult = FALSE;
        break;
      case WSA_WAIT_EVENT_0 + 1:
        WSAResetEvent(hEvents[1]);
        if (write(mypipe, &"1", 1) == -1)
          bResult = FALSE;
        break;
      case WSA_WAIT_EVENT_0 + 2:
        WSAResetEvent(hEvents[2]);
        if (write(mypipe, &"2", 1) == -1)
          bResult = FALSE;
        break;
      case WSA_WAIT_EVENT_0 + 3:
        WSAResetEvent(hEvents[3]);
        if (write(mypipe, &"3", 1) == -1)
          bResult = FALSE;
        break;
      case WSA_WAIT_EVENT_0 + 4:
        WSAResetEvent(hEvents[4]);
        bResult = FALSE;
        break;
      case WSA_WAIT_IO_COMPLETION:
        break;
      default:
        SOCKETERR("WSAWaitForMultipleEvents");
        bResult = FALSE;
      }    
    }
  }
  for (i = 0; i < EVENT_COUNT; i ++)
    CLOSESOCKEVENT(hEvents[i]);
  CLOSESOCKET(s[0]);
  CLOSESOCKET(s[1]);

  return dwReturn;
}

int cyginet_start_monitor_route_changes(int mypipe)
{
  if (WSA_INVALID_EVENT == event_notify_monitor_thread)
    event_notify_monitor_thread = WSACreateEvent();
  if (WSA_INVALID_EVENT == event_notify_monitor_thread)
    return -1;

  HANDLE hthread;
  hthread = CreateThread(NULL,              // default security
                         0,                 // stack size
                         libwinet_monitor_route_thread_proc,
                         (LPVOID)mypipe,
                         0,                 // startup flags
                         NULL
                         );
  if (hthread == NULL) {
    CLOSESOCKEVENT(event_notify_monitor_thread);
    event_notify_monitor_thread = WSA_INVALID_EVENT;
    return -1;
  }
  return 0;
}

int cyginet_stop_monitor_route_changes()
{
  int rc = 0;

  /* Notify thread to quit */
  WSASetEvent(event_notify_monitor_thread);
  CLOSESOCKEVENT(event_notify_monitor_thread);
  /* Waiting for thread exit in 5 seconds */
  event_notify_monitor_thread = WSA_INVALID_EVENT;

  return rc;
}

/*
 * There are 3 ways to change a route:
 *
 * Before Windows Vista
 *
 * 1. IPv4 route: CreateIpForwardEntry
 *                DeleteIpForwardEntry
 *                SetIpForwardEntry
 *
 * 2. IPv6 route: command "netsh"
 *
 *    C:/> netsh interface ipv6 add route
 *                   prefix=<IPv6 address>/<integer>
 *                   interface=]<string>
 *                   nexthop=<IPv6 address>
 *                   metric=<integer>
 *
 *    Example:
 *
 *      add route prefix=3ffe::/16 interface=1 nexthop=fe80::1
 *
 * In Windows Vista and later
 *
 * 3. API: CreateIpForwardEntry2
 *         DeleteIpForwardEntry2
 *         SetIpForwardEntry2
 *
 */
static int
libwinet_edit_route_entry(const struct sockaddr *dest,
                          unsigned short plen,
                          const struct sockaddr *gate,
                          int ifindex,
                          unsigned int metric,
                          int cmdflag)
{

#if _WIN32_WINNT < _WIN32_WINNT_VISTA

  /* Add ipv6 route before Windows Vista */
  if(dest->sa_family == AF_INET6) {
    const int MAX_BUFFER_SIZE = 1024;
    const char * cmdformat = "netsh interface ipv6 %s route "
                             "prefix=%s/%d interface=%d "
                             "nexthop=%s %s%d";
    char cmdbuf[MAX_BUFFER_SIZE];
    char sdest[INET6_ADDRSTRLEN];
    char sgate[INET6_ADDRSTRLEN];

    if (NULL == inet_ntop(AF_INET6,
                          (const void*)(&(((SOCKADDR_IN6*)dest)->sin6_addr)),
                          sdest,
                          INET6_ADDRSTRLEN
                          ))
      return -1;
    if (NULL == inet_ntop(AF_INET6,
                          (const void*)(&(((SOCKADDR_IN6*)gate)->sin6_addr)),
                          sgate,
                          INET6_ADDRSTRLEN
                          ))
      return -1;

    if (snprintf(cmdbuf,
                 MAX_BUFFER_SIZE,
                 cmdformat,
                 cmdflag == RTM_ADD ? "add" :
                 cmdflag == RTM_DELETE ? "delete" : "set",
                 sdest,
                 plen,
                 ifindex,
                 sgate,
                 cmdflag == RTM_DELETE ? "#" : "metric=",
                 metric
                 ) >= MAX_BUFFER_SIZE)
      return -1;

    if (libwinet_run_command(cmdbuf) != 0)
      return -1;

  }

  /* Add ipv4 route before Windows Vista */
  else if (0) {
    MIB_IPFORWARDROW Row;
    unsigned long Res;
    memset(&Row, 0, sizeof(MIB_IPFORWARDROW));

    Row.dwForwardDest = (((SOCKADDR_IN*)dest) -> sin_addr).S_un.S_addr;
    Row.dwForwardPolicy = 0;
    Row.dwForwardNextHop = (((SOCKADDR_IN*)gate) -> sin_addr).S_un.S_addr;
    Row.dwForwardIfIndex = ifindex;
    /*
     * MIB_IPROUTE_TYPE_DIRECT <==> dwForwardNextHop == dwForwardDest
     * MIB_IPROUTE_TYPE_LOCAL  <==> dwForwardNextHop == Ip of the interface
     * MIB_IPROUTE_TYPE_INDIRECT all the others
     */
    Row.dwForwardType = Row.dwForwardNextHop == Row.dwForwardDest ? \
      MIB_IPROUTE_TYPE_DIRECT : MIB_IPROUTE_TYPE_INDIRECT;
    Row.dwForwardProto = MIB_IPPROTO_NETMGMT;
    Row.dwForwardAge = 0;
    Row.dwForwardNextHopAS = 0;
    Row.dwForwardMetric1 = metric;
    Row.dwForwardMetric2 = -1;
    Row.dwForwardMetric3 = -1;
    Row.dwForwardMetric4 = -1;
    Row.dwForwardMetric5 = -1;
    switch(cmdflag) {
    case RTM_ADD:
      Res = CreateIpForwardEntry(&Row);
      break;
    case RTM_DELETE:
      Res = DeleteIpForwardEntry(&Row);
      break;
    case RTM_CHANGE:
      Res = SetIpForwardEntry(&Row);
      break;
    default:
      Res = -1;
      break;
    }
    if (Res != NO_ERROR)
      return -1;
  }

  /* Use route command */
  else {
    /* route ADD dest MASK mask gate METRIC n IF index */
    /* route CHANGE dest MASK mask gate METRIC n IF index */
    /* route DELETE dest MASK mask gate METRIC n IF index */
    const int MAX_BUFFER_SIZE = 1024;
    char cmdbuf[MAX_BUFFER_SIZE];
    char sdest[INET_ADDRSTRLEN];
    char sgate[INET_ADDRSTRLEN];
    char smask[INET_ADDRSTRLEN];
    
    struct in_addr mask;
    plen2mask(plen, &mask);

    if (NULL == inet_ntop(AF_INET,
                          (const void*)(&(((SOCKADDR_IN*)dest)->sin_addr)),
                          sdest,
                          INET_ADDRSTRLEN
                          ))
      return -1;
    if (NULL == inet_ntop(AF_INET,
                          (const void*)(&(((SOCKADDR_IN*)gate)->sin_addr)),
                          sgate,
                          INET_ADDRSTRLEN
                          ))
      return -1;
    if (NULL == inet_ntop(AF_INET,
                          (const void*)(&mask),
                          smask,
                          INET_ADDRSTRLEN
                          ))
      return -1;

    if (snprintf(cmdbuf,
                 MAX_BUFFER_SIZE,
                 "route %s %s MASK %s %s METRIC %d IF %d",
                 cmdflag == RTM_ADD ? "add" :
                 cmdflag == RTM_DELETE ? "delete" : "change",
                 sdest,
                 smask,
                 sgate,
                 metric,
                 ifindex
                 ) >= MAX_BUFFER_SIZE)
      return -1;

    if (libwinet_run_command(cmdbuf) != 0)
      return -1;
  }

#else
  /* Add route entry after Windows Vista */
    MIB_IPFORWARDROW2 Row2;
    unsigned long Res;

    memset(&Row2, 0, sizeof(MIB_IPFORWARDROW2));

    Row2.InterfaceLuid = NULL;
    Row2.InterfaceIndex = ifindex;
    Row2.DestinationPrefix.PrefixLength = plen;
    memcpy(&Row2.DestinationPrefix.Prefix, dest, sizeof(SOCKADDR_INET));
    memcpy(&Row2.NextHop, gate, sizeof(SOCKADDR_INET)) ;
    Row2.SitePrefixLength = 255; /* INVALID */
    Row2.ValidLifetime = WSA_INFINITE;;
    Row2.PreferredLifetime = WSA_INFINITE;
    Row2.Metric = metric;
    Row2.Protocol = MIB_IPPROTO_NETMGMT;
    Row2.Loopback = gate->sa_family == AF_INET6 ?
      IN6_IS_ADDR_LOOPBACK(&(((SOCKADDR_IN6*)gate)->sin6_addr)) :
      IN_LOOPBACK(ntohl(((SOCKADDR_IN*)gate)->sin_addr.S_un.S_addr));
    Row2.AutoconfigureAddress = FALSE;
    Row2.Publish = FALSE;
    Row2.Immortal = 0;
    Row2.Age = 0;
    Row2.Origin = 0;            /* NlroManual */

    switch(cmdflag) {
    case 0:
      Res = CreateIpForwardEntry2(&Row);
      break;
    case 1:
      Res = SetIpForwardEntry2(&Row);
      break;
    case 2:
      Res = DeleteIpForwardEntry2(&Row);
      break;
    }

    if (Res != NO_ERROR)
      return -1;
  }

#endif  /*  _WIN32_WINNT < _WIN32_WINNT_VISTA */

  return 1;
}

static int
libwinet_set_registry_key(char *key, char * name, int value, int defvalue)
{
  HKEY hKey;
  unsigned long type;
  unsigned long size;
  unsigned long old;

  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_READ | KEY_WRITE, &hKey) !=
      ERROR_SUCCESS)
    return -1;

  size = sizeof(old);

  if (RegQueryValueEx(hKey, name, NULL, &type, (unsigned char *)&old, &size) !=
      ERROR_SUCCESS || type != REG_DWORD)
    old = defvalue;

  if (RegSetValueEx(hKey,
                    name,
                    0,
                    REG_DWORD,
                    (unsigned char *)&value,
                    sizeof(value)
                    )) {
    RegCloseKey(hKey);
    return -1;
  }

  RegCloseKey(hKey);
  return old;
}

static int
libwinet_get_loopback_index(int family)
{
  IP_ADAPTER_ADDRESSES *pAdaptAddr = NULL;
  IP_ADAPTER_ADDRESSES *pTmpAdaptAddr = NULL;
  DWORD dwRet = 0;
  DWORD dwSize = 0x10000;
  DWORD dwReturn = 0;

  dwRet = GetAdaptersAddresses(family,
                               GAA_FLAG_SKIP_ANYCAST            \
                               | GAA_FLAG_SKIP_MULTICAST        \
                               | GAA_FLAG_SKIP_DNS_SERVER       \
                               | GAA_FLAG_SKIP_FRIENDLY_NAME,
                               NULL,
                               pAdaptAddr,
                               &dwSize
                               );
  if (ERROR_BUFFER_OVERFLOW == dwRet) {
    FREE(pAdaptAddr);
    if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize)))
      return 0;
    dwRet = GetAdaptersAddresses(family,
                                 GAA_FLAG_SKIP_ANYCAST            \
                                 | GAA_FLAG_SKIP_MULTICAST        \
                                 | GAA_FLAG_SKIP_DNS_SERVER       \
                                 | GAA_FLAG_SKIP_FRIENDLY_NAME,
                                 NULL,
                                 pAdaptAddr,
                                 &dwSize
                                 );
  }
  if (NO_ERROR == dwRet) {

    pTmpAdaptAddr = pAdaptAddr;

    while (pTmpAdaptAddr) {

      if (IF_TYPE_SOFTWARE_LOOPBACK == pTmpAdaptAddr -> IfType) {
        dwReturn = family == AF_INET ?
                   pTmpAdaptAddr -> IfIndex :
                   pTmpAdaptAddr -> Ipv6IfIndex;
        break;
      }

      pTmpAdaptAddr = pTmpAdaptAddr->Next;
    }
  }

  FREE(pAdaptAddr);
  return dwReturn;
}

int
cyginet_set_ipv6_forwards(int value)
{
  char * key = "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters";

  return libwinet_set_registry_key(key,
                                   "IPEnableRouter",
                                   value,
                                   0
                                   );
}

int
cyginet_set_icmp6_redirect_accept(int value)
{
  char * key = "SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters";

  return libwinet_set_registry_key(key,
                                   "EnableICMPRedirect",
                                   value,
                                   1
                                   );
}

/* 
 * On Windows Vista and later, wireless network cards are reported as
 * IF_TYPE_IEEE80211 by the GetAdaptersAddresses function.
 *
 * On earlier versions of Windows, wireless network cards are reported
 * as IF_TYPE_ETHERNET_CSMACD. On Windows XP with SP3 and on Windows
 * XP with SP2 x86 with the Wireless LAN API for Windows XP with SP2
 * installed, the WlanEnumInterfaces function can be used to enumerate
 * wireless interfaces on the local computer.
 */
int cyginet_interface_wireless(const char *ifname, int ifindex)
{
  LIBWINET_INTERFACE winf;

  if (1 == libwinet_get_interface_info(ifname, AF_INET6, ifindex, &winf)) {

    if (IF_TYPE_IEEE80211 == winf.IfType)
      return 1;

#if _WIN32_WINNT < _WIN32_WINNT_VISTA
    if (IF_TYPE_ETHERNET_CSMACD == winf.IfType) {
      return libwinet_is_wireless_interface(ifname);
    }
#endif

    return 0;
  }
  return -1;
}

int
cyginet_interface_mtu(const char *ifname, int ifindex)
{
  LIBWINET_INTERFACE winf;

  if (1 == libwinet_get_interface_info(ifname, AF_INET6, ifindex, &winf))
    return winf.Mtu;

  return -1;
}

int
cyginet_interface_operational(const char *ifname, int ifindex)
{
  LIBWINET_INTERFACE winf;

  if (1 == libwinet_get_interface_info(ifname, AF_INET6, ifindex, &winf))
    return winf.OperStatus;

  return -1;
}

int
cyginet_interface_ipv4(const char *ifname,
                       int ifindex,
                       unsigned char *addr_r)
{
  LIBWINET_INTERFACE winf;

  if (1 == libwinet_get_interface_info(ifname, AF_INET, ifindex, &winf)) {

    memcpy(addr_r, &((struct sockaddr_in*)&winf.Address)->sin_addr, 4);
    return 1;
  }
  return -1;
}

int
cyginet_interface_sdl(struct sockaddr_dl *sdl, char *ifname)
{
  IP_ADAPTER_ADDRESSES *pAdaptAddr = NULL;
  IP_ADAPTER_ADDRESSES *pTmpAdaptAddr = NULL;
  DWORD dwRet = 0;
  DWORD dwSize = 0x10000;
  DWORD dwReturn = -1;
  DWORD dwFamily = AF_UNSPEC;
  size_t size;

  int ifindex;

  if (0 == (ifindex = if_nametoindex(ifname)))
    return -1;

  dwRet = GetAdaptersAddresses(dwFamily,
                               GAA_FLAG_SKIP_ANYCAST            \
                               | GAA_FLAG_SKIP_MULTICAST        \
                               | GAA_FLAG_SKIP_DNS_SERVER       \
                               | GAA_FLAG_SKIP_FRIENDLY_NAME,
                               NULL,
                               pAdaptAddr,
                               &dwSize
                               );
  if (ERROR_BUFFER_OVERFLOW == dwRet) {
    FREE(pAdaptAddr);
    if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize)))
      return -1;
    dwRet = GetAdaptersAddresses(dwFamily,
                                 GAA_FLAG_SKIP_ANYCAST            \
                                 | GAA_FLAG_SKIP_MULTICAST        \
                                 | GAA_FLAG_SKIP_DNS_SERVER       \
                                 | GAA_FLAG_SKIP_FRIENDLY_NAME,
                                 NULL,
                                 pAdaptAddr,
                                 &dwSize
                                 );
  }
  if (NO_ERROR == dwRet) {

    pTmpAdaptAddr = pAdaptAddr;

    while (pTmpAdaptAddr) {

      if (pTmpAdaptAddr -> Ipv6IfIndex == ifindex) {
        size = strlen(ifname);
        sdl -> sdl_family = 0;
        sdl -> sdl_index = pTmpAdaptAddr -> Ipv6IfIndex;
        sdl -> sdl_type = pTmpAdaptAddr -> IfType;
        sdl -> sdl_nlen = size;
        sdl -> sdl_alen = pTmpAdaptAddr -> PhysicalAddressLength;
        sdl -> sdl_slen = 0;
        memcpy(sdl -> sdl_data, ifname, size);
        memcpy(sdl -> sdl_data + size,
               pTmpAdaptAddr -> PhysicalAddress,
               pTmpAdaptAddr -> PhysicalAddressLength
               );
        sdl -> sdl_len = ((void*)(sdl->sdl_data) - (void*)sdl) + size   \
          + pTmpAdaptAddr -> PhysicalAddressLength;
        dwReturn = 0;
        break;
      }

      pTmpAdaptAddr = pTmpAdaptAddr->Next;
    }

  }

  FREE(pAdaptAddr);
  return dwReturn;
}

int
cyginet_loopback_index(int family)
{
  return libwinet_get_loopback_index(family);
}

/*
 * There are 3 ways to dump route table in the Windows:
 *
 * Before Windows Vista
 *
 * 1. IPv4 route: GetIpForwardTable
 *
 * 2. IPv6 route: command "netsh"
 *
 *    C:/> netsh interface ipv6 show route verbose
 *
 * In Windows Vista and later
 *
 * 3. API: GetIpForwardTable2
 *
 */
int
cyginet_dump_route_table(struct cyginet_route *routes, int maxroutes)
{
  
  ULONG NumEntries = -1;
  struct cyginet_route *proute;
  int i;

#if _WIN32_WINNT < _WIN32_WINNT_VISTA

  /* First dump ipv6 route */
  NumEntries = libwinet_dump_ipv6_route_table(routes, maxroutes);
  
  if (NumEntries < 0)
    return -1;

  /* Then ipv4 route table */
  SOCKADDR_IN * paddr;
  PMIB_IPFORWARDTABLE pIpForwardTable;
  PMIB_IPFORWARDROW pRow;
  DWORD dwSize = sizeof(MIB_IPFORWARDTABLE);

  pIpForwardTable = (PMIB_IPFORWARDTABLE)MALLOC(dwSize);
  if (NULL == pIpForwardTable)
    return -1;

  if (ERROR_INSUFFICIENT_BUFFER == GetIpForwardTable(pIpForwardTable,
                                                     &dwSize,
                                                     0
                                                     )) {
    FREE(pIpForwardTable);
    pIpForwardTable = (PMIB_IPFORWARDTABLE) MALLOC(dwSize);
    if (pIpForwardTable == NULL)
      return -1;
  }
  if (NO_ERROR != GetIpForwardTable(pIpForwardTable,
                                    &dwSize,
                                    0))
    return -1;

  proute = routes + NumEntries;

  NumEntries += pIpForwardTable->dwNumEntries;
  if ((routes == NULL) || (NumEntries > maxroutes)) {
    FREE(pIpForwardTable);
    return NumEntries;
  }

  pRow = pIpForwardTable->table;    
  for (i = 0;
       i < (int) pIpForwardTable->dwNumEntries;
       i++, proute ++, pRow ++) {
    /* libwinet_map_ifindex_to_ipv6ifindex */
    proute -> ifindex = pRow -> dwForwardIfIndex;
    proute -> metric = pRow -> dwForwardMetric1;
    proute -> proto = pRow -> dwForwardProto;
    proute -> plen = mask2len((unsigned char*)&(pRow -> dwForwardMask), 4);

    /* Note that the IPv4 addresses returned in GetIpForwardTable
     * entries are in network byte order
     */
    paddr = (struct sockaddr_in*)&(proute -> prefix);
    paddr -> sin_family = AF_INET;
    (paddr -> sin_addr).S_un.S_addr = pRow -> dwForwardDest;

    paddr = (struct sockaddr_in*)&(proute -> gateway);
    paddr -> sin_family = AF_INET;
    (paddr -> sin_addr).S_un.S_addr = pRow -> dwForwardNextHop;
  }
  FREE(pIpForwardTable);

#else
  PMIB_IPFORWARD_TABLE2 pIpForwardTable2;
  PMIB_IPFORWARD_ROW2 pRow2;

  /* From Windows Vista later, use GetIpForwardTable2 instead */
  if (NO_ERROR == GetIpForwardTable2(family,
                                     pIpForwardTable2
                                     0)) {

    NumEntries = pIpForwardTable2->dwNumEntries;
    if ((routes == NULL) || (NumEntries > maxroutes)) {
      FreeMibTable(pIpForwardTable2);
      return NumEntries;
    }

    proute = routes;
    NumEntries = pIpForwardTable2->dwNumEntries;
    pRow2 = pIpForwardTable2 -> Table;

    for (i = 0; i < NumEntries; i++, proute ++, pRow2 ++) {
      proute -> ifindex = pRow2 -> InterfaceIndex;
      proute -> metric = pRow2 -> Metric;
      proute -> proto = pRow2 -> Protocol;
      proute -> plen = (pRow2 -> DestinationPrefix).PrefixLength;
      memcpy(proute -> prefix,
             (pRow2 -> DestinationPrefix).DestinationPrefix,
             sizeof(SOCKADDR_INET)
             );
      memcpy(proute -> gateway,
             pRow2 -> NextHop,
             sizeof(SOCKADDR_INET)
             );
    }
    FreeMibTable(pIpForwardTable2);
  }
#endif

  return NumEntries;
}

int
cyginet_add_route_entry(const struct sockaddr *dest,
                        unsigned short plen,
                        const struct sockaddr *gate,
                        int ifindex,
                        unsigned int metric)
{
  return libwinet_edit_route_entry(dest, plen, gate, ifindex, metric, 1);
}

int
cyginet_delete_route_entry(const struct sockaddr *dest,
                           unsigned short plen,
                           const struct sockaddr *gate,
                           int ifindex,
                           unsigned int metric)
{
  return libwinet_edit_route_entry(dest, plen, gate, ifindex, metric, 2);
}

int
cyginet_update_route_entry(const struct sockaddr *dest,
                           unsigned short plen,
                           const struct sockaddr *gate,
                           int ifindex,
                           unsigned int metric)
{
  return libwinet_edit_route_entry(dest, plen, gate, ifindex, metric, 3);
}

/*
 * This function is used to read route socket to get changes of route
 * table, and return a struct rt_msghdr in the buffer. However I can't
 * find windows API to implement it.
 */
int
cyginet_read_route_socket(void *buffer, size_t size)
{
  /* TODO */
  return 0;
}

int cyginet_startup()
{
  WORD wVersionRequested;
  WSADATA wsaData;

  /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
  wVersionRequested = MAKEWORD(2, 2);
  return WSAStartup(wVersionRequested, &wsaData);
}

void cyginet_cleanup()
{
  WSACleanup();
}

/* The following functions are reserved. */
#if 0

static PMIB_IPFORWARDTABLE
libwinet_get_ipforward_table(int forder)
{
  DWORD dwSize = sizeof(MIB_IPFORWARDTABLE);
  PMIB_IPFORWARDTABLE pIpForwardTable;
  pIpForwardTable = (PMIB_IPFORWARDTABLE)MALLOC(dwSize);
  if (NULL == pIpForwardTable)
    return NULL;

  if (ERROR_INSUFFICIENT_BUFFER == GetIpForwardTable(pIpForwardTable,
                                                     &dwSize,
                                                     forder
                                                     )) {
    FREE(pIpForwardTable);
    pIpForwardTable = (PMIB_IPFORWARDTABLE) MALLOC(dwSize);
    if (pIpForwardTable == NULL)
      return NULL;
  }
  if (NO_ERROR == GetIpForwardTable(pIpForwardTable,
                                    &dwSize,
                                    forder))
    return pIpForwardTable;
  return NULL;  
}

static int
convert_ipv6_route_table2()
{
  const MAX_LINE_SIZE = 80;
  const char * command = "netsh interface ipv6 show route verbose";
  /* One example entry of netsh output
     Prefix            : fe80::1/128
     Interface 1       : Loopback Pseudo-Interface
     Gateway           : fe80::1
     Metric            : 4
     Publish           : no
     Type              : System
     Valid Lifetime    : infinite
     Preferred Lifetime: infinite
     Site Prefix Length: 0
  */

  FILE *output;
  char buffer[MAX_LINE_SIZE];
  int index = -1;
  size_t size;
  char *s, *p;
  int ifindex;
  MIB_IPFORWARD_ROW2 iprow;
  MIB_IPFORWARD_ROW2 *piprow = &iprow;

  output = popen (command, "r");
  if (!output)
    return -1;

  /* Ignore the first line */
  fgets(buffer, MAX_LINE_SIZE, output);

  memset(piprow, 0, sizeof(MIB_IPFORWARD_ROW2));
  piprow -> Protocol = MIB_IPPROTO_OTHER;

  /* Read the output until EOF */
  while (fgets(buffer, MAX_LINE_SIZE, output)) {

    if (('\n' == buffer[0]) || ('\r' == buffer[0]))
      continue;

    if (NULL == (s = strchr(buffer, ':')))
      break;

    *s ++ = 0;
    s ++;

    if (strncmp(buffer, "Prefix", 6) == 0) {

      index ++;

      if (NULL == (p = strchr(s, '/')))
        break;
      *p ++ = 0;

      if (WSAStringToAddress(s,
                             AF_INET6,
                             NULL,
                             (LPSOCKADDR)(&(piprow -> DestinationPrefix.Prefix)),
                             &size
                             ) == SOCKET_ERROR)
        break;

      piprow -> DestinationPrefix.PrefixLength = strtol(p, NULL, 10);
    }

    else if (strncmp(buffer, "Interface", 9) == 0) {
      ifindex = strtol(buffer + 9, NULL, 10);
      piprow -> InterfaceIndex = ifindex;
    }

    else if (strncmp(buffer, "Gateway", 7) == 0) {
      /* NextHop */
      /* Loopback: A value that specifies if the route is a loopback
         route (the gateway is on the local host). */
    }

    else if (strncmp(buffer, "Metric", 6) == 0)
      piprow -> Metric = strtol(s, NULL, 10);

    else if (strncmp(buffer, "Publish", 7) == 0)
      piprow -> Publish = (strncmp("yes", s, 3) == 0);

    else if (strncmp(buffer, "Type", 4) == 0) {

      if (strncmp("Manual", s, 6) == 0) {
        piprow -> Origin = NlroManual;
      }
      else if (strncmp("System", s, 6) == 0) {
        piprow -> Origin = NlroDHCP;
      }
      else if (strncmp("Autoconf", s, 8) == 0) {
        piprow -> Origin = Nlro6to4;
        piprow -> AutoconfigureAddress = 1;
      }
    }

    else if (strncmp(buffer, "Valid Lifetime", 14) == 0)
      piprow -> ValidLifetime = convert_time_from_string_to_ulong(s);

    else if (strncmp(buffer, "Preferred Lifetime", 18) == 0)
      piprow -> PreferredLifetime = convert_time_from_string_to_ulong(s);

    else if (strncmp(buffer, "Site Prefix Length", 18) == 0)
      piprow -> SitePrefixLength = (UCHAR)strtol(s, NULL, 10);

    else {
      /* Immortal: persistent entry */
      /* Age */
      break;
    }
  }

  /* Return the exit code of command */
  return pclose (output);
}

/*
 * Output format: struct rt_msghdr
 *
 *     rtm_msglen
 *
 *     rtm_type    =  RTM_GET
 *
 *     rtm_index   =  IfIndex for Ipv4
 *                    Ipv6IfIndex for Ipv6
 *
 *     rtm_flags   =  RTF_HOST:     All netmask bits are 1.
 *                    RTF_GATEWAY:  Destination is a gateway.
 *
 *     rtm_addrs   =  RTA_DST RTA_GATEWAY RTA_NETMASK
 *     rtm_errno      Set when failed.
 *
 * RTF_HOST:  Set when all netmask bits are 1
 *
 * RTF_GATEWAY: Set when gateway is not local address.
 *              dwForwardNextHop is not
 *
 */
static int
convert_ipv6_route_table_to_rtm(struct rt_msghdr *rtm,
                                int maxroutes)
{
  const MAX_LINE_SIZE = 80;
  const char * command = "netsh interface ipv6 show route verbose";
  /* One example entry of netsh output
     Prefix            : fe80::1/128
     Interface 1       : Loopback Pseudo-Interface
     Gateway           : fe80::1
     Metric            : 4
     Publish           : no
     Type              : System
     Valid Lifetime    : infinite
     Preferred Lifetime: infinite
     Site Prefix Length: 0
  */

  FILE *output;
  char buffer[MAX_LINE_SIZE];
  int index = -1;
  size_t size;
  char *s, *p;
  int ifindex;
  int start = 0;

  SOCKADDR *sa;
  int msg_len = sizeof(struct rt_msghdr) + 3 * sizeof(SOCKADDR);

  output = popen (command, "r");
  if (!output)
    return -1;

  /* Ignore the first line */
  fgets(buffer, MAX_LINE_SIZE, output);

  /* Read the output until EOF */
  while (fgets(buffer, MAX_LINE_SIZE, output)) {

    if (('\n' == buffer[0]) || ('\r' == buffer[0]))
      continue;

    if (NULL == (s = strchr(buffer, ':')))
      break;

    *s ++ = 0;
    s ++;

    if (strncmp(buffer, "Prefix", 6) == 0) {
      rtm -> rtm_msglen = msg_len;
      rtm -> rtm_version = RTM_VERSION;
      rtm -> rtm_type = RTM_GET;
      rtm -> rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK;
      sa = (SOCKADDR*)(rtm + 1);

      if (NULL == (p = strchr(s, '/')))
        break;
      *p ++ = 0;

      if (WSAStringToAddress(s,
                             AF_INET6,
                             NULL,
                             sa,
                             &size
                             ) == SOCKET_ERROR)
        break;
      // PrefixLength = strtol(p, NULL, 10);
      sa ++;
    }

    else if (strncmp(buffer, "Interface", 9) == 0)
      rtm -> rtm_index = strtol(buffer + 9, NULL, 10);

    else if (strncmp(buffer, "Gateway", 7) == 0) {
      /* NextHop */
      /* Loopback: A value that specifies if the route is a loopback
         route (the gateway is on the local host). */
      if (WSAStringToAddress(s,
                             AF_INET6,
                             NULL,
                             sa,
                             &size
                             ) == SOCKET_ERROR)
        break;
      sa ++;
      /* Netmask */
    }

    else if (strncmp(buffer, "Site Prefix Length", 18) == 0) {
      rtm = (struct rt_msghdr*)((void*)rtm + msg_len);
      start ++;
      if (start > maxroutes)
        break;
    }

    else {
      /* Immortal: persistent entry */
      /* Age */
    }
  }

  pclose (output);
  return start;
}

static int
libwinet_map_ifindex_to_ipv6ifindex(int ifindex)
{
  IP_ADAPTER_ADDRESSES *pAdaptAddr = NULL;
  IP_ADAPTER_ADDRESSES *pTmpAdaptAddr = NULL;
  DWORD dwRet = 0;
  DWORD dwSize = 0x10000;
  DWORD dwReturn = 0;
  DWORD Family = AF_UNSPEC;

  dwRet = GetAdaptersAddresses(Family,
                               GAA_FLAG_SKIP_ANYCAST            \
                               | GAA_FLAG_SKIP_MULTICAST        \
                               | GAA_FLAG_SKIP_DNS_SERVER       \
                               | GAA_FLAG_SKIP_FRIENDLY_NAME,
                               NULL,
                               pAdaptAddr,
                               &dwSize
                               );
  if (ERROR_BUFFER_OVERFLOW == dwRet) {
    FREE(pAdaptAddr);
    if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize)))
      return 0;
    dwRet = GetAdaptersAddresses(Family,
                                 GAA_FLAG_SKIP_ANYCAST            \
                                 | GAA_FLAG_SKIP_MULTICAST        \
                                 | GAA_FLAG_SKIP_DNS_SERVER       \
                                 | GAA_FLAG_SKIP_FRIENDLY_NAME,
                                 NULL,
                                 pAdaptAddr,
                                 &dwSize
                                 );
  }
  if (NO_ERROR == dwRet) {

    pTmpAdaptAddr = pAdaptAddr;

    while (pTmpAdaptAddr) {
      if (pTmpAdaptAddr -> IfIndex == ifindex) {
        dwReturn = pTmpAdaptAddr -> Ipv6IfIndex;
        break;
      }

      pTmpAdaptAddr = pTmpAdaptAddr->Next;
    }
  }

  FREE(pAdaptAddr);
  return dwReturn;
}

static ULONG
convert_time_from_string_to_ulong(const char * stime)
{
  char *s;
  long k;
  long result;
  if (strncmp(stime, "infinite", 8) == 0)
    return WSA_INFINITE;

  result = 0;
  s = (char*)stime;
  do {
    k = strtol(s, &s, 10);
    if (*s == 's') {
      result += k;
      s = NULL;
    }
    else if (*s == 'm') {
      result += 60 * k;
      s ++;
    }
    else if (*s == 'h') {
      result += 3600 * k;
      s ++;
    }
    else if (*s == 'd') {
      result += 3600 * 24 * k;
      s ++;
    }
    else s = NULL;
  } while (s);

  return result;
}

/*
 * It's another way to tell wireless netcard, query device status by
 * DeviceIoControl.
 *
 */
#if !defined OID_802_11_CONFIGURATION
#define OID_802_11_CONFIGURATION 0x0d010211
#endif

#if !defined IOCTL_NDIS_QUERY_GLOBAL_STATS
#define IOCTL_NDIS_QUERY_GLOBAL_STATS 0x00170002
#endif

static int
libwinet_is_wireless_device(const wchar_t *pszwAdapterName)
{
  const int MAX_DEV_NAME_LEN = 45;
  const int MAX_OUT_BUF_SIZE = 100;
  wchar_t DevName[MAX_DEV_NAME_LEN];
  HANDLE DevHand;
  unsigned int ErrNo;
  unsigned int Oid;
  unsigned char OutBuff[MAX_OUT_BUF_SIZE];
  unsigned long OutBytes;

  if (swprintf(DevName,
               MAX_DEV_NAME_LEN,
               L"\\\\.\\%ls",
               pszwAdapterName
               ) >= MAX_DEV_NAME_LEN)
    return -1;

  DevHand = CreateFileW(DevName,
                        GENERIC_READ,
                        FILE_SHARE_READ | FILE_SHARE_WRITE,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL
                        );

  if (DevHand == INVALID_HANDLE_VALUE) {
    ErrNo = GetLastError();
    return -1;
  }

  Oid = OID_802_11_CONFIGURATION;

  if (!DeviceIoControl(DevHand,
                       IOCTL_NDIS_QUERY_GLOBAL_STATS,
                       &Oid,
                       sizeof(Oid),
                       OutBuff,
                       sizeof(OutBuff),
                       &OutBytes,
                       NULL
                       )) {

    ErrNo = GetLastError();
    CloseHandle(DevHand);

    /* OID not supported. Device probably not wireless. */
    if ((ErrNo == ERROR_GEN_FAILURE)
        || (ErrNo == ERROR_INVALID_PARAMETER)
        || ErrNo == ERROR_NOT_SUPPORTED) {
      return 0;
    }

    /* DeviceIoControl() Error */
    return -1;
  }

  CloseHandle(DevHand);
  return 1;
}

static int
libwinet_ipv6_interfaces_forwards(int forward)
{
  const int MAX_BUFFER_SIZE = 80;
  char cmdbuf[MAX_BUFFER_SIZE];
  int result;
  
  struct if_nameindex * p;
  struct if_nameindex * ptr;
  if (NULL == (ptr = (struct if_nameindex *)if_nameindex()))
    return -1;

  p = ptr;
  while (p -> if_index) {
    if (snprintf(cmdbuf,
                 MAX_BUFFER_SIZE,
                 "ipv6 ifc %d %cforward",
                 p -> if_index,
                 forward ? ' ' : '-'
                 ) >= MAX_BUFFER_SIZE)
      break;
    if (libwinet_run_command(cmdbuf) != 0)
      break;
    p ++;
  }
  result = ! (p -> if_index);
  if_freenameindex(ptr);
  return result;
}

BOOL RouteLookup(SOCKADDR   *destAddr,
                 int         destLen,
                 SOCKADDR   *localAddr,
                 int         localLen)
{
  DWORD       dwBytes = 0;
  BOOL        bRet = TRUE;
  CHAR        szAddr[MAX_PATH] = {0};
  SOCKET      s = INVALID_SOCKET;


  if (INVALID_SOCKET == (s = socket(destAddr->sa_family,SOCK_DGRAM,0)))
    {
      SOCKETERR("socket");
      return FALSE;
    }

  if (SOCKET_ERROR == WSAIoctl(s,
                               SIO_ROUTING_INTERFACE_QUERY,
                               destAddr,
                               destLen,
                               localAddr,
                               localLen,
                               &dwBytes,
                               NULL,
                               NULL
                               ))
    {
      SOCKETERR("WSAIoctl");
      bRet = FALSE;
    }

  if (bRet)
    {
      dwBytes = sizeof(szAddr);

      ZeroMemory(szAddr,dwBytes);

      WSAAddressToStringA(destAddr,
                          (DWORD)destLen,
                          NULL,
                          szAddr,
                          &dwBytes
                          );

      dwBytes = sizeof(szAddr);

      ZeroMemory(szAddr,dwBytes);

      WSAAddressToStringA(localAddr,
                          (DWORD)localLen,
                          NULL,
                          szAddr,
                          &dwBytes
                          );
    }

  CLOSESOCKET(s);

  return bRet;
}

DWORD GetInterfaceIndexForAddress(SOCKADDR *pAddr)
{
  IP_ADAPTER_UNICAST_ADDRESS *pTmpUniAddr = NULL;
  IP_ADAPTER_ADDRESSES *pAdaptAddr = NULL;
  IP_ADAPTER_ADDRESSES *pTmpAdaptAddr = NULL;
  BOOL bFound = FALSE;
  DWORD dwRet = 0;
  DWORD dwReturn = (DWORD) SOCKET_ERROR;
  DWORD dwSize = 0x10000;
  DWORD Family = AF_UNSPEC;

  switch (pAddr->sa_family) {
  case AF_INET:
    Family = AF_INET;
    break;
  case AF_INET6:
    Family = AF_INET6;
    break;
  default:
    WSASetLastError(WSAEAFNOSUPPORT);
    break;
  }
  if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize)))
    return -1;

  dwRet = GetAdaptersAddresses(Family,
                               GAA_FLAG_SKIP_ANYCAST \
                               | GAA_FLAG_SKIP_MULTICAST \
                               |GAA_FLAG_SKIP_DNS_SERVER,
                               NULL,
                               pAdaptAddr,
                               &dwSize
                               );
  if (ERROR_BUFFER_OVERFLOW == dwRet) {
    FREE(pAdaptAddr);
    if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize)))
      return -1;
    dwRet = GetAdaptersAddresses(Family,
                                 GAA_FLAG_SKIP_ANYCAST \
                                 | GAA_FLAG_SKIP_MULTICAST \
                                 |GAA_FLAG_SKIP_DNS_SERVER,
                                 NULL,
                                 pAdaptAddr,
                                 &dwSize
                                 );
  }
  if (NO_ERROR == dwRet) {

    pTmpAdaptAddr = pAdaptAddr;

    while (pTmpAdaptAddr) {

      //look at each IP_ADAPTER_UNICAST_ADDRESS node
      pTmpUniAddr = pTmpAdaptAddr->FirstUnicastAddress;

      while (pTmpUniAddr) {

        if (AF_INET == pTmpUniAddr->Address.lpSockaddr->sa_family) {

          /* IN4_ADDR_EQUAL */
          if (memcmp(&((SOCKADDR_IN*)pAddr)->sin_addr,
                     &((SOCKADDR_IN*)pTmpUniAddr->Address.lpSockaddr)->sin_addr,
                     sizeof(SOCKADDR_IN)
                     ) == 0)
            {
              dwReturn = pTmpAdaptAddr->IfIndex;
              bFound = TRUE;

              break;
            }

        }
        else {
          /* IN6_ADDR_EQUAL */
          if (memcmp(&((SOCKADDR_IN6*)pAddr)->sin6_addr,
                     &((SOCKADDR_IN6*)pTmpUniAddr->Address.lpSockaddr)->sin6_addr,
                     sizeof(SOCKADDR_IN6)
                     ) == 0)
            {
              dwReturn = pTmpAdaptAddr->Ipv6IfIndex;
              bFound = TRUE;

              break;
            }
        }

        pTmpUniAddr = pTmpUniAddr->Next;

      }

      if (bFound)
        break;

      pTmpAdaptAddr = pTmpAdaptAddr->Next;

    }

  }

  FREE(pAdaptAddr);
  return dwReturn;
}

VOID PrintAllInterfaces()
{
  IP_ADAPTER_ADDRESSES *pAdaptAddr = NULL;
  IP_ADAPTER_ADDRESSES *pTmpAdaptAddr = NULL;
  DWORD dwRet = 0;
  DWORD dwSize = 0x10000;
  DWORD Family = AF_UNSPEC;

  if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize))) {
    printf("Memory error.\n");
    return ;
  }
  dwRet = GetAdaptersAddresses(Family,
                               GAA_FLAG_SKIP_ANYCAST \
                               | GAA_FLAG_SKIP_MULTICAST \
                               |GAA_FLAG_SKIP_DNS_SERVER,
                               NULL,
                               pAdaptAddr,
                               &dwSize
                               );
  if (ERROR_BUFFER_OVERFLOW == dwRet) {
    FREE(pAdaptAddr);
    if (NULL == (pAdaptAddr = (IP_ADAPTER_ADDRESSES*)MALLOC(dwSize))) {
      printf("Memory error.\n");
      return ;
    }
    dwRet = GetAdaptersAddresses(Family,
                                 GAA_FLAG_SKIP_ANYCAST \
                                 | GAA_FLAG_SKIP_MULTICAST \
                                 |GAA_FLAG_SKIP_DNS_SERVER,
                                 NULL,
                                 pAdaptAddr,
                                 &dwSize
                                 );
  }
  if (NO_ERROR == dwRet) {

    pTmpAdaptAddr = pAdaptAddr;

    while (pTmpAdaptAddr) {
      printf("If6Index:\t %ld\n", pTmpAdaptAddr -> Ipv6IfIndex);
      printf("If4Index:\t %ld\n", pTmpAdaptAddr -> IfIndex);
      printf("Friendly Name:\t %S\n", pTmpAdaptAddr -> FriendlyName);
      printf("Adapter Name:\t %s\n", pTmpAdaptAddr -> AdapterName);
      printf("IfType:\t %ld\n", pTmpAdaptAddr -> IfType);
      printf("Oper Status:\t %d\n", pTmpAdaptAddr -> OperStatus);
      printf("\n\n");

      pTmpAdaptAddr = pTmpAdaptAddr->Next;
    }
  }

  else
    printf("GetAdaptersAddresses failed.\n");

  FREE(pAdaptAddr);
}

void WaitForNetworkChnages()
{
  WSAQUERYSET querySet = {0};
  querySet.dwSize = sizeof(WSAQUERYSET);
  querySet.dwNameSpace = NS_NLA;

  HANDLE LookupHandle = NULL;
  WSALookupServiceBegin(&querySet, LUP_RETURN_ALL, &LookupHandle);
  DWORD BytesReturned = 0;
  WSANSPIoctl(LookupHandle,
              SIO_NSP_NOTIFY_CHANGE,
              NULL,
              0,
              NULL,
              0,
              &BytesReturned,
              NULL
              );
  WSALookupServiceEnd(LookupHandle);
}

DWORD GetConnectedNetworks()
{
  WSAQUERYSET qsRestrictions;
  DWORD dwControlFlags;
  HANDLE hLookup;
  DWORD dwCount = 0;

  ZeroMemory(&qsRestrictions, sizeof(WSAQUERYSET));
  qsRestrictions.dwSize = sizeof(WSAQUERYSET);
  qsRestrictions.dwNameSpace = NS_ALL;
  dwControlFlags = LUP_RETURN_ALL;

  int result = WSALookupServiceBegin(&qsRestrictions,
    dwControlFlags, &hLookup);

  DWORD dwBufferLength;
  WSAQUERYSET qsResult;
  while (0 == result)
  {
    ZeroMemory(&qsResult, sizeof(WSAQUERYSET));
    result = WSALookupServiceNext(hLookup,
                                  LUP_RETURN_NAME,
                                  &dwBufferLength,
                                  &qsResult
                                  );
    dwCount ++;
  }

  result = WSALookupServiceEnd(hLookup);
  return dwCount;
}

#endif  /* Unused */

/* ------------------------------------------------------------- */
/*                                                               */
/* The following functions are used to test or verify something. */
/*                                                               */
/* ------------------------------------------------------------- */
#ifdef TEST_CYGINET

static void
runTestCases()
{
  printf("\n\nTest getifaddrs works in the Cygwin:\n\n");
  {
    struct ifaddrs * piftable, *pif;
    getifaddrs(&piftable);
    for (pif = piftable; pif != NULL; pif = pif -> ifa_next)
      printf("Iterface name is %s\n", pif -> ifa_name);
    freeifaddrs(piftable);
  }

  printf("\n\nTest if_indexname works in the Cygwin:\n\n");
  {
    struct if_nameindex * ptr = (struct if_nameindex *)if_nameindex();
    if (ptr) {
      struct if_nameindex * p = ptr;
      while (p -> if_index) {
        printf("%d\t\t%s\n", p -> if_index, p -> if_name);
        p ++;
      }
      if_freenameindex(ptr);
    }
  }

  printf("\n\nTest if_indextoname works in the Cygwin:\n\n");
  {
    CHAR ifname[256];
    if (if_indextoname(1, ifname))
      printf("Interface Index 1: %s\n", ifname);
    else
      printf("if_indextoname failed\n");
  }

#if _WIN32_WINNT < _WIN32_WINNT_VISTA

  printf("\n\nTest libwinet_dump_ipv6_route_table:\n\n");
  {
    struct cyginet_route routes[100];
    memset(routes, 0, sizeof(struct cyginet_route) * 100);
    int n = libwinet_dump_ipv6_route_table(routes, 100);
    printf("Get route numbers: %d\n", n);
  }

  printf("\n\nTest libwinet_run_command:\n\n");
  {
    printf("ls command return %d\n", libwinet_run_command("ls"));
  }

  printf("\n\nTest libwinet_is_wireless_interface:\n\n");
  {
    struct if_nameindex * ptr = (struct if_nameindex *)if_nameindex();
    if (ptr) {
      struct if_nameindex * p = ptr;
      while (p -> if_index) {
        printf("%s is wireless netcard: %d\n",
               p -> if_name,
               libwinet_is_wireless_interface(p -> if_name)
               );
        p ++;
      }
      if_freenameindex(ptr);
    }
  }

#endif  /* _WIN32_WINNT < _WIN32_WINNT_VISTA */

  printf("\n\nTest libwinet_get_loopback_index:\n\n");
  {
    printf("Ipv4 loopback ifindex is %d\n",
           libwinet_get_loopback_index(AF_INET)
           );
    printf("Ipv6 loopback ifindex is %d\n",
           libwinet_get_loopback_index(AF_INET6)
           );
  }

  printf("\n\nTest cyginet_set_ipv6_forwards:\n\n");
  {
    printf("cyginet_set_ipv6_forwards(1) return %d\n",
           cyginet_set_ipv6_forwards(1)
           );
    printf("cyginet_set_ipv6_forwards(0) return %d\n",
           cyginet_set_ipv6_forwards(0)
           );
  }

  printf("\n\nTest cyginet_set_icmp6_redirect_accept:\n\n");
  {
    printf("cyginet_set_icmp6_redirect_accept(0) return %d\n",
           cyginet_set_icmp6_redirect_accept(0)
           );
    printf("cyginet_set_icmp6_redirect_accept(1) return %d\n",
           cyginet_set_icmp6_redirect_accept(1)
           );
  }

  printf("\n\nTest cyginet_interface_wireless:\n\n");
  {
    struct if_nameindex * ptr = (struct if_nameindex *)if_nameindex();
    if (ptr) {
      struct if_nameindex * p = ptr;
      int n;
      while (p -> if_index) {
        n = cyginet_interface_wireless(p -> if_name, p -> if_index);
        printf("%s is wireless netcard: %d\n", p -> if_name, n);
        p ++;
      }
      if_freenameindex(ptr);
    }
  }

  printf("\n\nTest cyginet_interface_mtu:\n\n");
  {
    struct if_nameindex * ptr = (struct if_nameindex *)if_nameindex();
    if (ptr) {
      struct if_nameindex * p = ptr;
      int n;
      while (p -> if_index) {
        n = cyginet_interface_mtu(p -> if_name, p -> if_index);
        printf("mtu of %s is : %d\n", p -> if_name, n);
        p ++;
      }
      if_freenameindex(ptr);
    }
  }

  printf("\n\nTest cyginet_interface_operational:\n\n");
  {
    struct if_nameindex * ptr = (struct if_nameindex *)if_nameindex();
    if (ptr) {
      struct if_nameindex * p = ptr;
      int n;
      while (p -> if_index) {
        n = cyginet_interface_operational(p -> if_name, p -> if_index);
        printf("%s is up: %d\n", p -> if_name, n);
        p ++;
      }
      if_freenameindex(ptr);
    }
  }

  printf("\n\nTest cyginet_interface_ipv4:\n\n");
  {
    struct sockaddr_in sa;
    struct if_nameindex * ptr = (struct if_nameindex *)if_nameindex();
    if (ptr) {
      struct if_nameindex * p = ptr;
      int n;
      while (p -> if_index) {
        memset(&sa, 0, sizeof(sa));
        n = cyginet_interface_ipv4(p -> if_name,
                                   p -> if_index,
                                   (unsigned char*)&sa
                                   );
        printf("get ipv4 from %s: %d\n", p -> if_name, n);
        p ++;
      }
      if_freenameindex(ptr);
    }
  }

  printf("\n\nTest cyginet_interface_sdl:\n\n");
  {
    struct sockaddr_dl sdl;
    struct if_nameindex * ptr = (struct if_nameindex *)if_nameindex();
    if (ptr) {
      struct if_nameindex * p = ptr;
      int n;
      while (p -> if_index) {
        memset(&sdl, 0, sizeof(struct sockaddr_dl));
        n = cyginet_interface_sdl(&sdl, p -> if_name);
        printf("get sdl from %s: %d\n", p -> if_name, n);
        if (0 == n) {
          printf("sdl_len is %d\n", sdl.sdl_len);
          printf("sdl_nlen is %d\n", sdl.sdl_nlen);
          printf("sdl_alen is %d\n", sdl.sdl_alen);
        }
        p ++;
      }
      if_freenameindex(ptr);
    }
  }

  printf("\n\nTest libwinet_monitor_route_thread_proc:\n\n");
  do {
    int mypipes[2];
    int n;
    char *cmd1 = "netsh interface ipv6 add route 3ffe::/16 1 fe80::1";
    char *cmd2 = "netsh interface ipv6 delete route 3ffe::/16 1 fe80::1";
    // char *cmd3 = "netsh interface ipv6 update route 3ffe::/16 1 fe80::1";
    if (-1 == pipe(mypipes))
      break;
    n = cyginet_start_monitor_route_changes(mypipes[1]);
    if (n == 0) {
      char ch = ' ';
      printf("Run command: %s\n", cmd1);      
      libwinet_run_command(cmd1);
      Sleep(100);
      if (read(mypipes[0], &ch, 1) == 1)
        printf("Event number is %c\n", ch);

      printf("Run command: %s\n", cmd2);      
      libwinet_run_command(cmd2);
      Sleep(100);
      if (read(mypipes[0], &ch, 1) == 1)
        printf("Event number is %c\n", ch);
      
      cyginet_stop_monitor_route_changes();
    }
    close(mypipes[0]);
    close(mypipes[1]);
  } while(0);

  printf("\n\nTest select and pipe with \n");
  printf("\tcyginet_start_monitor_route_changes\n");
  printf("\tcyginet_stop_monitor_route_changes\n\n");
  do {
    break;                      /* We don't run it beacuse it need
                                   manual intervention. */
    int mypipes[2];
    int n;
    fd_set readfds;
    char buf[16];
    if (-1 == pipe(mypipes))
      break;
    if (fcntl(mypipes[0], F_SETFL, O_NONBLOCK) < 0)
      printf("Error set NONBLOCK\n");
    FD_ZERO(&readfds);
    n = cyginet_start_monitor_route_changes(mypipes[1]);
    if (n == 0) {
      FD_SET(mypipes[0], &readfds);
      printf("Please disable/enable your netcard or plug/unplug "
             "netting wire so as to change route table.\n");
      fflush(NULL);
      printf("select return: %d\n",
             select(FD_SETSIZE, &readfds, NULL, NULL, NULL)
             );
      memset(buf, 0, 16);
      printf("read pipe, return %d\n",
             read(mypipes[0], buf, 16));
      printf("Event number is %s\n",buf);             
      cyginet_stop_monitor_route_changes();
    }
    close(mypipes[0]);
    close(mypipes[1]);
  } while(0);

  printf("\n\nTest cyginet_dump_route_table:\n\n");
  do {
    #define MAX_ROUTES 120
    struct cyginet_route routes[MAX_ROUTES];
    memset(routes, 0, sizeof(struct cyginet_route) * MAX_ROUTES);
    int n = cyginet_dump_route_table(routes, MAX_ROUTES);
    printf("Get route numbers: %d\n", n);
  } while (0);

  printf("\n\nTest libwinet_edit_route_entry:\n\n");
  do {
    SOCKADDR *dest; 
    SOCKADDR *gate;
    SOCKADDR_IN dest4 = { AF_INET, 0, {{{ INADDR_ANY }}}, {0} };
    SOCKADDR_IN gate4 = { AF_INET, 0, {{{ INADDR_ANY }}}, {0} };
    SOCKADDR_IN6 dest6 = {
      AF_INET6,
      0,
      0,
      {{IN6ADDR_ANY_INIT}}
    };
    SOCKADDR_IN6 gate6 = {
      AF_INET6,
      0,
      0,
      {{IN6ADDR_ANY_INIT}}
    };
    int prefix;
    unsigned int metric;
    int ifindex;
    int n;

    if (inet_pton(AF_INET, "192.168.128.119", &dest4.sin_addr) != 1)
      break;
    if (inet_pton(AF_INET, "192.168.128.200", &gate4.sin_addr) != 1)
      break;
    ifindex = 2;
    metric = 3;
    prefix = 32;
    
    dest = (SOCKADDR*)&dest4;
    gate = (SOCKADDR*)&gate4;
    n = libwinet_edit_route_entry(dest,
                                  prefix,
                                  gate,
                                  ifindex,
                                  metric,
                                  RTM_ADD
                                  );
    printf("Add Ipv4 route return %d\n", n);
    n = libwinet_edit_route_entry(dest,
                                  prefix,
                                  gate,
                                  ifindex,
                                  metric,
                                  RTM_CHANGE
                                  );
    printf("Change Ipv4 route return %d\n", n);
    metric = 15;
    n = libwinet_edit_route_entry(dest,
                                  prefix,
                                  gate,
                                  ifindex,
                                  metric,
                                  RTM_DELETE
                                  );
    printf("Delete Ipv4 route return %d\n", n);

    if (inet_pton(AF_INET6, "3ffe::", &dest6.sin6_addr) != 1)
      break;
    if (inet_pton(AF_INET6, "fe80::1", &gate6.sin6_addr) != 1)
      break;
    prefix = 112;
    metric = 1200;
    ifindex = 1;
    dest = (SOCKADDR*)&dest6;
    gate = (SOCKADDR*)&gate6;
    n = libwinet_edit_route_entry(dest,
                                  prefix,
                                  gate,
                                  ifindex,
                                  metric,
                                  RTM_ADD
                                  );
    printf("Add Ipv6 route return %d\n", n);
    metric = 1100;
    n = libwinet_edit_route_entry(dest,
                                  prefix,
                                  gate,
                                  ifindex,
                                  metric,
                                  RTM_CHANGE
                                  );
    printf("Change Ipv6 route return %d\n", n);
    n = libwinet_edit_route_entry(dest,
                                  prefix,
                                  gate,
                                  ifindex,
                                  metric,
                                  RTM_DELETE
                                  );
    printf("Delete Ipv6 route return %d\n", n);

  } while(0);
}

int main(int argc, char* argv[])
{
  WORD wVersionRequested;
  WSADATA wsaData;
  int err;

  /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
  wVersionRequested = MAKEWORD(2, 2);

  err = WSAStartup(wVersionRequested, &wsaData);
  if (err != 0) {
    /* Tell the user that we could not find a usable */
    /* Winsock DLL.                                  */
    printf("WSAStartup failed with error: %d\n", err);
    return 1;
  }

  /* Confirm that the WinSock DLL supports 2.2.*/
  /* Note that if the DLL supports versions greater    */
  /* than 2.2 in addition to 2.2, it will still return */
  /* 2.2 in wVersion since that is the version we      */
  /* requested.                                        */

  if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
    /* Tell the user that we could not find a usable */
    /* WinSock DLL.                                  */
    printf("Could not find a usable version of Winsock.dll\n");
    WSACleanup();
    return 1;
  }
  else
    printf("The Winsock 2.2 dll was found okay\n");

  runTestCases();

  WSACleanup();
  return 0;
}

#endif  /* TEST_CYGINET */