`

Linux内核中流量控制(16)

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源: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);
}

...... 待续 ......
分享到:
评论

相关推荐

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核扩展模块的P2P流量控制

    基于Linux内核的BT流量控制的原理与实现.pdf

    基于Linux内核的BT流量控制的原理与实现.pdf

    基于Linux内核扩展模块的P2P流量控制.pdf

    基于Linux内核扩展模块的P2P流量控制.pdf

    Linux内核扩展模块的P2P流量控制方法与研究.pdf

    Linux内核扩展模块的P2P流量控制方法与研究.pdf

    Linux高级路由和流量控制

    15.8. 终极的流量控制:低延迟,高速上/下载 98 15.8.1. 为什么缺省设置不让人满意 99 15.8.2. 实际的脚本(CBQ) 100 15.8.3. 实际的脚本(HTB) 102 15.9. 为单个主机或子网限速 103 15.10. 一个完全NAT和QOS的范例...

    基于Linux LQL流量控制系统的研究与实现

    该方法摒弃了传统方法中所运用的TC命令解析,netlink传输,内核空间执行的3层结构,而直接在Linux内核的框架下,采用LQL库直接对内核进行操控,并改进了相关U32过滤器以对IP段的流量控制,从而实现对系统的智能流量控制。...

    编写Linuxc操作系统设备驱动程序概述

    在Linux内核的不断升级过程中,驱动程序的结构还是相对稳定。Linux的网络系统主要是基于BSD unix的socket机制 。在系统和驱动程序之间定义有专门的数据结构(sk_buff)进行数据的传递。系统里支持对发送数据和接收数据...

    xt_fset:扩展到Linux内核netfilter子系统(iptables)(插件),使您可以通过发送ICMP包远程操作linux内核ipset(向ipset中添加或删除一些ip地址)。

    xt_fset是linux内核netfilter子系统的内核模块和iptables扩展(插件),允许您通过发送控制ICMP数据包来远程操作linux内核ipset(在ipset中添加或删除一些ip地址)。 该插件的创建是对Linux内核netfilter子系统的...

    Linux Kernel v4.19.1 Stable.zip

    Linux Kernel主要新特性包括:合并了来自Android项目的内核代码,支持新的架构TI C6X,改进了Btrfs...网络优先控制组允许管理员动态设置网络流量的优先次序;支持EFI引导固件;改进内存管理,等等。 Linux Kernel截图

    Linux的高级路由和流量控制HOWTO-中文版

    虽然这些工具能够工作,但它们在 Linux2.2 和更高版本的内核上显 得有一些落伍。比如,现在 GRE 隧道已经成为了路由的一个主要概念,但却不 能通过上述工具来配置。 使用了 iproute2,隧道的配置与其他部分完全集成了。

    lksctp-rs:Rust 的 Linux 内核 SCTP 低级绑定

    流量控制和拥塞控制 此外,它还补充说: 多路复用流:通过单个连接,您可以多路复用多个信息流。 端点的多宿主:一个端点可以有多个 IP 地址,允许网络故障转移/可靠性(注意:它需要是一台机器,或者复制端点的...

    《精通Linux 设备驱动程序开发》.(Sreekrishnan).pdf

    15.2.3 流量控制315 15.3 缓冲区管理和并发控制315 15.4 设备实例:以太网nic316 15.5 isa网络驱动程序321 15.6 atm321 15.7 网络吞吐量322 15.7.1 驱动程序性能322 15.7.2 协议性能323 15.8 查看...

    精通LINUX设备驱动程序开发

    311 15.1.6 统计 312 15.1.7 配置 313 15.1.8 总线相关内容 314 15.2 与协议层会话 314 15.2.1 接收路径 314 15.2.2 发送路径 315 15.2.3 流量控制 315 15.3 缓冲区管理和并发控制 315 15.4 设备实例:...

    基于Linux 的防火墙技术研究

    络地址转换、流量控制及高级的包处理等。Netfilter/Iptables 系统采用模块化的架构方式,其主要模块 有:通用框架Netfilter、数据包选择系统、连接跟踪系统及NAT系统等等。 2.1 Netfilter/Iptables 系统工作原理 ...

    Linux模拟网络丢包与延迟的方法

    netem 与 tc: netem 是 Linux 2.6 及以上...tc 是 Linux 系统中的一个工具,全名为traffic control(流量控制)。tc 可以用来控制 netem 的工作模式,也就是说,如果想使用 netem ,需要至少两个条件,一个是内核中的

    DarkShell_Linux-Win集群版V2014年

    Linux支持路由内核、2.6、3.1等普通内核,路由内核支持路由三大内核、Ubuntu、admin等,独立开发的Linux穿盾CC模式,SYN稳定发包100%,自启动,无需Root权限上线即可发包。 VIP版本攻击代码实时更新,通过服务器...

    ip route2 源码 第二代网络工具

    如果你仍在使用net-tools,而且尤其需要跟上新版Linux内核中的最新最重要的网络特性的话,那么是时候转到iproute2的阵营了。原因就在于使用iproute2可以做很多net-tools无法做到的事情。对于那些想要转到使用iproute...

    Kali Linux渗透测试(安全牛).txt

    │ 任务085:答疑(Conky、、Linux4.4内核发布),手动漏洞挖掘.mp4 │ 任务086:手动漏洞挖掘(二).mp4 │ 任务087:手动漏洞挖掘(三).mp4 │ 任务088:手动漏洞挖掘(四).mp4 │ 任务089:KALI版本更新(第一个...

Global site tag (gtag.js) - Google Analytics