转载自:wfp网络过滤框架总结(一)

一、基本术语定义

callout:为扩展wfp性能提供的一个功能,由一系列callout function和一个guid key组成,wfp内置了几个callouts。用户可以通过callout drivers自己添加callout。

callout driver:实现一个或者多个callouts的内核驱动,这个驱动通过向filter engine注册callouts,来通知filter engine当计算机处理网络连接或者网络包时回调对应的callout function。

callout function:由callout driver实现的定义一个callout的一个函数。一个callout由三种函数定义组成,nofityFn处理连接或者包的通知,类似一个网络事件。

classifyFn:处理一次分类决策,即拒绝还是放行数据包的操作

flowDeleteFn:处理流的删除,这个函数是可选的。

filter为TCP/IP网络数据定义了一些过滤条件,以及当所有的条件都成立时采取的放行还是决绝的决策行为。如果一个filter要求对网络数据包做另外的处理时,它可以指定一个callout完成操作。如果这个filter的所有条件都成立时,filter engine会把网络数据转发给特定的callout做另外的处理。

通过FwpmFilterAdd添加一个filter,filter中指定对应的callout和filterCondition。

filter engine wfp平台的一个组件,用于存储filter和完成filter的过滤决定。filters在指定的filtering layers中添加到filter engine中,从而filter engine完成对过滤的拒绝,放行或者交给新的callout处理。如果一个filter在filter engine中指定新的callout来处理,则filter engine会调用这个callout的classifyFn来处理。

filtering layer TCP/IP网络栈中的一个层,filter engine匹配对应的网络数据找到合适的filters集合,交给这个层处理。每一层由一个filter layer identifier唯一标识。

Run-time Filtering Layer Identifiers(FWPS_XXX)用于内核层的callout驱动。ManagerMent Filtering Layer Identifiers(FWPM_XXX)和Base Filtering Engine(BFE)通信,既可以用于应用层也可以用于内核层。FWPM过滤由128的guid标示,FWPS由64位的luid标示。FWPS的位数少,提供了在实际网络流量中内核驱动处理时整数比较的性能。

过程基本上是注册callout,注册filter->在filtering layer监控到事件发送,提交给对应的filter,filter通过filter.action.calloutKey找到对应的callout,提交给callout的nofityFn或者ClassifyFn处理。

二、wfp架构

WFP的工作位置:

applications ——> network stack -- filter engine -- 各种callout——> network hardware

在tcp/ip栈中的关键点处都有filtering layer,在layer中网络数据被交给filter engine处理。filter engine中的filters可以指定一个callout来完成对应过滤条件的丢弃或者放行,交给新的callout的过滤操作。

三、callout driver可以完成的过滤操作

1.初始化callout driver

在DriverEntry函数中完成callout dirver的初始化操作。初始化需要三步操作,指定一个Unload函数,创建一个设备对象,向filter engine注册callouts。

前两步和以前的驱动开发一样,wdm驱动中通过IoCreateDevice,在wdf驱动中通过WdfDeviceCreate创建设备对象。即使对应的filter engine当前没有运行,第三步向这个filter engine注册callout也是可以的。callout driver通过FwpsCalloutRegister()函数注册一个callout。函数的参数是设备对象和一个FWPS_CALLOUT0类型的callout结构,结构中包含这个callout的ClassifyFn,NotifyFn,FlowDeleteFn函数指针。这三个函数指针需要提前声明。一个callout驱动可以实现多个callout,这个驱动通过FwpsCalloutRegister0函数每次向filter engine注册一个callout。

2.处理notify callouts

filter engine调用callout的notifyFn函数来通知callout驱动对应的网络事件的发生。主要是filter的添加,删除事件。

添加filter:当添加到filter engine的filter指定了一个callout执行filter的操作时,filter engine会回调callout的notifyFn函数,并且在notifyType参数中传递FWPS_CALLOUT_NOTIFY_ADD_FILTER。如果指定了callout处理操作的filters已经添加到filter engine中,对于这种已存在的filters,callout再通过这个filter注册callout时,filter engine不会调用这个callout的notifyFn。filter engine只会在向指定的callout添加新的filter时调用notifyFn函数。这种情况下,filter engine不一定会回调这个callout的每一个filter。

filter删除:当添加到filter engine中的filter被删除时,对于callout的notifyFn函数会被调用,并且传递一个FWPS_CALLOUT_NOTIFY_DELETE_FILTER参数,filterKey参数为NULL。

当callout的notifyFn函数不识别接收到的NotifyType参数时,应该忽略而且返回STATUS_SUCCESS。

当filter添加到filter engine时,callout驱动可以为filter指定一个context。这个callout的classifyFn函数可以使用这个context保存一些状态信息,当下次filter engine回调它时使用。当filter被删除时,callout驱动完成context的清理。

3.处理classify callouts

当有网络数据需要被某个callout处理时,filter engine会回调它的classify函数。这种情况发生在当指定的callout注册的某个filter的过滤条件全部满足时。如果这个filter没有过滤条件,filter engine会调用对应的callout的classifyFn函数。

canctiwall简易wfp防火墙中通过RegisterCalloutForLayer注册了accept和connect的ALE层事件处理网络数据的连接和接收,匹配对应的规则。

过滤引擎传输几种不同的数据项到callout的classifyFn函数。这些数据项包括fixed data values,metadata values,原始网络数据,过滤信息和流的context。

特定的传输数据依赖于指定的filtering lawyer和classifyFn的条件。

下面列举一些callout可以完成的典型操作。

(1)使用callout做深度检查

当callout要做深度检查时,它的classifyFn函数可以对数据的任意fixed data fields,metadata fields,原始包数据或者相关数据做组合检查。

处理过程如下:

VOID NTAPI

ClassifyFn(
    IN const FWPS_INCOMING_VALUES0* inFixedValues,
    IN const FWPS_INCOMING_METADATA_VALUES0* inMetaValues,
    IN OUT VOID* layerData,
    IN const FWPS_FILTER0* filter,
    IN UINT64 flowContext,
    OUT FWPS_CLASSIFY_OUT* classifyOut)
{
        PNET_BUFFER_LIST rawData;
        ...
        //检查是否有FWPS_RIGHT_ACTION_WRITE标志,该标志指示了本callout是否有权限修改过滤了action
        //如果这个标志没有设置,callout仍然可以返回block为了否决被前一个filter返回的permit行为。
        if (!(classifyOut->rights & FWPS_RIGHT_ACTION_WRITE)) {
            //不指定action直接返回
            return;
        }
        //获取inFixedValues数据
        ...
        //获取metadata字段从inMetaValues中
        ...
        //得到原始数据的指针
        rawData = (PNET_BUFFER_LIST)layerData;
        //Get any filter context data from filter->context
        ...
        //Get and flow context data from flowContext
        ...
        //检查数据来源用以决定对数据的action
        ...
        //如果允许数据通过
        if (...) {
            classifyOut->actionType = FWP_ACTIOn_PERMIT;
            //检查是否要清除FWPS_RIGHT_ACTIOn_WRITE标志
            if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT) {
                //清除FWPS_RIGHT_ACTION_WRITE标志
                classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
            }
            return;
        }
        ...
        //如果拒绝数据通过
        if (...) {
            //设置拒绝数据
            classifyOut->actionType = FWP_ACTION_BLOCK;
            //清除FWPS_RIGHT_ACTION_WRITE标志位
            classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
            return;
        }
        ...
        //如果允许或者拒绝行为需要继续向在filter engine后面的filter传递
        if (...) {
            //设置继续向下一个filter传递的标志
            classifyOut->actionType = FWP_ACTION_CONTINUE;
            return;
        }
        ...
}

在filter->action.type中指定callout返回的action,如果需要了解action的具体有哪些值,可以看msdn中FWPS_ACTION0_结构的定义。

如果一个callout必须完成对数据包的额外处理后才可以决定action,它可以先pend数据包直到处理完成。如果需要了解如何pend数据包,可以看

msdn中Types of Callouts和FwpsPendOperation0的定义。

在一些filtering layer中,filter engine传给callout的classifyFn的原始数据指针layerData可能为NULL。

(2) 做流数据的深度检查

类似上面,区别在

//获取流数据io packet的指针
ioPacket = (FWPS_STREAM_CALLOUT_IO_PACKET0 *)layerData;
//得到数据流指针
dataStream = ioPacket->dataStream;
//如果需要更多的流数据做action决定时
if (...) {
    //通知filter engine需要多少个字节
    ioPacket->streamAction = FWPS_STREAM_ACTIOn_NEED_MORE_DATA;
    ioPacket->countBytesRequired = bytesRequired;
    ioPacket->countBytesEnforced = 0;
    //通知filter engine传递给下一个filter继续处理
    classifyOut->actionType = FWP_ACTION_CONTINUE;
    return;
}

(3) 检查包和流数据

这部分包括下面几个点:

(3.1) 包的检查点

进来的数据包被指定按下面的顺序提交给本机,依次向上遍历fwp layer。

a.IP Packet(network layer)网络层

所有的ip包,包括ip包分片,在这层做检查。但是,如果包是IPsec-protected,对包内容的深度检查和修改这层不能做因为包还没有生效或者解密

b.Transport Layer传输层

所有排列好的或者重组的包都可以在这层检查。IPsec-protected包已经生活或者解密

c.Application Layer Enforcement(ALE) Receive or Accept应用层加强层

接收包或者连接到达本地端点的第一个包被提交到这层。例如,一个将要到达的tcp的syn段或者一次udp流的第一个udp消息被提交。一个被要求重新授权的包,比如防火墙策略改变了,也被提交到这层,此时,ALE的reauthorization标志将会被设置。

d.datagram数据或者流
a.ALE connect

​ Tcp连接请求(在syn产生之前)和第一个udp消息在这层提交发送给远程端口

b.datagram data or stream
c.transport and icmp error
d.ip packet

​ 如果一个目的地址是本地的包被修改为非本地地址,它应该被注入到forwarding layer。

(3.2) wfp layer要求和限制

a.forwarding layer

​ ip包的转发功能可以通过netsh interface ipv4 set interface开启。NET_BUFFER_LIST链表结构可以用来提交ip包的在路由上的组装(group)功能,这个不同于在目的地址的报的重组(reassembly)。当一个包组装提交给这层时,FWP_CONDITION_FLAG_IS_FRAGMENT_GROUP标志位会被传送到classifyFn函数中。此时NET_BUFFER_LIST结构指示了一个包分片的第一个节点。

​ 一个转发的注入包能被再次提交给callout驱动。为了阻止死循环,驱动先调用FwpsQueryPacketInjectionState()函数在调用classifyFn之前。

​ 驱动应该允许包,把包状态FWPS_PACKET_INJECTION_STATE设置为FWPS_PACKET_INJECTED_BY_SELF或者FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF来使包不被改变。

b.network layer
c.transport layer and ale

(3.3) 包的提交格式

net buffer list可以描述整个ip包,对于不同的layers,wfp提交一个从ip头开始的不同的偏移,例如对于接收包的网络层,net bufer list从ip头部开始。对于传输层,net buffer list从传输层头部开始。

对于发送的包,在net buffer list包的一些被发送时,callout dirver必须按下面步骤做:

复制而且block整个net buffer list。建立一个新的net buffer list来描述原来list的子集,把新的list注入到发送路径上去。

(3.4) callout的类型

Inline Inspection Callout,这种类型返回FWP_ACTION_CONTINUE值。它不修改任何网络包,比如只是做网络流量的统计监控。这种的FWPS_ACTION0结构的Type成员应该设置FWP_ACTION_CALLOUT_INSPECTION。

Out-of-band Inspection Callout,Inline Modification Callout,Out-of-band Modification Callout, Redirection Callout

为了和其他callout合作完成包的检查,修改和连接重定向,在包被pended之前,callout必须丢弃原始的包通过清除FWPS_CLASSIFY_OUT0结构中的FWPS_RIGHT_ACTION_WRITE标志位在classifyFn函数返回前。如果FWPS_RIGHT_ACTION_WRITE位被设置,对于classifyFn来说,意味着包可以被pended然后重注入或修改。此时,callout不能pend这次提交,不能改变action类型。它必须等待更高weight的callout注入这个包或者修改。FwpsPendOperation()函数用于pend一个包。

(3.5)包注入函数

callout驱动可以利用FwpsInjectForwardAsync0等函数注入pended或者修改一个包。

(3.6) 包修改的例子

(3.7)流的检查

(4) 修改流数据

在callout驱动注入流数据之前,需要先创建一个注入句柄。关于如何修改流数据,可以看wfp中的Windows Filtering Platform Stream Edit Sample示例源码。

(5)数据日志记录

callout的classifyFn可以记录的数据有data field,metadata fields,原始网络数据和存储在context中的相关数据。例如,如果callout在网络层通过filter日志记录接收的ipv4数据包中有多少被丢弃了,callout可以在FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD层向filter engine添加一个filter。calssifyFn函数如下:

VOID NTAPI

ClassifyFn()
{
    //增加所有丢弃包的统计数量
    InterlockedIncrement(&TotalDiscardCount);
    //检查丢弃原因字段metadata是否存在

    if (FWPS_IS_METADATA_FIELD_PRESEND(inMetaValues, FWPS_METADATA_FIELD_DISCARD_REASON))
    {
        //检查是不是普通的丢包
        if (inMetaValues->discardMetadata.discardModule == FWPS_DISCARD_MODULE_GENERAL) {
            //检查是不是因为filter丢包
            if (inMetaValues->discardMetadata.discardReason == FWPS_DISCARD_FIREWALL_POLICY) {
                //增加因为filter丢包的统计数量
                InterlockedIncrement(&FilterDiscardCount);
            }
        }
    }
    //对包的action不做改变
    classifyOut->actionType = FWP_ACTION_CONTINUE;
}

(6) 为一个数据流关联一个context

对于支持数据流的callout在filtering layer处理数据时,callout dirver可以和流关联的一个context。这个context对filter engine是不透明的。callout的classify函数可以使用这个context为指定的数据流存储状态信息,以便于filter engine下次调用callout时使用。filter engine通过flowContext传递给callout,如果没有关联context,则参数为0.
callout使用函数FwpsFlowAssociateContext0关联一个context。例如

//Context structure to be associated with data flows

typedef struct FLOW_CONTEXT_ {
    ...
        //Driver-specific context
        ...
} FLOW_CONTEXT, * PFLOW_CONTEXT;

#define FLOW_CONTEXT_POOL_TAG 'fcpt'

VOID NTAPI

ClassifyFn()
{
    PFLOW_CONTEXT context;
    UINT64 flowHandle;
    NTSTATUS status;
    ...
        //在metadata中检查流的句柄
        if (FWPS_IS_METADATA_FIELD_PRESENT(inMetaValues, FWPS_METADATA_FIELD_FLOW_HANDLE))
        {
            //获取流句柄
            flowHandle = inMetaValues->flowHandle;
            //分配一个context结构
            context = (PFLOW_CONTEXT)ExAllocatePoolWithTag(
                NonPagedPool, sizeof(FLOW_CONTEXT), FLOW_CONTEXT_POOL_TAG);
            //检查是否分配失败

            if (context == NULL) {

            }
            else {
                //初始化context结构
                ...
                //和数据流关联context
                status = FwpsFlowAssociateContext0(flowHandle, FWPS_LAYER_INBOUND_IPPACKET_V4, calloutId, (UINT64)context);
                //检查关联结果
                if (status != STATUS_SUCCESS) {
                    //Handle error
                    ...
                }
            }
        }
    ...
}

可以通过函数FwpsFlowRemoveContext0解除关联

(7)处理鉴别异步数据

wfp callout驱动可以通过在classifyFn函数中返回FWP_ACTION_PERMIT,FWP_ACTION_CONTINUE,or FWP_ACTION_BLOCK授权或者拒绝一个网络行为,或者放行,丢弃一个网络包。

很多情况下,callout驱动不能在classifyFn函数立即返回检查结果,必须异步的等待一些相关数据的处理。比如classifiable fields, metadata, packets可能被转发给别的组件处理,或者应用层返回判断结果。

wfp对不同的层的异步处理机制不同,一般的处理方式如下:

a.异步的ALE层鉴别

callout驱动在classifyFn中调用FwpsPendOperation0异步处理。异步处理完成后调用FwpsCompleteOperation0函数结束。

b.异步的packet鉴别

callout驱动应该在classify函数中返回FWP_ACTION_BLOCK并设置FWPS_CLASSIFY_OUT_FLAG_ABSORB标志。网络包应该被引用或者复制。异步的操作可以是重新注入复制或者修改后的包,或者丢弃包。

c.异步的包含packet的ALE鉴别

包含上述两种操作。

需要考虑的特殊情况如下:

a. ALE connect与Receive/Accept 层

当在ALE connect层(FWPS_LAYER_ALE_AUTH_CONNECT_V4 or FWPS_LAYER_ALE_AUTH_CONNECT_V6)完成了一个pended的classify操作时,FwpsCompleteOperation0函数被调用。

此时,ALE的reauthorization的classify行为被触发在各自的ALE连接层。callout驱动应该从这种reauthorization行为中返回一个检查的决定。可以通过FWP_CONDITION_FLAG_IS_REAUTHORIZE标志看是不是reauthoriaztion行为。

callout驱动必须为每一个pended的ALE_AUTH_CONNECTclassify行为维持一个唯一的状态,以便于在 FwpsCompleteOperation0触发的reauthoriaztion期间对每一个classify行为的classify决定都可以被查询。如果在pended的ALE_AUTH_CONNECTclassify行为期间,reauthoriaztion发生后包被引用或者复制时他们可以被重注入。

当在ALE的Receive/Accept((FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V4 or FWPS_LAYER_ALE_AUTH_RECV_ACCEPT_V6)完成一个classify操作时,FwpsCompleteOperation0 被调用不会

触发ALE reauthoriaztion操作。后面几句没看懂。 Instead a new call to classifyFn is made again when the cloned packet is reinjected incoming if the modification was not significant enough to bypass the filter. Permitting the self-injected clone from the ALE_RECV_ACCEPT layer effectively authorizes the incoming connection.

If the incoming connection is not to be allowed, discard the incoming packet after it calls FwpsCompleteOperation0.

b. ALE reauthoriaztion

当一些事件发生时,比如策略改变(例如增加删除一个filter),检测到新的arrival接口,通过使用IPsec更新连接密钥, callout驱动可以在ALE connect或者receive/accept层reclassified.
这种reauthoriaztion不能通过调用FwpsCompleteOperation0被pended。callout驱动在reauthoriaztion时可以利用规则处理这些包。

所有进来和出去的数据包都可以被reauthorized在ALE_AUTH_CONNECT或者ALE_RECV_ACCEPT层。例如,一个进来的数据包可以被reauthorized在ALE_AUTH_CONNECT层。callout驱动不可以假设包的方向和connection的方向相同。

c.ALE_FLOW_ESTABLISHED 层

FWPS_LAYER_ALE_FLOW_ESTABLISHED_V4 or FWPS_LAYER_ALE_FLOW_ESTABLISHED_V6)层不支持异步处理数据。

d.INBOUND_TRANSPORT Layers

当在进来的传输层((FWPS_LAYER_INBOUND_TRANSPORT_V4 or FWPS_LAYER_INBOUND_TRANSPORT_V6))要求ALE classify处理包时, callout驱动必须不完成异步处理过程。
这个可以和流的创建进行交互。当wfp对进来的包调用classify函数时,它对那些要求ALE classify处理的包设置了FWPS_METADATA_FIELD_ALE_CLASSIFY_REQUIRED标志。

callout驱动应该从INBOUND_TRANSPORT层对这种包放行,并且延迟处理直到ALE_RECV_ACCEPT层。

e. stream 层

在stream层(FWPS_LAYER_STREAM_V4 or FWPS_LAYER_STREAM_V6),tcp数据段代替了ip或者tcp头提交。stream层也是net buffer list可以被提交的在classify 函数中。WFP可以对stream层将调用的函数进行复制或者注入,通过FwpsCloneStreamData0和FwpsStreamInjectAsync0函数。

因为提交的stream层的数据的有序性,callout驱动必须复制和处理数据只要流数据是pending的。对一个给定的数据流混合同步和异步操作对导致异常行为。

(8)连接绑定或者重定向连接

WFP连接重定向callouts重定向一个程序的连接请求以便程序连接代理服务取代原始的连接。代理服务有两种sockets,一个用于被重定向的原始连接,一个用于新的代理发送连接。

一条wfp重定向记录是不透明的数据缓冲区,它被wfp在FWPM_LAYER_ALE_AUTH_CONNECT_REDIRECT_V4和FWPM_LAYER_ALE_AUTH_CONNECT_REDIRECT_V6层建立了对外的连接请求,从而把重定向后的连接和原始的连接关联起来。

win7之前,callout driver可以代理tcp连接的方式只有复制,丢弃,修改或者重注入传输层的包。有了连接绑定重定向的支持,callout driver可以修改任何tcp的4元组(本地,远程ip地址

和端口号).因为可以绑定重定向,所以没必要在连接重定向上支持本地地址和端口修改。

重定向可以在以下重定向层完成:

FWPM_LAYER_ALE_BIND_REDIRECT_V4 (FWPS_LAYER_ALE_BIND_REDIRECT_V4)

FWPM_LAYER_ALE_BIND_REDIRECT_V6 (FWPS_LAYER_ALE_BIND_REDIRECT_V6)

FWPM_LAYER_ALE_CONNECT_REDIRECT_V4 (FWPS_LAYER_ALE_CONNECT_REDIRECT_V4)

FWPM_LAYER_ALE_CONNECT_REDIRECT_V6 (FWPS_LAYER_ALE_CONNECT_REDIRECT_V6)

重定向的效果取决于所在层,connect层只影响连接流,bind层影响所有连接。

可以使用 FwpsCalloutRegister1或者FwpsCalloutRegister2而不是the older FwpsCalloutRegister0注册重定向。

重定向不是适用于所有类型的网络流量,支持的类型有tcp,udp,Raw UDPv4 without the header include option,raw icmp。

示例代码如下。

(9)ale终端的生命周期管理

支持ale的callout driver可能会分配资源处理提交数据。现在讨论当相关的端点关闭时,如何配置callout driver释放这些资源。ale端点的生命周期管理在win7后的系统都支持。

为了管理ale端点相关的资源,callout必须在以下层注册,

FWPS_LAYER_ALE_RESOURCE_RELEASE_V4 (FWPM_LAYER_ALE_RESOURCE_RELEASE_V4)

FWPS_LAYER_ALE_RESOURCE_RELEASE_V6 (FWPM_LAYER_ALE_RESOURCE_RELEASE_V6)

FWPS_LAYER_ALE_ENDPOINT_CLOSURE_V4 (FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V4)

FWPS_LAYER_ALE_ENDPOINT_CLOSURE_V6 (FWPM_LAYER_ALE_ENDPOINT_CLOSURE_V6)

。。。

(10)使用数据包的标记

callout driv可以标记有兴趣的包或者接受标记包的事件通知。包的标记在win7之后系统支持。

为了使用包标记,callout driver必须实现 FWPS_NET_BUFFER_LIST_NOTIFY_FN0和FWPS_NET_BUFFER_LIST_NOTIFY_FN1回调函数。