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 usto bind to an adapter. We go ahead and set up a binding.An OPEN_CONTEXT structure is allocated to keep state aboutthis binding.Arguments:pStatus - place to return bind statusBindContext - handle to use with NdisCompleteBindAdapterDeviceName - adapter to bind toSystemSpecific1 - used to access protocol-specific registrykey for this bindingSystemSpecific2 - unusedReturn 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);
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部