//=============================================================================
// Copyright (c) 2001 Microsoft Corporation
// Abstract:
//      This module implements IPv6 configuration commands.
//
// This code is based off ipv6.c from Rich Draves
//=============================================================================

#include "precomp.h"
#pragma hdrstop

HANDLE Handle = INVALID_HANDLE_VALUE;
BOOL AdminAccess = TRUE;

#define IPv6_MINIMUM_MTU  1280

DWORD
SetString(
    IN  HKEY    hKey,
    IN  LPCTSTR lpName,
    IN  PWCHAR  pwcValue
    );

PWCHAR
FormatTime(
    IN DWORD dwLife,
    OUT PWCHAR pwszLife
    )
{
    DWORD dwDays, dwHours, dwMinutes;
    PWCHAR pwszNext = pwszLife;
    
    if (dwLife == INFINITE_LIFETIME) {
        swprintf(pwszNext, L"%s", TOKEN_VALUE_INFINITE);
        return pwszLife;
    }

    if (dwLife < MINUTES)
        goto FormatSeconds;

    if (dwLife < HOURS)
        goto FormatMinutes;

    if (dwLife < DAYS)
        goto FormatHours;

    dwDays = dwLife / DAYS;
    pwszNext += swprintf(pwszNext, L"%ud", dwDays);
    dwLife -= dwDays * DAYS;

  FormatHours:
    dwHours = dwLife / HOURS;
    if (dwHours != 0)
        pwszNext += swprintf(pwszNext, L"%uh", dwHours);
    dwLife -= dwHours * HOURS;

  FormatMinutes:
    dwMinutes = dwLife / MINUTES;
    if (dwMinutes != 0)
        pwszNext += swprintf(pwszNext, L"%um", dwMinutes);
    dwLife -= dwMinutes * MINUTES;

    if (dwLife == 0) {
        return pwszLife;
    }
    
  FormatSeconds:
    swprintf(pwszNext, L"%us", dwLife);
    return pwszLife;
}

DWORD
OpenIPv6(
    VOID
    )
{
    WSADATA wsaData;
    BOOL bInstalled;
    DWORD dwErr = NO_ERROR;

    if (Handle != INVALID_HANDLE_VALUE) {
        return NO_ERROR;
    }

    dwErr = IsIpv6Installed(&bInstalled);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }
    if (!bInstalled) {
        DisplayMessage(g_hModule, EMSG_PROTO_NOT_INSTALLED);
        return ERROR_SUPPRESS_OUTPUT;
    }

    //
    // We initialize Winsock just so we can have access
    // to WSAStringToAddress and WSAAddressToString.
    //
    if (WSAStartup(MAKEWORD(2, 0), &wsaData)) {
        return WSAGetLastError();
    }

    //
    // First request write access.
    // This will fail if the process does not have local Administrator privs.
    //
    Handle = CreateFileW(WIN_IPV6_DEVICE_NAME,
                         GENERIC_WRITE,
                         FILE_SHARE_READ | FILE_SHARE_WRITE,
                         NULL,   // security attributes
                         OPEN_EXISTING,
                         0,      // flags & attributes
                         NULL);  // template file
    if (Handle == INVALID_HANDLE_VALUE) {
        //
        // We will not have Administrator access to the stack.
        //
        AdminAccess = FALSE;

        Handle = CreateFileW(WIN_IPV6_DEVICE_NAME,
                             0,
                             FILE_SHARE_READ | FILE_SHARE_WRITE,
                             NULL,   // security attributes
                             OPEN_EXISTING,
                             0,      // flags & attributes
                             NULL);  // template file
        if (Handle == INVALID_HANDLE_VALUE) {
            return GetLastError();
        }
    }

    return NO_ERROR;
}

//////////////////////////////////////////////////////////////////////////////
// Generic interface-related functions
//////////////////////////////////////////////////////////////////////////////

IPV6_INFO_INTERFACE *
GetInterfaceByIpv6IfIndex(
    IN DWORD dwIfIndex
    )
{
    IPV6_QUERY_INTERFACE Query;
    IPV6_INFO_INTERFACE *IF;
    DWORD dwInfoSize, dwBytesReturned;

    Query.Index = dwIfIndex;

    dwInfoSize = sizeof *IF + 2 * MAX_LINK_LAYER_ADDRESS_LENGTH;
    IF = (IPV6_INFO_INTERFACE *) MALLOC(dwInfoSize);
    if (IF == NULL) {
        return NULL;
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_INTERFACE,
                         &Query, sizeof Query,
                         IF, dwInfoSize, &dwBytesReturned,
                         NULL)) {
        FREE(IF);
        return NULL;
    }

    if ((dwBytesReturned < sizeof *IF) ||
        (IF->Length < sizeof *IF) ||
        (dwBytesReturned != IF->Length +
         ((IF->LocalLinkLayerAddress != 0) ?
          IF->LinkLayerAddressLength : 0) +
         ((IF->RemoteLinkLayerAddress != 0) ?
          IF->LinkLayerAddressLength : 0))) {

        FREE(IF);
        return NULL;
    }

    return IF;
}

DWORD
GetInterfaceByFriendlyName(
    IN PWCHAR pwszFriendlyName,
    IN IP_ADAPTER_ADDRESSES *pAdapterInfo,
    IN BOOL bPersistent,
    OUT IPV6_INFO_INTERFACE **pIF
    )
{
    DWORD dwIfIndex;
    DWORD dwErr;

    //
    // TODO: we cannot yet query an interface by GUID, so we can't
    // yet work for a persistent interface which is not currently present.
    //

    dwErr = MapFriendlyNameToIpv6IfIndex(pwszFriendlyName, pAdapterInfo,
                                         &dwIfIndex);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    *pIF = GetInterfaceByIpv6IfIndex(dwIfIndex);
    return (*pIF)? NO_ERROR : ERROR_NOT_FOUND;
}

DWORD
MyGetAdaptersInfo(
    OUT PIP_ADAPTER_ADDRESSES *ppAdapterInfo
    )
{
    IP_ADAPTER_ADDRESSES *pAdapterInfo;
    DWORD dwErr, BufLen = 0;
    DWORD Flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST;

    dwErr = GetAdaptersAddresses(AF_INET6, Flags, NULL, NULL, &BufLen);
    if (dwErr != ERROR_BUFFER_OVERFLOW) {
        return dwErr;
    }
    pAdapterInfo = (IP_ADAPTER_ADDRESSES *) MALLOC(BufLen);
    if (pAdapterInfo == NULL) {
        return GetLastError();
    }
    dwErr = GetAdaptersAddresses(AF_INET6, Flags, NULL, pAdapterInfo, &BufLen);
    if (dwErr != NO_ERROR) {
        FREE(pAdapterInfo);
        return dwErr;
    }

    *ppAdapterInfo = pAdapterInfo;
    return NO_ERROR;
}

DWORD
ForEachPersistentInterface(
    IN DWORD (*pfnFunc)(IPV6_INFO_INTERFACE *,PIP_ADAPTER_ADDRESSES,DWORD,FORMAT),
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format
    )
{
    IPV6_PERSISTENT_QUERY_INTERFACE Query;
    IPV6_INFO_INTERFACE *IF;
    DWORD dwInfoSize, dwBytesReturned;
    DWORD dwCount = 0;

    dwInfoSize = sizeof *IF + 2 * MAX_LINK_LAYER_ADDRESS_LENGTH;
    IF = (IPV6_INFO_INTERFACE *) MALLOC(dwInfoSize);
    if (IF == NULL) {
        return 0;
    }

    for (Query.RegistryIndex = 0; ; Query.RegistryIndex++) {
        if (!DeviceIoControl(Handle, 
                             IOCTL_IPV6_PERSISTENT_QUERY_INTERFACE,
                             &Query, sizeof Query,
                             IF, dwInfoSize, &dwBytesReturned,
                             NULL)) {
            break;
        }


        if ((dwBytesReturned < sizeof *IF) ||
            (IF->Length < sizeof *IF) ||
            (dwBytesReturned != IF->Length +
             ((IF->LocalLinkLayerAddress != 0) ?
              IF->LinkLayerAddressLength : 0) +
             ((IF->RemoteLinkLayerAddress != 0) ?
              IF->LinkLayerAddressLength : 0))) {

            break;
        }

        if ((*pfnFunc)(IF, pAdapterInfo, dwCount, Format) == NO_ERROR) {
            dwCount++;
        }
    }

    FREE(IF);

    return dwCount;
}

DWORD
ForEachInterface(
    IN DWORD (*pfnFunc)(IPV6_INFO_INTERFACE *,PIP_ADAPTER_ADDRESSES,DWORD,FORMAT),
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    IPV6_QUERY_INTERFACE Query;
    IPV6_INFO_INTERFACE *IF;
    DWORD dwInfoSize, dwBytesReturned;
    DWORD dwCount = 0;

    if (bPersistent) {
        return ForEachPersistentInterface(pfnFunc, pAdapterInfo, Format);
    }

    dwInfoSize = sizeof *IF + 2 * MAX_LINK_LAYER_ADDRESS_LENGTH;
    IF = (IPV6_INFO_INTERFACE *) MALLOC(dwInfoSize);
    if (IF == NULL) {
        return 0;
    }

    Query.Index = (u_int) -1;

    for (;;) {
        if (!DeviceIoControl(Handle, 
                             IOCTL_IPV6_QUERY_INTERFACE,
                             &Query, sizeof Query,
                             IF, dwInfoSize, &dwBytesReturned,
                             NULL)) {
            break;
        }

        if (Query.Index != (u_int) -1) {

            if ((dwBytesReturned < sizeof *IF) ||
                (IF->Length < sizeof *IF) ||
                (dwBytesReturned != IF->Length +
                 ((IF->LocalLinkLayerAddress != 0) ?
                  IF->LinkLayerAddressLength : 0) +
                 ((IF->RemoteLinkLayerAddress != 0) ?
                  IF->LinkLayerAddressLength : 0))) {

                break;
            }

            if ((*pfnFunc)(IF, pAdapterInfo, dwCount, Format) == NO_ERROR) {
                dwCount++;
            }
        }
        else {
            if (dwBytesReturned != sizeof IF->Next) {
                break;
            }
        }

        if (IF->Next.Index == (u_int) -1)
            break;
        Query = IF->Next;
    }

    FREE(IF);

    return dwCount;
}


//////////////////////////////////////////////////////////////////////////////
// Site prefix table functions
//////////////////////////////////////////////////////////////////////////////

DWORD
ForEachSitePrefix(
    IN DWORD (*pfnFunc)(IPV6_INFO_SITE_PREFIX *,FORMAT,DWORD,IP_ADAPTER_ADDRESSES *), 
    IN FORMAT Format,
    IN IP_ADAPTER_ADDRESSES *pAdapterInfo
    )
{
    IPV6_QUERY_SITE_PREFIX Query, NextQuery;
    IPV6_INFO_SITE_PREFIX SPE;
    DWORD dwBytesReturned;
    DWORD dwCount = 0;

    NextQuery.IF.Index = 0;

    for (;;) {
        Query = NextQuery;

        if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_SITE_PREFIX,
                             &Query, sizeof Query,
                             &SPE, sizeof SPE, &dwBytesReturned,
                             NULL)) {
            break;
        }

        NextQuery = SPE.Query;

        if (Query.IF.Index != 0) {

            SPE.Query = Query;
            if ((*pfnFunc)(&SPE, Format, dwCount, pAdapterInfo) == NO_ERROR) {
                dwCount++;
            }
        }

        if (NextQuery.IF.Index == 0) {
            break;
        }
    }

    return dwCount;
}

DWORD
PrintSitePrefix(
    IN IPV6_INFO_SITE_PREFIX *SPE, 
    IN FORMAT Format,
    IN DWORD dwCount,
    IN IP_ADAPTER_ADDRESSES *pAdapterInfo
    )
{
    DWORD dwErr;
    WCHAR *FriendlyName;
    WCHAR wszValid[64];
    
    dwErr = MapIpv6IfIndexToFriendlyName(SPE->Query.IF.Index, pAdapterInfo,
                                         &FriendlyName);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    FormatTime(SPE->ValidLifetime, wszValid);
    
    if (Format == FORMAT_DUMP) {
        DisplayMessageT(DMP_IPV6_ADD_SITEPREFIX);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_PREFIX,
                       FormatIPv6Prefix(&SPE->Query.Prefix, 
                                        SPE->Query.PrefixLength));
        DisplayMessageT(DMP_QUOTED_STRING_ARG, TOKEN_INTERFACE,
                        FriendlyName);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_LIFETIME, wszValid);
        DisplayMessage(g_hModule, MSG_NEWLINE);
    } else {
        if (!dwCount) {
            DisplayMessage(g_hModule, MSG_IPV6_SITE_PREFIX_HDR);
        }

        DisplayMessage(g_hModule, MSG_IPV6_SITE_PREFIX,
            FormatIPv6Prefix(&SPE->Query.Prefix, SPE->Query.PrefixLength),
            SPE->ValidLifetime, FriendlyName);
            // wszValid, FriendlyName);
    }

    return NO_ERROR;
}

DWORD
QuerySitePrefixTable(
    IN FORMAT Format
    )
{
    DWORD dwErr, dwCount = 0;
    IP_ADAPTER_ADDRESSES *pAdapterInfo;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != ERROR_NO_DATA) {
        if (dwErr == NO_ERROR) {
            dwCount = ForEachSitePrefix(PrintSitePrefix, Format, pAdapterInfo);
            FREE(pAdapterInfo);
        } else if (dwErr == ERROR_NO_DATA) {
            dwErr = NO_ERROR;
        }
    }
    if (!dwCount && (Format != FORMAT_DUMP)) {
        DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
    }

    return dwErr;
}

//////////////////////////////////////////////////////////////////////////////
// General global parameters functions
//////////////////////////////////////////////////////////////////////////////

DWORD
QueryGlobalParameters(
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    IPV6_GLOBAL_PARAMETERS Params;
    DWORD dwBytesReturned, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (Format != FORMAT_DUMP) {
        // DisplayMessage(g_hModule, (bPersistent)? MSG_PERSISTENT : MSG_ACTIVE);
    }

    if (!DeviceIoControl(Handle, 
                         (bPersistent)? IOCTL_IPV6_PERSISTENT_QUERY_GLOBAL_PARAMETERS : IOCTL_IPV6_QUERY_GLOBAL_PARAMETERS,
                         NULL, 0,
                         &Params, sizeof Params, &dwBytesReturned, NULL) ||
        (dwBytesReturned != sizeof Params)) {
        return GetLastError(); 
    }

    if (Format == FORMAT_DUMP) {
        DisplayMessageT(DMP_IPV6_SET_GLOBAL);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_DEFAULTCURHOPLIMIT,
                        Params.DefaultCurHopLimit);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_NEIGHBORCACHELIMIT,
                        Params.NeighborCacheLimit);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_DESTINATIONCACHELIMIT,
                        Params.RouteCacheLimit);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_REASSEMBLYLIMIT,
                        Params.ReassemblyLimit);
        DisplayMessage(g_hModule, MSG_NEWLINE);
    } else {
        DisplayMessage(g_hModule, MSG_IPV6_GLOBAL_PARAMETERS, 
                       Params.DefaultCurHopLimit, Params.NeighborCacheLimit, 
                       Params.RouteCacheLimit);
                       // Params.RouteCacheLimit, Params.ReassemblyLimit);
    }

    return NO_ERROR;
}

DWORD
QueryPrivacyParameters(
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    IPV6_GLOBAL_PARAMETERS Params;
    DWORD dwBytesReturned, dwErr;
    WCHAR wszValid[64], wszPreferred[64], wszRegenerate[64];
    WCHAR wszMaxRandom[64], wszRandom[64];
        

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (Format != FORMAT_DUMP) {
        // DisplayMessage(g_hModule, (bPersistent)? MSG_PERSISTENT : MSG_ACTIVE);
    }

    if (!DeviceIoControl(Handle, 
                         (bPersistent)? IOCTL_IPV6_PERSISTENT_QUERY_GLOBAL_PARAMETERS : IOCTL_IPV6_QUERY_GLOBAL_PARAMETERS,
                         NULL, 0,
                         &Params, sizeof Params, &dwBytesReturned, NULL) ||
        (dwBytesReturned != sizeof Params)) {
        return GetLastError();
    }

    FormatTime(Params.MaxAnonValidLifetime, wszValid);
    FormatTime(Params.MaxAnonPreferredLifetime, wszPreferred);
    FormatTime(Params.AnonRegenerateTime, wszRegenerate);
    FormatTime(Params.MaxAnonRandomTime, wszMaxRandom);
    FormatTime(Params.AnonRandomTime, wszRandom);
    
    if (Format == FORMAT_DUMP) {
        DisplayMessageT(DMP_IPV6_SET_PRIVACY);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_STATE,
                        ((Params.UseAnonymousAddresses == USE_ANON_NO)?
                        TOKEN_VALUE_DISABLED : TOKEN_VALUE_ENABLED));
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_MAXDADATTEMPTS,
                        Params.MaxAnonDADAttempts);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_MAXVALIDLIFETIME, wszValid);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_MAXPREFERREDLIFETIME, wszPreferred);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_REGENERATETIME, wszRegenerate);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_MAXRANDOMTIME, wszMaxRandom);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_RANDOMTIME, wszRandom);
        DisplayMessage(g_hModule, MSG_NEWLINE);
    } else {
        DisplayMessage(
            g_hModule, MSG_IPV6_PRIVACY_PARAMETERS,
            ((Params.UseAnonymousAddresses == USE_ANON_NO)
             ? TOKEN_VALUE_DISABLED
             : TOKEN_VALUE_ENABLED),
            Params.MaxAnonDADAttempts,

            Params.MaxAnonValidLifetime,
            Params.MaxAnonPreferredLifetime,
            Params.AnonRegenerateTime,
            Params.MaxAnonRandomTime,
            Params.AnonRandomTime);
    }

    return NO_ERROR;
}

DWORD
UpdateGlobalParameters(
    IN DWORD dwDefaultCurHopLimit, 
    IN DWORD dwNeighborCacheLimit,
    IN DWORD dwRouteCacheLimit,
    IN DWORD dwReassemblyLimit,
    IN BOOL bPersistent
    )
{
    IPV6_GLOBAL_PARAMETERS Params;
    DWORD dwBytesReturned;
    DWORD dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    Params.DefaultCurHopLimit = dwDefaultCurHopLimit;
    Params.UseAnonymousAddresses = (u_int) -1;
    Params.MaxAnonDADAttempts = (u_int) -1;
    Params.MaxAnonValidLifetime = (u_int) -1;
    Params.MaxAnonPreferredLifetime = (u_int) -1;
    Params.AnonRegenerateTime = (u_int) -1;
    Params.MaxAnonRandomTime = (u_int) -1;
    Params.AnonRandomTime = (u_int) -1;
    Params.NeighborCacheLimit = dwNeighborCacheLimit;
    Params.RouteCacheLimit = dwRouteCacheLimit;
    Params.ReassemblyLimit = dwReassemblyLimit;

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_UPDATE_GLOBAL_PARAMETERS,
                             &Params, sizeof Params,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_UPDATE_GLOBAL_PARAMETERS,
                         &Params, sizeof Params,
                         NULL, 0,
                         &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

DWORD 
UpdatePrivacyParameters(
    IN DWORD dwUseAnonymousAddresses,
    IN DWORD dwMaxDadAttempts,
    IN DWORD dwMaxValidLifetime,
    IN DWORD dwMaxPrefLifetime,
    IN DWORD dwRegenerateTime,
    IN DWORD dwMaxRandomTime,
    IN DWORD dwRandomTime,
    IN BOOL bPersistent
    )
{
    IPV6_GLOBAL_PARAMETERS Params;
    DWORD dwBytesReturned;
    DWORD dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    Params.DefaultCurHopLimit = -1;
    Params.UseAnonymousAddresses = dwUseAnonymousAddresses;
    Params.MaxAnonDADAttempts = dwMaxDadAttempts;
    Params.MaxAnonValidLifetime = dwMaxValidLifetime;
    Params.MaxAnonPreferredLifetime = dwMaxPrefLifetime;
    Params.AnonRegenerateTime = dwRegenerateTime;
    Params.MaxAnonRandomTime = dwMaxRandomTime;
    Params.AnonRandomTime = dwRandomTime;
    Params.NeighborCacheLimit = -1;
    Params.RouteCacheLimit = -1;
    Params.ReassemblyLimit = (u_int) -1;

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_UPDATE_GLOBAL_PARAMETERS,
                             &Params, sizeof Params,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_UPDATE_GLOBAL_PARAMETERS,
                         &Params, sizeof Params,
                         NULL, 0,
                         &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

//////////////////////////////////////////////////////////////////////////////
// Mobility-related functions
//////////////////////////////////////////////////////////////////////////////

DWORD
ForEachBinding(
    IN DWORD (*pfnFunc)(IPV6_INFO_BINDING_CACHE *)
    )
{
    IPV6_QUERY_BINDING_CACHE Query, NextQuery;
    IPV6_INFO_BINDING_CACHE BCE;
    DWORD dwBytesReturned;
    DWORD dwCount = 0;

    NextQuery.HomeAddress = in6addr_any;

    for (;;) {
        Query = NextQuery;

        if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_BINDING_CACHE,
                             &Query, sizeof Query,
                             &BCE, sizeof BCE, &dwBytesReturned,
                             NULL)) {
            DisplayMessage(g_hModule, IPV6_MESSAGE_126,
                      FormatIPv6Address(&Query.HomeAddress, 0));
            break;
        }

        NextQuery = BCE.Query;

        if (!IN6_ADDR_EQUAL(&Query.HomeAddress, &in6addr_any)) {
            BCE.Query = Query;
            if ((*pfnFunc)(&BCE) == NO_ERROR) {
                dwCount++;
            }
        }

        if (IN6_ADDR_EQUAL(&NextQuery.HomeAddress, &in6addr_any)) {
            break;
        }
    }

    return dwCount;
}

DWORD
PrintBindingCacheEntry(
    IN IPV6_INFO_BINDING_CACHE *BCE
    )
{
    WCHAR wszTime[64];
    
    DisplayMessage(g_hModule, IPV6_MESSAGE_127,
              FormatIPv6Address(&BCE->HomeAddress, 0));

    DisplayMessage(g_hModule, IPV6_MESSAGE_128,
              FormatIPv6Address(&BCE->CareOfAddress, 0));

    DisplayMessage(g_hModule, IPV6_MESSAGE_129,
              BCE->BindingSeqNumber,
              BCE->BindingLifetime);
              // FormatTime(BCE->BindingLifetime, wszTime));

    
    return NO_ERROR;
}

DWORD
QueryBindingCache(
    VOID
    )
{
    DWORD dwCount, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwCount = ForEachBinding(PrintBindingCacheEntry);

    if (dwCount == 0) {
        DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
    }

    return NO_ERROR;
}

DWORD
QueryMobilityParameters(
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    IPV6_GLOBAL_PARAMETERS Params;
    DWORD dwBytesReturned, dwErr;
    PWCHAR pwszTempString;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (Format != FORMAT_DUMP) {
        // DisplayMessage(g_hModule, (bPersistent)? MSG_PERSISTENT : MSG_ACTIVE);
    }

    if (!DeviceIoControl(Handle,
                         (bPersistent)? IOCTL_IPV6_PERSISTENT_QUERY_GLOBAL_PARAMETERS : IOCTL_IPV6_QUERY_GLOBAL_PARAMETERS,
                         NULL, 0,
                         &Params, sizeof Params, &dwBytesReturned, NULL) ||
        (dwBytesReturned != sizeof Params)) {
        return GetLastError();
    }

    pwszTempString = Params.MobilitySecurity ?
            TOKEN_VALUE_ENABLED : TOKEN_VALUE_DISABLED;

    if (Format == FORMAT_DUMP) {
        DisplayMessageT(DMP_IPV6_SET_MOBILITY);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_SECURITY, pwszTempString);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_BINDINGCACHELIMIT, 
                        Params.BindingCacheLimit);
        DisplayMessage(g_hModule, MSG_NEWLINE);
    } else {
        DisplayMessage(g_hModule, MSG_IPV6_MOBILITY_PARAMETERS, pwszTempString,
                       Params.BindingCacheLimit);
    }

    return NO_ERROR;
}

DWORD
UpdateMobilityParameters(
    IN DWORD dwSecurity,
    IN DWORD dwBindingCacheLimit,
    IN BOOL bPersistent
    )
{
    DWORD dwBytesReturned;
    DWORD dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = ERROR_OKAY;

    if ((dwBindingCacheLimit != -1) ||
        (dwSecurity != -1)) {
        IPV6_GLOBAL_PARAMETERS Params;

        Params.DefaultCurHopLimit = (u_int) -1;
        Params.UseAnonymousAddresses = (u_int) -1;
        Params.MaxAnonDADAttempts = (u_int) -1;
        Params.MaxAnonValidLifetime = (u_int) -1;
        Params.MaxAnonPreferredLifetime = (u_int) -1;
        Params.AnonRegenerateTime = (u_int) -1;
        Params.MaxAnonRandomTime = (u_int) -1;
        Params.AnonRandomTime = (u_int) -1;
        Params.NeighborCacheLimit = (u_int) -1;
        Params.RouteCacheLimit = (u_int) -1;
        Params.ReassemblyLimit = (u_int) -1;
        Params.BindingCacheLimit = dwBindingCacheLimit;
        Params.MobilitySecurity = dwSecurity;

        if (bPersistent) {
            if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_UPDATE_GLOBAL_PARAMETERS,
                                 &Params, sizeof Params,
                                 NULL, 0,
                                 &dwBytesReturned, NULL)) {
                dwErr = GetLastError();
            }
        }

        if (!DeviceIoControl(Handle, IOCTL_IPV6_UPDATE_GLOBAL_PARAMETERS,
                             &Params, sizeof Params,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            if (dwErr == ERROR_OKAY) {
                dwErr = GetLastError();
            }
        }
    }

    return dwErr;
}

//////////////////////////////////////////////////////////////////////////////
// Prefix policy table functions
//////////////////////////////////////////////////////////////////////////////

DWORD
ForEachPrefixPolicy(
    IN DWORD (*pfnFunc)(IPV6_INFO_PREFIX_POLICY *,FORMAT,DWORD), 
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    IPV6_QUERY_PREFIX_POLICY Query;
    IPV6_INFO_PREFIX_POLICY PPE;
    DWORD dwBytesReturned, dwCount = 0;

    Query.PrefixLength = (u_int) -1;

    for (;;) {
        if (!DeviceIoControl(Handle, 
                             (bPersistent)? IOCTL_IPV6_PERSISTENT_QUERY_PREFIX_POLICY : IOCTL_IPV6_QUERY_PREFIX_POLICY,
                             &Query, sizeof Query,
                             &PPE, sizeof PPE, &dwBytesReturned,
                             NULL)) {
            break;
        }

        if (Query.PrefixLength != (u_int) -1) {

            if (dwBytesReturned != sizeof PPE)
                break;

            if ((*pfnFunc)(&PPE, Format, dwCount) == NO_ERROR) {
                dwCount++;
            }
        }
        else {
            if (dwBytesReturned != sizeof PPE.Next)
                break;
        }

        if (PPE.Next.PrefixLength == (u_int) -1)
            break;
        Query = PPE.Next;
    }

    return dwCount;
}

DWORD
PrintPrefixPolicyEntry(
    IN IPV6_INFO_PREFIX_POLICY *PPE, 
    IN FORMAT Format, 
    IN DWORD dwCount
    )
{
    if (Format == FORMAT_DUMP) {
        DisplayMessageT(DMP_IPV6_ADD_PREFIXPOLICY);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_PREFIX,
                       FormatIPv6Prefix(&PPE->This.Prefix, 
                                        PPE->This.PrefixLength));
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_PRECEDENCE,
                        PPE->Precedence);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_LABEL,
                        PPE->SrcLabel);
        DisplayMessage(g_hModule, MSG_NEWLINE);
    } else {
        if (!dwCount) {
            DisplayMessage(g_hModule, MSG_IPV6_PREFIX_POLICY_HDR);
        }

        DisplayMessage(g_hModule, MSG_IPV6_PREFIX_POLICY,
                       FormatIPv6Prefix(&PPE->This.Prefix,
                                        PPE->This.PrefixLength),
                       PPE->Precedence,
                       PPE->SrcLabel,
                       PPE->DstLabel);
    }

    return NO_ERROR;
}

DWORD
QueryPrefixPolicy(
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    DWORD dwCount, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (Format != FORMAT_DUMP) {
        // DisplayMessage(g_hModule, (bPersistent)? MSG_PERSISTENT : MSG_ACTIVE);
    }

    dwCount = ForEachPrefixPolicy(PrintPrefixPolicyEntry, Format, bPersistent);

    if (!dwCount && (Format != FORMAT_DUMP)) {
        DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
    }

    return NO_ERROR;
}

DWORD
UpdatePrefixPolicy(
    IN IN6_ADDR *IpAddress, 
    IN DWORD dwPrefixLength, 
    IN DWORD dwPrecedence, 
    IN DWORD dwLabel,
    IN BOOL bPersistent
    )
{
    IPV6_INFO_PREFIX_POLICY Info;
    DWORD dwBytesReturned, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    Info.This.Prefix = *IpAddress;
    Info.This.PrefixLength = dwPrefixLength;
    Info.Precedence = dwPrecedence;
    Info.SrcLabel = Info.DstLabel = dwLabel;

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_UPDATE_PREFIX_POLICY,
                             &Info, sizeof Info,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_UPDATE_PREFIX_POLICY,
                         &Info, sizeof Info,
                         NULL, 0, &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

DWORD
DeletePrefixPolicy(
    IN IN6_ADDR *IpAddress,
    IN DWORD dwPrefixLength,
    IN BOOL bPersistent
    )
{
    IPV6_QUERY_PREFIX_POLICY Query;
    DWORD dwBytesReturned, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    Query.Prefix = *IpAddress;
    Query.PrefixLength = dwPrefixLength;

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_DELETE_PREFIX_POLICY,
                             &Query, sizeof Query,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_DELETE_PREFIX_POLICY,
                         &Query, sizeof Query,
                         NULL, 0, &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

//////////////////////////////////////////////////////////////////////////////
// Address table functions
//////////////////////////////////////////////////////////////////////////////

DWORD
ForEachAddress(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format,
    IN DWORD (*pfnFunc)(IPV6_INFO_INTERFACE *IF, PIP_ADAPTER_ADDRESSES, FORMAT, DWORD, IPV6_INFO_ADDRESS *))
{
    IPV6_QUERY_ADDRESS Query;
    IPV6_INFO_ADDRESS ADE;
    DWORD BytesReturned, dwCount = 0;

    Query.IF = IF->This;
    Query.Address = in6addr_any;

    for (;;) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_ADDRESS,
                             &Query, sizeof Query,
                             &ADE, sizeof ADE, &BytesReturned,
                             NULL)) {
            break;
        }

        if (!IN6_ADDR_EQUAL(&Query.Address, &in6addr_any)) {

            if (BytesReturned != sizeof ADE)
                break;

            if ((*pfnFunc)(IF, pAdapterInfo, Format, dwCount, &ADE) == NO_ERROR) {
                dwCount++;
            }
        }
        else {
            if (BytesReturned != sizeof ADE.Next)
                break;
        }

        if (IN6_ADDR_EQUAL(&ADE.Next.Address, &in6addr_any))
            break;
        Query = ADE.Next;
    }

    return dwCount;
}

PWCHAR
GetDadState(
    IN DWORD dwDadState,
    OUT BOOL *bDynamic
    )
{
    PWCHAR pwszTemp;
    DWORD dwMsg = 0;
    static WCHAR wszDadState[128];

    switch (dwDadState) {
    case DAD_STATE_INVALID: 
        dwMsg = STRING_INVALID; 
        break;
    case DAD_STATE_DUPLICATE: 
        dwMsg = STRING_DUPLICATE; 
        break;
    case DAD_STATE_TENTATIVE: 
        dwMsg = STRING_TENTATIVE; 
        break;
    case DAD_STATE_DEPRECATED: 
        dwMsg = STRING_DEPRECATED; 
        break;
    case DAD_STATE_PREFERRED: 
        dwMsg = STRING_PREFERRED; 
        break;
    default:
        swprintf(wszDadState, L"%u", dwDadState);
        *bDynamic = FALSE;
        return wszDadState;
    }

    *bDynamic = TRUE;
    pwszTemp = MakeString(g_hModule, dwMsg);
    return pwszTemp;
}

PWCHAR
GetAddressType(
    IN DWORD dwScope,
    IN DWORD dwPrefixConf,
    IN DWORD dwIidConf
    )
{
    DWORD dwMsg = 0;

    if ((dwScope == ADE_LINK_LOCAL) &&
        (dwPrefixConf == PREFIX_CONF_WELLKNOWN) &&
        (dwIidConf == IID_CONF_WELLKNOWN)) {

        dwMsg = STRING_LOOPBACK;

    } else if ((dwScope == ADE_LINK_LOCAL) &&
               (dwPrefixConf == PREFIX_CONF_WELLKNOWN) &&
               (dwIidConf == IID_CONF_LL_ADDRESS)) {
    
        dwMsg = STRING_LINK;

    } else if ((dwPrefixConf == PREFIX_CONF_MANUAL) &&
               (dwIidConf == IID_CONF_MANUAL)) {

        dwMsg = STRING_MANUAL;

    } else if ((dwPrefixConf == PREFIX_CONF_RA) &&
               (dwIidConf == IID_CONF_LL_ADDRESS)) {

        dwMsg = STRING_PUBLIC;

    } else if ((dwPrefixConf == PREFIX_CONF_RA) &&
        (dwIidConf == IID_CONF_RANDOM)) {

        dwMsg = STRING_ANONYMOUS;

    } else if ((dwPrefixConf == PREFIX_CONF_DHCP) &&
               (dwIidConf == IID_CONF_DHCP)) {

        dwMsg = STRING_DHCP;
    } else {
        dwMsg = STRING_OTHER;
    }

    return MakeString(g_hModule, dwMsg);
}

PWCHAR
GetScopeNoun(
    IN DWORD dwScope,
    OUT BOOL *bDynamic
    )
{
    PWCHAR pwszTemp;
    DWORD dwMsg = 0;
    static WCHAR wszScopeLevel[128];

    switch (dwScope) {
    case ADE_INTERFACE_LOCAL: 
        dwMsg = STRING_INTERFACE; 
        break;
    case ADE_LINK_LOCAL:
        dwMsg = STRING_LINK;
        break;
    case ADE_SUBNET_LOCAL:
        dwMsg = STRING_SUBNET;
        break;
    case ADE_ADMIN_LOCAL:
        dwMsg = STRING_ADMIN;
        break;
    case ADE_SITE_LOCAL:
        dwMsg = STRING_SITE;
        break;
    case ADE_ORG_LOCAL:
        dwMsg = STRING_ORG;
        break;
    case ADE_GLOBAL:
        dwMsg = STRING_GLOBAL;
        break;
    default:
        swprintf(wszScopeLevel, L"%u", dwScope);
        *bDynamic = FALSE;
        return wszScopeLevel;
    }

    *bDynamic = TRUE;
    pwszTemp = MakeString(g_hModule, dwMsg);
    return pwszTemp;
}

DWORD rgdwPrefixConfMsg[] = {
    0,
    STRING_MANUAL,
    STRING_WELLKNOWN,
    STRING_DHCP,
    STRING_RA
    };
#define PREFIX_CONF_MSG_COUNT (sizeof(rgdwPrefixConfMsg)/sizeof(DWORD))

DWORD rgdwIidConfMsg[] = {
    0,
    STRING_MANUAL,
    STRING_WELLKNOWN,
    STRING_DHCP,
    STRING_LL_ADDRESS,
    STRING_RANDOM
    };
#define IID_CONF_MSG_COUNT (sizeof(rgdwIidConfMsg)/sizeof(DWORD))

DWORD
PrintMulticastAddress(
    IN IPV6_INFO_INTERFACE *IF, 
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format,
    IN DWORD dwCount,
    IN IPV6_INFO_ADDRESS *ADE
    )
{
    BOOL bDynamicDadState, bDynamicScope;
    PWCHAR pwszDadState, pwszScope, pwszFriendlyName;
    DWORD dwErr = NO_ERROR;
    PWCHAR pwszLastReporter, pwszNever;
    WCHAR wszTime[64];    

    if (ADE->Type != ADE_MULTICAST) {
        return ERROR_NO_DATA;
    }

    if (dwCount == 0) {
        dwErr = MapIpv6IfIndexToFriendlyName(IF->This.Index, pAdapterInfo,
                                             &pwszFriendlyName);
        if (dwErr != NO_ERROR) {
            return dwErr;
        }

        DisplayMessage(g_hModule, 
                       (Format == FORMAT_VERBOSE)? MSG_IPV6_ADDRESS_HDR_VERBOSE : MSG_IPV6_MULTICAST_ADDRESS_HDR, 
                       IF->This.Index, pwszFriendlyName);
    }

    pwszScope = GetScopeNoun(ADE->Scope, &bDynamicScope);

    pwszNever = MakeString(g_hModule, STRING_NEVER);
    pwszLastReporter = MakeString(g_hModule, (ADE->MCastFlags & 0x02)? STRING_YES : STRING_NO);

    if (Format == FORMAT_VERBOSE) {
        DisplayMessage(g_hModule,
                       MSG_IPV6_MULTICAST_ADDRESS_VERBOSE,
                       pwszScope,
                       FormatIPv6Address(&ADE->This.Address, 0),
                       ADE->MCastRefCount,
                       (ADE->MCastFlags & 0x01) ? ADE->MCastTimer : 0,
                       pwszLastReporter);
    } else {
        DisplayMessage(g_hModule,
                       MSG_IPV6_MULTICAST_ADDRESS,
                       pwszScope,
                       FormatIPv6Address(&ADE->This.Address, 0),
                       ADE->MCastRefCount,
                       (ADE->MCastFlags & 0x01)
                       ? FormatTime(ADE->MCastTimer, wszTime)
                       : pwszNever,
                       pwszLastReporter);
    }

    FreeString(pwszLastReporter);
    FreeString(pwszNever);

    if (bDynamicScope) {
        FreeString(pwszScope);
    }

    return dwErr;
}

DWORD
PrintAddress(
    IN IPV6_INFO_INTERFACE *IF, 
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format,
    IN DWORD dwCount,
    IN IPV6_INFO_ADDRESS *ADE
    )
{
    DWORD dwPrefixConf, dwInterfaceIdConf;
    BOOL bDynamicDadState, bDynamicScope, bDynamicType;
    PWCHAR pwszDadState, pwszScope, pwszFriendlyName, pwszType;
    WCHAR wszValid[64], wszPreferred[64];
    DWORD dwErr = NO_ERROR;

    if (Format != FORMAT_VERBOSE) {
        //
        // Suppress invalid addresses.
        //
        if ((ADE->Type == ADE_UNICAST) &&
            (ADE->DADState == DAD_STATE_INVALID)) {
            return ERROR_NO_DATA;
        }

        //
        // Multicast addresses are handled by PrintMulticastAddress()
        // instead.
        //
        if (ADE->Type == ADE_MULTICAST) {
            return ERROR_NO_DATA;
        }
    }

    if (dwCount == 0) {
        dwErr = MapIpv6IfIndexToFriendlyName(IF->This.Index, pAdapterInfo,
                                             &pwszFriendlyName);
        if (dwErr != NO_ERROR) {
            return dwErr;
        }

        DisplayMessage(g_hModule, 
                       ((Format == FORMAT_VERBOSE)? MSG_IPV6_ADDRESS_HDR_VERBOSE : MSG_IPV6_ADDRESS_HDR),
                       IF->This.Index, pwszFriendlyName);
    }

    pwszScope = GetScopeNoun(ADE->Scope, &bDynamicScope);

    switch (ADE->Type) {
    case ADE_UNICAST:

        pwszDadState = GetDadState(ADE->DADState, &bDynamicDadState);

        pwszType = GetAddressType(ADE->Scope, ADE->PrefixConf, ADE->InterfaceIdConf);

        DisplayMessage(g_hModule, 
                       ((Format == FORMAT_VERBOSE)? MSG_IPV6_UNICAST_ADDRESS_VERBOSE : MSG_IPV6_UNICAST_ADDRESS),
                       pwszType,
                       pwszDadState,
                       FormatIPv6Address(&ADE->This.Address, 0),
                       ADE->ValidLifetime,
                       // FormatTime(ADE->ValidLifetime, wszValid),
                       ADE->PreferredLifetime,
                       // FormatTime(ADE->PreferredLifetime, wszPreferred),
                       pwszScope);

        if (bDynamicDadState) {
            FreeString(pwszDadState);
        }
        FreeString(pwszType);

        if (Format == FORMAT_VERBOSE) {
            //
            // Show prefix origin / interface id origin
            //
            DisplayMessage(g_hModule, MSG_IPV6_PREFIX_ORIGIN);

            dwPrefixConf = ADE->PrefixConf;
            if ((dwPrefixConf == PREFIX_CONF_OTHER) || 
                (dwPrefixConf >= PREFIX_CONF_MSG_COUNT)) {

                DisplayMessage(g_hModule, MSG_IPV6_INTEGER, dwPrefixConf);
            } else {
                DisplayMessage(g_hModule, rgdwPrefixConfMsg[dwPrefixConf]);
            }
            DisplayMessage(g_hModule, MSG_NEWLINE);

            DisplayMessage(g_hModule, MSG_IPV6_IID_ORIGIN);
            dwInterfaceIdConf = ADE->InterfaceIdConf;
            if ((dwInterfaceIdConf == IID_CONF_OTHER) ||
                (dwInterfaceIdConf >= IID_CONF_MSG_COUNT)) {

                DisplayMessage(g_hModule, MSG_IPV6_INTEGER, dwInterfaceIdConf);
            } else {
                DisplayMessage(g_hModule, rgdwIidConfMsg[dwInterfaceIdConf]);
            }
            DisplayMessage(g_hModule, MSG_NEWLINE);
        }

        break;

    case ADE_ANYCAST:
        DisplayMessage(g_hModule, 
                       ((Format == FORMAT_VERBOSE)? MSG_IPV6_ANYCAST_ADDRESS_VERBOSE : MSG_IPV6_ANYCAST_ADDRESS),
                       FormatIPv6Address(&ADE->This.Address, 0),
                       pwszScope);

        break;

    case ADE_MULTICAST:
    default:
        dwErr = ERROR_NO_DATA;
        break;
    }

    if (bDynamicScope) {
        FreeString(pwszScope);
    }

    return dwErr;
}

DWORD
PrintAddressTable(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwIfCount,
    IN FORMAT Format
    )
{
    DWORD dwCount = ForEachAddress(IF, pAdapterInfo, Format, PrintAddress);
    return (dwCount > 0)? NO_ERROR : ERROR_NO_DATA;
}

DWORD
PrintMulticastAddressTable(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwIfCount,
    IN FORMAT Format
    )
{
    DWORD dwCount = ForEachAddress(IF, pAdapterInfo, Format, 
                                   PrintMulticastAddress);
    return (dwCount > 0)? NO_ERROR : ERROR_NO_DATA;
}

DWORD
QueryAddressTable(
    IN PWCHAR pwszIfFriendlyName,
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    DWORD dwErr, dwCount = 0;
    IP_ADAPTER_ADDRESSES *pAdapterInfo;
    IPV6_INFO_INTERFACE *IF;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (Format != FORMAT_DUMP) {
        // DisplayMessage(g_hModule, (bPersistent)? MSG_PERSISTENT : MSG_ACTIVE);
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != ERROR_NO_DATA) {
        if (dwErr == NO_ERROR) {
    
            if (pwszIfFriendlyName == NULL) {
                dwCount = ForEachInterface(PrintAddressTable, pAdapterInfo, 
                                           Format, bPersistent);
            } else {
                dwErr = GetInterfaceByFriendlyName(pwszIfFriendlyName, 
                                                   pAdapterInfo, bPersistent, 
                                                   &IF);
                if (dwErr == NO_ERROR) {
                    PrintAddressTable(IF, pAdapterInfo, 0, Format);
                    FREE(IF);
                }
            }

            FREE(pAdapterInfo);
        } else if (dwErr == ERROR_NO_DATA) {
            dwErr = NO_ERROR;
        }
    }
    if (!dwCount && (Format != FORMAT_DUMP)) {
        DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
    }

    return dwErr;
}

DWORD
QueryMulticastAddressTable(
    IN PWCHAR pwszIfFriendlyName,
    IN FORMAT Format
    )
{
    DWORD dwErr, dwCount = 0;
    IP_ADAPTER_ADDRESSES *pAdapterInfo;
    IPV6_INFO_INTERFACE *IF;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != ERROR_NO_DATA) {
        if (dwErr == NO_ERROR) {
    
            if (pwszIfFriendlyName == NULL) {
                ForEachInterface(PrintMulticastAddressTable, 
                                 pAdapterInfo, Format, FALSE);
            } else {
                dwErr = GetInterfaceByFriendlyName(pwszIfFriendlyName, 
                                                   pAdapterInfo, FALSE, &IF);
                if (dwErr == NO_ERROR) {
                    dwErr = PrintMulticastAddressTable(IF, pAdapterInfo, 0, 
                                                       Format);
                    FREE(IF);
                }
            }

            FREE(pAdapterInfo);
        } else if (dwErr == ERROR_NO_DATA) {
            dwErr = NO_ERROR;
        }
    }

    return dwErr;
}

DWORD 
UpdateAddress(
    IN PWCHAR pwszIfFriendlyName, 
    IN IN6_ADDR *pipAddress,
    IN DWORD dwType, 
    IN DWORD dwValidLifetime,
    IN DWORD dwPreferredLifetime,
    IN BOOL bPersistent
    )
{
    IPV6_UPDATE_ADDRESS Update;
    DWORD dwBytesReturned, dwErr;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MapFriendlyNameToIpv6IfIndex(pwszIfFriendlyName, pAdapterInfo,
                                         &Update.This.IF.Index);
    FREE(pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    Update.This.Address = *pipAddress;
    Update.Type = dwType;
    Update.PrefixConf = PREFIX_CONF_MANUAL;
    Update.InterfaceIdConf = IID_CONF_MANUAL;
    Update.ValidLifetime = dwValidLifetime;
    Update.PreferredLifetime = dwPreferredLifetime;

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_UPDATE_ADDRESS,
                             &Update, sizeof Update,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_UPDATE_ADDRESS,
                         &Update, sizeof Update,
                         NULL, 0, &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

//////////////////////////////////////////////////////////////////////////////
// Interface table functions
//////////////////////////////////////////////////////////////////////////////

DWORD
RenewViaReconnect(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwCount,
    IN FORMAT Format
    )
{
    DWORD dwBytesReturned;

    if (!DeviceIoControl(Handle, IOCTL_IPV6_RENEW_INTERFACE,
                         &IF->This, sizeof IF->This,
                         NULL, 0, &dwBytesReturned, NULL)) {
        return GetLastError();
    }

    return NO_ERROR;
}

DWORD
RenewInterface(
    IN PWCHAR wszIfFriendlyName
    )
{
    DWORD dwErr;
    BOOL PokeService = FALSE;
    
    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (wszIfFriendlyName == NULL) {
        ForEachInterface(RenewViaReconnect, NULL, FALSE, FALSE);
        PokeService = TRUE;
    } else {
        IP_ADAPTER_ADDRESSES *pAdapterInfo;

        dwErr = MyGetAdaptersInfo(&pAdapterInfo);
        if (dwErr == NO_ERROR) {
            IPV6_INFO_INTERFACE *IF;

            dwErr = GetInterfaceByFriendlyName(wszIfFriendlyName, pAdapterInfo,
                                               FALSE, &IF);
            FREE(pAdapterInfo);
            if (dwErr != NO_ERROR) {
                return dwErr;
            }
    
            dwErr = RenewViaReconnect(IF, NULL, 0, FALSE);
            //
            // Poke the 6to4 service if it manages the interface being renewed.
            //
            PokeService = (IF->Type == IPV6_IF_TYPE_TUNNEL_6TO4) ||
                (IF->Type == IPV6_IF_TYPE_TUNNEL_TEREDO) ||
                (IF->Type == IPV6_IF_TYPE_TUNNEL_AUTO);            
            FREE(IF);
        } else if (dwErr == ERROR_NO_DATA) {
            dwErr = NO_ERROR;
        }
    }

    if (PokeService) {
        Ip6to4PokeService();
    }
    
    return dwErr;
}

DWORD dwMediaSenseMsg[] = {
    STRING_DISCONNECTED,
    STRING_RECONNECTED,
    STRING_CONNECTED,
};
#define MEDIA_SENSE_MSG_COUNT (sizeof(dwMediaSenseMsg)/sizeof(DWORD))

DWORD
PrintInterface(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwCount,
    IN FORMAT Format
    )
{
    PWCHAR pwszFriendlyName, pwszTemp;
    DWORD dwErr, dwMsg, dwScope;
    WCHAR wszReachable[64], wszBaseReachable[64], wszRetransTimer[64];
    PIP_ADAPTER_ADDRESSES pIf;
    CHAR szGuid[41];
    
    dwErr = MapGuidToFriendlyName(NULL, &IF->This.Guid, pAdapterInfo,
                                  &pwszFriendlyName);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (IF->MediaStatus < MEDIA_SENSE_MSG_COUNT) {
        dwMsg = dwMediaSenseMsg[IF->MediaStatus];
    } else {
        dwMsg = STRING_UNKNOWN;
    }

    switch (Format) {
    case FORMAT_DUMP:
        switch (IF->Type) {
        case IPV6_IF_TYPE_TUNNEL_6OVER4:
            DisplayMessageT(DMP_IPV6_ADD_6OVER4TUNNEL);
            DisplayMessageT(DMP_QUOTED_STRING_ARG, TOKEN_INTERFACE,
                            pwszFriendlyName);
            if (IF->LocalLinkLayerAddress != 0) {
                DisplayMessageT(DMP_STRING_ARG, TOKEN_LOCALADDRESS,
                        FormatLinkLayerAddress(IF->LinkLayerAddressLength,
                                (char *)IF + IF->LocalLinkLayerAddress));
            }
            DisplayMessage(g_hModule, MSG_NEWLINE);
            break;
        case IPV6_IF_TYPE_TUNNEL_V6V4:
            DisplayMessageT(DMP_IPV6_ADD_V6V4TUNNEL);
            DisplayMessageT(DMP_QUOTED_STRING_ARG, TOKEN_INTERFACE,
                            pwszFriendlyName);
            if (IF->LocalLinkLayerAddress != 0) {
                DisplayMessageT(DMP_STRING_ARG, TOKEN_LOCALADDRESS,
                        FormatLinkLayerAddress(IF->LinkLayerAddressLength,
                                (char *)IF + IF->LocalLinkLayerAddress));
            }
            if (IF->RemoteLinkLayerAddress != 0) {
                DisplayMessageT(DMP_STRING_ARG, TOKEN_REMOTEADDRESS,
                        FormatLinkLayerAddress(IF->LinkLayerAddressLength,
                                (char *)IF + IF->RemoteLinkLayerAddress));
            }
            if (IF->NeighborDiscovers) {
                DisplayMessageT(DMP_STRING_ARG, TOKEN_NEIGHBORDISCOVERY,
                                TOKEN_VALUE_ENABLED);
            }
            DisplayMessage(g_hModule, MSG_NEWLINE);
            break;
        }

        DisplayMessageT(DMP_IPV6_SET_INTERFACE);
        DisplayMessageT(DMP_QUOTED_STRING_ARG, TOKEN_INTERFACE,
                        pwszFriendlyName);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_METRIC,
                        IF->Preference);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_MTU,
                        IF->LinkMTU);
        DisplayMessage(g_hModule, MSG_NEWLINE);

        break;

    case FORMAT_NORMAL:
        if (dwCount == 0) {
            DisplayMessage(g_hModule, MSG_IPV6_INTERFACE_HDR);
        }

        pwszTemp = MakeString(g_hModule, dwMsg);

        DisplayMessage(g_hModule, MSG_IPV6_INTERFACE, IF->This.Index,
                       IF->Preference, IF->LinkMTU, pwszTemp, pwszFriendlyName);

        FreeString(pwszTemp);

        break;

    case FORMAT_VERBOSE:

        DisplayMessage(g_hModule, MSG_SEPARATOR);

        ForEachAddress(IF, pAdapterInfo, FORMAT_NORMAL, PrintAddress);

        //
        // Get extra interface information.
        //
        pIf = MapIfIndexToAdapter(AF_INET6, IF->This.Index, pAdapterInfo);

        pwszTemp = MakeString(g_hModule, dwMsg);

        ConvertGuidToStringA(&IF->This.Guid, szGuid);

        DisplayMessage(g_hModule, MSG_IPV6_INTERFACE_VERBOSE, 
                       szGuid,
                       pwszTemp, 
                       IF->Preference, 
                       IF->LinkMTU, 
                       IF->TrueLinkMTU, 
                       IF->CurHopLimit,
                       IF->ReachableTime,
                       // FormatTime(IF->ReachableTime, wszReachable), 
                       IF->BaseReachableTime,
                       // FormatTime(IF->BaseReachableTime, wszBaseReachable), 
                       IF->RetransTimer,
                       // FormatTime(IF->RetransTimer, wszRetransTimer), 
                       IF->DupAddrDetectTransmits, 
                       (pIf)? pIf->DnsSuffix : L""
                       // , pwszFriendlyName
                       );

        FreeString(pwszTemp);

        for (dwScope = ADE_LINK_LOCAL; dwScope < ADE_GLOBAL; dwScope++) {
            DWORD Expected = 0;

            //
            // Always print link & site.
            //
            if ((dwScope != ADE_LINK_LOCAL) && (dwScope != ADE_SITE_LOCAL)) {
                Expected = IF->ZoneIndices[dwScope + 1];
            }

            if (IF->ZoneIndices[dwScope] != Expected) {
                BOOL bDynamic;

                pwszTemp = GetScopeNoun(dwScope, &bDynamic);

                DisplayMessage(g_hModule, MSG_IPV6_INTERFACE_SCOPE,
                               pwszTemp, IF->ZoneIndices[dwScope]);

                if (bDynamic) {
                    FreeString(pwszTemp);
                }
            }
        }

        DisplayMessage(g_hModule, MSG_IPV6_ND_ENABLED);
        DisplayMessage(g_hModule, 
            (IF->NeighborDiscovers ? STRING_YES : STRING_NO));
        DisplayMessage(g_hModule, MSG_NEWLINE);
    
        DisplayMessage(g_hModule, MSG_IPV6_SENDS_RAS);
        DisplayMessage(g_hModule, 
            (IF->Advertises ? STRING_YES : STRING_NO));
        DisplayMessage(g_hModule, MSG_NEWLINE);
    
        DisplayMessage(g_hModule, MSG_IPV6_FORWARDS);
        DisplayMessage(g_hModule, 
            (IF->Forwards ? STRING_YES : STRING_NO));
        DisplayMessage(g_hModule, MSG_NEWLINE);

        if (IF->LocalLinkLayerAddress != 0) {
            DisplayMessage(g_hModule, MSG_IPV6_LL_ADDRESS,
                FormatLinkLayerAddress(IF->LinkLayerAddressLength,
                                (char *)IF + IF->LocalLinkLayerAddress));
        }
    
        if (IF->RemoteLinkLayerAddress != 0) {
            DisplayMessage(g_hModule, MSG_IPV6_REMOTE_LL_ADDRESS,
                FormatLinkLayerAddress(IF->LinkLayerAddressLength, 
                                (char *)IF + IF->RemoteLinkLayerAddress));
        }

        break;
    }

    return NO_ERROR;
}

DWORD
QueryInterface(
    IN PWCHAR pwszIfFriendlyName,
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    IPV6_INFO_INTERFACE *IF;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;
    DWORD dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (Format != FORMAT_DUMP) {
        // DisplayMessage(g_hModule, (bPersistent)? MSG_PERSISTENT : MSG_ACTIVE);
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr == ERROR_NO_DATA) {
        if (Format != FORMAT_DUMP) {
            DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
        }
        return NO_ERROR;
    }
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (pwszIfFriendlyName == NULL) {
        ForEachInterface(PrintInterface, pAdapterInfo, Format, bPersistent);
    } else {
        dwErr = GetInterfaceByFriendlyName(pwszIfFriendlyName, pAdapterInfo, 
                                           bPersistent, &IF);
        if (dwErr == NO_ERROR) {
            dwErr = PrintInterface(IF, pAdapterInfo, 0, Format);
            FREE(IF);
        }
    }

    FREE(pAdapterInfo);
    return dwErr;
}

DWORD
DeleteInterface(
    IN PWCHAR pwszIfFriendlyName,
    IN BOOL bPersistent
    )
{
    IPV6_QUERY_INTERFACE Query;
    IPV6_INFO_INTERFACE *IF;
    IP_ADAPTER_ADDRESSES *pAdapterInfo;
    DWORD dwBytesReturned, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MapFriendlyNameToIpv6IfIndex(pwszIfFriendlyName, pAdapterInfo, 
                                         &Query.Index);
    FREE(pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = ERROR_OKAY;

    if (!DeviceIoControl(Handle, 
                         (bPersistent)
                            ? IOCTL_IPV6_PERSISTENT_DELETE_INTERFACE
                            : IOCTL_IPV6_DELETE_INTERFACE,
                         &Query, sizeof Query,
                         NULL, 0, &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

#define KEY_TCPIP6_IF L"System\\CurrentControlSet\\Services\\Tcpip6\\Parameters\\Interfaces"

DWORD
SetFriendlyName(
    IN GUID *pGuid, 
    IN PWCHAR pwszFriendlyName
    )
{
    DWORD dwErr;
    HKEY  hInterfaces = NULL, hIf = NULL;
    UNICODE_STRING usGuid; 

    dwErr = RtlStringFromGUID(pGuid, &usGuid);
    if (!NT_SUCCESS(dwErr)) {
        return dwErr;
    }

    dwErr = RegOpenKeyExW(HKEY_LOCAL_MACHINE, KEY_TCPIP6_IF, 0, GENERIC_READ,
                          &hInterfaces);
    if (dwErr != NO_ERROR) {
        goto Cleanup;
    }

    dwErr = RegOpenKeyExW(hInterfaces, usGuid.Buffer, 0, GENERIC_WRITE, &hIf);
    if (dwErr != NO_ERROR) {
        goto Cleanup;
    }

    dwErr = SetString(hIf, L"FriendlyName", pwszFriendlyName);

Cleanup:
    if (hInterfaces) {
        RegCloseKey(hInterfaces);
    }
    if (hIf) {
        RegCloseKey(hIf);
    }
    RtlFreeUnicodeString(&usGuid);

    return dwErr;
}

DWORD
AddTunnelInterface(
    IN PWCHAR pwszFriendlyName,
    IN IN_ADDR *pipLocalAddr,
    IN IN_ADDR *pipRemoteAddr,
    IN DWORD dwType,
    IN DWORD dwDiscovery,
    IN BOOL bPersistent
    )
{
    struct {
        IPV6_INFO_INTERFACE Info;
        IN_ADDR SrcAddr;
        IN_ADDR DstAddr;
    } Create;
    IPV6_QUERY_INTERFACE Result;
    DWORD dwBytesReturned, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    //
    // TODO: use pwszFriendlyName when persistent config is ready
    //

    IPV6_INIT_INFO_INTERFACE(&Create.Info);
    Create.Info.Type = dwType;
    Create.Info.NeighborDiscovers = dwDiscovery;
    Create.Info.RouterDiscovers = dwDiscovery;
    Create.Info.LinkLayerAddressLength = sizeof(IN_ADDR);
    if (pipLocalAddr != NULL) {
        Create.SrcAddr = *pipLocalAddr;
        Create.Info.LocalLinkLayerAddress = (u_int)
            ((char *)&Create.SrcAddr - (char *)&Create.Info);
    }
    if (pipRemoteAddr != NULL) {
        Create.DstAddr = *pipRemoteAddr;
        Create.Info.RemoteLinkLayerAddress = (u_int)
            ((char *)&Create.DstAddr - (char *)&Create.Info);
    }

    dwErr = ERROR_OKAY;

    if (!DeviceIoControl(Handle, 
                         (bPersistent)
                            ? IOCTL_IPV6_PERSISTENT_CREATE_INTERFACE 
                            : IOCTL_IPV6_CREATE_INTERFACE,
                         &Create, sizeof Create,
                         &Result, sizeof Result, &dwBytesReturned, NULL) ||
        (dwBytesReturned != sizeof Result)) {
        dwErr = GetLastError();
    } else if (bPersistent) {
        SetFriendlyName(&Result.Guid, pwszFriendlyName);
    }

    return dwErr;
}

DWORD
UpdateInterface(
    IN PWCHAR pwszIfFriendlyName, 
    IN DWORD dwForwarding, 
    IN DWORD dwAdvertises,
    IN DWORD dwMtu,
    IN DWORD dwSiteId,
    IN DWORD dwMetric,
    IN BOOL bPersistent
    )
{
    IPV6_INFO_INTERFACE Update;
    DWORD dwBytesReturned, dwErr;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    IPV6_INIT_INFO_INTERFACE(&Update);

    dwErr = MapFriendlyNameToIpv6IfIndex(pwszIfFriendlyName, pAdapterInfo,
                                         &Update.This.Index);
    FREE(pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    Update.Advertises = dwAdvertises;
    Update.Forwards = dwForwarding;
    Update.LinkMTU = dwMtu;
    Update.Preference = dwMetric;
    Update.ZoneIndices[ADE_SITE_LOCAL] = dwSiteId;

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_UPDATE_INTERFACE,
                             &Update, sizeof Update,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_UPDATE_INTERFACE,
                         &Update, sizeof Update,
                         NULL, 0, &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

//////////////////////////////////////////////////////////////////////////////
// Neighbor cache functions
//////////////////////////////////////////////////////////////////////////////

DWORD
PrintNeighborCacheEntry(
    IN IPV6_INFO_NEIGHBOR_CACHE *NCE,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwCount
    )
{
    DWORD dwErr;
    PWCHAR pwszLinkLayerAddress = L"";
    PWCHAR pwszUnreachable;

    if (NCE->NDState != ND_STATE_INCOMPLETE) {
        pwszLinkLayerAddress = FormatLinkLayerAddress(
                      NCE->LinkLayerAddressLength, (u_char *)(NCE + 1));
    }

    if (!dwCount) {
        PWCHAR pwszFriendlyName;

        dwErr = MapIpv6IfIndexToFriendlyName(NCE->Query.IF.Index, pAdapterInfo,
                                             &pwszFriendlyName);
        if (dwErr != NO_ERROR) {
            return dwErr;
        }

        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_CACHE_HDR, 
                       NCE->Query.IF.Index, pwszFriendlyName);
    }

    pwszUnreachable = MakeString(g_hModule, MSG_IPV6_NEIGHBOR_UNREACHABLE);

    DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_CACHE_ENTRY,
                   FormatIPv6Address(&NCE->Query.Address, 0),
                   ((NCE->IsUnreachable)? pwszUnreachable : pwszLinkLayerAddress));
    FreeString(pwszUnreachable);

    switch (NCE->NDState) {
    case ND_STATE_INCOMPLETE:
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_INCOMPLETE);
        break;

    case ND_STATE_PROBE:
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_PROBE);
        break;

    case ND_STATE_DELAY:
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_DELAY);
        break;

    case ND_STATE_STALE:
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_STALE);
        break;

    case ND_STATE_REACHABLE:
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_REACHABLE,
                       NCE->ReachableTimer / 1000);
        break;

    case ND_STATE_PERMANENT:
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_PERMANENT);
        break;

    default:
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_UNKNOWN,
                       NCE->NDState);
        break;
    }

    if (NCE->IsRouter) {
        DisplayMessage(g_hModule, MSG_IPV6_NEIGHBOR_ISROUTER);
    }

    DisplayMessage(g_hModule, MSG_NEWLINE);

    return NO_ERROR;
}

DWORD
ForEachNeighborCacheEntry(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD (*pfnFunc)(IPV6_INFO_NEIGHBOR_CACHE *,PIP_ADAPTER_ADDRESSES,DWORD)
    )
{
    IPV6_QUERY_NEIGHBOR_CACHE Query, NextQuery;
    IPV6_INFO_NEIGHBOR_CACHE *NCE;
    DWORD dwInfoSize, dwBytesReturned;
    DWORD dwCount = 0;

    dwInfoSize = sizeof *NCE + MAX_LINK_LAYER_ADDRESS_LENGTH;
    NCE = (IPV6_INFO_NEIGHBOR_CACHE *) MALLOC(dwInfoSize);
    if (NCE == NULL) {
        return 0;
    }

    NextQuery.IF = IF->This;
    NextQuery.Address = in6addr_any;

    for (;;) {
        Query = NextQuery;

        if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_NEIGHBOR_CACHE,
                             &Query, sizeof Query,
                             NCE, dwInfoSize, &dwBytesReturned,
                             NULL)) {
            return dwCount;
        }

        NextQuery = NCE->Query;

        if (!IN6_ADDR_EQUAL(&Query.Address, &in6addr_any)) {

            if ((dwBytesReturned < sizeof *NCE) ||
                (dwBytesReturned != sizeof *NCE + NCE->LinkLayerAddressLength)) {
                return dwCount;
            }

            NCE->Query = Query;
            if ((*pfnFunc)(NCE,pAdapterInfo,dwCount) == NO_ERROR) {
                dwCount++;
            }
        }

        if (IN6_ADDR_EQUAL(&NextQuery.Address, &in6addr_any))
            break;
    }

    FREE(NCE);

    return dwCount;
}

DWORD
PrintNeighborCache(
    IN IPV6_INFO_INTERFACE *IF, 
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwIfCount,
    IN FORMAT Format
    )
{
    DWORD dwCount = ForEachNeighborCacheEntry(IF, pAdapterInfo, 
                                              PrintNeighborCacheEntry);
    return (dwCount > 0)? NO_ERROR : ERROR_NO_DATA;
}

IPV6_INFO_NEIGHBOR_CACHE *
GetNeighborCacheEntry(
    IN IPV6_INFO_INTERFACE *IF, 
    IN IN6_ADDR *Address
    )
{
    IPV6_QUERY_NEIGHBOR_CACHE Query;
    IPV6_INFO_NEIGHBOR_CACHE *NCE;
    DWORD dwInfoSize, dwBytesReturned;

    dwInfoSize = sizeof *NCE + MAX_LINK_LAYER_ADDRESS_LENGTH;
    NCE = (IPV6_INFO_NEIGHBOR_CACHE *) malloc(dwInfoSize);
    if (NCE == NULL) {
        return NULL;
    }

    Query.IF = IF->This;
    Query.Address = *Address;

    if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_NEIGHBOR_CACHE,
                         &Query, sizeof Query,
                         NCE, dwInfoSize, &dwBytesReturned,
                         NULL)) {
        return NULL;
    }

    if ((dwBytesReturned < sizeof *NCE) ||
        (dwBytesReturned != sizeof *NCE + NCE->LinkLayerAddressLength)) {
        return NULL;
    }

    NCE->Query = Query;
    return NCE;
}

DWORD
QueryNeighborCache(
    IN PWCHAR pwszInterface,
    IN IN6_ADDR *pipAddress
    )
{
    IPV6_INFO_INTERFACE *IF;
    IPV6_INFO_NEIGHBOR_CACHE *NCE;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;
    DWORD dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (!pwszInterface) {
        ForEachInterface(PrintNeighborCache, pAdapterInfo, FORMAT_NORMAL, 
                         FALSE);
        FREE(pAdapterInfo);
        return NO_ERROR;
    } 

    dwErr = GetInterfaceByFriendlyName(pwszInterface, pAdapterInfo, FALSE, &IF);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (!pipAddress) {
        PrintNeighborCache(IF, pAdapterInfo, 0, FALSE);
    } else {
        NCE = GetNeighborCacheEntry(IF, pipAddress);
        PrintNeighborCacheEntry(NCE, pAdapterInfo, 0);
        FREE(NCE);
    }

    FREE(IF);
    FREE(pAdapterInfo);
    return dwErr;
}

DWORD
FlushNeighborCacheForInterface(
    IN IPV6_INFO_INTERFACE *IF, 
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwCount,
    IN FORMAT Format
    )
{
    IPV6_QUERY_NEIGHBOR_CACHE Query;
    DWORD dwBytesReturned;

    Query.IF = IF->This;
    Query.Address = in6addr_any;

    if (!DeviceIoControl(Handle, IOCTL_IPV6_FLUSH_NEIGHBOR_CACHE,
                         &Query, sizeof Query,
                         NULL, 0, &dwBytesReturned, NULL)) {
        return GetLastError();
    }

    return NO_ERROR;
}

DWORD
FlushNeighborCache(
    IN PWCHAR pwszInterface,
    IN IN6_ADDR *pipAddress
    )
{
    IPV6_QUERY_NEIGHBOR_CACHE Query;
    DWORD dwBytesReturned, dwErr;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (!pwszInterface) {
        ForEachInterface(FlushNeighborCacheForInterface, NULL, FORMAT_NORMAL, 
                         FALSE);
        return NO_ERROR;
    } 

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MapFriendlyNameToIpv6IfIndex(pwszInterface,
                                         pAdapterInfo,
                                         &Query.IF.Index);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (pipAddress) {
        Query.Address = *pipAddress;
    } else {
        Query.Address = in6addr_any;
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_FLUSH_NEIGHBOR_CACHE,
                         &Query, sizeof Query,
                         NULL, 0, &dwBytesReturned, NULL)) {
        return GetLastError();
    }

    return ERROR_OKAY;
}

//////////////////////////////////////////////////////////////////////////////
// Destination cache functions
//////////////////////////////////////////////////////////////////////////////

DWORD
PrintDestination(
    IN IPV6_INFO_ROUTE_CACHE *RCE,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwCount,
    IN FORMAT Format
    )
{
    DWORD dwErr;
    PWCHAR pwszTemp;
    WCHAR wszTime[64];
    
    if (!dwCount) {
        PWCHAR pwszFriendlyName;

        dwErr = MapIpv6IfIndexToFriendlyName(RCE->Query.IF.Index, pAdapterInfo,
                                             &pwszFriendlyName);
        if (dwErr != NO_ERROR) {
            return dwErr;
        }

        DisplayMessage(g_hModule, 
                       ((Format == FORMAT_VERBOSE)? MSG_IPV6_DESTINATION_HDR_VERBOSE : MSG_IPV6_DESTINATION_HDR), 
                       RCE->Query.IF.Index, pwszFriendlyName);
    }

    DisplayMessage(g_hModule, 
                   ((Format == FORMAT_VERBOSE)? MSG_IPV6_DESTINATION_ENTRY_VERBOSE : MSG_IPV6_DESTINATION_ENTRY),
                   ((RCE->PathMTU == 0)? IPv6_MINIMUM_MTU : RCE->PathMTU),
                   FormatIPv6Address(&RCE->Query.Address, 0));
    DisplayMessage(g_hModule, 
                   ((Format == FORMAT_VERBOSE)? MSG_IPV6_DESTINATION_NEXTHOP_VERBOSE : MSG_IPV6_DESTINATION_NEXTHOP),
                   FormatIPv6Address(&RCE->NextHopAddress, 0));

    if (Format == FORMAT_VERBOSE) {

        DisplayMessage(g_hModule, 
                       MSG_IPV6_DESTINATION_SOURCE_ADDR,
                       FormatIPv6Address(&RCE->SourceAddress, 0));

        pwszTemp = MakeString(g_hModule, ((RCE->Valid)? STRING_NO : STRING_YES));
        DisplayMessage(g_hModule, MSG_IPV6_STALE, pwszTemp);

        FreeString(pwszTemp);
    
        switch (RCE->Flags) {
        case RCE_FLAG_CONSTRAINED:
            DisplayMessage(g_hModule, MSG_IPV6_IF_SPECIFIC);
            break;
    
        case RCE_FLAG_CONSTRAINED_SCOPEID:
            DisplayMessage(g_hModule, MSG_IPV6_ZONE_SPECIFIC);
            break;
        }

        if (RCE->PMTUProbeTimer != INFINITE_LIFETIME) {
            DisplayMessage(g_hModule, MSG_IPV6_PMTU_PROBE_TIME,
                           RCE->PMTUProbeTimer/1000);
                           // FormatTime(RCE->PMTUProbeTimer/1000, wszTime));
        }
    
        if ((RCE->ICMPLastError != 0) && (RCE->ICMPLastError < 10*60*1000)) {
            DisplayMessage(g_hModule, MSG_IPV6_ICMP_ERROR_TIME,
                           RCE->ICMPLastError/1000);
                           // FormatTime(RCE->ICMPLastError/1000, wszTime));
        }
    
        if ((RCE->BindingSeqNumber != 0) ||
            (RCE->BindingLifetime != 0) ||
            ! IN6_ADDR_EQUAL(&RCE->CareOfAddress, &in6addr_any)) {

            DisplayMessage(g_hModule, MSG_IPV6_CAREOF,
                   FormatIPv6Address(&RCE->CareOfAddress, 0),
                   RCE->BindingSeqNumber,
                   RCE->BindingLifetime);
                   // FormatTime(RCE->BindingLifetime, wszTime));
        }
    }

    return NO_ERROR;
}

DWORD
ForEachDestination(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format,
    IN DWORD (*pfnFunc)(IPV6_INFO_ROUTE_CACHE *,PIP_ADAPTER_ADDRESSES,DWORD,FORMAT)
    )
{
    IPV6_QUERY_ROUTE_CACHE Query, NextQuery;
    IPV6_INFO_ROUTE_CACHE RCE;
    DWORD dwInfoSize, dwBytesReturned, dwCount = 0;

    NextQuery.IF.Index = 0;
    NextQuery.Address = in6addr_any;

    for (;;) {
        Query = NextQuery;

        if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_ROUTE_CACHE,
                             &Query, sizeof Query,
                             &RCE, sizeof(RCE), &dwBytesReturned,
                             NULL)) {
            return dwCount;
        }

        NextQuery = RCE.Query;

        if (Query.IF.Index == IF->This.Index) {
            RCE.Query = Query;
            if ((*pfnFunc)(&RCE,pAdapterInfo,dwCount,Format) == NO_ERROR) {
                dwCount++;
            }
        } else if (dwCount > 0) {
            //
            // Stop if we're done with the desired interface.
            //
            break;
        }

        if (NextQuery.IF.Index == 0) {
            break;
        }
    }

    return dwCount;
}

IPV6_INFO_ROUTE_CACHE *
GetDestination(
    IN IPV6_QUERY_INTERFACE *IF,
    IN IN6_ADDR *Address
    )
{
    IPV6_QUERY_ROUTE_CACHE Query;
    IPV6_INFO_ROUTE_CACHE *RCE;
    DWORD dwBytesReturned;

    Query.IF = *IF;
    Query.Address = *Address;

    RCE = (IPV6_INFO_ROUTE_CACHE *) malloc(sizeof *RCE);
    if (RCE == NULL) {
        return NULL;
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_QUERY_ROUTE_CACHE,
                         &Query, sizeof Query,
                         RCE, sizeof *RCE, &dwBytesReturned,
                         NULL)) {
        return NULL;
    }

    RCE->Query = Query;
    return RCE;
}


DWORD
PrintRouteCache(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwIfCount,
    IN FORMAT Format
    )
{
    DWORD dwCount = ForEachDestination(IF, pAdapterInfo, Format, PrintDestination);
    return (dwCount > 0)? NO_ERROR : ERROR_NO_DATA;
}

DWORD
QueryRouteCache(
    IN PWCHAR pwszInterface,
    IN IN6_ADDR *pipAddress,
    IN FORMAT Format
    )
{
    IPV6_INFO_INTERFACE *IF;
    IPV6_INFO_ROUTE_CACHE *RCE;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;
    DWORD dwCount, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (!pwszInterface) {
        dwCount = ForEachInterface(PrintRouteCache, pAdapterInfo, Format, 
                                   FALSE);
        FREE(pAdapterInfo);
        if (dwCount == 0) {
            DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
        }
        return NO_ERROR;
    } 

    dwErr = GetInterfaceByFriendlyName(pwszInterface, pAdapterInfo, FALSE, &IF);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (!pipAddress) {
        if (PrintRouteCache(IF, pAdapterInfo, 0, Format) == ERROR_NO_DATA) {
            DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
        }
    } else {
        RCE = GetDestination(&IF->This, pipAddress);
        PrintDestination(RCE, pAdapterInfo, 0, Format);
        FREE(RCE);
    }

    FREE(IF);
    FREE(pAdapterInfo);
    return dwErr;
}


DWORD
FlushRouteCacheForInterface(
    IN IPV6_INFO_INTERFACE *IF,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN DWORD dwCount,
    IN FORMAT Format
    )
{
    IPV6_QUERY_ROUTE_CACHE Query;
    DWORD dwBytesReturned;

    Query.IF = IF->This;
    Query.Address = in6addr_any;

    if (!DeviceIoControl(Handle, IOCTL_IPV6_FLUSH_ROUTE_CACHE,
                         &Query, sizeof Query,
                         NULL, 0, &dwBytesReturned, NULL)) {
        return GetLastError();
    }

    return NO_ERROR;
}

DWORD
FlushRouteCache(
    IN PWCHAR pwszInterface,
    IN IN6_ADDR *pipAddress
    )
{
    IPV6_QUERY_ROUTE_CACHE Query;
    DWORD dwBytesReturned, dwErr;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (!pwszInterface) {
        ForEachInterface(FlushRouteCacheForInterface, NULL, FORMAT_NORMAL, 
                         FALSE);
        return NO_ERROR;
    } 

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MapFriendlyNameToIpv6IfIndex(pwszInterface,
                                         pAdapterInfo,
                                         &Query.IF.Index);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (pipAddress) {
        Query.Address = *pipAddress;
    } else {
        Query.Address = in6addr_any;
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_FLUSH_ROUTE_CACHE,
                         &Query, sizeof Query,
                         NULL, 0, &dwBytesReturned, NULL)) {
        return GetLastError();
    }

    return ERROR_OKAY;
}

//////////////////////////////////////////////////////////////////////////////
// Route table functions
//////////////////////////////////////////////////////////////////////////////

DWORD
ForEachRoute(
    IN DWORD (*pfnFunc)(IPV6_INFO_ROUTE_TABLE *, DWORD, DWORD, PIP_ADAPTER_ADDRESSES, FORMAT), 
    IN DWORD dwArg,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    IPV6_QUERY_ROUTE_TABLE Query, NextQuery;
    IPV6_INFO_ROUTE_TABLE RTE;
    DWORD dwBytesReturned, dwCount = 0;

    ZeroMemory(&NextQuery, sizeof(NextQuery));

    for (;;) {
        Query = NextQuery;

        if (!DeviceIoControl(Handle, 
                             (bPersistent)? IOCTL_IPV6_PERSISTENT_QUERY_ROUTE_TABLE : IOCTL_IPV6_QUERY_ROUTE_TABLE,
                             &Query, sizeof Query,
                             &RTE, sizeof RTE, &dwBytesReturned,
                             NULL)) {
            return dwCount;
        }

        NextQuery = RTE.Next;

        if (Query.Neighbor.IF.Index != 0) {

            RTE.This = Query;
            if ((*pfnFunc)(&RTE, dwArg, dwCount, pAdapterInfo, Format) == NO_ERROR) {
                dwCount++;
            }
        }

        if (NextQuery.Neighbor.IF.Index == 0)
            break;
    }

    return dwCount;
}

//
// These are RFC 2465 ipv6RouteProtocol values, and must match
// RTE_TYPE_... in ntddip6.h.
//
DWORD RteTypeMsg[] = { 0,0, 
                       STRING_SYSTEM, 
                       STRING_MANUAL,
                       STRING_AUTOCONF,
                       STRING_RIP,
                       STRING_OSPF,
                       STRING_BGP,
                       STRING_IDRP,
                       STRING_IGRP
                     };
#define RTE_TYPE_MSG_COUNT (sizeof(RteTypeMsg)/sizeof(DWORD))

DWORD
PrintRouteTableEntry(
    IN IPV6_INFO_ROUTE_TABLE *RTE, 
    IN DWORD dwArg,
    IN DWORD dwCount,
    IN PIP_ADAPTER_ADDRESSES pAdapterInfo,
    IN FORMAT Format
    )
{
    DWORD dwTypeMsg, dwErr;
    PWCHAR pwszPublishMsg, pwszFriendlyName;

    if (Format != FORMAT_VERBOSE) {
        //
        // Suppress system routes (used for loopback).
        //
        if (RTE->Type == RTE_TYPE_SYSTEM) {
            return ERROR_NO_DATA;
        }
    }

    dwErr = MapIpv6IfIndexToFriendlyName(RTE->This.Neighbor.IF.Index,
                                         pAdapterInfo, &pwszFriendlyName);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    pwszPublishMsg = TOKEN_VALUE_NO;
    if (RTE->Publish) {
        pwszPublishMsg = (RTE->Immortal)? TOKEN_VALUE_YES : TOKEN_VALUE_AGE;
    }

    if (Format == FORMAT_DUMP) {
        DisplayMessageT(DMP_IPV6_ADD_ROUTE);
        DisplayMessageT(DMP_STRING_ARG, TOKEN_PREFIX,
                       FormatIPv6Prefix(&RTE->This.Prefix,
                                        RTE->This.PrefixLength));
        DisplayMessageT(DMP_QUOTED_STRING_ARG, TOKEN_INTERFACE,
                        pwszFriendlyName);
        DisplayMessageT(DMP_INTEGER_ARG, TOKEN_METRIC, RTE->Preference);

        if (!IN6_ADDR_EQUAL(&RTE->This.Neighbor.Address, &in6addr_any)) {
            DisplayMessageT(DMP_STRING_ARG, TOKEN_NEXTHOP,
                            FormatIPv6Address(&RTE->This.Neighbor.Address, 0));
        }

        if (RTE->Publish) {
            DisplayMessageT(DMP_STRING_ARG, TOKEN_PUBLISH, pwszPublishMsg);

            if ((RTE->SitePrefixLength != 0) &&
                IN6_ADDR_EQUAL(&RTE->This.Neighbor.Address, &in6addr_any)) {

                DisplayMessageT(DMP_STRING_ARG, TOKEN_SITEPREFIXLENGTH,
                                RTE->SitePrefixLength);
            }
        }
        DisplayMessage(g_hModule, MSG_NEWLINE);
    } else {
        WCHAR wszValid[64], wszPreferred[64];
        PWCHAR pwszTemp, pwszPrefix, pwszGateway;
        IPV6_INFO_INTERFACE *IF;

        if ((Format == FORMAT_NORMAL) && (dwCount == 0)) {
            DisplayMessage(g_hModule, MSG_IPV6_ROUTE_TABLE_HDR);
        }

        dwTypeMsg = (RTE->Type < RTE_TYPE_MSG_COUNT)? RteTypeMsg[RTE->Type] : STRING_UNKNOWN;
        pwszTemp = MakeString(g_hModule, dwTypeMsg);

        pwszPrefix = FormatIPv6Prefix(&RTE->This.Prefix,
                                      RTE->This.PrefixLength);

        if (IN6_ADDR_EQUAL(&RTE->This.Neighbor.Address, &in6addr_any)) {
            pwszGateway = pwszFriendlyName;
        } else {
            pwszGateway = FormatIPv6Address(&RTE->This.Neighbor.Address, 0);
        }

        IF = GetInterfaceByIpv6IfIndex(RTE->This.Neighbor.IF.Index);
        if (!IF) {
            FreeString(pwszTemp);
            return ERROR_NO_DATA;
        }

        DisplayMessage(g_hModule, 
                       ((Format == FORMAT_VERBOSE)? MSG_IPV6_ROUTE_TABLE_ENTRY_VERBOSE : MSG_IPV6_ROUTE_TABLE_ENTRY),
                       pwszPrefix,
                       RTE->This.Neighbor.IF.Index,
                       pwszGateway,
                       IF->Preference + RTE->Preference,
                       pwszPublishMsg,
                       pwszTemp,
                       pwszFriendlyName,
                       RTE->ValidLifetime,
                       // FormatTime(RTE->ValidLifetime, wszValid),
                       RTE->PreferredLifetime,
                       // FormatTime(RTE->PreferredLifetime, wszPreferred),
                       RTE->SitePrefixLength);

        FREE(IF);

        FreeString(pwszTemp);
    }

    return NO_ERROR;
}

DWORD
QueryRouteTable(
    IN FORMAT Format,
    IN BOOL bPersistent
    )
{
    DWORD dwErr, dwCount = 0;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    if (Format != FORMAT_DUMP) {
        // DisplayMessage(g_hModule, (bPersistent)? MSG_PERSISTENT : MSG_ACTIVE);
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != ERROR_NO_DATA) {
        
        if (dwErr != NO_ERROR) {
            return dwErr;
        }

        dwCount = ForEachRoute(PrintRouteTableEntry, 0, pAdapterInfo, Format,
                               bPersistent);
    
        FREE(pAdapterInfo);
    }

    if ((Format != FORMAT_DUMP) && !dwCount) {
        DisplayMessage(g_hModule, MSG_IP_NO_ENTRIES);
    }

    return NO_ERROR;
}

DWORD 
UpdateRouteTable(
    IN IN6_ADDR *pipPrefix, 
    IN DWORD dwPrefixLength,
    IN PWCHAR pwszIfFriendlyName, 
    IN IN6_ADDR *pipNextHop,
    IN DWORD dwMetric, 
    IN PUBLISH Publish,
    IN DWORD dwSitePrefixLength,
    IN DWORD dwValidLifetime,
    IN DWORD dwPreferredLifetime,
    IN BOOL bPersistent
    )
{
    IPV6_INFO_ROUTE_TABLE Route;
    DWORD dwBytesReturned;
    PIP_ADAPTER_ADDRESSES pAdapterInfo;
    DWORD dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MyGetAdaptersInfo(&pAdapterInfo);
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = MapFriendlyNameToIpv6IfIndex(pwszIfFriendlyName, pAdapterInfo,
                                         &Route.This.Neighbor.IF.Index);

    FREE(pAdapterInfo);

    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    Route.This.Prefix = *pipPrefix;
    Route.This.PrefixLength = dwPrefixLength;
    Route.This.Neighbor.Address = (pipNextHop)? *pipNextHop : in6addr_any;

    Route.SitePrefixLength = dwSitePrefixLength;
    Route.ValidLifetime = dwValidLifetime;
    Route.PreferredLifetime = dwPreferredLifetime;
    Route.Preference = dwMetric;
    Route.Type = RTE_TYPE_MANUAL;
    switch (Publish) {
    case PUBLISH_IMMORTAL:
        Route.Publish  = TRUE;
        Route.Immortal = TRUE;
        break;
    case PUBLISH_AGE:
        Route.Publish  = TRUE;
        Route.Immortal = FALSE;
        break;
    case PUBLISH_NO:
        Route.Publish  = FALSE;
        Route.Immortal = FALSE;
        break;
    default:
        ASSERT(FALSE);
    }

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_UPDATE_ROUTE_TABLE,
                             &Route, sizeof Route,
                             NULL, 0,
                             &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_UPDATE_ROUTE_TABLE,
                         &Route, sizeof Route,
                         NULL, 0, &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}

DWORD
ResetIpv6Config(
    IN BOOL bPersistent
    )
{
    DWORD dwBytesReturned, dwErr;

    dwErr = OpenIPv6();
    if (dwErr != NO_ERROR) {
        return dwErr;
    }

    dwErr = ERROR_OKAY;

    if (bPersistent) {
        if (!DeviceIoControl(Handle, IOCTL_IPV6_PERSISTENT_RESET,
                             NULL, 0,
                             NULL, 0, &dwBytesReturned, NULL)) {
            dwErr = GetLastError();
        }
    }

    if (!DeviceIoControl(Handle, IOCTL_IPV6_RESET,
                         NULL, 0,
                         NULL, 0, &dwBytesReturned, NULL)) {
        if (dwErr == ERROR_OKAY) {
            dwErr = GetLastError();
        }
    }

    return dwErr;
}