由于此 API 支持在同一内核中并发运行多个 IP 栈实例,因此要使用包过滤钩子,需要大量的编程工作。通过 IP 栈,对多个区域使用的多个 IP 栈实例以及多个框架实例均支持在 IP 中进行包拦截。
本节说明了使用包过滤钩子 API 接收入站 IPv4 包的设置代码。
在使用此 API 时,首先需要确定,是允许在内核中运行多个 IP 实例,还是仅与全局区域进行交互。
要了解 IP 实例是否存在,请注册在创建、销毁和关闭实例时要激活的回调函数。可使用 net_instance_alloc() 分配 net_instance_t 包事件结构,以便存储这三种函数指针。当不再需要这些回调和结构时,可使用 net_instance_free() 释放资源。指定 nin_name,以便为结构实例提供一个名称。请至少指定 nin_create() 和 nin_destroy() 回调。在创建新 IP 实例时,会调用 nin_create() 函数,在销毁 IP 实例时,会调用 nin_destroy() 函数。
指定 nin_shutdown() 是可选的,除非代码需要将信息导出到 kstat。要对每个实例使用 kstat,请在执行 create 回调期间使用 net_kstat_create()。必须在 shutdown 回调(而不是 destroy 回调)期间清除 kstat 信息。可使用 net_kstat_delete() 清除 kstat 信息。
extern void *mycreate(const netid_t); net_instance_t *n; n = net_instance_alloc(NETINFO_VERSION); if (n != NULL) { n->nin_create = mycreate; n->nin_destroy = mydestroy; n->nin_name = "my module"; if (net_instance_register(n) != 0) net_instance_free(n); }
如果在调用 net_instance_alloc() 时存在一个或多个 IP 实例,则会对当前处于活动状态的每个实例调用 create 回调。支持回调的框架可确保,对于给定的实例,在任意时刻只有 create、destroy 或 shutdown 函数之一处于活动状态。此框架还可确保,一旦调用了 create 回调,则只有在 create 完成之后,才会调用 shutdown 回调。同样,直到 shutdown 回调完成之后,才会启动 destroy 回调。
以下示例中的 mycreate() 函数是一个简单的 create 回调示例。mycreate() 函数可在自己的专用上下文结构中记录网络实例标识符,并注册在向此框架注册新协议(例如,IPv4 或 IPv6)时要调用的新回调。
如果目前没有运行任何区域(此时,除了全局区域之外没有其他任何实例),则调用 net_instance_register() 将对全局区域运行 create 回调。必须提供 destroy 回调,以便日后可以调用 net_instance_unregister()。如果在尝试调用 net_instance_register() 时 nin_create 或 nin_destroy 字段设置为 NULL,则该尝试将失败。
void * mycreate(const netid_t id) { mytype_t *ctx; ctx = kmem_alloc(sizeof(*ctx), KM_SLEEP); ctx->instance_id = id; net_instance_notify_register(id, mynewproto, ctx); return (ctx); }
每当在联网实例中添加或删除网络协议时,都应调用 mynewproto() 函数。如果注册的网络协议已在给定实例中运行,则会对每个已有协议调用 create 回调。
对于此回调,调用者只会填写 proto 参数。此时,提供的事件名或钩子名均无意义。在此示例函数中,将只查找通告 IPv4 协议注册的事件。
此函数的下一步是,通过使用 net_protocol_notify_register() 接口注册 mynewevent() 函数来搜索向 IPv4 协议添加事件的时间。
static int mynewproto(hook_notify_cmd_t cmd, void *arg, const char *proto, const char *event, const char *hook) { mytype_t *ctx = arg; if (strcmp(proto, NHF_INET) != 0) return (0); switch (cmd) { case HN_REGISTER : ctx->inet = net_protocol_lookup(s->id, proto); net_protocol_notify_register(s->inet, mynewevent, ctx); break; case HN_UNREGISTER : case HN_NONE : break; } return (0); }
下表列出了应能够通过 mynewproto() 回调查看到的所有三种协议。将来可能会添加新协议,因此,必须安全地舍弃所有未知协议(即,返回值为 0)。
|
由于对实例和协议的处理是动态的,因此,对每个协议下活动事件的处理也是动态的。此 API 支持两类事件:网络接口事件和包事件。
以下函数将检查是否已对 IPv4 入站包发出事件存在通告。一旦发现此通告,就会分配 hook_t 结构,该结构说明了要对每个 IPv4 入站包调用的函数。
static int mynewevent(hook_notify_cmd_t cmd, void *arg, const char *parent, const char *event, const char *hook) { mytype_t *ctx = arg; char buffer[32]; hook_t *h; if ((strcmp(event, NH_PHYSICAL_IN) == 0) && (strcmp(parent, NHF_INET) == 0)) { sprintf(buffer, "mypkthook_%s_%s", parent, event); h = hook_alloc(HOOK_VERSION); h->h_hint = HH_NONE; h->h_arg = s; h->h_name = strdup(buffer); h->h_func = mypkthook; s->hook_in = h; net_hook_register(ctx->inet, (char *)event, h); } else { h = NULL; } return (0); }
对于添加和删除的每个事件,会调用 mynewevent() 函数。以下事件可用。
|
对于包事件,将对 IP 栈中每个特定点生成一个特定事件。这样可以使您准确地选择要在包流中的哪个位置拦截包,而不会花费过多的精力去检查在内核中发生的每个包事件。而对于网络接口事件,则模型与此不同,部分原因是,事件数量比较少,而且很可能开发者会关注其中若干个事件,而不只是关注一个事件。
网络接口事件会通告以下事件之一:
创建接口 (NE_PLUMB) 或销毁接口 (NE_UNPLUMB)。
接口状态更改为打开 (NE_UP) 或关闭 (NE_DOWN)。
接口地址发生更改 (NE_ADDRESS_CHANGE)。
将来可能会添加新网络接口事件,因此,对于回调函数收到的任何未知或无法识别的事件,必须始终返回 0。
收到包时,会调用包钩子函数。此时,对于从物理网络接口到达内核的每个入站包,应调用 mypkthook() 函数。通过共享 IP 实例模型或回送接口在区域之间传输的内部生成包将不会显示。
为了说明接受一个包以及允许函数在通常情况下返回丢弃此包的条件之间的区别,以下代码将输出每第 100 个包的源和目标地址,然后丢弃此包,从而使包的丢失率为 1%。
static int mypkthook(hook_event_token_t tok, hook_data_t data, void *arg) { static int counter = 0; mytupe_t *ctx = arg; hook_pkt_event_t *pkt = (hook_pkt_event_t)data; struct ip *ip; size_t bytes; bytes = msgdsize(pkt->hpe_mb); ip = (struct ip *)pkt->hpe_hdr; counter++; if (counter == 100) { printf("drop %d bytes received from %x to %x\n", bytes, ntohl(ip->ip_src.s_addr), ntohl(ip->ip_dst.s_addr)); counter = 0; freemsg(*pkt->hpe_mp); *pkt->hpe_mp = NULL; pkt->hpe_mb = NULL; pkt->hpe_hdr = NULL; return (1); } return (0); }
通过此函数以及作为回调从包事件调用的所有其他函数收到的包将一次接收一个。通过此接口不会将包链接在一起,因此,每个调用将仅处理一个包,并且 b_next 将始终为 NULL。如果不存在其他包,则一个包可能包含通过 b_cont 链接在一起的多个 mblk_t 结构。