一、ingress入口排队规则模块初始化
ingress_module_init
//注册INGRESS类型排队规则
register_qdisc(&ingress_qdisc_ops)
write_lock(&qdisc_mod_lock);
//查找如果排列规则类链表中如果已经注册,则直接跳出
for (qp = &qdisc_base; (q = *qp) != NULL; qp = &q->next)
if (!strcmp(qops->id, q->id))
goto out;
//如果当前注册的排队规则中没有对应的回调,则使用noop的默认值
if (qops->enqueue == NULL)
qops->enqueue = noop_qdisc_ops.enqueue;
if (qops->requeue == NULL)
qops->requeue = noop_qdisc_ops.requeue;
if (qops->dequeue == NULL)
qops->dequeue = noop_qdisc_ops.dequeue;
//将当前注册的排队规则加入到qdisc_base链表末尾
qops->next = NULL;
*qp = qops;
out:
write_unlock(&qdisc_mod_lock);
二、包调度API子系统初始化
pktsched_init
//计算出每纳秒多少TICKET,以及每TICKET多少纳秒
#ifdef CONFIG_NET_SCH_CLK_CPU
psched_calibrate_clock()
#elif defined(CONFIG_NET_SCH_CLK_JIFFIES)
psched_tick_per_us = HZ<
三、TC过滤子系统初始化
tc_filter_init
//向ROUTE类型的netlink套接口注册过滤相关的消息处理函数
link_p = rtnetlink_links[PF_UNSPEC];
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;
四、FW分类器模块初始化
init_fw
//向tcf_proto_base链表中注册FW分类器
register_tcf_proto_ops(&cls_fw_ops);
for (tp = &tcf_proto_base; (t = *tp) != NULL; tp = &t->next)
if (!strcmp(ops->kind, t->kind))
goto out;
ops->next = NULL;
*tp = ops;
rc = 0;
out:
return rc;
五、给网络接口设置INGRESS排队规则
命令:tc qdisc add dev eth0 ingress
1、用户层代码
//初始化,获取每纳秒对应多少TICKET
tc_core_init();
fp = fopen("/proc/net/psched", "r");
fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);
fclose(fp);
if (clock_res == 1000000000)
t2us = us2t;
clock_factor = (double)clock_res / TIME_UNITS_PER_SEC;
tick_in_usec = (double)t2us / us2t * clock_factor;
//创建一个ROUTE类型的netlink套接口
rtnl_open(&rth, 0)
rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))
setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))
rth->local.nl_family = AF_NETLINK;
rth->local.nl_groups = subscriptions; //0
bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))
rth->seq = time(NULL);
do_cmd(argc-1, argv+1);
if (matches(*argv, "qdisc") == 0)
//执行设置排队规则的命令
do_qdisc(argc-1, argv+1);
if (matches(*argv, "add") == 0)
tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE,
argc-1, argv+1);
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
req.n.nlmsg_flags = NLM_F_REQUEST|flags;
req.n.nlmsg_type = cmd; //RTM_NEWQDISC
req.t.tcm_family = AF_UNSPEC;
while (argc > 0)
if (strcmp(*argv, "dev") == 0)
NEXT_ARG();
strncpy(d, *argv, sizeof(d)-1); //eth0
else if (strcmp(*argv, "ingress") == 0)
//ingress排队规则没有父类,所以会设置特定的值
req.t.tcm_parent = TC_H_INGRESS;
//如果有/usr/lib/tc/ingress.so动态库中则从中获
//取ingress_qdisc_util符号结构,否则检测当前tc
//程序是否有ingress_qdisc_util符号结构则从中获取
//,否则返回q 为空。
strncpy(k, "ingress", sizeof(k)-1);
q = get_qdisc_kind(k);
//ingress排队规则特定的句柄
req.t.tcm_handle = 0xffff0000;
//在消息尾部追加属性值
//rta->rta_type = type; //TCA_KIND
//rta->rta_len = len;
//属性值为 “ingress”
addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
//当前q为空
if (q)
//不走此流程
//根据接口名获取接口索引
if (d[0])
idx = ll_name_to_index(d)
req.t.tcm_ifindex = idx;
//给内核发送该netlink消息
rtnl_talk(&rth, &req.n, 0, 0, NULL)
rtnl_close(&rth);
2、内核层代码
用户侧发出RTM_NEWQDISC套接口消息后,在内核侧对应的处理回调函数为tc_modify_qdisc,该函数是在pktsched_init中初始化的。
tc_modify_qdisc
tcm = NLMSG_DATA(n);
clid = tcm->tcm_parent; //当前用户侧传入值为 TC_H_INGRESS
//根据设备索引获取设备对象,上面用户侧传入设备名为eth0
dev = __dev_get_by_index(tcm->tcm_ifindex)
if (clid)
//ingress类型入口排队规则比较特殊,使用单独的qdisc_ingress
if (clid != TC_H_ROOT)
if (clid != TC_H_INGRESS)
//不走此流程
else
q = dev->qdisc_ingress;
if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle)
if (tcm->tcm_handle) //用户侧传入为特定的0xffff0000
//当前设备的qdisc_list排队规则链表中不含有此规则,进行创建
if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
goto create_n_graft;
create_n_graft:
if (clid == TC_H_INGRESS)
//创建排队规则
q = qdisc_create(dev, tcm->tcm_parent, tca, &err);
//从已经注册到qdisc_base链表中获取匹配排队规则,当前ingress已经注册
//,则ops = ingress_qdisc_ops
ops = qdisc_lookup_ops(kind);
sch = qdisc_alloc(dev, ops);
INIT_LIST_HEAD(&sch->list);
skb_queue_head_init(&sch->q); //初始化规则中的SKB队列
sch->ops = ops; //ingress_qdisc_ops
sch->enqueue = ops->enqueue; //ingress_enqueue
sch->dequeue = ops->dequeue; //ingress_dequeue
sch->dev = dev; //eth0设备对象
dev_hold(dev); //设备对象引用递增
sch->stats_lock = &dev->queue_lock;
atomic_set(&sch->refcnt, 1);
if (handle == TC_H_INGRESS)
sch->flags |= TCQ_F_INGRESS;
//handle = 0xFFFF0000
handle = TC_H_MAKE(TC_H_INGRESS, 0);
sch->handle = handle;
//使用排队规则中的初始化回调进行初始化,当前ingress的回调函数为
//ingress_init
ops->init(sch, tca[TCA_OPTIONS-1])
ingress_init(tca[TCA_OPTIONS-1])
ingress_qdisc_data *p = PRIV(sch); //指向排队规则的私有数据
//当没有开启分类动作编译功能宏时,使用netfilter的钩子来实现
//ingress的分类处理。之前在《网卡驱动收包》小节分析收包时,
//也在netif_receive_skb函数中看到有对
//CONFIG_NET_CLS_ACT功能宏的处理,也就是说如果该功能宏
//开启,则ingress入口排队规则处理从netif_receive_skb接口进入。
//否则,就在netfilter的基础上从PRE_ROUTING链上注册的钩子
//函数ing_hook进入。
#ifndef CONFIG_NET_CLS_ACT
#ifdef CONFIG_NETFILTER
//向netfillter的nf_hooks中注册IPV4和IPV6的钩子处理函数。
//当前ingress将钩子放置在netfilter的PRE_ROUTING链上,优先级
//在FILTER过滤的优先级之后,钩子回调函数分别为ing_hook
nf_register_hook(&ing_ops)
nf_register_hook(&ing6_ops)
#endif
#endif
p->q = &noop_qdisc; //私有数据中存储的q为无效的排队规则
//将当前排队规则加入到设备的qdisc_list链表中
qdisc_lock_tree(dev);
list_add_tail(&sch->list, &dev->qdisc_list);
qdisc_unlock_tree(dev);
//排队规则嫁接处理
qdisc_graft(dev, p, clid, q, &old_q);
//ingress类型排队规则没有父类
if (parent == NULL)
//将排队规则加入到设备根规则上,其中ingress类型的设置到特殊的
//dev->qdisc_ingress位置
dev_graft_qdisc(dev, new);
//设备激活的情况下,先去激活
if (dev->flags & IFF_UP)
dev_deactivate(dev);
qdisc_lock_tree(dev);
//把当前构造好的排队规则设置到qdisc_ingress
if (qdisc && qdisc->flags&TCQ_F_INGRESS)
dev->qdisc_ingress = qdisc;
qdisc_unlock_tree(dev);
//激活设备
if (dev->flags & IFF_UP)
dev_activate(dev);
//当前old_q老的排队规则不存在,仅存在新的,发送netlink消息,告知添加成功
qdisc_notify(skb, n, clid, old_q, q);
六、设置速率限制
命令:tc filter add dev eth0 parent ffff: protocol ip prio 50 handle 1 fw police rate 1kbit burst 40 mtu 9k drop flowid :1
1、用户层代码
//初始化,获取每纳秒对应多少TICKET
tc_core_init();
fp = fopen("/proc/net/psched", "r");
fscanf(fp, "%08x%08x%08x", &t2us, &us2t, &clock_res);
fclose(fp);
if (clock_res == 1000000000)
t2us = us2t;
clock_factor = (double)clock_res / TIME_UNITS_PER_SEC;
tick_in_usec = (double)t2us / us2t * clock_factor;
//创建一个ROUTE类型的netlink套接口
rtnl_open(&rth, 0)
rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
setsockopt(rth->fd,SOL_SOCKET,SO_SNDBUF,&sndbuf,sizeof(sndbuf))
setsockopt(rth->fd,SOL_SOCKET,SO_RCVBUF,&rcvbuf,sizeof(rcvbuf))
rth->local.nl_family = AF_NETLINK;
rth->local.nl_groups = subscriptions; //0
bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local))
rth->seq = time(NULL);
do_cmd(argc-1, argv+1);
do_filter(argc-1, argv+1);
tc_filter_modify(RTM_NEWTFILTER, NLM_F_EXCL|NLM_F_CREATE, argc-1,
argv+1);
req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
req.n.nlmsg_flags = NLM_F_REQUEST|flags;
req.n.nlmsg_type = cmd; //RTM_NEWTFILTER
req.t.tcm_family = AF_UNSPEC;
//新建的过滤规则,如果没有设置protocol,则默认为匹配、所有以太网下
//的上层协议类型
if (cmd == RTM_NEWTFILTER && flags & NLM_F_CREATE)
protocol = htons(ETH_P_ALL);
while (argc > 0)
if (strcmp(*argv, "dev") == 0)
NEXT_ARG();
strncpy(d, *argv, sizeof(d)-1); //eth0
else if (strcmp(*argv, "parent") == 0)
NEXT_ARG();
get_tc_classid(&handle, *argv); //将输入字符转换成类ID
req.t.tcm_parent = handle; //类ID为 0xFFFF0000
else if (matches(*argv, "protocol") == 0)
NEXT_ARG();
ll_proto_a2n(&id, *argv)
protocol = id; //ETH_P_IP
else if (matches(*argv, "priority") == 0)
NEXT_ARG();
get_u32(&prio, *argv, 0) //50
else if (strcmp(*argv, "handle") == 0)
NEXT_ARG();
fhandle = *argv; //1
else
//如果有/usr/lib/tc/f_fw.so共享库,则从中获取fw_filter_util的符号结 //构,否则使用tc程序中的fw_filter_util的符号结构,当前假设从tc
//程序中取fw_filter_util符号结构。
strncpy(k, *argv, sizeof(k)-1);
q = get_filter_kind(k);
//高16位为优先级,低16位为匹配协议
req.t.tcm_info = TC_H_MAKE(prio<<16, protocol);
//在消息尾部追加KIND属性项
//rta->rta_type = type; //TCA_KIND
//rta->rta_len = len;
//属性值为 “fw”
addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
//进行fw规则后继的解析处时
//q->parse_fopt当前为fw_parse_opt
q->parse_fopt(q, fhandle, argc, argv, &req.n)
fw_parse_opt
//当前handle为1,在fw规则中,命令行中的handle值用于匹配
//iptables规则的mark,就是说fw规则需要和iptables进行配合处理
//比如此时我们命令行中handle 1作用所有经过PREROUTING链
//中的TCP报文,则iptables的规则设置为
//iptables -A PREROUTING -i eth0 -t managle -p tcp -j MARK
// --set-mark 1
if (handle)
get_u32(&t->tcm_handle, handle, 0) //tcm_handle = 1
//在消息尾部增加OPTIONS属性项,值为空值
addattr_l(n, 4096, TCA_OPTIONS, NULL, 0);
while (argc > 0)
else if (matches(*argv, "police") == 0)
NEXT_ARG();
//策略规则解析
parse_police(&argc, &argv, TCA_FW_POLICE, n)
act_parse_police(NULL,argc_p,argv_p,tca_id,n);
//1kbit * 1000 / 8 = 125bps
get_rate(&p.rate.rate, *argv)
//buffer = 40byte
get_size_and_cell(&buffer, &Rcell_log, *argv)
//mtu = 9 * 1024 byte
get_size_and_cell(&mtu, &Pcell_log, *argv)
//drop规则
p.action = TC_POLICE_SHOT;
if (p.rate.rate)
p.rate.mpu = mpu; //9 * 1024 byte
p.rate.overhead = overhead; //0 当前没配置
//计算速率表
//Pcell_log = -1
//mtu = 0
//linklayer = LINKLAYER_ETHERNET
tc_calc_rtable(&p.peakrate, ptab, Pcell_log,
mtu, linklayer)
if (mtu == 0)
mtu = 2047;
//根据最大传输单元计算需要多少槽位
//我理解是不可能每个字节都有准确速
//率,所以划定字节范围,从多少字节到
//多少字节的速率相同。
if (cell_log < 0)
cell_log = 0;
while ((mtu >> cell_log) > 255)
cell_log++;
for (i=0; i<256; i++)
//校正当前槽位的字节大小。这个算
//法比较简单,当前链路类型为以太
//网,则包根据原值处理,不会影响
//包大小。mpu为最小包大小,如果
//槽位字节小于mpu,则校正为mpu
//的值。
sz = tc_adjust_size((i + 1) << cell_log,
mpu, linklayer);
//根据当前槽位字节大小,及用户
//配置的速率,计算当前槽位所需
//ticket时间
rtab[i] = tc_calc_xmittime(bps, sz);
r->cell_align=-1;
r->cell_log=cell_log;
r->linklayer = (linklayer &
TC_LINKLAYER_MASK);
//计算单包的峰值,这里通过单包峰值的大小
//转换成所需要的ticket时间
p.burst = tc_calc_xmittime(p.rate.rate, buffer);
p.mtu = 0;
//在消息尾部增加TCA_FW_POLICE属性项
addattr_l(n, MAX_MSG, tca_id, NULL, 0);
//在消息尾部增加TCA_POLICE_TBF属性项
addattr_l(n, MAX_MSG, TCA_POLICE_TBF, &p,
sizeof(p));
//在消息尾部增加TCA_POLICE_RATE属性项
addattr_l(n, MAX_MSG, TCA_POLICE_RATE,
rtab, 1024);
if (matches(*argv, "flowid") == 0)
//handle = 0x0000001
get_tc_classid(&handle, *argv)
//在消息尾部追加FW_CLASSID属性项
//rta = NLMSG_TAIL(n);
//rta->rta_type = type; //TCA_FW_CLASSID
//rta->rta_len = len;
addattr_l(n, 4096, TCA_FW_CLASSID, &handle, 4);
//根据接口名获取接口索引
if (d[0])
idx = ll_name_to_index(d)
req.t.tcm_ifindex = idx;
//给内核发送该netlink消息
rtnl_talk(&rth, &req.n, 0, 0, NULL)
rtnl_close(&rth);
2、内核层代码
用户侧发出RTM_NEWTFILTER套接口消息后,在内核侧对应的处理回调函数为tc_ctl_tfilter,该函数是在tc_filter_init中初始化的。
protocol = TC_H_MIN(t->tcm_info); //ETH_P_IP
prio = TC_H_MAJ(t->tcm_info); //50
nprio = prio;
parent = t->tcm_parent; //0xFFFF0000
dev = __dev_get_by_index(t->tcm_ifindex) //eth0设备对象
//从设备的qdisc_list列表中查找排队规则,之前ingress排队规则已经加入到链表中,所以
//这里的q就等于ingress排队规则。
q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent))
//前2个字节为排队规则索引,后2个字节为类索引,ingress类型是没无类的排队规则,
//该条件不是满足,此时cl变量取值为初始的0。
if (TC_H_MIN(parent))
//不走此流程
//ingress排队规则中对应的回调为ingress_find_tcf
//获取该排队规则中的过滤链表。
chain = cops->tcf_chain(q, cl);
ingress_find_tcf
ingress_qdisc_data *p = PRIV(sch);
return &p->filter_list;
//查找待插入的位置,优先级的值越小表示越高。
for (back = chain; (tp=*back) != NULL; back = &tp->next)
if (tp->prio >= prio)
if (tp->prio == prio)
if (!nprio || (tp->protocol != protocol && protocol))
goto errout;
else
tp = NULL;
break;
//新建过滤项
if (tp == NULL)
//从tcf_proto_base链表中查找fw分类器,当前tp_ops为cls_fw_ops
tp_ops = tcf_proto_lookup_ops(tca[TCA_KIND-1]);
tp->ops = tp_ops; //cls_fw_ops
tp->protocol = protocol; //ETH_P_IP
tp->prio = nprio; //50
tp->q = q; //ingress类型排队规则
tp->classify = tp_ops->classify; //fw_classify
tp->classid = parent; //0xFFFF0000
//当前fw类的初始化回调为fw_init,该函数内容为空。
tp_ops->init(tp)
//将当前过滤器加入到当前排队规则的过滤链表中
tp->next = *back;
*back = tp;
//fw分类器的get回调为fw_get,这里tcm_handle是之前命令行的handle值,当前为1
tp->ops->get(tp, t->tcm_handle);
//当前分类器还没有存储对应的处理handle,返回为0
fw_get
head = (struct fw_head*)tp->root;
if (head == NULL)
return 0;
//fw分类器的change回调为
tp->ops->change(tp, cl, t->tcm_handle, tca, &fh);
fw_change
head = (struct fw_head*)tp->root;
opt = tca[TCA_OPTIONS-1]
//把OPTIONS之后的属性项全部复制到临时变量tb中。
rtattr_parse_nested(tb, TCA_FW_MAX, opt)
//当前fw分类器还没有head
if (head == NULL)
u32 mask = 0xFFFFFFFF;
head = kzalloc(sizeof(struct fw_head), GFP_KERNEL);
head->mask = mask; //0xFFFFFFFF
//将当前过滤对象的root指向新创建的head控制块
tp->root = head;
f = kzalloc(sizeof(struct fw_filter), GFP_KERNEL);
f->id = handle; //1
//fw过滤对象属性修改
fw_change_attrs(tp, f, tb, tca, base);
//这里面有两个互斥的功能编译宏NET_CLS_ACT和
//NET_CLS_POLICE,一些提示显示NET_CLS_POLICE已经不建议使用
//,所以这里仅分析NET_CLS_ACT相关代码。
tcf_exts_validate(tp, tb, tca[TCA_RATE-1], &e, &fw_ext_map);
//当前命令行输入为police,走此流程
if (map->police && tb[map->police-1])
act = tcf_action_init_1(tb[map->police-1], rate_tlv, "police",
TCA_ACT_NOREPLACE, TCA_ACT_BIND, &err);
//查找已经注册的police动作模块,a_o为act_police_ops
a_o = tc_lookup_action_n(act_name);
//police对象初始化,当前回调为tcf_act_police_locate
a_o->init(rta, est, a, ovr, bind);
tcf_act_police_locate
//获取用户侧设置的TBF过滤规则参数
parm = RTA_DATA(tb[TCA_POLICE_TBF-1]);
police = kzalloc(sizeof(*police), GFP_KERNEL);
police->tcf_refcnt = 1;
spin_lock_init(&police->tcf_lock);
police->tcf_stats_lock = &police->tcf_lock;
police->tcf_bindcnt = 1;
if (parm->rate.rate)
//将速率参数加入到qdisc_rtab_list速率链表中
R_tab = qdisc_get_rtab(&parm->rate,
tb[TCA_POLICE_RATE-1]);
//释放之前老的速率表对象
qdisc_put_rtab(police->tcfp_R_tab);
//加载新的速率对象
police->tcfp_R_tab = R_tab;
//数据包峰值、最大传送单元、动作类型
police->tcfp_toks = police->tcfp_burst = parm->burst;
police->tcfp_mtu = parm->mtu;
police->tcf_action = parm->action;
//获取系统当前时间
PSCHED_GET_TIME(police->tcfp_t_c);
//生成新的策略对象索引
police->tcf_index =
tcf_hash_new_index(&police_idx_gen,
&police_hash_info);
//这里tcf_next是一个宏
//#define tcf_next common.tcfc_next
//将策略对象与tcf_police_ht互相引用。
h = tcf_hash(police->tcf_index, POL_TAB_MASK);
police->tcf_next = tcf_police_ht[h];
tcf_police_ht[h] = &police->common;
//新建的策略对象关联到action对象的私有数据
a->priv = police;
a->ops = a_o; //act->ops指向act_police_ops
//动作对象类型
act->type = TCA_OLD_COMPAT;
//构建好的动作对象先临时存于临时变量exts中,后面在
//tcf_exts_change函数中会把act存入fw过滤对象的
//exts.police中,使各fw过滤对象关联该动作。
exts->action = act;
if (tb[TCA_FW_CLASSID-1])
//classid = 0x00000001
f->res.classid = *(u32*)RTA_DATA(tb[TCA_FW_CLASSID-1]);
//将该过滤对象与类绑定
tcf_bind_filter(tp, &f->res, base);
//调用排队规则对象的ops->cl_ops->bind_tcf,当前排队规则
//为ingress,对应回调为ingress_bind_filter
cl = tp->q->ops->cl_ops->bind_tcf(tp->q, base, r->classid);
ingress_bind_filter
//0x0001 + 1 = 2
return ingress_get(sch, classid);
//r->class = cl //2
//同时返回值为r->class原来的值
cl = cls_set_class(tp, &r->class, cl);
old_cl = __cls_set_class(clp, cl);
old_cl = *clp;
*clp = cl;
return old_cl;
return old_cl;
//如果之前过滤对象与类有关联,则去除绑定
if (cl)
tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
//这里传参e是在上面tcf_exts_validate中构造的扩展动作对象
//将上面构造的扩展动作对象存储到过滤对象f->exts->action中,完成
//过滤对象与动作对象的关联。
tcf_exts_change(tp, &f->exts, &e);
//将fw过滤对象加入到head的hash链表中
f->next = head->ht[fw_hash(handle)];
head->ht[fw_hash(handle)] = f;
//告知添加成功
tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER);
七、收包处理(NET_CLS_ACT开启)
详细收包流程参见《网卡驱动收包》,这里仅分析在收包处理流程中涉及入口排队规则的代码。
网卡驱动调用netif_receive_skb来进行收包处理。
netif_receive_skb
......
#ifdef CONFIG_NET_CLS_ACT
//查看代码发现在IFB接口设备中会设置NCLS值,不再需要进行入口排队规则处理,
//直接跳过到ncls标记处。
if (skb->tc_verd & TC_NCLS)
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
goto ncls;
#endif
......
#ifdef CONFIG_NET_CLS_ACT
//设置OK2MUNGE标记,查看代码,当前仅在pedit类型的动作中触发,如果没有此
//标记,则pedit类型动作在进行处理时,需要复制一次skb。
skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
//进行过滤
ret = ing_filter(skb);
result = TC_ACT_OK;
//如果用户向该接口配置了ingress排队规则,则此条件成功,否则返回默认OK,
//让进来的报文直接向下继续处理。
if (dev->qdisc_ingress)
//如果出现报文多次内部环回,则将报文丢弃。
__u32 ttl = (__u32) G_TC_RTTL(skb->tc_verd);
if (MAX_RED_LOOP < ttl++)
return TC_ACT_SHOT;
//更新报文环回值
skb->tc_verd = SET_TC_RTTL(skb->tc_verd,ttl);
//标记报文是接收方向
skb->tc_verd = SET_TC_AT(skb->tc_verd,AT_INGRESS);
//调用当前排队规则的入队处理回调,当前ingress的回调为ingress_enqueue
result = q->enqueue(skb, q)
ingress_enqueue
//使用过滤器进行分类处理
result = tc_classify(skb, p->filter_list, &res);
__be16 protocol = skb->protocol;
protocol = skb->protocol;
reclassify:
//遍历所有过滤器,当前例子仅注册了一个fw过滤器
for ( ; tp; tp = tp->next)
//先进行协议匹配,如果之前用户配置过滤器未设置协议参
//数,则protocol为ETH_P_ALL表示匹配任意协议。
//之后使用当前过滤器的classify回调进行处理。
//当前fw过滤器的回调为fw_classify,下面单独分析。
if ((tp->protocol == protocol ||
tp->protocol == __constant_htons(ETH_P_ALL))
&& (err = tp->classify(skb, tp, res)) >= 0)
#ifdef CONFIG_NET_CLS_ACT
//如果过滤结果为需要重分类,则继续尝试重分类,当
//然也需要对重分类次数进行限制。
if ( TC_ACT_RECLASSIFY == err)
__u32 verd = (__u32) G_TC_VERD(skb->tc_verd);
if (MAX_REC_LOOP < verd++)
return TC_ACT_SHOT;
skb->tc_verd = SET_TC_VERD(skb->tc_verd,verd);
goto reclassify;
//其它过滤结果则直接返回,同时清除用于重分类限制
//的VERD标记。
else
if (skb->tc_verd)
skb->tc_verd =
SET_TC_VERD(skb->tc_verd,0);
return err;
#endif
return -1;
#ifdef CONFIG_NET_CLS_ACT
//处理包个数、字节数统计
sch->bstats.packets++;
sch->bstats.bytes += skb->len;
//根据过滤器结果进行返回值映射
switch (result)
case TC_ACT_SHOT:
result = TC_ACT_SHOT;
sch->qstats.drops++;
case TC_ACT_STOLEN:
case TC_ACT_QUEUED:
result = TC_ACT_STOLEN;
case TC_ACT_RECLASSIFY:
case TC_ACT_OK:
case TC_ACT_UNSPEC:
default:
skb->tc_index = TC_H_MIN(res.classid);
result = TC_ACT_OK;
return result;
#endif
return result;
//根据过滤结果,决定把进来的包丢弃,还是继续处理。
if (ret == TC_ACT_SHOT || (ret == TC_ACT_STOLEN))
kfree_skb(skb);
goto out;
skb->tc_verd = 0;
ncls:
#endif
......
---------------------------------------------------------------------------------------------------------------------
fw_classify
//分类器头列表
head = (struct fw_head*)tp->root;
//包的mark,该mark是由iptables或ebtables打上的标签,fw分类器主要用于和iptables、
//ebtables配合来完成对特定包的匹配。
id = skb->mark;
if (head != NULL)
id &= head->mask; //当前用例没有设置掩码,则完整匹配
//遍历满足该id的hash链表
for (f=head->ht[fw_hash(id)]; f; f=f->next)
//找到匹配的过滤对象
if (f->id == id)
*res = f->res; //这里记载了该匹配对象绑定的类,参见命令分析
//进行过滤对象的扩展处理,当前例子扩展为速率的限制
r = tcf_exts_exec(skb, &f->exts, res);
#ifdef CONFIG_NET_CLS_ACT
return tcf_action_exec(skb, exts->action, res);
/查看代码发现在IFB接口设备中会设置NCLS值,不再需要进
//行入口排队规则处理
if (skb->tc_verd & TC_NCLS)
skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
ret = TC_ACT_OK;
goto exec_done;
//遍历该过滤器下所有动作,当前实例仅一个限速
while ((a = act) != NULL)
repeat:
//进行当前动作的act回调处理,当前动作为policce,则对
//应的回调为tcf_act_police,下面单独分析。
ret = a->ops->act(skb, a, res);
//当前仅pedit动作对象会设置TC_MUNGED标记,当设置
//了该标记后,则去除该标记,同时设置OK2MUNGE标记
//后续在进行pedit时,已经复制过了,就不怕乱处理了?
if (TC_MUNGED & skb->tc_verd)
skb->tc_verd = SET_TC_OK2MUNGE(skb->tc_verd);
skb->tc_verd = CLR_TC_MUNGED(skb->tc_verd);
//重复处理当前动作
if (ret == TC_ACT_REPEAT)
goto repeat;
//当动作结果为非PIPE时,则处理完成返回结果,否则继
//续使用下一个动作对象进行处理。
if (ret != TC_ACT_PIPE)
goto exec_done;
act = a->next;
exec_done:
return ret;
#endif
if (r < 0)
continue;
return r;
----------------------------------------------------------------------------------------------------------------------
tcf_act_police
//统计
police->tcf_bstats.bytes += skb->len;
police->tcf_bstats.packets++;
//如果当前报文长度不大于用户设置的MTU值,则为合法条件
if (skb->len <= police->tcfp_mtu)
//获取当前系统时间
PSCHED_GET_TIME(now);
//计算从上一次到现在经过的ticket值,该值不能超过用户设置的最大单包峰值。
toks = PSCHED_TDIFF_SAFE(now, police->tcfp_t_c,police->tcfp_burst);
//在经过一段时间流逝后,当前可以继续多处理一些数据包,这里将之前的值进行
//累加补充,得到这个时间点可以处理的总的数据量(这里单位是tickt,是将根据
//用户设置的速率进行的字节到ticket的转换,参见上面用户命令的分析可以更清
//楚这里的转换机制)
toks += police->tcfp_toks;
//将当前报文大小,根据用户侧的速率表进行计算得到在当前速率下这个大小的数
//据需要消耗多少ticket
toks -= L2T(police, skb->len);
//如果这次发包未达到用户设置的速率限制,则条件满足,此时记录当前剩余的
//令牌值,并返回结果。这里的tcfp_result用户也可以在命令行自行设置,如果
//用户没有设置则为0,对应的值为TC_ACT_OK
if ((toks|ptoks) >= 0)
police->tcfp_t_c = now;
police->tcfp_toks = toks;
police->tcfp_ptoks = ptoks;
return police->tcfp_result;
//当前报文长度大于用户设置的MTU值,或者已经超过当前速率,执行用户设置的
//动作,当前设置为drop,对应的动作值为TC_POLICE_SHOT,该报文被丢弃。
police->tcf_qstats.overlimits++;
return police->tcf_action;
八、收包处理(NET_CLS_ACT未开启)
当用户没有开启NET_CLS_ACT宏时,入口排队规则的处理被延迟到netfilter架构的PRE_ROUTING链上,优先级在FILTER过滤之后,钩子回调函数为ing_hook。该钩子注册点详见上面“给接口设置ingress入口排队规则”。这里仅关注相关钩子函数,netfilter架构在网上有好多经验文章,这里不再对这块进行详细分析。
ing_hook
int fwres=NF_ACCEPT;
//如果用户设置入口排队规则,则使用该排队规则的回调enqueue进行入队列的分类处
//理,否则直接通过。入队的处理在上面已经分析过了,这里不再重复。可以看到使用
//NET_CLS_ACT机制和netfilter的机制来处理入口排队规则因为时间点不同,各有各
//优缺点。NET_CLS_ACT机制是在包刚进来就进行QOS处理,必免了很多分支流程
//的干扰,而使用netfilter的机制,可以让包先进行网桥处理、之后再进行iptables的
//过滤链处理,之后才进行QOS处理,可以在进行QOS处理前做一些其它事情。
if (dev->qdisc_ingress)
fwres = q->enqueue(skb, q);
return fwres;