Ingress traffic control

原文链接:http://ju.outofmemory.cn/entry/53825

相信很多人都知道 Linux 内核很难做 ingress 流量控制的,毕竟发送方理论上可以无限制地发送包到达网络接口。即使你控制了从网络接口到达协议栈的流量,你也很难从根源上控制这个流量。

利用 Linux 内核你可以轻松地完成前者,即控制从网络接口到达协议栈的流量。这个是通过一个叫做 ifb 的设备完成的,这个设备如此不起眼以至于很多人不知道它的存在。

它的使用很简单,加载 ifb 模块后,激活 ifbX 设备:

modprobe ifb numifbs=1
ip link set dev ifb0 up # ifb1, ifb2, ... 操作相同

然后就要通过 ingress tc filter 把流入某个网络接口的流量 redirect 到 ifbX 上:

tc qdisc add dev eth0 handle ffff: ingress
tc filter add dev eth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0

ingress 这个 qdisc 是不能加 class,但是我们这里使用了 ifb0,所以就可以在 ifb0 设备上添加各种 tc class,这样就和 egress 没有多少区别了。

ifb 的实现其实很简单,因为流入网络接口的流量是无法直接控制的,那么我们必须要把流入的包导入(通过 tc action)到一个中间的队列,该队列在 ifb 设备上,然后让这些包重走 tc 层,最后流入的包再重新入栈,流出的包重新出栈。看代码一目了然:

C:
  1. while ((skb = __skb_dequeue(&dp->tq)) != NULL) {
  2.                 u32 from = G_TC_FROM(skb->tc_verd);
  3.                 skb->tc_verd = 0;
  4.                 skb->tc_verd = SET_TC_NCLS(skb->tc_verd);
  5.                 u64_stats_update_begin(&dp->tsync);
  6.                 dp->tx_packets++;
  7.                 dp->tx_bytes += skb->len;
  8.                 u64_stats_update_end(&dp->tsync);
  9.                 rcu_read_lock();
  10.                 skb->dev = dev_get_by_index_rcu(dev_net(_dev), skb->skb_iif);
  11.                 if (!skb->dev) {
  12.                         rcu_read_unlock();
  13.                         dev_kfree_skb(skb);
  14.                         _dev->stats.tx_dropped++;
  15.                         if (skb_queue_len(&dp->tq) != 0)
  16.                                 goto resched;
  17.                         break;
  18.                 }
  19.                 rcu_read_unlock();
  20.                 skb->skb_iif = _dev->ifindex;
  21.                 if (from & AT_EGRESS) {
  22.                         dev_queue_xmit(skb);
  23.                 } else if (from & AT_INGRESS) {
  24.                         skb_pull(skb, skb->dev->hard_header_len);
  25.                         netif_receive_skb(skb);
  26.                 } else
  27.                         BUG();
  28.         }

这里有两个不太明显的地方值得注意:

1) 不论流入还是流出,tc 层的代码都是工作在 L2 的,也就是说代码是可以重入的,尤其是 skb 里的一些 header pointer;

2) 网络设备上的 ingress 队列只有一个,这也是为啥 ingress qdisc 只能做 filter 的原因,可以看下面的代码:

C:
  1. static int ing_filter(struct sk_buff *skb, struct netdev_queue *rxq)
  2. {
  3.         struct net_device *dev = skb->dev;
  4.         u32 ttl = G_TC_RTTL(skb->tc_verd);
  5.         int result = TC_ACT_OK;
  6.         struct Qdisc *q;
  7.         if (unlikely(MAX_RED_LOOP <ttl++)) {
  8.                 net_warn_ratelimited("Redir loop detected Dropping packet (%d->%d)\n",
  9.                                      skb->skb_iif, dev->ifindex);
  10.                 return TC_ACT_SHOT;
  11.         }
  12.         skb->tc_verd = SET_TC_RTTL(skb->tc_verd, ttl);
  13.         skb->tc_verd = SET_TC_AT(skb->tc_verd, AT_INGRESS);
  14.         q = rxq->qdisc;
  15.         if (q != &noop_qdisc) {
  16.                 spin_lock(qdisc_lock(q));
  17.                 if (likely(!test_bit(__QDISC_STATE_DEACTIVATED, &q->state)))
  18.                         result = qdisc_enqueue_root(skb, q);
  19.                 spin_unlock(qdisc_lock(q));
  20.         }
  21.         return result;
  22. }
  23. static inline struct sk_buff *handle_ing(struct sk_buff *skb,
  24.                                          struct packet_type **pt_prev,
  25.                                          int *ret, struct net_device *orig_dev)
  26. {
  27.         struct netdev_queue *rxq = rcu_dereference(skb->dev->ingress_queue);
  28.         if (!rxq || rxq->qdisc == &noop_qdisc)
  29.                 goto out;
  30.         if (*pt_prev) {
  31.                 *ret = deliver_skb(skb, *pt_prev, orig_dev);
  32.                 *pt_prev = NULL;
  33.         }
  34.         switch (ing_filter(skb, rxq)) {
  35.         case TC_ACT_SHOT:
  36.         case TC_ACT_STOLEN:
  37.                 kfree_skb(skb);
  38.                 return NULL;
  39.         }
  40. out:
  41.         skb->tc_verd = 0;
  42.         return skb;
  43. }

而经过 tc action 后是导入到了 ifb 设备的发送队列,见 net/sched/act_mirred.c 的 tcf_mirred() 函数:

C:
  1. //...
  2.         skb2->skb_iif = skb->dev->ifindex;
  3.         skb2->dev = dev;
  4.         err = dev_queue_xmit(skb2);

也就是说这样就会有多个队列了,可以添加 class 了。:) 这大体也是 ingress traffic control 如何工作的了,可见 ifb 的存在既简单又巧妙地粘合了 egress 上的流量控制。

正如本文一开始讲到的,即使用 ifb 我们所做的流量控制仍然有限,很难从根本上控制发送方的速率。如果双方是通过交换机连接的话,或许通过 L2 上的一些协议可以控制速率,而如果中间经过路由器,那么所能做的就更加有限,只能期待传输层的协议进行控制,而不是靠传输层自身感受到丢包的多少去自动调整速率,因为这可能会花很长时间。

参考资料:

1. http://serverfault.com/questions/350023/tc-ingress-policing-and-ifb-mirroring
2. http://stackoverflow.com/questions/15881921/why-tc-cannot-do-ingress-shaping-does-ingress-shaping-make-sense

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值