`

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网络体系结构 Linux内核中网络协议的设计与实现

    在Linux内核中,TCP和UDP模块处理连接建立、数据传输、流量控制和拥塞控制等问题。 5. **应用层**:这一层包含各种应用协议,如HTTP、FTP、SMTP等,它们直接与用户交互。Linux内核通过socket API为上层应用提供了与...

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

    【基于Linux内核的BT流量控制的原理与实现】 Linux操作系统以其开源、可定制的特点,在系统开发领域广泛应用,尤其在网络流量控制方面具有显著优势。针对BitTorrent(BT)这种大量占用带宽的P2P协议,Linux内核提供...

    Linux内核完全注释V3.0_linux内核_linux_

    4. **网络堆栈**:从硬件接口到应用层协议的整个网络传输流程,如TCP/IP协议族、套接字API、网络设备驱动程序以及流量控制策略。 5. **设备驱动**:内核如何与硬件交互,驱动程序的工作原理,包括字符设备、块设备...

    深入分析Linux内核源码

    通过分析源码,我们可以了解到数据包的接收与发送过程,理解TCP连接的建立与断开、拥塞控制、流量控制等机制,这对于网络编程和网络故障排查非常有帮助。 此外,Linux内核还涉及中断处理、设备驱动、I/O管理等多个...

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

    【基于Linux内核扩展模块的P2P流量控制】这篇文献主要探讨了如何在Linux操作系统中,通过内核扩展模块来实现对P2P流量的有效控制。P2P(Peer-to-Peer)技术的兴起改变了互联网的中心化结构,使得资源分享更为便捷,...

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

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

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

    基于LQL库的流量控制方法可以直接在Linux内核的框架下实现,而不需要使用传统方法中的TC命令解析、netlink传输和内核空间执行的三层结构。这可以提高流量控制的效率和可靠性,同时也可以减少流量控制的延迟和资源...

    Linux内核修炼之道精华版

    书中的内容涵盖了从内核基础到高级技术的方方面面,为那些希望提升Linux内核理解和开发能力的读者提供了宝贵的资源。在本文中,我们将探讨几个关键的知识点,包括Linux内核的基本结构、进程管理、内存管理和设备驱动...

    Linux内核源码(2.6.24)

    2.6.24版本在网络方面加强了IPv6的支持,并改进了网络流量控制算法。 6. **安全与权限管理**:Linux内核采用了用户和组的概念,通过权限系统(如chmod、chown等)来控制文件访问。此外,还有SELinux(Security-...

    深入理解linux内核word版本

    接着,作者深入剖析了网络设备数据结构net_device,它包含了设备的配置信息、统计信息、状态标志以及各种管理列表和流量控制字段,这些细节揭示了网络设备如何在内核中被抽象和管理。 通过以上内容,我们可以看到,...

    Linux内核情景分析(上下全集高清版)

    它处理网络数据的接收和发送,进行网络层的路由选择,以及传输层的拥塞控制和流量控制。 5. **设备驱动**:设备驱动程序是内核与硬件之间的桥梁,使得内核能够控制硬件设备。Linux内核支持大量设备驱动,包括块设备...

    基于Linux的网络流量控制机制

    该模型内置于Linux内核中,并利用队列算法对不同服务质量(Quality of Service, QoS)需求的数据流进行分类,以提供灵活且差异化的服务。实验结果表明,采用该流量控制模型后,网络性能显著提高,并能很好地适应未来...

    linux高级路由和流量控制HOWTO中文版(牛老师译)

    在Linux操作系统中,高级路由和流量控制是网络管理员和系统管理员必须掌握的关键技能。这篇文档“Linux高级路由和流量控制HOWTO中文版”由牛老师翻译,为读者提供了深入理解这些概念的宝贵资源。以下是对其中核心...

    《Linux内核源码剖析 TCP IP实现(上册) 樊东东 莫澜 pdf扫描版.

    同时,还会讨论TCP的流量控制和拥塞控制机制,如滑动窗口、慢启动、快速重传和快速恢复算法等,这些都是保证网络通信质量和效率的关键。 其次,关于IP协议,书里会涉及IP地址、子网掩码、路由选择等概念,以及IP分...

    TC(linux下流量控制工具)详细说明及应用实例借鉴.pdf

    TC 工具基于 Linux 内核的网络设备驱动程序,通过对网络设备的控制,来实现流量控制。TC 的工作原理可以分为以下三个阶段: 1. 流量控制方式:TC 提供了多种流量控制方式,包括 Token Bucket Filter(TBF)、...

    TC(linux下流量控制工具)详细说明及应用实例.pdf

    TC(Linux 下流量控制工具)详细说明及应用实例 TC 是 Linux 下的一种流量控制工具,用于控制和管理网络流量。它提供了一个灵活的方式来管理网络带宽、延迟和丢包率等网络性能参数,以满足不同应用场景的需求。 TC...

    linux内核协议栈源码解析(2.6.18内核)

    2. **TCP/IP协议**:在传输层,TCP(传输控制协议)提供可靠的数据传输服务,通过确认、重传和流量控制确保数据的完整性和顺序。IP(互联网协议)在网络层负责数据包的路由和分片,是互联网的基础协议。 3. **套接...

    linux内核中sock和socket数据结构

    Linux内核中的sock和socket数据结构是网络编程的核心组成部分,它们是实现网络通信的基础构件。在Linux操作系统中,网络通信的实现依赖于BSD套接字接口,而这一接口在内核中是通过sock和socket数据结构来实现的。 ...

Global site tag (gtag.js) - Google Analytics