Ingress traffic control

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

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

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

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

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

tc qdisc add dev eth0 handle ffff: ingresstc 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 层,最后流入的包再重新入栈,流出的包重新出栈。看代码一目了然:

PLAIN TEXT

C:

    while ((skb = __skb_dequeue(&dp->tq)) != NULL) {

    u32 from = G_TC_FROM(skb->tc_verd);

    skb->tc_verd = 0;

    skb->tc_verd = SET_TC_NCLS(skb->tc_verd);

    u64_stats_update_begin(&dp->tsync);

    dp->tx_packets++;

    dp->tx_bytes += skb->len;

    u64_stats_update_end(&dp->tsync);

    rcu_read_lock();

    skb->dev = dev_get_by_index_rcu(dev_net(_dev), skb->skb_iif);

    if (!skb->dev) {

    rcu_read_unlock();

    dev_kfree_skb(skb);

    _dev->stats.tx_dropped++;

    if (skb_queue_len(&dp->tq) != 0)

    goto resched;

    break;

    }

    rcu_read_unlock();

    skb->skb_iif = _dev->ifindex;

    if (from & AT_EGRESS) {

    dev_queue_xmit(skb);

    } else if (from & AT_INGRESS) {

    skb_pull(skb, skb->dev->hard_header_len);

    netif_receive_skb(skb);

    } else

    BUG();

    }

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

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

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

PLAIN TEXT

C:

    static int ing_filter(struct sk_buff *skb, struct netdev_queue *rxq)

    {

    struct net_device *dev = skb->dev;

    u32 ttl = G_TC_RTTL(skb->tc_verd);

    int result = TC_ACT_OK;

    struct Qdisc *q;

    if (unlikely(MAX_RED_LOOP <ttl++)) {

    net_warn_ratelimited("Redir loop detected Dropping packet (%d->%d)\n",

    skb->skb_iif, dev->ifindex);

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

    q = rxq->qdisc;

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

    }

    return result;

    }

    static inline struct sk_buff *handle_ing(struct sk_buff *skb,

    struct packet_type **pt_prev,

    int *ret, struct net_device *orig_dev)

    {

    struct netdev_queue *rxq = rcu_dereference(skb->dev->ingress_queue);

    if (!rxq || rxq->qdisc == &noop_qdisc)

    goto out;

    if (*pt_prev) {

    *ret = deliver_skb(skb, *pt_prev, orig_dev);

    *pt_prev = NULL;

    }

    switch (ing_filter(skb, rxq)) {

    case TC_ACT_SHOT:

    case TC_ACT_STOLEN:

    kfree_skb(skb);

    return NULL;

    }

    out:

    skb->tc_verd = 0;

    return skb;

    }

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

PLAIN TEXT

C:

    //…

    skb2->skb_iif = skb->dev->ifindex;

    skb2->dev = dev;

    err = dev_queue_xmit(skb2);

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

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

参考资料:

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

Ingress traffic control

相关文章:

你感兴趣的文章:

标签云: