/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    cnpsend.c

Abstract:

    Cluster Network Protocol send processing code.

Author:

    Mike Massa (mikemas)           January 24, 1997

Revision History:

    Who         When        What
    --------    --------    ----------------------------------------------
    mikemas     01-24-97    created

Notes:

--*/

#include "precomp.h"
#pragma hdrstop
#include "cnpsend.tmh"

#ifdef ALLOC_PRAGMA

#pragma alloc_text(PAGE, CnpCreateSendRequestPool)

#endif // ALLOC_PRAGMA


//
// Private Utility Functions
//
PCN_RESOURCE
CnpCreateSendRequest(
    IN PVOID   Context
    )
{
    PCNP_SEND_REQUEST_POOL_CONTEXT   context = Context;
    PCNP_SEND_REQUEST                request;
    PCNP_HEADER                      cnpHeader;
    ULONG                            cnpHeaderSize;

    //
    // The CNP header size includes signature data for version 2.
    //
    cnpHeaderSize = sizeof(CNP_HEADER);
    if (context->CnpVersionNumber == 2) {
        cnpHeaderSize += CNP_SIG_LENGTH(CX_SIGNATURE_LENGTH);
    }

    //
    // Allocate a new send request. Include space for the upper protocol
    // and CNP headers.
    //
    request = CnAllocatePool(
                  sizeof(CNP_SEND_REQUEST) + cnpHeaderSize +
                  ((ULONG) context->UpperProtocolHeaderLength) +
                  context->UpperProtocolContextSize
                  );

    if (request != NULL) {
        //
        // Allocate an MDL to describe the CNP and upper transport headers.
        //
        
        // On I64 Context has to be 64 bit aligned, 
        // let's put it before CnpHeader
        if (context->UpperProtocolContextSize > 0) {
            request->UpperProtocolContext = request + 1;
            request->CnpHeader = ( ((PCHAR) request->UpperProtocolContext) +
                                     context->UpperProtocolContextSize );
        } else {
            request->UpperProtocolContext = NULL;
            request->CnpHeader = request + 1;
        }

        request->HeaderMdl = IoAllocateMdl(
                                 request->CnpHeader,
                                 (ULONG) (context->UpperProtocolHeaderLength +
                                          cnpHeaderSize),
                                 FALSE,
                                 FALSE,
                                 NULL
                                 );

        if (request->HeaderMdl != NULL) {
            MmBuildMdlForNonPagedPool(request->HeaderMdl);

            //
            // Finish initializing the request.
            //
            request->UpperProtocolHeader = ( ((PCHAR) request->CnpHeader) +
                                             cnpHeaderSize );

            request->UpperProtocolHeaderLength =
                context->UpperProtocolHeaderLength;

            RtlZeroMemory(
                &(request->TdiSendDatagramInfo),
                sizeof(request->TdiSendDatagramInfo)
                );

            request->McastGroup = NULL;

            //
            // Fill in the constant CNP header values.
            //
            cnpHeader = request->CnpHeader;
            cnpHeader->Version = context->CnpVersionNumber;
            cnpHeader->NextHeader = context->UpperProtocolNumber;

            return((PCN_RESOURCE) request);
        }

        CnFreePool(request);
    }

    return(NULL);

}  // CnpCreateSendRequest


VOID
CnpDeleteSendRequest(
    PCN_RESOURCE  Resource
    )
{
    PCNP_SEND_REQUEST  request = (PCNP_SEND_REQUEST) Resource;

    IoFreeMdl(request->HeaderMdl);
    CnFreePool(request);

    return;

} // CnpDeleteSendRequest


//
// Routines Exported within the Cluster Transport
//
PCN_RESOURCE_POOL
CnpCreateSendRequestPool(
    IN UCHAR  CnpVersionNumber,
    IN UCHAR  UpperProtocolNumber,
    IN USHORT UpperProtocolHeaderSize,
    IN USHORT UpperProtocolContextSize,
    IN USHORT PoolDepth
    )
{
    PCN_RESOURCE_POOL                pool;
    PCNP_SEND_REQUEST_POOL_CONTEXT   context;


    PAGED_CODE();

    CnAssert((0xFFFF - sizeof(CNP_HEADER)) >= UpperProtocolHeaderSize);

    pool = CnAllocatePool(
               sizeof(CN_RESOURCE_POOL) +
                   sizeof(CNP_SEND_REQUEST_POOL_CONTEXT)
               );

    if (pool != NULL) {
        context = (PCNP_SEND_REQUEST_POOL_CONTEXT) (pool + 1);

        context->UpperProtocolNumber = UpperProtocolNumber;
        context->UpperProtocolHeaderLength = UpperProtocolHeaderSize;
        context->UpperProtocolContextSize = UpperProtocolContextSize;
        context->CnpVersionNumber = CnpVersionNumber;

        CnInitializeResourcePool(
                   pool,
                   PoolDepth,
                   CnpCreateSendRequest,
                   context,
                   CnpDeleteSendRequest
                   );
    }

    return(pool);

}  // CnpCreateSendRequestPool



VOID
CnpCompleteSendPacketCommon(
    IN PIRP              Irp,
    IN PCNP_SEND_REQUEST Request,
    IN PMDL              DataMdl
    )
{
    PCNP_NETWORK       network = Request->Network;
    ULONG              bytesSent = (ULONG)Irp->IoStatus.Information;
    NTSTATUS           status = Irp->IoStatus.Status;
    PCNP_HEADER        cnpHeader = Request->CnpHeader;


    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    if (NT_SUCCESS(status)) {
        //
        // Subtract the CNP header from the count of bytes sent.
        //
        if (bytesSent >= sizeof(CNP_HEADER)) {
            bytesSent -= sizeof(CNP_HEADER);
        }
        else {
            CnAssert(FALSE);
            bytesSent = 0;
        }

        //
        // If CNP signed the message, subtract the signature
        // data from the count of bytes sent.
        //
        if (cnpHeader->Version == CNP_VERSION_MULTICAST) {
            CNP_SIGNATURE UNALIGNED * cnpSig;

            cnpSig = (CNP_SIGNATURE UNALIGNED *)(cnpHeader + 1);

            if (bytesSent >= (ULONG)CNP_SIGHDR_LENGTH &&
                bytesSent >= cnpSig->PayloadOffset) {
                bytesSent -= cnpSig->PayloadOffset;
            } else {
                CnAssert(FALSE);
                bytesSent = 0;
            }
        }

        CnTrace(CNP_SEND_DETAIL, CnpTraceSendComplete,
            "[CNP] Send of packet to node %u on net %u complete, "
            "bytes sent %u.",
            cnpHeader->DestinationAddress, // LOGULONG
            network->Id, // LOGULONG
            bytesSent // LOGULONG
            );                
    }
    else {
        //
        // It is possible to reach this path with 
        // status = c0000240 (STATUS_REQUEST_ABORTED) and
        // bytesSent > 0.
        //
        bytesSent = 0;

        CnTrace(CNP_SEND_ERROR, CnpTraceSendFailedBelow,
            "[CNP] Tcpip failed to send packet to node %u on net %u, "
            "data len %u, status %!status!",
            cnpHeader->DestinationAddress, // LOGULONG
            network->Id, // LOGULONG
            cnpHeader->PayloadLength, // LOGUSHORT
            status // LOGSTATUS
            );                
    }

    //
    // Remove the active reference we put on the network.
    //
    CnAcquireLock(&(network->Lock), &(network->Irql));
    CnpActiveDereferenceNetwork(network);

    //
    // Free the TDI address buffer
    //
    CnFreePool(Request->TdiSendDatagramInfo.RemoteAddress);

    //
    // Call the upper protocol's completion routine
    //
    if (Request->CompletionRoutine) {
        (*(Request->CompletionRoutine))(
            status,
            &bytesSent,
            Request,
            DataMdl
            );
    }

    //
    // Update the Information field of the completed IRP to
    // reflect the actual bytes sent (adjusted for the CNP
    // and upper protocol headers).
    //
    Irp->IoStatus.Information = bytesSent;

    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    return;

}  // CnpCompleteSendPacketCommon



NTSTATUS
CnpCompleteSendPacketNewIrp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp,
    IN PVOID          Context
    )
{
    PCNP_SEND_REQUEST  request = Context;
    PIRP               upperIrp = request->UpperProtocolIrp;
    PMDL               dataMdl;
    
    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    //
    // Unlink the data MDL chain from the header MDL.
    //
    CnAssert(Irp->MdlAddress == request->HeaderMdl);
    dataMdl = request->HeaderMdl->Next;
    request->HeaderMdl->Next = NULL;
    Irp->MdlAddress = NULL;

    CnpCompleteSendPacketCommon(Irp, request, dataMdl);

    //
    // Complete the upper-level IRP, if there is one
    //
    if (upperIrp != NULL) {
        
        IF_CNDBG( CN_DEBUG_CNPSEND )
            CNPRINT(("[CNP] CnpCompleteSendPacketNewIrp calling "
                     "CnCompleteRequest for IRP %p with status "
                     "%08x\n",
                     upperIrp, Irp->IoStatus.Status));
        
        CnAcquireCancelSpinLock(&(upperIrp->CancelIrql));
        CnCompletePendingRequest(
            upperIrp,
            Irp->IoStatus.Status,            // status
            (ULONG)Irp->IoStatus.Information // bytes returned
            );
        
        //
        // The IoCancelSpinLock was released by the completion routine.
        //
    }

    //
    // Free the new IRP
    //
    IoFreeIrp(Irp);

    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    return(STATUS_MORE_PROCESSING_REQUIRED);

}  // CnpCompleteSendPacketNewIrp



NTSTATUS
CnpCompleteSendPacketReuseIrp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP           Irp,
    IN PVOID          Context
    )
{
    PCNP_SEND_REQUEST  request = Context;
    PMDL               dataMdl;
    
    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    //
    // Unlink the data MDL chain from the header MDL.
    //
    CnAssert(Irp->MdlAddress == request->HeaderMdl);
    dataMdl = request->HeaderMdl->Next;
    request->HeaderMdl->Next = NULL;

    //
    // Restore the requestor mode of the upper protocol IRP.
    //
    Irp->RequestorMode = request->UpperProtocolIrpMode;

    //
    // Restore the MDL of the upper protocol IRP.
    //
    Irp->MdlAddress = request->UpperProtocolMdl;

    CnpCompleteSendPacketCommon(Irp, request, dataMdl);

    if (Irp->PendingReturned) {
        IoMarkIrpPending(Irp);
    }

    IF_CNDBG( CN_DEBUG_CNPSEND )
        CNPRINT(("[CNP] CnpCompleteSendPacketReuseIrp returning "
                 "IRP %p to I/O Manager\n",
                 Irp));

    CnVerifyCpuLockMask(
        0,                // Required
        0xFFFFFFFF,       // Forbidden
        0                 // Maximum
        );

    return(STATUS_SUCCESS);

}  // CnpCompleteSendPacketReuseIrp



NTSTATUS
CnpSendPacket(
    IN PCNP_SEND_REQUEST    SendRequest,
    IN CL_NODE_ID           DestNodeId,
    IN PMDL                 DataMdl,
    IN USHORT               DataLength,
    IN BOOLEAN              CheckDestState,
    IN CL_NETWORK_ID        NetworkId        OPTIONAL
    )
/*++

Routine Description:

    Main send routine for CNP. Handles unicast and multicast
    sends.
    
--*/
{
    NTSTATUS               status = STATUS_SUCCESS;
    PCNP_HEADER            cnpHeader = SendRequest->CnpHeader;
    PIRP                   upperIrp = SendRequest->UpperProtocolIrp;
    CN_IRQL                tableIrql;
    BOOLEAN                multicast = FALSE;
    CL_NETWORK_ID          networkId = NetworkId;
    CN_IRQL                cancelIrql;
    BOOLEAN                cnComplete = FALSE;
    BOOLEAN                destNodeLocked = FALSE;
    PCNP_NODE              destNode;
    ULONG                  sigDataLen;
    PCNP_INTERFACE         interface;
    PCNP_NETWORK           network;
    BOOLEAN                networkReferenced = FALSE;
    PIRP                   irp;
    PVOID                  addressBuffer = NULL;
    PIO_COMPLETION_ROUTINE compRoutine;
    PDEVICE_OBJECT         targetDeviceObject;
    PFILE_OBJECT           targetFileObject;
    BOOLEAN                mcastGroupReferenced = FALSE;


    CnVerifyCpuLockMask(
        0,                           // Required
        CNP_LOCK_RANGE,              // Forbidden
        CNP_PRECEEDING_LOCK_RANGE    // Maximum
        );

    IF_CNDBG( CN_DEBUG_CNPSEND )
        CNPRINT(("[CNP] CnpSendPacket called with upper IRP %p\n",
                 upperIrp));

    //
    // Make all the tests to see if we can send the packet.
    //

    //
    // Acquire the node table lock to match the destination node id
    // to a node object.
    //
    CnAcquireLock(&CnpNodeTableLock, &tableIrql);

    if (CnpNodeTable == NULL) {
        CnReleaseLock(&CnpNodeTableLock, tableIrql);
        status = STATUS_NETWORK_UNREACHABLE;
        goto error_exit;
    }

    //
    // Fill in the local node ID while we still hold the node table lock.
    //
    CnAssert(CnLocalNodeId != ClusterInvalidNodeId);
    cnpHeader->SourceAddress = CnLocalNodeId;

    //
    // Check first if the destination node id indicates that this is
    // a multicast.
    //
    if (DestNodeId == ClusterAnyNodeId) {

        //
        // This is a multicast. For multicasts, we use the local
        // node in place of the dest node to validate the network
        // and interface.
        //
        multicast = TRUE;
        destNode = CnpLockedFindNode(CnLocalNodeId, tableIrql);
    }

    //
    // Not a multicast. The destination node id must be valid.
    //
    else if (!CnIsValidNodeId(DestNodeId)) {
        CnReleaseLock(&CnpNodeTableLock, tableIrql);
        status = STATUS_INVALID_ADDRESS_COMPONENT;
        goto error_exit;
    }

    //
    // Find the destination node object in the node table.
    //
    else {
        destNode = CnpLockedFindNode(DestNodeId, tableIrql);
    }

    //
    // The NodeTableLock was released. Verify that we know about
    // the destination node.
    //
    if (destNode == NULL) {
        status = STATUS_HOST_UNREACHABLE;
        goto error_exit;
    }

    destNodeLocked = TRUE;

    //
    // CNP multicast messages must be signed.
    //
    if (multicast) {

        CnAssert(((CNP_HEADER UNALIGNED *)(SendRequest->CnpHeader))
                 ->Version = CNP_VERSION_MULTICAST);

        //
        // Sign the data, starting with the upper protocol header
        // and finishing with the data payload.
        // 
        // If we are requesting the current best multicast network,
        // we need to make sure that the mcast group data structure
        // is dereferenced.
        //
        mcastGroupReferenced = (BOOLEAN)(networkId == ClusterAnyNetworkId);

        status = CnpSignMulticastMessage(
                     SendRequest,
                     DataMdl,
                     &networkId,
                     &sigDataLen
                     );
        if (status != STATUS_SUCCESS) {
            mcastGroupReferenced = FALSE;
            goto error_exit;
        }


    } else {
        sigDataLen = 0;
    }

    //
    // Choose the destination interface.
    //
    if (networkId != ClusterAnyNetworkId) {
        
        //
        // we really want to send this packet over the indicated
        // network. walk the node's interface list matching the
        // supplied network id to the interface's network ID and
        // send the packet on that interface
        //
        
        PLIST_ENTRY      entry;
        
        for (entry = destNode->InterfaceList.Flink;
             entry != &(destNode->InterfaceList);
             entry = entry->Flink
             )
            {
                interface = CONTAINING_RECORD(
                                entry,
                                CNP_INTERFACE,
                                NodeLinkage
                                );

                if ( interface->Network->Id == networkId ) {
                    break;
                }
            }

        if ( entry == &destNode->InterfaceList ) {
            //
            // no network object with the specified ID. if 
            // this is the network the sender designated,
            // fail the send.
            //
            status = STATUS_NETWORK_UNREACHABLE;
            goto error_exit;
        }
    } else {
        interface = destNode->CurrentInterface;
    }

    //
    // Verify that we know about the destination interface.
    //
    if (interface == NULL) {
        // No interface for node. Must be down. Note that the
        // HOST_DOWN error code should cause the caller to give
        // up immediately.
        status = STATUS_HOST_DOWN;
        // status = STATUS_HOST_UNREACHABLE;

        goto error_exit;
    }

    //
    // Verify that everything is online. If all looks okay,
    // take an active reference on the network.
    //
    // For unicasts, verify the state of destination interface,
    // node, and intervening network.
    //
    // For multicasts, verify the state of the network and 
    // its multicast capability.
    //
    network = interface->Network;

    if ( (!multicast)
         && 
         ( (interface->State > ClusnetInterfaceStateOfflinePending)
           &&
           (destNode->CommState == ClusnetNodeCommStateOnline)
         )
       )
    {
        //
        // Everything checks out. Reference the network so
        // it can't go offline while we are using it.
        //
        CnAcquireLockAtDpc(&(network->Lock));
        CnAssert(network->State >= ClusnetNetworkStateOfflinePending);
        CnpActiveReferenceNetwork(network);
        CnReleaseLockFromDpc(&(network->Lock));
        networkReferenced = TRUE;

    } else {
        //
        // Either the node is not online or this is a 
        // multicast (in which case we don't bother checking
        // the status of all the nodes). Figure out what to do.
        //
        if (!multicast && CheckDestState) {
            //
            // Caller doesn't want to send to a down node.
            // Bail out. Note that the HOST_DOWN error code
            // should cause the caller to give up immediately.
            //
            status = STATUS_HOST_DOWN;
            // status = STATUS_HOST_UNREACHABLE;

            goto error_exit;
        }

        CnAcquireLockAtDpc(&(network->Lock));

        if (network->State <= ClusnetNetworkStateOfflinePending) {
            //
            // The chosen network is not online.
            // Bail out.
            //
            CnReleaseLockFromDpc(&(network->Lock));
            status = STATUS_HOST_UNREACHABLE;
            goto error_exit;
        }

        //
        // Multicast checks.
        //
        if (multicast) {

            //
            // Verify that the chosen network has been
            // enabled for multicast.
            //
            if (!CnpIsNetworkMulticastCapable(network)) {
                CnReleaseLockFromDpc(&(network->Lock));
                status = STATUS_HOST_UNREACHABLE;
                goto error_exit;
            }
        }

        //
        // The network is online, even if the host isn't.
        // The caller doesn't care. Go for it.
        //
        CnpActiveReferenceNetwork(network);
        CnReleaseLockFromDpc(&(network->Lock));
        networkReferenced = TRUE;
    }

    //
    // Allocate a buffer for the destination address.
    //
    addressBuffer = CnAllocatePool(interface->TdiAddressLength);

    if (addressBuffer == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto error_exit;
    }

    //
    // Fill in the address buffer, and save it in the send
    // request data structure.
    //
    if (multicast) {

        PCNP_MULTICAST_GROUP   mcastGroup = SendRequest->McastGroup;
        
        CnAssert(mcastGroup != NULL);

        CnAssert(
            CnpIsIPv4McastTransportAddress(mcastGroup->McastTdiAddress)
            );
        CnAssert(
            mcastGroup->McastTdiAddressLength == interface->TdiAddressLength
            );

        RtlMoveMemory(
            addressBuffer,
            mcastGroup->McastTdiAddress,
            mcastGroup->McastTdiAddressLength
            );

        SendRequest->TdiSendDatagramInfo.RemoteAddressLength =
            mcastGroup->McastTdiAddressLength;

        if (mcastGroupReferenced) {
            CnpDereferenceMulticastGroup(mcastGroup);
            mcastGroupReferenced = FALSE;
            SendRequest->McastGroup = NULL;
        }

        targetFileObject = network->DatagramFileObject;
        targetDeviceObject = network->DatagramDeviceObject;

    } else {

        CnAssert(mcastGroupReferenced == FALSE);
        
        RtlMoveMemory(
            addressBuffer,
            &(interface->TdiAddress),
            interface->TdiAddressLength
            );

        SendRequest->TdiSendDatagramInfo.RemoteAddressLength =
            interface->TdiAddressLength;

        targetFileObject = network->DatagramFileObject;
        targetDeviceObject = network->DatagramDeviceObject;
    }

    SendRequest->TdiSendDatagramInfo.RemoteAddress =
        addressBuffer;

    //
    // Release the node lock.
    //
    CnReleaseLock(&(destNode->Lock), destNode->Irql);
    destNodeLocked = FALSE;

    //
    // If there is an upper protocol IRP, see 
    // if it has enough stack locations.
    //
    if ( (upperIrp != NULL)
         && 
         (CnpIsIrpStackSufficient(upperIrp, targetDeviceObject))
       ) {

        //
        // We can use the upper protocol IRP.
        //
        irp = upperIrp;
        compRoutine = CnpCompleteSendPacketReuseIrp;

        //
        // Ensure that IRP is marked as a kernel request,
        // but first save the current requestor mode so
        // that it can be restored later.
        //
        SendRequest->UpperProtocolIrpMode = irp->RequestorMode;
        irp->RequestorMode = KernelMode;

        //
        // Save the upper protocol IRP MDL to restore
        // later. This is probably the same as DataMdl,
        // but we don't want to make any assumptions.
        //
        SendRequest->UpperProtocolMdl = irp->MdlAddress;

    } else {

        //
        // We cannot use the upper protocol IRP.
        //
        // If there is an upper protocol IRP, it needs
        // to be marked pending.
        //
        if (upperIrp != NULL) {

            CnAcquireCancelSpinLock(&cancelIrql);

            status = CnMarkRequestPending(
                         upperIrp, 
                         IoGetCurrentIrpStackLocation(upperIrp),
                         NULL
                         );

            CnReleaseCancelSpinLock(cancelIrql);

            if (status == STATUS_CANCELLED) {
                //
                // Bail out
                //
                status = STATUS_INSUFFICIENT_RESOURCES;
                goto error_exit;

            } else {
                //
                // If IoAllocateIrp fails, we need
                // to call CnCompletePendingRequest
                // now that we've called 
                // CnMarkRequestPending.
                //
                cnComplete = TRUE;
            }
        }

        //
        // Allocate a new IRP
        //
        irp = IoAllocateIrp(
                  targetDeviceObject->StackSize,
                  FALSE
                  );
        if (irp == NULL) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            goto error_exit;
        }
    
        //
        // Use the completion routine for having
        // allocated a new IRP
        //
        compRoutine = CnpCompleteSendPacketNewIrp;

        //
        // Fill in IRP fields that are not specific
        // to any one stack location.
        //
        irp->Flags = 0;
        irp->RequestorMode = KernelMode;
        irp->PendingReturned = FALSE;

        irp->UserIosb = NULL;
        irp->UserEvent = NULL;

        irp->Overlay.AsynchronousParameters.UserApcRoutine = NULL;

        irp->AssociatedIrp.SystemBuffer = NULL;
        irp->UserBuffer = NULL;

        irp->Tail.Overlay.Thread = PsGetCurrentThread();
        irp->Tail.Overlay.AuxiliaryBuffer = NULL;
    }

    //
    // Ok, we can finally send the packet.
    //
    SendRequest->Network = network;

    //
    // Link the data MDL chain after the header MDL.
    //
    SendRequest->HeaderMdl->Next = DataMdl;

    //
    // Finish building the CNP header.
    //
    cnpHeader->DestinationAddress = DestNodeId;
    cnpHeader->PayloadLength =
        SendRequest->UpperProtocolHeaderLength + DataLength;

    //
    // Build the next irp stack location.
    //
    TdiBuildSendDatagram(
        irp,
        targetDeviceObject,
        targetFileObject,
        compRoutine,
        SendRequest,
        SendRequest->HeaderMdl,
        cnpHeader->PayloadLength + sizeof(CNP_HEADER) + sigDataLen,
        &(SendRequest->TdiSendDatagramInfo)
        );

    CnTrace(CNP_SEND_DETAIL, CnpTraceSend,
        "[CNP] Sending packet to node %u on net %u, "
        "data len %u",
        cnpHeader->DestinationAddress, // LOGULONG
        network->Id, // LOGULONG
        cnpHeader->PayloadLength // LOGUSHORT
        );         

    //
    // Now send the packet.
    //
    status = IoCallDriver(
                 targetDeviceObject,
                 irp
                 );

    CnVerifyCpuLockMask(
        0,                           // Required
        CNP_LOCK_RANGE,              // Forbidden
        CNP_PRECEEDING_LOCK_RANGE    // Maximum
        );

    return(status);


    //
    // The following code is only executed in an error condition,
    // No send IRP has been submitted to a lower-level driver.
    //

error_exit:

    CnTrace(CNP_SEND_ERROR, CnpTraceSendFailedInternal,
        "[CNP] Failed to send packet to node %u on net %u, "
        "data len %u, status %!status!",
        cnpHeader->DestinationAddress, // LOGULONG
        NetworkId, // LOGULONG
        cnpHeader->PayloadLength, // LOGUSHORT
        status // LOGSTATUS
        );

    if (destNodeLocked) {
        CnReleaseLock(&(destNode->Lock), destNode->Irql);
        destNodeLocked = FALSE;
    }
    
    if (networkReferenced) {
        //
        // Remove the active reference we put on the network.
        //
        CnAcquireLock(&(network->Lock), &(network->Irql));
        CnpActiveDereferenceNetwork(network);
        networkReferenced = FALSE;
    }

    if (mcastGroupReferenced) {
        CnAssert(SendRequest->McastGroup != NULL);
        CnpDereferenceMulticastGroup(SendRequest->McastGroup);
        SendRequest->McastGroup = NULL;
        mcastGroupReferenced = FALSE;
    }

    if (addressBuffer != NULL) {
        CnFreePool(addressBuffer);
    }

    //
    // Call the upper protocol completion routine
    //
    if (SendRequest->CompletionRoutine) {

        ULONG bytesSent = 0;

        (*SendRequest->CompletionRoutine)(
            status,
            &bytesSent,
            SendRequest,
            DataMdl
            );
    }

    //
    // Complete the upper protocol IRP, if there is one
    //
    if (upperIrp) {

        if (cnComplete) {

            //
            // CnMarkRequestPending was called for upperIrp.
            //
            IF_CNDBG( CN_DEBUG_CNPSEND )
                CNPRINT(("[CNP] Calling CnCompletePendingRequest "
                         "for IRP %p with status %08x\n",
                         upperIrp, status));

            CnCompletePendingRequest(upperIrp, status, 0);
        
        } else {
            
            IF_CNDBG( CN_DEBUG_CNPSEND )
                CNPRINT(("[CNP] Completing IRP %p with status %08x\n",
                         upperIrp, status));

            upperIrp->IoStatus.Status = status;
            upperIrp->IoStatus.Information = 0;
            IoCompleteRequest(upperIrp, IO_NO_INCREMENT);
        }        
    }
    
    CnVerifyCpuLockMask(
        0,                           // Required
        CNP_LOCK_RANGE,              // Forbidden
        CNP_PRECEEDING_LOCK_RANGE    // Maximum
        );

    return(status);

} // CnpSendPacket