NDIS协议驱动学习三——协议与网卡的绑定

协议与网卡的绑定

协议与网卡之间的绑定和之前章节中设备对象之间的绑定不同

一般来说协议和网卡的绑定不是一对一的,而是一对多的,同一个协议是会在同一台主机的所有网卡生效。当然一张网卡也可以绑定不同的多个协议,但是实际上这是没有任何意义的,因为一般来说一个数据包只会被一个协议处理。

这一节我们的主要学习对象是以下两个回调函数

protocolChar.BindAdapterHandler = NdisProtBindAdapter;
protocolChar.UnbindAdapterHandler = NdisProtUnbindAdapter;

这表示协议的绑定与解绑过程 

NdisProtBindAdapter的实现主要工作有

1.打开上下文的分配和初始化(通俗易懂的理解就是将打开上下文理解为与绑定相关的一些信息,没有这些信息程序会出错)

2.读取配置(寒江将这部分阉割了)

3.将这个打开上下文保存到全局链表,并调用ndisprotCreateBinding完成绑定

以下是《寒江独钓》中的协议驱动绑定代码

 

函数原型

VOID
NdisProtBindAdapter(
    OUT PNDIS_STATUS                pStatus,
    IN NDIS_HANDLE                  BindContext,
    IN PNDIS_STRING                 pDeviceName,
    IN PVOID                        SystemSpecific1,
    IN PVOID                        SystemSpecific2
    );
/*++

Routine Description:

    Protocol Bind Handler entry point called when NDIS wants us
    to bind to an adapter. We go ahead and set up a binding.
    An OPEN_CONTEXT structure is allocated to keep state about
    this binding.

Arguments:

    pStatus - place to return bind status
    BindContext - handle to use with NdisCompleteBindAdapter
    DeviceName - adapter to bind to
    SystemSpecific1 - used to access protocol-specific registry
                 key for this binding
    SystemSpecific2 - unused

Return Value:

    No--*/

然后是一些变量的定义

    PNDISPROT_OPEN_CONTEXT           pOpenContext;//打开上下文结构
    NDIS_STATUS                     Status, ConfigStatus;//状态码
    NDIS_HANDLE                     ConfigHandle;//句柄

    UNREFERENCED_PARAMETER(BindContext);//没用到
    UNREFERENCED_PARAMETER(SystemSpecific2);//没用到

 

    do
    {
        //
        //  Allocate our context for this open.
        //

        // 分配空间给每个打开上下文。所谓打开上下文就是每次绑定,
        // 用户分配的一片空间,用来保存这次绑定相关的信息。这里
        // 用宏NPROT_ALLOC_MEM分配内存是为了调试的方便。实际上本
        // 质是用NdisAllocateMemoryWithTag分配空间。读者如果用
        // ExAllocatePoolWithTag代替也是可行的。只是要注意必须是
        // Nonpaged空间。
        NPROT_ALLOC_MEM(pOpenContext, sizeof(NDISPROT_OPEN_CONTEXT));
        if (pOpenContext == NULL)
        {
            Status = NDIS_STATUS_RESOURCES;
            break;
        }

        // 内存清0。同样用宏。实际上用的NdisZeroMemory。 
        NPROT_ZERO_MEM(pOpenContext, sizeof(NDISPROT_OPEN_CONTEXT));

        // 给这个空间写一个特征数据便于识别判错。
        NPROT_SET_SIGNATURE(pOpenContext, oc);

        // 初始化几个用到的数据成员。锁、读队列、写对队列、包队列
        // 电源打开事件
        NPROT_INIT_LOCK(&pOpenContext->Lock);
        NPROT_INIT_LIST_HEAD(&pOpenContext->PendedReads);
        NPROT_INIT_LIST_HEAD(&pOpenContext->PendedWrites);
        NPROT_INIT_LIST_HEAD(&pOpenContext->RecvPktQueue);
        NPROT_INIT_EVENT(&pOpenContext->PoweredUpEvent);

        //
        //  Start off by assuming that the device below is powered up.
        //

        // 认为开始的时候电源是打开的。
        NPROT_SIGNAL_EVENT(&pOpenContext->PoweredUpEvent);

        //
        //  Determine the platform we are running on.
        //

        // 下面开始检测我们运行在什么平台。首先假定是Win9x.
        // 但是为了去掉多余的部分,实际上我已经去掉了对Win9x
        // 的支持。所以下面这一段已经没有意义了。但是下面的
        // 代码依然有参考价值。实际上是在读取注册表的配置。
        //pOpenContext->bRunningOnWin9x = TRUE;

        //NdisOpenProtocolConfiguration(
        //    &ConfigStatus,
        //    &ConfigHandle,
        //    (PNDIS_STRING)SystemSpecific1);
        //
        //if (ConfigStatus == NDIS_STATUS_SUCCESS)
        //{
        //    PNDIS_CONFIGURATION_PARAMETER   pParameter;
        //    NDIS_STRING                     VersionKey = NDIS_STRING_CONST("Environment");

        //    NdisReadConfiguration(
        //        &ConfigStatus,
        //        &pParameter,
        //        ConfigHandle,
        //        &VersionKey,
        //        NdisParameterInteger);
        //    
        //    if ((ConfigStatus == NDIS_STATUS_SUCCESS) &&
        //        ((pParameter->ParameterType == NdisParameterInteger) ||
        //         (pParameter->ParameterType == NdisParameterHexInteger)))
        //    {
        //        pOpenContext->bRunningOnWin9x =
        //            (pParameter->ParameterData.IntegerData == NdisEnvironmentWindows);
        //    }

        //    NdisCloseConfiguration(ConfigHandle);
        //}
        //给打开上下文增加一个引用计数

        NPROT_REF_OPEN(pOpenContext); 

        //
        //  Add it to the global list.
        //

        // 因为打开上下文已经被分配好。所以这里将这个打开上下文
        // 保存到全局链表里以便日后检索。注意这个操作要加锁。实
         // 际上这里用的就是读者前面学过的自旋锁。
        NPROT_ACQUIRE_LOCK(&Globals.GlobalLock);
        NPROT_INSERT_TAIL_LIST(&Globals.OpenList,
                             &pOpenContext->Link);
        NPROT_RELEASE_LOCK(&Globals.GlobalLock);

        // 正式的绑定过程。
        Status = ndisprotCreateBinding(
                     pOpenContext,
                     (PUCHAR)pDeviceName->Buffer,
                     pDeviceName->Length);
        
        if (Status != NDIS_STATUS_SUCCESS)
        {
            break;
        }
    }
    while (FALSE);

其实这个函数的内容主要是处理上下文,然后调用ndisprotCreateBinding完成绑定。

绑定网卡的主要是在ndisprotCreateBinding这个函数中实现的,而完成一个绑定只需要调用一个NdisOpenAdapter API,这个API就将一个协议绑定到一个网卡上。

这个API的原型如下:

VOID NdisOpenAdapter(PNDIS_STATUSStatus,
PNDIS_STATUSOpenErrorStatus,
PNDIS_HANDLENdisBindingHandle,
PUINTSelectedMediumIndex,
PNDIS_MEDIUMMediumArray,
UINTMediumArraySize,
NDIS_HANDLENdisProtocolHandle,
NDIS_HANDLEProtocolBindingContext,
PNDIS_STRINGAdapterName,
UINTOpenOptions,
PSTRINGAddressingInformation);

关于这个API的介绍可以参考MSDN

ndisprotCreateBinding中调用这个API如下:

        NdisOpenAdapter(
            &Status,
            &OpenErrorCode,
            &pOpenContext->BindingHandle,
            &SelectedMediumIndex,
            &MediumArray[0],
            sizeof(MediumArray) / sizeof(NDIS_MEDIUM),
            Globals.NdisProtocolHandle,
            (NDIS_HANDLE)pOpenContext,
            &pOpenContext->DeviceName,
            0,
            NULL);

解决绑定竞争

ndisprotCreateBinding主要工作

1.设法防止多线程竞争

2.分配和初始化这次绑定的相关资源

3.获得网卡的一些参数

我们知道防止多线程竞争的主要方式是线程同步,而在内核中线程同步用的最多的就是自旋锁。为什么在内核中自旋锁用的最多?可以参考下面的链接

https://blog.csdn.net/daaikuaichuan/article/details/82950711

使用自旋锁处理线程同步,采用以下方式:

        // 获得锁。为什么这里要用锁?这是因为只有对空闲的打开上下文
        NPROT_ACQUIRE_LOCK(&pOpenContext->Lock);

        ..............//添加要保护的代码
        // 释放锁。
        NPROT_RELEASE_LOCK(&pOpenContext->Lock);

其中NPROT_ACQUIRE_LOCKNPROT_RELEASE_LOCK

分别对应NdisAcquireSpinLock(_pLock)、 NdisReleaseSpinLock(_pLock)这两个API

 

分配接收和发送的包池与缓冲池

包池是一组预先已经分配好的”包描述符“,缓冲池是一组已经分配好的”包缓冲区描述符“

为什么要有包池和缓冲池的概念呢?这是因为在NDIS中每一个以太网包是用一个包描述符来描述,并且包内容用包缓冲区描述符来描述的

在发送和接收包的时候,包不是立即接收和发送的,它们存放在缓冲区中排队等待接收和发送,那么这个时候我们可以自己创建两个包池

来容纳发送和接收的包,这样就没有必要多次分配包描述符和包缓冲区描述符

        // 分配包池。用来做发送缓冲区,容纳将要发送出去的包。
        //这个函数的作用其实就是分配堆内存
        NdisAllocatePacketPoolEx(
            &Status,
            &pOpenContext->SendPacketPool,
            MIN_SEND_PACKET_POOL_SIZE,
            MAX_SEND_PACKET_POOL_SIZE - MIN_SEND_PACKET_POOL_SIZE,
            sizeof(NPROT_SEND_PACKET_RSVD));
       
        //若不成功就直接返回
        if (Status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP(DL_WARN, ("CreateBinding: failed to alloc"
                    " send packet pool: %x\n", Status));
            break;
        }

        // 分配包池,用来容纳接收包
        NdisAllocatePacketPoolEx(
            &Status,
            &pOpenContext->RecvPacketPool,
            MIN_RECV_PACKET_POOL_SIZE,
            MAX_RECV_PACKET_POOL_SIZE - MIN_RECV_PACKET_POOL_SIZE,
            sizeof(NPROT_RECV_PACKET_RSVD));
       
        if (Status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP(DL_WARN, ("CreateBinding: failed to alloc"
                    " recv packet pool: %x\n", Status));
            break;
        }

 

OID请求的发送和请求完成回调

OIDs是NDIS Object Identifiers的简称,使用这个东东的主要目的是我们需要获得显卡的MAC地址以及最大帧长等MAC层、物理层相关的信息,这些信息对于发送包至关重要。

  // 获得下面网卡的Mac地址
        Status = ndisprotDoRequest(
                    pOpenContext,
                    NdisRequestQueryInformation,
                    OID_802_3_CURRENT_ADDRESS,
                    &pOpenContext->CurrentAddress[0],
                    NPROT_MAC_ADDR_LEN,
                    &BytesProcessed
                    );
        
        if (Status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP(DL_WARN, ("CreateBinding: qry current address failed: %x\n",
                    Status));
            break;
        }
        
        // 获得网卡选项
        Status = ndisprotDoRequest(
                    pOpenContext,
                    NdisRequestQueryInformation,
                    OID_GEN_MAC_OPTIONS,
                    &pOpenContext->MacOptions,
                    sizeof(pOpenContext->MacOptions),
                    &BytesProcessed
                    );

        if (Status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP(DL_WARN, ("CreateBinding: qry MAC options failed: %x\n",
                    Status));
            break;
        }

        // 获得最大帧长
        Status = ndisprotDoRequest(
                    pOpenContext,
                    NdisRequestQueryInformation,
                    OID_GEN_MAXIMUM_FRAME_SIZE,
                    &pOpenContext->MaxFrameSize,
                    sizeof(pOpenContext->MaxFrameSize),
                    &BytesProcessed
                    );

        if (Status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP(DL_WARN, ("CreateBinding: qry max frame failed: %x\n",
                    Status));
            break;
        }

        // 获得下层连接状态。
        Status = ndisprotDoRequest(
                    pOpenContext,
                    NdisRequestQueryInformation,
                    OID_GEN_MEDIA_CONNECT_STATUS,
                    &GenericUlong,
                    sizeof(GenericUlong),
                    &BytesProcessed
                    );

        if (Status != NDIS_STATUS_SUCCESS)
        {
            DEBUGP(DL_WARN, ("CreateBinding: qry media connect status failed: %x\n",
                    Status));
            break;
        }

ndisprotDoRequest这个函数是协议驱动中作者自己封装的一个函数,底层架构其实是调用的NdisRequest这个函数

VOID NdisRequest(
  _Out_ PNDIS_STATUS  Status,
  _In_  NDIS_HANDLE   NdisBindingHandle,
  _In_  PNDIS_REQUEST NdisRequest
);

所有的OID请求都通过它来发送,返回的NTSTATUS如果是未决,则请求完成时会调用xxxComplete函数

NDIS_STATUS
ndisprotDoRequest(
    IN PNDISPROT_OPEN_CONTEXT pOpenContext,
    IN NDIS_REQUEST_TYPE    RequestType,
    IN NDIS_OID Oid,
    IN PVOID InformationBuffer,
    IN ULONG InformationBufferLength,
    OUT PULONG pBytesProcessed
    )
{
    NDISPROT_REQUEST ReqContext;
    PNDIS_REQUEST pNdisRequest = &ReqContext.Request;
    NDIS_STATUS Status;

    // 初始化一个事件。这个事件会在请求完成函数中被设置,
    // 以便通知请求完成了。
    NPROT_INIT_EVENT(&ReqContext.ReqEvent);

    // 请求的类型。如果只是查询信息,只要用NdisRequestQueryInformation
    // 就可以了。
    pNdisRequest->RequestType = RequestType;

    // 根据不同的请求类型,填写OID和输入输出缓冲区。
    switch (RequestType)
    {
        case NdisRequestQueryInformation:
            pNdisRequest->DATA.QUERY_INFORMATION.Oid = Oid;
            pNdisRequest->DATA.QUERY_INFORMATION.InformationBuffer =
                                    InformationBuffer;
            pNdisRequest->DATA.QUERY_INFORMATION.InformationBufferLength =
                                    InformationBufferLength;
            break;

        case NdisRequestSetInformation:
            pNdisRequest->DATA.SET_INFORMATION.Oid = Oid;
            pNdisRequest->DATA.SET_INFORMATION.InformationBuffer =
                                    InformationBuffer;
            pNdisRequest->DATA.SET_INFORMATION.InformationBufferLength =
                                    InformationBufferLength;
            break;

        default:
            NPROT_ASSERT(FALSE);
            break;
    }

    // 发送请求
    NdisRequest(&Status,
                pOpenContext->BindingHandle,
                pNdisRequest);
    
    // 如果是未决,则等待事件。这个事件会在完成函数中设置。
    if (Status == NDIS_STATUS_PENDING)
    {
        NPROT_WAIT_EVENT(&ReqContext.ReqEvent, 0);
        Status = ReqContext.Status;
    }

    // 如果成功了的话...
    if (Status == NDIS_STATUS_SUCCESS)
    {
        // 获得结果的长度。这个结果的长度是实际需要的长度。可
        // 能比我们实际提供的长度要长。
        *pBytesProcessed = (RequestType == NdisRequestQueryInformation)?
                            pNdisRequest->DATA.QUERY_INFORMATION.BytesWritten:
                            pNdisRequest->DATA.SET_INFORMATION.BytesRead;

        // 如果结果长度比实际上我们提供的缓冲区要长,那么就简
        // 单的设置为输入参数中缓冲区的最大长度。
        if (*pBytesProcessed > InformationBufferLength)
        {
            *pBytesProcessed = InformationBufferLength;
        }
    }
    return (Status);
}

完成函数

VOID
NdisProtRequestComplete(
    IN NDIS_HANDLE                  ProtocolBindingContext,
    IN PNDIS_REQUEST                pNdisRequest,
    IN NDIS_STATUS                  Status
    )
{
    PNDISPROT_OPEN_CONTEXT       pOpenContext;
    PNDISPROT_REQUEST            pReqContext;

    // 这两句话起验证的作用,确保输入参数ProtocolBindingContext
    // 是合法的。但是对后面的处理没影响。
    pOpenContext = (PNDISPROT_OPEN_CONTEXT)ProtocolBindingContext;
    NPROT_STRUCT_ASSERT(pOpenContext, oc);

    // 从pNdisRequest中得到请求上下文
    pReqContext = CONTAINING_RECORD(pNdisRequest, NDISPROT_REQUEST, Request);

    // 保存结果状态
    pReqContext->Status = Status;

    // 设置事件
    NPROT_SIGNAL_EVENT(&pReqContext->ReqEvent);
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值