9.4.4 NPF_tap函数的数据包过滤部分

当一个数据包从网络上到达时,函数NPF_tap被NDIS库调用。这是由DriverEntry函数在如下所示代码中设置的:


ProtocolChar.ReceiveHandler=NPF_tap;


NPF_tap函数的原型如下:


NDIS_STATUS NPF_tap(

IN NDIS_HANDLE ProtocolBindingContext,

IN NDIS_HANDLE MacReceiveContext,

IN PVOID HeaderBuffer,

IN UINT HeaderBufferSize,

IN PVOID LookAheadBuffer,

IN UINT LookaheadBufferSize,

IN UINT PacketSize

);


上述函数中,参数ProtocolBindingContext指向上下文的区块,指向一个OPEN_INSTANCE结构体,标识数据包去往哪个NPF实例;参数MacReceiveContext用于标识生成请求的底层NIC驱动句柄,当使用NdisTransferData函数从NIC驱动传输数据包时必须使用该参数;参数HeaderBuffer指向NIC驱动中包含数据包头的内存;参数HeaderBufferSize为数据包头的字节长度;参数LookaheadBuffer指向NIC驱动中所包含的对NPF有用的数据包的内存(前视缓冲区)。该值并不一定需要与数据包的实际大小一致,可能只有数据包的一部分数据,余下部分可以通过NDIS库的NdisTransferData函数获取;参数LookaheadBufferSize为前视缓冲区(中可用数据)的字节长度;参数PacketSize为进入NIC的数据包总的大小,不包括数据包头。

NPF_tap函数返回操作的状态,可通过DDK的ntstatus.h文件了解状态的详细定义。

NPF_tap函数是NPF中最重要的、最复杂的函数之一:它执行过滤,运行统计模块(如果该实例是处于统计模式),收集时间戳,搬移缓冲区中的数据包。NPF_tap函数是NPF中唯一的一个对每个进入的数据包都执行相应操作(包含过滤器)的函数,因此它被仔细地优化过。

NPF_tap函数中与执行数据包过滤的相关代码实现如下:


NdisAcquireSpinLock(&Open->MachineLock);

/*检查前视缓冲区是否紧接着MAC头。

*如果数据紧接着该头(比如,只有一个缓冲区),

*则在该数据包上执行一个正常的bpf_filte函数。

*如果有两个分开的缓冲区(这可能在LAN仿真或类似的情况下出现),

*则需要执行bpf_filter_with_2_buffers函数

*/

if((UINT)((PUCHAR)LookaheadBuffer-(PUCHAR)HeaderBuffer)

!=HeaderBufferSize)

{//两个缓冲区的情况

fres=bpf_flter_with_2_buffers(

(struct bpf_insn*)(Open->bpfprogram),

HeaderBuffer,

LookaheadBuffer,

HeaderBufferSize,

PacketSize+HeaderBufferSize,

LookaheadBufferSize+HeaderBufferSize,

&Open->mem_ex,

&Open->tme,

&G_Start_Time);

}

else

{//一个缓冲区的情况

if(Open->Filter!=NULL)

{//JIT编译器生成的过滤器只在x86(32位)的平台上有效

if(Open->bpfprogram!=NULL)

{//调用BPFtoX86函数

//(已为JIT编译器编译为直接可执行的机器码)执行过滤

fres=Open->Filter->Function(HeaderBuffer,

PacketSize+HeaderBufferSize,

LookaheadBufferSize+HeaderBufferSize);

}

else

fres=-1;

}

else

{//无JIT编译器生成的过滤器,则调用BPF过滤器

fres=bpf_flter(

(struct bpf_insn*)(Open->bpfprogram),

HeaderBuffer,

PacketSize+HeaderBufferSize,

LookaheadBufferSize+HeaderBufferSize,

&Open->mem_ex,

&Open->tme,

&G_Start_Time);

}

}

NdisReleaseSpinLock(&Open->MachineLock);

/*

*根据所处的模式(统计/监视/转储/捕获),

*与过滤器的结果fres决定后续的处理

*/

……


从下面的代码片段中可以看出,NPF_tap函数对Open->Filter->Function的调用实际上是调用BPFtoX86函数,来执行过滤操作。


NTSTATUS NPF_IoControl(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

{

case BIOCSETF:

……

/JIT编译器只被x86(32位)架构支持/

//创建新的JIT过滤器函数

if(!IsExtendedFilter)

{

if((Open->Filter=BPF_jitter(NewBpfProgram,cnt))==NULL)

{

……

}

……

}

typedef struct JIT_BPF_Filter{

BPF_flter_function Function;

PINT mem;

}JIT_BPF_Filter;

JIT_BPF_FilterBPF_jitter(struct bpf_insnfp,INT nins)

{

JIT_BPF_Filter*Filter;

……

if((Filter->Function=BPFtoX86(fp,nins,Filter->mem))==NULL)

{

……

}

……

}

NDIS_STATUS NPF_tap(

IN NDIS_HANDLE ProtocolBindingContext,

IN NDIS_HANDLE MacReceiveContext,

IN PVOID HeaderBuffer,

IN UINT HeaderBufferSize,

IN PVOID LookaheadBuffer,

IN UINT LookaheadBufferSize,

IN UINT PacketSize)

{

……

fres=Open->Filter->Function(HeaderBuffer,

PacketSize+HeaderBufferSize,

LookaheadBufferSize+HeaderBufferSize);

……

}


上述代码中,bpf_filter_with_2_buffers函数用于处理当NDIS库给NPF_tap函数传递的数据包放置在两个缓冲区中而不是一个的情况。该函数比BPFtoX86或bpf_filter函数慢,但其可正常工作于数据包的MAC头与数据在两个不同的缓冲区中的情况。该函数原型如下:


u_int bpf_flter_with_2_buffers(register struct bpf_insn*pc,

register u_char*p,

register u_char*pd,

register int headersize,

u_int wirelen,

register u_int bufen,

PMEM_TYPE mem_ex,

PTME_CORE tme,

struct time_conv*time_ref);


上述函数中,参数pc为过滤器指令集;参数p指向包含数据包MAC头的内存缓冲区;参数pd指向包含数据包数据的内存缓冲区;参数wirelen为数据包原始长度;参数buflen为数据包当前长度(比如当数据包传递到RAM还没结束时),bpf_filter可以在部分数据包上执行过滤操作;参数mem_ex为扩展内存;参数tme为虚拟的TME协处理器;参数time_ref为TME协处理器时间戳数据所需的数据结构体。

NPF_tap函数返回需要保留的数据包长度,0表示该数据包拒收,-1表示整个数据包必须被保留。