Linux内核中流量控制(16)
生活随笔
收集整理的這篇文章主要介紹了
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);
}
...... 待續 ......
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);
}
...... 待續 ......
轉載于:https://blog.51cto.com/enchen/158040
總結
以上是生活随笔為你收集整理的Linux内核中流量控制(16)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日期转换格式
- 下一篇: Ruby之旅—Ruby的Hello Wo