/*++

Copyright (c) 1996-1999  Microsoft Corporation

Module Name:

    interface.c

Abstract:

    Implements the Node Manager network interface management routines.

Author:

    Mike Massa (mikemas) 7-Nov-1996


Revision History:

--*/


#include "nmp.h"
#include <iphlpapi.h>
#include <iprtrmib.h>
#include <ntddndis.h>
#include <ndispnp.h>


/////////////////////////////////////////////////////////////////////////////
//
// Data
//
/////////////////////////////////////////////////////////////////////////////
#define    NM_MAX_IF_PING_ENUM_SIZE      10
#define    NM_MAX_UNION_PING_ENUM_SIZE    5


LIST_ENTRY          NmpInterfaceList = {NULL, NULL};
LIST_ENTRY          NmpDeletedInterfaceList = {NULL, NULL};
DWORD               NmpInterfaceCount = 0;
WCHAR               NmpUnknownString[] = L"<Unknown>";
WCHAR               NmpNullString[] = L"";


RESUTIL_PROPERTY_ITEM
NmpInterfaceProperties[] =
    {
        {
            L"Id", NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, Id)
        },
        {
            CLUSREG_NAME_NETIFACE_NAME, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, Name)
        },
        {
            CLUSREG_NAME_NETIFACE_DESC, NULL, CLUSPROP_FORMAT_SZ,
            (DWORD_PTR) NmpNullString, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, Description)
        },
        {
            CLUSREG_NAME_NETIFACE_NODE, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, NodeId)
        },
        {
            CLUSREG_NAME_NETIFACE_NETWORK, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, NetworkId)
        },
        {
            CLUSREG_NAME_NETIFACE_ADAPTER_NAME, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, AdapterName)
        },
        {
            CLUSREG_NAME_NET_ADDRESS, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, Address)
        },
        {
            CLUSREG_NAME_NETIFACE_ENDPOINT, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, ClusnetEndpoint)
        },
        {
            CLUSREG_NAME_NETIFACE_STATE, NULL, CLUSPROP_FORMAT_DWORD,
            0, 0, 0xFFFFFFFF,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, State)
        },
        {
            CLUSREG_NAME_NETIFACE_ADAPTER_ID, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_INTERFACE_INFO2, AdapterId)
        },
        {
            0
        }
    };


/////////////////////////////////////////////////////////////////////////////
//
// Initialization & cleanup routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpInitializeInterfaces(
    VOID
    )
/*++

Routine Description:

    Initializes network interface resources.

Arguments:

    None.

Return Value:

   A Win32 status value.

--*/

{
    DWORD                       status;
    OM_OBJECT_TYPE_INITIALIZE   objectTypeInitializer;


    ClRtlLogPrint(LOG_NOISE,"[NM] Initializing network interfaces.\n");

    //
    // Create the network interface object type
    //
    ZeroMemory(&objectTypeInitializer, sizeof(OM_OBJECT_TYPE_INITIALIZE));
    objectTypeInitializer.ObjectSize = sizeof(NM_INTERFACE);
    objectTypeInitializer.Signature = NM_INTERFACE_SIG;
    objectTypeInitializer.Name = L"Network Interface";
    objectTypeInitializer.DeleteObjectMethod = &NmpDestroyInterfaceObject;

    status = OmCreateType(ObjectTypeNetInterface, &objectTypeInitializer);

    if (status != ERROR_SUCCESS) {
        WCHAR  errorString[12];
        wsprintfW(&(errorString[0]), L"%u", status);
        CsLogEvent1(LOG_CRITICAL, CS_EVENT_ALLOCATION_FAILURE, errorString);
        ClRtlLogPrint(LOG_CRITICAL,
            "[NM] Unable to create network interface object type, %1!u!\n",
            status
            );
    }

    return(status);

}  // NmpInitializeInterfaces


VOID
NmpCleanupInterfaces(
    VOID
    )
/*++

Routine Description:

    Destroys all existing network interface resources.

Arguments:

    None.

Return Value:

   None.

--*/

{
    PNM_INTERFACE  netInterface;
    PLIST_ENTRY    entry;
    DWORD          status;


    ClRtlLogPrint(
        LOG_NOISE, 
        "[NM] Interface cleanup starting...\n"
        );

    //
    // Now clean up all the interface objects.
    //
    NmpAcquireLock();

    while (!IsListEmpty(&NmpInterfaceList)) {

        entry = RemoveHeadList(&NmpInterfaceList);

        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, Linkage);
        CL_ASSERT(NM_OM_INSERTED(netInterface));

        NmpDeleteInterfaceObject(netInterface, FALSE);
    }

    NmpReleaseLock();

    ClRtlLogPrint(LOG_NOISE,"[NM] Network interface cleanup complete\n");

    return;

}  // NmpCleanupInterfaces


/////////////////////////////////////////////////////////////////////////////
//
// Top-level routines called during network configuration
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCreateInterface(
    IN RPC_BINDING_HANDLE    JoinSponsorBinding,
    IN PNM_INTERFACE_INFO2   InterfaceInfo
    )
/*++

Notes:

    Must not be called with NM lock held.

--*/
{
    DWORD   status;


    CL_ASSERT(InterfaceInfo->NetIndex == NmInvalidInterfaceNetIndex);

    if (JoinSponsorBinding != NULL) {
        //
        // We are joining a cluster. Ask the sponsor to do the dirty work.
        //
        status = NmRpcCreateInterface2(
                     JoinSponsorBinding,
                     NmpJoinSequence,
                     NmLocalNodeIdString,
                     InterfaceInfo
                     );
    }
    else if (NmpState == NmStateOnlinePending) {
        HLOCALXSACTION   xaction;

        //
        // We are forming a cluster. Add the definition directly to the
        // database. The corresponding object will be created later in
        // the form process.
        //

        //
        // Start a transaction - this must be done before acquiring the
        //                       NM lock.
        //
        xaction = DmBeginLocalUpdate();

        if (xaction == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to start a transaction, status %1!u!\n",
                status
                );
            return(status);
        }

        status = NmpCreateInterfaceDefinition(InterfaceInfo, xaction);

        //
        // Complete the transaction - this must be done after releasing
        //                            the NM lock.
        //
        if (status == ERROR_SUCCESS) {
            DmCommitLocalUpdate(xaction);
        }
        else {
            DmAbortLocalUpdate(xaction);
        }
    }
    else {
        //
        // We are online. This is a PnP update.
        //
        NmpAcquireLock();

        status = NmpGlobalCreateInterface(InterfaceInfo);

        NmpReleaseLock();
    }

    return(status);

}  // NmpCreateInterface


DWORD
NmpSetInterfaceInfo(
    IN RPC_BINDING_HANDLE    JoinSponsorBinding,
    IN PNM_INTERFACE_INFO2   InterfaceInfo
    )
/*++

Notes:

    Must not be called with NM lock held.

--*/
{
    DWORD   status;


    if (JoinSponsorBinding != NULL) {
        //
        // We are joining a cluster. Ask the sponsor to do the dirty work.
        //
        status = NmRpcSetInterfaceInfo2(
                     JoinSponsorBinding,
                     NmpJoinSequence,
                     NmLocalNodeIdString,
                     InterfaceInfo
                     );
    }
    else if (NmpState == NmStateOnlinePending) {
        //
        // We are forming a cluster. Update the database directly.
        //
        HLOCALXSACTION   xaction;


        //
        // Start a transaction - this must be done before acquiring the
        //                       NM lock.
        //
        xaction = DmBeginLocalUpdate();

        if (xaction == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to start a transaction, status %1!u!\n",
                status
                );
            return(status);
        }

        status = NmpSetInterfaceDefinition(InterfaceInfo, xaction);

        //
        // Complete the transaction - this must be done after releasing
        //                            the NM lock.
        //
        if (status == ERROR_SUCCESS) {
            DmCommitLocalUpdate(xaction);
        }
        else {
            DmAbortLocalUpdate(xaction);
        }
    }
    else {
        //
        // We are online. This is a PnP update.
        //
        NmpAcquireLock();

        status = NmpGlobalSetInterfaceInfo(InterfaceInfo);

        NmpReleaseLock();
    }

    return(status);

}  // NmpSetInterfaceInfo


DWORD
NmpDeleteInterface(
    IN     RPC_BINDING_HANDLE   JoinSponsorBinding,
    IN     LPWSTR               InterfaceId,
    IN     LPWSTR               NetworkId,
    IN OUT PBOOLEAN             NetworkDeleted
    )
/*++

Notes:

    Must not be called with NM lock held.

--*/
{
    DWORD                status;


    *NetworkDeleted = FALSE;

    if (JoinSponsorBinding != NULL) {
        //
        // We are joining a cluster. Ask the sponsor to perform the update.
        //
        status = NmRpcDeleteInterface(
                     JoinSponsorBinding,
                     NmpJoinSequence,
                     NmLocalNodeIdString,
                     InterfaceId,
                     NetworkDeleted
                     );
    }
    else if (NmpState == NmStateOnlinePending) {
        //
        // We are forming a cluster. Update the database directly.
        //
        HLOCALXSACTION  xaction;

        //
        // Start a transaction - this must be done before acquiring the
        //                       NM lock.
        //
        xaction = DmBeginLocalUpdate();

        if (xaction == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to start a transaction, status %1!u!\n",
                status
                );
            return(status);
        }

        //
        // Delete the interface from the database.
        //
        status = DmLocalDeleteTree(xaction, DmNetInterfacesKey, InterfaceId);

        if (status == ERROR_SUCCESS) {
            PNM_INTERFACE_ENUM2   interfaceEnum = NULL;

            //
            // If this interface was the last one defined for the associated
            // network, delete the network.
            //
            status = NmpEnumInterfaceDefinitions(&interfaceEnum);

            if (status == ERROR_SUCCESS) {
                BOOLEAN              deleteNetwork = TRUE;
                PNM_INTERFACE_INFO2  interfaceInfo;
                DWORD                i;


                for (i=0; i<interfaceEnum->InterfaceCount; i++) {
                    interfaceInfo = &(interfaceEnum->InterfaceList[i]);

                    if (wcscmp(interfaceInfo->NetworkId, NetworkId) == 0) {
                        deleteNetwork = FALSE;
                        break;
                    }
                }

                if (deleteNetwork) {
                    status = DmLocalDeleteTree(
                                 xaction,
                                 DmNetworksKey,
                                 NetworkId
                                 );

                    if (status == ERROR_SUCCESS) {
                        *NetworkDeleted = TRUE;
                    }
                }

                ClNetFreeInterfaceEnum(interfaceEnum);
            }
        }

        //
        // Complete the transaction - this must be done after releasing
        //                            the NM lock.
        //
        if (status == ERROR_SUCCESS) {
            DmCommitLocalUpdate(xaction);
        }
        else {
            DmAbortLocalUpdate(xaction);
        }
    }
    else {
        //
        // We are online. This is a PnP update.
        //
        NmpAcquireLock();

        status = NmpGlobalDeleteInterface(
                     InterfaceId,
                     NetworkDeleted
                     );

        NmpReleaseLock();
    }

    return(status);

} // NmpDeleteInterface


/////////////////////////////////////////////////////////////////////////////
//
// Remote procedures called by active member nodes
//
/////////////////////////////////////////////////////////////////////////////
error_status_t
s_NmRpcReportInterfaceConnectivity(
    IN PRPC_ASYNC_STATE            AsyncState,
    IN handle_t                    IDL_handle,
    IN LPWSTR                      InterfaceId,
    IN PNM_CONNECTIVITY_VECTOR     ConnectivityVector
    )
{
    PNM_INTERFACE  netInterface;
    DWORD          status = ERROR_SUCCESS;
    RPC_STATUS     tempStatus;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)){
        netInterface = OmReferenceObjectById(
                           ObjectTypeNetInterface,
                           InterfaceId
                           );

        if (netInterface != NULL) {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Received connectivity report from node %1!u! (interface %2!u!) for network %3!ws! (%4!ws!).\n",
                netInterface->Node->NodeId,
                netInterface->NetIndex,
                OmObjectId(netInterface->Network),
                OmObjectName(netInterface->Network)
                );

            NmpProcessInterfaceConnectivityReport(
                netInterface,
                ConnectivityVector
                );

            OmDereferenceObject(netInterface);
        }
        else {
            status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Received connectivity report from unknown interface %1!ws!.\n",
                InterfaceId
                );
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process connectivity report.\n"
            );
    }

    NmpReleaseLock();

    tempStatus = RpcAsyncCompleteCall(AsyncState, &status);

    if(tempStatus != RPC_S_OK)
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] s_NmRpcReportInterfaceConnectivity(), Error Completing Async RPC call, status %1!u!\n",
            tempStatus
            );

    return(status);

} // s_NmRpcReportInterfaceConnectivity


error_status_t
s_NmRpcGetInterfaceOnlineAddressEnum(
    IN handle_t             IDL_handle,
    IN LPWSTR               InterfaceId,
    OUT PNM_ADDRESS_ENUM *  OnlineAddressEnum
    )
{
    PNM_INTERFACE  netInterface;
    DWORD          status = ERROR_SUCCESS;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received request to get online address enum for interface %1!ws!.\n",
        InterfaceId
        );

    *OnlineAddressEnum = NULL;

    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)){
        netInterface = OmReferenceObjectById(ObjectTypeNetInterface, InterfaceId);

        if (netInterface != NULL) {
            status = NmpBuildInterfaceOnlineAddressEnum(
                         netInterface,
                         OnlineAddressEnum
                         );

            OmDereferenceObject(netInterface);
        }
        else {
            status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] GetInterfaceOnlineAddressEnum: interface %1!ws! doesn't exist.\n",
                InterfaceId
                );
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process GetInterfaceOnlineAddressEnum request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

} // s_NmRpcGetInterfaceOnlineAddressEnum


error_status_t
s_NmRpcGetInterfacePingAddressEnum(
    IN handle_t             IDL_handle,
    IN LPWSTR               InterfaceId,
    IN PNM_ADDRESS_ENUM     OnlineAddressEnum,
    OUT PNM_ADDRESS_ENUM *  PingAddressEnum
    )
{
    PNM_INTERFACE  netInterface;
    DWORD          status = ERROR_SUCCESS;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received request to get ping address enum for interface %1!ws!.\n",
        InterfaceId
        );

    *PingAddressEnum = NULL;

    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)){
        netInterface = OmReferenceObjectById(ObjectTypeNetInterface, InterfaceId);

        if (netInterface != NULL) {
            status = NmpBuildInterfacePingAddressEnum(
                         netInterface,
                         OnlineAddressEnum,
                         PingAddressEnum
                         );

            OmDereferenceObject(netInterface);
        }
        else {
            status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] GetInterfacePingAddressEnum: interface %1!ws! doesn't exist.\n",
                InterfaceId
                );
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process GetInterfacePingAddressEnum request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

} // s_NmRpcGetInterfacePingAddressEnum


//
// Note: s_NmRpcDoInterfacePing returns void rather than CallStatus
//       due to a MIDL compiler error in an early beta of W2K. Since
//       the CallStatus is the final parameter, the format on the 
//       wire is the same; however, the call works in its current
//       format, so there is no point in changing it now.
//
void
s_NmRpcDoInterfacePing(
    IN  PRPC_ASYNC_STATE     AsyncState,
    IN  handle_t             IDL_handle,
    IN  LPWSTR               InterfaceId,
    IN  PNM_ADDRESS_ENUM     PingAddressEnum,
    OUT BOOLEAN *            PingSucceeded,
    OUT error_status_t *     CallStatus
    )
{
    PNM_INTERFACE  netInterface;
    DWORD          status = ERROR_SUCCESS;
    RPC_STATUS     tempStatus;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received request to ping targets for interface %1!ws!.\n",
        InterfaceId
        );

    *PingSucceeded = FALSE;

    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)){
        PNM_INTERFACE  netInterface = OmReferenceObjectById(
                                          ObjectTypeNetInterface,
                                          InterfaceId
                                          );

        if (netInterface != NULL) {
            PNM_NETWORK    network = netInterface->Network;

            if ( (network->LocalInterface == netInterface) &&
                 NmpIsNetworkRegistered(network)
               )
            {
                NmpReleaseLock();

                status = NmpDoInterfacePing(
                             netInterface,
                             PingAddressEnum,
                             PingSucceeded
                             );

                NmpAcquireLock();
            }
            else {
                status = ERROR_INVALID_PARAMETER;
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] RpcDoInterfacePing: interface %1!ws! isn't local.\n",
                    InterfaceId
                    );
            }

            OmDereferenceObject(netInterface);
        }
        else {
            status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] RpcDoInterfacePing: interface %1!ws! doesn't exist.\n",
                InterfaceId
                );
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process RpcDoInterfacePing request.\n"
            );
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Finished pinging targets for interface %1!ws!.\n",
        InterfaceId
        );

    NmpReleaseLock();

    *CallStatus = status;

    tempStatus = RpcAsyncCompleteCall(AsyncState, NULL);

    if(tempStatus != RPC_S_OK)
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] s_NmRpcDoInterfacePing() Failed to complete Async RPC call, status %1!u!\n",
            tempStatus
            );


    return;

}  // s_NmRpcDoInterfacePing

/////////////////////////////////////////////////////////////////////////////
//
// Remote procedures called by joining nodes
//
/////////////////////////////////////////////////////////////////////////////
error_status_t
s_NmRpcCreateInterface(
    IN handle_t            IDL_handle,
    IN DWORD               JoinSequence,  OPTIONAL
    IN LPWSTR              JoinerNodeId,  OPTIONAL
    IN PNM_INTERFACE_INFO  InterfaceInfo1
    )
{
    DWORD                status;
    NM_INTERFACE_INFO2   interfaceInfo2;

    //
    // Translate and call the V2.0 procedure.
    //
    CopyMemory(&interfaceInfo2, InterfaceInfo1, sizeof(NM_INTERFACE_INFO));

    //
    // The NetIndex isn't used in this call.
    //
    interfaceInfo2.NetIndex = NmInvalidInterfaceNetIndex;

    //
    // Use the unknown string for the adapter ID.
    //
    interfaceInfo2.AdapterId = NmpUnknownString;

    status = s_NmRpcCreateInterface2(
                 IDL_handle,
                 JoinSequence,
                 JoinerNodeId,
                 &interfaceInfo2
                 );

    return(status);

}  // s_NmRpcCreateInterface


error_status_t
s_NmRpcCreateInterface2(
    IN handle_t             IDL_handle,
    IN DWORD                JoinSequence,  OPTIONAL
    IN LPWSTR               JoinerNodeId,  OPTIONAL
    IN PNM_INTERFACE_INFO2  InterfaceInfo2
    )
{
    DWORD  status = ERROR_SUCCESS;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        PNM_NODE joinerNode = NULL;

        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Processing request to create new interface %1!ws! for joining node.\n",
            InterfaceInfo2->Id
            );

        if (lstrcmpW(JoinerNodeId, NmpInvalidJoinerIdString) != 0) {
            joinerNode = OmReferenceObjectById(
                             ObjectTypeNode,
                             JoinerNodeId
                             );

            if (joinerNode != NULL) {
                if ( (JoinSequence == NmpJoinSequence) &&
                     (NmpJoinerNodeId == joinerNode->NodeId) &&
                     (NmpSponsorNodeId == NmLocalNodeId) &&
                     !NmpJoinAbortPending
                   )
                {
                    CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                    CL_ASSERT(NmpJoinerUp == FALSE);
                    CL_ASSERT(NmpJoinTimer != 0);

                    //
                    // Suspend the join timer while we are working on
                    // behalf of the joiner. This precludes an abort
                    // from occuring as well.
                    //
                    NmpJoinTimer = 0;
                }
                else {
                    status = ERROR_CLUSTER_JOIN_ABORTED;
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] CreateInterface call for joining node %1!ws! failed because the join was aborted.\n",
                        JoinerNodeId
                        );
                }
            }
            else {
                status = ERROR_CLUSTER_NODE_NOT_MEMBER;
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NMJOIN] CreateInterface call for joining node %1!ws! failed because the node is not a member of the cluster.\n",
                    JoinerNodeId
                    );
            }
        }

        if (status == ERROR_SUCCESS) {
            CL_ASSERT(InterfaceInfo2->NetIndex == NmInvalidInterfaceNetIndex);
            //
            // Just to be safe
            //
            InterfaceInfo2->NetIndex = NmInvalidInterfaceNetIndex;

            status = NmpGlobalCreateInterface(InterfaceInfo2);

            if (joinerNode != NULL) {
                //
                // Verify that the join is still in progress
                //
                if ( (JoinSequence == NmpJoinSequence) &&
                     (NmpJoinerNodeId == joinerNode->NodeId)
                   )
                {
                    CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                    CL_ASSERT(NmpJoinerUp == FALSE);
                    CL_ASSERT(NmpSponsorNodeId == NmLocalNodeId);
                    CL_ASSERT(NmpJoinTimer == 0);
                    CL_ASSERT(NmpJoinAbortPending == FALSE);

                    if (status == ERROR_SUCCESS) {
                        //
                        // Restart the join timer.
                        //
                        NmpJoinTimer = NM_JOIN_TIMEOUT;

                    }
                    else {
                        //
                        // Abort the join
                        //
                        NmpJoinAbort(status, joinerNode);
                    }
                }
                else {
                    status = ERROR_CLUSTER_JOIN_ABORTED;
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] CreateInterface call for joining node %1!ws! failed because the join was aborted.\n",
                        JoinerNodeId
                        );
                }
            }
        }

        if (joinerNode != NULL) {
            OmDereferenceObject(joinerNode);
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Not in valid state to process CreateInterface request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // s_NmRpcCreateInterface2


error_status_t
s_NmRpcSetInterfaceInfo(
    IN handle_t             IDL_handle,
    IN DWORD                JoinSequence,  OPTIONAL
    IN LPWSTR               JoinerNodeId,  OPTIONAL
    IN PNM_INTERFACE_INFO   InterfaceInfo1
    )
{
    DWORD                status;
    NM_INTERFACE_INFO2   interfaceInfo2;

    //
    // Translate and call the V2.0 procedure.
    //
    CopyMemory(&interfaceInfo2, InterfaceInfo1, sizeof(NM_INTERFACE_INFO));

    //
    // The NetIndex is not used in this call.
    //
    interfaceInfo2.NetIndex = NmInvalidInterfaceNetIndex;

    //
    // Use the unknown string for the adapter ID.
    //
    interfaceInfo2.AdapterId = NmpUnknownString;

    status = s_NmRpcSetInterfaceInfo2(
                 IDL_handle,
                 JoinSequence,
                 JoinerNodeId,
                 &interfaceInfo2
                 );

    return(status);

}  // s_NmRpcSetInterfaceInfo


error_status_t
s_NmRpcSetInterfaceInfo2(
    IN handle_t              IDL_handle,
    IN DWORD                 JoinSequence,  OPTIONAL
    IN LPWSTR                JoinerNodeId,  OPTIONAL
    IN PNM_INTERFACE_INFO2   InterfaceInfo2
    )
{
    DWORD      status = ERROR_SUCCESS;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        PNM_NODE joinerNode = NULL;

        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Processing request to set info for interface %1!ws! for joining node.\n",
            InterfaceInfo2->Id
            );

        if (lstrcmpW(JoinerNodeId, NmpInvalidJoinerIdString) != 0) {
            joinerNode = OmReferenceObjectById(
                             ObjectTypeNode,
                             JoinerNodeId
                             );

            if (joinerNode != NULL) {
                if ( (JoinSequence == NmpJoinSequence) &&
                     (NmpJoinerNodeId == joinerNode->NodeId) &&
                     (NmpSponsorNodeId == NmLocalNodeId) &&
                     !NmpJoinAbortPending
                   )
                {
                    CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                    CL_ASSERT(NmpJoinerUp == FALSE);
                    CL_ASSERT(NmpJoinTimer != 0);

                    //
                    // Suspend the join timer while we are working on
                    // behalf of the joiner. This precludes an abort
                    // from occuring as well.
                    //
                    NmpJoinTimer = 0;
                }
                else {
                    status = ERROR_CLUSTER_JOIN_ABORTED;
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] SetInterfaceInfo call for joining node %1!ws! failed because the join was aborted.\n",
                        JoinerNodeId
                        );
                }
            }
            else {
                status = ERROR_CLUSTER_NODE_NOT_MEMBER;
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NMJOIN] SetInterfaceInfo call for joining node %1!ws! failed because the node is not a member of the cluster.\n",
                    JoinerNodeId
                    );
            }
        }

        if (status == ERROR_SUCCESS) {
            status = NmpGlobalSetInterfaceInfo(InterfaceInfo2);

            if (joinerNode != NULL) {
                //
                // Verify that the join is still in progress
                //
                if ( (JoinSequence == NmpJoinSequence) &&
                     (NmpJoinerNodeId == joinerNode->NodeId)
                   )
                {
                    CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                    CL_ASSERT(NmpJoinerUp == FALSE);
                    CL_ASSERT(NmpSponsorNodeId == NmLocalNodeId);
                    CL_ASSERT(NmpJoinTimer == 0);
                    CL_ASSERT(NmpJoinAbortPending == FALSE);

                    if (status == ERROR_SUCCESS) {
                        //
                        // Restart the join timer.
                        //
                        NmpJoinTimer = NM_JOIN_TIMEOUT;

                    }
                    else {
                        //
                        // Abort the join
                        //
                        NmpJoinAbort(status, joinerNode);
                    }
                }
                else {
                    status = ERROR_CLUSTER_JOIN_ABORTED;
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] SetInterfaceInfo call for joining node %1!ws! failed because the join was aborted.\n",
                        JoinerNodeId
                        );
                }
            }
        }

        if (joinerNode != NULL) {
            OmDereferenceObject(joinerNode);
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Not in valid state to process SetInterfaceInfo request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // s_NmRpcSetInterfaceInfo2


error_status_t
s_NmRpcDeleteInterface(
    IN  handle_t   IDL_handle,
    IN  DWORD      JoinSequence,  OPTIONAL
    IN  LPWSTR     JoinerNodeId,  OPTIONAL
    IN  LPWSTR     InterfaceId,
    OUT BOOLEAN *  NetworkDeleted
    )
{
    DWORD           status = ERROR_SUCCESS;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        PNM_NODE joinerNode = NULL;

        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Processing request to delete interface %1!ws! for joining node.\n",
            InterfaceId
            );

        if (lstrcmpW(JoinerNodeId, NmpInvalidJoinerIdString) != 0) {
            joinerNode = OmReferenceObjectById(
                             ObjectTypeNode,
                             JoinerNodeId
                             );

            if (joinerNode != NULL) {
                if ( (JoinSequence == NmpJoinSequence) &&
                     (NmpJoinerNodeId == joinerNode->NodeId) &&
                     (NmpSponsorNodeId == NmLocalNodeId) &&
                     !NmpJoinAbortPending
                   )
                {
                    CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                    CL_ASSERT(NmpJoinerUp == FALSE);
                    CL_ASSERT(NmpJoinTimer != 0);

                    //
                    // Suspend the join timer while we are working on
                    // behalf of the joiner. This precludes an abort
                    // from occuring as well.
                    //
                    NmpJoinTimer = 0;
                }
                else {
                    status = ERROR_CLUSTER_JOIN_ABORTED;
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] DeleteInterface call for joining node %1!ws! failed because the join was aborted.\n",
                        JoinerNodeId
                        );
                }
            }
            else {
                status = ERROR_CLUSTER_NODE_NOT_MEMBER;
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NMJOIN] DeleteInterface call for joining node %1!ws! failed because the node is not a member of the cluster.\n",
                    JoinerNodeId
                    );
            }
        }

        if (status == ERROR_SUCCESS) {

            status = NmpGlobalDeleteInterface(
                         InterfaceId,
                         NetworkDeleted
                         );

            if (joinerNode != NULL) {
                //
                // Verify that the join is still in progress
                //
                if ( (JoinSequence == NmpJoinSequence) &&
                     (NmpJoinerNodeId == joinerNode->NodeId)
                   )
                {
                    CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                    CL_ASSERT(NmpJoinerUp == FALSE);
                    CL_ASSERT(NmpSponsorNodeId == NmLocalNodeId);
                    CL_ASSERT(NmpJoinTimer == 0);
                    CL_ASSERT(NmpJoinAbortPending == FALSE);

                    if (status == ERROR_SUCCESS) {
                        //
                        // Restart the join timer.
                        //
                        NmpJoinTimer = NM_JOIN_TIMEOUT;
                    }
                    else {
                        //
                        // Abort the join
                        //
                        NmpJoinAbort(status, joinerNode);
                    }
                }
                else {
                    status = ERROR_CLUSTER_JOIN_ABORTED;
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] DeleteInterface call for joining node %1!ws! failed because the join was aborted.\n",
                        JoinerNodeId
                        );
                }
            }
        }

        if (joinerNode != NULL) {
            OmDereferenceObject(joinerNode);
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Not in valid state to process DeleteInterface request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // s_NmRpcDeleteInterface


error_status_t
NmpEnumInterfaceDefinitionsForJoiner(
    IN  DWORD                  JoinSequence,   OPTIONAL
    IN  LPWSTR                 JoinerNodeId,   OPTIONAL
    OUT PNM_INTERFACE_ENUM  *  InterfaceEnum1,
    OUT PNM_INTERFACE_ENUM2 *  InterfaceEnum2
    )
{
    DWORD     status = ERROR_SUCCESS;
    PNM_NODE  joinerNode = NULL;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Supplying interface information to joining node.\n"
            );

        if (lstrcmpW(JoinerNodeId, NmpInvalidJoinerIdString) != 0) {
            joinerNode = OmReferenceObjectById(
                             ObjectTypeNode,
                             JoinerNodeId
                             );

            if (joinerNode != NULL) {
                if ( (JoinSequence == NmpJoinSequence) &&
                     (NmpJoinerNodeId == joinerNode->NodeId) &&
                     (NmpSponsorNodeId == NmLocalNodeId) &&
                     !NmpJoinAbortPending
                   )
                {
                    CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                    CL_ASSERT(NmpJoinerUp == FALSE);
                    CL_ASSERT(NmpJoinTimer != 0);

                    //
                    // Suspend the join timer while we are working on
                    // behalf of the joiner. This precludes an abort
                    // from occuring as well.
                    //
                    NmpJoinTimer = 0;
                }
                else {
                    status = ERROR_CLUSTER_JOIN_ABORTED;
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] EnumInterfaceDefinitions call for joining node %1!ws! failed because the join was aborted.\n",
                        JoinerNodeId
                        );
                }
            }
            else {
                status = ERROR_CLUSTER_NODE_NOT_MEMBER;
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NMJOIN] EnumInterfaceDefinitions call for joining node %1!ws! failed because the node is not a member of the cluster.\n",
                    JoinerNodeId
                    );
            }
        }

        if (status == ERROR_SUCCESS) {
            if (InterfaceEnum1 != NULL) {
                status = NmpEnumInterfaceObjects1(InterfaceEnum1);
            }
            else {
                CL_ASSERT(InterfaceEnum2 != NULL);
                status = NmpEnumInterfaceObjects(InterfaceEnum2);
            }

            if (joinerNode != NULL) {
                if (status == ERROR_SUCCESS) {
                    //
                    // Restart the join timer.
                    //
                    NmpJoinTimer = NM_JOIN_TIMEOUT;
                }
                else {
                    ClRtlLogPrint(LOG_CRITICAL, 
                        "[NMJOIN] EnumInterfaceDefinitions failed, status %1!u!.\n",
                        status
                        );

                    //
                    // Abort the join
                    //
                    NmpJoinAbort(status, joinerNode);
                }
            }
        }

        if (joinerNode != NULL) {
            OmDereferenceObject(joinerNode);
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Not in valid state to process EnumInterfaceDefinitions request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // NmpEnumInterfaceDefinitionsForJoiner


error_status_t
s_NmRpcEnumInterfaceDefinitions(
    IN  handle_t              IDL_handle,
    IN  DWORD                 JoinSequence,   OPTIONAL
    IN  LPWSTR                JoinerNodeId,   OPTIONAL
    OUT PNM_INTERFACE_ENUM *  InterfaceEnum1
    )
{
    error_status_t  status;

    status = NmpEnumInterfaceDefinitionsForJoiner(
                 JoinSequence,
                 JoinerNodeId,
                 InterfaceEnum1,
                 NULL
                 );

    return(status);

}  // s_NmRpcEnumInterfaceDefinitions

error_status_t
s_NmRpcEnumInterfaceDefinitions2(
    IN  handle_t               IDL_handle,
    IN  DWORD                  JoinSequence,   OPTIONAL
    IN  LPWSTR                 JoinerNodeId,   OPTIONAL
    OUT PNM_INTERFACE_ENUM2 *  InterfaceEnum2
    )
{
    error_status_t  status;

    status = NmpEnumInterfaceDefinitionsForJoiner(
                 JoinSequence,
                 JoinerNodeId,
                 NULL,
                 InterfaceEnum2
                 );

    return(status);

}  // s_NmRpcEnumInterfaceDefinitions2


error_status_t
s_NmRpcReportJoinerInterfaceConnectivity(
    IN handle_t                    IDL_handle,
    IN DWORD                       JoinSequence,
    IN LPWSTR                      JoinerNodeId,
    IN LPWSTR                      InterfaceId,
    IN PNM_CONNECTIVITY_VECTOR     ConnectivityVector
    )
{
    DWORD status = ERROR_SUCCESS;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)){
        PNM_NODE joinerNode = OmReferenceObjectById(
                                  ObjectTypeNode,
                                  JoinerNodeId
                                  );

        if (joinerNode != NULL) {
            //
            // If the node is still joining, forward the report to the
            // leader. Note that reports may race with the node transitioning
            // to the up state, so accept reports from up nodes as well.
            //
            if ( ( (JoinSequence == NmpJoinSequence) &&
                   (NmpJoinerNodeId == joinerNode->NodeId) &&
                   (NmpSponsorNodeId == NmLocalNodeId) &&
                   !NmpJoinAbortPending
                 )
                 ||
                 NM_NODE_UP(joinerNode)
               )
            {
                PNM_INTERFACE  netInterface = OmReferenceObjectById(
                                                  ObjectTypeNetInterface,
                                                  InterfaceId
                                                  );

                if (netInterface != NULL) {
                    PNM_NETWORK   network = netInterface->Network;
                    LPCWSTR       networkId = OmObjectId(network);

                    if (NmpLeaderNodeId == NmLocalNodeId) {
                        //
                        // This node is the leader. Process the report.
                        //
                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Processing connectivity report from joiner"
                            "node %1!ws! for network %2!ws!.\n",
                            JoinerNodeId,
                            networkId
                            );

                        NmpProcessInterfaceConnectivityReport(
                            netInterface,
                            ConnectivityVector
                            );
                    }
                    else {

                        //
                        // Forward the report to the leader.
                        //
                        RPC_BINDING_HANDLE  binding;

                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Forwarding connectivity report from joiner "
                            "node %1!ws! for network %2!ws! to leader.\n",
                            JoinerNodeId,
                            networkId
                            );

                        binding = Session[NmpLeaderNodeId];
                        CL_ASSERT(binding != NULL);

                        OmReferenceObject(network);

                        status = NmpReportInterfaceConnectivity(
                                     binding,
                                     InterfaceId,
                                     ConnectivityVector,
                                     (LPWSTR) networkId
                                     );

                        if (status != ERROR_SUCCESS) {
                            ClRtlLogPrint(LOG_UNUSUAL, 
                                "[NM] Failed to forward connectivity report "
                                "from joiner node %1!ws! for network %2!ws!"
                                "to leader, status %3!u!\n",
                                JoinerNodeId,
                                networkId,
                                status
                                );
                        }

                        OmDereferenceObject(network);
                    }

                    OmDereferenceObject(netInterface);
                }
                else {
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NMJOIN] Rejecting connectivity report from joining "
                        "node %1!ws! because interface %2!ws! does not "
                        "exist.\n",
                        JoinerNodeId,
                        InterfaceId
                        );
                    status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
                }
            }
            else {
                status = ERROR_CLUSTER_JOIN_ABORTED;
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NMJOIN] Ignoring connectivity report from joining "
                    "node %1!ws! because the join was aborted.\n",
                    JoinerNodeId
                    );
            }

            OmDereferenceObject(joinerNode);
        }
        else {
            status = ERROR_CLUSTER_NODE_NOT_MEMBER;
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NMJOIN] Ignoring connectivity report from joining node "
                "%1!ws! because the joiner is not a member of the cluster.\n",
                JoinerNodeId
                );
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Not in valid state to process connectivity report "
            "from joiner node %1!ws!.\n",
            JoinerNodeId
            );
    }

    NmpReleaseLock();

    return(status);

} // s_NmRpcReportJoinerInterfaceConnectivity


/////////////////////////////////////////////////////////////////////////////
//
// Routines used to make global configuration changes when the node
// is online.
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpGlobalCreateInterface(
    IN PNM_INTERFACE_INFO2  InterfaceInfo
    )
/*++

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD  status;
    DWORD  interfacePropertiesSize;
    PVOID  interfaceProperties;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Issuing global update to create interface %1!ws!.\n",
        InterfaceInfo->Id
        );

    //
    // Marshall the info structures into property lists.
    //
    status = NmpMarshallObjectInfo(
                 NmpInterfaceProperties,
                 InterfaceInfo,
                 &interfaceProperties,
                 &interfacePropertiesSize
                 );

    if (status == ERROR_SUCCESS) {

        NmpReleaseLock();

        //
        // Issue a global update
        //
        status = GumSendUpdateEx(
                     GumUpdateMembership,
                     NmUpdateCreateInterface,
                     2,
                     interfacePropertiesSize,
                     interfaceProperties,
                     sizeof(interfacePropertiesSize),
                     &interfacePropertiesSize
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Global update to create interface %1!ws! failed, status %2!u!.\n",
                InterfaceInfo->Id,
                status
                );
        }

        MIDL_user_free(interfaceProperties);

        NmpAcquireLock();
    }
    else {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to marshall properties for new interface %1!ws!, status %2!u!\n",
            InterfaceInfo->Id,
            status
            );
    }

    return(status);

}  // NmpGlobalCreateInterface


DWORD
NmpGlobalSetInterfaceInfo(
    IN PNM_INTERFACE_INFO2   InterfaceInfo
    )
/*++

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD      status = ERROR_SUCCESS;
    DWORD      interfacePropertiesSize;
    PVOID      interfaceProperties;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Issuing global update to set info for interface %1!ws!.\n",
        InterfaceInfo->Id
        );

    //
    // Marshall the info structures into property lists.
    //
    status = NmpMarshallObjectInfo(
                 NmpInterfaceProperties,
                 InterfaceInfo,
                 &interfaceProperties,
                 &interfacePropertiesSize
                 );

    if (status == ERROR_SUCCESS) {
        NmpReleaseLock();

        //
        // Issue a global update
        //
        status = GumSendUpdateEx(
                     GumUpdateMembership,
                     NmUpdateSetInterfaceInfo,
                     2,
                     interfacePropertiesSize,
                     interfaceProperties,
                     sizeof(interfacePropertiesSize),
                     &interfacePropertiesSize
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Global update to set properties for interface %1!ws! failed, status %2!u!.\n",
                InterfaceInfo->Id,
                status
                );
        }

        MIDL_user_free(interfaceProperties);

        NmpAcquireLock();
    }
    else {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to marshall properties for interface %1!ws!, status %2!u!\n",
            InterfaceInfo->Id,
            status
            );
    }

    return(status);

}  // NmpGlobalSetInterfaceInfo


DWORD
NmpGlobalDeleteInterface(
    IN     LPWSTR    InterfaceId,
    IN OUT PBOOLEAN  NetworkDeleted
    )
/*++

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD           status = ERROR_SUCCESS;
    PNM_INTERFACE   netInterface;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Issuing global update to delete interface %1!ws!.\n",
        InterfaceId
        );

    //
    // Find the interface object
    //
    netInterface = OmReferenceObjectById(ObjectTypeNetInterface, InterfaceId);

    if (netInterface != NULL) {
        NmpReleaseLock();

        //
        // Issue a global update
        //
        status = GumSendUpdateEx(
                     GumUpdateMembership,
                     NmUpdateDeleteInterface,
                     1,
                     (lstrlenW(InterfaceId)+1) * sizeof(WCHAR),
                     InterfaceId
                     );

        NmpAcquireLock();

        if (status == ERROR_SUCCESS) {
            //
            // Check if the network was deleted too
            //
            if (netInterface->Network->Flags & NM_FLAG_DELETE_PENDING) {
                *NetworkDeleted = TRUE;
            }
            else {
                *NetworkDeleted = FALSE;
            }
        }
        else {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Global update to delete interface %1!ws! failed, status %2!u!.\n",
                InterfaceId,
                status
                );
        }

        OmDereferenceObject(netInterface);
    }
    else {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Unable to find interface %1!ws!.\n",
            InterfaceId
            );
        status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
    }

    return(status);

}  // NmpGlobalDeleteInterface


/////////////////////////////////////////////////////////////////////////////
//
// Routines called by other cluster service components
//
/////////////////////////////////////////////////////////////////////////////
CLUSTER_NETINTERFACE_STATE
NmGetInterfaceState(
    IN  PNM_INTERFACE  Interface
    )
/*++

Routine Description:



Arguments:



Return Value:


Notes:

   Because the caller must have a reference on the object and the
   call is so simple, there is no reason to put the call through the
   EnterApi/LeaveApi dance.

--*/
{
    CLUSTER_NETINTERFACE_STATE  state;


    NmpAcquireLock();

    state = Interface->State;

    NmpReleaseLock();

    return(state);

} // NmGetInterfaceState


DWORD
NmGetInterfaceForNodeAndNetwork(
    IN     LPCWSTR    NodeName,
    IN     LPCWSTR    NetworkName,
    OUT    LPWSTR *   InterfaceName
    )
/*++

Routine Description:

    Returns the name of the interface which connects a specified node
    to a specified network.

Arguments:

    NodeName - A pointer to the unicode name of the node.

    NetworkName - A pointer to the unicode name of the network.

    InterfaceName - On output, contains a pointer to the unicode name of the
                    interface.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

--*/
{
    DWORD      status;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        PNM_NODE   node = OmReferenceObjectByName(ObjectTypeNode, NodeName);

        if (node != NULL) {
            PNM_NETWORK   network = OmReferenceObjectByName(
                                        ObjectTypeNetwork,
                                        NetworkName
                                        );

            if (network != NULL) {
                PLIST_ENTRY     entry;
                PNM_INTERFACE   netInterface;


                status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;

                for (entry = node->InterfaceList.Flink;
                     entry != &(node->InterfaceList);
                     entry = entry->Flink
                    )
                {
                    netInterface = CONTAINING_RECORD(
                                       entry,
                                       NM_INTERFACE,
                                       NodeLinkage
                                       );

                    if (netInterface->Network == network) {
                        LPCWSTR  interfaceId = OmObjectName(netInterface);
                        DWORD    nameLength = NM_WCSLEN(interfaceId);

                        *InterfaceName = MIDL_user_allocate(nameLength);

                        if (*InterfaceName != NULL) {
                            lstrcpyW(*InterfaceName, interfaceId);
                            status = ERROR_SUCCESS;
                        }
                        else {
                            status = ERROR_NOT_ENOUGH_MEMORY;
                        }
                    }
                }

                OmDereferenceObject(network);
            }
            else {
                status = ERROR_CLUSTER_NETWORK_NOT_FOUND;
            }

            OmDereferenceObject(node);
        }
        else {
            status = ERROR_CLUSTER_NODE_NOT_FOUND;
        }

        NmpLockedLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process GetInterfaceForNodeAndNetwork request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // NmGetInterfaceForNodeAndNetwork


/////////////////////////////////////////////////////////////////////////////
//
// Handlers for global updates
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpUpdateCreateInterface(
    IN BOOL     IsSourceNode,
    IN PVOID    InterfacePropertyList,
    IN LPDWORD  InterfacePropertyListSize
    )
/*++

Routine Description:

    Global update handler for creating a new interface. The interface
    definition is read from the cluster database, and a corresponding
    object is instantiated. The cluster transport is also updated if
    necessary.

Arguments:

    IsSourceNode  - Set to TRUE if this node is the source of the update.
                    Set to FALSE otherwise.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

    This routine must not be called with the NM lock held.

--*/
{
    DWORD                  status = ERROR_SUCCESS;
    NM_INTERFACE_INFO2     interfaceInfo;
    BOOLEAN                lockAcquired = FALSE;
    HLOCALXSACTION         xaction = NULL;
    PNM_INTERFACE          netInterface = NULL;


    if (!NmpEnterApi(NmStateOnline)) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process CreateInterface update.\n"
            );
        return(ERROR_NODE_NOT_AVAILABLE);
    }

    ZeroMemory(&interfaceInfo, sizeof(interfaceInfo));

    //
    // Unmarshall the property list.
    //
    status = NmpConvertPropertyListToInterfaceInfo(
                 InterfacePropertyList,
                 *InterfacePropertyListSize,
                 &interfaceInfo
                 );

    if (status == ERROR_SUCCESS) {
        //
        // Fake missing V2 fields
        //
        if (interfaceInfo.AdapterId == NULL) {
            interfaceInfo.AdapterId = NmpUnknownString;
        }

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Received update to create interface %1!ws!.\n",
            interfaceInfo.Id
            );

        //
        // Start a transaction - this must be done before acquiring
        //                       the NM lock.
        //
        xaction = DmBeginLocalUpdate();

        if (xaction != NULL) {

            NmpAcquireLock(); lockAcquired = TRUE;

            status = NmpCreateInterfaceDefinition(&interfaceInfo, xaction);

            if (status == ERROR_SUCCESS) {
                CL_NODE_ID             joinerNodeId;


                joinerNodeId = NmpJoinerNodeId;

                NmpReleaseLock();

                netInterface = NmpCreateInterfaceObject(
                                   &interfaceInfo,
                                   TRUE  // Do retry on failure
                                   );

                NmpAcquireLock();

                if (netInterface != NULL) {
                    //
                    // If a node happens to be joining right now, flag
                    // the fact that it is now out of synch with the
                    // cluster config.
                    //
                    if ( ( (joinerNodeId != ClusterInvalidNodeId) &&
                           (netInterface->Node->NodeId != joinerNodeId)
                         ) ||
                         ( (NmpJoinerNodeId != ClusterInvalidNodeId) &&
                           (netInterface->Node->NodeId != NmpJoinerNodeId)
                         )
                       )
                    {
                        NmpJoinerOutOfSynch = TRUE;
                    }

                    ClusterEvent(
                        CLUSTER_EVENT_NETINTERFACE_ADDED,
                        netInterface
                        );
                }
                else {
                    status = GetLastError();
                    ClRtlLogPrint(LOG_CRITICAL, 
                        "[NM] Failed to create object for interface %1!ws!, "
                        "status %2!u!.\n",
                        interfaceInfo.Id,
                        status
                        );
                }
            }
            else {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Failed to write definition for interface %1!ws!, "
                    "status %2!u!.\n",
                    interfaceInfo.Id,
                    status
                    );
            }
        }
        else {
            status = GetLastError();
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to begin a transaction, status %1!u!\n",
                status
                );
        }

        //
        // Remove faked V2 fields
        //
        if (interfaceInfo.AdapterId == NmpUnknownString) {
            interfaceInfo.AdapterId = NULL;
        }

        ClNetFreeInterfaceInfo(&interfaceInfo);
    }
    else {
        ClRtlLogPrint( LOG_CRITICAL, 
            "[NM] Failed to unmarshall properties for new interface, "
            "status %1!u!.\n",
            status
            );
    }

    if (lockAcquired) {
        NmpLockedLeaveApi();
        NmpReleaseLock();
    }
    else {
        NmpLeaveApi();
    }

    if (xaction != NULL) {
        //
        // Complete the transaction - this must be done after releasing
        //                            the NM lock.
        //
        if (status == ERROR_SUCCESS) {
            DmCommitLocalUpdate(xaction);
        }
        else {
            DmAbortLocalUpdate(xaction);
        }
    }

    if (netInterface != NULL) {
        //
        // Remove the reference put on by
        // NmpCreateInterfaceObject.
        //
        OmDereferenceObject(netInterface);
    }

    return(status);

} // NmpUpdateCreateInterface


DWORD
NmpUpdateSetInterfaceInfo(
    IN BOOL     IsSourceNode,
    IN PVOID    InterfacePropertyList,
    IN LPDWORD  InterfacePropertyListSize
    )
/*++

Routine Description:

    Global update handler for setting the properties of an interface.
    This update is issued in response to interface property changes that
    are detected internally.

Arguments:

    IsSourceNode  - Set to TRUE if this node is the source of the update.
                    Set to FALSE otherwise.

    InterfacePropertyList - A pointer to a property list that encodes the
                            new properties for the interface. All of the
                            string properties must be present, except those
                            noted in the code below.

    InterfacePropertyListSize - A pointer to a variable containing the size,
                                in bytes, of the property list described
                                by the InterfacePropertyList parameter.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

--*/
{
    DWORD                status;
    NM_INTERFACE_INFO2   interfaceInfo;


    if (!NmpEnterApi(NmStateOnline)) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process SetInterfaceInfo update.\n"
            );
        return(ERROR_NODE_NOT_AVAILABLE);
    }

    //
    // Unmarshall the property list so we can extract the interface ID.
    //
    status = NmpConvertPropertyListToInterfaceInfo(
                 InterfacePropertyList,
                 *InterfacePropertyListSize,
                 &interfaceInfo
                 );

    if (status == ERROR_SUCCESS) {
        PNM_INTERFACE  netInterface;

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Received update to set properties for interface %1!ws!.\n",
            interfaceInfo.Id
            );

        //
        // Fake missing V2 fields
        //
        if (interfaceInfo.AdapterId == NULL) {
            interfaceInfo.AdapterId = NmpUnknownString;
        }

        //
        // Find the interface object
        //
        netInterface = OmReferenceObjectById(
                           ObjectTypeNetInterface,
                           interfaceInfo.Id
                           );

        if (netInterface != NULL) {
            HLOCALXSACTION   xaction;

            //
            // Begin a transaction - this must be done before acquiring the
            //                       NM lock.
            //
            xaction = DmBeginLocalUpdate();

            if (xaction != NULL) {

                NmpAcquireLock();

                //
                // Process the changes
                //
                status = NmpLocalSetInterfaceInfo(
                             netInterface,
                             &interfaceInfo,
                             xaction
                             );

                NmpReleaseLock();

                //
                // Complete the transaction - this must be done after
                //                            releasing the NM lock.
                //
                if (status == ERROR_SUCCESS) {
                    DmCommitLocalUpdate(xaction);
                }
                else {
                    DmAbortLocalUpdate(xaction);
                }
            }
            else {
                status = GetLastError();
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Failed to begin a transaction, status %1!u!\n",
                    status
                    );
            }

            OmDereferenceObject(netInterface);
        }
        else {
            status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Unable to find interface %1!ws!.\n",
                interfaceInfo.Id
                );
        }

        //
        // Remove faked V2 fields
        //
        if (interfaceInfo.AdapterId == NmpUnknownString) {
            interfaceInfo.AdapterId = NULL;
        }

        ClNetFreeInterfaceInfo(&interfaceInfo);
    }
    else {
        ClRtlLogPrint( LOG_CRITICAL, 
            "[NM] Failed to unmarshall properties for interface update, "
            "status %1!u!.\n",
            status
            );
    }

    NmpLeaveApi();

    return(status);

} // NmpUpdateSetInterfaceInfo


DWORD
NmpUpdateSetInterfaceCommonProperties(
    IN BOOL     IsSourceNode,
    IN LPWSTR   InterfaceId,
    IN UCHAR *  PropertyList,
    IN LPDWORD  PropertyListLength
    )
/*++

Routine Description:

    Global update handler for setting the common properties of a interface.
    This update is issued in response to a property change request made
    through the cluster API.

Arguments:

    IsSourceNode  - Set to TRUE if this node is the source of the update.
                    Set to FALSE otherwise.

    InterfaceId - A pointer to a unicode string containing the ID of the
                  interface to update.

    PropertyList - A pointer to a property list that encodes the
                   new properties for the interface. The list might contain
                   only a partial property set for the object.

    PropertyListLength - A pointer to a variable containing the size,
                         in bytes, of the property list described
                         by the PropertyList parameter.



Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

--*/
{
    DWORD          status = ERROR_SUCCESS;
    PNM_INTERFACE  netInterface;


    if (!NmpEnterApi(NmStateOnline)) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process SetInterfaceCommonProperties "
            "update.\n"
            );
        return(ERROR_NODE_NOT_AVAILABLE);
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received update to set common properties for "
        "interface %1!ws!.\n",
        InterfaceId
        );

    //
    // Find the interface's object
    //
    netInterface = OmReferenceObjectById(
                       ObjectTypeNetInterface,
                       InterfaceId
                       );

    if (netInterface != NULL) {
        HLOCALXSACTION   xaction;

        //
        // Begin a transaction - this must be done before acquiring the
        //                       NM lock.
        //
        xaction = DmBeginLocalUpdate();

        if (xaction != NULL) {
            NM_INTERFACE_INFO2      interfaceInfo;


            ZeroMemory(&interfaceInfo, sizeof(interfaceInfo));

            NmpAcquireLock();

            //
            // Validate the property list and convert it to an
            // interface info struct. Properties that are not present
            // in the property list will be copied from the interface
            // object.
            //
            status = NmpInterfaceValidateCommonProperties(
                         netInterface,
                         PropertyList,
                         *PropertyListLength,
                         &interfaceInfo
                         );

            if (status == ERROR_SUCCESS) {
                //
                // Fake missing V2 fields
                //
                if (interfaceInfo.AdapterId == NULL) {
                    interfaceInfo.AdapterId = NmpUnknownString;
                }

                //
                // Apply the changes
                //
                status = NmpLocalSetInterfaceInfo(
                             netInterface,
                             &interfaceInfo,
                             xaction
                             );

                NmpReleaseLock();

                //
                // Remove faked V2 fields
                //
                if (interfaceInfo.AdapterId == NmpUnknownString) {
                    interfaceInfo.AdapterId = NULL;
                }

                ClNetFreeInterfaceInfo(&interfaceInfo);
            }
            else {
                NmpReleaseLock();

                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Update to set common properties for interface "
                    "%1!ws! failed because property list validation failed "
                    "with status %1!u!.\n",
                    InterfaceId,
                    status
                    );
            }

            //
            // Complete the transaction - this must be done after releasing
            //                            the NM lock.
            //
            if (status == ERROR_SUCCESS) {
                DmCommitLocalUpdate(xaction);
            }
            else {
                DmAbortLocalUpdate(xaction);
            }
        }
        else {
            status = GetLastError();
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to begin a transaction, status %1!u!\n",
                status
                );
        }

        OmDereferenceObject(netInterface);
    }
    else {
        status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Unable to find interface %1!ws!.\n",
            InterfaceId
            );
    }

    NmpLeaveApi();

    return(status);

} // NmpUpdateSetInterfaceCommonProperties


DWORD
NmpUpdateDeleteInterface(
    IN BOOL     IsSourceNode,
    IN LPWSTR   InterfaceId
    )
/*++

Routine Description:

    Global update handler for deleting an interface. The corresponding
    object is deleted. The cluster transport is also updated if
    necessary.

Arguments:

    IsSourceNode  - Set to TRUE if this node is the source of the update.
                    Set to FALSE otherwise.

    InterfaceId - A pointer to a unicode string containing the ID of the
                  interface to delete.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

--*/
{
    DWORD            status;
    PNM_INTERFACE    netInterface;
    HLOCALXSACTION   xaction;


    if (!NmpEnterApi(NmStateOnline)) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process DeleteInterface update.\n"
            );
        return(ERROR_NODE_NOT_AVAILABLE);
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received update request to delete interface %1!ws!.\n",
        InterfaceId
        );

    xaction = DmBeginLocalUpdate();

    if (xaction != NULL) {
        //
        // Find the interface object
        //
        netInterface = OmReferenceObjectById(
                           ObjectTypeNetInterface,
                           InterfaceId
                           );

        if (netInterface != NULL) {
            BOOLEAN      deleteNetwork = FALSE;
            PNM_NETWORK  network;
            LPCWSTR      networkId;

            NmpAcquireLock();

            network = netInterface->Network;
            networkId = OmObjectId(network);

            //
            // Delete the interface definition from the database.
            //
            status = DmLocalDeleteTree(
                         xaction,
                         DmNetInterfacesKey,
                         InterfaceId
                         );

            if (status == ERROR_SUCCESS) {
                if (network->InterfaceCount == 1) {
                    //
                    // This is the last interface on the network.
                    // Delete the network too.
                    //
                    deleteNetwork = TRUE;

                    status = DmLocalDeleteTree(
                                 xaction,
                                 DmNetworksKey,
                                 networkId
                                 );

                    if (status != ERROR_SUCCESS) {
                        ClRtlLogPrint(LOG_CRITICAL, 
                            "[NM] Failed to delete definition for network "
                            "%1!ws!, status %2!u!.\n",
                            networkId,
                            status
                            );
                    }
                }
            }
            else {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Failed to delete definition for interface %1!ws!, "
                    "status %2!u!.\n",
                    InterfaceId,
                    status
                    );
            }

            if (status == ERROR_SUCCESS) {
                NmpDeleteInterfaceObject(netInterface, TRUE);

                if (deleteNetwork) {
                    NmpDeleteNetworkObject(network, TRUE);
                }
                else if (NmpIsNetworkRegistered(network)) {
                    //
                    // Schedule a connectivity report.
                    //
                    NmpScheduleNetworkConnectivityReport(network);
                }

                //
                // If a node happens to be joining right now, flag the
                // fact that it is now out of synch with the cluster
                // config.
                //
                if ( (NmpJoinerNodeId != ClusterInvalidNodeId) &&
                     (netInterface->Node->NodeId != NmpJoinerNodeId)
                   )
                {
                    NmpJoinerOutOfSynch = TRUE;
                }
            }

            NmpReleaseLock();

            OmDereferenceObject(netInterface);
        }
        else {
            status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Unable to find interface %1!ws!.\n",
                InterfaceId
                );
        }

        //
        // Complete the transaction
        //
        if (status == ERROR_SUCCESS) {
            DmCommitLocalUpdate(xaction);
        }
        else {
            DmAbortLocalUpdate(xaction);
        }
    }
    else {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to begin a transaction, status %1!u!\n",
            status
            );
    }

    NmpLeaveApi();

    return(status);

} // NmpUpdateDeleteInterface


/////////////////////////////////////////////////////////////////////////////
//
// Update helper routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpLocalSetInterfaceInfo(
    IN  PNM_INTERFACE         Interface,
    IN  PNM_INTERFACE_INFO2   InterfaceInfo,
    IN  HLOCALXSACTION        Xaction
    )
/*++

Routine Description:


Arguments:


Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD          status = ERROR_SUCCESS;
    PNM_NETWORK    network = Interface->Network;
    LPCWSTR        interfaceId = OmObjectId(Interface);
    HDMKEY         interfaceKey = NULL;
    BOOLEAN        updateClusnet = FALSE;
    BOOLEAN        propertyChanged = FALSE;
    BOOLEAN        nameChanged = FALSE;
    LPWSTR         descString = NULL;
    LPWSTR         adapterNameString = NULL;
    LPWSTR         adapterIdString = NULL;
    LPWSTR         addressString = NULL;
    LPWSTR         endpointString = NULL;
    DWORD          size;
    ULONG          ifAddress;


    //
    // Open the interface's database key
    //
    interfaceKey = DmOpenKey(DmNetInterfacesKey, interfaceId, KEY_WRITE);

    if (interfaceKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to open database key for interface %1!ws!, "
            "status %2!u!\n",
            interfaceId,
            status
            );
        goto error_exit;
    }

    //
    // Check if the description changed.
    //
    if (wcscmp(Interface->Description, InterfaceInfo->Description) != 0) {
        size = NM_WCSLEN(InterfaceInfo->Description);

        //
        // Update the database
        //
        status = DmLocalSetValue(
                     Xaction,
                     interfaceKey,
                     CLUSREG_NAME_NETIFACE_DESC,
                     REG_SZ,
                     (CONST BYTE *) InterfaceInfo->Description,
                     size
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Set of name value failed for interface %1!ws!, "
                "status %2!u!.\n",
                interfaceId,
                status
                );
            goto error_exit;
        }

        //
        // Allocate new memory resources. The object will be updated when the
        // transaction commits.
        //
        descString = MIDL_user_allocate(size);

        if (descString == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to allocate memory\n");
            goto error_exit;
        }

        wcscpy(descString, InterfaceInfo->Description);

        propertyChanged = TRUE;
    }

    //
    // Check if the adapter name changed.
    //
    if (wcscmp(Interface->AdapterName, InterfaceInfo->AdapterName) != 0) {
        size = NM_WCSLEN(InterfaceInfo->AdapterName);

        //
        // Update the database
        //
        status = DmLocalSetValue(
                     Xaction,
                     interfaceKey,
                     CLUSREG_NAME_NETIFACE_ADAPTER_NAME,
                     REG_SZ,
                     (CONST BYTE *) InterfaceInfo->AdapterName,
                     size
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Set of adapter name value failed for interface %1!ws!, "
                "status %2!u!.\n",
                interfaceId,
                status
                );
            goto error_exit;
        }

        //
        // Allocate new memory resources. The object will be updated when the
        // transaction commits.
        //
        adapterNameString = MIDL_user_allocate(size);

        if (adapterNameString == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to allocate memory\n");
            goto error_exit;
        }

        wcscpy(adapterNameString, InterfaceInfo->AdapterName);

        propertyChanged = TRUE;
    }

    //
    // Check if the adapter Id changed.
    //
    if (wcscmp(Interface->AdapterId, InterfaceInfo->AdapterId) != 0) {
        size = NM_WCSLEN(InterfaceInfo->AdapterId);

        //
        // Update the database
        //
        status = DmLocalSetValue(
                     Xaction,
                     interfaceKey,
                     CLUSREG_NAME_NETIFACE_ADAPTER_ID,
                     REG_SZ,
                     (CONST BYTE *) InterfaceInfo->AdapterId,
                     size
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Set of adapter Id value failed for interface %1!ws!, "
                "status %2!u!.\n",
                interfaceId,
                status
                );
            goto error_exit;
        }

        //
        // Allocate new memory resources. The object will be updated when the
        // transaction commits.
        //
        adapterIdString = MIDL_user_allocate(size);

        if (adapterIdString == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to allocate memory\n");
            goto error_exit;
        }

        wcscpy(adapterIdString, InterfaceInfo->AdapterId);

        propertyChanged = TRUE;
    }

    //
    // Check if the address changed.
    //
    if (wcscmp(Interface->Address, InterfaceInfo->Address) != 0) {

        status = ClRtlTcpipStringToAddress(
                     InterfaceInfo->Address,
                     &ifAddress
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to convert interface address string %1!ws! to "
                "binary, status %2!u!.\n",
                InterfaceInfo->Address,
                status
                );
            goto error_exit;
        }

        size = NM_WCSLEN(InterfaceInfo->Address);

        //
        // Update the database
        //
        status = DmLocalSetValue(
                     Xaction,
                     interfaceKey,
                     CLUSREG_NAME_NETIFACE_ADDRESS,
                     REG_SZ,
                     (CONST BYTE *) InterfaceInfo->Address,
                     size
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Set of address value failed for interface %1!ws!, "
                "status %2!u!.\n",
                interfaceId,
                status
                );
            goto error_exit;
        }

        //
        // Allocate new memory resources. The object will be updated when the
        // transaction commits.
        //
        addressString = MIDL_user_allocate(size);

        if (addressString == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to allocate memory\n");
            goto error_exit;
        }

        wcscpy(addressString, InterfaceInfo->Address);

        updateClusnet = TRUE;
        propertyChanged = TRUE;
    }

    //
    // Check if the clusnet endpoint changed.
    //
    if (wcscmp(
            Interface->ClusnetEndpoint,
            InterfaceInfo->ClusnetEndpoint
            ) != 0
       )
    {
        size = NM_WCSLEN(InterfaceInfo->ClusnetEndpoint);

        //
        // Update the database
        //
        status = DmLocalSetValue(
                     Xaction,
                     interfaceKey,
                     CLUSREG_NAME_NETIFACE_ENDPOINT,
                     REG_SZ,
                     (CONST BYTE *) InterfaceInfo->ClusnetEndpoint,
                     size
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Set of endpoint value failed for interface %1!ws!, "
                "status %2!u!.\n",
                interfaceId,
                status
                );
            goto error_exit;
        }

        //
        // Allocate new memory resources. The object will be updated when the
        // transaction commits.
        //
        endpointString = MIDL_user_allocate(size);

        if (endpointString == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to allocate memory\n");
            goto error_exit;
        }

        wcscpy(endpointString, InterfaceInfo->ClusnetEndpoint);

        updateClusnet = TRUE;
        propertyChanged = TRUE;
    }

    //
    // Check if the object name changed.
    //
    if (wcscmp(OmObjectName(Interface), InterfaceInfo->Name) != 0) {
        size = NM_WCSLEN(InterfaceInfo->Name);

        //
        // Update the database
        //
        status = DmLocalSetValue(
                     Xaction,
                     interfaceKey,
                     CLUSREG_NAME_NETIFACE_NAME,
                     REG_SZ,
                     (CONST BYTE *) InterfaceInfo->Name,
                     size
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Set of name value failed for interface %1!ws!, "
                "status %2!u!.\n",
                interfaceId,
                status
                );
            goto error_exit;
        }

        nameChanged = TRUE;
        propertyChanged = TRUE;
    }

#ifdef CLUSTER_TESTPOINT
    TESTPT(TpFailNmSetInterfaceInfoAbort) {
        status = 999999;
        goto error_exit;
    }
#endif

    //
    // Commit the changes
    //
    if (nameChanged) {
        status = OmSetObjectName(Interface, InterfaceInfo->Name);

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to change name for interface %1!ws!, "
                "status %2!u!\n",
                interfaceId,
                status
                );
            goto error_exit;
        }
    }

    if (descString != NULL) {
        MIDL_user_free(Interface->Description);
        Interface->Description = descString;
    }

    if (adapterNameString != NULL) {
        MIDL_user_free(Interface->AdapterName);
        Interface->AdapterName = adapterNameString;
    }

    if (adapterIdString != NULL) {
        MIDL_user_free(Interface->AdapterId);
        Interface->AdapterId = adapterIdString;
    }

    if (addressString != NULL) {
        MIDL_user_free(Interface->Address);
        Interface->Address = addressString;
        Interface->BinaryAddress = ifAddress;
    }

    if (endpointString != NULL) {
        MIDL_user_free(Interface->ClusnetEndpoint);
        Interface->ClusnetEndpoint = endpointString;
    }

    //
    // Update the cluster transport if this network is active and the local
    // node is attached to it.
    //
    // This operation is not reversible. Failure is fatal for this node.
    //
    network = Interface->Network;

    if (updateClusnet && NmpIsNetworkRegistered(network)) {
        PNM_NODE     node = Interface->Node;
        LPCWSTR      networkId = OmObjectId(network);


        if (Interface == network->LocalInterface) {
            //
            // This is the local node's interface to the network.
            // We must deregister and then re-register the entire network.
            //
            NmpDeregisterNetwork(network);

            status = NmpRegisterNetwork(
                         network,
                         TRUE  // Do retry on failure
                         );
        }
        else {
            //
            // This is another node's interface to the network.
            // Deregister and then re-register the interface.
            //
            NmpDeregisterInterface(Interface);

            status = NmpRegisterInterface(
                         Interface,
                         TRUE   // Do retry on failure
                         );
        }

#ifdef CLUSTER_TESTPOINT
        TESTPT(TpFailNmSetInterfaceInfoHalt) {
            status = 999999;
        }
#endif

        if (status != ERROR_SUCCESS) {
            //
            // This is fatal.
            //
            CsInconsistencyHalt(status);
        }
    }

    if (propertyChanged) {
        ClusterEvent(CLUSTER_EVENT_NETINTERFACE_PROPERTY_CHANGE, Interface);

        //
        // If a node happens to be joining right now, flag the fact
        // that it is now out of synch with the cluster config.
        //
        if ( (NmpJoinerNodeId != ClusterInvalidNodeId) &&
             (Interface->Node->NodeId != NmpJoinerNodeId)
           )
        {
            NmpJoinerOutOfSynch = TRUE;
        }
    }

    return(ERROR_SUCCESS);

error_exit:

    if (descString != NULL) {
        MIDL_user_free(descString);
    }

    if (adapterNameString != NULL) {
        MIDL_user_free(adapterNameString);
    }

    if (adapterIdString != NULL) {
        MIDL_user_free(adapterIdString);
    }

    if (addressString != NULL) {
        MIDL_user_free(addressString);
    }

    if (endpointString != NULL) {
        MIDL_user_free(endpointString);
    }

    if (interfaceKey != NULL) {
        DmCloseKey(interfaceKey);
    }

    return(status);

} // NmpLocalSetInterfaceInfo


/////////////////////////////////////////////////////////////////////////////
//
// Database management routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCreateInterfaceDefinition(
    IN PNM_INTERFACE_INFO2   InterfaceInfo,
    IN HLOCALXSACTION        Xaction
    )
/*++

Routine Description:

    Creates a new network interface definition in the cluster database.

Arguments:

    InterfaceInfo - A structure containing the interface's definition.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

--*/
{
    DWORD     status;
    HDMKEY    interfaceKey = NULL;
    DWORD     valueLength;
    DWORD     disposition;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Creating database entry for interface %1!ws!\n",
        InterfaceInfo->Id
        );

    CL_ASSERT(InterfaceInfo->Id != NULL);

    interfaceKey = DmLocalCreateKey(
                        Xaction,
                        DmNetInterfacesKey,
                        InterfaceInfo->Id,
                        0,
                        KEY_WRITE,
                        NULL,
                        &disposition
                        );

    if (interfaceKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to create key for interface %1!ws!, status %2!u!\n",
            InterfaceInfo->Id,
            status
            );
        return(status);
    }

    CL_ASSERT(disposition == REG_CREATED_NEW_KEY);

    //
    // Write the network ID value for this interface.
    //
    valueLength = NM_WCSLEN(InterfaceInfo->NetworkId);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_NETWORK,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->NetworkId,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Write of interface network ID failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the node ID value for this interface.
    //
    valueLength = NM_WCSLEN(InterfaceInfo->NodeId);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_NODE,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->NodeId,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Write of interface node ID failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the rest of the parameters
    //
    status = NmpSetInterfaceDefinition(InterfaceInfo, Xaction);

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to set database definition for interface %1!ws!, status %2!u!.\n",
            InterfaceInfo->Id,
            status
            );
    }

error_exit:

    if (interfaceKey != NULL) {
        DmCloseKey(interfaceKey);
    }

    return(status);

} // NmpCreateInterfaceDefinition



DWORD
NmpGetInterfaceDefinition(
    IN  LPWSTR               InterfaceId,
    OUT PNM_INTERFACE_INFO2  InterfaceInfo
    )
/*++

Routine Description:

    Reads information about a defined cluster network interface from the
    cluster database and fills in a structure describing it.

Arguments:

    InterfaceId - A pointer to a unicode string containing the ID of the
                  interface to query.

    InterfaceInfo - A pointer to the structure to fill in with node
                    information. The ID, NetworkId, and NodeId fields of the
                    structure must already be filled in.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

--*/

{
    DWORD      status;
    HDMKEY     interfaceKey = NULL;
    DWORD      valueLength, valueSize;


    CL_ASSERT(InterfaceId != NULL);

    ZeroMemory(InterfaceInfo, sizeof(NM_INTERFACE_INFO2));

    interfaceKey = DmOpenKey(DmNetInterfacesKey, InterfaceId, KEY_READ);

    if (interfaceKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to open key for interface %1!ws!, status %2!u!\n",
            InterfaceId,
            status
            );
        return(status);
    }

    //
    // Copy the ID string
    //
    InterfaceInfo->Id = MIDL_user_allocate(NM_WCSLEN(InterfaceId));

    if (InterfaceInfo->Id == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory for interface %1!ws!.\n",
            InterfaceId
            );
        goto error_exit;
    }

    wcscpy(InterfaceInfo->Id, InterfaceId);

    //
    // Read the Name for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_NAME,
                 REG_SZ,
                 &(InterfaceInfo->Name),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Query of network interface name failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Read the Description for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_DESC,
                 REG_SZ,
                 &(InterfaceInfo->Description),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Query of network interface description failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Read the Network ID for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_NETWORK,
                 REG_SZ,
                 &(InterfaceInfo->NetworkId),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Query of network id for interface %1!ws! failed, status %2!u!.\n",
            InterfaceId,
            status
            );
        goto error_exit;
    }

    //
    // Read the Node ID for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_NODE,
                 REG_SZ,
                 &(InterfaceInfo->NodeId),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Query of node Id for interface %1!ws! failed, status %2!u!.\n",
            InterfaceId,
            status
            );
        goto error_exit;
    }

    //
    // Read the adapter name value for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ADAPTER_NAME,
                 REG_SZ,
                 &(InterfaceInfo->AdapterName),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Query of network interface adapter name failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Read the adapter Id value for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ADAPTER_ID,
                 REG_SZ,
                 &(InterfaceInfo->AdapterId),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Query of network interface adapter Id failed, status %1!u!.\n",
            status
            );

        InterfaceInfo->AdapterId = midl_user_allocate(
                                       NM_WCSLEN(NmpUnknownString)
                                       );

        if (InterfaceInfo->AdapterId == NULL) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to allocate memory for adapter Id.\n"
                );
            status = ERROR_NOT_ENOUGH_MEMORY;
            goto error_exit;
        }

        lstrcpyW(InterfaceInfo->AdapterId, NmpUnknownString);
    }

    //
    // Read the address value for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ADDRESS,
                 REG_SZ,
                 &(InterfaceInfo->Address),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Query of network interface address failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Read the ClusNet endpoint value for this interface.
    //
    valueLength = 0;

    status = NmpQueryString(
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ENDPOINT,
                 REG_SZ,
                 &(InterfaceInfo->ClusnetEndpoint),
                 &valueLength,
                 &valueSize
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Query of ClusNet endpoint value for network interface failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    InterfaceInfo->State = ClusterNetInterfaceUnavailable;
    InterfaceInfo->NetIndex = NmInvalidInterfaceNetIndex;

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

    if (status != ERROR_SUCCESS) {
        ClNetFreeInterfaceInfo(InterfaceInfo);
    }

    if (interfaceKey != NULL) {
        DmCloseKey(interfaceKey);
    }

    return(status);

}  // NmpGetInterfaceDefinition



DWORD
NmpSetInterfaceDefinition(
    IN PNM_INTERFACE_INFO2  InterfaceInfo,
    IN HLOCALXSACTION       Xaction
    )
/*++

Routine Description:

    Updates information for a network interface in the cluster database.

Arguments:

    InterfaceInfo - A pointer to a structure containing the
                    interface's definition.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

--*/
{
    DWORD     status;
    HDMKEY    interfaceKey = NULL;
    DWORD     valueLength;


    CL_ASSERT(InterfaceInfo->Id != NULL);

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Setting database entry for interface %1!ws!\n",
        InterfaceInfo->Id
        );

    interfaceKey = DmOpenKey(
                       DmNetInterfacesKey,
                       InterfaceInfo->Id,
                       KEY_WRITE
                       );

    if (interfaceKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to open key for interface %1!ws!, status %2!u!\n",
            InterfaceInfo->Id,
            status
            );
        return(status);
    }

    //
    // Write the name value for this interface.
    //
    valueLength = (wcslen(InterfaceInfo->Name) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_NAME,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->Name,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Update of interface name failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the description value for this interface.
    //
    valueLength = (wcslen(InterfaceInfo->Description) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_DESC,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->Description,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Update of interface description failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the adapter name value for this interface.
    //
    valueLength = (wcslen(InterfaceInfo->AdapterName) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ADAPTER_NAME,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->AdapterName,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Update of interface adapter name failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the adapter Id value for this interface.
    //
    valueLength = (wcslen(InterfaceInfo->AdapterId) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ADAPTER_ID,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->AdapterId,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Update of interface adapter Id failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the address value for this interface.
    //
    valueLength = (wcslen(InterfaceInfo->Address) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ADDRESS,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->Address,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Update of interface address failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the ClusNet endpoint value for this interface.
    //
    valueLength = (wcslen(InterfaceInfo->ClusnetEndpoint) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 interfaceKey,
                 CLUSREG_NAME_NETIFACE_ENDPOINT,
                 REG_SZ,
                 (CONST BYTE *) InterfaceInfo->ClusnetEndpoint,
                 valueLength
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Update of interface endpoint name failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

    if (interfaceKey != NULL) {
        DmCloseKey(interfaceKey);
    }

    return(status);

}  // NmpSetInterfaceDefinition



DWORD
NmpEnumInterfaceDefinitions(
    OUT PNM_INTERFACE_ENUM2 *  InterfaceEnum
    )
/*++

Routine Description:

    Reads interface information from the cluster database and
    fills in an enumeration structure.

Arguments:

    InterfaceEnum -  A pointer to the variable into which to place a
                     pointer to the allocated interface enumeration.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

--*/

{
    DWORD                status;
    PNM_INTERFACE_ENUM2  interfaceEnum = NULL;
    PNM_INTERFACE_INFO2  interfaceInfo;
    WCHAR                interfaceId[CS_NETINTERFACE_ID_LENGTH + 1];
    DWORD                i;
    DWORD                valueLength;
    DWORD                numInterfaces;
    DWORD                ignored;
    FILETIME             fileTime;


    *InterfaceEnum = NULL;

    //
    // First count the number of interfaces.
    //
    status = DmQueryInfoKey(
                 DmNetInterfacesKey,
                 &numInterfaces,
                 &ignored,   // MaxSubKeyLen
                 &ignored,   // Values
                 &ignored,   // MaxValueNameLen
                 &ignored,   // MaxValueLen
                 &ignored,   // lpcbSecurityDescriptor
                 &fileTime
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to query NetworkInterfaces key information, status %1!u!\n",
            status
            );
        return(status);
    }

    if (numInterfaces == 0) {
        valueLength = sizeof(NM_INTERFACE_ENUM2);

    }
    else {
        valueLength = sizeof(NM_INTERFACE_ENUM2) +
                      (sizeof(NM_INTERFACE_INFO2) * (numInterfaces-1));
    }

    valueLength = sizeof(NM_INTERFACE_ENUM2) +
                  (sizeof(NM_INTERFACE_INFO2) * (numInterfaces-1));

    interfaceEnum = MIDL_user_allocate(valueLength);

    if (interfaceEnum == NULL) {
        ClRtlLogPrint(LOG_CRITICAL, "[NM] Failed to allocate memory.\n");
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    ZeroMemory(interfaceEnum, valueLength);

    for (i=0; i < numInterfaces; i++) {
        interfaceInfo = &(interfaceEnum->InterfaceList[i]);

        valueLength = sizeof(interfaceId);

        status = DmEnumKey(
                     DmNetInterfacesKey,
                     i,
                     &(interfaceId[0]),
                     &valueLength,
                     NULL
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to enumerate interface key, status %1!u!\n",
                status
                );
            goto error_exit;
        }

        status = NmpGetInterfaceDefinition(interfaceId, interfaceInfo);

        if (status != ERROR_SUCCESS) {
            goto error_exit;
        }

        interfaceEnum->InterfaceCount++;
    }

    *InterfaceEnum = interfaceEnum;

    return(ERROR_SUCCESS);


error_exit:

    if (interfaceEnum != NULL) {
        ClNetFreeInterfaceEnum(interfaceEnum);
    }

    return(status);

}  // NmpEnumInterfaceDefinitions


/////////////////////////////////////////////////////////////////////////////
//
// Object management routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCreateInterfaceObjects(
    IN PNM_INTERFACE_ENUM2    InterfaceEnum
    )
/*++

Routine Description:

    Processes an interface enumeration and creates interface objects.

Arguments:

    InterfaceEnum - A pointer to an interface enumeration structure.

Return Value:

    ERROR_SUCCESS if the routine completes successfully.
    A Win32 error code otherwise.

--*/
{
    DWORD                status = ERROR_SUCCESS;
    PNM_INTERFACE_INFO2  interfaceInfo;
    PNM_INTERFACE        netInterface;
    DWORD                i;


    for (i=0; i < InterfaceEnum->InterfaceCount; i++) {
        interfaceInfo = &(InterfaceEnum->InterfaceList[i]);

        netInterface = NmpCreateInterfaceObject(
                           interfaceInfo,
                           FALSE    // Don't retry on failure
                           );

        if (netInterface == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to create interface %1!ws!, status %2!u!.\n",
                interfaceInfo->Id,
                status
                );
            break;
        }
        else {
            OmDereferenceObject(netInterface);
        }
    }

    return(status);

}  // NmpCreateInterfaceObjects


PNM_INTERFACE
NmpCreateInterfaceObject(
    IN PNM_INTERFACE_INFO2   InterfaceInfo,
    IN BOOLEAN               RetryOnFailure
    )
/*++

Routine Description:

    Creates an interface object.

Arguments:

    InterfacInfo - A pointer to a structure containing the definition for
                   the interface to create.

    RegisterWithClusterTransport - TRUE if this interface should be registered
                                   with the cluster transport.
                                   FALSE otherwise.

    IssueEvent - TRUE if an INTERFACE_ADDED event should be issued when this
                 object is created. FALSE otherwise.

Return Value:

    A pointer to the new interface object on success.
    NULL on failure.

--*/
{
    DWORD                        status;
    PNM_NETWORK                  network = NULL;
    PNM_NODE                     node = NULL;
    PNM_INTERFACE                netInterface = NULL;
    BOOL                         created = FALSE;
    PNM_CONNECTIVITY_MATRIX      matrixEntry;


    ClRtlLogPrint(LOG_NOISE,
        "[NM] Creating object for interface %1!ws! (%2!ws!).\n",
        InterfaceInfo->Id,
        InterfaceInfo->Name
        );

    status = NmpPrepareToCreateInterface(
                 InterfaceInfo,
                 &network,
                 &node
                 );

    if (status != ERROR_SUCCESS) {
        SetLastError(status);
        return(NULL);
    }

    //
    // Create the interface object.
    //
    netInterface = OmCreateObject(
                       ObjectTypeNetInterface,
                       InterfaceInfo->Id,
                       InterfaceInfo->Name,
                       &created
                       );

    if (netInterface == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL,
            "[NM] Failed to create object for interface %1!ws!, status %2!u!\n",
            InterfaceInfo->Id,
            status
            );
        goto error_exit;
    }

    CL_ASSERT(created == TRUE);

    //
    // Initialize the interface object.
    //
    ZeroMemory(netInterface, sizeof(NM_INTERFACE));

    netInterface->Network = network;
    netInterface->Node = node;
    netInterface->State = ClusterNetInterfaceUnavailable;

    netInterface->Description = MIDL_user_allocate(
                                    NM_WCSLEN(InterfaceInfo->Description)
                                    );

    if (netInterface->Description == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory.\n"
            );
        goto error_exit;
    }

    wcscpy(netInterface->Description, InterfaceInfo->Description);

    netInterface->AdapterName = MIDL_user_allocate(
                                 (wcslen(InterfaceInfo->AdapterName) + 1) *
                                     sizeof(WCHAR)
                                 );

    if (netInterface->AdapterName == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory.\n"
            );
        goto error_exit;
    }

    wcscpy(netInterface->AdapterName, InterfaceInfo->AdapterName);

    netInterface->AdapterId = MIDL_user_allocate(
                               (wcslen(InterfaceInfo->AdapterId) + 1) *
                                   sizeof(WCHAR)
                               );

    if (netInterface->AdapterId == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory.\n"
            );
        goto error_exit;
    }

    wcscpy(netInterface->AdapterId, InterfaceInfo->AdapterId);

    netInterface->Address = MIDL_user_allocate(
                             (wcslen(InterfaceInfo->Address) + 1) *
                                 sizeof(WCHAR)
                             );

    if (netInterface->Address == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory.\n"
            );
        goto error_exit;
    }

    wcscpy(netInterface->Address, InterfaceInfo->Address);

    status = ClRtlTcpipStringToAddress(
                 InterfaceInfo->Address,
                 &(netInterface->BinaryAddress)
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to convert interface address string %1!ws! to binary, status %2!u!.\n",
            InterfaceInfo->Address,
            status
            );
        goto error_exit;
    }

    netInterface->ClusnetEndpoint =
        MIDL_user_allocate(
            (wcslen(InterfaceInfo->ClusnetEndpoint) + 1) * sizeof(WCHAR)
            );

    if (netInterface->ClusnetEndpoint == NULL) {

        status = ERROR_NOT_ENOUGH_MEMORY;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory.\n"
            );
        goto error_exit;
    }

    wcscpy(netInterface->ClusnetEndpoint, InterfaceInfo->ClusnetEndpoint);

    NmpAcquireLock();

    //
    // Assign an index into the network's connectivity vector.
    //
    if (InterfaceInfo->NetIndex == NmInvalidInterfaceNetIndex) {
        //
        // Need to pick an index for this interface. Search for a free
        // entry in the network's connectivity vector.
        //
        DWORD  i;
        PNM_CONNECTIVITY_VECTOR vector = network->ConnectivityVector;


        for ( i=0; i<vector->EntryCount; i++) {
            if ( vector->Data[i] ==
                 (NM_STATE_ENTRY) ClusterNetInterfaceStateUnknown
               )
            {
                break;
            }
        }

        netInterface->NetIndex = i;

        ClRtlLogPrint(LOG_NOISE, 
        "[NM] Assigned index %1!u! to interface %2!ws!.\n",
        netInterface->NetIndex,
        InterfaceInfo->Id
        );

    }
    else {
        //
        // Use the index that was already assigned by our peers.
        //
        netInterface->NetIndex = InterfaceInfo->NetIndex;

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Using preassigned index %1!u! for interface %2!ws!.\n",
            netInterface->NetIndex,
            InterfaceInfo->Id
            );
    }

    if (netInterface->NetIndex >= network->ConnectivityVector->EntryCount) {
        //
        // Grow the connectivity vector by the required number of entries.
        //
        PNM_STATE_ENTRY              oldMatrixEntry, newMatrixEntry;
        DWORD                        i;
        PNM_CONNECTIVITY_VECTOR      oldConnectivityVector =
                                         network->ConnectivityVector;
        PNM_CONNECTIVITY_VECTOR      newConnectivityVector;
        PNM_STATE_WORK_VECTOR        oldStateVector = network->StateWorkVector;
        PNM_STATE_WORK_VECTOR        newStateVector;
        PNM_CONNECTIVITY_MATRIX      newMatrix;
        DWORD                        oldVectorSize =
                                         oldConnectivityVector->EntryCount;
        DWORD                        newVectorSize = netInterface->NetIndex + 1;


        //
        // Note that one vector entry is included
        // in sizeof(NM_CONNECTIVITY_VECTOR).
        //
        newConnectivityVector = LocalAlloc(
                                    LMEM_FIXED,
                                    ( sizeof(NM_CONNECTIVITY_VECTOR) +
                                      ( (newVectorSize - 1) *
                                        sizeof(NM_STATE_ENTRY)
                                      )
                                    ));

        if (newConnectivityVector == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            NmpReleaseLock();
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to allocate memory for connectivity vector\n"
                );
            goto error_exit;
        }

        //
        // Initialize the new vector
        //
        newConnectivityVector->EntryCount = newVectorSize;

        CopyMemory(
            &(newConnectivityVector->Data[0]),
            &(oldConnectivityVector->Data[0]),
            oldVectorSize * sizeof(NM_STATE_ENTRY)
            );

        FillMemory(
            &(newConnectivityVector->Data[oldVectorSize]),
            (newVectorSize - oldVectorSize) * sizeof(NM_STATE_ENTRY),
            (UCHAR) ClusterNetInterfaceStateUnknown
            );

        //
        // Grow the state work vector
        //
        newStateVector = LocalAlloc(
                             LMEM_FIXED,
                             newVectorSize * sizeof(NM_STATE_WORK_ENTRY)
                             );

        if (newStateVector == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            NmpReleaseLock();
            LocalFree(newConnectivityVector);
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to allocate memory for state work vector\n"
                );
            goto error_exit;
        }

        CopyMemory(
            &(newStateVector[0]),
            &(oldStateVector[0]),
            oldVectorSize * sizeof(NM_STATE_WORK_ENTRY)
            );

        for (i=oldVectorSize; i<newVectorSize; i++) {
            newStateVector[i].State =
                (NM_STATE_ENTRY) ClusterNetInterfaceStateUnknown;
        }

        //
        // Grow the network connecivitity matrix
        //
        newMatrix = LocalAlloc(
                        LMEM_FIXED,
                        NM_SIZEOF_CONNECTIVITY_MATRIX(newVectorSize)
                        );

        if (newMatrix == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            NmpReleaseLock();
            LocalFree(newConnectivityVector);
            LocalFree(newStateVector);
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to allocate memory for connectivity matrix\n"
                );
            goto error_exit;
        }

        //
        // Initialize the new matrix
        //
        FillMemory(
            newMatrix,
            NM_SIZEOF_CONNECTIVITY_MATRIX(newVectorSize),
            (UCHAR) ClusterNetInterfaceStateUnknown
            );

        oldMatrixEntry = network->ConnectivityMatrix;
        newMatrixEntry = newMatrix;

        for (i=0; i<oldVectorSize; i++) {
            CopyMemory(
                newMatrixEntry,
                oldMatrixEntry,
                oldVectorSize * sizeof(NM_STATE_ENTRY)
                );

            //
            // Move the pointers to the next vector
            //
            oldMatrixEntry = NM_NEXT_CONNECTIVITY_MATRIX_ROW(
                                 oldMatrixEntry,
                                 oldVectorSize
                                 );

            newMatrixEntry = NM_NEXT_CONNECTIVITY_MATRIX_ROW(
                                 newMatrixEntry,
                                 newVectorSize
                                 );
        }

        //
        // Swap the pointers
        //
        LocalFree(network->ConnectivityVector);
        network->ConnectivityVector = newConnectivityVector;

        LocalFree(network->StateWorkVector);
        network->StateWorkVector = newStateVector;

        LocalFree(network->ConnectivityMatrix);
        network->ConnectivityMatrix = newMatrix;
    }

    //
    // Initialize the connectivity data for this interface
    //
    NmpSetInterfaceConnectivityData(
        network,
        netInterface->NetIndex,
        ClusterNetInterfaceUnavailable
        );

    //
    // Link the interface object onto the various object lists
    //
    InsertTailList(&(node->InterfaceList), &(netInterface->NodeLinkage));
    node->InterfaceCount++;

    InsertTailList(&(network->InterfaceList), &(netInterface->NetworkLinkage));
    network->InterfaceCount++;

    InsertTailList(&NmpInterfaceList, &(netInterface->Linkage));
    NmpInterfaceCount++;

    OmInsertObject(netInterface);
    netInterface->Flags |= NM_FLAG_OM_INSERTED;

    //
    // Remember the interface for the local node.
    //
    if (node == NmLocalNode) {
        network->LocalInterface = netInterface;
    }

    //
    // Register with the cluster transport if needed.
    //
    if (NmpIsNetworkEnabledForUse(network)) {
        if (node == NmLocalNode) {
            //
            // This is the local node. Register the network and all
            // its interfaces with the cluster transport.
            //
            status = NmpRegisterNetwork(network, RetryOnFailure);

            if (status != ERROR_SUCCESS) {
                NmpReleaseLock();
                goto error_exit;
            }
        }
        else if (NmpIsNetworkRegistered(network)) {
            //
            // Just register this interface.
            //
            status = NmpRegisterInterface(netInterface, RetryOnFailure);

            if (status != ERROR_SUCCESS) {
                NmpReleaseLock();
                goto error_exit;
            }
        }
    }

    //
    // Put an additional reference on the object for the caller.
    //
    OmReferenceObject(netInterface);

    NmpReleaseLock();

    return(netInterface);

error_exit:

    if (netInterface != NULL) {
        NmpAcquireLock();
        NmpDeleteInterfaceObject(netInterface, FALSE);
        NmpReleaseLock();
    }

    SetLastError(status);

    return(NULL);

}  // NmpCreateInterfaceObject



DWORD
NmpGetInterfaceObjectInfo1(
    IN     PNM_INTERFACE        Interface,
    IN OUT PNM_INTERFACE_INFO   InterfaceInfo1
    )
/*++

Routine Description:

    Reads information about a defined cluster network interface from the
    interface object and fills in a structure describing it.

Arguments:

    Interface     - A pointer to the interface object to query.

    InterfaceInfo - A pointer to the structure to fill in with node
                    information.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

    Called with NmpLock held.

--*/

{
    DWORD               status;
    NM_INTERFACE_INFO2  interfaceInfo2;


    //
    // Call the V2.0 routine and translate.
    //
    ZeroMemory(&interfaceInfo2, sizeof(interfaceInfo2));
    status = NmpGetInterfaceObjectInfo(Interface, &interfaceInfo2);

    if (status == ERROR_SUCCESS) {
        CopyMemory(InterfaceInfo1, &interfaceInfo2, sizeof(NM_INTERFACE_INFO));
    }

    //
    // Free the unused V2 fields
    //
    midl_user_free(interfaceInfo2.AdapterId);

    return(status);

}  // NmpGetInterfaceObjectInfo1



DWORD
NmpGetInterfaceObjectInfo(
    IN     PNM_INTERFACE        Interface,
    IN OUT PNM_INTERFACE_INFO2  InterfaceInfo
    )
/*++

Routine Description:

    Reads information about a defined cluster network interface from the
    interface object and fills in a structure describing it.

Arguments:

    Interface     - A pointer to the interface object to query.

    InterfaceInfo - A pointer to the structure to fill in with node
                    information.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

    Called with NmpLock held.

--*/

{
    LPWSTR     tmpString = NULL;
    LPWSTR     interfaceId = (LPWSTR) OmObjectId(Interface);
    LPWSTR     interfaceName = (LPWSTR) OmObjectName(Interface);
    LPWSTR     nodeId = (LPWSTR) OmObjectId(Interface->Node);
    LPWSTR     networkId = (LPWSTR) OmObjectId(Interface->Network);


    tmpString = MIDL_user_allocate(NM_WCSLEN(interfaceId));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, interfaceId);
    InterfaceInfo->Id = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(interfaceName));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, interfaceName);
    InterfaceInfo->Name = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(Interface->Description));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, Interface->Description);
    InterfaceInfo->Description = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(nodeId));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, nodeId);
    InterfaceInfo->NodeId = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(networkId));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, networkId);
    InterfaceInfo->NetworkId = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(Interface->AdapterName));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, Interface->AdapterName);
    InterfaceInfo->AdapterName = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(Interface->AdapterId));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, Interface->AdapterId);
    InterfaceInfo->AdapterId = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(Interface->Address));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, Interface->Address);
    InterfaceInfo->Address = tmpString;

    tmpString = MIDL_user_allocate(NM_WCSLEN(Interface->ClusnetEndpoint));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, Interface->ClusnetEndpoint);
    InterfaceInfo->ClusnetEndpoint = tmpString;

    InterfaceInfo->State = Interface->State;
    InterfaceInfo->NetIndex = Interface->NetIndex;

    return(ERROR_SUCCESS);


error_exit:

    ClNetFreeInterfaceInfo(InterfaceInfo);

    return(ERROR_NOT_ENOUGH_MEMORY);

}  // NmpGetInterfaceObjectInfo2


VOID
NmpDeleteInterfaceObject(
    IN PNM_INTERFACE  Interface,
    IN BOOLEAN        IssueEvent
    )
/*++

Notes:

    Called with NM global lock held.

--*/
{
    LPWSTR       interfaceId = (LPWSTR) OmObjectId(Interface);
    PNM_NETWORK  network = Interface->Network;


    if (NM_DELETE_PENDING(Interface)) {
        CL_ASSERT(!NM_OM_INSERTED(Interface));
        return;
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] deleting object for interface %1!ws!\n",
        interfaceId
        );

    Interface->Flags |= NM_FLAG_DELETE_PENDING;

    if (NM_OM_INSERTED(Interface)) {
        //
        // Remove the interface from the various object lists.
        //
        DWORD   status = OmRemoveObject(Interface);
        CL_ASSERT(status == ERROR_SUCCESS);

        RemoveEntryList(&(Interface->Linkage));
        CL_ASSERT(NmpInterfaceCount > 0);
        NmpInterfaceCount--;

        RemoveEntryList(&(Interface->NetworkLinkage));
        CL_ASSERT(network->InterfaceCount > 0);
        network->InterfaceCount--;

        RemoveEntryList(&(Interface->NodeLinkage));
        CL_ASSERT(Interface->Node->InterfaceCount > 0);
        Interface->Node->InterfaceCount--;

        Interface->Flags &= ~NM_FLAG_OM_INSERTED;
    }

    //
    // Place the object on the deleted list
    //
#if DBG
    {
        PLIST_ENTRY  entry;

        for ( entry = NmpDeletedInterfaceList.Flink;
              entry != &NmpDeletedInterfaceList;
              entry = entry->Flink
            )
        {
            if (entry == &(Interface->Linkage)) {
                break;
            }
        }

        CL_ASSERT(entry != &(Interface->Linkage));
    }
#endif DBG

    InsertTailList(&NmpDeletedInterfaceList, &(Interface->Linkage));

    if (network != NULL) {
        if ( (Interface->Node != NULL) &&
             NmpIsNetworkEnabledForUse(network)
           )
        {
            DWORD status;

            //
            // Deregister the interface from the cluster transport
            //
            if ( (network->LocalInterface == Interface) &&
                 NmpIsNetworkRegistered(network)
               )
            {
                //
                // Deregister the network and all of its interfaces
                //
                NmpDeregisterNetwork(network);
            }
            else if (NmpIsInterfaceRegistered(Interface)) {
                //
                // Just deregister this interface
                //
                NmpDeregisterInterface(Interface);
            }
        }

        //
        // Invalidate the connectivity data for the interface.
        //
        NmpSetInterfaceConnectivityData(
            network,
            Interface->NetIndex,
            ClusterNetInterfaceStateUnknown
            );

        if (network->LocalInterface == Interface) {
            network->LocalInterface = NULL;
            network->Flags &= ~NM_NET_IF_WORK_FLAGS;
        }

        //
        // If this is not the last interface on the network,
        // then update the network state.
        //
        if ((network->InterfaceCount != 0) &&
            (NmpLeaderNodeId == NmLocalNodeId)) {
                NmpScheduleNetworkStateRecalc(network);
        }
    }

    if (IssueEvent) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Issuing interface deleted event for interface %1!ws!.\n",
            interfaceId
            );

        ClusterEvent(CLUSTER_EVENT_NETINTERFACE_DELETED, Interface);
    }

    //
    // Remove the initial reference so the object can be destroyed.
    //
    OmDereferenceObject(Interface);

    return;

}  // NmpDeleteInterfaceObject


BOOL
NmpDestroyInterfaceObject(
    PNM_INTERFACE  Interface
    )
{
    DWORD  status;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] destroying object for interface %1!ws!\n",
        (LPWSTR) OmObjectId(Interface)
        );

    CL_ASSERT(NM_DELETE_PENDING(Interface));
    CL_ASSERT(!NM_OM_INSERTED(Interface));

    //
    // Remove the interface from the deleted list
    //
#if DBG
    {
        PLIST_ENTRY  entry;

        for ( entry = NmpDeletedInterfaceList.Flink;
              entry != &NmpDeletedInterfaceList;
              entry = entry->Flink
            )
        {
            if (entry == &(Interface->Linkage)) {
                break;
            }
        }

        CL_ASSERT(entry == &(Interface->Linkage));
    }
#endif DBG

    RemoveEntryList(&(Interface->Linkage));

    //
    // Dereference the node and network objects
    //
    if (Interface->Node != NULL) {
        OmDereferenceObject(Interface->Node);
    }

    if (Interface->Network != NULL) {
        OmDereferenceObject(Interface->Network);
    }

    //
    // Free storage used by the object fields.
    //
    NM_FREE_OBJECT_FIELD(Interface, Description);
    NM_FREE_OBJECT_FIELD(Interface, AdapterName);
    NM_FREE_OBJECT_FIELD(Interface, AdapterId);
    NM_FREE_OBJECT_FIELD(Interface, Address);
    NM_FREE_OBJECT_FIELD(Interface, ClusnetEndpoint);

    return(TRUE);

}  // NmpDestroyInterfaceObject



DWORD
NmpEnumInterfaceObjects1(
    OUT PNM_INTERFACE_ENUM *  InterfaceEnum1
    )
/*++

Routine Description:

    Reads interface information for all defined cluster networks
    and fills in an enumeration structure.

Arguments:

    InterfaceEnum1 -  A pointer to the variable into which to place a
                      pointer to the allocated interface enumeration.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

    Called with the NmpLock held.

--*/

{
    DWORD                status = ERROR_SUCCESS;
    PNM_INTERFACE_ENUM   interfaceEnum1 = NULL;
    DWORD                i;
    DWORD                valueLength;
    PLIST_ENTRY          entry;
    PNM_INTERFACE        netInterface;


    *InterfaceEnum1 = NULL;

    if (NmpInterfaceCount == 0) {
        valueLength = sizeof(NM_INTERFACE_ENUM);
    }
    else {
        valueLength =
            sizeof(NM_INTERFACE_ENUM) +
            (sizeof(NM_INTERFACE_INFO) * (NmpInterfaceCount - 1));
    }

    interfaceEnum1 = MIDL_user_allocate(valueLength);

    if (interfaceEnum1 == NULL) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    ZeroMemory(interfaceEnum1, valueLength);

    for (entry = NmpInterfaceList.Flink, i=0;
         entry != &NmpInterfaceList;
         entry = entry->Flink, i++
        )
    {
        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, Linkage);

        status = NmpGetInterfaceObjectInfo1(
                     netInterface,
                     &(interfaceEnum1->InterfaceList[i])
                     );

        if (status != ERROR_SUCCESS) {
            ClNetFreeInterfaceEnum1(interfaceEnum1);
            return(status);
        }
    }

    interfaceEnum1->InterfaceCount = NmpInterfaceCount;
    *InterfaceEnum1 = interfaceEnum1;

    return(ERROR_SUCCESS);

}  // NmpEnumInterfaceObjects1



DWORD
NmpEnumInterfaceObjects(
    OUT PNM_INTERFACE_ENUM2 *  InterfaceEnum
    )
/*++

Routine Description:

    Reads interface information for all defined cluster networks
    and fills in an enumeration structure.

Arguments:

    InterfaceEnum -  A pointer to the variable into which to place a
                     pointer to the allocated interface enumeration.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

    Called with the NmpLock held.

--*/

{
    DWORD                 status = ERROR_SUCCESS;
    PNM_INTERFACE_ENUM2   interfaceEnum = NULL;
    DWORD                 i;
    DWORD                 valueLength;
    PLIST_ENTRY           entry;
    PNM_INTERFACE         netInterface;


    *InterfaceEnum = NULL;

    if (NmpInterfaceCount == 0) {
        valueLength = sizeof(NM_INTERFACE_ENUM2);
    }
    else {
        valueLength =
            sizeof(NM_INTERFACE_ENUM2) +
            (sizeof(NM_INTERFACE_INFO2) * (NmpInterfaceCount - 1));
    }

    interfaceEnum = MIDL_user_allocate(valueLength);

    if (interfaceEnum == NULL) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    ZeroMemory(interfaceEnum, valueLength);

    for (entry = NmpInterfaceList.Flink, i=0;
         entry != &NmpInterfaceList;
         entry = entry->Flink, i++
        )
    {
        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, Linkage);

        status = NmpGetInterfaceObjectInfo(
                     netInterface,
                     &(interfaceEnum->InterfaceList[i])
                     );

        if (status != ERROR_SUCCESS) {
            ClNetFreeInterfaceEnum((PNM_INTERFACE_ENUM2) interfaceEnum);
            return(status);
        }
    }

    interfaceEnum->InterfaceCount = NmpInterfaceCount;
    *InterfaceEnum = interfaceEnum;

    return(ERROR_SUCCESS);

}  // NmpEnumInterfaceObjects


DWORD
NmpEnumInterfaceObjectStates(
    OUT PNM_INTERFACE_STATE_ENUM *  InterfaceStateEnum
    )
/*++

Routine Description:

    Reads state information for all defined cluster network interfaces
    and fills in an enumeration structure.

Arguments:

    InterfaceStateEnum -  A pointer to the variable into which to place a
                          pointer to the allocated interface enumeration.

Return Value:

    ERROR_SUCCESS if the routine succeeds.
    A Win32 error code otherwise.

Notes:

    Called with the NmpLock held.

--*/

{
    DWORD                      status = ERROR_SUCCESS;
    PNM_INTERFACE_STATE_ENUM   interfaceStateEnum = NULL;
    PNM_INTERFACE_STATE_INFO   interfaceStateInfo;
    DWORD                      i;
    DWORD                      valueLength;
    PLIST_ENTRY                entry;
    PNM_INTERFACE              netInterface;
    LPWSTR                     interfaceId;


    *InterfaceStateEnum = NULL;

    if (NmpInterfaceCount == 0) {
        valueLength = sizeof(NM_INTERFACE_STATE_ENUM);
    }
    else {
        valueLength =
            sizeof(NM_INTERFACE_STATE_ENUM) +
            (sizeof(NM_INTERFACE_STATE_INFO) * (NmpInterfaceCount - 1));
    }

    interfaceStateEnum = MIDL_user_allocate(valueLength);

    if (interfaceStateEnum == NULL) {
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    ZeroMemory(interfaceStateEnum, valueLength);

    for (entry = NmpInterfaceList.Flink, i=0;
         entry != &NmpInterfaceList;
         entry = entry->Flink, i++
        )
    {
        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, Linkage);
        interfaceId = (LPWSTR) OmObjectId(netInterface);
        interfaceStateInfo = &(interfaceStateEnum->InterfaceList[i]);

        interfaceStateInfo->State = netInterface->State;

        interfaceStateInfo->Id = MIDL_user_allocate(NM_WCSLEN(interfaceId));

        if (interfaceStateInfo->Id == NULL) {
            NmpFreeInterfaceStateEnum(interfaceStateEnum);
            return(ERROR_NOT_ENOUGH_MEMORY);
        }

        lstrcpyW(interfaceStateInfo->Id, interfaceId);
    }

    interfaceStateEnum->InterfaceCount = NmpInterfaceCount;
    *InterfaceStateEnum = interfaceStateEnum;

    return(ERROR_SUCCESS);

}  // NmpEnumInterfaceObjectStates


/////////////////////////////////////////////////////////////////////////////
//
// State Management routines
//
/////////////////////////////////////////////////////////////////////////////
VOID
NmpSetInterfaceConnectivityData(
    PNM_NETWORK                  Network,
    DWORD                        InterfaceNetIndex,
    CLUSTER_NETINTERFACE_STATE   State
    )
{
    PNM_CONNECTIVITY_MATRIX   matrixEntry;


    Network->ConnectivityVector->Data[InterfaceNetIndex] =
        (NM_STATE_ENTRY) State;

    Network->StateWorkVector[InterfaceNetIndex].State =
        (NM_STATE_ENTRY) State;

    matrixEntry = NM_GET_CONNECTIVITY_MATRIX_ENTRY(
                      Network->ConnectivityMatrix,
                      InterfaceNetIndex,
                      InterfaceNetIndex,
                      Network->ConnectivityVector->EntryCount
                      );

    *matrixEntry = (NM_STATE_ENTRY)State;

    return;

}  // NmpSetInterfaceConnectivityData


VOID
NmpReportLocalInterfaceStateEvent(
    IN CL_NODE_ID     NodeId,
    IN CL_NETWORK_ID  NetworkId,
    IN DWORD          NewState
    )
{
    PNM_INTERFACE  netInterface;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnlinePending)){
        netInterface = NmpGetInterfaceForNodeAndNetworkById(
                           NodeId,
                           NetworkId
                           );

        if (netInterface != NULL) {
            NmpProcessLocalInterfaceStateEvent(netInterface, NewState);
        }

        NmpLockedLeaveApi();
    }

    NmpReleaseLock();

    return;

} // NmReportLocalInterfaceStateEvent


VOID
NmpProcessLocalInterfaceStateEvent(
    IN PNM_INTERFACE                Interface,
    IN CLUSTER_NETINTERFACE_STATE   NewState
    )
/*+

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD                     status;
    PNM_NETWORK               network = Interface->Network;
    LPCWSTR                   interfaceId = OmObjectId(Interface);
    LPCWSTR                   networkId = OmObjectId(network);
    LPCWSTR                   networkName = OmObjectName(network);
    PNM_NODE                  node = Interface->Node;
    LPCWSTR                   nodeName = OmObjectName(node);
    PNM_CONNECTIVITY_VECTOR   vector = network->ConnectivityVector;
    DWORD                     ifNetIndex = Interface->NetIndex;


    //
    // Filter out stale reports for dead nodes.
    //
    if ((node == NmLocalNode) || (node->State != ClusterNodeDown)) {
        CL_ASSERT(
            vector->Data[ifNetIndex] !=
            (NM_STATE_ENTRY) ClusterNetInterfaceStateUnknown
            );

        //
        // Apply the change to the local connectivity vector.
        //
        vector->Data[ifNetIndex] = (NM_STATE_ENTRY) NewState;

        //
        // Log an event
        //
        switch (NewState) {

        case ClusterNetInterfaceUp:
            //
            // A local interface is now operational, or a remote interface
            // is now reachable. Schedule an immediate connectivity report,
            // since this event may avert failure of resources that depend
            // on the interface.
            //
            if (node != NmLocalNode) {
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Communication was (re)established with "
                    "interface %1!ws! (node: %2!ws!, network: %3!ws!)\n",
                    interfaceId,
                    nodeName,
                    networkName
                    );

                CsLogEvent2(
                    LOG_NOISE,
                    NM_EVENT_NETINTERFACE_UP,
                    nodeName,
                    networkName
                    );
            }

            if (NmpLeaderNodeId == NmLocalNodeId) {
                //
                // This node is the leader. Call the handler routine
                // directly.
                //
                NmpReportNetworkConnectivity(network);
            }
            else {
                //
                // We need to report to the leader.
                // Defer to a worker thread.
                //
                NmpScheduleNetworkConnectivityReport(network);
            }

            break;

        case ClusterNetInterfaceUnreachable:
            //
            // A remote interface is unreachable.
            //
            if (node != NmLocalNode) {
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] Communication was lost with interface "
                    "%1!ws! (node: %2!ws!, network: %3!ws!)\n",
                    interfaceId,
                    nodeName,
                    networkName
                    );

                CsLogEvent2(
                    LOG_UNUSUAL,
                    NM_EVENT_NETINTERFACE_UNREACHABLE,
                    nodeName,
                    networkName
                    );
            }

            if (NmpLeaderNodeId == NmLocalNodeId) {
                //
                // This node is the leader. Call the handler routine
                // directly.
                //
                NmpReportNetworkConnectivity(network);
            }
            else {
                //
                // Schedule a delayed connectivity report in order to
                // aggregate multiple failures.
                //
                NmpStartNetworkConnectivityReportTimer(network);
            }

            break;

        case ClusterNetInterfaceFailed:
            //
            // A local interface has failed. Schedule an immediate
            // connectivity report.
            //
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Local interface %1!ws! on network %2!ws! "
                "has failed\n",
                interfaceId,
                networkName
                );
            CsLogEvent1(
                LOG_UNUSUAL,
                NM_EVENT_NETINTERFACE_FAILED,
                networkName
                );

            if (NmpLeaderNodeId == NmLocalNodeId) {
                //
                // This node is the leader. Call the handler routine
                // directly.
                //
                NmpReportNetworkConnectivity(network);
            }
            else {
                //
                // We need to report to the leader. Defer to a worker thread.
                //
                NmpScheduleNetworkConnectivityReport(network);
            }

            break;

        default:
            break;
        }
    }
    else {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Ignoring stale report from clusnet for interface %1!ws! (node: %2!ws!, network: %3!ws!).\n",
            interfaceId,
            nodeName,
            networkName
            );
    }

    return;

} // NmpProcessLocalInterfaceStateEvent


DWORD
NmpReportInterfaceConnectivity(
    IN RPC_BINDING_HANDLE       RpcBinding,
    IN LPWSTR                   InterfaceId,
    IN PNM_CONNECTIVITY_VECTOR  ConnectivityVector,
    IN LPWSTR                   NetworkId
    )
/*++

Routine Description:

    Sends a network connectivity report to the leader node via RPC.

Arguments:

    RpcBinding - The RPC binding handle to use in the call to the leader.

    InterfaceId - A pointer to a string that identifies the interface
                  to which the report applies.

    ConnectivityVector - A pointer to the connectivity vector to be included
                         in the report.

    NetworkId - A pointer to a string that identifies the network with
                which the interface is associated.

Return Value:

    A Win32 status code.

Notes:

   Called with NM lock held.
   Releases & reacquires NM lock during processing.

--*/
{
    RPC_ASYNC_STATE                  rpcAsyncState;
    DWORD                            status;
    PNM_CONNECTIVITY_REPORT_CONTEXT  context;
    PNM_LEADER_CHANGE_WAIT_ENTRY     waitEntry;
    BOOL                             result;



    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Reporting connectivity to leader for network %1!ws!.\n",
        NetworkId
        );

    //
    // Allocate a context block for this report
    //
    context = LocalAlloc(
                  (LMEM_FIXED | LMEM_ZEROINIT),
                  sizeof(NM_CONNECTIVITY_REPORT_CONTEXT)
                  );

    if (context == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to allocate connectivity report context, "
            "status %1!u!.\n",
            status
            );
        return(status);
    }

    context->ConnectivityReportEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (context->ConnectivityReportEvent == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL,
            "[NM] Unable to create event for connectivity report, "
            "status %1!u!\n",
            status
            );
        goto error_exit;
    }

    waitEntry = &(context->LeaderChangeWaitEntry);

    waitEntry->LeaderChangeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if (waitEntry->LeaderChangeEvent == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL,
            "[NM] Unable to create event for connectivity report, "
            "status %1!u!\n",
            status
            );
        goto error_exit;
    }

    //
    // Initialize the status block for the async RPC call
    //
    status = RpcAsyncInitializeHandle(
                 &rpcAsyncState,
                 sizeof(rpcAsyncState)
                 );

    if (status != RPC_S_OK) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to initialize RPC status block for connectivity "
            "report, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    rpcAsyncState.NotificationType = RpcNotificationTypeEvent;
    rpcAsyncState.u.hEvent = context->ConnectivityReportEvent;
    result = ResetEvent(context->ConnectivityReportEvent);
    CL_ASSERT(result != 0);

    //
    // Hook changes in the node leadership.
    //
    result = ResetEvent(waitEntry->LeaderChangeEvent);
    CL_ASSERT(result != 0);
    InsertTailList(&NmpLeaderChangeWaitList, &(waitEntry->Linkage));

    NmpReleaseLock();

    //
    // Send the report to the leader
    //
    status = NmRpcReportInterfaceConnectivity(
                 &rpcAsyncState,
                 RpcBinding,
                 InterfaceId,
                 ConnectivityVector
                 );

    if (status == RPC_S_OK) {
        //
        // The call is pending.
        //
        HANDLE  waitHandles[2];
        DWORD   rpcStatus;


        //
        // Wait for the call to complete or a leadership change.
        //
        waitHandles[0] = context->ConnectivityReportEvent;
        waitHandles[1] = waitEntry->LeaderChangeEvent;

        status = WaitForMultipleObjects(
                     2,
                     waitHandles,
                     FALSE,
                     INFINITE
                     );

        if (status != WAIT_OBJECT_0) {
            //
            // The leadership changed. Cancel the RPC call.
            //
            // We would also go through this path if the wait failed for
            // some reason, but that really should not happen. Either way,
            // we should cancel the call.
            //
            CL_ASSERT(status == (WAIT_OBJECT_0 + 1));

            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Leadership changed. Cancelling connectivity report for "
                "network %1!ws!.\n",
                NetworkId
                );

            rpcStatus = RpcAsyncCancelCall(&rpcAsyncState, TRUE);
            CL_ASSERT(rpcStatus == RPC_S_OK);

            //
            // Wait for the call to complete.
            //
            status = WaitForSingleObject(
                         context->ConnectivityReportEvent,
                         INFINITE
                         );
            CL_ASSERT(status == WAIT_OBJECT_0);
        }

        //
        // At this point, the call should be complete. Get the completion
        // status. Any RPC error will be returned in 'rpcStatus'. If there
        // was no RPC error, then any application error will be returned
        // in 'status'.
        //
        rpcStatus = RpcAsyncCompleteCall(&rpcAsyncState, &status);

        if (rpcStatus != RPC_S_OK) {
            //
            // Either the call was cancelled or an RPC error
            // occurred. The application status is irrelevant.
            //
            status = rpcStatus;
        }

        if (status == RPC_S_OK) {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Connectivity report completed successfully "
                "for network %1!ws!.\n",
                NetworkId
                );
        }
        else if (status == RPC_S_CALL_CANCELLED) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Connectivity report was cancelled for "
                "network %1!ws!.\n",
                NetworkId
                );
        }
        else {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Connectivity report failed for network "
                "%1!ws!, status %2!u!.\n",
                NetworkId,
                status
                );

            CL_ASSERT(status != RPC_S_ASYNC_CALL_PENDING);
        }
    }
    else {
        //
        // A synchronous error was returned.
        //
        CL_ASSERT(status != RPC_S_ASYNC_CALL_PENDING);

        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Connectivity report failed for network %1!ws!, "
            "status %2!u!.\n",
            NetworkId,
            status
            );
    }

    NmpAcquireLock();

error_exit:

    //
    // Free the context block
    //
    if (context != NULL) {
        if (context->ConnectivityReportEvent != NULL) {
            CloseHandle(context->ConnectivityReportEvent);
        }

        if (waitEntry->LeaderChangeEvent != NULL) {
            //
            // Remove our leadership change notification hook.
            //
            if (waitEntry->Linkage.Flink != NULL) {
                RemoveEntryList(&(waitEntry->Linkage));
            }

            CloseHandle(waitEntry->LeaderChangeEvent);
        }

        LocalFree(context);
    }

    return(status);

} // NmpReportInterfaceConnectivity


VOID
NmpProcessInterfaceConnectivityReport(
    IN PNM_INTERFACE               SourceInterface,
    IN PNM_CONNECTIVITY_VECTOR     ConnectivityVector
    )
/*+

Notes:

    Called with NmpLock held.

--*/
{
    PNM_NETWORK              network = SourceInterface->Network;
    PNM_CONNECTIVITY_MATRIX  matrix = network->ConnectivityMatrix;
    DWORD                    entryCount;
    PNM_NODE                 node = SourceInterface->Node;
    PNM_CONNECTIVITY_VECTOR  vector = network->ConnectivityVector;
    DWORD                    ifNetIndex = SourceInterface->NetIndex;


    //
    // Filter out stale reports from dead nodes and for
    // disabled/deleted networks.
    //
    if ( ((node == NmLocalNode) || (node->State != ClusterNodeDown)) &&
         NmpIsNetworkEnabledForUse(network) &&
         !NM_DELETE_PENDING(network)
       )
    {
        //
        // Update the network's connectivity matrix
        //
        if (network->ConnectivityVector->EntryCount <= vector->EntryCount) {
            entryCount = network->ConnectivityVector->EntryCount;
        }
        else {
            //
            // An interface must have been added while this
            // call was in flight. Ignore the missing data.
            //
            entryCount = ConnectivityVector->EntryCount;
        }

        CopyMemory(
            NM_GET_CONNECTIVITY_MATRIX_ROW(
                matrix,
                ifNetIndex,
                entryCount
                ),
            &(ConnectivityVector->Data[0]),
            entryCount * sizeof(NM_STATE_ENTRY)
            );

        //
        // If this is the leader node, and no NT4 nodes are in the cluster,
        // schedule a state recalculation.
        //
        if (NmpLeaderNodeId == NmLocalNodeId) {
            NmpStartNetworkStateRecalcTimer(
                network,
                NM_NET_STATE_RECALC_TIMEOUT
                );
        }
    }
    else {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Ignoring stale connectivity report from interface %1!ws!.\n",
            OmObjectId(SourceInterface)
            );
    }

    return;

} // NmpProcessInterfaceConnectivityReport


VOID
NmpFreeInterfaceStateEnum(
    PNM_INTERFACE_STATE_ENUM   InterfaceStateEnum
    )
{
    PNM_INTERFACE_STATE_INFO  interfaceStateInfo;
    DWORD                     i;


    for (i=0; i<InterfaceStateEnum->InterfaceCount; i++) {
        interfaceStateInfo = &(InterfaceStateEnum->InterfaceList[i]);

        if (interfaceStateInfo->Id != NULL) {
            MIDL_user_free(interfaceStateInfo->Id);
        }
    }

    MIDL_user_free(InterfaceStateEnum);

    return;

} // NmpFreeInterfaceStateEnum


BOOL
NmpIsAddressInAddressEnum(
    ULONGLONG           Address,
    PNM_ADDRESS_ENUM    AddressEnum
    )
{
    DWORD    i;


    for (i=0; i<AddressEnum->AddressCount; i++) {
        if (AddressEnum->AddressList[i] == Address) {
            return(TRUE);
        }
    }

    return(FALSE);

}  // NmpIsAddressInAddressEnum


DWORD
NmpBuildInterfaceOnlineAddressEnum(
    PNM_INTERFACE       Interface,
    PNM_ADDRESS_ENUM *  OnlineAddressEnum
    )
/*++

    Called with NmpLock held and Interface referenced.

--*/
{
    DWORD                       status = ERROR_SUCCESS;
    PNM_ADDRESS_ENUM            onlineAddressEnum = NULL;
    DWORD                       onlineAddressEnumSize;
    PCLRTL_NET_ADAPTER_ENUM     adapterEnum = NULL;
    PCLRTL_NET_ADAPTER_INFO     adapterInfo = NULL;
    PCLRTL_NET_INTERFACE_INFO   adapterIfInfo = NULL;
    PNM_ADDRESS_ENUM            onlineEnum = NULL;
    DWORD                       onlineEnumSize;


    *OnlineAddressEnum = NULL;

    //
    // Get the local network configuration.
    //
    adapterEnum = ClRtlEnumNetAdapters();

    if (adapterEnum == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to obtain local network configuration, status %1!u!.\n",
            status
            );
        return(status);
    }

    //
    // Find the adapter for this interface
    //
    adapterInfo = ClRtlFindNetAdapterById(
                      adapterEnum,
                      Interface->AdapterId
                      );

    if (adapterInfo == NULL) {
        status = ERROR_NOT_FOUND;
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to find adapter for interface %1!ws!, status %2!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Allocate an address enum structure.
    //
    if (adapterInfo->InterfaceCount == 0) {
        onlineEnumSize = sizeof(NM_ADDRESS_ENUM);
    }
    else {
        onlineEnumSize = sizeof(NM_ADDRESS_ENUM) +
                         ( (adapterInfo->InterfaceCount - 1) *
                           sizeof(ULONGLONG)
                         );
    }

    onlineEnum = midl_user_allocate(onlineEnumSize);

    if (onlineEnum == NULL) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to allocate memory for ping list.\n"
            );
        status = ERROR_NOT_ENOUGH_MEMORY;
        goto error_exit;
    }

    onlineEnum->AddressSize = sizeof(ULONG);
    onlineEnum->AddressCount = 0;

    for (adapterIfInfo = adapterInfo->InterfaceList;
         adapterIfInfo != NULL;
         adapterIfInfo = adapterIfInfo->Next
        )
    {
        //
        // Skip invalid addresses (0.0.0.0)
        //
        if (adapterIfInfo->InterfaceAddress != 0) {
            onlineEnum->AddressList[onlineEnum->AddressCount++] =
                (ULONGLONG) adapterIfInfo->InterfaceAddress;

                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Found address %1!ws! for interface %2!ws!.\n",
                    adapterIfInfo->InterfaceAddressString,
                    OmObjectId(Interface)
                    );
        }
    }

    *OnlineAddressEnum = onlineEnum;
    status = ERROR_SUCCESS;

error_exit:

    if (adapterEnum != NULL) {
        ClRtlFreeNetAdapterEnum(adapterEnum);
    }

    return(status);

} // NmpBuildInterfaceOnlineAddressEnum


DWORD
NmpBuildInterfacePingAddressEnum(
    IN  PNM_INTERFACE       Interface,
    IN  PNM_ADDRESS_ENUM    OnlineAddressEnum,
    OUT PNM_ADDRESS_ENUM *  PingAddressEnum
    )
/*++

    Called with NmpLock held and Interface referenced.

--*/
{
    DWORD                       status = ERROR_SUCCESS;
    PNM_NETWORK                 network = Interface->Network;
    PMIB_IPFORWARDTABLE         ipForwardTable = NULL;
    PMIB_IPFORWARDROW           ipRow, ipRowLimit;
    PMIB_TCPTABLE               tcpTable = NULL;
    PMIB_TCPROW                 tcpRow, tcpRowLimit;
    ULONG                       netAddress, netMask;
    DWORD                       allocSize, tableSize;
    BOOL                        duplicate;
    DWORD                       i;
    PNM_ADDRESS_ENUM            pingEnum = NULL;
    DWORD                       pingEnumSize;


    *PingAddressEnum = NULL;

    //
    // Convert the network address & mask strings to binary
    //
    status = ClRtlTcpipStringToAddress(network->Address, &netAddress);

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to convert network address string %1!ws! to binary, status %2!u!.\n",
            network->Address,
            status
            );
        return(status);
    }

    status = ClRtlTcpipStringToAddress(network->AddressMask, &netMask);

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to convert network address mask string %1!ws! to binary, status %2!u!.\n",
            network->AddressMask,
            status
            );
        return(status);
    }

    //
    // We don't need the lock for the rest of the function.
    //
    NmpReleaseLock();

    //
    // Allocate a ping enum structure
    //
    pingEnumSize = sizeof(NM_ADDRESS_ENUM) +
                   ((NM_MAX_IF_PING_ENUM_SIZE - 1) * sizeof(ULONGLONG));

    pingEnum = midl_user_allocate(pingEnumSize);

    if (pingEnum == NULL) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to allocate memory for ping list.\n"
            );
        status = ERROR_NOT_ENOUGH_MEMORY;
        goto error_exit;
    }

    pingEnum->AddressSize = sizeof(ULONG);
    pingEnum->AddressCount = 0;

    //
    // Fetch the IP Route Table
    //
    allocSize = sizeof(MIB_IPFORWARDTABLE) + (sizeof(MIB_IPFORWARDROW) * 20);

    do {
        if (ipForwardTable != NULL) {
            LocalFree(ipForwardTable);
        }

        ipForwardTable = LocalAlloc(LMEM_FIXED, allocSize);

        if (ipForwardTable == NULL) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to allocate memory for IP route table.\n"
                );
            status = ERROR_NOT_ENOUGH_MEMORY;
            goto error_exit;
        }

        tableSize = allocSize;

        status = GetIpForwardTable(
                     ipForwardTable,
                     &tableSize,
                     FALSE
                     );

        allocSize = tableSize;

    } while (status == ERROR_INSUFFICIENT_BUFFER);

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[ClNet] Failed to obtain IP route table, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Add the IP route entries to the ping list.
    //
    for ( ipRow = &(ipForwardTable->table[0]),
            ipRowLimit = ipRow + ipForwardTable->dwNumEntries;
          ipRow < ipRowLimit;
          ipRow++
        )
    {
        if ((ipRow->dwForwardNextHop & netMask) == netAddress) {
            //
            // Make sure this address isn't in the online address enum.
            //
            duplicate = NmpIsAddressInAddressEnum(
                            (ULONGLONG) ipRow->dwForwardNextHop,
                            OnlineAddressEnum
                            );

            if (!duplicate) {
                //
                // Make sure this address isn't already in the ping enum.
                //
                duplicate = NmpIsAddressInAddressEnum(
                                (ULONGLONG) ipRow->dwForwardNextHop,
                                pingEnum
                                );

                if (!duplicate) {
                    pingEnum->AddressList[pingEnum->AddressCount++] =
                        (ULONGLONG) ipRow->dwForwardNextHop;

                    if (pingEnum->AddressCount == NM_MAX_IF_PING_ENUM_SIZE) {
                        LocalFree(ipForwardTable);
                        *PingAddressEnum = pingEnum;
                        NmpAcquireLock();

                        return(ERROR_SUCCESS);
                    }
                }
            }
        }
    }

    LocalFree(ipForwardTable); ipForwardTable = NULL;

    //
    // Fetch the TCP Connection Table
    //
    allocSize = sizeof(MIB_TCPTABLE) + (sizeof(MIB_TCPROW) * 20);

    do {
        if (tcpTable != NULL) {
            LocalFree(tcpTable);
        }

        tcpTable = LocalAlloc(LMEM_FIXED, allocSize);

        if (tcpTable == NULL) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to allocate memory for TCP conn table.\n"
                );
            status = ERROR_NOT_ENOUGH_MEMORY;
            goto error_exit;
        }

        tableSize = allocSize;

        status = GetTcpTable(
                     tcpTable,
                     &tableSize,
                     FALSE
                     );

        allocSize = tableSize;

    } while (status == ERROR_INSUFFICIENT_BUFFER);

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[ClNet] Failed to obtain TCP conn table, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    //
    // Add the TCP remote addresses to the ping list.
    //
    for ( tcpRow = &(tcpTable->table[0]),
            tcpRowLimit = tcpRow + tcpTable->dwNumEntries;
          tcpRow < tcpRowLimit;
          tcpRow++
        )
    {
        if ((tcpRow->dwRemoteAddr & netMask) == netAddress) {
            //
            // Make sure this address isn't in the online address enum.
            //
            duplicate = NmpIsAddressInAddressEnum(
                            (ULONGLONG) tcpRow->dwRemoteAddr,
                            OnlineAddressEnum
                            );

            if (!duplicate) {
                //
                // Make sure this address isn't already in the ping enum.
                //
                duplicate = NmpIsAddressInAddressEnum(
                                (ULONGLONG) tcpRow->dwRemoteAddr,
                                pingEnum
                                );

                if (!duplicate) {
                    pingEnum->AddressList[pingEnum->AddressCount++] =
                        (ULONGLONG) tcpRow->dwRemoteAddr;

                    if (pingEnum->AddressCount == NM_MAX_IF_PING_ENUM_SIZE) {
                        break;
                    }
                }
            }
        }
    }

    *PingAddressEnum = pingEnum; pingEnum = NULL;

error_exit:

    if (pingEnum != NULL) {
        midl_user_free(pingEnum);
    }

    if (ipForwardTable != NULL) {
        LocalFree(ipForwardTable);
    }

    if (tcpTable != NULL) {
        LocalFree(tcpTable);
    }

    NmpAcquireLock();

    return(status);

} // NmpBuildInterfacePingAddressEnum


NmpGetInterfaceOnlineAddressEnum(
    PNM_INTERFACE       Interface,
    PNM_ADDRESS_ENUM *  OnlineAddressEnum
    )
/*++

Notes:

    Called with NmpLock held and Interface referenced. Releases and
    reacquires NmpLock.

--*/
{
    DWORD               status;
    LPCWSTR             interfaceId = OmObjectId(Interface);
    PNM_NODE            node = Interface->Node;
    RPC_BINDING_HANDLE  rpcBinding = node->IsolateRpcBinding;


    if (node == NmLocalNode) {
        //
        // Call the internal routine directly
        //
        status = NmpBuildInterfaceOnlineAddressEnum(
                     Interface,
                     OnlineAddressEnum
                     );
    }
    else {
        OmReferenceObject(node);

        NmpReleaseLock();

        CL_ASSERT(rpcBinding != NULL);

        NmStartRpc(node->NodeId);
        status = NmRpcGetInterfaceOnlineAddressEnum(
                     rpcBinding,
                     (LPWSTR) interfaceId,
                     OnlineAddressEnum
                     );
        NmEndRpc(node->NodeId);
        if(status != RPC_S_OK) {
            NmDumpRpcExtErrorInfo(status);
        }

        NmpAcquireLock();

        OmDereferenceObject(node);
    }

    if (status == ERROR_SUCCESS) {
        if ((*OnlineAddressEnum)->AddressSize != sizeof(ULONG)) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Online enum address size is invalid for interface %1!ws!\n",
                interfaceId
                );
            status = ERROR_INCORRECT_ADDRESS;
            midl_user_free(*OnlineAddressEnum);
            *OnlineAddressEnum = NULL;
        }
        else {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Online enum for interface %1!ws! contains %2!u! addresses\n",
                interfaceId,
                (*OnlineAddressEnum)->AddressCount
                );
        }
    }
    else {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to get online address enum for interface %1!ws!, status %2!u!\n",
            interfaceId,
            status
            );
    }

    return(status);

}  // NmpGetInterfaceOnlineAddressEnum


NmpGetInterfacePingAddressEnum(
    PNM_INTERFACE       Interface,
    PNM_ADDRESS_ENUM    OnlineAddressEnum,
    PNM_ADDRESS_ENUM *  PingAddressEnum
    )
/*++

Notes:

    Called with NmpLock held and Interface referenced. Releases and
    reacquires NmpLock.

--*/
{
    DWORD               status;
    LPCWSTR             interfaceId = OmObjectId(Interface);
    PNM_NODE            node = Interface->Node;
    RPC_BINDING_HANDLE  rpcBinding = node->IsolateRpcBinding;


    if (node == NmLocalNode) {
        //
        // Call the internal routine directly
        //
        status = NmpBuildInterfacePingAddressEnum(
                     Interface,
                     OnlineAddressEnum,
                     PingAddressEnum
                     );
    }
    else {
        OmReferenceObject(node);

        NmpReleaseLock();

        CL_ASSERT(rpcBinding != NULL);

        NmStartRpc(node->NodeId);
        status = NmRpcGetInterfacePingAddressEnum(
                     rpcBinding,
                     (LPWSTR) interfaceId,
                     OnlineAddressEnum,
                     PingAddressEnum
                     );
        NmEndRpc(node->NodeId);
        if(status != RPC_S_OK) {
            NmDumpRpcExtErrorInfo(status);
        }

        NmpAcquireLock();

        OmDereferenceObject(node);
    }

    if (status == ERROR_SUCCESS) {
        if ((*PingAddressEnum)->AddressSize != sizeof(ULONG)) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Ping enum address size is invalid for interface %1!ws!\n",
                interfaceId
                );
            status = ERROR_INCORRECT_ADDRESS;
            midl_user_free(*PingAddressEnum);
            *PingAddressEnum = NULL;
        }
        else {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Ping enum for interface %1!ws! contains %2!u! addresses\n",
                interfaceId,
                (*PingAddressEnum)->AddressCount
                );
        }
    }
    else {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to get ping address enum for interface %1!ws!, status %2!u!\n",
            interfaceId,
            status
            );
    }

    return(status);

}  // NmpGetInterfacePingAddressEnum


DWORD
NmpDoInterfacePing(
    IN  PNM_INTERFACE     Interface,
    IN  PNM_ADDRESS_ENUM  PingAddressEnum,
    OUT BOOLEAN *         PingSucceeded
    )
/*++

Notes:

    Called with Interface referenced.

--*/
{
    DWORD     status = ERROR_SUCCESS;
    LPCWSTR   interfaceId = OmObjectId(Interface);
    LPWSTR    addressString;
    DWORD     maxAddressStringLength;
    DWORD     i;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Pinging targets for interface %1!ws!.\n",
        interfaceId
        );

    *PingSucceeded = FALSE;

    if (PingAddressEnum->AddressSize != sizeof(ULONG)) {
        return(ERROR_INCORRECT_ADDRESS);
    }

    ClRtlQueryTcpipInformation(
        &maxAddressStringLength,
        NULL,
        NULL
        );

    addressString = LocalAlloc(
                        LMEM_FIXED,
                        (maxAddressStringLength + 1) * sizeof(WCHAR)
                        );

    if (addressString == NULL) {
        ClRtlLogPrint(LOG_UNUSUAL,
            "[NM] Failed to allocate memory for address string.\n"
            );
        return(ERROR_NOT_ENOUGH_MEMORY);
    }

    for (i=0; i<PingAddressEnum->AddressCount; i++) {
        status = ClRtlTcpipAddressToString(
                     (ULONG) PingAddressEnum->AddressList[i],
                     &addressString
                     );

        if (status == ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Pinging host %1!ws!\n",
                addressString
                );
        }
        else {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to convert address %1!x! to string %2!u!.\n",
                (ULONG) PingAddressEnum->AddressList[i],
                status
                );
        }

        if ( ClRtlIsDuplicateTcpipAddress(
                 (ULONG) PingAddressEnum->AddressList[i])
           )
        {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Ping of host %1!ws! succeeded.\n",
                addressString
                );
            *PingSucceeded = TRUE;
            break;
        }
        else {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Ping of host %1!ws! failed.\n",
                addressString
                );
        }
    }

    LocalFree(addressString);

    return(status);

} // NmpDoInterfacePing


DWORD
NmpTestInterfaceConnectivity(
    PNM_INTERFACE  Interface1,
    PBOOLEAN       Interface1HasConnectivity,
    PNM_INTERFACE  Interface2,
    PBOOLEAN       Interface2HasConnectivity
    )
/*++

Notes:

    Called with NmpLock held. This routine releases and reacquires the
    NmpLock. It must be called with references on the target interfaces.

--*/
{
    DWORD               status, status1, status2;
    PNM_NETWORK         network = Interface1->Network;
    PNM_INTERFACE       localInterface = network->LocalInterface;
    LPCWSTR             networkId = OmObjectId(network);
    LPCWSTR             interface1Id = OmObjectId(Interface1);
    LPCWSTR             interface2Id = OmObjectId(Interface2);
    ULONG               interface1Address, interface2Address;
    PNM_ADDRESS_ENUM    pingEnum1 = NULL, pingEnum2 = NULL;
    PNM_ADDRESS_ENUM    onlineEnum1 = NULL, onlineEnum2 = NULL;
    PNM_ADDRESS_ENUM    unionPingEnum = NULL, unionOnlineEnum = NULL;
    DWORD               addressCount;
    RPC_ASYNC_STATE     async1, async2;
    HANDLE              event1 = NULL, event2 = NULL;
    RPC_BINDING_HANDLE  rpcBinding1 = NULL, rpcBinding2 = NULL;
    DWORD               i1, i2;
    BOOL                duplicate;


    //
    // Reference the nodes associated with the target interfaces so they
    // can't go away during this process.
    //
    OmReferenceObject(Interface1->Node);
    OmReferenceObject(Interface2->Node);

    if (localInterface != NULL) {
        OmReferenceObject(localInterface);
    }

    *Interface1HasConnectivity = *Interface2HasConnectivity = FALSE;

    //
    // Convert the interface address strings to binary form.
    //
    status = ClRtlTcpipStringToAddress(
                 Interface1->Address,
                 &interface1Address
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to convert interface address string %1!ws! to binary, status %2!u!.\n",
            Interface1->Address,
            status
            );
        goto error_exit;
    }

    status = ClRtlTcpipStringToAddress(
                 Interface2->Address,
                 &interface2Address
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to convert interface address string %1!ws! to binary, status %2!u!.\n",
            Interface2->Address,
            status
            );
        goto error_exit;
    }

    //
    // Fetch the online address list from each of the interfaces.
    // The NmpLock will be released when querying a remote interface.
    //
    status = NmpGetInterfaceOnlineAddressEnum(
                 Interface1,
                 &onlineEnum1
                 );

    if (status != ERROR_SUCCESS) {
        goto error_exit;
    }

    status = NmpGetInterfaceOnlineAddressEnum(
                 Interface2,
                 &onlineEnum2
                 );

    if (status != ERROR_SUCCESS) {
        goto error_exit;
    }

    //
    // Bail out if either of the interfaces was deleted while the NmpLock
    // was released.
    //
    if ((NM_DELETE_PENDING(Interface1) || NM_DELETE_PENDING(Interface2))) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Aborting interface connectivity test on network %1!ws! "
            "because an interface was deleted.\n",
            networkId
            );
        status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
        goto error_exit;
    }

    //
    // Take the union of the two online lists
    //
    addressCount = onlineEnum1->AddressCount + onlineEnum2->AddressCount;

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Total online address count for network %1!ws! is %2!u!\n",
        networkId,
        addressCount
        );

    if (addressCount == 0) {
        unionOnlineEnum = LocalAlloc(LMEM_FIXED, sizeof(NM_ADDRESS_ENUM));
    }
    else {
        unionOnlineEnum = LocalAlloc(
                            LMEM_FIXED,
                            sizeof(NM_ADDRESS_ENUM) +
                                ((addressCount - 1) * sizeof(ULONGLONG))
                            );
    }

    if (unionOnlineEnum == NULL) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to allocate memory for union ping list.\n"
            );
        status = ERROR_NOT_ENOUGH_MEMORY;
        goto error_exit;
    }

    unionOnlineEnum->AddressSize = sizeof(ULONG);
    unionOnlineEnum->AddressCount = 0;

    if (onlineEnum1->AddressCount != 0) {
        CopyMemory(
            &(unionOnlineEnum->AddressList[0]),
            &(onlineEnum1->AddressList[0]),
            onlineEnum1->AddressCount * sizeof(ULONGLONG)
            );
        unionOnlineEnum->AddressCount = onlineEnum1->AddressCount;
    }

    if (onlineEnum2->AddressCount != 0) {
        CopyMemory(
            &(unionOnlineEnum->AddressList[unionOnlineEnum->AddressCount]),
            &(onlineEnum2->AddressList[0]),
            onlineEnum2->AddressCount * sizeof(ULONGLONG)
            );
        unionOnlineEnum->AddressCount += onlineEnum2->AddressCount;
    }

    midl_user_free(onlineEnum1); onlineEnum1 = NULL;
    midl_user_free(onlineEnum2); onlineEnum2 = NULL;

    //
    // Fetch the ping target list from each of the interfaces.
    // The NmpLock will be released when querying a remote interface.
    //
    status = NmpGetInterfacePingAddressEnum(
                 Interface1,
                 unionOnlineEnum,
                 &pingEnum1
                 );

    if (status != ERROR_SUCCESS) {
        goto error_exit;
    }

    status = NmpGetInterfacePingAddressEnum(
                 Interface2,
                 unionOnlineEnum,
                 &pingEnum2
                 );

    if (status != ERROR_SUCCESS) {
        goto error_exit;
    }

    //
    // Bail out if either of the interfaces was deleted while the NmpLock
    // was released.
    //
    if ((NM_DELETE_PENDING(Interface1) || NM_DELETE_PENDING(Interface2))) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Aborting interface connectivity test on network %1!ws! "
            "because an interface was deleted.\n",
            networkId
            );
        status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
        goto error_exit;
    }

    NmpReleaseLock();

    //
    // Take the union of the two ping lists
    //
    addressCount = pingEnum1->AddressCount + pingEnum2->AddressCount;

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Total ping address count for network %1!ws! is %2!u!\n",
        networkId,
        addressCount
        );

    if (addressCount == 0) {
        status = ERROR_SUCCESS;
        goto error_lock_and_exit;
    }

    unionPingEnum = LocalAlloc(
                        LMEM_FIXED,
                        sizeof(NM_ADDRESS_ENUM) +
                            ( (NM_MAX_UNION_PING_ENUM_SIZE - 1) *
                              sizeof(ULONGLONG)
                            )
                        );


    if (unionPingEnum == NULL) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to allocate memory for union ping list.\n"
            );
        status = ERROR_NOT_ENOUGH_MEMORY;
        goto error_lock_and_exit;
    }

    unionPingEnum->AddressSize = sizeof(ULONG);
    unionPingEnum->AddressCount = 0;

    i1 = 0; i2 = 0;

    while (TRUE) {
        while (i1 < pingEnum1->AddressCount) {
            duplicate = NmpIsAddressInAddressEnum(
                            pingEnum1->AddressList[i1],
                            unionPingEnum
                            );

            if (!duplicate) {
                unionPingEnum->AddressList[unionPingEnum->AddressCount++] =
                    pingEnum1->AddressList[i1++];
                break;
            }
            else {
                i1++;
            }
        }

        if (unionPingEnum->AddressCount == NM_MAX_UNION_PING_ENUM_SIZE) {
            break;
        }

        while (i2 < pingEnum2->AddressCount) {
            duplicate = NmpIsAddressInAddressEnum(
                            pingEnum2->AddressList[i2],
                            unionPingEnum
                            );

            if (!duplicate) {
                unionPingEnum->AddressList[unionPingEnum->AddressCount++] =
                    pingEnum2->AddressList[i2++];
                break;
            }
            else {
                i2++;
            }
        }

        if ( (unionPingEnum->AddressCount == NM_MAX_UNION_PING_ENUM_SIZE) ||
             ( (i1 == pingEnum1->AddressCount) &&
               (i2 == pingEnum2->AddressCount)
             )
           )
        {
            break;
        }
    }

    midl_user_free(pingEnum1); pingEnum1 = NULL;
    midl_user_free(pingEnum2); pingEnum2 = NULL;

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Union ping list for network %1!ws! contains %2!u! addresses\n",
        networkId,
        unionPingEnum->AddressCount
        );

    //
    // Ask each interface to ping the list of targets using async RPC calls
    //

    //
    // Allocate resources for the async RPC calls
    //
    if (Interface1 != localInterface) {
        event1 = CreateEvent(NULL, FALSE, FALSE, NULL);

        if (event1 == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to allocate event for async rpc, status %1!u!.\n",
                status
                );
            goto error_lock_and_exit;
        }

        status = RpcAsyncInitializeHandle(&async1, sizeof(async1));

        if (status != RPC_S_OK) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to initialize RPC async state, status %1!u!.\n",
                status
                );
            goto error_lock_and_exit;
        }

        async1.NotificationType = RpcNotificationTypeEvent;
        async1.u.hEvent = event1;

        rpcBinding1 = Interface1->Node->IsolateRpcBinding;
    }

    if (Interface2 != localInterface) {
        event2 = CreateEvent(NULL, FALSE, FALSE, NULL);

        if (event2 == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to allocate event for async rpc, status %1!u!.\n",
                status
                );
            goto error_lock_and_exit;
        }

        status = RpcAsyncInitializeHandle(&async2, sizeof(async2));

        if (status != RPC_S_OK) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to initialize RPC async state, status %1!u!.\n",
                status
                );
            goto error_lock_and_exit;
        }

        async2.NotificationType = RpcNotificationTypeEvent;
        async2.u.hEvent = event2;

        rpcBinding2 = Interface2->Node->IsolateRpcBinding;
    }

    if (rpcBinding1 != NULL) {
        //
        // Issue the RPC for interface1 first. Then deal with interface2
        //

        //
        // We need the try-except until a bug is fixed in MIDL
        //
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Issuing RpcDoInterfacePing for interface %1!ws!\n",
            interface1Id
            );

        status = ERROR_SUCCESS;

        try {
            NmRpcDoInterfacePing(
                &async1,
                rpcBinding1,
                (LPWSTR) interface1Id,
                unionPingEnum,
                Interface1HasConnectivity,
                &status1
                );
        } except(I_RpcExceptionFilter(RpcExceptionCode())) {
            status = GetExceptionCode();
        }

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] DoPing RPC failed for interface %1!ws!, status %2!u!.\n",
                interface1Id,
                status
                );
            goto error_lock_and_exit;
        }

        if (rpcBinding2 != NULL) {
            //
            // Issue the RPC for interface2.
            //
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Issuing RpcDoInterfacePing for interface %1!ws!\n",
                interface2Id
                );

            status = ERROR_SUCCESS;

            try {
                NmRpcDoInterfacePing(
                    &async2,
                    rpcBinding2,
                    (LPWSTR) interface2Id,
                    unionPingEnum,
                    Interface2HasConnectivity,
                    &status2
                    );
            } except(I_RpcExceptionFilter(RpcExceptionCode())) {
                status = GetExceptionCode();
            }

            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] DoPing RPC failed for interface %1!ws!, status %2!u!.\n",
                    interface1Id,
                    status
                    );
                goto error_lock_and_exit;
            }

            //
            // Wait for the RPC for interface2 to complete
            //
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Waiting for RpcDoInterfacePing for interface %1!ws! to complete\n",
                interface2Id
                );

            status = WaitForSingleObjectEx(event2, INFINITE, FALSE);
            CL_ASSERT(status == WAIT_OBJECT_0);

            status = RpcAsyncCompleteCall(
                         &async2,
                         &status2
                         );

            CL_ASSERT(status == RPC_S_OK);

            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Wait for RpcDoInterfacePing for interface %1!ws! completed.\n",
                interface2Id
                );
        }
        else {
            //
            // Call the local routine for interface2.
            //
            status2 = NmpDoInterfacePing(
                          Interface2,
                          unionPingEnum,
                          Interface2HasConnectivity
                          );
        }

        //
        // Wait for the RPC for interface1 to complete
        //
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Waiting for RpcDoInterfacePing for interface %1!ws! to complete\n",
            interface1Id
            );

        status = WaitForSingleObjectEx(event1, INFINITE, FALSE);
        CL_ASSERT(status == WAIT_OBJECT_0);

        status = RpcAsyncCompleteCall(
                     &async1,
                     &status1
                     );

        CL_ASSERT(status == RPC_S_OK);

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Wait for RpcDoInterfacePing for interface %1!ws! completed.\n",
            interface1Id
            );
    }
    else {
        //
        // Send the RPC to interface2 first. Then call the local
        // routine for interface1
        //
        CL_ASSERT(rpcBinding2 != NULL);

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Issuing RpcDoInterfacePing for interface %1!ws!\n",
            interface2Id
            );

        status = ERROR_SUCCESS;

        try {
            NmRpcDoInterfacePing(
                &async2,
                rpcBinding2,
                (LPWSTR) interface2Id,
                unionPingEnum,
                Interface2HasConnectivity,
                &status2
                );
        } except(I_RpcExceptionFilter(RpcExceptionCode())) {
            status = GetExceptionCode();
        }

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] DoPing RPC failed for interface %1!ws!, status %2!u!.\n",
                interface1Id,
                status
                );
            goto error_lock_and_exit;
        }

        status1 = NmpDoInterfacePing(
                      Interface1,
                      unionPingEnum,
                      Interface1HasConnectivity
                      );

        //
        // Wait for the RPC for interface2 to complete
        //
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Waiting for RpcDoInterfacePing for interface %1!ws! to complete\n",
            interface2Id
            );

        status = WaitForSingleObject(event2, INFINITE);
        CL_ASSERT(status == WAIT_OBJECT_0);

        status = RpcAsyncCompleteCall(
                     &async2,
                     &status2
                     );

        CL_ASSERT(status == RPC_S_OK);

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Wait for RpcDoInterfacePing for interface %1!ws! completed.\n",
            interface2Id
            );
    }

    if (status1 != RPC_S_OK) {
        status = status1;
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] DoPing RPC failed for interface %1!ws!, status %2!u!.\n",
            interface1Id,
            status
            );
        goto error_lock_and_exit;
    }

    if (status2 != RPC_S_OK) {
        status = status2;
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] DoPing RPC failed for interface %1!ws!, status %2!u!.\n",
            interface2Id,
            status
            );
        goto error_lock_and_exit;

    }

error_lock_and_exit:

    NmpAcquireLock();

    if ( (status == ERROR_SUCCESS) &&
         (NM_DELETE_PENDING(Interface1) || NM_DELETE_PENDING(Interface2))
       )
    {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Aborting interface connectivity test on network %1!ws! "
            "because an interface was deleted.\n",
            networkId
            );
        status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
    }

error_exit:

    OmDereferenceObject(Interface1->Node);
    OmDereferenceObject(Interface2->Node);

    if (localInterface != NULL) {
        OmDereferenceObject(localInterface);
    }

    if (onlineEnum1 != NULL) {
        midl_user_free(onlineEnum1);
    }

    if (onlineEnum2 != NULL) {
        midl_user_free(onlineEnum2);
    }

    if (unionOnlineEnum != NULL) {
        LocalFree(unionOnlineEnum);
    }

    if (pingEnum1 != NULL) {
        midl_user_free(pingEnum1);
    }

    if (pingEnum2 != NULL) {
        midl_user_free(pingEnum2);
    }

    if (unionPingEnum != NULL) {
        LocalFree(unionPingEnum);
    }

    if (event1 != NULL) {
        CloseHandle(event1);
    }

    if (event2 != NULL) {
        CloseHandle(event2);
    }

    return(status);

} // NmpTestInterfaceConnectivity


/////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpRegisterInterface(
    IN PNM_INTERFACE  Interface,
    IN BOOLEAN        RetryOnFailure
    )
/*++

    Called with the NmpLock held.

--*/
{
    DWORD            status;
    LPWSTR           interfaceId = (LPWSTR) OmObjectId(Interface);
    PNM_NETWORK      network = Interface->Network;
    PVOID            tdiAddress = NULL;
    ULONG            tdiAddressLength = 0;
    NDIS_MEDIA_STATE mediaStatus;


    CL_ASSERT(!NmpIsInterfaceRegistered(Interface));

    ClRtlLogPrint(LOG_NOISE,
        "[NM] Registering interface %1!ws! (%2!ws!) with cluster transport, "
        "addr %3!ws!, endpoint %4!ws!.\n",
        interfaceId,
        OmObjectName(Interface),
        Interface->Address,
        Interface->ClusnetEndpoint
        );

    status = ClRtlBuildTcpipTdiAddress(
                 Interface->Address,
                 Interface->ClusnetEndpoint,
                 &tdiAddress,
                 &tdiAddressLength
                 );

    if (status == ERROR_SUCCESS) {
        status = ClusnetRegisterInterface(
                     NmClusnetHandle,
                     Interface->Node->NodeId,
                     Interface->Network->ShortId,
                     0,
                     Interface->AdapterId,
                     wcslen(Interface->AdapterId) * sizeof(WCHAR),
                     tdiAddress,
                     tdiAddressLength,
                     (PULONG) &mediaStatus
                     );

        LocalFree(tdiAddress);

        if (status == ERROR_SUCCESS) {
            Interface->Flags |= NM_FLAG_IF_REGISTERED;
            network->RegistrationRetryTimeout = 0;

            //
            // If this is a local interface, and if its media status
            // indicates that it is connected, schedule a worker thread to
            // deliver an interface up notification. Clusnet does not
            // deliver interface up events for local interfaces.
            //
            if (network->LocalInterface == Interface) {
                if (mediaStatus == NdisMediaStateConnected) {
                    network->Flags |= NM_FLAG_NET_REPORT_LOCAL_IF_UP;
                    NmpScheduleNetworkConnectivityReport(network);
                } else {
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NM] Local interface %1!ws! reported "
                        "disconnected.\n",
                        interfaceId,
                        status
                        );
                    network->Flags |= NM_FLAG_NET_REPORT_LOCAL_IF_FAILED;
                    NmpScheduleNetworkConnectivityReport(network);
                }
            }
        }
        else {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to register interface %1!ws! with cluster "
                "transport, status %2!u!.\n",
                interfaceId,
                status
                );
        }
    }
    else {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to build TDI bind address for interface %1!ws!, "
            "status %2!u!.\n",
            interfaceId,
            status
            );
    }

    if (status != ERROR_SUCCESS) {
        WCHAR  string[16];

        wsprintfW(&(string[0]), L"%u", status);

        CsLogEvent3(
            LOG_UNUSUAL,
            NM_EVENT_REGISTER_NETINTERFACE_FAILED,
            OmObjectName(Interface->Node),
            OmObjectName(Interface->Network),
            string
            );

        //
        // Retry if the error is transient
        //
        if ( RetryOnFailure &&
             ( (status == ERROR_INVALID_NETNAME) ||
               (status == ERROR_NOT_ENOUGH_MEMORY) ||
               (status == ERROR_NO_SYSTEM_RESOURCES)
             )
           )
        {
            NmpStartNetworkRegistrationRetryTimer(network);

            status = ERROR_SUCCESS;
        }
    }

    return(status);

}  // NmpRegisterInterface


VOID
NmpDeregisterInterface(
    IN  PNM_INTERFACE   Interface
    )
/*++

Routine Description:

    Deregisters an interface from the cluster transport.

Arguments:

    Interface - A pointer to the interface to deregister.

Return Value:

    None.

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD status;


    CL_ASSERT(NmpIsInterfaceRegistered(Interface));

    ClRtlLogPrint(LOG_NOISE,
        "[NM] Deregistering interface %1!ws! (%2!ws!) from cluster "
        "transport.\n",
        OmObjectId(Interface),
        OmObjectName(Interface)
        );

    status = ClusnetDeregisterInterface(
                 NmClusnetHandle,
                 Interface->Node->NodeId,
                 Interface->Network->ShortId
                 );

    CL_ASSERT(
        (status == ERROR_SUCCESS) ||
        (status == ERROR_CLUSTER_NETINTERFACE_NOT_FOUND)
        );

    Interface->Flags &= ~NM_FLAG_IF_REGISTERED;

    return;

} // NmpDeregisterNetwork


DWORD
NmpPrepareToCreateInterface(
    IN  PNM_INTERFACE_INFO2   InterfaceInfo,
    OUT PNM_NETWORK *         Network,
    OUT PNM_NODE *            Node
    )
{
    DWORD          status;
    PNM_INTERFACE  netInterface = NULL;
    PNM_NODE       node = NULL;
    PNM_NETWORK    network = NULL;
    PLIST_ENTRY    entry;


    *Node = NULL;
    *Network = NULL;

    //
    // Verify that the associated node and network objects exist.
    //
    network = OmReferenceObjectById(
                  ObjectTypeNetwork,
                  InterfaceInfo->NetworkId
                  );

    if (network == NULL) {
        status = ERROR_CLUSTER_NETWORK_NOT_FOUND;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Network %1!ws! does not exist. Cannot create "
            "interface %2!ws!\n",
            InterfaceInfo->NetworkId,
            InterfaceInfo->Id
            );
        goto error_exit;
    }

    node = OmReferenceObjectById(ObjectTypeNode, InterfaceInfo->NodeId);

    if (node == NULL) {
        status = ERROR_CLUSTER_NODE_NOT_FOUND;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Node %1!ws! does not exist. Cannot create interface %2!ws!\n",
            InterfaceInfo->NodeId,
            InterfaceInfo->Id
            );
        goto error_exit;
    }

    //
    // Verify that the interface doesn't already exist.
    //
    NmpAcquireLock();

    for ( entry = node->InterfaceList.Flink;
          entry != &(node->InterfaceList);
          entry = entry->Flink
        )
    {
        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, NodeLinkage);

        if (netInterface->Network == network) {
            status = ERROR_CLUSTER_NETINTERFACE_EXISTS;
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] An interface already exists for node %1!ws! on network %2!ws!\n",
                InterfaceInfo->NodeId,
                InterfaceInfo->NetworkId
                );
            NmpReleaseLock();
            goto error_exit;
        }
    }

    NmpReleaseLock();

    //
    // Verify that the specified interface ID is unique.
    //
    netInterface = OmReferenceObjectById(
                       ObjectTypeNetInterface,
                       InterfaceInfo->Id
                       );

    if (netInterface != NULL) {
        OmDereferenceObject(netInterface);
        status = ERROR_CLUSTER_NETINTERFACE_EXISTS;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] An interface with ID %1!ws! already exists\n",
            InterfaceInfo->Id
            );
        goto error_exit;
    }

    *Node = node;
    *Network = network;

    return(ERROR_SUCCESS);


error_exit:

    if (network != NULL) {
        OmDereferenceObject(network);
    }

    if (node != NULL) {
        OmDereferenceObject(node);
    }

    return(status);

}  // NmpPrepareToCreateInterface


PNM_INTERFACE
NmpGetInterfaceForNodeAndNetworkById(
    IN  CL_NODE_ID     NodeId,
    IN  CL_NETWORK_ID  NetworkId
    )

/*++

Routine Description:

    Give the node Id and network short Id, return a pointer to
    the intersecting interface object

Arguments:

    NodeId - The ID of the node associated with this interface

    NetworkId - The short Id of the network associated with this interface

Return Value:

    A pointer to the interface object if successful.

    NULL if unsuccessful. Extended error information is available from
    GetLastError().

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD      status;
    PNM_NODE   node = NmpIdArray[NodeId];


    if (node != NULL) {
        PLIST_ENTRY     entry;
        PNM_INTERFACE   netInterface;

        //
        // run down the list of interfaces associated with this node,
        // looking for one whose network matches the specified short ID
        //

        for (entry = node->InterfaceList.Flink;
             entry != &(node->InterfaceList);
             entry = entry->Flink
             )
        {
            netInterface = CONTAINING_RECORD(
                               entry,
                               NM_INTERFACE,
                               NodeLinkage
                               );

            if (netInterface->Network->ShortId == NetworkId) {
                return(netInterface);
            }
        }

        status = ERROR_CLUSTER_NETINTERFACE_NOT_FOUND;
    }
    else {
        status = ERROR_CLUSTER_NODE_NOT_FOUND;
    }

    SetLastError(status);

    return(NULL);

}  // NmpGetInterfaceForNodeAndNetworkById


DWORD
NmpConvertPropertyListToInterfaceInfo(
    IN PVOID              InterfacePropertyList,
    IN DWORD              InterfacePropertyListSize,
    PNM_INTERFACE_INFO2   InterfaceInfo
    )
{
    DWORD  status;

    //
    // Unmarshall the property list.
    //
    ZeroMemory(InterfaceInfo, sizeof(NM_INTERFACE_INFO2));

    status = ClRtlVerifyPropertyTable(
                 NmpInterfaceProperties,
                 NULL,    // Reserved
                 FALSE,   // Don't allow unknowns
                 InterfacePropertyList,
                 InterfacePropertyListSize,
                 (LPBYTE) InterfaceInfo
                 );

    if (status == ERROR_SUCCESS) {
        InterfaceInfo->NetIndex = NmInvalidInterfaceNetIndex;
    }

    return(status);

} // NmpConvertPropertyListToInterfaceInfo


BOOLEAN
NmpVerifyLocalInterfaceConnected(
    IN  PNM_INTERFACE     Interface
    )
/*++

Routine Description:

    Queries local interface adapter for current media
    status using an NDIS ioctl. 
    
Arguments:

    Interface - interface object for local adapter to query
    
Return value:

    TRUE if media status is connected or cannot be determined
    FALSE if media status is disconnected
    
Notes:

    Called and returns with NM lock acquired.
    
--*/
{
    PWCHAR             adapterDevNameBuffer = NULL;
    PWCHAR             adapterDevNamep, prefix, brace;
    DWORD              prefixSize, allocSize, adapterIdSize;
    DWORD              status = ERROR_SUCCESS;
    UNICODE_STRING     adapterDevName;
    NIC_STATISTICS     ndisStats;
    BOOLEAN            mediaConnected = TRUE;

    // verify parameters
    if (Interface == NULL || Interface->AdapterId == NULL) {
        return TRUE;
    }

    // the adapter device name is of the form
    //
    //     \Device\{AdapterIdGUID}
    //
    // the AdapterId field in the NM_INTERFACE structure is
    // currently not enclosed in braces, but we handle the
    // case where it is.

    // set up the adapter device name prefix
    prefix = L"\\Device\\";
    prefixSize = wcslen(prefix) * sizeof(WCHAR);

    // allocate a buffer for the adapter device name.
    adapterIdSize = wcslen(Interface->AdapterId) * sizeof(WCHAR);
    allocSize = prefixSize + adapterIdSize + sizeof(UNICODE_NULL);
    brace = L"{";
    if (*((PWCHAR)Interface->AdapterId) != *brace) {
        allocSize += 2 * sizeof(WCHAR);
    }
    adapterDevNameBuffer = LocalAlloc(LMEM_FIXED, allocSize);
    if (adapterDevNameBuffer == NULL) {
        ClRtlLogPrint(
            LOG_UNUSUAL, 
            "[NM] Failed to allocate device name buffer for "
            "adapter %1!ws!. Assuming adapter is connected.\n",
            Interface->AdapterId
            );        
        return(TRUE);
    }

    // build the adapter device name from the adapter ID
    ZeroMemory(adapterDevNameBuffer, allocSize);

    adapterDevNamep = adapterDevNameBuffer;

    CopyMemory(adapterDevNamep, prefix, prefixSize);

    adapterDevNamep += prefixSize / sizeof(WCHAR);

    if (*((PWCHAR)Interface->AdapterId) != *brace) {
        *adapterDevNamep = *brace;
        adapterDevNamep++;
    }

    CopyMemory(adapterDevNamep, Interface->AdapterId, adapterIdSize);

    if (*((PWCHAR)Interface->AdapterId) != *brace) {
        brace = L"}";
        adapterDevNamep += adapterIdSize / sizeof(WCHAR);
        *adapterDevNamep = *brace;
    }

    RtlInitUnicodeString(&adapterDevName, (LPWSTR)adapterDevNameBuffer);

    // query the adapter for NDIS statistics
    ZeroMemory(&ndisStats, sizeof(ndisStats));
    ndisStats.Size = sizeof(ndisStats);

    if (!NdisQueryStatistics(&adapterDevName, &ndisStats)) {

        status = GetLastError();
        ClRtlLogPrint(
            LOG_UNUSUAL, 
            "[NM] NDIS statistics query to adapter %1!ws! failed, "
            "error %2!u!. Assuming adapter is connected.\n",
            Interface->AdapterId, status
            );
    
    } else {

        if (ndisStats.MediaState == MEDIA_STATE_DISCONNECTED) {
            mediaConnected = FALSE;
        }
    }

    LocalFree(adapterDevNameBuffer);
    
    return(mediaConnected);

} // NmpVerifyLocalInterfaceConnected