上面这种方式采用数组的方式组织,为每个报文提供一个最大报文长度的空间。因为缓冲区数目有限,因此这种方式可以满足需要。如果要考虑到减少空间的浪费,那么可以按每个报文的实际长度存储,上面的算法不能够适应这种方式。& ?% e3 Y4 C% ^" V2 Z
6 E9 n. a5 I2 U. g% V! G6 f应用层和驱动程序的通信
C: |" U4 b z% K/ z- T `2 @2 O+ d) P* ]( |) M4 a: \ F* D+ s在网卡接收/IP发送过程中,驱动程序缓存报文,用事件通知应用层有报文需要处理。那么应用层可以通过IO方式或者共享内存方式取得此报文。1 d' |8 c! p; F
实践说明,在100Mbps速率下,以上两种方式都可以满足需要,最为简便的方式就是使用有缓冲的IO方式。
* z C N- h- T/ c. l. x) e应用层处理完毕,也可以使用以上两种方式之一来向驱动程序递交结果。不过,IO方式因为一次只能发送一个报文,100Mbps网络速度下降为70%~80%网络速度,10Mbps不会有影响。也就是说,主机发出的最大速度只有70%的网络速度,这和应用程序发送不超过MTU的UDP数据报的速度是一样的。对TCP来说,由于是双向通信,损失更加大一些,大约40%~60%速度。. V! J d/ l( p
这时候,使用共享内存方式,因为减少了系统调用的开销,可以避免速度下降。
# Q4 N" {" @' H ]0 y' r0 x2 R. R' c) q6 g3 S报文发送的速度控制
9 `3 |* B( S- v- F! W- h7 s/ m# I3 B/ L+ v8 ~
当IP协议发送报文的时候,一般来说,我们的中间层驱动必须把这些报文缓存起来,告诉IP软件发送成功,然后让应用层处理完毕之后再做决定。显然,存储报文的速度远远超过网卡能够发送的速度,然而IP软件(特别是UDP)将以我们存储的速度发送报文。造成缓存迅速耗尽。后续的报文只好丢弃。这样一来,UDP发送将不能正常工作。TCP由于可以自行适应网络状况,依然可以在这种情况下工作,速度在70%左右。在Passthru里,可以转发至低层驱动,然后用异步或同步方式返回,从而达到网卡的发送速度一致。9 G- ~% ^2 g; q
因此,必须有一个办法避免这种状况。中间层驱动把这些报文缓存起来,告诉IP软件发送状态未决(Pending)。等到最后处理完毕,告诉IP软件发送完成。从而协调了发送速度。这种方式带来一个问题,就是驱动程序必须在发送超时的情况下放弃对这些缓冲报文的所有权。具体来说,就是MiniportReset被调用的时候,就有可能是NDIS察觉到发送超时,从而放弃所有未完成的发送操作,如果没有正确处理这种情况,将会导致严重问题。如果中间层在Miniport初始化的时候通过调用NdisMSetAttributesEx函数设置了NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT标志,那么中间层驱动程序将不会得到报文超时通知,中间层必须自行处理缓存的报文。2 ?+ @# f }' j/ K
9 b p; M( f9 G4 z与Passthru协同工作1 ~- r- k4 f$ @, E8 Z! B
% T- h- D% [+ n) y% r
当上层应用不再需要截包时,驱动程序应该完全是Passthru行为。这就要求所有发送/接收函数应该正确处理在截包与非截包状态,不至于做出危害行为。
) J# B7 a: h6 N7 g4 x2 `! d, b5 z7 R 具体来说,在从NIC上接收/发送,向IP协议提交数据包/接受IP协议发送四个方向上正确处理所有接收/发送函数。
% S: ~2 B- T% x0 g/ y1 `# Z+ `; r% b' M ] A2 I! Y2 _8 n9 d其它辅助设施
6 m) _/ s+ h8 `: Z! f% c7 R8 b5 V1 [. M
添加一些控制功能提供更细粒度的控制,让应用程序获得更多的自由。比如,可以控制截取哪一个网卡,可以控制截取某个方向上的流量,网络是否有变化(网卡卸载/Disable)等等。- o3 O& W; V H- y+ i9 p9 I
- ^, b H* \+ ]
实现" x$ R& j: }* E( X
! U: i! n; y7 `0 }$ C
实现选择Passthru源代码,在其上进行修改,主要修改包括:
; T5 u- a8 [' c9 f" P6 @3 B1. 修改接收函数
1 @# E! A2 p; ~6 S6 V5 k2. 修改发送函数' Y1 T9 `) K3 `4 X p
3. 增加报文缓存
, Z# P; I! e$ i, ]2 m( h% I4. 增加IO部分
# b) l3 N1 K$ p$ Q; T1 y! |. M0 W5. 增加控制功能
# q3 |, t# ?- c9 J% u# m8 B6. 增加应用层处理后的后续处理
, M1 Q! C* L. f0 D" Z6 T4 A: W* N0 t ?2 |8 e8 C" G, R
这个实现使用了共享内存方式,具有一个处理前缓冲池和一个应用程序处理后的缓冲池。由于接收报文和待发送报文使用同一个缓冲池,也因为其他一些原因,这个实现的发送效率并没有比用IO方式快多少。
; |, s8 R' u5 V3 M, M通过精心的设计和比较,完全可以做到100Mbps的收发速度。9 _$ F. W2 S% E t- f
这份文章旨在讨论这种应用层截报的工作方式和可行性。也由于驱动程序源代码并没有经过特别严格的测试,不适合商业使用,作为示范,也仅仅对以太网类型的报文进行了拦截。因此示范的驱动程序将不包含源代码。1 X& ^( H5 Q& y z/ F" O
& k0 U4 z$ w8 Q2 s! y
API说明
( F; s, X# Y. I. [ F: O8 s& i/ { ^6 I9 a第三方开发使用cap.h头文件,capdll.dll包含了下列函数:
, I4 Z/ L% z2 c& KBOOL CapInitialize();
# N6 e! W% u* n5 U+ @9 fVOID CapUninitialize();
J2 @& M$ O2 a; ]* ^$ HBOOL CapStartCapture(PKTPROC PacketProc, ADAPTERS_CHANGE_CALLBACK AdaptChange);
4 i! e% z- N1 P* i* ]/ nVOID CapStopCapture();
. ?7 x; q4 z5 I! \DWORD CapGetAdaptList(PADAPT_INFO pAdaptInfo, DWORD BufferSize);. S4 E4 Y; u- ~4 J9 F* B
VOID CapSetRule(HANDLE Adapter, ULONG Rule);: O4 N- m' w6 ]$ t
BOOL CapSendPacket(HANDLE Adapter, ULONG Opcode, ULONG Length, PUCHAR Data);0 }5 n7 C/ o0 }& O" u2 \
同时提供了该dll的capdll.lib文件以便在vc工程文件中引入capdll.lib使用更为方便的编译连接方式。6 V1 B5 p- W8 m/ a+ s" Y8 h
* q: b) x9 j ]" Y
说明
9 A8 W. S+ |4 g) n& k" d' X, ]3 H2 |: ~0 ~# i所有函数的返回值都没有指明错误原因。DEBUG版本可以在控制台打印出运行信息,并且在C:\ capture.txt有同样的输出信息。
$ v/ g% {8 q& h' N T6 Y# O8 y$ E3 W( s, V8 @6 VBOOL CapInitialize();% o$ U& h' |+ p- R/ Z8 X* x0 S
说明:
4 {8 B$ T! G( a* L) I" k. I通知截报中间层驱动做一些必要的初始化工作。& k3 | I% F, \+ Y* B2 A' y
参数:
( z. m8 y" u/ P* O. F) X7 p% R无。
/ b% Y2 i& P; D/ y N* f; i/ H返回值:
9 x1 R+ f! }0 y失败返回FALSE。 i# R. c! s4 |- X) E2 u) h
9 P) Y A3 l+ QVOID CapUninitialize();
3 h6 D$ R' O* Q* }说明:
# `, l( G" ~* o) t' r; B: y5 ^释放驱动程序创建的事件,线程,内存等。
9 l# J( C! j! d* J参数:
. K9 E! D: k& P% }, ~无。
0 h4 s7 O8 Y& F9 F/ Q. R+ G$ h# I返回值:" k2 E( K' u1 i" M% ^6 ?0 p
无。! {( u! ]2 C( V7 N- M1 V
注意:" ^4 f- V" R A, \
在调用此函数之前,应当调用CapSetRule将驱动程序截报规则设置成Opcode_PASSTHRU,以便恢复PASSTHRU行为。0 {6 w, ^2 N5 J9 m1 u9 A
2 R: u# W! S4 T$ j
BOOL CapStartCapture(PKTPROC PacketProc, ADAPTERS_CHANGE_CALLBACK AdaptChange); 说明:1 F: r1 l- e$ C9 ^% ?7 Y) \
启动截报。Capdll将会创建一个线程,运行在THREAD_PRIORITY_HIGHEST优先级,并等待网络事件,当有驱动程序接收到报文,或者IP协议发送报文,或者发现网卡启动/禁用/插入/拔除等,将会通过用户提供的回调函数通知用户。5 H4 K `4 p$ H1 n
参数:% w- E3 \/ c r. g; [* n
PacketProc:用户提供的报文处理函数;; } J7 `0 A/ i+ F/ T2 q
AdaptChange:用户提供的网络变化通知函数;* ~+ R7 ^% P& U$ y0 H, M" I
返回值:' V$ N' D2 }: D6 p- @8 i& R$ e
- y C3 H% s2 i4 n8 [VOID CapStopCapture();2 p" z3 R i( x. D: Y) y8 ?( W* W
说明:
* ~9 A6 y8 N" R. |1 x) L$ i) l4 m 停止截报。销毁创建的线程。
5 Y6 ~4 }8 h* O% P4 U. S参数:
5 t, W% m: n- d: A5 W 无。* F. y1 o9 }8 s; b" g
返回值:: s* I: B- x# k; m) Z! @9 `* \& D* l
无。. ^$ u5 W! ^4 d; k1 S1 O) ~: z/ ?
( ~: t7 t* l6 }DWORD CapGetAdaptList(PADAPT_INFO pAdaptInfo, DWORD BufferSize);3 l/ \, N) v6 Y o$ ?9 q& r
说明:
, p& b% u- U& W1 N9 j+ A 获取网络适配卡列表。- |7 H9 H8 ^! C( y
参数:
& D( [" f8 ]6 q$ h- M& x pAdaptInfo ADAPT_INFO结构数组,用户提供足够的空间。
/ I7 L+ E8 \6 K& m9 ~8 G BufferSize 缓冲区尺寸。
( D6 n6 r# W X* x. R" U3 e; j返回值:! a' D; D# v( l% M: F; L! R% R
网络适配卡数目。. N* `0 A* } g
1 s; x: T9 k1 tVOID CapSetRule(HANDLE Adapter, ULONG Rule);6 L& \4 {% G g! g$ R, |# M7 Z
说明:
! j0 ~3 k$ Q7 a5 m0 p& \: G 设置截报规则。. ^. ~( ]1 N2 p @; |
参数:; k) M& ^/ T9 j
Adapter:指定截取的网卡句柄。$ a2 t8 `! H; F: J
Rule:为Opcode_PASSTHRU:PASSTHRU行为;Opcode_SND:截取所有发送报文;Opcode_RCV:截取所有接收报文。可以使用Opcode_SND | Opcode_RCV。5 n( Y5 _8 B L: c6 H5 P& s
返回值:. {0 N, n# G, y4 \
无。 a- H8 H+ F! G* N6 g
6 x5 y* _+ q- _
BOOL CapSendPacket(HANDLE Adapter, ULONG Opcode, ULONG Length, PUCHAR Data);. c& a% o, y& X& w) H( l
说明:/ D3 d+ {) C+ y# b
将处理后的报文放入缓冲区。也可以自行构造报文。不仅可以发送报文,也可以将报文送给本机IP软件。8 r7 n w7 ?& | P9 ?: Q
参数:, x) {4 m) D: l2 ]( B G+ C
Adapter:指定使用的网卡句柄。+ G! E" l' a: x) n3 T
Opcode:Opcode_SND,将报文发送到网络上;Opcode_RCV,将报文传递给本机软件。* \$ X9 s! A* B
Length:报文长度;
* t% j. q2 A) C, p7 V6 x Data:报文内容;: N: k/ c7 `: L
返回值:5 Y8 F! ?, x& Z! k% ^. V$ p" [. [7 q
成功返回TRUE,失败返回FALSE。
8 G3 ] O9 n, q: u( ` x! Y; m& ^4 S
Sample9 Z, q% r% _: k! s3 ]
& e5 C5 ?! l" }6 e
$ `3 j- U! L) Z8 L; M7 J! c+ h2 x' A# F
( u( v& e/ s3 s5 l8 l% S
#include "cap.h"
1 ~) u* E9 d P+ A#include <stdio.h>
$ I9 `7 T; s1 E3 s( \/ l, S% D( r' H4 j# h1 K0 B+ l$ F. @// global data.1 S# T/ c! @# ~- j$ Z D
ADAPT_INFO AdaptInfo[16];
$ [. H3 U2 J# M, }2 kint AdapterNum;* V3 s' b4 z7 Z5 v
) m& s) N9 ]5 `7 h
VOID PacketProc(HANDLE Adapter, ULONG Opcode, ULONG Length, PUCHAR Data)
' ]6 O7 E4 \0 Q4 O0 v: y" X( S1 u{
: M- o9 H$ o7 m* b$ @ CapSendPacket(Adapter, Opcode, Length, Data);0 V9 ]) v E) }
}* [3 N/ \* D, O1 w1 f
: F2 L/ q0 e# ?, zVOID AdaptersChangeCallback()
; t0 L! q G4 F& c1 v, A{
9 T& h5 _: R1 q; c4 u% ~: }# k AdapterNum = CapGetAdaptList(AdaptInfo, sizeof(AdaptInfo));$ e, }$ u% ]$ S) V) O% F1 T; L8 Q
}* R2 W7 @( @5 Z" Q2 I
y+ X$ k) \/ f6 v
int main(int argc, char* argv[])
4 C+ j' P5 g& b( D' G6 Y. N- {{
9 B" r3 G: j6 ?6 V/ y! x4 _: R8 @' |& d d BOOL bRet;
% k' p- V k, e' C6 J* \ char cmd[80];
/ [) @+ i: {3 O' i. S int i;& T* ^6 G: O, m
! |, d& Q8 ]& J2 V* S0 c bRet = CapInitialize();
1 s, q1 Z4 U: y& m6 n! u: x if(bRet)/ b4 g% W ]/ b5 a+ E1 i! n
{6 R* f, e9 E1 o% ~. \7 j( y
AdapterNum = CapGetAdaptList(AdaptInfo, sizeof(AdaptInfo));
8 N! ^0 m/ j# L$ O1 K# N' M# ?# W for(i=0; i<AdapterNum; i++)
9 }: T0 i, Y/ T% n {
' ~$ n& _6 b$ Q3 J4 S, {8 F5 N CapSetRule(AdaptInfo.Adapter, Opcode_SND | Opcode_RCV);; c- D6 I, ]' i4 B/ `+ u$ a
}
0 l' x: j. G5 G* R: [, k. { R4 B$ ]$ s2 X
CapStartCapture(PacketProc, AdaptersChangeCallback);6 l+ F, X& B8 |6 [$ a( ]
- g `1 m8 V% u1 F3 V2 d6 v' C
for(;;): E+ o( ^% g7 k) J/ b5 y" o
{
+ I: s( Z3 h7 a @ gets(cmd);
( g# ], m# E3 n9 s. u if(strcmp(cmd, "quit")==0)
. b7 W( t+ Z: D @# p% d {
( ?* n4 }$ C, @3 v break;
' C& `& w5 v# B! p; {: l( a }- h* C$ ]# c2 V8 p) n6 [: C+ q
}" |! T$ h4 [' c4 r" ~0 L
0 p" t4 u& S: c
for(i=0; i<AdapterNum; i++)3 [4 w2 [3 z7 d
{3 I9 L$ o r1 o+ [
CapSetRule(AdaptInfo.Adapter, Opcode_PASSTHRU);
: B% c5 T8 `) ~1 G7 q. ] }
; b: C! ^# [3 ~" t1 Z, B8 r! h4 w8 d+ S8 w/ l `$ c2 ~- Z CapStopCapture();8 z) S) u: \7 q% C# R6 ?0 c* [
CapUninitialize();
+ C5 M l8 T; ?: y4 q" p! p7 B }* ?- o R H. R+ R. F
, B2 K9 R" W! }: n return 0;- `" q2 ?0 O( D# j/ `
}* a3 _* Z( I1 S) s+ k* a+ F! x8 L* o
: G% p/ W9 z# Z0 X% ^" q# L m4 x4 I$ c0 n8 A# ]4 {2 G. k% H7 g
应用举例8 \7 ?, w# c3 L" b X
8 M! z. A- s; T x y4 m 上述代码做了一个Passthru行为。
; p1 V1 M2 j. {8 ~! X* l 作网桥或者NAT,需要在报文处理函数里,将报文内容根据需要修改以太网头部或其他行为,然后从合适的另一块网卡上发出去;
' M- K' m! s# o8 u$ Z 作协议转换,比如IP/UDP隧道或者复杂如IPSEC之类,可以在报文处理函数里将报文内容解开隧道或者解密,重新组报文,放入缓冲区,让驱动程序送到IP软件;
) W) B0 I% f7 t, ]作防火墙,根据规则,丢弃不受欢迎的报文,正常的报文同样PASSTHRU;& M" L. z+ a# M( w! S0 Q) I
作入侵监测/安全审计(当然只能保护本机),PASSTHRU同时纪录网络事件;$ A. i" C5 \: i" q( [