FreeBSD之netgraph简要解析

qiaosym 2012-04-03

FreeBSD的netgraph真是太帅了,它到底是个什么玩艺呢?知道Linux的Netfilter的不少,那么就用Netfilter来类比吧。netgraph是一个基于图的钩子系统,正如其名称所展示的那样,什么样的图呢?很简单,就是通过边连接的节点,和数据结构里面学到的一样。netgraph系统挂接在内核协议栈的特定点上,哪些点呢?这个和Netfilter很类似,但是却不是Netfilter精心设计的那5个点,而是更简单的每一层处理的输入点和输出点,如下图所示:
FreeBSD之netgraph简要解析
netgraph到底长什么样子呢?到目前为止,我们只是知道了一张图挂上去了,这仅仅是个接口,一个开始,既然挂上去了,数据包就从此处进入这张图了,把它叫做地图更加适合,因此从此以后,数据包就要在游历于这张地图了,最终的结果有两个:
1.数据包从地图的某处出来,重新进入系统标准的协议栈的当初被拦截的那个地方;
2.数据包再也没有出来回到原点,要么被地图吃掉了(进入了某一房间?),要么就是从某处出去,进入协议栈的别的地方。

以上两点很类似于Netfilter的ACCEPT,STOLEN这样的结果,仔细想想不是么?netgraph和标准协议栈的衔接如下图所示:
FreeBSD之netgraph简要解析
既然知道了netgraph的位置,那么下面就看看它的样子吧。还是先给出一幅图
FreeBSD之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定义如下:
  1. struct ng_type {  
  2.     u_int32_t       version;        /* must equal NG_API_VERSION */  
  3.     const char      *name;          /* Unique type name */  
  4.     modeventhand_t  mod_event;      /* Module event handler (optional) */  
  5.     ng_constructor_t *constructor;  /* Node constructor */  
  6.     ng_rcvmsg_t     *rcvmsg;        /* control messages come here */  
  7.     ng_close_t      *close;         /* warn about forthcoming shutdown */  
  8.     ng_shutdown_t   *shutdown;      /* reset, and free resources */  
  9.     ng_newhook_t    *newhook;       /* first notification of new hook */  
  10.     ng_findhook_t   *findhook;      /* only if you have lots of hooks */  
  11.     ng_connect_t    *connect;       /* final notification of new hook */  
  12.     ng_rcvdata_t    *rcvdata;       /* data comes here */  
  13.     ng_disconnect_t *disconnect;    /* notify on disconnect */  
  14.     const struct    ng_cmdlist *cmdlist;    /* commands we can convert */  
  15.     LIST_ENTRY(ng_type) types;              /* linked list of all types */  
  16.     int                 refs;               /* number of instances */  
  17. };  
注释很清楚了,自不必说,如果我们看看其中一些回调函数的定义,就更能理解了。“构造函数”和“析构函数”都有,每一个“成员函数”的参数列表的第一个参数类型都是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的核心逻辑实现的,我们可以从下面的这个核心宏中看到这一点:
  1. #define NG_FWD_ITEM_HOOK_FLAGS(error, item, hook, flags)                \   
  2.     do {                                                            \  
  3.         (error) =                                               \  
  4.         ng_address_hook(NULL, (item), (hook), NG_NOFLAGS);  \  
  5.         if (error == 0) {                                       \  
  6.             SAVE_LINE(item);                                \  
  7.             (error) = ng_snd_item((item), (flags));         \  
  8.         }                                                       \  
  9.         (item) = NULL;                                          \  
  10.     } while (0)  
其中ng_address_hook完成了peer的定位,这个peer可以通过ngctl命令来设置。

相关推荐