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表示整个数据包必须被保留。