By feivirus 2013-11-24
一。基本术语定义
callout 为扩展wfp性能提供的一个功能,由一系列call 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的calssifyFn来处理。
filtering layer TCP/IP网络栈中的一个层,filter engine匹配对应的网络数据找到合适的filters集合,交给这个层处理。每一层由一个filter layer identifier唯一标示。
Run-time Filtering Layer Identifiers (FWPS_XXX)用于内核层的callout驱动。Management 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架构
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 driver的初始化操作。初始化需要三步操作,指定一个Unload函数,创建一个设备对象,向filter engine注册callouts。
前两部和以前的驱动开发一样,wdm驱动中通过IoCreateDevice,在wdf驱动中通过WdfDeviceCreate创建设备对象。即使对应的filter engine当前没有运行,
第三部向这个filter engine注册callout也是可以的。callout driver通过FwpsCalloutRegister0函数注册一个callout.函数的参数是设备对象和一个FWPS_CALLOUT0
类型的callout结构,结构中包含这个callou的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和classsifyFn的条件。
下面列举一些callout可以完成的典型操作。
(1)使用callout做深度检查
当callout要做深度检查时,它的classsifyFn函数可以对数据的任意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 any 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)包的检查点
进来的数据包被指定按下面的顺序提交给本机,依次向上遍历wfp 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驱动。为了阻止死循环,驱动先调用FwpsQueryPacketInjectionState0函数在调用classifyFn之前。
驱动应该允许包,把包状态FWPS_PACKET_INJECTION_STATE 设置为FWPS_PACKET_INJECTED_BY_SELF或者FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF
来使包不被改变。
b.netwok layer
c.transport layer and ale
(3.3)包的提交格式
net buffer list可以描述整个ip包,对于不同的layers, wfp提交一个从ip头开始的不同的偏移,例如对于接收包的网络层,net buffer list
从ip头部开始。对于传输层,net buffer list从传输层头部开始。
对于发送的包,在net buffer list包的一些被发送时,callout driver必须按下面步骤做:
复制而且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注入这个包或者修改。FwpsPendOperation0 函数用于pend一个包。
(3.5)包注入函数
callout驱动可以利用FwpsInjectForwardAsync0等函数注入pended或者修改一个包。
(3.6)包修改的例子
(3.7)流的检查
(4)修改流数据
在callout驱动注入流数据之前,需要先创建一个注入句柄.关于如何修改流数据,可以看wfp中的Windows Filtering Platform Stream Edit Sample示例源码。
(5)数据日志记录
callout的classifyFn可以记录的数据有data fields,metadata fields,原始网络数据和存储在context中的相关数据。例如,如果callout在网络层通过filter日志记录接收的ipv4数据包中
有多少被丢弃了,callout可以在FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD层向filter engine添加一个filter。classifyFn函数如下:
VOID NTAPI
ClassifyFn()
{
//增加所有丢弃包的统计数量
InterlockedIncrement(&TotalDiscardCount);
// 检查丢弃原因字段metadata 是否存在
if (FWPS_IS_METADATA_FIELD_PRESENT(
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)为一个数据流关联一个cotext
对于支持数据流的callou在filtering layer处理数据时,callout driver可以和流关联一个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 content
.
} 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回调函数。