先来看下traffic control在内核里是如何实现的,首先内核在发送数据时,最终会调用到dev_queue_xmit,
struct Qdisc *q
if (q->enqueue) {
rc = __dev_xmit_skb(skb, q, dev, txq);
goto out;
}
如果q->enqueue函数不为空,此时就进入traffic control的逻辑,下面调用__dev_xmit_skb
static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,
struct net_device *dev,
struct netdev_queue *txq)
函数会判断Qdisc->state状态,如果__QDISC_STATE_DEACTIVATED,那么free skb返回NET_XMIT_DROP。如果Qdisc->state状态为非__QDISC_STATE_RUNNING,同时Qdisc的标签有TCQ_F_CAN_BYPASS,那么直接把包发出去。否则调用qdisc_enqueue_skb把skb放到root Qdisc中,再调用qdisc_run
qdisc_run如果判断Qdisc->state为__QDISC_STATE_RUNNING,则调用__qdisc_run,
void __qdisc_run(struct Qdisc *q)
{
unsigned long start_time = jiffies;
while (qdisc_restart(q)) {
/*
* Postpone processing if
* 1. another process needs the CPU;
* 2. we've been doing it for too long.
*/
if (need_resched() || jiffies != start_time) {
__netif_schedule(q);
break;
}
}
clear_bit(__QDISC_STATE_RUNNING, &q->state);
}
__qdisc_run会循环调用qdisc_restart,直到消耗了一个jiffy或者该CPU需要被调度给其他进程(need_resched),此时调用__netif_reschedule把当前CPU的softnet_data->output_queue交给Qdisc->output_queue,并触发一个NET_TX_SOFTIRQ软中断
qdisc_restart函数如下:
static inline int qdisc_restart(struct Qdisc *q)
{
struct netdev_queue *txq;
struct net_device *dev;
spinlock_t *root_lock;
struct sk_buff *skb;
/* Dequeue packet */
skb = dequeue_skb(q);
if (unlikely(!skb))
return 0;
root_lock = qdisc_lock(q);
dev = qdisc_dev(q);
txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));
return sch_direct_xmit(skb, q, dev, txq, root_lock);
}
qdisc_restart从Qdisc头里取一个skb出来,调用qdisc_lock获取一个Qdisc根锁,然后调用netdev_get_tx_queue基于skb哈希获取netdev_queue队列,调用sch_direct_xmit直接把skb发送出去
sch_direct_xmit先调用qdisc_unlock释放Qdisc根锁,调用dev_hard_start_xmit通过驱动发送skb,然后判断返回值,如果是NETDEV_TX_OK,返回qdisc_qlen的Qdisc队列长度;如果是NETDEV_TX_LOCKED,此时出现了锁异常,这里不深入探讨了;如果返回了NETDEV_TX_BUSY,则调用dev_requeue_skb把skb重新入队列
traffic control还支持对入包进行控制,内核对于入包会调用netif_receive_skb,该函数会调用handle_ing。handle_ing首先判断skb->dev->rx_queue.qdisc是否是noop_qdisc,如果是noop_qdisc那么不会有任何QoS控制,否则调用ing_filter
static int ing_filter(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
u32 ttl = G_TC_RTTL(skb->tc_verd);
struct netdev_queue *rxq;
int result = TC_ACT_OK;
struct Qdisc *q;
if (MAX_RED_LOOP < ttl++) {
printk(KERN_WARNING
"Redir loop detected Dropping packet (%d->%d)\n",
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);
rxq = &dev->rx_queue;
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;
}
ing_filter首先找出skb->dev->rx_queue.qdisc,如果不是noop_qdisc,同时状态为非__QDISC_STATE_DEACTIVATED,则调用qdisc_enqueue_root,把skb入队列
可以看出,如果网络设备支持traffic control,其驱动的收发函数必须要支持Qdisc的enqueue/dequeue,从测试结果和代码上看,xen netback就可以支持,而bridge就无法支持
对于xen netback而言,限制虚拟机出包QoS有两种方法,首先要知道,netfront发包到了netback,会触发netback的net_tx_action tasklet,里面会调用net_tx_submit,继而最终调用到netif_rx,还记得这个函数不,内核协议栈处理非NAPI的时候就是调用netif_rx来收包的。netif_rx会调用netif_receive_skb,里面会有一个和handle_ing函数,用来对ingress的包做QoS,这就是第一种方法,对netback做tc qdisc ingress规则来进行traffic control;
限制出包的第二钟方法前面也提过啦,就是在网桥上做mark神马的,然后在物理口针对mark做traffic control,由于这里是egress规则,因此可以用各种tc class来做。
那么netback上限制虚拟机入包呢?对于入包而言,首先从物理口进入网桥,然后从网桥进入netback,网桥把包交换到网络设备的过程中,会调用br_forward_finish,继而调用br_dev_queue_push_xmit通过dev_queue_xmit把包“发”出去。因此限制虚拟机入包QoS,只需要在netback上设置tc egress规则即可。由于netback是net_device设备的一个超集,因此支持dev_queue_xmit方法,也支持egress方向的qdisc规则
最后,来看下ingress QoS的相关设置,内核自带的ingress qdisc的功能是非常简单的,只支持最基本的rate limit,对于超出的流量一律drop,目前好的做法是通过一个虚拟网络设备把ingress流量redirect到这个设备上,再在这个虚拟设备设置traffic control规则
modprobe ifb
ip link set ifb0 up
ip link set ifb1 up
ifb设备是内核用来做traffic control redirect的,驱动加载之后,可以对该设备设置和egress相同的规则
tc qdisc add dev ifb0 root handle 1: htb default 100
tc class add dev ifb0 parent 1: classid 1:10 htb rate 100mbit ceil 100mbit
tc filter add dev ifb0 parent 1: protocol ip prio 1 u32 match ip src 0.0.0.0/0 flowid 1:10 #所有流量都match classid 1:10
最后还需要一条规则把peth0的入流量redirect到ifb0设备上
tc qdisc add dev peth0 ingress
tc filter add dev peth0 parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev ifb0