
Linux ifb中间功能块设备与重定向流控IFBIntermediate Functional Block是一个虚拟网络设备位于drivers/net/ifb.c用于重定向入站流量以便应用egress方向的qdisc策略。核心思路是将物理网卡的ingress流量通过mirred action重定向到ifb设备再由ifb设备的tx qdisc做整形/限速。IFB设备注册为独立net_device其netdev_ops实现简单static const struct net_device_ops ifb_netdev_ops {.ndo_open ifb_open,.ndo_stop ifb_close,.ndo_start_xmit ifb_xmit,.ndo_get_stats64 ifb_get_stats64,.ndo_set_rx_mode ifb_set_rx_mode,};核心发送函数为ifb_xmit所有被重定向到ifb的报文在此入队static netdev_tx_t ifb_xmit(struct sk_buff *skb, struct net_device *dev){struct ifb_dev_priv *dp netdev_priv(dev);struct sk_buff *skb2;struct pcpu_lstats *stats this_cpu_ptr(dp-pcpu_stats);int len skb-len;int ret NETDEV_TX_OK;skb2 skb_clone(skb, GFP_ATOMIC);if (!skb2) {dev-stats.tx_errors;dev_kfree_skb(skb);return NETDEV_TX_OK;}skb2-dev dev;skb2-protocol eth_type_trans(skb2, dev);skb2-skb_iif dev-ifindex;u64_stats_update_begin(stats-syncp);stats-bytes len;stats-packets;u64_stats_update_end(stats-syncp);if (skb2-len 0) {struct Qdisc *q rcu_dereference_bh(dp-qdisc);int err;if (q-enqueue) {err q-enqueue(skb2, q, to_free);if (err ! NET_XMIT_SUCCESS) {if (netif_xmit_frozen_or_stopped(q))ret NETDEV_TX_BUSY;dev-stats.tx_dropped;return ret;}}__netif_schedule(q);}dev_kfree_skb(skb);return ret;}ifb_xmit克隆原始skb后将新skb的dev设置为ifb设备调用eth_type_trans重新解析以太网头然后将skb送入ifb的tx qdisc队列。这样原本ingress方向的流量就走了一遍egress qdisc的整形流程。ifb设备初始化时挂载默认qdiscpfifo_fast用户可以通过tc qdisc replace dev ifb0 root handle 1: htb等命令将htb或其它qdisc绑定到ifb上static int ifb_open(struct net_device *dev){struct ifb_dev_priv *dp netdev_priv(dev);struct Qdisc *q;q fifo_create_dflt(dev, dp-tq, 1);if (IS_ERR(q))return PTR_ERR(q);rcu_assign_pointer(dp-qdisc, q);netif_start_queue(dev);return 0;}流量重定向的配置在内核外部通过tc mirred action完成tc filter add dev eth0 ingress prio 1 \matchall action mirred egress redirect dev ifb0这条命令在内核中创建ingress filter使用cls_matchall分类器匹配所有报文action类型为TCA_ACT_MIRRED方向为egress redirect到ifb0。mirred action在tcf_mirred_act中处理static int tcf_mirred_act(struct sk_buff *skb, const struct tc_action *a,struct tcf_result *res){struct tcf_mirred *m to_mirred(a);struct net_device *dev rcu_dereference_bh(m-tcfm_dev);struct sk_buff *skb2;int retval TC_ACT_SHOT;bool at_ingress skb_at_tc_ingress(skb);bool use_mac_header false;if (unlikely(!dev))goto out;if (unlikely(!(dev-flags IFF_UP)))goto out;skb2 skb_clone(skb, GFP_ATOMIC);if (!skb2)goto out;if (m-tcfm_eaction TCA_EGRESS_MIRROR ||m-tcfm_eaction TCA_EGRESS_REDIR) {skb2-dev dev;dev_queue_xmit(skb2);} else {netif_receive_skb(skb2);}retval TC_ACT_PIPE;out:return retval;}在ingress方向mirred到ifb时eaction为TCA_EGRESS_REDIR调用dev_queue_xmit。dev_queue_xmit触发__dev_queue_xmit最终调用ifb_xmit入队。ifb设备上挂载的qdisc完成整形后报文的最终发送由ifb的netdev_ops-ndo_start_xmit处理——ifb_xmit在上面的流程中实际只负责入队而非真正的物理发送。ifb没有物理链路所有报文在stuck队列中由ifb_dequeue处理后者实际为空操作无实际物理发送报文最终被丢弃或由其他设备接管。ifb的stats统计通过ifb_get_stats64收集每个CPU的字节/包计数static void ifb_get_stats64(struct net_device *dev,struct rtnl_link_stats64 *stats){struct ifb_dev_priv *dp netdev_priv(dev);unsigned int start;int i;for_each_possible_cpu(i) {struct pcpu_lstats *lstats per_cpu_ptr(dp-pcpu_stats, i);do {start u64_stats_fetch_begin_irq(lstats-syncp);stats-rx_packets lstats-packets;stats-rx_bytes lstats-bytes;} while (u64_stats_fetch_retry_irq(lstats-syncp, start));}stats-tx_packets stats-rx_packets;stats-tx_bytes stats-rx_bytes;}通过ifb配合ingress qdisc和mirred action可以在不修改应用程序的情况下对入站流量施加完整的egress QoS策略。