Linux内核中流量控制(16)

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

6. 类别操作

6.1 概述

类别操作是通过tc class命令来完成的, 当网卡使用的流控算法是可分类的(如HTB, CBQ等)时候使用, 功能是对Qdisc根节点进行划分, 定义出分类树, 同时可定义每个类别的流量限制参数,但具体那些数据属于哪一类则是通过tc filter命令来实现。

分类举例,以下命令在eth0上设置HTB流控,设置了3个类别,分别定义了各个类别的流量限制:
tc qdisc add dev eth0 root handle 1: htb default 12
tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbps
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 60kbps ceil 100kbps

类别操作的具体实现实际是通过Qdisc的类别操作来完成的, 下面的处理仅仅是一个接口处理而已, 而具体的Qdisc类别操作函数已经在分析Qdisc时介绍了, 所以也没有引入新的数据结构。
 
6.2 初始化

前面5.15.1节中的初始化处理已经包括了类别的初始化:
......
// class操作, 也就是对应tc class add/delete/modify/get等操作
  link_p[RTM_NEWTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_DELTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_GETTCLASS-RTM_BASE].doit = tc_ctl_tclass;
  link_p[RTM_GETTCLASS-RTM_BASE].dumpit = tc_dump_tclass;
......

6.3 类别控制操作
/* net/sched/sch_api.c */
static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
{
 struct tcmsg *tcm = NLMSG_DATA(n);
 struct rtattr **tca = arg;
 struct net_device *dev;
 struct Qdisc *q = NULL;
 struct Qdisc_class_ops *cops;
 unsigned long cl = 0;
 unsigned long new_cl;
// parent id
 u32 pid = tcm->tcm_parent;
// class id
 u32 clid = tcm->tcm_handle;
// qdisc id: 初始化位类别id的高16位
 u32 qid = TC_H_MAJ(clid);
 int err;
// 根据TC信息中的网卡索引值查找网卡
 if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return -ENODEV;
 /*
// 以下是tc class的parent参数取值的说明
    parent == TC_H_UNSPEC - unspecified parent.
    parent == TC_H_ROOT   - class is root, which has no parent.
    parent == X:0  - parent is root class.
    parent == X:Y  - parent is a node in hierarchy.
    parent == 0:Y  - parent is X:Y, where X:0 is qdisc.
// 以下是tc class的classid参数取值的说明
    handle == 0:0  - generate handle from kernel pool.
    handle == 0:Y  - class is X:Y, where X:0 is qdisc.
    handle == X:Y  - clear.
    handle == X:0  - root class.
  */
 /* Step 1. Determine qdisc handle X:0 */
 if (pid != TC_H_ROOT) {
// parent id非根节点的情况
  u32 qid1 = TC_H_MAJ(pid);
  if (qid && qid1) {
   /* If both majors are known, they must be identical. */
   if (qid != qid1)
    return -EINVAL;
  } else if (qid1) {
   qid = qid1;
  } else if (qid == 0)
   qid = dev->qdisc_sleeping->handle;
  /* Now qid is genuine qdisc handle consistent
     both with parent and child.
     TC_H_MAJ(pid) still may be unspecified, complete it now.
   */
  if (pid)
   pid = TC_H_MAKE(qid, pid);
 } else {
// 为根节点, 如果当前qid为0, 更新为设备的qdisc_sleeping的handle
  if (qid == 0)
   qid = dev->qdisc_sleeping->handle;
 }
 /* OK. Locate qdisc */
// 根据qid查找该dev上的Qdisc指针, 找不到的话返回失败
 if ((q = qdisc_lookup(dev, qid)) == NULL)
  return -ENOENT;
 /* An check that it supports classes */
// 获取Qdisc的类别操作指针
 cops = q->ops->cl_ops;
// 如果Qdisc是非分类的, 类别操作结构指针位空, 返回失败
 if (cops == NULL)
  return -EINVAL;
 /* Now try to get class */
// 生成合法的类别ID
 if (clid == 0) {
  if (pid == TC_H_ROOT)
   clid = qid;
 } else
  clid = TC_H_MAKE(qid, clid);
// 如果clid非0, 调用get函数获取该类别, 增加类别的引用计数
// cl虽然定义是unsigned long, 但实际是个指针的数值
 if (clid)
  cl = cops->get(q, clid);
 if (cl == 0) {
// 类别为空
  err = -ENOENT;
// 如果netlink命令不是新建类别的话, 返回错误
  if (n->nlmsg_type != RTM_NEWTCLASS || !(n->nlmsg_flags&NLM_F_CREATE))
   goto out;
 } else {
// 获取类别成功, 根据netlink命令类型进行相关操作
  switch (n->nlmsg_type) {
  case RTM_NEWTCLASS: 
// 新建class
   err = -EEXIST;
// 如果设置了互斥标志, 返回错误, 因为现在该class已经存在
   if (n->nlmsg_flags&NLM_F_EXCL)
    goto out;
   break;
  case RTM_DELTCLASS:
// 删除class
   err = cops->delete(q, cl);
   if (err == 0)
    tclass_notify(skb, n, q, cl, RTM_DELTCLASS);
   goto out;
  case RTM_GETTCLASS:
// 获取class信息, 进行class通知操作
   err = tclass_notify(skb, n, q, cl, RTM_NEWTCLASS);
   goto out;
  default:
   err = -EINVAL;
   goto out;
  }
 }
 new_cl = cl;
// 不论是新建还是修改class参数, 都是调用类别操作结构的change函数
 err = cops->change(q, clid, pid, tca, &new_cl);
// 操作成功, 进行class通知操作
 if (err == 0)
  tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);
out:
 if (cl)
  cops->put(q, cl);
 return err;
}

// 类别通知处理, 向用户层发送消息数据
static int tclass_notify(struct sk_buff *oskb, struct nlmsghdr *n,
     struct Qdisc *q, unsigned long cl, int event)
{
 struct sk_buff *skb;
// 从老数据包中查找通信进程的pid, 否则发送给所有打开netlink接口的进程
 u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;

// 分配数据包
 skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
 if (!skb)
  return -ENOBUFS;
// 填充class参数
 if (tc_fill_tclass(skb, q, cl, pid, n->nlmsg_seq, 0, event) < 0) {
  kfree_skb(skb);
  return -EINVAL;
 }
// 通过rtnetlink发送数据包, 标志位ECHO包
 return rtnetlink_send(skb, pid, RTNLGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
}

6.4 TC类输出
 
// 参数输出所用的临时数据结构
struct qdisc_dump_args
{
 struct qdisc_walker w;
 struct sk_buff *skb;
 struct netlink_callback *cb;
};

// 类别输出
static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
{
 int t;
 int s_t;
 struct net_device *dev;
 struct Qdisc *q;
 struct tcmsg *tcm = (struct tcmsg*)NLMSG_DATA(cb->nlh);
 struct qdisc_dump_args arg;
// 输入数据长度检查
 if (cb->nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*tcm)))
  return 0;
// 查找网卡设备
 if ((dev = dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return 0;
// s_t: 起始class索引
 s_t = cb->args[0];
 t = 0;
 read_lock(&qdisc_tree_lock);
// 遍历设备的Qdisc链表
 list_for_each_entry(q, &dev->qdisc_list, list) {
// 当前索引号小于起始索引号, 或者当前Qdisc是非分类的,
// 或者句柄handle不匹配, 跳过
  if (t < s_t || !q->ops->cl_ops ||
      (tcm->tcm_parent &&
       TC_H_MAJ(tcm->tcm_parent) != q->handle)) {
   t++;
   continue;
  }
// 索引号超过了起始索引号, 将从数组1号开始的数据缓冲区清零
  if (t > s_t)
   memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(cb->args[0]));
// 填写arg结构参数
// 输出单个class函数
  arg.w.fn = qdisc_class_dump;
// 数据包指针
  arg.skb = skb;
// 控制块指针
  arg.cb = cb;
// 遍历结构walker参数
  arg.w.stop  = 0;
  arg.w.skip = cb->args[1];
  arg.w.count = 0;
// 调用Qdisc类别操作结构的walk函数遍历该Qdisc所有类别
  q->ops->cl_ops->walk(q, &arg.w);
// 记录处理的类别数
  cb->args[1] = arg.w.count;
// 如果设置了停止标志, 退出循环
  if (arg.w.stop)
   break;
// 索引计数
  t++;
 }
 read_unlock(&qdisc_tree_lock);
// 找过的Qdisc数, 有的Qdisc可能是跳过没处理的
 cb->args[0] = t;
 dev_put(dev);
 return skb->len;
}
 
// 类别输出
static int qdisc_class_dump(struct Qdisc *q, unsigned long cl, struct qdisc_walker *arg)
{
 struct qdisc_dump_args *a = (struct qdisc_dump_args *)arg;
// 调用TC class填充函数
 return tc_fill_tclass(a->skb, q, cl, NETLINK_CB(a->cb->skb).pid,
         a->cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTCLASS);
}

// 填充class参数用于netlink通信返回用户层
static int tc_fill_tclass(struct sk_buff *skb, struct Qdisc *q,
     unsigned long cl,
     u32 pid, u32 seq, u16 flags, int event)
{
 struct tcmsg *tcm;
 struct nlmsghdr  *nlh;
 unsigned char  *b = skb->tail;
 struct gnet_dump d;
// Qdisc的类别操作结构指针
 struct Qdisc_class_ops *cl_ops = q->ops->cl_ops;

// 在数据包缓冲区中定位填写的信息位置
 nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*tcm), flags);
// TC信息头位置
 tcm = NLMSG_DATA(nlh);
// 填写TC信息参数
 tcm->tcm_family = AF_UNSPEC;
 tcm->tcm_ifindex = q->dev->ifindex;
 tcm->tcm_parent = q->handle;
 tcm->tcm_handle = q->handle;
 tcm->tcm_info = 0;
 RTA_PUT(skb, TCA_KIND, IFNAMSIZ, q->ops->id);
// 调用Qdisc类别参数的输出函数
 if (cl_ops->dump && cl_ops->dump(q, cl, skb, tcm) < 0)
  goto rtattr_failure;
// 进行统计
 if (gnet_stats_start_copy_compat(skb, TCA_STATS2, TCA_STATS,
   TCA_XSTATS, q->stats_lock, &d) < 0)
  goto rtattr_failure;
// 输出统计参数
 if (cl_ops->dump_stats && cl_ops->dump_stats(q, cl, &d) < 0)
  goto rtattr_failure;
 if (gnet_stats_finish_copy(&d) < 0)
  goto rtattr_failure;
// 新添加的netlink信息长度
 nlh->nlmsg_len = skb->tail - b;
// 返回数据总长度
 return skb->len;
nlmsg_failure:
rtattr_failure:
 skb_trim(skb, b - skb->data);
 return -1;
}
 
7. filter操作

7.1 概述

tc filter命令是用来定义数据包进行分类的命令, 中间就要用到各种匹配条件, 其功能就象netfilter的match一样, filter的处理和class的处理是紧密联系在一起的,用于完成对数据包的分类。

filter处理的基本api在net/sched/cls_api.c中定义, 而各种匹配方法在net/sched/cls_*.c中定义。

7.2 数据结构
/* include/net/sch_generic.h */
// tc过滤协议结构, 这个结构在流控算法的分类函数中已经见过了
struct tcf_proto
{
 /* Fast access part */
// 链表中的下一项
 struct tcf_proto *next;
// 根节点
 void   *root;
// 分类操作函数, 通常是tcf_proto_ops的classify函数, 就象Qdisc结构中的enqueue就是
// Qdisc_class_ops中的enqueue一样, 目的是向上层隐藏tcf_proto_ops结构
 int   (*classify)(struct sk_buff*, struct tcf_proto*,
     struct tcf_result *);
// 协议
 u32   protocol;
 /* All the rest */
// 优先权
 u32   prio;
// 类别ID
 u32   classid;
// 流控节点
 struct Qdisc  *q;
// 私有数据
 void   *data;
// filter操作结构
 struct tcf_proto_ops *ops;
};

// filter操作结构, 实际就是定义匹配操作, 通常每个匹配操作都由一个静态tcf_proto_ops
// 结构定义, 作为一个内核模块, 初始化事登记系统的链表
struct tcf_proto_ops
{
// 链表中的下一项
 struct tcf_proto_ops *next;
// 名称
 char   kind[IFNAMSIZ];
// 分类操作
 int   (*classify)(struct sk_buff*, struct tcf_proto*,
     struct tcf_result *);
// 初始化
 int   (*init)(struct tcf_proto*);
// 释放
 void   (*destroy)(struct tcf_proto*);
// 获取, 增加引用
 unsigned long  (*get)(struct tcf_proto*, u32 handle);
// 减少引用
 void   (*put)(struct tcf_proto*, unsigned long);
// 参数修改
 int   (*change)(struct tcf_proto*, unsigned long,
     u32 handle, struct rtattr **,
     unsigned long *);
// 删除
 int   (*delete)(struct tcf_proto*, unsigned long);
// 遍历
 void   (*walk)(struct tcf_proto*, struct tcf_walker *arg);
 /* rtnetlink specific */
// 输出
 int   (*dump)(struct tcf_proto*, unsigned long,
     struct sk_buff *skb, struct tcmsg*);
// 模块指针
 struct module  *owner;
};

// filter操作结果, 返回分类结果: 类别和类别ID
struct tcf_result
{
 unsigned long class;
 u32  classid;
};

7.3 初始化

/* net/sched/cls_api.c */
static int __init tc_filter_init(void)
{
 struct rtnetlink_link *link_p = rtnetlink_links[PF_UNSPEC];
 /* Setup rtnetlink links. It is made here to avoid
    exporting large number of public symbols.
  */
 if (link_p) {
// 定义filter操作处理函数
// 关于filter的增加/删除/获取等操作
  link_p[RTM_NEWTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
  link_p[RTM_DELTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
  link_p[RTM_GETTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
  link_p[RTM_GETTFILTER-RTM_BASE].dumpit = tc_dump_tfilter;
 }
 return 0;
}

7.4 filter控制

/* Add/change/delete/get a filter node */
// 用于增加, 修改, 删除, 获取过滤结构
static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
{
 struct rtattr **tca;
 struct tcmsg *t;
 u32 protocol;
 u32 prio;
 u32 nprio;
 u32 parent;
 struct net_device *dev;
 struct Qdisc  *q;
 struct tcf_proto **back, **chain;
// tc proto
 struct tcf_proto *tp;
 struct tcf_proto_ops *tp_ops;
 struct Qdisc_class_ops *cops;
 unsigned long cl;
// filter handle
 unsigned long fh;
 int err;
replay:
 tca = arg;
 t = NLMSG_DATA(n);
// TC信息的低16位是协议, 高16位是优先权
 protocol = TC_H_MIN(t->tcm_info);
 prio = TC_H_MAJ(t->tcm_info);
// 备份优先权参数
 nprio = prio;
 parent = t->tcm_parent;
 cl = 0;
 if (prio == 0) {
// 如果没指定优先权值, 在新建filter情况下是错误, 其他情况则构造一个缺省值
  /* If no priority is given, user wants we allocated it. */
  if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE))
   return -ENOENT;
  prio = TC_H_MAKE(0x80000000U,0U);
 }
 /* Find head of filter chain. */
 /* Find link */
// 查找网卡设备
 if ((dev = __dev_get_by_index(t->tcm_ifindex)) == NULL)
  return -ENODEV;
 /* Find qdisc */
// 查找网卡所用的Qdisc
 if (!parent) {
// 根节点的情况, 使用qdisc_sleeping
  q = dev->qdisc_sleeping;
  parent = q->handle;
// 非根节点的话根据handle查找
 } else if ((q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent))) == NULL)
  return -EINVAL;
 /* Is it classful? */
// 如果该流控不支持分类操作, 返回失败
 if ((cops = q->ops->cl_ops) == NULL)
  return -EINVAL;
 /* Do we search for filter, attached to class? */
// 低16位是子类别值
 if (TC_H_MIN(parent)) {
// 获取类别结构, cl实际就是结构指针转的unsigned long值
  cl = cops->get(q, parent);
  if (cl == 0)
   return -ENOENT;
 }
 /* And the last stroke */
// 获取过滤规则链表头地址, 因为是地址的地址, 所以这个值基本不应该是空的
 chain = cops->tcf_chain(q, cl);
 err = -EINVAL;
 if (chain == NULL)
  goto errout;
 /* Check the chain for existence of proto-tcf with this priority */
// 遍历规则链表, 这个链表是有序表, 由小到大
 for (back = chain; (tp=*back) != NULL; back = &tp->next) {
// 如果某过滤规则的优先权值大于指定的prio
  if (tp->prio >= prio) {
   if (tp->prio == prio) {
// 如果优先权相同,
    if (!nprio || (tp->protocol != protocol && protocol))
     goto errout;
   } else
// 否则优先权不同, 没有相同的优先权的节点, tp置为空
    tp = NULL;
   break;
  }
 }
// 退出循环时, *back指向要链表中插入的位置后面那个的节点
 if (tp == NULL) {
// tp为空, 当前规则中不存在指定优先权的节点
  /* Proto-tcf does not exist, create new one */
// 如果参数不全, 返回失败
  if (tca[TCA_KIND-1] == NULL || !protocol)
   goto errout;
  err = -ENOENT;
// 如果不是新建命令, 返回失败
  if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE))
   goto errout;

  /* Create new proto tcf */
// 分配新的tcf_proto结构节点
  err = -ENOBUFS;
  if ((tp = kmalloc(sizeof(*tp), GFP_KERNEL)) == NULL)
   goto errout;
  err = -EINVAL;
// 根据名称查找tp操作结构, 比如rsvp, u32, fw等
  tp_ops = tcf_proto_lookup_ops(tca[TCA_KIND-1]);
  if (tp_ops == NULL) {
#ifdef CONFIG_KMOD
// 如果当前内核中没找到的话, 使用模块方式加载后重新查找
   struct rtattr *kind = tca[TCA_KIND-1];
   char name[IFNAMSIZ];
// 检查一下名称算法合法
   if (kind != NULL &&
       rtattr_strlcpy(name, kind, IFNAMSIZ) < IFNAMSIZ) {
// 合法的话加载模块
    rtnl_unlock();
    request_module("cls_%s", name);
    rtnl_lock();
// 重新进行查找操作
    tp_ops = tcf_proto_lookup_ops(kind);
    /* We dropped the RTNL semaphore in order to
     * perform the module load.  So, even if we
     * succeeded in loading the module we have to
     * replay the request.  We indicate this using
     * -EAGAIN.
     */
    if (tp_ops != NULL) {
// 找到的话还是返回错误, 不过是EAGAIN, 会重来一次
     module_put(tp_ops->owner);
     err = -EAGAIN;
    }
   }
#endif
// 释放tcf_proto空间, 返回失败值
   kfree(tp);
   goto errout;
  }
// 查找成功的情况
// 结构空间清零
  memset(tp, 0, sizeof(*tp));
// 设置结构各参数
  tp->ops = tp_ops;
  tp->protocol = protocol;
  tp->prio = nprio ? : tcf_auto_prio(*back);
  tp->q = q;
// classify函数赋值
  tp->classify = tp_ops->classify;
  tp->classid = parent;
// 调用tp_ops的初始化函数初始化
  if ((err = tp_ops->init(tp)) != 0) {
   module_put(tp_ops->owner);
   kfree(tp);
   goto errout;
  }
  qdisc_lock_tree(dev);
// 将tp插入*back节点前面
  tp->next = *back;
// 更新*back, dummy header算法, 即使是第一次插入也是正确的
  *back = tp;
  qdisc_unlock_tree(dev);
 }
// 找到了节点, 比较一下名称, 不同的话返回错误
 else if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], tp->ops->kind))
  goto errout;
// 获取与t->tcm_handle对应的filter
 fh = tp->ops->get(tp, t->tcm_handle);
 if (fh == 0) {
// 获取filter失败
  if (n->nlmsg_type == RTM_DELTFILTER && t->tcm_handle == 0) {
// 如果是删除命令, 而且TC信息的句柄为0, 则可认为删除操作是成功的
   qdisc_lock_tree(dev);
// 将找到的tp从链表中断开
   *back = tp->next;
   qdisc_unlock_tree(dev);
// 删除通告, 释放tp
   tfilter_notify(skb, n, tp, fh, RTM_DELTFILTER);
   tcf_destroy(tp);
// err=0表示命令成功
   err = 0;
   goto errout;
  }
// 如果不是新建filter的话, 没找到filter就表示失败
  err = -ENOENT;
  if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE))
   goto errout;
 } else {
// 找到filter, 根据命令类型进行操作
  switch (n->nlmsg_type) {
  case RTM_NEWTFILTER: 
// 新建filter, 如果定义了互斥标志, 返回错误, 因为filter已经存在了
   err = -EEXIST;
   if (n->nlmsg_flags&NLM_F_EXCL)
    goto errout;
   break;
  case RTM_DELTFILTER:
// 删除filter命令, 运行tcf_proto_ops的delete函数
   err = tp->ops->delete(tp, fh);
// 如果操作成功, 发送通告消息
   if (err == 0)
    tfilter_notify(skb, n, tp, fh, RTM_DELTFILTER);
   goto errout;
  case RTM_GETTFILTER:
// 获取filter命令, 发送通告信息, 其中包含了filter的参数
   err = tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER);
   goto errout;
  default:
   err = -EINVAL;
   goto errout;
  }
 }
// 新建,修改操作都通过tcf_proto_ops的change函数完成
 err = tp->ops->change(tp, cl, t->tcm_handle, tca, &fh);
// 如果操作成功, 发送通告消息
 if (err == 0)
  tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER);
errout:
// 减少cl引用
 if (cl)
  cops->put(q, cl);
// 如果错误是EAGAIN, 重新操作
 if (err == -EAGAIN)
  /* Replay the request. */
  goto replay;
 return err;
}

/* Find classifier type by string name */
// 根据名称查找tp_proto_ops
static struct tcf_proto_ops * tcf_proto_lookup_ops(struct rtattr *kind)
{
 struct tcf_proto_ops *t = NULL;
// 要指定tp_proto_ops的名称(字符串)
 if (kind) {
  read_lock(&cls_mod_lock);
// 遍历链表
  for (t = tcf_proto_base; t; t = t->next) {
// 比较名称是否相同
   if (rtattr_strcmp(kind, t->kind) == 0) {
// 找到的话增加模块引用计数, 如果该tp_proto_ops是模块的话, 中断循环返回
    if (!try_module_get(t->owner))
     t = NULL;
    break;
   }
  }
  read_unlock(&cls_mod_lock);
 }
 return t;
}
 
// filter通告
static int tfilter_notify(struct sk_buff *oskb, struct nlmsghdr *n,
     struct tcf_proto *tp, unsigned long fh, int event)
{
 struct sk_buff *skb;
// 获取正在通信的用户进程的pid
 u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
// 分配数据包用于发送
 skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
 if (!skb)
  return -ENOBUFS;
// 填充数据到skb中
 if (tcf_fill_node(skb, tp, fh, pid, n->nlmsg_seq, 0, event) <= 0) {
  kfree_skb(skb);
  return -EINVAL;
 }
// 发送
 return rtnetlink_send(skb, pid, RTNLGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
}
 
// 填充数据包
static int
tcf_fill_node(struct sk_buff *skb, struct tcf_proto *tp, unsigned long fh,
       u32 pid, u32 seq, u16 flags, int event)
{
 struct tcmsg *tcm;
 struct nlmsghdr  *nlh;
 unsigned char  *b = skb->tail;
// 填充pid, seq, event等参数, 到缓冲区, 同时将缓冲区剩余空间清零
 nlh = NLMSG_NEW(skb, pid, seq, event, sizeof(*tcm), flags);
// TC消息头
 tcm = NLMSG_DATA(nlh);
// 填充TC消息
 tcm->tcm_family = AF_UNSPEC;
 tcm->tcm__pad1 = 0;
 tcm->tcm__pad1 = 0;
 tcm->tcm_ifindex = tp->q->dev->ifindex;
 tcm->tcm_parent = tp->classid;
 tcm->tcm_info = TC_H_MAKE(tp->prio, tp->protocol);
 RTA_PUT(skb, TCA_KIND, IFNAMSIZ, tp->ops->kind);
 tcm->tcm_handle = fh;
// 如果不是删除事件
 if (RTM_DELTFILTER != event) {
  tcm->tcm_handle = 0;
// 调用tp_ops的输出函数输出tp信息
  if (tp->ops->dump && tp->ops->dump(tp, fh, skb, tcm) < 0)
   goto rtattr_failure;
 }
// 计算netlink消息长度
 nlh->nlmsg_len = skb->tail - b;
 return skb->len;
nlmsg_failure:
rtattr_failure:
 skb_trim(skb, b - skb->data);
 return -1;
}

7.5 filter输出

// 为方便输出定义的合并各数据的结构
struct tcf_dump_args
{
 struct tcf_walker w;
 struct sk_buff *skb;
 struct netlink_callback *cb;
};

static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb)
{
 int t;
 int s_t;
 struct net_device *dev;
 struct Qdisc *q;
 struct tcf_proto *tp, **chain;
 struct tcmsg *tcm = (struct tcmsg*)NLMSG_DATA(cb->nlh);
 unsigned long cl = 0;
 struct Qdisc_class_ops *cops;
 struct tcf_dump_args arg;
// 结构中的消息长度和结构大小不符, 返回的是数据包的当前数据长度, 也就是没加新数据
 if (cb->nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*tcm)))
  return skb->len;
// 查找网卡设备
 if ((dev = dev_get_by_index(tcm->tcm_ifindex)) == NULL)
  return skb->len;
 read_lock(&qdisc_tree_lock);
// 查找相应的流控节点Qdisc
 if (!tcm->tcm_parent)
// 根节点的情况
  q = dev->qdisc_sleeping;
 else
// 非根节点的情况
  q = qdisc_lookup(dev, TC_H_MAJ(tcm->tcm_parent));
// 找不到Qdisc的话返回
 if (!q)
  goto out;
// 如果Qdisc是非分类的, 返回
 if ((cops = q->ops->cl_ops) == NULL)
  goto errout;
// 类别值非0, 查找类别结构, 找不到的话也返回
 if (TC_H_MIN(tcm->tcm_parent)) {
  cl = cops->get(q, tcm->tcm_parent);
  if (cl == 0)
   goto errout;
 }
// 过滤规则链表头地址
 chain = cops->tcf_chain(q, cl);
// 规则为空的话返回
 if (chain == NULL)
  goto errout;
// s_t是起始序号
 s_t = cb->args[0];
// 遍历规则链表
 for (tp=*chain, t=0; tp; tp = tp->next, t++) {
// 序号小于起始序号的话, 跳过
  if (t < s_t) continue;
// 优先权不匹配的话, 跳过
  if (TC_H_MAJ(tcm->tcm_info) &&
      TC_H_MAJ(tcm->tcm_info) != tp->prio)
   continue;
// 协议不匹配的话, 跳过
  if (TC_H_MIN(tcm->tcm_info) &&
      TC_H_MIN(tcm->tcm_info) != tp->protocol)
   continue;
// 对于序号超过起始序号的那些节点, 清空args[1]起始的参数空间
  if (t > s_t)
   memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(cb->args[0]));
  if (cb->args[1] == 0) {
// 高序号节点
// 填充tp信息, MULTI标志, NEWTFILTER(新建)类型
   if (tcf_fill_node(skb, tp, 0, NETLINK_CB(cb->skb).pid,
       cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTFILTER) <= 0) {
    break;
   }
// 一个tp消息
   cb->args[1] = 1;
  }
// 如果tp_ops的遍历操作为空, 跳过
  if (tp->ops->walk == NULL)
   continue;
// 遍历输出各个节点参数
  arg.w.fn = tcf_node_dump;
  arg.skb = skb;
  arg.cb = cb;
  arg.w.stop = 0;
  arg.w.skip = cb->args[1]-1;
  arg.w.count = 0;
  tp->ops->walk(tp, &arg.w);
// 数据的数量
  cb->args[1] = arg.w.count+1;
// 如果设置了stop标志, 中断
  if (arg.w.stop)
   break;
 }
 cb->args[0] = t;
errout:
 if (cl)
  cops->put(q, cl);
out:
 read_unlock(&qdisc_tree_lock);
 dev_put(dev);
 return skb->len;
}
 
// 填充tp节点
static int tcf_node_dump(struct tcf_proto *tp, unsigned long n, struct tcf_walker *arg)
{
 struct tcf_dump_args *a = (void*)arg;
// 填充tp信息到skb, MULTI标志, NEWTFILTER(新建)类型
 return tcf_fill_node(a->skb, tp, n, NETLINK_CB(a->cb->skb).pid,
        a->cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTFILTER);
}

...... 待续 ......

猜你喜欢

转载自cxw06023273.iteye.com/blog/867347
今日推荐