/*++

Copyright (c) 1996-1999  Microsoft Corporation

Module Name:

    network.c

Abstract:

    Implements the Node Manager's network management routines.

Author:

    Mike Massa (mikemas) 7-Nov-1996


Revision History:

--*/


#include "nmp.h"


/////////////////////////////////////////////////////////////////////////////
//
// Data
//
/////////////////////////////////////////////////////////////////////////////

ULONG                  NmpNextNetworkShortId = 0;
LIST_ENTRY             NmpNetworkList = {NULL, NULL};
LIST_ENTRY             NmpInternalNetworkList = {NULL, NULL};
LIST_ENTRY             NmpDeletedNetworkList = {NULL, NULL};
DWORD                  NmpNetworkCount = 0;
DWORD                  NmpInternalNetworkCount = 0;
DWORD                  NmpClientNetworkCount = 0;
BOOLEAN                NmpIsConnectivityReportWorkerRunning = FALSE;
BOOLEAN                NmpNeedConnectivityReport = FALSE;
CLRTL_WORK_ITEM        NmpConnectivityReportWorkItem;


RESUTIL_PROPERTY_ITEM
NmpNetworkProperties[] =
    {
        {
            L"Id", NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, Id)
        },
        {
            CLUSREG_NAME_NET_NAME, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, Name)
        },
        {
            CLUSREG_NAME_NET_DESC, NULL, CLUSPROP_FORMAT_SZ,
            (DWORD_PTR) NmpNullString, 0, 0,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, Description)
        },
        {
            CLUSREG_NAME_NET_ROLE, NULL, CLUSPROP_FORMAT_DWORD,
            ClusterNetworkRoleClientAccess,
            ClusterNetworkRoleNone,
            ClusterNetworkRoleInternalAndClient,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, Role)
        },
        {
            CLUSREG_NAME_NET_PRIORITY, NULL, CLUSPROP_FORMAT_DWORD,
            0, 0, 0xFFFFFFFF,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, Priority)
        },
        {
            CLUSREG_NAME_NET_TRANSPORT, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, Transport)
        },
        {
            CLUSREG_NAME_NET_ADDRESS, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, Address)
        },
        {
            CLUSREG_NAME_NET_ADDRESS_MASK, NULL, CLUSPROP_FORMAT_SZ,
            0, 0, 0,
            0,
            FIELD_OFFSET(NM_NETWORK_INFO, AddressMask)
        },
        {
            0
        }
    };

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

Routine Description:

    Initializes network resources.

Arguments:

    None.

Return Value:

   A Win32 status value.

--*/

{
    DWORD                       status;
    OM_OBJECT_TYPE_INITIALIZE   networkTypeInitializer;


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

    //
    // Create the network object type
    //
    ZeroMemory(&networkTypeInitializer, sizeof(OM_OBJECT_TYPE_INITIALIZE));
    networkTypeInitializer.ObjectSize = sizeof(NM_NETWORK);
    networkTypeInitializer.Signature = NM_NETWORK_SIG;
    networkTypeInitializer.Name = L"Network";
    networkTypeInitializer.DeleteObjectMethod = &NmpDestroyNetworkObject;

    status = OmCreateType(ObjectTypeNetwork, &networkTypeInitializer);

    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 object type, status %1!u!\n",
            status
            );
        return(status);
    }

    return(status);

}  // NmpInitializeNetworks


VOID
NmpCleanupNetworks(
    VOID
    )
/*++

Routine Description:

    Destroys all existing network resources.

Arguments:

    None.

Return Value:

   None.

--*/

{
    PNM_NETWORK  network;
    PLIST_ENTRY  entry;
    DWORD        status;


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

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

    while (!IsListEmpty(&NmpNetworkList)) {

        entry = NmpNetworkList.Flink;

        network = CONTAINING_RECORD(entry, NM_NETWORK, Linkage);

        CL_ASSERT(NM_OM_INSERTED(network));

        NmpDeleteNetworkObject(network, FALSE);
    }

    NmpCleanupMulticast();

    NmpReleaseLock();

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

    return;

}  // NmpCleanupNetworks


/////////////////////////////////////////////////////////////////////////////
//
// Top-level routines called during network configuration
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCreateNetwork(
    IN RPC_BINDING_HANDLE    JoinSponsorBinding,
    IN PNM_NETWORK_INFO      NetworkInfo,
    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 add the definition
        // to the cluster database. The sponsor will also prompt all active
        // nodes to instantiate a corresponding object. The object will be
        // instantiated locally later in the join process.
        //
        status = NmRpcCreateNetwork2(
                     JoinSponsorBinding,
                     NmpJoinSequence,
                     NmLocalNodeIdString,
                     NetworkInfo,
                     InterfaceInfo
                     );
    }
    else if (NmpState == NmStateOnlinePending) {
        HLOCALXSACTION   xaction;

        //
        // We are forming a cluster. Add the definitions 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 = NmpCreateNetworkDefinition(NetworkInfo, xaction);

        if (status == ERROR_SUCCESS) {
            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 = NmpGlobalCreateNetwork(NetworkInfo, InterfaceInfo);

        NmpReleaseLock();
    }

    return(status);

}  // NmpCreateNetwork

DWORD
NmpSetNetworkName(
    IN PNM_NETWORK_INFO     NetworkInfo
    )
/*++

Notes:

    Must not be called with NM lock held.

--*/
{
    DWORD            status;


    if (NmpState == NmStateOnlinePending) {
        HLOCALXSACTION   xaction;

        //
        // We are forming a cluster. The local connectoid name has
        // precedence. Fix the cluster network name stored in the
        // cluster database.
        //

        //
        // 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 = NmpSetNetworkNameDefinition(NetworkInfo, 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 either a PnP update or we were called
        // back to indicate that a local connectoid name changed.
        // Issue a global update to set the cluster network name accordingly.
        //
        status = NmpGlobalSetNetworkName( NetworkInfo );
    }

    return(status);

}  // NmpSetNetworkName

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


    //
    // Translate and call the V2.0 procedure. The NetIndex isn't used in this call.
    //
    CopyMemory(&interfaceInfo2, InterfaceInfo1, sizeof(NM_INTERFACE_INFO));
    interfaceInfo2.AdapterId = NmpUnknownString;
    interfaceInfo2.NetIndex = NmInvalidInterfaceNetIndex;

    status = s_NmRpcCreateNetwork2(
                 IDL_handle,
                 JoinSequence,
                 JoinerNodeId,
                 NetworkInfo,
                 &interfaceInfo2
                 );

    return(status);

}  // s_NmRpcCreateNetwork


error_status_t
s_NmRpcCreateNetwork2(
    IN handle_t              IDL_handle,
    IN DWORD                 JoinSequence,   OPTIONAL
    IN LPWSTR                JoinerNodeId,   OPTIONAL
    IN PNM_NETWORK_INFO      NetworkInfo,
    IN PNM_INTERFACE_INFO2   InterfaceInfo
    )
{
    DWORD  status = ERROR_SUCCESS;


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

    NmpAcquireLock();

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

        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] CreateNetwork 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] CreateNetwork call for joining node %1!ws! "
                    "failed because the node is not a member of the "
                    "cluster.\n",
                    JoinerNodeId
                    );
            }
        }

        if (status == ERROR_SUCCESS) {

            status = NmpGlobalCreateNetwork(NetworkInfo, InterfaceInfo);

            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] CreateNetwork 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 CreateNetwork request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // s_NmRpcCreateNetwork2


error_status_t
s_NmRpcSetNetworkName(
    IN handle_t             IDL_handle,
    IN DWORD                JoinSequence,   OPTIONAL
    IN LPWSTR               JoinerNodeId,   OPTIONAL
    IN PNM_NETWORK_INFO     NetworkInfo
    )
{
    DWORD  status = ERROR_SUCCESS;


    ClRtlLogPrint(LOG_NOISE, 
        "[NMJOIN] Received request to set name of network %1!ws! from "
        "joining node %2!ws!.\n",
        NetworkInfo->Id,
        JoinerNodeId
        );

    NmpAcquireLock();

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

        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] SetNetworkName 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] SetNetworkName call for joining node %1!ws! "
                    "failed because the node is not a member of the cluster.\n",
                    JoinerNodeId
                    );
            }
        }

        if (status == ERROR_SUCCESS) {

            status = NmpGlobalSetNetworkName( NetworkInfo );

            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] SetNetworkName 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 SetNetworkName request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // s_NmRpcSetNetworkName

error_status_t
s_NmRpcEnumNetworkDefinitions(
    IN  handle_t            IDL_handle,
    IN  DWORD               JoinSequence,   OPTIONAL
    IN  LPWSTR              JoinerNodeId,   OPTIONAL
    OUT PNM_NETWORK_ENUM *  NetworkEnum
    )
{
    DWORD    status = ERROR_SUCCESS;
    PNM_NODE joinerNode = NULL;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Supplying network 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] EnumNetworkDefinitions 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] EnumNetworkDefinitions call for joining "
                    "node %1!ws! failed because the node is not a member "
                    "of the cluster.\n",
                    JoinerNodeId
                    );
            }
        }

        if (status == ERROR_SUCCESS) {
            status = NmpEnumNetworkObjects(NetworkEnum);

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

                }
                else {
                    ClRtlLogPrint(LOG_CRITICAL, 
                        "[NMJOIN] Failed to enumerate network definitions, "
                        "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 EnumNetworkDefinitions "
            "request.\n"
            );
    }

    NmpReleaseLock();

    return(status);

}  // s_NmRpcEnumNetworkDefinitions


error_status_t
s_NmRpcEnumNetworkAndInterfaceStates(
    IN  handle_t                    IDL_handle,
    IN  DWORD                       JoinSequence,
    IN  LPWSTR                      JoinerNodeId,
    OUT PNM_NETWORK_STATE_ENUM *    NetworkStateEnum,
    OUT PNM_INTERFACE_STATE_ENUM *  InterfaceStateEnum
    )
{
    DWORD     status = ERROR_SUCCESS;


    NmpAcquireLock();

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

        ClRtlLogPrint(LOG_NOISE, 
            "[NMJOIN] Supplying network and interface state information "
            "to joining node.\n"
            );

        if (joinerNode != NULL) {
            if ( (JoinSequence != NmpJoinSequence) ||
                 (NmpJoinerNodeId != joinerNode->NodeId) ||
                 (NmpSponsorNodeId != NmLocalNodeId) ||
                 NmpJoinAbortPending
               )
            {
                status = ERROR_CLUSTER_JOIN_ABORTED;
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NMJOIN] EnumNetworkAndInterfaceStates call for "
                    "joining node %1!ws! failed because the join was "
                    "aborted.\n",
                    JoinerNodeId
                    );
            }
            else {
                CL_ASSERT(joinerNode->State == ClusterNodeJoining);
                CL_ASSERT(NmpJoinerUp);
                CL_ASSERT(NmpJoinTimer == 0);
            }
        }
        else {
            status = ERROR_CLUSTER_NODE_NOT_MEMBER;
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NMJOIN] EnumNetworkAndInterfaceStates call for joining "
                "node %1!ws! failed because the node is not a member of "
                "the cluster.\n",
                JoinerNodeId
                );
        }

        if (status == ERROR_SUCCESS) {

            status = NmpEnumNetworkObjectStates(NetworkStateEnum);

            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NMJOIN] EnumNetworkAndInterfaceStates failed, "
                    "status %1!u!.\n",
                    status
                    );

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

            status = NmpEnumInterfaceObjectStates(InterfaceStateEnum);

            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NMJOIN] EnumNetworkAndInterfaceStates failed, "
                    "status %1!u!.\n",
                    status
                    );

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

                NmpFreeNetworkStateEnum(*NetworkStateEnum);
                *NetworkStateEnum = NULL;
            }
        }

        OmDereferenceObject(joinerNode);

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

    NmpReleaseLock();

    return(status);

}  // s_NmRpcEnumNetworkAndInterfaceStates


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

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD  status = ERROR_SUCCESS;
    DWORD  networkPropertiesSize;
    PVOID  networkProperties;


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

    //
    // Marshall the info structures into property lists.
    //
    status = NmpMarshallObjectInfo(
                 NmpNetworkProperties,
                 NetworkInfo,
                 &networkProperties,
                 &networkPropertiesSize
                 );

    if (status == ERROR_SUCCESS) {
        DWORD  interfacePropertiesSize;
        PVOID  interfaceProperties;

        status = NmpMarshallObjectInfo(
                     NmpInterfaceProperties,
                     InterfaceInfo,
                     &interfaceProperties,
                     &interfacePropertiesSize
                     );

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

            //
            // Issue a global update to create the network
            //
            status = GumSendUpdateEx(
                         GumUpdateMembership,
                         NmUpdateCreateNetwork,
                         4,
                         networkPropertiesSize,
                         networkProperties,
                         sizeof(networkPropertiesSize),
                         &networkPropertiesSize,
                         interfacePropertiesSize,
                         interfaceProperties,
                         sizeof(interfacePropertiesSize),
                         &interfacePropertiesSize
                         );

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

            NmpAcquireLock();

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

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

    return(status);

} // NmpGlobalCreateNetwork


DWORD
NmpGlobalSetNetworkName(
    IN PNM_NETWORK_INFO NetworkInfo
    )

/*++

Routine Description:

    Changes the name of a network defined for the cluster.

Arguments:

    NetworkInfo - A pointer to info about the network to be modified.

Return Value:

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

Notes:

    Must not be called with NM lock held.

--*/

{
    DWORD  status = ERROR_SUCCESS;

    if (status == ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Processing request to set name for network %1!ws! "
            "to '%2!ws!'.\n",
            NetworkInfo->Id,
            NetworkInfo->Name
            );

        //
        // Issue a global update
        //
        status = GumSendUpdateEx(
                     GumUpdateMembership,
                     NmUpdateSetNetworkName,
                     2,
                     NM_WCSLEN(NetworkInfo->Id),
                     NetworkInfo->Id,
                     NM_WCSLEN( NetworkInfo->Name ),
                     NetworkInfo->Name
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Global update to set name of network %1!ws! "
                "failed, status %2!u!.\n",
                NetworkInfo->Id,
                status
                );
        }
    }
    else {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] New name parameter supplied for network %1!ws! is invalid\n",
            NetworkInfo->Id
            );
    }

    return(status);

}  // NmpGlobalSetNetworkName


DWORD
NmpGlobalSetNetworkAndInterfaceStates(
    PNM_NETWORK             Network,
    CLUSTER_NETWORK_STATE   NewNetworkState
    )
/*++

Notes:

    Called with NmpLock held and the Network referenced.

--*/
{
    DWORD            status;
    DWORD            i;
    LPCWSTR          networkId = OmObjectId(Network);
    DWORD            entryCount = Network->ConnectivityVector->EntryCount;
    DWORD            vectorSize = sizeof(NM_STATE_ENTRY) * entryCount;
    PNM_STATE_ENTRY  ifStateVector;


    ifStateVector = LocalAlloc(LMEM_FIXED, vectorSize);

    if (ifStateVector != NULL ) {

        for (i=0; i< entryCount; i++) {
            ifStateVector[i] = Network->StateWorkVector[i].State;
        }

        if (NmpState == NmStateOnline) {
            //
            // Issue a global state update for this network.
            //
            NmpReleaseLock();

            status = GumSendUpdateEx(
                         GumUpdateMembership,
                         NmUpdateSetNetworkAndInterfaceStates,
                         4,
                         NM_WCSLEN(networkId),
                         networkId,
                         sizeof(NewNetworkState),
                         &NewNetworkState,
                         vectorSize,
                         ifStateVector,
                         sizeof(entryCount),
                         &entryCount
                         );

            NmpAcquireLock();
        }
        else {
            CL_ASSERT(NmpState == NmStateOnlinePending);
            //
            // We're still in the form process. Bypass GUM.
            //
            NmpSetNetworkAndInterfaceStates(
                Network,
                NewNetworkState,
                ifStateVector,
                entryCount
                );

            status = ERROR_SUCCESS;
        }

        LocalFree(ifStateVector);
    }
    else {
        status = ERROR_NOT_ENOUGH_MEMORY;
    }

    return(status);

} // NmpGlobalSetNetworkAndInterfaceStates


/////////////////////////////////////////////////////////////////////////////
//
// Routines called by other cluster service components
//
/////////////////////////////////////////////////////////////////////////////
CLUSTER_NETWORK_STATE
NmGetNetworkState(
    IN  PNM_NETWORK  Network
    )
/*++

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_NETWORK_STATE  state;


    NmpAcquireLock();

    state = Network->State;

    NmpReleaseLock();

    return(state);

} // NmGetNetworkState


DWORD
NmSetNetworkName(
    IN PNM_NETWORK   Network,
    IN LPCWSTR       Name
    )
/*++

Routine Description:

    Changes the name of a network defined for the cluster.

Arguments:

    Network - A pointer to the object for the network to be modified.

Return Value:

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

Notes:

    The network object must be referenced by the caller.

--*/

{
    DWORD  status = ERROR_SUCCESS;


    if (NmpEnterApi(NmStateOnline)) {
        LPCWSTR   networkId = OmObjectId(Network);
        DWORD     nameLength;


        //
        // Validate the name
        //
        try {
            nameLength = lstrlenW(Name);

            if (nameLength == 0) {
                status = ERROR_INVALID_PARAMETER;
            }
        }
        except (EXCEPTION_EXECUTE_HANDLER) {
            status = ERROR_INVALID_PARAMETER;
        }

        if (status == ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Processing request to set name for network %1!ws! "
                "to %2!ws!.\n",
                networkId,
                Name
                );

            //
            // Issue a global update
            //
            status = GumSendUpdateEx(
                         GumUpdateMembership,
                         NmUpdateSetNetworkName,
                         2,
                         NM_WCSLEN(networkId),
                         networkId,
                         (nameLength + 1) * sizeof(WCHAR),
                         Name
                         );

            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Global update to set name of network %1!ws! "
                    "failed, status %2!u!.\n",
                    networkId,
                    status
                    );
            }
        }
        else {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] New name parameter supplied for network %1!ws! "
                "is invalid\n",
                networkId
                );
        }

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

    return(status);

}  // NmSetNetworkName


DWORD
NmSetNetworkPriorityOrder(
    IN DWORD     NetworkCount,
    IN LPWSTR *  NetworkIdList
    )
/*++

Routine Description:

    Sets the priority ordering of internal networks.

Arguments:

    NetworkCount - Contains the count of items in NetworkIdList.

    NetworkIdList - A pointer to an array of pointers to unicode strings.
                    Each string contains the ID of one internal network.
                    The array is sorted in priority order. The highest
                    priority network is listed first in the array.

Return Value:

    ERROR_SUCCESS if the routine is successful.

    A Win32 error code othewise.

--*/
{
    DWORD  status = ERROR_SUCCESS;


    if (NetworkCount == 0) {
        return(ERROR_INVALID_PARAMETER);
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received request to set network priority order.\n"
        );

    if (NmpEnterApi(NmStateOnline)) {
        DWORD     i;
        DWORD     multiSzLength = 0;
        PVOID     multiSz = NULL;

        //
        // Marshall the network ID list into a MULTI_SZ.
        //
        for (i=0; i< NetworkCount; i++) {
            multiSzLength += NM_WCSLEN(NetworkIdList[i]);
        }

        multiSzLength += sizeof(UNICODE_NULL);

        multiSz = MIDL_user_allocate(multiSzLength);

        if (multiSz != NULL) {
            LPWSTR  tmp = multiSz;

            for (i=0; i< NetworkCount; i++) {
                lstrcpyW(tmp, NetworkIdList[i]);
                tmp += lstrlenW(NetworkIdList[i]) + 1;
            }

            *tmp = UNICODE_NULL;

            //
            // Issue a global update
            //
            status = GumSendUpdateEx(
                         GumUpdateMembership,
                         NmUpdateSetNetworkPriorityOrder,
                         1,
                         multiSzLength,
                         multiSz
                         );

            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Global update to reprioritize networks failed, "
                    "status %1!u!.\n",
                    status
                    );
            }

            MIDL_user_free(multiSz);
        }
        else {
            status = ERROR_NOT_ENOUGH_MEMORY;
        }

        NmpLeaveApi();
    }
    else {
        status = ERROR_NODE_NOT_AVAILABLE;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process a request to set the "
            "network priority order.\n"
            );
    }

    return(status);

}  // NmSetNetworkPriorityOrder


DWORD
NmEnumInternalNetworks(
    OUT LPDWORD         NetworkCount,
    OUT PNM_NETWORK *   NetworkList[]
    )
/*++

Routine Description:

    Returns a prioritized list of networks that are eligible to
    carry internal communication.

Arguments:

    NetworkCount - On output, contains the number of items in NetworkList.

    NetworkList - On output, points to an array of pointers to network
                  objects. The highest priority network is first in the
                  array. Each pointer in the array must be dereferenced
                  by the caller. The storage for the array must be
                  deallocated by the caller.

Return Value:

    ERROR_SUCCESS if the routine is successful.

    A Win32 error code othewise.

--*/
{
    DWORD  status;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {

        status = NmpEnumInternalNetworks(NetworkCount, NetworkList);

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

    NmpReleaseLock();

    return(status);

}  // NmEnumInternalNetworks


DWORD
NmpEnumInternalNetworks(
    OUT LPDWORD         NetworkCount,
    OUT PNM_NETWORK *   NetworkList[]
    )
/*++

Routine Description:

    Returns a prioritized list of networks that are eligible to
    carry internal communication.

Arguments:

    NetworkCount - On output, contains the number of items in NetworkList.

    NetworkList - On output, points to an array of pointers to network
                  objects. The highest priority network is first in the
                  array. Each pointer in the array must be dereferenced
                  by the caller. The storage for the array must be
                  deallocated by the caller.

Return Value:

    ERROR_SUCCESS if the routine is successful.

    A Win32 error code othewise.

Notes:

    Called with NM Lock held.

--*/
{
    DWORD  status = ERROR_SUCCESS;


    if (NmpInternalNetworkCount > 0) {
        PNM_NETWORK *  networkList = LocalAlloc(
                                         LMEM_FIXED,
                                         ( sizeof(PNM_NETWORK) *
                                           NmpInternalNetworkCount)
                                         );

        if (networkList != NULL) {
            PNM_NETWORK   network;
            PLIST_ENTRY   entry;
            DWORD         networkCount = 0;

            //
            // The internal network list is sorted in priority order.
            // The highest priority network is at the head of the list.
            //
            for (entry = NmpInternalNetworkList.Flink;
                 entry != &NmpInternalNetworkList;
                 entry = entry->Flink
                )
            {
                network = CONTAINING_RECORD(
                              entry,
                              NM_NETWORK,
                              InternalLinkage
                              );

                CL_ASSERT(NmpIsNetworkForInternalUse(network));
                OmReferenceObject(network);
                networkList[networkCount++] = network;
            }

            CL_ASSERT(networkCount == NmpInternalNetworkCount);
            *NetworkCount = networkCount;
            *NetworkList = networkList;
        }
        else {
            status = ERROR_NOT_ENOUGH_MEMORY;
        }
    }
    else {
        *NetworkCount = 0;
        *NetworkList = NULL;
    }

    return(status);

}  // NmpEnumInternalNetworks


DWORD
NmEnumNetworkInterfaces(
    IN  PNM_NETWORK       Network,
    OUT LPDWORD           InterfaceCount,
    OUT PNM_INTERFACE *   InterfaceList[]
    )
/*++

Routine Description:

    Returns the list of interfaces associated with a specified network.

Arguments:

    Network - A pointer to the network object for which to enumerate
              interfaces.

    InterfaceCount - On output, contains the number of items in InterfaceList.

    InterfaceList - On output, points to an array of pointers to interface
                    objects. Each pointer in the array must be dereferenced
                    by the caller. The storage for the array must be
                    deallocated by the caller.

Return Value:

    ERROR_SUCCESS if the routine is successful.

    A Win32 error code othewise.

--*/
{
    DWORD  status = ERROR_SUCCESS;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        if (Network->InterfaceCount > 0) {
            PNM_INTERFACE *  interfaceList = LocalAlloc(
                                                 LMEM_FIXED,
                                                 ( sizeof(PNM_INTERFACE) *
                                                   Network->InterfaceCount)
                                                 );

            if (interfaceList != NULL) {
                PNM_INTERFACE  netInterface;
                PLIST_ENTRY    entry;
                DWORD          i;

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

                    OmReferenceObject(netInterface);
                    interfaceList[i] = netInterface;
                }

                *InterfaceCount = Network->InterfaceCount;
                *InterfaceList = interfaceList;
            }
            else {
                status = ERROR_NOT_ENOUGH_MEMORY;
            }
        }
        else {
            *InterfaceCount = 0;
            *InterfaceList = NULL;
        }

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

    NmpReleaseLock();

    return(status);

} // NmEnumNetworkInterfaces


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

Routine Description:

    Global update handler for creating a new network. The network
    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 NM lock held.

--*/
{
    DWORD                  status;
    NM_NETWORK_INFO        networkInfo;
    NM_INTERFACE_INFO2     interfaceInfo;
    PNM_NETWORK            network = NULL;
    PNM_INTERFACE          netInterface = NULL;
    HLOCALXSACTION         xaction = NULL;
    BOOLEAN                isInternalNetwork = FALSE;
    BOOLEAN                isLockAcquired = FALSE;
    CL_NODE_ID             joinerNodeId;


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

    //
    // Unmarshall the property lists.
    //
    ZeroMemory(&networkInfo, sizeof(networkInfo));
    ZeroMemory(&interfaceInfo, sizeof(interfaceInfo));

    status = ClRtlVerifyPropertyTable(
                 NmpNetworkProperties,
                 NULL,    // Reserved
                 FALSE,   // Don't allow unknowns
                 NetworkPropertyList,
                 *NetworkPropertyListSize,
                 (LPBYTE) &networkInfo
                 );

    if ( status != ERROR_SUCCESS ) {
        ClRtlLogPrint( LOG_CRITICAL, 
            "[NM] Failed to unmarshall properties for new network, "
            "status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    status = ClRtlVerifyPropertyTable(
                 NmpInterfaceProperties,
                 NULL,    // Reserved
                 FALSE,   // Don't allow unknowns
                 InterfacePropertyList,
                 *InterfacePropertyListSize,
                 (LPBYTE) &interfaceInfo
                 );

    if ( status != ERROR_SUCCESS ) {
        ClRtlLogPrint( LOG_CRITICAL, 
            "[NM] Failed to unmarshall properties for new interface, "
            "status %1!u!.\n",
            status
            );
        goto error_exit;
    }


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

    //
    // 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 begin a transaction, status %1!u!\n",
            status
            );
        goto error_exit;
    }

    NmpAcquireLock(); isLockAcquired = TRUE;

    //
    // Fix up the network's priority, if needed.
    //
    if (networkInfo.Role & ClusterNetworkRoleInternalUse) {
        CL_ASSERT(networkInfo.Priority == 0xFFFFFFFF);

        //
        // The network's priority is one greater than that of the lowest
        // priority network already in the internal network list.
        //
        if (IsListEmpty(&NmpInternalNetworkList)) {
            networkInfo.Priority = 1;
        }
        else {
            PNM_NETWORK network = CONTAINING_RECORD(
                                      NmpInternalNetworkList.Blink,
                                      NM_NETWORK,
                                      InternalLinkage
                                      );

            CL_ASSERT(network->Priority != 0);
            CL_ASSERT(network->Priority != 0xFFFFFFFF);

            networkInfo.Priority = network->Priority + 1;
        }

        isInternalNetwork = TRUE;
    }

    //
    // Update the database.
    //
    status = NmpCreateNetworkDefinition(&networkInfo, xaction);

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

    status = NmpCreateInterfaceDefinition(&interfaceInfo, xaction);

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

    joinerNodeId = NmpJoinerNodeId;

    NmpReleaseLock(); isLockAcquired = FALSE;

    network = NmpCreateNetworkObject(&networkInfo);

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

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

#ifdef CLUSTER_TESTPOINT
    TESTPT(TpFailNmCreateNetwork) {
        NmpAcquireLock();
        NmpDeleteInterfaceObject(netInterface, FALSE); netInterface = NULL;
        NmpReleaseLock();
        SetLastError(999999);
    }
#endif

    NmpAcquireLock(); isLockAcquired = TRUE;

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

        NmpDeleteNetworkObject(network, FALSE);
        OmDereferenceObject(network);

        goto error_exit;
    }

    //
    // 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_NETWORK_ADDED, network);
    ClusterEvent(CLUSTER_EVENT_NETINTERFACE_ADDED, netInterface);

    if (isInternalNetwork) {
        NmpIssueClusterPropertyChangeEvent();
    }

    OmDereferenceObject(netInterface);
    OmDereferenceObject(network);

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

    if (isLockAcquired) {
        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);
        }
    }

    ClNetFreeNetworkInfo(&networkInfo);
    ClNetFreeInterfaceInfo(&interfaceInfo);

    return(status);

} // NmpUpdateCreateNetwork


DWORD
NmpUpdateSetNetworkName(
    IN BOOL     IsSourceNode,
    IN LPWSTR   NetworkId,
    IN LPWSTR   NewNetworkName
    )
/*++

Routine Description:

    Global update handler for setting the name of a network.

Arguments:

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

    NetworkId - A pointer to a unicode string containing the ID of the
                  network to update.

    NewNetworkName - A pointer to a unicode string containing the new network name.

Return Value:

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

Notes:

    This routine must not be called with NM lock held.

--*/
{
    DWORD             status;
    DWORD             i;
    PLIST_ENTRY       entry;
    HLOCALXSACTION    xaction = NULL;
    HDMKEY            networkKey;
    HDMKEY            netInterfaceKey;
    PNM_NETWORK       network = NULL;
    PNM_INTERFACE     netInterface;
    LPCWSTR           netInterfaceId;
    LPCWSTR           netInterfaceName;
    LPCWSTR           networkName;
    LPCWSTR           nodeName;
    LPWSTR            oldNetworkName = NULL;
    LPWSTR *          oldNameVector = NULL;
    LPWSTR *          newNameVector = NULL;
    BOOLEAN           isLockAcquired = FALSE;
    DWORD             interfaceCount;


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

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received update to set the name for network %1!ws! "
        "to '%2!ws!'.\n",
        NetworkId,
        NewNetworkName
        );

    //
    // Find the network's object
    //
    network = OmReferenceObjectById(ObjectTypeNetwork, NetworkId);

    if (network == NULL) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Unable to find network %1!ws!.\n",
            NetworkId
            );
        status = ERROR_CLUSTER_NETWORK_NOT_FOUND;
        goto error_exit;
    }

    //
    // 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 begin a transaction, status %1!u!\n",
            status
            );
        goto error_exit;
    }

    NmpAcquireLock(); isLockAcquired = TRUE;

    //
    // compare the names. If the same, return success
    //
    if ( _wcsicmp( OmObjectName( network ), NewNetworkName ) == 0 ) {
        ClRtlLogPrint(LOG_NOISE, "[NM] Network name does not need changing.\n");

        status = ERROR_SUCCESS;
        goto error_exit;
    }

    networkName = OmObjectName(network);

    oldNetworkName = LocalAlloc(LMEM_FIXED, NM_WCSLEN(networkName));

    if (oldNetworkName == NULL) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory for network %1!ws! name change!\n",
            NetworkId
            );
        status = ERROR_NOT_ENOUGH_MEMORY;
        goto error_exit;
    }

    wcscpy(oldNetworkName, networkName);

    //
    // Update the database.
    //
    // This processing can always be undone on error.
    //
    networkKey = DmOpenKey(DmNetworksKey, NetworkId, KEY_WRITE);

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

    status = DmLocalSetValue(
                 xaction,
                 networkKey,
                 CLUSREG_NAME_NET_NAME,
                 REG_SZ,
                 (CONST BYTE *) NewNetworkName,
                 NM_WCSLEN(NewNetworkName)
                 );

    DmCloseKey(networkKey);

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

    //
    // Update the names of all of the interfaces on this network
    //
    interfaceCount = network->InterfaceCount;

    oldNameVector = LocalAlloc(
                        LMEM_FIXED | LMEM_ZEROINIT,
                        interfaceCount * sizeof(LPWSTR)
                        );

    newNameVector = LocalAlloc(
                        LMEM_FIXED | LMEM_ZEROINIT,
                        interfaceCount * sizeof(LPWSTR)
                        );

    if ((oldNameVector == NULL) || (newNameVector == NULL)) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory for net interface name change.\n"
            );
        goto error_exit;
    }

    for (entry = network->InterfaceList.Flink, i = 0;
         entry != &(network->InterfaceList);
         entry = entry->Flink, i++
        )
    {
        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, NetworkLinkage);
        netInterfaceId = OmObjectId(netInterface);
        netInterfaceName = OmObjectName(netInterface);
        nodeName = OmObjectName(netInterface->Node);

        oldNameVector[i] = LocalAlloc(
                               LMEM_FIXED,
                               NM_WCSLEN(netInterfaceName)
                               );

        newNameVector[i] = ClNetMakeInterfaceName(
                               NULL,
                               (LPWSTR) nodeName,
                               NewNetworkName
                               );

        if ((oldNameVector[i] == NULL) || (newNameVector[i] == NULL)) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to allocate memory for net interface name "
                "change.\n"
                );
            goto error_exit;
        }

        wcscpy(oldNameVector[i], netInterfaceName);

        netInterfaceKey = DmOpenKey(
                              DmNetInterfacesKey,
                              netInterfaceId,
                              KEY_WRITE
                              );

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

        status = DmLocalSetValue(
                     xaction,
                     netInterfaceKey,
                     CLUSREG_NAME_NETIFACE_NAME,
                     REG_SZ,
                     (CONST BYTE *) newNameVector[i],
                     NM_WCSLEN(newNameVector[i])
                     );

        DmCloseKey(netInterfaceKey);

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

    //
    // Update the in-memory objects.
    //
    // This processing may not be undoable on error.
    //

    //
    // Update name of the network
    //
    status = OmSetObjectName(network, NewNetworkName);

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

    //
    // Update the names of all of the interfaces on the network.
    //
    for (entry = network->InterfaceList.Flink, i = 0;
         entry != &(network->InterfaceList);
         entry = entry->Flink, i++
        )
    {
        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, NetworkLinkage);
        netInterfaceId = OmObjectId(netInterface);

        status = OmSetObjectName(netInterface, newNameVector[i]);

        if (status != ERROR_SUCCESS) {
            //
            // Try to undo what has already been done. If we fail, we must
            // commit suicide to preserve consistency.
            //
            DWORD        j;
            PLIST_ENTRY  entry2;
            DWORD        undoStatus;

            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to change name for net interface %1!ws!, "
                "status %2!u!\n",
                netInterfaceId,
                status
                );

            //
            // Undo the update of the network name
            //
            undoStatus = OmSetObjectName(network, oldNetworkName);

            if (undoStatus != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Failed to undo change of name for network %1!ws!, "
                    "status %2!u!\n",
                    NetworkId,
                    undoStatus
                    );
                CsInconsistencyHalt(undoStatus);
            }

            //
            // Undo update of network interface names
            //
            for (j = 0, entry2 = network->InterfaceList.Flink;
                 j < i;
                 j++, entry2 = entry2->Flink
                )
            {
                netInterface = CONTAINING_RECORD(
                                   entry2,
                                   NM_INTERFACE,
                                   NetworkLinkage
                                   );

                netInterfaceId = OmObjectId(netInterface);

                undoStatus = OmSetObjectName(netInterface, oldNameVector[i]);

                if (undoStatus != ERROR_SUCCESS) {
                    ClRtlLogPrint(LOG_CRITICAL, 
                        "[NM] Failed to undo change of name for net "
                        "interface %1!ws!, status %2!u!\n",
                        netInterfaceId,
                        undoStatus
                        );
                    CsInconsistencyHalt(undoStatus);
                }
            }

            goto error_exit;
        }
    }

    //
    // Set the corresponding connectoid object name if necessary.
    //
    if (network->LocalInterface != NULL) {
        INetConnection *  connectoid;
        LPWSTR            connectoidName;
        DWORD             tempStatus;

        connectoid = ClRtlFindConnectoidByGuid(
                         network->LocalInterface->AdapterId
                         );

        if (connectoid != NULL) {
            connectoidName = ClRtlGetConnectoidName(connectoid);

            if (connectoidName != NULL) {
                if (lstrcmpW(connectoidName, NewNetworkName) != 0) {
                    tempStatus = ClRtlSetConnectoidName(
                                     connectoid,
                                     NewNetworkName
                                     );

                    if (tempStatus != ERROR_SUCCESS) {
                        ClRtlLogPrint(LOG_UNUSUAL, 
                            "[NM] Failed to set name of Network Connection "
                            "Object for interface on cluster network %1!ws! "
                            "(%2!ws!), status %3!u!\n",
                            oldNetworkName,
                            NetworkId,
                            tempStatus
                            );
                    }
                }
            }
            else {
                tempStatus = GetLastError();
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] Failed to query name of Network Connection Object "
                    "for interface on cluster network %1!ws! (%2!ws!), "
                    "status %3!u!\n",
                    oldNetworkName,
                    NetworkId,
                    tempStatus
                    );
            }

            INetConnection_Release( connectoid );
        }
        else {
            tempStatus = GetLastError();
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Failed to find Network Connection Object for "
                "interface on cluster network %1!ws! (%2!ws!), "
                "status %3!u!\n",
                oldNetworkName,
                NetworkId,
                tempStatus
                );
        }
    }

    //
    // Issue property change events.
    //
    ClusterEvent(CLUSTER_EVENT_NETWORK_PROPERTY_CHANGE, network);

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

        ClusterEvent(
            CLUSTER_EVENT_NETINTERFACE_PROPERTY_CHANGE,
            netInterface
            );
    }

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

    if (isLockAcquired) {
        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 (network != NULL) {
        OmDereferenceObject(network);

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

        if (oldNameVector != NULL) {
            for (i=0; i < interfaceCount; i++) {
                if (oldNameVector[i] == NULL) {
                    break;
                }

                LocalFree(oldNameVector[i]);
            }

            LocalFree(oldNameVector);
        }

        if (newNameVector != NULL) {
            for (i=0; i < interfaceCount; i++) {
                if (newNameVector[i] == NULL) {
                    break;
                }

                LocalFree(newNameVector[i]);
            }

            LocalFree(newNameVector);
        }
    }

    return(status);

} // NmpUpdateSetNetworkName


DWORD
NmpUpdateSetNetworkPriorityOrder(
    IN BOOL      IsSourceNode,
    IN LPCWSTR   NetworkIdList
    )
{
    DWORD             status = ERROR_SUCCESS;
    PNM_NETWORK       network;
    PLIST_ENTRY       entry;
    DWORD             matchCount=0;
    DWORD             networkCount=0;
    PNM_NETWORK *     networkList=NULL;
    DWORD             i;
    DWORD             multiSzLength;
    LPCWSTR           networkId;
    HLOCALXSACTION    xaction = NULL;
    BOOLEAN           isLockAcquired = FALSE;


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

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Received update to set network priority order.\n"
        );

    //
    // Unmarshall the MULTI_SZ
    //
    try {
        multiSzLength = ClRtlMultiSzLength(NetworkIdList);

        for (i=0; ; i++) {
            networkId = ClRtlMultiSzEnum(
                            NetworkIdList,
                            multiSzLength,
                            i
                            );

            if (networkId == NULL) {
                break;
            }

            networkCount++;
        }
    }
    except(EXCEPTION_EXECUTE_HANDLER) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Hit exception while parsing network ID list for "
            "priority change\n"
            );
        status = ERROR_INVALID_PARAMETER;
        goto error_exit;
    }

    if (networkCount == 0) {
        status = ERROR_INVALID_PARAMETER;
        goto error_exit;
    }

    networkList = LocalAlloc(
                      LMEM_ZEROINIT| LMEM_FIXED,
                      networkCount * sizeof(PNM_NETWORK)
                      );

    if (networkList == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        goto error_exit;
    }

    //
    // 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
            );
        goto error_exit;
    }

    NmpAcquireLock(); isLockAcquired = TRUE;

    if (NmpJoinerNodeId != ClusterInvalidNodeId) {
        status = ERROR_CLUSTER_JOIN_IN_PROGRESS;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Cannot set network priority order because a node is "
            "joining the cluster.\n"
            );
        goto error_exit;
    }

    for (i=0; i<networkCount; i++) {
        networkId = ClRtlMultiSzEnum(
                        NetworkIdList,
                        multiSzLength,
                        i
                        );

        CL_ASSERT(networkId != NULL);

        networkList[i] = OmReferenceObjectById(
                             ObjectTypeNetwork,
                             networkId
                             );

        if (networkList[i] == NULL) {
            goto error_exit;
        }
    }

    //
    // Verify that all of the networks specified are internal, and
    // that all of the internal networks are specified.
    //
    if (networkCount != NmpInternalNetworkCount) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Supplied network count %1!u! doesn't match internal "
            "network count %2!u!\n",
            networkCount,
            NmpInternalNetworkCount
            );
        status = ERROR_INVALID_PARAMETER;
        goto error_exit;
    }

    for (entry = NmpInternalNetworkList.Flink, matchCount = 0;
         entry != &NmpInternalNetworkList;
         entry = entry->Flink
        )
    {
        network = CONTAINING_RECORD(entry, NM_NETWORK, InternalLinkage);

        if (NmpIsNetworkForInternalUse(network)) {
            for (i=0; i<networkCount; i++) {
                if (network == networkList[i]) {
                    matchCount++;
                    break;
                }
            }

            if (i == networkCount) {
                //
                // This network is not in the list.
                //
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Internal use network %1!ws! is not in priority "
                    "list\n",
                    (LPWSTR) OmObjectId(network)
                    );
                status = ERROR_INVALID_PARAMETER;
                goto error_exit;
            }
        }
    }

    if (matchCount != networkCount) {
        //
        // Some of the specified networks are not internal use.
        //
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Some non-internal use networks are in priority list\n"
            );
        status = ERROR_INVALID_PARAMETER;
        goto error_exit;
    }

    //
    // The list is kosher. Set the priorities.
    //
    status = NmpSetNetworkPriorityOrder(networkCount, networkList, xaction);

error_exit:

    if (isLockAcquired) {
        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 (networkList != NULL) {
        for (i=0; i<networkCount; i++) {
            if (networkList[i] != NULL) {
                OmDereferenceObject(networkList[i]);
            }
        }

        LocalFree(networkList);
    }

    return(status);

}  // NmpUpdateSetNetworkPriorityOrder


DWORD
NmpSetNetworkPriorityOrder(
    IN DWORD           NetworkCount,
    IN PNM_NETWORK *   NetworkList,
    IN HLOCALXSACTION  Xaction
    )
/*++

Notes:

    Called with NM Lock held.

--*/
{
    DWORD             status = ERROR_SUCCESS;
    PNM_NETWORK       network;
    DWORD             i;
    LPCWSTR           networkId;
    HDMKEY            networkKey;
    DWORD             priority;


    //
    // Update the database first.
    //
    for (i=0, priority = 1; i<NetworkCount; i++, priority++) {
        network = NetworkList[i];
        networkId = OmObjectId(network);

        CL_ASSERT(NmpIsNetworkForInternalUse(network));

        if (network->Priority != priority) {
            networkKey = DmOpenKey(DmNetworksKey, networkId, KEY_WRITE);

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

            status = DmLocalSetValue(
                         Xaction,
                         networkKey,
                         CLUSREG_NAME_NET_PRIORITY,
                         REG_DWORD,
                         (CONST BYTE *) &priority,
                         sizeof(priority)
                         );

            DmCloseKey(networkKey);

            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Set of priority value failed for network %1!ws!, "
                    "status %2!u!.\n",
                    networkId,
                    status
                    );
                return(status);
            }
        }
    }

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

    //
    // Update the in-memory representation
    //
    InitializeListHead(&NmpInternalNetworkList);

    for (i=0, priority = 1; i<NetworkCount; i++, priority++) {
        network = NetworkList[i];
        networkId = OmObjectId(network);

        InsertTailList(
            &NmpInternalNetworkList,
            &(network->InternalLinkage)
            );

        if (network->Priority != priority) {
            ClRtlLogPrint(LOG_NOISE,
                "[NM] Set priority for network %1!ws! to %2!u!.\n",
                networkId,
                priority
                );

            network->Priority = priority;

            //
            // If the local node is attached to this network, set its
            // priority in the cluster transport
            //
            if (NmpIsNetworkRegistered(network)) {
                status = ClusnetSetNetworkPriority(
                             NmClusnetHandle,
                             network->ShortId,
                             network->Priority
                             );

#ifdef CLUSTER_TESTPOINT
                TESTPT(TpFailNmSetNetworkPriorityOrder2) {
                    status = 999999;
                }
#endif
                if (status != ERROR_SUCCESS) {
                    //
                    // We can't easily abort here. We must either continue
                    // or commit suicide. We choose to continue and log an
                    // event.
                    //
                    WCHAR  string[16];

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

                    CsLogEvent2(
                        LOG_UNUSUAL,
                        NM_EVENT_SET_NETWORK_PRIORITY_FAILED,
                        OmObjectName(network),
                        string
                        );

                    ClRtlLogPrint(LOG_CRITICAL, 
                        "[NM] Cluster transport failed to set priority for "
                        "network %1!ws!, status %2!u!\n",
                        networkId,
                        status
                        );

                    status = ERROR_SUCCESS;
                }
            }
        }
    }

    CL_ASSERT(status == ERROR_SUCCESS);

    //
    // Issue a cluster property change event.
    //
    NmpIssueClusterPropertyChangeEvent();

    return(ERROR_SUCCESS);

} // NmpSetNetworkPriorityOrder


DWORD
NmpUpdateSetNetworkCommonProperties(
    IN BOOL     IsSourceNode,
    IN LPWSTR   NetworkId,
    IN UCHAR *  PropertyList,
    IN LPDWORD  PropertyListLength
    )
/*++

Routine Description:

    Global update handler for setting the common properties of a network.

Arguments:

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

    NetworkId - A pointer to a unicode string containing the ID of the
                  network to update.

Return Value:

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

--*/
{
    DWORD                    status = ERROR_SUCCESS;
    NM_NETWORK_INFO          networkInfo;
    PNM_NETWORK              network = NULL;
    HLOCALXSACTION           xaction = NULL;
    HDMKEY                   networkKey = NULL;
    DWORD                    descSize = 0;
    LPWSTR                   descBuffer = NULL;
    BOOLEAN                  propertyChanged = FALSE;
    BOOLEAN                  isLockAcquired = FALSE;


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

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

    ZeroMemory(&networkInfo, sizeof(NM_NETWORK_INFO));

    //
    // Find the network's object
    //
    network = OmReferenceObjectById(ObjectTypeNetwork, NetworkId);

    if (network == NULL) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Unable to find network %1!ws!.\n",
            NetworkId
            );
        status = ERROR_CLUSTER_NETWORK_NOT_FOUND;
        goto error_exit;
    }

    //
    // Open the network's database key
    //
    networkKey = DmOpenKey(DmNetworksKey, NetworkId, KEY_WRITE);

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

    //
    // 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 begin a transaction, status %1!u!\n",
            status
            );
        goto error_exit;
    }

    NmpAcquireLock(); isLockAcquired = TRUE;

    if (NmpJoinerNodeId != ClusterInvalidNodeId) {
        status = ERROR_CLUSTER_JOIN_IN_PROGRESS;
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Cannot set network common properties because a node is "
            "joining the cluster.\n"
            );
        goto error_exit;
    }

    //
    // Validate the property list and convert it to a network params struct.
    //
    status = NmpNetworkValidateCommonProperties(
                 network,
                 PropertyList,
                 *PropertyListLength,
                 &networkInfo
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Property list validation failed, status %1!u!.\n",
            status
            );
        goto error_exit;
    }

    CL_ASSERT(network->Priority == networkInfo.Priority);
    CL_ASSERT(wcscmp(NetworkId, networkInfo.Id) == 0);
    CL_ASSERT(wcscmp(OmObjectName(network), networkInfo.Name) == 0);
    CL_ASSERT(wcscmp(network->Transport, networkInfo.Transport) == 0);
    CL_ASSERT(wcscmp(network->Address, networkInfo.Address) == 0);
    CL_ASSERT(wcscmp(network->AddressMask, networkInfo.AddressMask) == 0);

    //
    // Check if the network's description has changed.
    //
    if (wcscmp(network->Description, networkInfo.Description) != 0) {
        ClRtlLogPrint(LOG_NOISE,
            "[NM] Setting description for network %1!ws! to %2!ws!.\n",
            NetworkId,
            networkInfo.Description
            );

        //
        // Allocate a buffer for the description string
        //
        descSize = NM_WCSLEN(networkInfo.Description);

        descBuffer = MIDL_user_allocate(descSize);

        if (descBuffer == NULL) {
            status = ERROR_NOT_ENOUGH_MEMORY;
            goto error_exit;
        }

        RtlMoveMemory(descBuffer, networkInfo.Description, descSize);

        //
        // Update the database
        //
        status = DmLocalSetValue(
                     xaction,
                     networkKey,
                     CLUSREG_NAME_NET_DESC,
                     REG_SZ,
                     (CONST BYTE *) networkInfo.Description,
                     descSize
                     );

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

        //
        // Updating the network object is deferred until the transaction
        // commits.
        //

        propertyChanged = TRUE;
    }

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

    //
    // Check if the network's role has changed.
    //
    //
    // NOTE: This operation is not guaranteed to be reversible, so it must
    //       be the last thing we do in this routine. If it succeeds, the
    //       update must be committed.
    //
    if ( network->Role != ((CLUSTER_NETWORK_ROLE) networkInfo.Role) ) {
        status = NmpSetNetworkRole(
                     network,
                     networkInfo.Role,
                     xaction,
                     networkKey
                     );

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

        propertyChanged = TRUE;
    }

    if (propertyChanged) {
        //
        // Commit the updates to the network object in memory
        //
        if (descBuffer != NULL) {
            MIDL_user_free(network->Description);
            network->Description = descBuffer;
            descBuffer = NULL;
        }

        //
        // Issue a network property change event.
        //
        if (propertyChanged && (status == ERROR_SUCCESS)) {
            ClusterEvent(CLUSTER_EVENT_NETWORK_PROPERTY_CHANGE, network);
        }
    }

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

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

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

    ClNetFreeNetworkInfo(&networkInfo);

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

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

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

    return(status);

} // NmpUpdateSetNetworkCommonProperties


DWORD
NmpUpdateSetNetworkAndInterfaceStates(
    IN BOOL                        IsSourceNode,
    IN LPWSTR                      NetworkId,
    IN CLUSTER_NETWORK_STATE *     NewNetworkState,
    IN PNM_STATE_ENTRY             InterfaceStateVector,
    IN LPDWORD                     InterfaceStateVectorSize
    )
{
    DWORD             status = ERROR_SUCCESS;
    PNM_NETWORK       network;


    NmpAcquireLock();

    if (NmpLockedEnterApi(NmStateOnline)) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Received update to set state for network %1!ws!.\n",
            NetworkId
            );

        //
        // Find the network's object
        //
        network = OmReferenceObjectById(ObjectTypeNetwork, NetworkId);

        if (network != NULL) {
            NmpSetNetworkAndInterfaceStates(
                network,
                *NewNetworkState,
                InterfaceStateVector,
                *InterfaceStateVectorSize
                );

            OmDereferenceObject(network);
        }
        else {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Unable to find network %1!ws!.\n",
                NetworkId
                );
            status = ERROR_CLUSTER_NETWORK_NOT_FOUND;
        }
    }
    else {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Not in valid state to process SetNetworkAndInterfaceStates "
            "update.\n"
            );
        status = ERROR_NODE_NOT_AVAILABLE;
    }

    NmpLockedLeaveApi();

    NmpReleaseLock();

    return(status);

} // NmpUpdateSetNetworkAndInterfaceStates


/////////////////////////////////////////////////////////////////////////////
//
// Helper routines for updates
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpSetNetworkRole(
    PNM_NETWORK            Network,
    CLUSTER_NETWORK_ROLE   NewRole,
    HLOCALXSACTION         Xaction,
    HDMKEY                 NetworkKey
    )
/*++

    Called with the NmpLock acquired.

    This operation is not guaranteed to be reversible, so this
    function is coded such that it either succeeds and makes the update
    or it fails and no update is made.

--*/
{
    DWORD                        status = ERROR_SUCCESS;
    CLUSTER_NETWORK_ROLE         oldRole = Network->Role;
    DWORD                        dwordNewRole = (DWORD) NewRole;
    LPCWSTR                      networkId = OmObjectId(Network);
    DWORD                        oldPriority = Network->Priority;
    DWORD                        newPriority = oldPriority;
    BOOLEAN                      internalNetworkListChanged = FALSE;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Changing role for network %1!ws! to %2!u!\n",
        networkId,
        NewRole
        );

    CL_ASSERT(NewRole != oldRole);
    CL_ASSERT(
        NmpValidateNetworkRoleChange(Network, NewRole) == ERROR_SUCCESS
        );

    //
    // First, make any registry updates since these can be
    // aborted by the caller.
    //

    //
    // Update the role property.
    //
    status = DmLocalSetValue(
                 Xaction,
                 NetworkKey,
                 CLUSREG_NAME_NET_ROLE,
                 REG_DWORD,
                 (CONST BYTE *) &dwordNewRole,
                 sizeof(DWORD)
                 );

    if (status != ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Set of role value failed for network %1!ws!, "
            "status %2!u!.\n",
            networkId,
            status
            );
        return(status);
    }

    //
    // Update the priority property, if needed.
    //
    if (oldRole & ClusterNetworkRoleInternalUse) {
        if (!(NewRole & ClusterNetworkRoleInternalUse)) {
            //
            // This network is no longer used for internal communication.
            // Invalidate its priority value.
            //
            newPriority = 0xFFFFFFFF;
            internalNetworkListChanged = TRUE;
        }
    }
    else if (NewRole & ClusterNetworkRoleInternalUse) {
        //
        // This network is now used for internal communication.
        // The network's priority is one greater than that of the lowest
        // (numerically highest) priority network already in the list.
        //
        if (IsListEmpty(&NmpInternalNetworkList)) {
            newPriority = 1;
        }
        else {
            PNM_NETWORK network = CONTAINING_RECORD(
                                      NmpInternalNetworkList.Blink,
                                      NM_NETWORK,
                                      InternalLinkage
                                      );

            CL_ASSERT(network->Priority != 0);
            CL_ASSERT(network->Priority != 0xFFFFFFFF);

            newPriority = network->Priority + 1;
        }

        internalNetworkListChanged = TRUE;
    }

    if (newPriority != oldPriority) {
        status = DmLocalSetValue(
                     Xaction,
                     NetworkKey,
                     CLUSREG_NAME_NET_PRIORITY,
                     REG_DWORD,
                     (CONST BYTE *) &newPriority,
                     sizeof(newPriority)
                     );

        if (status != ERROR_SUCCESS) {
            ClRtlLogPrint(LOG_CRITICAL, 
                "[NM] Failed to set priority value for network %1!ws!, "
                "status %2!u!.\n",
                networkId,
                status
                );
            return(status);
        }
    }

    //
    // Update the network object. Some of the subsequent subroutine calls
    // depend on this.
    //
    Network->Role = NewRole;
    Network->Priority = newPriority;

    //
    // Do other housekeeping based on the change.
    //
    // Note that the housekeeping work is coded such that none of it needs
    // to be reversed if an error occurs.
    //
    if (NewRole == ClusterNetworkRoleNone) {
        PLIST_ENTRY              entry;
        PNM_INTERFACE            netInterface;

        //
        // Case 1: This network is no longer used for clustering.
        //
        if (NmpIsNetworkRegistered(Network)) {
            //
            // Delete the network from the cluster transport.
            // This will delete all of its interfaces as well.
            //
            NmpDeregisterNetwork(Network);

            ClRtlLogPrint(LOG_NOISE, 
                "[NM] No longer hearbeating on network %1!ws!.\n",
                networkId
                );
        }

        //
        // Invalidate the connectivity data for all interfaces on
        // the network.
        //
        for ( entry = Network->InterfaceList.Flink;
              entry != &(Network->InterfaceList);
              entry = entry->Flink
            )
        {
            netInterface = CONTAINING_RECORD(
                               entry,
                               NM_INTERFACE,
                               NetworkLinkage
                               );

            NmpSetInterfaceConnectivityData(
                Network,
                netInterface->NetIndex,
                ClusterNetInterfaceUnavailable
                );
        }

        //
        // Clean up tracking data.
        //
        if (oldRole & ClusterNetworkRoleInternalUse) {
            RemoveEntryList(&(Network->InternalLinkage));
            CL_ASSERT(NmpInternalNetworkCount > 0);
            NmpInternalNetworkCount--;
        }

        if (oldRole & ClusterNetworkRoleClientAccess) {
            CL_ASSERT(NmpClientNetworkCount > 0);
            NmpClientNetworkCount--;
        }

        //
        // Use the NT5 state logic.
        //
        if (NmpLeaderNodeId == NmLocalNodeId) {
            //
            // Schedule an immediate state update.
            //
            NmpScheduleNetworkStateRecalc(Network);
        }
    }
    else if (oldRole == ClusterNetworkRoleNone) {
        //
        // Case 2: This network is now used for clustering.
        //
        if (Network->LocalInterface != NULL) {
            //
            // Register this network with the cluster transport.
            //
            // Note that this action will trigger a connectivity report,
            // which will in turn trigger a state update under the NT5 logic.
            //
            status = NmpRegisterNetwork(
                         Network,
                         TRUE  // Do retry on failure
                         );

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

            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Now heartbeating on network %1!ws!.\n",
                networkId
                );
        }
        else if (NmpLeaderNodeId == NmLocalNodeId) {
            //
            // Schedule a delayed state update in case none of the other
            // nodes attached to this network are up right now.
            //
            NmpStartNetworkStateRecalcTimer(
                Network,
                NM_NET_STATE_RECALC_TIMEOUT
                );
        }

        //
        // Update tracking data.
        //
        if (NewRole & ClusterNetworkRoleInternalUse) {
            InsertTailList(
                &NmpInternalNetworkList,
                &(Network->InternalLinkage)
                );
            NmpInternalNetworkCount++;
        }

        if (NewRole & ClusterNetworkRoleClientAccess) {
            NmpClientNetworkCount++;
        }
    }
    else {
        //
        // Case 3: We are using the network in a different way now.
        //         Note that the network is already registered with
        //         the cluster transport and will remain so. As a result,
        //         there is no need to perform a state update.
        //

        //
        //         First, examine the former and new use of the
        //         network for internal communication, and make any
        //         necessary updates to the cluster transport.
        //
        if (oldRole & ClusterNetworkRoleInternalUse) {
            if (!(NewRole & ClusterNetworkRoleInternalUse)) {
                //
                // This network is no longer used for internal communication.
                // It is used for client access.
                //
                CL_ASSERT(NewRole & ClusterNetworkRoleClientAccess);

                if (NmpIsNetworkRegistered(Network)) {
                    //
                    // Restrict the network to heartbeats only.
                    //
                    status = ClusnetSetNetworkRestriction(
                                 NmClusnetHandle,
                                 Network->ShortId,
                                 TRUE,  // Network is restricted
                                 0
                                 );

                    if (status != ERROR_SUCCESS) {
                        ClRtlLogPrint(LOG_CRITICAL, 
                            "[NM] Failed to restrict network %1!ws!, "
                            "status %2!u!.\n",
                            networkId,
                            status
                            );
                        goto error_exit;
                    }
                }

                //
                // Update tracking data
                //
                RemoveEntryList(&(Network->InternalLinkage));
                CL_ASSERT(NmpInternalNetworkCount > 0);
                NmpInternalNetworkCount--;
            }
        }
        else {
            //
            // The network is now used for internal communication.
            //
            CL_ASSERT(NewRole & ClusterNetworkRoleInternalUse);

            if (NmpIsNetworkRegistered(Network)) {
                //
                // Clear the restriction and set the priority.
                //
                status = ClusnetSetNetworkRestriction(
                             NmClusnetHandle,
                             Network->ShortId,
                             FALSE,      // Network is not restricted
                             newPriority
                             );

                if (status != ERROR_SUCCESS) {
                    ClRtlLogPrint(LOG_CRITICAL, 
                        "[NM] Failed to unrestrict network %1!ws!, "
                        "status %2!u!.\n",
                        networkId,
                        status
                        );
                    goto error_exit;
                }
            }

            //
            // Update the tracking data
            //
            InsertTailList(
                &NmpInternalNetworkList,
                &(Network->InternalLinkage)
                );
            NmpInternalNetworkCount++;
        }

        //
        // Now update the remaining tracking data based on former and
        // current use of the network for client access.
        //

        if (oldRole & ClusterNetworkRoleClientAccess) {
            if (!NewRole & ClusterNetworkRoleClientAccess) {
                //
                // This network is no longer used for client access.
                //
                CL_ASSERT(NmpClientNetworkCount > 0);
                NmpClientNetworkCount--;
            }
        }
        else {
            //
            // This network is now used for client access.
            //
            CL_ASSERT(NewRole & ClusterNetworkRoleClientAccess);
            NmpClientNetworkCount++;
        }
    }

    if (internalNetworkListChanged) {
        NmpIssueClusterPropertyChangeEvent();
    }

    return(ERROR_SUCCESS);

error_exit:

    //
    // Undo the updates to the network object.
    //
    Network->Role = oldRole;
    Network->Priority = oldPriority;

    return(status);

}   // NmpSetNetworkRole


VOID
NmpSetNetworkAndInterfaceStates(
    IN PNM_NETWORK                 Network,
    IN CLUSTER_NETWORK_STATE       NewNetworkState,
    IN PNM_STATE_ENTRY             InterfaceStateVector,
    IN DWORD                       VectorSize
    )
/*++

Notes:

    Called with NmpLock held.

    Because NM_STATE_ENTRY is an unsigned type, while
    CLUSTER_NETINTERFACE_STATE is a signed type, and
    ClusterNetInterfaceStateUnknown is defined to be -1, we cannot cast
    from NM_STATE_ENTRY to CLUSTER_NETINTERFACE_STATE and preserve the
    value of ClusterNetInterfaceStateUnknown.

--*/
{
    PLIST_ENTRY     entry;
    PNM_INTERFACE   netInterface;
    PNM_NODE        node;
    DWORD           logLevel, logCode;
    DWORD           ifNetIndex;
    LPCWSTR         netInterfaceId;
    LPCWSTR         nodeName;
    LPCWSTR         networkName = OmObjectName(Network);
    LPCWSTR         networkId = OmObjectId(Network);


    //
    // Examine each interface on this network to see if its state
    // has changed.
    //
    for ( entry = Network->InterfaceList.Flink;
          entry != &(Network->InterfaceList);
          entry = entry->Flink
        )
    {
        netInterface = CONTAINING_RECORD(
                           entry,
                           NM_INTERFACE,
                           NetworkLinkage
                           );

        ifNetIndex = netInterface->NetIndex;
        netInterfaceId = OmObjectId(netInterface);
        node = netInterface->Node;
        nodeName = OmObjectName(node);

        if ( (ifNetIndex < VectorSize) &&
             (InterfaceStateVector[ifNetIndex] !=
              (NM_STATE_ENTRY) netInterface->State
             )
           )
        {
            BOOLEAN          logEvent = FALSE;
            CLUSTER_EVENT    eventCode = 0;
            NM_STATE_ENTRY   newState = InterfaceStateVector[ifNetIndex];


            if (newState == (NM_STATE_ENTRY) ClusterNetInterfaceUnavailable) {
                //
                // Either the node has gone down or the network has been
                // disabled.
                //
                netInterface->State = ClusterNetInterfaceUnavailable;
                eventCode = CLUSTER_EVENT_NETINTERFACE_UNAVAILABLE;

                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] Interface %1!ws! is unavailable (node: %2!ws!, "
                    "network: %3!ws!).\n",
                    netInterfaceId,
                    nodeName,
                    networkName
                    );
            }
            else if (newState == (NM_STATE_ENTRY) ClusterNetInterfaceUp) {
                netInterface->State = ClusterNetInterfaceUp;
                eventCode = CLUSTER_EVENT_NETINTERFACE_UP;
                logCode = NM_EVENT_CLUSTER_NETINTERFACE_UP;
                logLevel = LOG_NOISE;
                logEvent = TRUE;

                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Interface %1!ws! is up (node: %2!ws!, "
                    "network: %3!ws!).\n",
                    netInterfaceId,
                    nodeName,
                    networkName
                    );
            }
            else if ( newState ==
                      (NM_STATE_ENTRY) ClusterNetInterfaceUnreachable
                    )
            {
                netInterface->State = ClusterNetInterfaceUnreachable;
                eventCode = CLUSTER_EVENT_NETINTERFACE_UNREACHABLE;
                logCode = NM_EVENT_CLUSTER_NETINTERFACE_UNREACHABLE;
                logLevel = LOG_UNUSUAL;
                logEvent = TRUE;

                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] Interface %1!ws! is unreachable (node: %2!ws!, "
                    "network: %3!ws!).\n",
                    netInterfaceId,
                    nodeName,
                    networkName
                    );
            }
            else if (newState == (NM_STATE_ENTRY) ClusterNetInterfaceFailed) {
                netInterface->State = ClusterNetInterfaceFailed;
                eventCode = CLUSTER_EVENT_NETINTERFACE_FAILED;
                logCode = NM_EVENT_CLUSTER_NETINTERFACE_FAILED;
                logLevel = LOG_UNUSUAL;
                logEvent = TRUE;

                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] Interface %1!ws! failed (node: %2!ws!, "
                    "network: %3!ws!).\n",
                    netInterfaceId,
                    nodeName,
                    networkName
                    );
            }
            else if ( newState ==
                      (NM_STATE_ENTRY) ClusterNetInterfaceStateUnknown
                    )
            {
                //
                // This case can happen if a create update races with a
                // state update. This will be the new interface. Just
                // ignore it. Another state update will arrive shortly.
                //
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] State for interface %1!ws! is unknown "
                    "(node: %2!ws!, network: %3!ws!).\n",
                    netInterfaceId,
                    nodeName,
                    networkName
                    );
            }
            else {
                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] UpdateInterfaceState: Invalid state %1!u! "
                    "specified for interface %2!ws!\n",
                    newState,
                    netInterfaceId
                    );
                CL_ASSERT(FALSE);
            }

            if (eventCode != 0) {
                ClusterEvent(eventCode, netInterface);
            }

            if (logEvent && (NmpLeaderNodeId == NmLocalNodeId)) {
                CsLogEvent2(
                    logLevel,
                    logCode,
                    nodeName,
                    networkName
                    );
            }
        }
    }

    if (Network->State != NewNetworkState) {
        BOOLEAN          logEvent = FALSE;
        CLUSTER_EVENT    eventCode = 0;


        if (NewNetworkState == ClusterNetworkUnavailable) {
            Network->State = ClusterNetworkUnavailable;
            eventCode = CLUSTER_EVENT_NETWORK_UNAVAILABLE;
        }
        else if (NewNetworkState == ClusterNetworkUp) {
            Network->State = ClusterNetworkUp;
            eventCode = CLUSTER_EVENT_NETWORK_UP;
            logCode = NM_EVENT_CLUSTER_NETWORK_UP;
            logLevel = LOG_NOISE;
            logEvent = TRUE;

            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Network %1!ws! (%2!ws!) is up.\n",
                networkId,
                networkName
                );
        }
        else if (NewNetworkState == ClusterNetworkDown) {
            Network->State = ClusterNetworkDown;
            eventCode = CLUSTER_EVENT_NETWORK_DOWN;
            logCode = NM_EVENT_CLUSTER_NETWORK_DOWN;
            logLevel = LOG_UNUSUAL;
            logEvent = TRUE;

            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Network %1!ws! (%2!ws!) is down.\n",
                networkId,
                networkName
                );
        }
        else if (NewNetworkState == ClusterNetworkPartitioned) {
            Network->State = ClusterNetworkPartitioned;
            eventCode = CLUSTER_EVENT_NETWORK_PARTITIONED;
            logCode = NM_EVENT_CLUSTER_NETWORK_PARTITIONED;
            logLevel = LOG_UNUSUAL;
            logEvent = TRUE;

            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Network %1!ws! (%2!ws!) is partitioned.\n",
                networkId,
                networkName
                );
        }
        else {
            ClRtlLogPrint(LOG_UNUSUAL, 
                "[NM] Invalid state %1!u! for network %2!ws!\n",
                Network->State,
                networkId
                );
            CL_ASSERT(FALSE);
        }

        if (eventCode != 0) {
            ClusterEvent(eventCode, Network);
        }

        if (logEvent && (NmpLeaderNodeId == NmLocalNodeId)) {
            CsLogEvent1(
                logLevel,
                logCode,
                networkName
                );
        }
    }

    return;

} // NmpSetNetworkAndInterfaceStates


/////////////////////////////////////////////////////////////////////////////
//
// Network state management routines
//
/////////////////////////////////////////////////////////////////////////////
VOID
NmpRecomputeNT5NetworkAndInterfaceStates(
    VOID
    )
{
    PNM_NETWORK   network;
    PLIST_ENTRY   entry;


    for (entry = NmpNetworkList.Flink;
         entry != &NmpNetworkList;
         entry = entry->Flink
        )
    {
        network = CONTAINING_RECORD(
                      entry,
                      NM_NETWORK,
                      Linkage
                      );

        NmpStartNetworkStateRecalcTimer(
            network,
            NM_NET_STATE_RECALC_TIMEOUT_AFTER_REGROUP
            );
    }

    return;

} // NmpRecomputeNT5NetworkAndInterfaceStates


BOOLEAN
NmpComputeNetworkAndInterfaceStates(
    IN  PNM_NETWORK               Network,
    IN  BOOLEAN                   IsolateFailure,
    OUT CLUSTER_NETWORK_STATE *   NewNetworkState
    )
/*++

Routine Description:

    Computes the state of a network and all of its interfaces based on
    connectivity reports from each constituent interface. Attempts
    to distinguish between failures of individual interfaces and
    failure of an entire network.

Arguments:

    Network - A pointer to the network object to be processed.

    IsolateFailure - Triggers failure isolation analysis if set to true.

    NewNetworkState - A pointer to a variable that, upon return, contains
                      the new state of the network.

Return Value:

    TRUE if either the state of the network or the state of at least one
    of its constituent interfaces changed. FALSE otherwise.

Notes:

    Called with NmpLock held and the network object referenced.

--*/
{
    DWORD                       numIfUnavailable = 0;
    DWORD                       numIfFailed = 0;
    DWORD                       numIfUnreachable = 0;
    DWORD                       numIfUp = 0;
    DWORD                       numIfReachable = 0;
    PNM_CONNECTIVITY_MATRIX     matrix = Network->ConnectivityMatrix;
    PNM_CONNECTIVITY_MATRIX     matrixEntry;
    PNM_STATE_WORK_VECTOR       workVector = Network->StateWorkVector;
    DWORD                       entryCount =
                                    Network->ConnectivityVector->EntryCount;
    DWORD                       reporter, ifNetIndex;
    BOOLEAN                     stateChanged = FALSE;
    LPCWSTR                     networkId = OmObjectId(Network);
    LPCWSTR                     netInterfaceId;
    PLIST_ENTRY                 entry;
    PNM_INTERFACE               netInterface;
    NM_STATE_ENTRY              state;
    BOOLEAN                     selfDeclaredFailure = FALSE;
    DWORD                       interfaceFailureTimeout = 0;
    BOOLEAN                     abortComputation = FALSE;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Beginning phase 1 of state computation for network %1!ws!.\n",
        networkId
        );

    //
    // Phase 1 - Compute the state of each interface from the data
    //           in the connectivity matrix.
    //
    for ( entry = Network->InterfaceList.Flink;
          entry != &(Network->InterfaceList);
          entry = entry->Flink
        )
    {
        netInterface = CONTAINING_RECORD(
                           entry,
                           NM_INTERFACE,
                           NetworkLinkage
                           );

        netInterfaceId = OmObjectId(netInterface);
        ifNetIndex = netInterface->NetIndex;
        workVector[ifNetIndex].ReachableCount = 0;

        if (NmpIsNetworkEnabledForUse(Network)) {
            //
            // First, check what the interface thinks its own state is
            //
            matrixEntry = NM_GET_CONNECTIVITY_MATRIX_ENTRY(
                              matrix,
                              ifNetIndex,
                              ifNetIndex,
                              entryCount
                              );

            if ( *matrixEntry ==
                 (NM_STATE_ENTRY) ClusterNetInterfaceStateUnknown
               )
            {
                //
                // This case should never happen.
                //
                // An existing interface cannot think its own state is
                // unknown. The reflexive entry is always initialized to
                // Unavailable whenever an interface is created.
                //
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] No data for interface %1!u! (%2!ws!) on network "
                    "%3!ws!\n",
                    ifNetIndex,
                    netInterfaceId,
                    networkId
                    );

                CL_ASSERT(
                    *matrixEntry !=
                     (NM_STATE_ENTRY) ClusterNetInterfaceStateUnknown
                     );

                state = ClusterNetInterfaceUnavailable;
                numIfUnavailable++;
            }
            else if ( *matrixEntry ==
                      (NM_STATE_ENTRY) ClusterNetInterfaceUnavailable
                    )
            {
                if (NM_NODE_UP(netInterface->Node)) {
                    //
                    // The node is up, but its connectivity report has
                    // not been received yet. This case may happen while a
                    // node is joining. It can also happen if this node has
                    // just become the new leader.
                    //
                    // Abort the state computation.
                    //
                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Data is not yet valid for interface %1!u! "
                        "(%2!ws!) on network %3!ws!.\n",
                        ifNetIndex,
                        netInterfaceId,
                        networkId
                        );

                    abortComputation = TRUE;
                    break;
                }
                else {
                    //
                    // The owning node is down or joining.
                    // The interface is in the unavailable state.
                    //
                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Node is down for interface %1!u! (%2!ws!) on "
                        "network %3!ws!\n",
                        ifNetIndex,
                        netInterfaceId,
                        networkId
                        );

                    state = ClusterNetInterfaceUnavailable;
                    numIfUnavailable++;
                }
            }
            else if ( *matrixEntry ==
                      (NM_STATE_ENTRY) ClusterNetInterfaceFailed
                    )
            {
                //
                // The node declared its own interface as failed.
                // The interface is in the failed state.
                //
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Interface %1!u! (%2!ws!) has failed on network "
                    "%3!ws!\n",
                    ifNetIndex,
                    netInterfaceId,
                    networkId
                    );

                state = ClusterNetInterfaceFailed;
                numIfFailed++;
                if (netInterface->State == ClusterNetInterfaceUp) {
                    selfDeclaredFailure = TRUE;
                }
            }
            else {
                CL_ASSERT(
                    *matrixEntry == (NM_STATE_ENTRY) ClusterNetInterfaceUp
                    );
                //
                // The owning node thinks its interface is Up.
                //
                // If there are no other operational interfaces on the
                // network, then the interface is in the Up state.
                //
                // If there are other operational interfaces on the
                // network, but all of their reports are not yet valid,
                // then we defer calculating a new state for the interface.
                //
                // If there are other operational interfaces on the network,
                // and all of their reports are valid, then if at least one
                // of the operational interfaces reports that the interface
                // is unreachable, then then the interface is in the
                // Unreachable state. If all of the other operational
                // interfaces report that the interface is reachable, then
                // the interface is in the Up state.
                //
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Examining connectivity data for interface %1!u! "
                    "(%2!ws!) on network %2!ws!.\n",
                    ifNetIndex,
                    netInterfaceId,
                    networkId
                    );

                //
                // Assume that the interface is Up until proven otherwise.
                //
                state = ClusterNetInterfaceUp;

                //
                // Examine the reports from other interfaces -
                // i.e. scan the matrix column - to see if any node with
                // an operational interface reports this interface to
                // be unreachable.
                //
                for (reporter=0; reporter<entryCount; reporter++) {

                    if (reporter == ifNetIndex) {
                        continue;
                    }

                    //
                    // First, see if the reporting interface is operational
                    // by checking what the repoerter thinks of its own
                    // interface.
                    //
                    matrixEntry = NM_GET_CONNECTIVITY_MATRIX_ENTRY(
                                      matrix,
                                      reporter,
                                      reporter,
                                      entryCount
                                      );

                    if (*matrixEntry == ClusterNetInterfaceUp) {
                        PNM_CONNECTIVITY_MATRIX   matrixEntry2;

                        //
                        // Both the reporter and the reportee believe that
                        // their respective interfaces are operational.
                        // Check if they agree on the state of their
                        // connectivity before going any further.
                        // ClusNet guarantees that eventually they will
                        // agree.
                        //
                        matrixEntry = NM_GET_CONNECTIVITY_MATRIX_ENTRY(
                                          matrix,
                                          reporter,
                                          ifNetIndex,
                                          entryCount
                                          );

                        matrixEntry2 = NM_GET_CONNECTIVITY_MATRIX_ENTRY(
                                           matrix,
                                           ifNetIndex,
                                           reporter,
                                           entryCount
                                           );

                        if (*matrixEntry == *matrixEntry2) {
                            //
                            // The two interfaces agree on the state of
                            // their connectivity. Check what they agree on.
                            //
                            if (*matrixEntry == ClusterNetInterfaceUp) {
                                //
                                // The interface is reported to be up.
                                //
                                ClRtlLogPrint(LOG_NOISE, 
                                    "[NM] Interface %1!u! reports interface "
                                    "%2!u! is up on network %3!ws!\n",
                                    reporter,
                                    ifNetIndex,
                                    networkId
                                    );

                                workVector[ifNetIndex].ReachableCount++;
                            }
                            else if ( *matrixEntry ==
                                      ClusterNetInterfaceUnreachable
                                    )
                            {
                                //
                                // The interface is reported to be
                                // unreachable.
                                //
                                ClRtlLogPrint(LOG_NOISE, 
                                    "[NM] Interface %1!u! reports interface "
                                    "%2!u! is unreachable on network %3!ws!\n",
                                    reporter,
                                    ifNetIndex,
                                    networkId
                                    );

                                state = ClusterNetInterfaceUnreachable;
                                
                                //
                                // If this interface is already in failed state do fault isolation immediately.
                                //
                                if(workVector[ifNetIndex].State == ClusterNetInterfaceFailed)
                                    IsolateFailure = TRUE;
                            }
                            else {
                                CL_ASSERT(
                                    *matrixEntry != ClusterNetInterfaceFailed
                                    );
                                //
                                // The interface report is not valid yet.
                                // Abort the computation.
                                //
                                ClRtlLogPrint(LOG_NOISE, 
                                    "[NM] Report from interface %1!u! for "
                                    "interface %2!u! is not valid yet on "
                                    "network %3!ws!.\n",
                                    reporter,
                                    ifNetIndex,
                                    *matrixEntry,
                                    networkId
                                    );
                                abortComputation = TRUE;
                                break;
                            }
                        }
                        else {
                            //
                            // The two interfaces do not yet agree on
                            // their connectivity. Abort the computation.
                            //
                            ClRtlLogPrint(LOG_NOISE, 
                                "[NM] Interface %1!u! and interface %2!u! do "
                                "not agree on their connectivity on network "
                                "%3!ws!\n",
                                reporter,
                                ifNetIndex,
                                networkId
                                );
                            abortComputation = TRUE;
                            break;
                        }
                    }
                    else {
                        //
                        // The reporter does not think its own interface is
                        // operational.
                        //
                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] The report from interface %1!u! is not "
                            "valid on network %2!ws!.\n",
                            reporter,
                            networkId
                            );
                    }
                } // end of connectivity matrix scan loop

                if (abortComputation) {
                    //
                    // Abort Phase 1 computation.
                    //
                    break;
                }

                if (state == ClusterNetInterfaceUp) {
                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Interface %1!u! (%2!ws!) is up on network "
                        "%3!ws!.\n",
                        ifNetIndex,
                        netInterfaceId,
                        networkId
                        );
                    numIfUp++;
                }
                else {
                    CL_ASSERT(state == ClusterNetInterfaceUnreachable);

                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Interface %1!u! (%2!ws!) is unreachable on "
                        "network %3!ws!\n",
                        ifNetIndex,
                        netInterfaceId,
                        networkId
                        );
                    numIfUnreachable++;
                }
            }
        }
        else {
            //
            // The network is disabled. It is in the Unavailable state.
            //
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Interface %1!u! (%2!ws!) is unavailable because "
                "network %3!ws! is disabled. \n",
                ifNetIndex,
                netInterfaceId,
                networkId
                );
            state = ClusterNetInterfaceUnavailable;
            numIfUnavailable++;
        }

        workVector[ifNetIndex].State = state;

        //
        // Keep track of how many interfaces on the network are
        // reachable by at least one peer.
        //
        if ( (state == ClusterNetInterfaceUp) ||
             (workVector[ifNetIndex].ReachableCount > 0)
           ) {
            numIfReachable++;
        }

        if (netInterface->State != state) {
            stateChanged = TRUE;
        }

    } // end of phase one interface loop

    if (!abortComputation && !IsolateFailure && selfDeclaredFailure) {

        interfaceFailureTimeout = 
            NmpGetNetworkInterfaceFailureTimerValue(networkId);

        if (interfaceFailureTimeout > 0) {

            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Delaying state computation for network %1!ws! "
                "for %2!u! ms to damp self-declared failure event.\n",
                networkId, interfaceFailureTimeout
                );

            NmpStartNetworkFailureIsolationTimer(
                Network,
                interfaceFailureTimeout
                );

            abortComputation = TRUE;
        }
    }

    if (abortComputation) {

        if (interfaceFailureTimeout == 0) {
            
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Aborting state computation for network %1!ws! "
                " until connectivity data is valid.\n",
                networkId
                );
        }
    
        //
        // Undo any changes we made to the work vector.
        //
        for ( entry = Network->InterfaceList.Flink;
              entry != &(Network->InterfaceList);
              entry = entry->Flink
            )
        {
            netInterface = CONTAINING_RECORD(
                               entry,
                               NM_INTERFACE,
                               NetworkLinkage
                               );

            ifNetIndex = netInterface->NetIndex;
            workVector[ifNetIndex].State = (NM_STATE_ENTRY)
                                           netInterface->State;
        }

        *NewNetworkState = Network->State;

        return(FALSE);
    }

    //
    // Phase 2
    //
    // Try to determine the scope of any failures and recompute the
    // interface states. There are two cases in which we can isolate
    // failures.
    //
    //     Case 1: Three or more interfaces are operational. At least two
    //             interfaces can communicate with a peer. One or more
    //             interfaces cannot communicate with any peer.
    //             Those that cannot communicate at all have failed.
    //
    //     Case 2: Exactly two interfaces are operational and they cannot
    //             communicate with one another. If one interface can
    //             communicate with an external host while the other
    //             cannot communicate with any external host, then the one
    //             that cannot communicate has failed.
    //
    // In any other situation, we do nothing.
    //
    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Completed phase 1 of state computation for network "
        "%1!ws!.\n",
        networkId
        );

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Unavailable=%1!u!, Failed = %2!u!, Unreachable=%3!u!, "
        "Reachable=%4!u!, Up=%5!u! on network %6!ws! \n",
        numIfUnavailable,
        numIfFailed,
        numIfUnreachable,
        numIfReachable,
        numIfUp,
        networkId
        );

    if (numIfUnreachable > 0) {
        //
        // Some interfaces are unreachable.
        //
        if ( ((numIfUp + numIfUnreachable) >= 3)  && (numIfReachable >= 2) ) {
            if (IsolateFailure) {
                //
                // Case 1.
                //
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Trying to determine scope of connectivity failure "
                    "for >3 interfaces on network %1!ws!.\n",
                    networkId
                    );

                //
                // Any operational interface that cannot communicate with at
                // least one other operational interface has failed.
                //
                for ( entry = Network->InterfaceList.Flink;
                      entry != &(Network->InterfaceList);
                      entry = entry->Flink
                    )
                {
                    netInterface = CONTAINING_RECORD(
                                       entry,
                                       NM_INTERFACE,
                                       NetworkLinkage
                                       );

                    ifNetIndex = netInterface->NetIndex;
                    netInterfaceId = OmObjectId(netInterface);

                    if ( ( workVector[ifNetIndex].State ==
                           ClusterNetInterfaceUnreachable
                         )
                         &&
                         (workVector[ifNetIndex].ReachableCount == 0)
                       )
                    {
                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Interface %1!u! (%2!ws!) has failed on "
                            "network %3!ws!\n",
                            ifNetIndex,
                            netInterfaceId,
                            networkId
                            );
                        workVector[ifNetIndex].State =
                            ClusterNetInterfaceFailed;
                        numIfUnreachable--;
                        numIfFailed++;
                    }
                }

                //
                // If any interface, whose state is still unreachable, can see
                // all other reachable interfaces, change its state to up.
                //
                for ( entry = Network->InterfaceList.Flink;
                      entry != &(Network->InterfaceList);
                      entry = entry->Flink
                    )
                {
                    netInterface = CONTAINING_RECORD(
                                       entry,
                                       NM_INTERFACE,
                                       NetworkLinkage
                                       );

                    ifNetIndex = netInterface->NetIndex;

                    if ( ( workVector[ifNetIndex].State ==
                           ClusterNetInterfaceUnreachable
                         )
                         &&
                         ( workVector[ifNetIndex].ReachableCount ==
                           (numIfUp + numIfUnreachable - 1)
                         )
                       )
                    {
                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Interface %1!u! (%2!ws!) is up on network "
                            "%3!ws!\n",
                            ifNetIndex,
                            netInterfaceId,
                            networkId
                            );
                        workVector[ifNetIndex].State = ClusterNetInterfaceUp;
                        numIfUnreachable--;
                        numIfUp++;
                    }
                }

                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Connectivity failure scope determination complete "
                    "for network %1!ws!.\n",
                    networkId
                    );

            }
            else {
                //
                // Schedule a failure isolation calculation to run later.
                // Declaring a failure can affect cluster resources, so we
                // don't want to do it unless we are sure. Delaying for a
                // while reduces the risk of a false positive.
                //
                NmpStartNetworkFailureIsolationTimer(Network,
                    NM_NET_STATE_FAILURE_ISOLATION_TIMEOUT);

            }
        }
        else if ((numIfUnreachable == 2) && (numIfUp == 0)) {
            if (IsolateFailure) {
                //
                // Case 2.
                //
                PNM_INTERFACE  interface1 = NULL;
                BOOLEAN        interface1HasConnectivity;
                LPCWSTR        interfaceId1;
                PNM_INTERFACE  interface2 = NULL;
                BOOLEAN        interface2HasConnectivity;
                LPCWSTR        interfaceId2;
                DWORD          status;


                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Trying to determine scope of connectivity failure "
                    "for 2 interfaces on network %1!ws!.\n",
                    networkId
                    );

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

                    ifNetIndex = netInterface->NetIndex;

                    if ( workVector[ifNetIndex].State ==
                         ClusterNetInterfaceUnreachable
                       )
                    {
                        if (interface1 == NULL) {
                            interface1 = netInterface;
                            interfaceId1 = OmObjectId(interface1);

                            ClRtlLogPrint(LOG_NOISE, 
                                "[NM] Unreachable interface 1 = %1!ws! on "
                                "network %2!ws!\n",
                                interfaceId1,
                                networkId
                                );
                        }
                        else {
                            interface2 = netInterface;
                            interfaceId2 = OmObjectId(interface2);

                            ClRtlLogPrint(LOG_NOISE, 
                                "[NM] Unreachable interface 2 = %1!ws! on "
                                "network %2!ws!\n",
                                interfaceId2,
                                networkId
                                );
                            break;
                        }
                    }
                }

                //
                // NmpTestInterfaceConnectivity releases and reacquires
                // the NmpLock. We must reference the interface objects
                // to ensure that they are still valid upon return from
                // that routine.
                //
                OmReferenceObject(interface1);
                OmReferenceObject(interface2);

                status = NmpTestInterfaceConnectivity(
                             interface1,
                             &interface1HasConnectivity,
                             interface2,
                             &interface2HasConnectivity
                             );

                if (status == ERROR_SUCCESS) {
                    if ( interface1HasConnectivity &&
                         !interface2HasConnectivity
                       )
                    {
                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Interface %1!u! (%2!ws!) has Failed on "
                            "network %3!ws!\n",
                            interface2->NetIndex,
                            interfaceId2,
                            networkId
                            );

                        workVector[interface2->NetIndex].State =
                            ClusterNetInterfaceFailed;
                        numIfUnreachable--;
                        numIfFailed++;

                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Interface %1!u! (%2!ws!) is Up on network "
                            "%3!ws!\n",
                            interface1->NetIndex,
                            interfaceId1,
                            networkId
                            );

                        workVector[interface1->NetIndex].State =
                            ClusterNetInterfaceUp;
                        numIfUnreachable--;
                        numIfUp++;
                    }
                    else if ( !interface1HasConnectivity &&
                              interface2HasConnectivity
                            )
                    {
                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Interface %1!u! (%2!ws!) has Failed on "
                            "network %3!ws!\n",
                            interface1->NetIndex,
                            interfaceId1,
                            networkId
                            );

                        workVector[interface1->NetIndex].State =
                            ClusterNetInterfaceFailed;
                        numIfUnreachable--;
                        numIfFailed++;

                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Interface %1!u! (%2!ws!) is Up on network "
                            "%3!ws!\n",
                            interface2->NetIndex,
                            interfaceId2,
                            networkId
                            );

                        workVector[interface2->NetIndex].State =
                            ClusterNetInterfaceUp;
                        numIfUnreachable--;
                        numIfUp++;
                    }
                    else {
                        ClRtlLogPrint(LOG_UNUSUAL, 
                            "[NM] Network %1!ws! state is indeterminate, Scheduling"
                            " Failure Isolation poll\n",
                            networkId
                            );
                        NmpStartNetworkFailureIsolationTimer(Network, 
                            NmpGetIsolationPollTimerValue());
                    }
                }
                else {
                    ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] Error in Interface Connectivity test for Network %1!ws!" 
                    ", Scheduling Failure Isolation poll\n",
                    networkId
                    );
                    NmpStartNetworkFailureIsolationTimer(Network,
                        NmpGetIsolationPollTimerValue()); 
                }

                OmDereferenceObject(interface1);
                OmDereferenceObject(interface2);

                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Connectivity failure scope determination complete "
                    "for network %1!ws!.\n",
                    networkId
                    );
            }
            else {
                //
                // Schedule a failure isolation calculation to run later.
                // Declaring a failure can affect cluster resources, so we
                // don't want to do it unless we are sure. Delaying for a
                // while reduces the risk of a false positive.
                //
                NmpStartNetworkFailureIsolationTimer(Network,
                    NM_NET_STATE_FAILURE_ISOLATION_TIMEOUT);
            }
        }
        else {
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Cannot determine scope of connectivity failure on "
                "network %1!ws!.\n",
                networkId
                );
        }
    }
    else {
        //
        // No unreachable interfaces. Disable the failure isolation timer,
        // if it is running.
        //
        Network->FailureIsolationTimer = 0;
        Network->Flags &= ~NM_FLAG_NET_ISOLATE_FAILURE;
    }

    //
    // Phase 3 - Compute the new network state.
    //
    if (numIfUnavailable == Network->InterfaceCount) {
        //
        // All interfaces are unavailable.
        //
        *NewNetworkState = ClusterNetworkUnavailable;
    }
    else if (numIfUnreachable > 0) {
        //
        // Some operational interfaces have experienced
        // a loss of connectivity, but the fault could not be
        // isolated to them.
        //
        if (numIfReachable > 0) {
            CL_ASSERT(numIfReachable >= 2);
            //
            // At least two interfaces can still communicate
            // with each other, so the network is not completely down.
            //
            *NewNetworkState = ClusterNetworkPartitioned;
        }
        else {
            CL_ASSERT(numIfUp == 0);
            //
            // None of the operational interfaces can communicate
            //
            *NewNetworkState = ClusterNetworkDown;
        }
    }
    else if (numIfUp > 0) {
        //
        // Some interfaces are Up, all others are Failed or Unavailable
        //
        *NewNetworkState = ClusterNetworkUp;
    }
    else {
        //
        // Some interfaces are Unavailable, rest are Failed.
        //
        *NewNetworkState = ClusterNetworkDown;
    }

    if (Network->State != *NewNetworkState) {
        stateChanged = TRUE;

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Network %1!ws! is now in state %2!u!\n",
            networkId,
            *NewNetworkState
            );
    }

    return(stateChanged);

} // NmpComputeNetworkAndInterfaceStates


DWORD
NmpGetIsolationPollTimerValue(
    VOID
    )
/*--
 * Reads the IsolationPollTimerValue Parameter from the registry if it's there
 * else returns default value.
 */
{

    DWORD value;
    DWORD type;
    DWORD len = sizeof(value);
    DWORD status;

	// Release NM Lock to avoid deadlock with DM lock
    NmpReleaseLock();
    
    status = DmQueryValue(
                DmClusterParametersKey,
                L"IsolationPollTimerValue",
                &type,
                (LPBYTE)&value,
                &len
                );

	NmpAcquireLock();
    if((status != ERROR_SUCCESS) || (type != REG_DWORD) ||
       (value < 10) || (value > 600000)) {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to read IsolationPollTimerValue or value out of range,"
            "status %1!u! using default %2!u! ms\n",
            status,
            NM_NET_STATE_FAILURE_ISOLATION_POLL
            );
        return NM_NET_STATE_FAILURE_ISOLATION_POLL;
    }
    else {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] IsolationPollTimerValue = %1!u!\n",
            value
            );
        return value;
    }
}

DWORD
NmpGetNetworkInterfaceFailureTimerValue(
    IN LPCWSTR   NetworkId
    )
/*++

Routine Description:

    Reads InterfaceFailure timer value from registry.
    If a value is located in the network key, it is used.
    Otherwise the cluster parameters key is checked.
    If no value is present, returns default.
    
Arguments:

    NetworkId - id of network whose timer value to determine
    
Return value:

    InterfaceFailure timer value
    
Notes:

    Called with NM lock held (from NmpComputeNetworkAndInterfaceStates).
    
    This routine queries the cluster database; hence, it drops the
    NM lock. Since NmpComputeNetworkAndInterfaceStates is always called
    in the context of the NmpWorkerThread, the Network is always 
    referenced during execution of this routine.
    
--*/
{
    HDMKEY  networkKey, paramKey;
    DWORD   status;
    DWORD   type;
    DWORD   value = NM_NET_STATE_INTERFACE_FAILURE_TIMEOUT;
    DWORD   len = sizeof(value);
    BOOLEAN found = FALSE;

    //
    // To avoid deadlock, the DM lock must be acquired before the
    // NM lock. Hence, release the NM lock prior to querying the
    // cluster database. 
    //
    NmpReleaseLock();

    //
    // First check the network key
    //
    networkKey = DmOpenKey(DmNetworksKey, NetworkId, KEY_READ);
    if (networkKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to open key for network %1!ws!, "
            "status %1!u!\n",
            NetworkId, status
            );
    }
    else {
        paramKey = DmOpenKey(networkKey, L"Parameters", KEY_READ);
        if (paramKey == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Failed to find Parameters key "
                "for network %1!ws!, status %2!u!. Checking "
                "cluster parameters ...\n",
                NetworkId, status
                );
        }
        else {
            status = DmQueryValue(
                         paramKey,
                         L"InterfaceFailureTimeout",
                         &type,
                         (LPBYTE) &value,
                         &len
                         );
            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Failed to read InterfaceFailureTimeout "
                    "for network %1!ws!, status %2!u!. Checking "
                    "cluster parameters ...\n",
                    NetworkId, status
                    );
            }
            else if (type != REG_DWORD) {
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Unexpected type (%1!u!) for network "
                    "%2!ws! InterfaceFailureTimeout, status %3!u!. "
                    "Checking cluster parameters ...\n",
                    type, NetworkId, status
                    );
            }
            else {
                found = TRUE;
            }
            
            DmCloseKey(paramKey);
        }
        
        DmCloseKey(networkKey);
    }


    //
    // If no value was found under the network key, check the
    // cluster parameters key.
    //
    if (!found) {

        paramKey = DmOpenKey(DmClusterParametersKey, L"Parameters", KEY_READ);
        if (paramKey == NULL) {
            status = GetLastError();
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Failed to find cluster Parameters key, status %1!u!.\n",
                status
                );
        }
        else {
            status = DmQueryValue(
                         paramKey,
                         L"InterfaceFailureTimeout",
                         &type,
                         (LPBYTE) &value,
                         &len
                         );
            if (status != ERROR_SUCCESS) {
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Failed to read cluster "
                    "InterfaceFailureTimeout, status %1!u!. "
                    "Using default value ...\n",
                    status
                    );
                value = NM_NET_STATE_INTERFACE_FAILURE_TIMEOUT;
            }
            else if (type != REG_DWORD) {
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Unexpected type (%1!u!) for cluster "
                    "InterfaceFailureTimeout, status %2!u!. "
                    "Using default value ...\n",
                    type, status
                    );
                value = NM_NET_STATE_INTERFACE_FAILURE_TIMEOUT;
            }

            DmCloseKey(paramKey);
        }
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Using InterfaceFailureTimeout of %1!u! ms "
        "for network %2!ws!.\n",
        value, NetworkId
        );

    //
    // Reacquire NM lock.
    //
    NmpAcquireLock();

    return(value);
}

VOID
NmpConnectivityReportWorker(
    IN PCLRTL_WORK_ITEM   WorkItem,
    IN DWORD              Status,
    IN DWORD              BytesTransferred,
    IN ULONG_PTR          IoContext
    )
/*++

Routine Description:

    Worker routine for deferred operations on network objects.
    Invoked to process items placed in the cluster delayed work queue.

Arguments:

    WorkItem - Ignored.

    Status - Ignored.

    BytesTransferred - Ignored.

    IoContext - Ignored.

Return Value:

    None.

Notes:

    This routine is run in an asynchronous worker thread.
    The NmpActiveThreadCount was incremented when the thread was
    scheduled.

--*/
{
    BOOLEAN       rescheduled = FALSE;


    NmpAcquireLock();

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Connectivity report worker thread running.\n"
        );

    CL_ASSERT(NmpIsConnectivityReportWorkerRunning == TRUE);
    CL_ASSERT(NmpNeedConnectivityReport == TRUE);

    if (NmpState >= NmStateOnlinePending) {
        PNM_NETWORK  network;
        LPCWSTR      networkId;
        PLIST_ENTRY  entry;
        DWORD        status;

        while(TRUE) {

            NmpNeedConnectivityReport = FALSE;

            for (entry = NmpNetworkList.Flink;
                 entry != &NmpNetworkList;
                 entry = entry->Flink
                )
            {
                network = CONTAINING_RECORD(entry, NM_NETWORK, Linkage);

                if (!NM_DELETE_PENDING(network)) {
                    networkId = OmObjectId(network);

                    //
                    // Deliver an InterfaceFailed event for the local interface
                    // if needed.
                    //
                    if (network->Flags & NM_FLAG_NET_REPORT_LOCAL_IF_FAILED) {
                        network->Flags &= ~NM_FLAG_NET_REPORT_LOCAL_IF_FAILED;

                        if (NmpIsNetworkRegistered(network)) {
                            ClRtlLogPrint(LOG_NOISE, 
                                "[NM] Processing local interface failed "
                                " event for network %1!ws!.\n",
                                networkId
                                );

                            NmpProcessLocalInterfaceStateEvent(
                                network->LocalInterface,
                                ClusterNetInterfaceFailed
                                );
                        }
                    }

                    //
                    // Deliver an InterfaceUp event for the local interface
                    // if needed.
                    //
                    if (network->Flags & NM_FLAG_NET_REPORT_LOCAL_IF_UP) {
                        network->Flags &= ~NM_FLAG_NET_REPORT_LOCAL_IF_UP;

                        if (NmpIsNetworkRegistered(network)) {
                            ClRtlLogPrint(LOG_NOISE, 
                                "[NM] Processing local interface up event "
                                "for network %1!ws!.\n",
                                networkId
                                );

                            NmpProcessLocalInterfaceStateEvent(
                                network->LocalInterface,
                                ClusterNetInterfaceUp
                                );
                        }
                    }

                    //
                    // Send a connectivity report if needed.
                    //
                    if (network->Flags & NM_FLAG_NET_REPORT_CONNECTIVITY) {
                        CL_NODE_ID   leaderNodeId = NmpLeaderNodeId;

                        network->Flags &= ~NM_FLAG_NET_REPORT_CONNECTIVITY;

                        //
                        // Report our connectivity to the leader.
                        //
                        status = NmpReportNetworkConnectivity(network);

                        if (status == ERROR_SUCCESS) {
                            //
                            // Clear the report retry count.
                            //
                            network->ConnectivityReportRetryCount = 0;
                        }
                        else {
                            if (NmpLeaderNodeId == leaderNodeId) {
                                if (network->ConnectivityReportRetryCount++ <
                                    NM_CONNECTIVITY_REPORT_RETRY_LIMIT
                                   )
                                {
                                    //
                                    // Try again in 500ms.
                                    //
                                    network->ConnectivityReportTimer = 500;
                                }
                                else {
                                    //
                                    // We have failed to communicate with
                                    // the leader for too long. Mutiny.
                                    //
                                    NmpAdviseNodeFailure(
                                        NmpIdArray[NmpLeaderNodeId],
                                        status
                                        );

                                    network->ConnectivityReportRetryCount = 0;
                                }
                            }
                            else {
                                //
                                // New leader, clear the retry count. We
                                // already scheduled another connectivity
                                // report in the node down handling.
                                //
                                network->ConnectivityReportRetryCount = 0;
                            }
                        }
                    }
                }
            } // end network for loop

            if (NmpNeedConnectivityReport == FALSE) {
                //
                // No more work to do - break out of loop.
                //
                break;
            }

            //
            // More work to do. Resubmit the work item. We do this instead
            // of looping so we don't hog the worker thread. If
            // rescheduling fails, we will loop again in this thread.
            //
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] More connectivity reports to send. Rescheduling "
                "worker thread.\n"
                );

            status = NmpScheduleConnectivityReportWorker();

            if (status == ERROR_SUCCESS) {
                rescheduled = TRUE;
                break;
            }
        } // end while(TRUE)
    }

    if (!rescheduled) {
        NmpIsConnectivityReportWorkerRunning = FALSE;
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Connectivity report worker thread finished.\n"
        );

    //
    // Decrement active thread reference count.
    //
    NmpLockedLeaveApi();

    NmpReleaseLock();

    return;

} // NmpConnectivityReportWorker


VOID
NmpNetworkWorker(
    IN PCLRTL_WORK_ITEM   WorkItem,
    IN DWORD              Status,
    IN DWORD              BytesTransferred,
    IN ULONG_PTR          IoContext
    )
/*++

Routine Description:

    Worker routine for deferred operations on network objects.
    Invoked to process items placed in the cluster delayed work queue.

Arguments:

    WorkItem - A pointer to a work item structure that identifies the
               network for which to perform work.

    Status - Ignored.

    BytesTransferred - Ignored.

    IoContext - Ignored.

Return Value:

    None.

Notes:

    This routine is run in an asynchronous worker thread.
    The NmpActiveThreadCount was incremented when the thread was
    scheduled. The network object was also referenced.

--*/
{
    PNM_NETWORK   network = (PNM_NETWORK) WorkItem->Context;
    LPCWSTR       networkId = OmObjectId(network);
    BOOLEAN       rescheduled = FALSE;


    NmpAcquireLock();

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Worker thread processing network %1!ws!.\n",
        networkId
        );

    if ((NmpState >= NmStateOnlinePending) && !NM_DELETE_PENDING(network)) {
        DWORD     status;

        while(TRUE) {
            CL_ASSERT(network->Flags & NM_FLAG_NET_WORKER_RUNNING);

            //
            // Register the network if needed.
            //
            if (network->Flags & NM_FLAG_NET_NEED_TO_REGISTER) {
                network->Flags &= ~NM_FLAG_NET_NEED_TO_REGISTER;

                if (network->LocalInterface != NULL) {
                    (VOID) NmpRegisterNetwork(
                               network,
                               TRUE    // Do retry on failure
                               );
                }
            }

            //
            // Isolate a network failure if needed.
            //
            if (network->Flags & NM_FLAG_NET_ISOLATE_FAILURE) {

                BOOLEAN                stateChanged;
                CLUSTER_NETWORK_STATE  newNetworkState;
                
                network->Flags &= ~NM_FLAG_NET_ISOLATE_FAILURE;

                //
                // Turn off the state recalc timer and flag, since we will
                // do a recalc here.
                //
                network->Flags &= ~NM_FLAG_NET_RECALC_STATE;
                network->StateRecalcTimer = 0;

                CL_ASSERT(NmpLeaderNodeId == NmLocalNodeId);

                //
                // Recompute the interface and network states
                // with failure isolation enabled.
                //
                stateChanged = NmpComputeNetworkAndInterfaceStates(
                                    network,
                                    TRUE,
                                    &newNetworkState
                                    );

                if (stateChanged) {
                    //
                    // Broadcast the new network and interface states to
                    // all nodes
                    //
                    status = NmpGlobalSetNetworkAndInterfaceStates(
                                    network,
                                    newNetworkState
                                    );

                    if (status != ERROR_SUCCESS) {
                        //
                        // Try again in 1 second.
                        //
                        ClRtlLogPrint(LOG_NOISE, 
                            "[NM] Global update failed for network %1!ws!, "
                            "status %2!u! - restarting failure isolation "
                            "timer.\n",
                            networkId,
                            status
                            );

                        network->FailureIsolationTimer = 1000;
                    }
                }
            }

            //
            // Recalculate the network and interface states if needed.
            //
            if (network->Flags & NM_FLAG_NET_RECALC_STATE) {
                BOOLEAN                stateChanged;
                CLUSTER_NETWORK_STATE  newNetworkState;

                network->Flags &= ~NM_FLAG_NET_RECALC_STATE;

                CL_ASSERT(NmpLeaderNodeId == NmLocalNodeId);

                //
                // Recompute the interface and network states
                // with failure isolation disabled. It will be
                // enabled if needed.
                //
                stateChanged = NmpComputeNetworkAndInterfaceStates(
                                    network,
                                    FALSE,
                                    &newNetworkState
                                    );

                if (stateChanged) {
                    //
                    // Broadcast the new network and interface states to
                    // all nodes
                    //
                    status = NmpGlobalSetNetworkAndInterfaceStates(
                                    network,
                                    newNetworkState
                                    );

                    if (status != ERROR_SUCCESS) {
                        //
                        // Try again in 500ms.
                        //
                        ClRtlLogPrint(LOG_NOISE,
                            "[NM] NetworkStateUpdateWorker failed issue "
                            "global update for network %1!ws!, status "
                            "%2!u! - restarting update timer.\n",
                            networkId,
                            status
                            );

                        network->StateRecalcTimer = 500;
                    }
                }
            }

            if (!(network->Flags & NM_NET_WORK_FLAGS)) {
                //
                // No more work to do - break out of loop.
                //
                break;
            }

            //
            // More work to do. Resubmit the work item. We do this instead
            // of looping so we don't hog the worker thread. If
            // rescheduling fails, we will loop again in this thread.
            //
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] More work to do for network %1!ws!. Rescheduling "
                "worker thread.\n",
                networkId
                );

            status = NmpScheduleNetworkWorker(network);

            if (status == ERROR_SUCCESS) {
                rescheduled = TRUE;
                break;
            }
        }
    }
    else {
        network->Flags &= ~NM_NET_WORK_FLAGS;
    }

    if (!rescheduled) {
        network->Flags &= ~NM_FLAG_NET_WORKER_RUNNING;
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Worker thread finished processing network %1!ws!.\n",
        networkId
        );

    NmpLockedLeaveApi();

    NmpReleaseLock();

    OmDereferenceObject(network);

    return;

}  // NmpNetworkWorker


VOID
NmpNetworkTimerTick(
    IN DWORD  MsTickInterval
    )
/*++

Routine Description:

    Called by NM periodic timer to decrement network timers.

Arguments:

    MsTickInterval - The number of milliseconds that have passed since
                     the last timer tick.

Return Value:

    None.

Notes:

    Called with NmpLock held.

--*/
{
    if (NmpLockedEnterApi(NmStateOnlinePending)) {
        PLIST_ENTRY   entry;
        PNM_NETWORK   network;


        //
        // Walk thru the list of networks and decrement any running timers.
        //
        for ( entry = NmpNetworkList.Flink;
              entry != &NmpNetworkList;
              entry = entry->Flink
            )
        {
            network = CONTAINING_RECORD(entry, NM_NETWORK, Linkage);

            //
            // Network registration retry timer.
            //
            if (network->RegistrationRetryTimer != 0) {
                if (network->RegistrationRetryTimer > MsTickInterval) {
                    network->RegistrationRetryTimer -= MsTickInterval;
                }
                else {
                    //
                    // The timer has expired. Schedule a worker thread
                    // to register the network.
                    //
                    LPCWSTR   networkId = OmObjectId(network);
                    LPCWSTR   networkName = OmObjectName(network);


                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Registration retry timer expired for "
                        "network %1!ws! (%2!ws!).\n",
                        networkId,
                        networkName
                        );

                    NmpScheduleNetworkRegistration(network);
                }
            }

            //
            // Connectivity report generation timer.
            //
            if (network->ConnectivityReportTimer != 0) {
                if (network->ConnectivityReportTimer > MsTickInterval) {
                    network->ConnectivityReportTimer -= MsTickInterval;
                }
                else {
                    //
                    // The timer has expired. Schedule a worker thread
                    // to deliver a connectivity report.
                    //
                    LPCWSTR   networkId = OmObjectId(network);
                    LPCWSTR   networkName = OmObjectName(network);


                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Connectivity report timer expired for "
                        "network %1!ws! (%2!ws!).\n",
                        networkId,
                        networkName
                        );

                    NmpScheduleNetworkConnectivityReport(network);
                }
            }

            //
            // Network state recalculation timer.
            //
            if (network->StateRecalcTimer != 0) {
                if (network->StateRecalcTimer > MsTickInterval) {
                    network->StateRecalcTimer -= MsTickInterval;
                }
                else {
                    //
                    // The timer has expired. Schedule a worker thread
                    // to recalculate the state of the network.
                    //
                    LPCWSTR   networkId = OmObjectId(network);
                    LPCWSTR   networkName = OmObjectName(network);


                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] State recalculation timer expired for "
                        "network %1!ws! (%2!ws!).\n",
                        networkId,
                        networkName
                        );

                    NmpScheduleNetworkStateRecalc(network);
                }
            }

            //
            // Network multicast address renewal timer.
            //
            if (network->McastAddressRenewTimer != 0) {
                if (network->McastAddressRenewTimer > MsTickInterval) {
                    network->McastAddressRenewTimer -= MsTickInterval;
                }
                else {
                    //
                    // The timer has expired. Schedule a worker thread
                    // to renew the network's multicast address.
                    //
                    LPCWSTR   networkId = OmObjectId(network);
                    LPCWSTR   networkName = OmObjectName(network);


                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Multicast address lease renewal timer "
                        "expired for network %1!ws! (%2!ws!).\n",
                        networkId,
                        networkName
                        );

                    NmpScheduleMulticastAddressRenewal(network);
                }
            }

            //
            // Network multicast address release timer.
            //
            if (network->McastAddressReleaseRetryTimer != 0) {
                if (network->McastAddressReleaseRetryTimer > MsTickInterval) {
                    network->McastAddressReleaseRetryTimer -= MsTickInterval;
                }
                else {
                    //
                    // The timer has expired. Schedule a worker thread
                    // to release the network's multicast address.
                    //
                    LPCWSTR   networkId = OmObjectId(network);
                    LPCWSTR   networkName = OmObjectName(network);


                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Multicast address release timer "
                        "expired for network %1!ws! (%2!ws!).\n",
                        networkId,
                        networkName
                        );

                    NmpScheduleMulticastAddressRelease(network);
                }
            }

            //
            // Network failure isolation timer.
            //
            if (network->FailureIsolationTimer != 0) {
                if (network->FailureIsolationTimer > MsTickInterval) {
                    network->FailureIsolationTimer -= MsTickInterval;
                }
                else {
                    //
                    // The timer has expired. Schedule a worker thread
                    // to perform failure isolation on the network.
                    //
                    DWORD     status = ERROR_SUCCESS;
                    LPCWSTR   networkId = OmObjectId(network);
                    LPCWSTR   networkName = OmObjectName(network);


                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Failure isolation timer expired for network "
                        "%1!ws! (%2!ws!).\n",
                        networkId,
                        networkName
                        );

                    if (!NmpIsNetworkWorkerRunning(network)) {
                        status = NmpScheduleNetworkWorker(network);
                    }

                    //
                    // Zero out the timer if we succeeded in scheduling a
                    // worker thread. If we failed, leave the timer value
                    // non-zero and we'll try again on the next tick.
                    //
                    if (status == ERROR_SUCCESS) {
                        network->FailureIsolationTimer = 0;
                        network->Flags |= NM_FLAG_NET_ISOLATE_FAILURE;
                    }
                }
            }            
        }

        NmpLockedLeaveApi();
    }

    return;

} // NmpNetworkTimerTick


VOID
NmpStartNetworkConnectivityReportTimer(
    PNM_NETWORK Network
    )
/*++

Routine Description:

    Starts the connectivity report timer for a network. Connectivity
    reports are delayed in order to aggregate events when a failure
    occurs that affects multiple nodes.

Arguments:

    Network - A pointer to the network for which to start the timer.

Return Value

    None.

Notes:

    Called with NM lock held.

--*/
{
    //
    // Check if the timer is already running.
    //
    if (Network->ConnectivityReportTimer == 0) {
        //
        // Check how many nodes are attached to this network.
        //
        if (Network->InterfaceCount <= 2) {
            //
            // There is no point in waiting to aggregate reports when
            // only two nodes are connected to the network.
            // Just schedule a worker thread to deliver the report.
            //
            NmpScheduleNetworkConnectivityReport(Network);
        }
        else {
            //
            // More than two nodes are connected to this network.
            // Start the timer.
            //
            LPCWSTR   networkId = OmObjectId(Network);
            LPCWSTR   networkName = OmObjectName(Network);

            Network->ConnectivityReportTimer =
                NM_NET_CONNECTIVITY_REPORT_TIMEOUT;

                ClRtlLogPrint(LOG_NOISE,
                    "[NM] Started connectivity report timer (%1!u!ms) for "
                    "network %2!ws! (%3!ws!)\n",
                    Network->ConnectivityReportTimer,
                    networkId,
                    networkName
                    );
        }
    }

    return;

} // NmpStartNetworkConnectivityReportTimer


VOID
NmpStartNetworkStateRecalcTimer(
    PNM_NETWORK  Network,
    DWORD        Timeout
    )
{
    LPCWSTR   networkId = OmObjectId(Network);
    LPCWSTR   networkName = OmObjectName(Network);

    if (Network->StateRecalcTimer == 0) {
        Network->StateRecalcTimer = Timeout;

        ClRtlLogPrint(LOG_NOISE,
            "[NM] Started state recalculation timer (%1!u!ms) for "
            "network %2!ws! (%3!ws!)\n",
            Network->StateRecalcTimer,
            networkId,
            networkName
            );
    }

    return;

} // NmpStartNetworkStateRecalcTimer


VOID
NmpStartNetworkFailureIsolationTimer(
    PNM_NETWORK Network,
    DWORD Timeout
    )
{

    if (Network->FailureIsolationTimer == 0) {
        Network->FailureIsolationTimer = Timeout;

        ClRtlLogPrint(LOG_NOISE,
            "[NM] Started failure isolation timer (%1!u!ms) for "
            "network %2!ws! (%3!ws!)\n",
            Network->FailureIsolationTimer,
            OmObjectId(Network),
            OmObjectName(Network)
            );
    }

    return;

} // NmpStartNetworkFailureIsolationTimer


VOID
NmpStartNetworkRegistrationRetryTimer(
    PNM_NETWORK   Network
    )
{
    if (Network->RegistrationRetryTimer == 0) {
        if (Network->RegistrationRetryTimeout == 0) {
            Network->RegistrationRetryTimeout =
                NM_NET_MIN_REGISTRATION_RETRY_TIMEOUT;
        }
        else {
            //
            // Exponential backoff
            //
            Network->RegistrationRetryTimeout *= 2;

            if ( Network->RegistrationRetryTimeout >
                 NM_NET_MAX_REGISTRATION_RETRY_TIMEOUT
               )
            {
                Network->RegistrationRetryTimeout =
                    NM_NET_MAX_REGISTRATION_RETRY_TIMEOUT;
            }
        }

        Network->RegistrationRetryTimer = Network->RegistrationRetryTimeout;

        ClRtlLogPrint(LOG_NOISE,
            "[NM] Started registration retry timer (%1!u!ms) for "
            "network %2!ws! (%3!ws!)\n",
            Network->RegistrationRetryTimer,
            OmObjectId(Network),
            OmObjectName(Network)
            );
    }

    return;

} // NmpStartNetworkRegistrationRetryTimer


VOID
NmpScheduleNetworkConnectivityReport(
    PNM_NETWORK   Network
    )
/*++

Routine Description:

    Schedules a worker thread to deliver a connectivity report to
    the leader node for the specified network. Called when the
    ConnectivityReport timer expires for a network. Also called
    directly in some cases.

Arguments:

    A pointer to the network object for which to generate a report.

Return Value:

    None.

Notes:

    This routine is called with the NM lock held.

--*/
{
    DWORD status = ERROR_SUCCESS;

    //
    // Check if a worker thread is already scheduled.
    //
    if (!NmpIsConnectivityReportWorkerRunning) {
        status = NmpScheduleConnectivityReportWorker();
    }

    if (status == ERROR_SUCCESS) {
        //
        // We succeeded in scheduling a worker thread. Stop the
        // ConnectivityReport timer and set the work flag to generate
        // a report.
        //
        Network->ConnectivityReportTimer = 0;
        Network->Flags |= NM_FLAG_NET_REPORT_CONNECTIVITY;
        NmpNeedConnectivityReport = TRUE;
    }
    else {
        //
        // We failed to schedule a worker thread. Set the
        // ConnecivityReport timer to expire on the next tick, so we
        // can try again.
        //
        Network->ConnectivityReportTimer = 1;
    }

    return;

}  // NmpScheduleNetworkConnectivityReport


VOID
NmpScheduleNetworkStateRecalc(
    PNM_NETWORK   Network
    )
/*++

Routine Description:

    Schedules a worker thread to recalculate the state of the
    specified network and all of the network's interface. A network
    state recalculation can be triggered by the arrival of a connectivity
    report, the joining/death of a node, or a network role change.

Arguments:

    A pointer to the network object whose state is to be recalculated.

Return Value:

    None.

Notes:

    This routine is called with the NM lock held.

--*/
{
    DWORD     status = ERROR_SUCCESS;

    //
    // Check if a worker thread is already scheduled to
    // service this network.
    //
    if (!NmpIsNetworkWorkerRunning(Network)) {
        status = NmpScheduleNetworkWorker(Network);
    }

    if (status == ERROR_SUCCESS) {
        //
        // We succeeded in scheduling a worker thread. Stop the
        // StateRecalc timer and set the state recalculation work flag.
        //
        Network->StateRecalcTimer = 0;
        Network->Flags |= NM_FLAG_NET_RECALC_STATE;
    }
    else {
        //
        // We failed to schedule a worker thread. Set the StateRecalc
        // timer to expire on the next tick, so we can try again.
        //
        Network->ConnectivityReportTimer = 1;
    }

    return;

} // NmpScheduleNetworkStateRecalc


VOID
NmpScheduleNetworkRegistration(
    PNM_NETWORK   Network
    )
/*++

Routine Description:

    Schedules a worker thread to register a network with the cluster
    transport.

Arguments:

    A pointer to the network to register.

Return Value:

    None.

Notes:

    This routine is called with the NM lock held.

--*/
{
    DWORD     status = ERROR_SUCCESS;

    //
    // Check if a worker thread is already scheduled to
    // service this network.
    //
    if (!NmpIsNetworkWorkerRunning(Network)) {
        status = NmpScheduleNetworkWorker(Network);
    }

    if (status == ERROR_SUCCESS) {
        //
        // We succeeded in scheduling a worker thread. Stop the
        // retry timer and set the registration work flag.
        //
        Network->RegistrationRetryTimer = 0;
        Network->Flags |= NM_FLAG_NET_NEED_TO_REGISTER;
    }
    else {
        //
        // We failed to schedule a worker thread. Set the retry
        // timer to expire on the next tick, so we can try again.
        //
        Network->RegistrationRetryTimer = 1;
    }

    return;

} // NmpScheduleNetworkRegistration


DWORD
NmpScheduleConnectivityReportWorker(
    VOID
    )
/*++

Routine Description:

    Schedule a worker thread to deliver network connectivity reports.

Arguments:

    None.

Return Value:

    A Win32 status code.

Notes:

    Called with the NM global lock held.

--*/
{
    DWORD     status;


    ClRtlInitializeWorkItem(
        &NmpConnectivityReportWorkItem,
        NmpConnectivityReportWorker,
        NULL
        );

    status = ClRtlPostItemWorkQueue(
                 CsDelayedWorkQueue,
                 &NmpConnectivityReportWorkItem,
                 0,
                 0
                 );

    if (status == ERROR_SUCCESS) {
        NmpActiveThreadCount++;
        NmpIsConnectivityReportWorkerRunning = TRUE;

        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Scheduled network connectivity report worker thread.\n"
            );
    }
    else {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to schedule network connectivity report worker "
            "thread, status %1!u!\n",
            status
            );
    }

    return(status);

}  // NmpScheduleConnectivityReportWorker


DWORD
NmpScheduleNetworkWorker(
    PNM_NETWORK   Network
    )
/*++

Routine Description:

    Schedule a worker thread to service this network

Arguments:

    Network - Pointer to the network for which to schedule a worker thread.

Return Value:

    A Win32 status code.

Notes:

    Called with the NM global lock held.

--*/
{
    DWORD     status;
    LPCWSTR   networkId = OmObjectId(Network);


    ClRtlInitializeWorkItem(
        &(Network->WorkItem),
        NmpNetworkWorker,
        (PVOID) Network
        );

    status = ClRtlPostItemWorkQueue(
                 CsDelayedWorkQueue,
                 &(Network->WorkItem),
                 0,
                 0
                 );

    if (status == ERROR_SUCCESS) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Scheduled worker thread to service network %1!ws!.\n",
            networkId
            );

        NmpActiveThreadCount++;
        Network->Flags |= NM_FLAG_NET_WORKER_RUNNING;
        OmReferenceObject(Network);
    }
    else {
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Failed to schedule worker thread to service network "
            "%1!ws!, status %2!u!\n",
            networkId,
            status
            );
    }

    return(status);

} // NmpScheduleNetworkWorker


DWORD
NmpReportNetworkConnectivity(
    IN PNM_NETWORK    Network
    )
/*+

Notes:

    Called with the NmpLock held.
    May be called by asynchronous worker threads.

--*/
{
    DWORD                    status = ERROR_SUCCESS;
    LPCWSTR                  networkId = OmObjectId(Network);


    //
    // Since this routine is called by asynchronous worker threads,
    // check if the report is still valid.
    //
    if (NmpIsNetworkRegistered(Network)) {
        PNM_CONNECTIVITY_VECTOR  vector = Network->ConnectivityVector;
        PNM_INTERFACE            localInterface = Network->LocalInterface;

        //
        // Record the information in our local data structures.
        //
        ClRtlLogPrint(LOG_UNUSUAL, 
            "[NM] Updating local connectivity info for network %1!ws!.\n",
            networkId
            );

        NmpProcessInterfaceConnectivityReport(
            localInterface,
            vector
            );

        if (NmpLeaderNodeId != NmLocalNodeId) {
            //
            // Send the report to the leader via RPC.
            //
            PNM_CONNECTIVITY_VECTOR  tmpVector;
            DWORD                    vectorSize;
            LPCWSTR                  localInterfaceId =
                                         OmObjectId(localInterface);

            //
            // Allocate a temporary connectivity vector, since the
            // one in the network object can be resized during the
            // RPC call.
            //
            vectorSize = sizeof(NM_CONNECTIVITY_VECTOR) +
                         ((vector->EntryCount - 1) * sizeof(NM_STATE_ENTRY));

            tmpVector = LocalAlloc(LMEM_FIXED, vectorSize);

            if (tmpVector != NULL) {
                CopyMemory(tmpVector, vector, vectorSize);

                OmReferenceObject(Network);
                OmReferenceObject(localInterface);

                if (NM_NODE_UP(NmLocalNode) && (NmpState == NmStateOnline)) {
                    //
                    // This node is fully operational. Send the report
                    // directly to the leader.
                    //
                    PNM_NODE            node = NmpIdArray[NmpLeaderNodeId];
                    RPC_BINDING_HANDLE  rpcBinding = node->ReportRpcBinding;

                    OmReferenceObject(node);

                    status = NmpReportInterfaceConnectivity(
                                 rpcBinding,
                                 (LPWSTR) localInterfaceId,
                                 tmpVector,
                                 (LPWSTR) networkId
                                 );

                    OmDereferenceObject(node);
                }
                else if (CsJoinSponsorBinding != NULL) {
                    //
                    // This node is joining. Forward the report to the
                    // sponsor.
                    //
                    ClRtlLogPrint(LOG_UNUSUAL, 
                        "[NM] Reporting connectivity to sponsor for "
                        "network %1!ws!.\n",
                        networkId
                        );

                    NmpReleaseLock();

                    status = NmRpcReportJoinerInterfaceConnectivity(
                                 CsJoinSponsorBinding,
                                 NmpJoinSequence,
                                 NmLocalNodeIdString,
                                 (LPWSTR) localInterfaceId,
                                 tmpVector
                                 );

                    NmpAcquireLock();
                }
                else {
                    //
                    // This node must be shutting down
                    //
                    CL_ASSERT(NmpState == NmStateOfflinePending);
                    status = ERROR_SUCCESS;
                }

                if (status != ERROR_SUCCESS) {
                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Failed to report connectivity for network "
                        "%1!ws!, status %2!u!.\n",
                        networkId,
                        status
                        );
                }

                LocalFree(tmpVector);

                OmDereferenceObject(localInterface);
                OmDereferenceObject(Network);
            }
            else {
                status = ERROR_NOT_ENOUGH_MEMORY;
            }
        }
    }

    return(status);

} // NmpReportNetworkConnectivity


VOID
NmpUpdateNetworkConnectivityForDownNode(
    PNM_NODE  Node
    )
/*++

Notes:

   Called with NmpLock held.

--*/
{
    PLIST_ENTRY              entry;
    PNM_NETWORK              network;
    LPCWSTR                  networkId;
    PNM_INTERFACE            netInterface;
    DWORD                    entryCount;
    DWORD                    i;
    PNM_CONNECTIVITY_MATRIX  matrixEntry;


    ClRtlLogPrint(LOG_NOISE,
        "[NM] Cleaning up network and interface states for dead node %1!u!\n",
        Node->NodeId
        );

    //
    // Walk the dead node's interface list and clean up the network and
    // interface states.
    //
    for (entry = Node->InterfaceList.Flink;
         entry != &(Node->InterfaceList);
         entry = entry->Flink
        )
    {
        netInterface = CONTAINING_RECORD(
                           entry,
                           NM_INTERFACE,
                           NodeLinkage
                           );

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

        ClRtlLogPrint(LOG_NOISE,
            "[NM] Cleaning up state of network %1!ws!\n",
            networkId
            );

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

        //
        // If the local node is attached to the network, schedule a
        // connectivity report to the new leader.
        //
        if (NmpIsNetworkRegistered(network)) {
            NmpScheduleNetworkConnectivityReport(network);
        }

        //
        // If the local node is the (possibly new) leader, schedule
        // a state update. We explicitly enable this timer here in case
        // there are no active nodes attached to the network.
        //
        if (NmpLeaderNodeId == NmLocalNodeId) {
            NmpStartNetworkStateRecalcTimer(
                network,
                NM_NET_STATE_RECALC_TIMEOUT_AFTER_REGROUP
                );
        }
    }

    return;

}  // NmpUpdateNetworkConnectivityForDownNode


VOID
NmpFreeNetworkStateEnum(
    PNM_NETWORK_STATE_ENUM   NetworkStateEnum
    )
{
    PNM_NETWORK_STATE_INFO  networkStateInfo;
    DWORD                   i;


    for (i=0; i<NetworkStateEnum->NetworkCount; i++) {
        networkStateInfo = &(NetworkStateEnum->NetworkList[i]);

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

    MIDL_user_free(NetworkStateEnum);

    return;

}  // NmpFreeNetworkStateEnum


/////////////////////////////////////////////////////////////////////////////
//
// Database management routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCreateNetworkDefinition(
    IN PNM_NETWORK_INFO     NetworkInfo,
    IN HLOCALXSACTION       Xaction
    )
/*++

Routine Description:

    Creates a new network definition in the cluster database.

Arguments:

    NetworkInfo - A pointer to the information structure describing the
                  network to create.

Return Value:

    ERROR_SUCCESS if successful.
    A Win32 error code otherwise.

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


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

    networkKey = DmLocalCreateKey(
                     Xaction,
                     DmNetworksKey,
                     NetworkInfo->Id,
                     0,
                     KEY_WRITE,
                     NULL,
                     &disposition
                     );

    if (networkKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to create network key, status %1!u!\n",
            status
            );
        return(status);
    }

    CL_ASSERT(disposition == REG_CREATED_NEW_KEY);

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

    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_NAME,
                 REG_SZ,
                 (CONST BYTE *) NetworkInfo->Name,
                 valueLength
                 );

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

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

    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_DESC,
                 REG_SZ,
                 (CONST BYTE *) NetworkInfo->Description,
                 valueLength
                 );

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

    //
    // Write the role value for this network
    //
    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_ROLE,
                 REG_DWORD,
                 (CONST BYTE *) &(NetworkInfo->Role),
                 sizeof(DWORD)
                 );

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

    //
    // Write the priority value for this network
    //
    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_PRIORITY,
                 REG_DWORD,
                 (CONST BYTE *) &(NetworkInfo->Priority),
                 sizeof(DWORD)
                 );

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

    //
    // Write the transport value for this network
    //
    valueLength = (wcslen(NetworkInfo->Transport) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_TRANSPORT,
                 REG_SZ,
                 (CONST BYTE *) NetworkInfo->Transport,
                 valueLength
                 );

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

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

    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_ADDRESS,
                 REG_SZ,
                 (CONST BYTE *) NetworkInfo->Address,
                 valueLength
                 );

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

    //
    // Write the address mask value for this network
    //
    valueLength = (wcslen(NetworkInfo->AddressMask) + 1) * sizeof(WCHAR);

    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_ADDRESS_MASK,
                 REG_SZ,
                 (CONST BYTE *) NetworkInfo->AddressMask,
                 valueLength
                 );

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

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

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

    return(status);

}  // NmpCreateNetworkDefinition


DWORD
NmpSetNetworkNameDefinition(
    IN PNM_NETWORK_INFO     NetworkInfo,
    IN HLOCALXSACTION       Xaction
    )

/*++

Routine Description:

    Changes the network name in the local database

Arguments:

    NetworkInfo - A pointer to the information structure describing the
                  network to create.

Return Value:

    ERROR_SUCCESS if successful.
    A Win32 error code otherwise.

--*/
{
    DWORD     status;
    HDMKEY    networkKey = NULL;
    DWORD     disposition;


    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Changing network name database entry for network %1!ws!\n",
        NetworkInfo->Id
        );

    //
    // Open the network's key.
    //
    networkKey = DmOpenKey(DmNetworksKey, NetworkInfo->Id, KEY_WRITE);

    if (networkKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to open network key, status %1!u!\n",
            status
            );
        goto error_exit;
    }

    //
    // Write the name value for this network
    //

    status = DmLocalSetValue(
                 Xaction,
                 networkKey,
                 CLUSREG_NAME_NET_NAME,
                 REG_SZ,
                 (CONST BYTE *) NetworkInfo->Name,
                 NM_WCSLEN( NetworkInfo->Name )
                 );

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

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

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

    return(status);

}  // NmpSetNetworkNameDefinition


DWORD
NmpGetNetworkDefinition(
    IN  LPWSTR            NetworkId,
    OUT PNM_NETWORK_INFO  NetworkInfo
    )
/*++

Routine Description:

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

Arguments:

    NetworkId   - A pointer to a unicode string containing the ID of the
                  network to query.

    NetworkInfo - A pointer to the network info structure to fill in.

Return Value:

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

--*/

{
    DWORD                    status;
    HDMKEY                   networkKey = NULL;
    DWORD                    valueLength, valueSize;
    DWORD                    i;


    ZeroMemory(NetworkInfo, sizeof(NM_NETWORK_INFO));

    //
    // Open the network's key.
    //
    networkKey = DmOpenKey(DmNetworksKey, NetworkId, KEY_READ);

    if (networkKey == NULL) {
        status = GetLastError();
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to open network key, status %1!u!\n",
            status
            );
        goto error_exit;
    }

    //
    // Copy the ID value.
    //
    NetworkInfo->Id = MIDL_user_allocate(NM_WCSLEN(NetworkId));

    if (NetworkInfo->Id == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        goto error_exit;
    }

    wcscpy(NetworkInfo->Id, NetworkId);

    //
    // Read the network's name.
    //
    valueLength = 0;

    status = NmpQueryString(
                 networkKey,
                 CLUSREG_NAME_NET_NAME,
                 REG_SZ,
                 &(NetworkInfo->Name),
                 &valueLength,
                 &valueSize
                 );

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

    //
    // Read the description value.
    //
    valueLength = 0;

    status = NmpQueryString(
                 networkKey,
                 CLUSREG_NAME_NET_DESC,
                 REG_SZ,
                 &(NetworkInfo->Description),
                 &valueLength,
                 &valueSize
                 );

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

    //
    // Read the role value.
    //
    status = DmQueryDword(
                 networkKey,
                 CLUSREG_NAME_NET_ROLE,
                 &(NetworkInfo->Role),
                 NULL
                 );

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

    //
    // Read the priority value.
    //
    status = DmQueryDword(
                 networkKey,
                 CLUSREG_NAME_NET_PRIORITY,
                 &(NetworkInfo->Priority),
                 NULL
                 );

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

    //
    // Read the address value.
    //
    valueLength = 0;

    status = NmpQueryString(
                 networkKey,
                 CLUSREG_NAME_NET_ADDRESS,
                 REG_SZ,
                 &(NetworkInfo->Address),
                 &valueLength,
                 &valueSize
                 );

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

    //
    // Read the address mask.
    //
    valueLength = 0;

    status = NmpQueryString(
                 networkKey,
                 CLUSREG_NAME_NET_ADDRESS_MASK,
                 REG_SZ,
                 &(NetworkInfo->AddressMask),
                 &valueLength,
                 &valueSize
                 );

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

    //
    // Read the transport name.
    //
    valueLength = 0;

    status = NmpQueryString(
                 networkKey,
                 CLUSREG_NAME_NET_TRANSPORT,
                 REG_SZ,
                 &(NetworkInfo->Transport),
                 &valueLength,
                 &valueSize
                 );

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

    CL_ASSERT(status == ERROR_SUCCESS);

error_exit:

    if (status != ERROR_SUCCESS) {
        ClNetFreeNetworkInfo(NetworkInfo);
    }

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

    return(status);

}  // NmpGetNetworkDefinition


DWORD
NmpEnumNetworkDefinitions(
    OUT PNM_NETWORK_ENUM *   NetworkEnum
    )
/*++

Routine Description:

    Reads information about defined cluster networks from the cluster
    database. and builds an enumeration structure to hold the information.

Arguments:

    NetworkEnum -  A pointer to the variable into which to place a pointer to
                   the allocated network enumeration.

Return Value:

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

--*/

{
    DWORD              status;
    PNM_NETWORK_ENUM   networkEnum = NULL;
    PNM_NETWORK_INFO   networkInfo;
    WCHAR              networkId[CS_NETWORK_ID_LENGTH + 1];
    DWORD              i;
    DWORD              valueLength;
    DWORD              numNetworks;
    DWORD              ignored;
    FILETIME           fileTime;


    *NetworkEnum = NULL;

    //
    // First count the number of networks.
    //
    status = DmQueryInfoKey(
                 DmNetworksKey,
                 &numNetworks,
                 &ignored,   // MaxSubKeyLen
                 &ignored,   // Values
                 &ignored,   // MaxValueNameLen
                 &ignored,   // MaxValueLen
                 &ignored,   // lpcbSecurityDescriptor
                 &fileTime
                 );

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

    if (numNetworks == 0) {
        valueLength = sizeof(NM_NETWORK_ENUM);

    }
    else {
        valueLength = sizeof(NM_NETWORK_ENUM) +
                      (sizeof(NM_NETWORK_INFO) * (numNetworks-1));
    }

    valueLength = sizeof(NM_NETWORK_ENUM) +
                  (sizeof(NM_NETWORK_INFO) * (numNetworks-1));

    networkEnum = MIDL_user_allocate(valueLength);

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

    ZeroMemory(networkEnum, valueLength);

    for (i=0; i < numNetworks; i++) {
        networkInfo = &(networkEnum->NetworkList[i]);

        valueLength = sizeof(networkId);

        status = DmEnumKey(
                     DmNetworksKey,
                     i,
                     &(networkId[0]),
                     &valueLength,
                     NULL
                     );

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

        status = NmpGetNetworkDefinition(networkId, networkInfo);

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

        networkEnum->NetworkCount++;
    }

    *NetworkEnum = networkEnum;

    return(ERROR_SUCCESS);


error_exit:

    if (networkEnum != NULL) {
        ClNetFreeNetworkEnum(networkEnum);
    }

    return(status);
}


/////////////////////////////////////////////////////////////////////////////
//
// Object management routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpCreateNetworkObjects(
    IN  PNM_NETWORK_ENUM    NetworkEnum
    )
/*++

Routine Description:

    Processes a network information enumeration and creates network objects.

Arguments:

    NetworkEnum - A pointer to a network information enumeration structure.

Return Value:

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

--*/
{
    DWORD             status = ERROR_SUCCESS;
    PNM_NETWORK_INFO  networkInfo;
    PNM_NETWORK       network;
    DWORD             i;


    for (i=0; i < NetworkEnum->NetworkCount; i++) {
        networkInfo = &(NetworkEnum->NetworkList[i]);

        network = NmpCreateNetworkObject(networkInfo);

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

    return(status);

}  // NmpCreateNetworkObjects


PNM_NETWORK
NmpCreateNetworkObject(
    IN  PNM_NETWORK_INFO   NetworkInfo
    )
/*++

Routine Description:

    Instantiates a cluster network object.

Arguments:

    NetworkInfo - A pointer to a structure describing the network to create.

Return Value:

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

--*/
{
    DWORD           status;
    PNM_NETWORK     network = NULL;
    BOOL            created = FALSE;
    DWORD           i;


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

    //
    // Make sure that an object with the same name doesn't already exist.
    //
    network = OmReferenceObjectById(ObjectTypeNetwork, NetworkInfo->Id);

    if (network != NULL) {
        OmDereferenceObject(network);
        ClRtlLogPrint(LOG_CRITICAL,
            "[NM] A network object named %1!ws! already exists. Cannot "
            "create a new network with the same name.\n",
            NetworkInfo->Id
            );
        SetLastError(ERROR_OBJECT_ALREADY_EXISTS);
        return(NULL);
    }

    //
    // Ensure that the IP (sub)network is unique in the cluster. Two
    // nodes can race to create a new network in some cases.
    //
    network = NmpReferenceNetworkByAddress(
                  NetworkInfo->Address
                  );

    if (network != NULL) {
        OmDereferenceObject(network);
        ClRtlLogPrint(LOG_CRITICAL,
            "[NM] A network object already exists for IP network %1!ws!. "
            "Cannot create a new network with the same address.\n",
            NetworkInfo->Address
            );
        SetLastError(ERROR_OBJECT_ALREADY_EXISTS);
        return(NULL);
    }

    //
    // Create a network object.
    //
    network = OmCreateObject(
                 ObjectTypeNetwork,
                 NetworkInfo->Id,
                 NetworkInfo->Name,
                 &created
                 );

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

    CL_ASSERT(created == TRUE);

    //
    // Initialize the network object
    //
    ZeroMemory(network, sizeof(NM_NETWORK));

    network->ShortId = InterlockedIncrement(&NmpNextNetworkShortId);
    network->State = ClusterNetworkUnavailable;
    network->Role = NetworkInfo->Role;
    network->Priority = NetworkInfo->Priority;
    network->Description = NetworkInfo->Description;
    NetworkInfo->Description = NULL;
    network->Transport = NetworkInfo->Transport;
    NetworkInfo->Transport = NULL;
    network->Address = NetworkInfo->Address;
    NetworkInfo->Address = NULL;
    network->AddressMask = NetworkInfo->AddressMask;
    NetworkInfo->AddressMask = NULL;
    InitializeListHead(&(network->InterfaceList));
    InitializeListHead(&(network->McastAddressReleaseList));

    //
    // Allocate an initial connectivity vector.
    // Note that we get one vector entry as part of
    // the NM_CONNECTIVITY_VECTOR structure.
    //
#define NM_INITIAL_VECTOR_SIZE   2

    network->ConnectivityVector = LocalAlloc(
                                      LMEM_FIXED,
                                      ( sizeof(NM_CONNECTIVITY_VECTOR) +
                                        ( ((NM_INITIAL_VECTOR_SIZE) - 1) *
                                          sizeof(NM_STATE_ENTRY)
                                        )
                                      ));

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

    network->ConnectivityVector->EntryCount = NM_INITIAL_VECTOR_SIZE;

    FillMemory(
        &(network->ConnectivityVector->Data[0]),
        NM_INITIAL_VECTOR_SIZE * sizeof(NM_STATE_ENTRY),
        (UCHAR) ClusterNetInterfaceStateUnknown
        );

    //
    // Allocate a state work vector
    //
    network->StateWorkVector = LocalAlloc(
                                   LMEM_FIXED,
                                   (NM_INITIAL_VECTOR_SIZE) *
                                     sizeof(NM_STATE_WORK_ENTRY)
                                   );

    if (network->StateWorkVector == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory for state work vector\n"
            );
        goto error_exit;
    }

    //
    // Initialize the state work vector
    //
    for (i=0; i<NM_INITIAL_VECTOR_SIZE; i++) {
        network->StateWorkVector[i].State =
            (NM_STATE_ENTRY) ClusterNetInterfaceStateUnknown;
    }

    //
    // Put a reference on the object for the caller.
    //
    OmReferenceObject(network);

    NmpAcquireLock();

    //
    // Allocate the corresponding connectivity matrix
    //
    network->ConnectivityMatrix =
        LocalAlloc(
            LMEM_FIXED,
            NM_SIZEOF_CONNECTIVITY_MATRIX(NM_INITIAL_VECTOR_SIZE)
            );

    if (network->ConnectivityMatrix == NULL) {
        status = ERROR_NOT_ENOUGH_MEMORY;
        NmpReleaseLock();
        OmDereferenceObject(network);
        ClRtlLogPrint(LOG_CRITICAL, 
            "[NM] Failed to allocate memory for connectivity matrix\n"
            );
        goto error_exit;
    }

    //
    // Initialize the matrix
    //
    FillMemory(
        network->ConnectivityMatrix,
        NM_SIZEOF_CONNECTIVITY_MATRIX(NM_INITIAL_VECTOR_SIZE),
        (UCHAR) ClusterNetInterfaceStateUnknown
        );

    //
    // Make the network object available.
    //
    InsertTailList(&NmpNetworkList, &(network->Linkage));
    NmpNetworkCount++;

    if (NmpIsNetworkForInternalUse(network)) {
        NmpInsertInternalNetwork(network);
        NmpInternalNetworkCount++;
    }

    if (NmpIsNetworkForClientAccess(network)) {
        NmpClientNetworkCount++;
    }

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

    NmpReleaseLock();

    return(network);

error_exit:

    if (network != NULL) {
        NmpAcquireLock();
        NmpDeleteNetworkObject(network, FALSE);
        NmpReleaseLock();
    }

    SetLastError(status);

    return(NULL);

}  // NmpCreateNetworkObject



DWORD
NmpGetNetworkObjectInfo(
    IN  PNM_NETWORK        Network,
    OUT PNM_NETWORK_INFO   NetworkInfo
    )
/*++

Routine Description:

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

Arguments:

    Network     - A pointer to the network object to query.

    NetworkInfo - A pointer to the structure to fill in with network
                    information.

Return Value:

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

Notes:

    Called with NmpLock held.

--*/

{
    DWORD      status = ERROR_NOT_ENOUGH_MEMORY;
    LPWSTR     tmpString = NULL;
    LPWSTR     networkId = (LPWSTR) OmObjectId(Network);
    LPWSTR     networkName = (LPWSTR) OmObjectName(Network);


    ZeroMemory(NetworkInfo, sizeof(NM_NETWORK_INFO));

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

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

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

    NetworkInfo->Role = Network->Role;
    NetworkInfo->Priority = Network->Priority;

    tmpString = MIDL_user_allocate(NM_WCSLEN(Network->Transport));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, Network->Transport);
    NetworkInfo->Transport = tmpString;

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

    tmpString = MIDL_user_allocate(NM_WCSLEN(Network->AddressMask));
    if (tmpString == NULL) {
        goto error_exit;
    }
    wcscpy(tmpString, Network->AddressMask);
    NetworkInfo->AddressMask = tmpString;

    return(ERROR_SUCCESS);

error_exit:

    ClNetFreeNetworkInfo(NetworkInfo);

    return(status);

}  // NmpGetNetworkObjectInfo


VOID
NmpDeleteNetworkObject(
    IN PNM_NETWORK  Network,
    IN BOOLEAN      IssueEvent
    )
/*++

Routine Description:

    Deletes a cluster network object.

Arguments:

    Network - A pointer to the network object to delete.

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

Return Value:

    None.

Notes:

    Called with NM global lock held.

--*/
{
    DWORD           status;
    PLIST_ENTRY     entry;
    LPWSTR          networkId = (LPWSTR) OmObjectId(Network);
    BOOLEAN         wasInternalNetwork = FALSE;


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

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Deleting object for network %1!ws!.\n",
        networkId
        );

    CL_ASSERT(IsListEmpty(&(Network->InterfaceList)));

    Network->Flags |= NM_FLAG_DELETE_PENDING;

    //
    // Remove from the object lists
    //
    if (NM_OM_INSERTED(Network)) {
        status = OmRemoveObject(Network);
        CL_ASSERT(status == ERROR_SUCCESS);

        Network->Flags &= ~NM_FLAG_OM_INSERTED;

        RemoveEntryList(&(Network->Linkage));
        CL_ASSERT(NmpNetworkCount > 0);
        NmpNetworkCount--;

        if (NmpIsNetworkForInternalUse(Network)) {
            RemoveEntryList(&(Network->InternalLinkage));
            CL_ASSERT(NmpInternalNetworkCount > 0);
            NmpInternalNetworkCount--;
            wasInternalNetwork = TRUE;
        }

        if (NmpIsNetworkForClientAccess(Network)) {
            CL_ASSERT(NmpClientNetworkCount > 0);
            NmpClientNetworkCount--;
        }
    }

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

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

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

    InsertTailList(&NmpDeletedNetworkList, &(Network->Linkage));

    if (NmpIsNetworkEnabledForUse(Network)) {
        //
        // Deregister the network from the cluster transport
        //
        NmpDeregisterNetwork(Network);
    }

    //
    // Issue an event if needed
    //
    if (IssueEvent) {
        ClRtlLogPrint(LOG_NOISE, 
            "[NM] Issuing network deleted event for network %1!ws!.\n",
            networkId
            );

        ClusterEvent(CLUSTER_EVENT_NETWORK_DELETED, Network);

        //
        // Issue a cluster property change event if this network was
        // used for internal communication. The network priority list
        // was changed.
        //
        if (wasInternalNetwork) {
            NmpIssueClusterPropertyChangeEvent();
        }
    }

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

    return;

}  // NmpDeleteNetworkObject


BOOL
NmpDestroyNetworkObject(
    PNM_NETWORK  Network
    )
{
    DWORD  status;


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

    CL_ASSERT(NM_DELETE_PENDING(Network));
    CL_ASSERT(!NM_OM_INSERTED(Network));
    CL_ASSERT(Network->InterfaceCount == 0);

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

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

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

    RemoveEntryList(&(Network->Linkage));

    NM_FREE_OBJECT_FIELD(Network, Description);
    NM_FREE_OBJECT_FIELD(Network, Transport);
    NM_FREE_OBJECT_FIELD(Network, Address);
    NM_FREE_OBJECT_FIELD(Network, AddressMask);

    if (Network->ConnectivityVector != NULL) {
        LocalFree(Network->ConnectivityVector);
        Network->ConnectivityVector = NULL;
    }

    if (Network->StateWorkVector != NULL) {
        LocalFree(Network->StateWorkVector);
        Network->StateWorkVector = NULL;
    }

    if (Network->ConnectivityMatrix != NULL) {
        LocalFree(Network->ConnectivityMatrix);
        Network->ConnectivityMatrix = NULL;
    }

    NM_MIDL_FREE_OBJECT_FIELD(Network, MulticastAddress);
    NM_MIDL_FREE_OBJECT_FIELD(Network, MulticastKey);
    NM_MIDL_FREE_OBJECT_FIELD(Network, MulticastKeySalt);
    NM_MIDL_FREE_OBJECT_FIELD(Network, MulticastLeaseServer);
    NM_MIDL_FREE_OBJECT_FIELD(Network, MulticastLeaseRequestId.ClientUID);

    NmpFreeMulticastAddressReleaseList(Network);

    return(TRUE);

}  // NmpDestroyNetworkObject


DWORD
NmpEnumNetworkObjects(
    OUT PNM_NETWORK_ENUM *   NetworkEnum
    )
/*++

Routine Description:

    Reads information about defined cluster networks from the cluster
    objects and builds an enumeration structure to hold the information.

Arguments:

    NetworkEnum -  A pointer to the variable into which to place a pointer to
                   the allocated network 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_NETWORK_ENUM   networkEnum = NULL;
    DWORD              i;
    DWORD              valueLength;
    PLIST_ENTRY        entry;
    PNM_NETWORK        network;


    *NetworkEnum = NULL;

    if (NmpNetworkCount == 0) {
        valueLength = sizeof(NM_NETWORK_ENUM);

    }
    else {
        valueLength = sizeof(NM_NETWORK_ENUM) +
                      (sizeof(NM_NETWORK_INFO) * (NmpNetworkCount - 1));
    }

    networkEnum = MIDL_user_allocate(valueLength);

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

    ZeroMemory(networkEnum, valueLength);

    for (entry = NmpNetworkList.Flink, i=0;
         entry != &NmpNetworkList;
         entry = entry->Flink, i++
        )
    {
        network = CONTAINING_RECORD(entry, NM_NETWORK, Linkage);

        status = NmpGetNetworkObjectInfo(
                     network,
                     &(networkEnum->NetworkList[i])
                     );

        if (status != ERROR_SUCCESS) {
            ClNetFreeNetworkEnum(networkEnum);
            return(status);
        }
    }

    networkEnum->NetworkCount = NmpNetworkCount;
    *NetworkEnum = networkEnum;
    networkEnum = NULL;

    return(ERROR_SUCCESS);

}  // NmpEnumNetworkObjects


DWORD
NmpEnumNetworkObjectStates(
    OUT PNM_NETWORK_STATE_ENUM *  NetworkStateEnum
    )
/*++

Routine Description:

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

Arguments:

    NetworkStateEnum -  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_NETWORK_STATE_ENUM     networkStateEnum = NULL;
    PNM_NETWORK_STATE_INFO     networkStateInfo;
    DWORD                      i;
    DWORD                      valueLength;
    PLIST_ENTRY                entry;
    PNM_NETWORK                network;
    LPWSTR                     networkId;


    *NetworkStateEnum = NULL;

    if (NmpNetworkCount == 0) {
        valueLength = sizeof(NM_NETWORK_STATE_ENUM);
    }
    else {
        valueLength =
            sizeof(NM_NETWORK_STATE_ENUM) +
            (sizeof(NM_NETWORK_STATE_INFO) * (NmpNetworkCount - 1));
    }

    networkStateEnum = MIDL_user_allocate(valueLength);

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

    ZeroMemory(networkStateEnum, valueLength);

    for (entry = NmpNetworkList.Flink, i=0;
         entry != &NmpNetworkList;
         entry = entry->Flink, i++
        )
    {
        network = CONTAINING_RECORD(entry, NM_NETWORK, Linkage);
        networkId = (LPWSTR) OmObjectId(network);
        networkStateInfo = &(networkStateEnum->NetworkList[i]);

        networkStateInfo->State = network->State;

        networkStateInfo->Id = MIDL_user_allocate(NM_WCSLEN(networkId));

        if (networkStateInfo->Id == NULL) {
            NmpFreeNetworkStateEnum(networkStateEnum);
            return(ERROR_NOT_ENOUGH_MEMORY);
        }

        lstrcpyW(networkStateInfo->Id, networkId);
    }

    networkStateEnum->NetworkCount = NmpNetworkCount;
    *NetworkStateEnum = networkStateEnum;

    return(ERROR_SUCCESS);

}  // NmpEnumNetworkObjectStates


/////////////////////////////////////////////////////////////////////////////
//
// Miscellaneous routines
//
/////////////////////////////////////////////////////////////////////////////
DWORD
NmpRegisterNetwork(
    IN PNM_NETWORK   Network,
    IN BOOLEAN       RetryOnFailure
    )
/*++

Routine Description:

    Registers a network and the associated interfaces with the
    cluster transport and brings the network online.

Arguments:

    Network - A pointer to the network to register.

Return Value:

    ERROR_SUCCESS if successful.
    A Win32 error code otherwise.

Notes:

    Called with the NmpLock held.

--*/
{
    PLIST_ENTRY     entry;
    PNM_INTERFACE   netInterface;
    DWORD           status = ERROR_SUCCESS;
    DWORD           tempStatus;
    PVOID           tdiAddress = NULL;
    ULONG           tdiAddressLength = 0;
    LPWSTR          networkId = (LPWSTR) OmObjectId(Network);
    PVOID           tdiAddressInfo = NULL;
    ULONG           tdiAddressInfoLength = 0;
    DWORD           responseLength;
    PNM_INTERFACE   localInterface = Network->LocalInterface;
    BOOLEAN         restricted = FALSE;
    BOOLEAN         registered = FALSE;


    if (Network->LocalInterface != NULL) {
        if (!NmpIsNetworkRegistered(Network)) {
            //
            // Register the network
            //
            ClRtlLogPrint(LOG_NOISE, 
                "[NM] Registering network %1!ws! (%2!ws!) with cluster "
                "transport.\n",
                networkId,
                OmObjectName(Network)
                );

            if (!NmpIsNetworkForInternalUse(Network)) {
                restricted = TRUE;
            }

            status = ClusnetRegisterNetwork(
                         NmClusnetHandle,
                         Network->ShortId,
                         Network->Priority,
                         restricted
                         );

            if (status == ERROR_SUCCESS) {
                registered = TRUE;

                //
                // Bring the network online.
                //
                ClRtlLogPrint(LOG_NOISE, 
                    "[NM] Bringing network %1!ws! online.\n",
                    networkId
                    );

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

                if (status == ERROR_SUCCESS) {
                    ClRtlQueryTcpipInformation(
                        NULL,
                        NULL,
                        &tdiAddressInfoLength
                        );

                    tdiAddressInfo = LocalAlloc(
                                         LMEM_FIXED,
                                         tdiAddressInfoLength
                                         );

                    if (tdiAddressInfo != NULL) {
                        responseLength = tdiAddressInfoLength;

                        status = ClusnetOnlineNetwork(
                                     NmClusnetHandle,
                                     Network->ShortId,
                                     L"\\Device\\Udp",
                                     tdiAddress,
                                     tdiAddressLength,
                                     localInterface->AdapterId,
                                     tdiAddressInfo,
                                     &responseLength
                                     );

                        if (status != ERROR_SUCCESS) {
                            ClRtlLogPrint(LOG_CRITICAL, 
                                "[NM] Cluster transport failed to bring "
                                "network %1!ws! online, status %2!u!.\n",
                                networkId,
                                status
                                );
                        }
                        else {
                            CL_ASSERT(responseLength == tdiAddressInfoLength);
                        }

                        LocalFree(tdiAddressInfo);
                    }
                    else {
                        status = ERROR_NOT_ENOUGH_MEMORY;
                        ClRtlLogPrint(LOG_UNUSUAL, 
                            "[NM] Failed to allocate memory to register "
                            "network %1!ws! with cluster transport.\n",
                            networkId
                            );
                    }

                    LocalFree(tdiAddress);
                }
                else {
                    ClRtlLogPrint(LOG_CRITICAL, 
                        "[NM] Failed to build address to register "
                        "network %1!ws! withh cluster transport, "
                        "status %2!u!.\n",
                        networkId,
                        status
                        );
                }
            }
            else {
                ClRtlLogPrint(LOG_CRITICAL, 
                    "[NM] Failed to register network %1!ws! with cluster "
                    "transport, status %2!u!.\n",
                    networkId,
                    status
                    );
            }

            if (status == ERROR_SUCCESS) {
                Network->Flags |= NM_FLAG_NET_REGISTERED;
                Network->RegistrationRetryTimeout = 0;
            }
            else {
                WCHAR  string[16];

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

                CsLogEvent2(
                    LOG_UNUSUAL,
                    NM_EVENT_REGISTER_NETWORK_FAILED,
                    OmObjectName(Network),
                    string
                    );

                if (registered) {
                    NmpDeregisterNetwork(Network);
                }

                //
                // 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);
            }
        }

        //
        // Register the network's interfaces.
        //
        for (entry = Network->InterfaceList.Flink;
             entry != &(Network->InterfaceList);
             entry = entry->Flink
            )
        {
            netInterface = CONTAINING_RECORD(
                               entry,
                               NM_INTERFACE,
                               NetworkLinkage
                               );

            if (!NmpIsInterfaceRegistered(netInterface)) {
                tempStatus = NmpRegisterInterface(
                                 netInterface,
                                 RetryOnFailure
                                 );

                if (tempStatus != ERROR_SUCCESS) {
                    status = tempStatus;
                }
            }
        }
    }


    return(status);

}  // NmpRegisterNetwork


VOID
NmpDeregisterNetwork(
    IN  PNM_NETWORK   Network
    )
/*++

Routine Description:

    Deregisters a network and the associated interfaces from the
    cluster transport.

Arguments:

    Network - A pointer to the network to deregister.

Return Value:

    None.

Notes:

    Called with the NmpLock held.

--*/
{
    DWORD           status;
    PNM_INTERFACE   netInterface;
    PLIST_ENTRY     entry;


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

    status = ClusnetDeregisterNetwork(
                 NmClusnetHandle,
                 Network->ShortId
                 );

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

    //
    // Mark all of the network's interfaces as deregistered.
    //
    for (entry = Network->InterfaceList.Flink;
         entry != &(Network->InterfaceList);
         entry = entry->Flink
        )
    {
        netInterface = CONTAINING_RECORD(entry, NM_INTERFACE, NetworkLinkage);

        netInterface->Flags &= ~NM_FLAG_IF_REGISTERED;
    }

    //
    // Mark the network as deregistered
    //
    Network->Flags &= ~NM_FLAG_NET_REGISTERED;

    return;

} // NmpDeregisterNetwork


VOID
NmpInsertInternalNetwork(
    PNM_NETWORK   Network
    )
/*++

Routine Description:

    Inserts a network into internal networks list based on its priority.

Arguments:

    Network - A pointer to the network object to be inserted.

Return Value:

    None.

Notes:

    Called with the NmpLock held.

--*/
{
    PLIST_ENTRY    entry;
    PNM_NETWORK    network;


    //
    // Maintain internal networks in highest to lowest
    // (numerically lowest to highest) priority order.
    //
    for (entry = NmpInternalNetworkList.Flink;
         entry != &NmpInternalNetworkList;
         entry = entry->Flink
        )
    {
        network = CONTAINING_RECORD(entry, NM_NETWORK, InternalLinkage);

        if (Network->Priority < network->Priority) {
            break;
        }
    }

    //
    // Insert the network in front of this entry.
    //
    InsertTailList(entry, &(Network->InternalLinkage));

    return;

}  // NmpInsertNetwork


DWORD
NmpValidateNetworkRoleChange(
    PNM_NETWORK            Network,
    CLUSTER_NETWORK_ROLE   NewRole
    )
{
    if ( !(NewRole & ClusterNetworkRoleInternalUse) &&
         NmpIsNetworkForInternalUse(Network)
       )
    {
        //
        // This change eliminates an internal network. This is only
        // legal if we would still have at least one internal network
        // between all active nodes.
        //
        if ((NmpInternalNetworkCount < 2) || !NmpVerifyConnectivity(Network)) {
            return(ERROR_CLUSTER_LAST_INTERNAL_NETWORK);
        }
    }

    if ( ( !(NewRole & ClusterNetworkRoleClientAccess) )
         &&
         NmpIsNetworkForClientAccess(Network)
       )
    {
        BOOL  hasDependents;

        //
        // This change eliminates a public network. This is only
        // legal if there are no dependencies (IP address resources) on
        // the network.
        //
        NmpReleaseLock();

        hasDependents = FmCheckNetworkDependency(OmObjectId(Network));

        NmpAcquireLock();

        if (hasDependents) {
            return(ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS);
        }
    }

    return(ERROR_SUCCESS);

}  // NmpValidateNetworkRoleChange


BOOLEAN
NmpVerifyNodeConnectivity(
    PNM_NODE      Node1,
    PNM_NODE      Node2,
    PNM_NETWORK   ExcludedNetwork
    )
{
    PLIST_ENTRY      ifEntry1, ifEntry2;
    PNM_NETWORK      network;
    PNM_INTERFACE    interface1, interface2;


    for (ifEntry1 = Node1->InterfaceList.Flink;
         ifEntry1 != &(Node1->InterfaceList);
         ifEntry1 = ifEntry1->Flink
        )
    {
        interface1 = CONTAINING_RECORD(
                         ifEntry1,
                         NM_INTERFACE,
                         NodeLinkage
                         );

        network = interface1->Network;

        if ( (network != ExcludedNetwork) &&
             NmpIsNetworkForInternalUse(network)
           )
        {
            for (ifEntry2 = Node2->InterfaceList.Flink;
                 ifEntry2 != &(Node2->InterfaceList);
                 ifEntry2 = ifEntry2->Flink
                )
            {
                interface2 = CONTAINING_RECORD(
                                 ifEntry2,
                                 NM_INTERFACE,
                                 NodeLinkage
                                 );

                if (interface2->Network == interface1->Network) {
                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] nodes %1!u! & %2!u! are connected over "
                        "network %3!ws!\n",
                        Node1->NodeId,
                        Node2->NodeId,
                        OmObjectId(interface1->Network)
                        );
                    return(TRUE);
                }
            }
        }
    }

    ClRtlLogPrint(LOG_NOISE, 
        "[NM] Nodes %1!u! & %2!u! are not connected over any internal "
        "networks\n",
        Node1->NodeId,
        Node2->NodeId
        );

    return(FALSE);

}  // NmpVerifyNodeConnectivity


BOOLEAN
NmpVerifyConnectivity(
    PNM_NETWORK   ExcludedNetwork
    )
{
    PLIST_ENTRY    node1Entry, node2Entry;
    PNM_NODE       node1, node2;


    ClRtlLogPrint(LOG_NOISE, "[NM] Verifying connectivity\n");

    for (node1Entry = NmpNodeList.Flink;
         node1Entry != &NmpNodeList;
         node1Entry = node1Entry->Flink
        )
    {
        node1 = CONTAINING_RECORD(
                         node1Entry,
                         NM_NODE,
                         Linkage
                         );

        if (NM_NODE_UP(node1)) {
            for (node2Entry = node1->Linkage.Flink;
                 node2Entry != &NmpNodeList;
                 node2Entry = node2Entry->Flink
                )
            {
                node2 = CONTAINING_RECORD(
                                 node2Entry,
                                 NM_NODE,
                                 Linkage
                                 );

                if (NM_NODE_UP(node2)) {
                    ClRtlLogPrint(LOG_NOISE, 
                        "[NM] Verifying nodes %1!u! & %2!u! are connected\n",
                        node1->NodeId,
                        node2->NodeId
                        );

                    if (!NmpVerifyNodeConnectivity(
                            node1,
                            node2,
                            ExcludedNetwork
                            )
                        )
                    {
                        return(FALSE);
                    }
                }
            }
        }
    }

    return(TRUE);

}  // NmpVerifyConnectivity


VOID
NmpIssueClusterPropertyChangeEvent(
    VOID
    )
{
    DWORD      status;
    DWORD      valueLength = 0;
    DWORD      valueSize = 0;
    PWCHAR     clusterName = NULL;


    //
    // The notification API expects a
    // cluster name to be associated with this event.
    //
    status = NmpQueryString(
                 DmClusterParametersKey,
                 CLUSREG_NAME_CLUS_NAME,
                 REG_SZ,
                 &clusterName,
                 &valueLength,
                 &valueSize
                 );

    if (status == ERROR_SUCCESS) {
        ClusterEventEx(
            CLUSTER_EVENT_PROPERTY_CHANGE,
            EP_CONTEXT_VALID | EP_FREE_CONTEXT,
            clusterName
            );

        //
        // clusterName will be freed by the event processing code.
        //
    }
    else {
        ClRtlLogPrint(LOG_WARNING, 
            "[NM] Failed to issue cluster property change event, "
            "status %1!u!.\n",
            status
            );
    }

    return;

}  // NmpIssueClusterPropertyChangeEvent


DWORD
NmpMarshallObjectInfo(
    IN  const PRESUTIL_PROPERTY_ITEM PropertyTable,
    IN  PVOID                        ObjectInfo,
    OUT PVOID *                      PropertyList,
    OUT LPDWORD                      PropertyListSize
    )
{
    DWORD   status;
    PVOID   propertyList = NULL;
    DWORD   propertyListSize = 0;
    DWORD   bytesReturned = 0;
    DWORD   bytesRequired = 0;


    status = ClRtlPropertyListFromParameterBlock(
                 PropertyTable,
                 NULL,
                 &propertyListSize,
                 (LPBYTE) ObjectInfo,
                 &bytesReturned,
                 &bytesRequired
                 );

    if (status != ERROR_MORE_DATA) {
        CL_ASSERT(status != ERROR_SUCCESS);
        return(status);
    }

    CL_ASSERT(bytesRequired > 0);

    propertyList = MIDL_user_allocate(bytesRequired);

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

    propertyListSize = bytesRequired;

    status = ClRtlPropertyListFromParameterBlock(
                 PropertyTable,
                 propertyList,
                 &propertyListSize,
                 (LPBYTE) ObjectInfo,
                 &bytesReturned,
                 &bytesRequired
                 );

    if (status != ERROR_SUCCESS) {
        CL_ASSERT(status != ERROR_MORE_DATA);
        MIDL_user_free(propertyList);
    }
    else {
        CL_ASSERT(bytesReturned == propertyListSize);
        *PropertyList = propertyList;
        *PropertyListSize = bytesReturned;
    }

    return(status);

}  // NmpMarshallObjectInfo


VOID
NmpReferenceNetwork(
    PNM_NETWORK  Network
    )
{
    OmReferenceObject(Network);

    return;
}

VOID
NmpDereferenceNetwork(
    PNM_NETWORK  Network
    )
{
    OmDereferenceObject(Network);

    return;
}


PNM_NETWORK
NmpReferenceNetworkByAddress(
    LPWSTR  NetworkAddress
    )
/*++

Notes:

    Called with NM lock held.

--*/
{
    PNM_NETWORK   network;
    PLIST_ENTRY   entry;


    for ( entry = NmpNetworkList.Flink;
          entry != &NmpNetworkList;
          entry = entry->Flink
        )
    {
        network = CONTAINING_RECORD(entry, NM_NETWORK, Linkage);

        if (lstrcmpW(network->Address, NetworkAddress) == 0) {
            NmpReferenceNetwork(network);

            return(network);
        }
    }

    return(NULL);

} // NmpReferenceNetworkByAddress


BOOLEAN
NmpCheckForNetwork(
    VOID
    )
/*++

Routine Description:

    Checks whether at least one network on this node configured for MSCS
    has media sense.

Arguments:

    None.

Return Value:

    TRUE if a viable network is found. FALSE otherwise.

Notes:

    Called with and returns with no locks held.

--*/
{
    PLIST_ENTRY      entry;
    PNM_NETWORK      network;
    BOOLEAN          haveNetwork = FALSE;

    NmpAcquireLock();

    for (entry = NmpNetworkList.Flink;
         entry != &NmpNetworkList;
         entry = entry->Flink
        )
    {
        network = CONTAINING_RECORD(
                      entry,
                      NM_NETWORK,
                      Linkage
                      );

        // if a network's local interface is disabled, it is not
        // considered a viable network. in this case the 
        // LocalInterface field is NULL.
        if (network->LocalInterface != NULL) {
            if (NmpVerifyLocalInterfaceConnected(network->LocalInterface)) {

                haveNetwork = TRUE;
                break;

            } else {

                ClRtlLogPrint(LOG_UNUSUAL, 
                    "[NM] Network adapter %1!ws! with address %2!ws! "
                    "reported not connected.\n",
                    network->LocalInterface->AdapterId,
                    network->Address
                    );
            }
        }
    }

    NmpReleaseLock();

    if (!haveNetwork) {
        SetLastError(ERROR_NETWORK_NOT_AVAILABLE);
    }

    return(haveNetwork);

} // NmpCheckForNetwork