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);
}

...... 待续 ......
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值