Linux TC(28)
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:13512次
排名:千里之外
转载:202篇
(7)(13)(16)(94)(34)(34)(18)proc_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
上传于|0|0|暂无简介
你可能喜欢来自CSDN博客:ingress入口排队规则分析
最后更新时间
blog__5103536
&strong&一、ingress入口排队规则模块初始化&/strong&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);&strong&二、包调度API子系统初始化&/strong&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&&PSCHED_JSCALE; psched_us_per_tick = 1000000; #endif //向ROUTE类型的netlink套接口挂载处理回调,当用户侧使用tc命令处理排队规则 //及类时,这些回调对应在内核侧进行排队规则及类的添加、删除等操作。 link_p = rtnetlink_links[PF_UNSPEC]; link_p[RTM_NEWQDISC-RTM_BASE].doit = tc_modify_qdisc; link_p[RTM_DELQDISC-RTM_BASE].doit = tc_get_qdisc; link_p[RTM_GETQDISC-RTM_BASE].doit = tc_get_qdisc; link_p[RTM_GETQDISC-RTM_BASE].dumpit = tc_dump_qdisc; 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; //向qdisc_base链表注册pfifo、bfifo两种排队规则 register_qdisc(&pfifo_qdisc_ops); register_qdisc(&bfifo_qdisc_ops); //向/proc/net/psched创建文件,用于用户侧获取psched_tick_per_us等参数 proc_net_fops_create(&psched&, 0, &psched_fops);&strong&三、TC过滤子系统初始化&/strong&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;&strong&四、FW分类器模块初始化&/strong&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;&strong&五、给网络接口设置INGRESS排队规则&/strong&命令:tc qdisc add dev eth0 ingress1、用户层代码//初始化,获取每纳秒对应多少TICKETtc_core_init(); fp = fopen(&/proc/net/psched&, &r&); fscanf(fp, &%08x%08x%08x&, &t2us, &us2t, &clock_res); fclose(fp); if (clock_res == ) 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 =
//TCA_KIND //rta-&rta_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);&strong&六、设置速率限制&/strong&命令:tc filter add dev eth0 parent ffff: protocol ip prio 50 handle 1 fw police rate 1kbit burst 40 mtu 9k drop flowid :11、用户层代码//初始化,获取每纳秒对应多少TICKETtc_core_init(); fp = fopen(&/proc/net/psched&, &r&); fscanf(fp, &%08x%08x%08x&, &t2us, &us2t, &clock_res); fclose(fp); if (clock_res == ) 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 =
//TCA_KIND //rta-&rta_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;
当前没配置 //计算速率表 //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 = //TCA_FW_CLASSID //rta-&rta_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_IPprio = TC_H_MAJ(t-&tcm_info);
//50nprio = prio;parent = t-&tcm_parent;
//0xFFFF0000dev = __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值,当前为1tp-&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 = 0x 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);&strong&七、收包处理(NET_CLS_ACT开启)&/strong&详细收包流程参见《网卡驱动收包》,这里仅分析在收包处理流程中涉及入口排队规则的代码。网卡驱动调用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;&strong&八、收包处理(NET_CLS_ACT未开启)&/strong&当用户没有开启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;