本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严
禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源: http://yfydz.cublog.cn

1. 前言

在linux-2.6.1*版本的内核中nfnetlink正式纳入了官方发布版本中,nfnetlink通过使用子系统扩展了
原来的ip_queue的功能,使之可以处理不同类型的数据。

以下代码的内核版本为2.6.19.2。

2. 数据结构
/* include/linux/netfilter/nfnetlink.h */
// netnetlink子系统结构
struct nfnetlink_subsystem
{
// 子系统名称
 const char *name;
// 子系统的ID号
 __u8 subsys_id;  /* nfnetlink subsystem ID */
// 子系统中回调控制块的数量
// 不知道为什么用两个__u8定义,对于结构总是4字节对齐的,浪费了两字节
 __u8 cb_count;  /* number of callbacks */
// 回调控制块数组
 struct nfnl_callback *cb; /* callback for individual types */
};

// 回调控制块数据结构
struct nfnl_callback
{
// 回调函数
 int (*call)(struct sock *nl, struct sk_buff *skb,
  struct nlmsghdr *nlh, struct nfattr *cda[], int *errp);
// nfnetlink属性数量
 u_int16_t attr_count; /* number of nfattr's */
};

// nfnetlink子系统类型定义
#define NFNL_SUBSYS_NONE   0
#define NFNL_SUBSYS_CTNETLINK  1
#define NFNL_SUBSYS_CTNETLINK_EXP 2
#define NFNL_SUBSYS_QUEUE  3
#define NFNL_SUBSYS_ULOG  4
#define NFNL_SUBSYS_COUNT  5
// netlink数据类型转换为子系统ID和消息类型的宏
// 16位数,高8位为ID号, 低8位为消息类型
#define NFNL_SUBSYS_ID(x) ((x & 0xff00) >> 8)
#define NFNL_MSG_TYPE(x) (x & 0x00ff)

/* net/netfilter/nfnetlink.c */
// nfnetlink子系统指针数组, 目前共5个元素
static struct nfnetlink_subsystem *subsys_table[NFNL_SUBSYS_COUNT];

3. 子系统操作
/* net/netfilter/nfnetlink.c */
3.1 登记
// 将一个nfnetlink子系统登记到子系统指针数组中
int nfnetlink_subsys_register(struct nfnetlink_subsystem *n)
{
 DEBUGP("registering subsystem ID %u\n", n->subsys_id);
// 操作是要加锁的
 nfnl_lock();
// 每个ID只能登记一个子系统
 if (subsys_table[n->subsys_id]) {
  nfnl_unlock();
  return -EBUSY;
 }
// 占上数组中的位置
 subsys_table[n->subsys_id] = n;
 nfnl_unlock();
 return 0;
}

3.2 撤销

int nfnetlink_subsys_unregister(struct nfnetlink_subsystem *n)
{
 DEBUGP("unregistering subsystem ID %u\n", n->subsys_id);
 nfnl_lock();
// 直接清空数组中位置,不管原来是否已经存在
 subsys_table[n->subsys_id] = NULL;
 nfnl_unlock();
 return 0;
}

3.3 查找

static inline struct nfnetlink_subsystem *nfnetlink_get_subsys(u_int16_t type)
{
// 提取子系统的ID值
 u_int8_t subsys_id = NFNL_SUBSYS_ID(type);
// 基本判断一下数据范围
 if (subsys_id >= NFNL_SUBSYS_COUNT
     || subsys_table[subsys_id] == NULL)
  return NULL;
// 返回数组中的元素
 return subsys_table[subsys_id];
}

3.4 找控制函数
static inline struct nfnl_callback *
nfnetlink_find_client(u_int16_t type, struct nfnetlink_subsystem *ss)
{
// 获取消息类型,即控制块回调函数的索引号
 u_int8_t cb_id = NFNL_MSG_TYPE(type);
// 判断是否超过当前控制块回调函数的数量
 if (cb_id >= ss->cb_count) {
  DEBUGP("msgtype %u >= %u, returning\n", type, ss->cb_count);
  return NULL;
 }
// 返回控制块指针
 return &ss->cb[cb_id];
}

4. 具体应用

4.1 nfnetlink_queue
/* net/netfilter/nfnetlink_queue.c */
4.1.1 初始化
static int __init nfnetlink_queue_init(void)
{
......
// 登记nf_queue子系统
 status = nfnetlink_subsys_register(&nfqnl_subsys);
 if (status < 0) {
  printk(KERN_ERR "nf_queue: failed to create netlink socket\n");
  goto cleanup_netlink_notifier;
 }
......
}

4.1.2 nf_queue子系统定义
static struct nfnetlink_subsystem nfqnl_subsys = {
 .name  = "nf_queue",
 .subsys_id = NFNL_SUBSYS_QUEUE,
 .cb_count = NFQNL_MSG_MAX, /* 3 */
 .cb  = nfqnl_cb,
};
控制块:
static struct nfnl_callback nfqnl_cb[NFQNL_MSG_MAX] = {
// 处理数据包, 不过在nf_queue中实际是个空函数
 [NFQNL_MSG_PACKET] = { .call = nfqnl_recv_unsupp,
        .attr_count = NFQA_MAX, },
// 处理对数据包的判断结果
 [NFQNL_MSG_VERDICT] = { .call = nfqnl_recv_verdict,
        .attr_count = NFQA_MAX, },
// 处理对queue的配置信息, 如绑定,取消绑定, 设置模式等
 [NFQNL_MSG_CONFIG] = { .call = nfqnl_recv_config,
        .attr_count = NFQA_CFG_MAX, },
};
 
4.2 nfnetlink_log

/* net/netlink/nfnetlink_log.c */

4.2.1 初始化
static int __init nfnetlink_log_init(void)
{
......
 status = nfnetlink_subsys_register(&nfulnl_subsys);
 if (status < 0) {
  printk(KERN_ERR "log: failed to create netlink socket\n");
  goto cleanup_netlink_notifier;
 }
......
}

4.2.2  nfnetlink log子系统定义

static struct nfnetlink_subsystem nfulnl_subsys = {
 .name  = "log",
 .subsys_id = NFNL_SUBSYS_ULOG,
 .cb_count = NFULNL_MSG_MAX,
 .cb  = nfulnl_cb,
};

控制块:
static struct nfnl_callback nfulnl_cb[NFULNL_MSG_MAX] = {
// PACKET操作没定义
 [NFULNL_MSG_PACKET] = { .call = nfulnl_recv_unsupp,
        .attr_count = NFULA_MAX, },
// 处理对nf_log的配置信息, 如绑定,取消绑定, 设置模式等
 [NFULNL_MSG_CONFIG] = { .call = nfulnl_recv_config,
        .attr_count = NFULA_CFG_MAX, },
// 数据发送到用户层后不需要返回判断,所以没有VETDICT
};

5. 数据流程
以下分析netlink数据包如何分配进入各个子系统处理:
在nfnetlink初始化时, 将nfnetlink_rcv函数作为nfnetlink接口的接收数据函数:
/* net/netfilter/nfnetlink.c */
......
 nfnl = netlink_kernel_create(NETLINK_NETFILTER, NFNLGRP_MAX,
                              nfnetlink_rcv, THIS_MODULE);
......

static void nfnetlink_rcv(struct sock *sk, int len)
{
 do {
  struct sk_buff *skb;
  if (nfnl_shlock_nowait())
   return;
  while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) {
// 对skb数据包的基本处理函数
   if (nfnetlink_rcv_skb(skb)) {
    if (skb->len)
     skb_queue_head(&sk->sk_receive_queue,
             skb);
    else
     kfree_skb(skb);
    break;
   }
   kfree_skb(skb);
  }
  /* don't call nfnl_shunlock, since it would reenter
   * with further packet processing */
  up(&nfnl_sem);
 } while(nfnl && nfnl->sk_receive_queue.qlen);
}

/* Process one packet of messages. */
static inline int nfnetlink_rcv_skb(struct sk_buff *skb)
{
 int err;
 struct nlmsghdr *nlh;
 while (skb->len >= NLMSG_SPACE(0)) {
  u32 rlen;
// 对netlink消息长度的检查和对齐处理
  nlh = (struct nlmsghdr *)skb->data;
  if (nlh->nlmsg_len < sizeof(struct nlmsghdr)
      || skb->len < nlh->nlmsg_len)
   return 0;
  rlen = NLMSG_ALIGN(nlh->nlmsg_len);
  if (rlen > skb->len)
   rlen = skb->len;
// 基本的netlink消息处理函数
  if (nfnetlink_rcv_msg(skb, nlh, &err)) {
   if (!err)
    return -1;
   netlink_ack(skb, nlh, err);
  } else
   if (nlh->nlmsg_flags & NLM_F_ACK)
    netlink_ack(skb, nlh, 0);
  skb_pull(skb, rlen);
 }
 return 0;
}

/* Process one complete nfnetlink message. */
static int nfnetlink_rcv_msg(struct sk_buff *skb,
        struct nlmsghdr *nlh, int *errp)
{
// 回调处理结构
 struct nfnl_callback *nc;
// 子系统
 struct nfnetlink_subsystem *ss;
 int type, err = 0;
 DEBUGP("entered; subsys=%u, msgtype=%u\n",
   NFNL_SUBSYS_ID(nlh->nlmsg_type),
   NFNL_MSG_TYPE(nlh->nlmsg_type));
// 判断是否具备CAP_NET_ADMIN权限,只能是ROOT才能打开netlink接口
 if (security_netlink_recv(skb, CAP_NET_ADMIN)) {
  DEBUGP("missing CAP_NET_ADMIN\n");
  *errp = -EPERM;
  return -1;
 }
 /* Only requests are handled by kernel now. */
// 只处理请求类型数据, 数据方向是用户空间发送到内核
 if (!(nlh->nlmsg_flags & NLM_F_REQUEST)) {
  DEBUGP("received non-request message\n");
// 不处理但不认为出错
  return 0;
 }
 /* All the messages must at least contain nfgenmsg */
// 判断消息长度是否合法
 if (nlh->nlmsg_len < NLMSG_SPACE(sizeof(struct nfgenmsg))) {
  DEBUGP("received message was too short\n");
// 不处理但不认为出错
  return 0;
 }
// 消息类型
 type = nlh->nlmsg_type;
// 根据消息类型查找子系统
 ss = nfnetlink_get_subsys(type);
 if (!ss) {
// 找不到子系统出错
#ifdef CONFIG_KMOD
  /* don't call nfnl_shunlock, since it would reenter
   * with further packet processing */
  up(&nfnl_sem);
  request_module("nfnetlink-subsys-%d", NFNL_SUBSYS_ID(type));
  nfnl_shlock();
  ss = nfnetlink_get_subsys(type);
  if (!ss)
#endif
   goto err_inval;
 }
// 查找对该类型的消息的回调处理结构
 nc = nfnetlink_find_client(type, ss);
 if (!nc) {
// 找不到出错
  DEBUGP("unable to find client for type %d\n", type);
  goto err_inval;
 }
 {
  u_int16_t attr_count =
   ss->cb[NFNL_MSG_TYPE(nlh->nlmsg_type)].attr_count;
  struct nfattr *cda[attr_count];
// 处理消息中的后续属性
  memset(cda, 0, sizeof(struct nfattr *) * attr_count);
  
  err = nfnetlink_check_attributes(ss, nlh, cda);
  if (err < 0)
   goto err_inval;
  DEBUGP("calling handler\n");
// 将消息属性作为参数调用子系统回调控制块中的相应回调函数进行处理
  err = nc->call(nfnl, skb, nlh, cda, errp);
  *errp = err;
  return err;
 }
err_inval:
 DEBUGP("returning -EINVAL\n");
 *errp = -EINVAL;
 return -1;
}
 
6. 结论
nfnetlink的子系统处理模式使得netfilter只需要一个netlink接口就可以处理不同类型的通信, 而不
需要打开多个netlink接口, 不过目前类型还是比较少, 只有queue和log, 原来2.4补丁中的conntrack
好象没有了?