- <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 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);
- <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 :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);
- <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;
ingress入口排队规则分析
最新推荐文章于 2024-08-16 12:29:37 发布