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 12tc class add dev eth0 parent 1: classid 1:1 htb rate 100kbps ceil 100kbpstc class add dev eth0 parent 1:1 classid 1:10 htb rate 30kbps ceil 100kbpstc class add dev eth0 parent 1:1 classid 1:11 htb rate 10kbps ceil 100kbpstc 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操作结果, 返回分类结果: 类别和类别IDstruct 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_opsstatic 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 inttcf_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);}...... 待续 ......