FreeBSD的netgraph真是太帅了,它到底是个什么玩艺呢?知道Linux的Netfilter的不少,那么就用Netfilter来类比吧。netgraph是一个基于图的钩子系统,正如其名称所展示的那样,什么样的图呢?很简单,就是通过边连接的节点,和数据结构里面学到的一样。netgraph系统挂接在内核协议栈的特定点上,哪些点呢?这个和Netfilter很类似,但是却不是Netfilter精心设计的那5个点,而是更简单的每一层处理的输入点和输出点,如下图所示:
netgraph到底长什么样子呢?到目前为止,我们只是知道了一张图挂上去了,这仅仅是个接口,一个开始,既然挂上去了,数据包就从此处进入这张图了,把它叫做地图更加适合,因此从此以后,数据包就要在游历于这张地图了,最终的结果有两个:
1.数据包从地图的某处出来,重新进入系统标准的协议栈的当初被拦截的那个地方;
2.数据包再也没有出来回到原点,要么被地图吃掉了(进入了某一房间?),要么就是从某处出去,进入协议栈的别的地方。
以上两点很类似于Netfilter的ACCEPT,STOLEN这样的结果,仔细想想不是么?netgraph和标准协议栈的衔接如下图所示:
既然知道了netgraph的位置,那么下面就看看它的样子吧。还是先给出一幅图
该图中有两种元素,一种是节点,另一种是连接到节点的边的两端的顶点。在netgraph的术语中,节点就是Node,而顶点叫做hook,一条边连接两个hook,hook通过CONNECT/MKPEER构成一条边。从上图中可以看出,一条边的两端必然有两个hook,从命名上可以看出这些“边的端点”其实就是真正处理数据的地方,而Node其实就是一个“数据+操作”的封装,一个Node可以有多个hook,通过这些hook连接到其它的Node。
我们可以用OO的思想来理解这些个netgraph的概念,Node就是一个对象,每一个Node都有它所属的Type,可以将Type理解成类。而hook其实就是一个Node对象的私有数据,整个graph通过“各个hook的对接”来完成,FreeBSD提供了丰富的命令来完成netgraph的构建,说白了其实就是以下几步骤:
1.生成一系列的Node对象;
2.为每一个Node定义一个或多个hook;
3.将特定的Node通过hook连接在一起。
如此一来整个graph就构建好了,FreeBSD提供了struct ng_type,它便是代表了一个类,然后你每生成一个特定ng_type的实例就相当于生成了一个对象,通过对该结构体里面的一些字段的理解,我们就可以完整理解数据包在这个graph中的游历过成了。struct ng_type定义如下:
struct ng_type { u_int32_t version; /* must equal NG_API_VERSION */ const char *name; /* Unique type name */ modeventhand_t mod_event; /* Module event handler (optional) */ ng_constructor_t *constructor; /* Node constructor */ ng_rcvmsg_t *rcvmsg; /* control messages come here */ ng_close_t *close; /* warn about forthcoming shutdown */ ng_shutdown_t *shutdown; /* reset, and free resources */ ng_newhook_t *newhook; /* first notification of new hook */ ng_findhook_t *findhook; /* only if you have lots of hooks */ ng_connect_t *connect; /* final notification of new hook */ ng_rcvdata_t *rcvdata; /* data comes here */ ng_disconnect_t *disconnect; /* notify on disconnect */ const struct ng_cmdlist *cmdlist; /* commands we can convert */ LIST_ENTRY(ng_type) types; /* linked list of all types */ int refs; /* number of instances */ };
注释很清楚了,自不必说,如果我们看看其中一些回调函数的定义,就更能理解了。“构造函数”和“析构函数”都有,每一个“成员函数”的参数列表的第一个参数类型都是node_p,这难道不是this么?这里唯一要注意的就是rcvdata回调函数,该函数接收从另一个Node发送过来的数据,接收者是hook,而不是Node,再次强调,Node之间通过hook相连接,而不是通过node本身,然而每一个hook都要唯一绑定一个Node对象,因此我们可以从hook解析出唯一的Node对象,却不能从Node中直接得到hook(一个Node对象拥有N多hook呢),要分清一对一和一对多的关系。因此rcvdata的第一个参数是hook_p就是合理的了。Node和Node之间通过hook传递控制信息,而网络数据包则是通过一个hook向其peer hook发送消息的方式完成的,当然所谓的发送消息大多数情况下就是函数直接调用。既然一条边两端有两个hook,那么每一个hook就有一个peer,每当我们将数据包发送到一个hook的时候,实际的效果就是数据包被发送到了该hook的peer,这是netgraph的核心逻辑实现的,我们可以从下面的这个核心宏中看到这一点:
#define NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags) \ do { \ (error) = \ ng_address_hook(NULL, (item), (hook), NG_NOFLAGS); \ if (error == 0) { \ SAVE_LINE(item); \ (error) = ng_snd_item((item), (flags)); \ } \ (item) = NULL; \ } while (0)
其中ng_address_hook完成了peer的定位,这个peer可以通过ngctl命令来设置。