linux下ipv6的Qos限速实现

来源:互联网 发布:淘宝女士沙滩鞋 编辑:程序博客网 时间:2024/04/29 11:53

在网上差了好多资料,发现提到ipv6限速的不少,但是没发现有人提到怎么修改实现linux内核对ipv6进行限速,不知道是因为太简单,人们觉得没什么价值,没必要放到网上去,还是因为什么别的... 最近因为工作需要,花了不少时间看了下。毕竟linux本身就是开源的嘛,所以就献给大家吧,希望大牛们不要见笑,以下就是我的修改方法(ip6tables+tc的用法我就不说了,和iptables+tc一样):


修改tc内核代码,使linux支持IPv6 Qos限速

现有的嵌入式系统采用的linux内核版本是2.6.32.11,这个版本的内核可以利用iptables+ tc(tc队列采用的是分层令牌桶算法的HTB)实现对ipv4的数据进行限速、带宽保障、动态带宽调整(流量整形是在htb队列调度的过程中进行的)。而对于ipv6来说,开始使用同样类似的方法,即ip6tables+tc去实现限速,发现ip6tables是好使的(对匹配的数据流打mark),但是配合上tc后,发现无法实现限速。

 

于是着手分析限速无效的原因出在什么地方。首先确认,问题不会出现在ip6tables上,而是在tc上;其次,tc采用htb队列时,分成三个部分:接口上绑定队列、队列分类、过滤数据到相应的分类上进行调度。分析比较ipv4与ipv6封装的差异并分析htb这三部分内核代码发现,ipv6限速不生效的原因并不在于v4和v6数据封包的不同,而在于其外层以太网封包,tc根据以太数据包中封装的上层协议类型来进行判断,因为对ipv6的协议类型没有识别出来,直接转发了,所以限速失败了。

 

QOS在内核的入口点有两个,但是不能同时启用,这取决于内核编译选项。当打开了CONFIG_NET_CLS_ACT时,入口点在../net/core/dev.c的netif_receive_skb函数中,代码片段如下:

#ifdef CONFIG_NET_CLS_ACT

skb = handle_ing(skb,&pt_prev, &ret, orig_dev);

if (!skb)

           goto out;

#endif

 

之后进入hand_ing()后,代码片段:

switch (ing_filter(skb)) {

case TC_ACT_SHOT:

case TC_ACT_STOLEN:

           kfree_skb(skb);

           return NULL;

}

 

进入ing_filter后,代码片段如下:

if (q != &noop_qdisc) {

           spin_lock(qdisc_lock(q));

           if(likely(!test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))

                    result = qdisc_enqueue_root(skb, q);

           spin_unlock(qdisc_lock(q));

}

在ing_filter()中可以看到,在这里判断了是否在设备上配置了摸个调度规则,如果配置了则调用其qdisc_enqueue_root函数进行处理。默认的调度队列为fifo。

 

因为tc采用的队列为HTB,所以ing_filter最终调用了htbqdisc的htb_enqueue,需要修改的地方在../net/sched/sch_htb.c中,队列分类处理的部分代码里:

inttc_classify_compat(struct sk_buff *skb, struct tcf_proto *tp,

                   struct tcf_result *res)

{

  __be16 protocol;// = skb->protocol;

  int err = 0;

 

  if(skb->protocol ==0xdd86)/*ipv6*/                  //ipv6的协议类型为0x86dd  

            protocol =0x8;                               //ipv4的协议类型为0x0008

  else

            protocol =skb->protocol;

 

  for (; tp; tp = tp->next) {

            if ((tp->protocol == protocol ||

                tp->protocol == htons(ETH_P_ALL)) &&

               (err = tp->classify(skb, tp, res)) >= 0) {

#ifdefCONFIG_NET_CLS_ACT

                     if (err !=TC_ACT_RECLASSIFY && skb->tc_verd)

                              skb->tc_verd =SET_TC_VERD(skb->tc_verd, 0);

#endif

                     return err;

            }

  }

  return -1;

}

其实还有一种改法,就是不需要修改内核部分,只需要修改用户空间的代码也行,就是修改tc filter的命令与内核的接口部分:

tc filter add dev eth1 parent 1:0 protocol ip prio 1 handle 1 fw flowid 1:1

中的“ip”是指ipv4,添加“ipv6”或“ip6”以支持ipv6,如

tc filter add dev eth1 parent 1:0 protocol ip6 prio 1 handle 1 fw flowid 1:1或

tc filter add dev eth1 parent 1:0 protocol ipv6 prio 1 handle 1 fw flowid 1:1

修改位置在../iproute/tc中的tc.c和tc_filter.c等一个文件中(貌似最新的内核中是这样做的,还没来得急仔细看,这是我修改完2.6.32这个内核后才发现的,也没去试最新内核的限速好不好使,但是我修改完后得对ipv6限速是好使的)。


要解决内核对ipv6 Qos的支持,以上需要修改的地方虽然很少,但当遇到的问题无论从个人还是从网上都无法获取太多有用信息时,就需要善于利用现有的知识和技能根据问题的需求一步步仔细分析和推敲。