ingress入口排队规则分析

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值