macvlan是linux的一种虚拟网络接口,macvlan 允许你在主机的一个网络接口上配置多个虚拟的网络接口,这些网络 interface 有自己独立的 mac 地址,也可以配置上 ip 地址进行通信。macvlan 下的虚拟机或者容器网络和主机在同一个网段中,共享同一个广播域。
macvlan 和 bridge 比较相似,但因为它省去了 bridge 的存在,所以配置和调试起来比较简单,而且效率也相对高。除此之外,macvlan 自身也完美支持 VLAN。
macvlan 虚拟网卡设备包括5种模式:
private 模式:在这种模式下,macvlan设备不能接受寄生在同一个物理网卡的其他macvlan设备的数据包,即使是其他macvlan设备通过物理网卡发送出去并通过hairpin设备返回的包。
vepa 模式:在这种模式下,macvlan设备不能直接接受寄生在同一个物理网卡的其他macvlan设备的数据包,但是其他macvlan设备可以将数据包通过物理网卡发送出去,然后通过hairpin设备返回的给其他macvlan设备。
passthru 模式:在这种模式下,每一个物理设备只能寄生一个macvlan设备
bridge 模式:在这种模式下,寄生在同一个物理设备的macvlan设备可以直接通讯,不需要外接的hairpin设备帮助。
source 模式: 在这种模式下,寄生在物理设备的这类macvlan设备,只能接受指定的源 mac source的数据包,其他数据包都不接受。
macvlan在协议栈中两个重要的数据结构:
创建macvlan接口的时候,struct macvlan_dev 会作为macvlan接口设备数据结构net_device的私有数据结构创建(netdev_priv(dev)获取)保存单个macvlan接口的信息。
同时会为其宿主接口(如果是在其上创建的第一个macvlan接口)的net_device挂载特殊设备接收处理函数rx_handler=macvlan_handle_frame,以及这个函数需要用到的参数rx_handler_data,就是macvlan_port。保存的是这个宿主接口以及其下的macvlan接口的整体信息,最重要的当然是查找报文dmac所对应的macvlan接口。
所有相关处理都在macvlan_common_newlink函数中。
struct macvlan_port {
struct net_device *dev;
struct hlist_head vlan_hash[MACVLAN_HASH_SIZE]; // macvlan设备macvlan_dev结构hash表,用于查找
struct list_head vlans; // macvlan设备macvlan_dev结构链表,用于遍历
struct rcu_head rcu;
struct sk_buff_head bc_queue; // 广播报文队列
struct work_struct bc_work; // 广播报文处理任务进程
bool passthru;
int count;
struct hlist_head vlan_source_hash[MACVLAN_HASH_SIZE]; // source模式用到
DECLARE_BITMAP(mc_filter, MACVLAN_MC_FILTER_SZ);
};
struct macvlan_dev {
struct net_device *dev; //macvlan网卡设备回指
struct list_head list;
struct hlist_node hlist;
struct macvlan_port *port; // struct macvlan_port 回指
struct net_device *lowerdev; // 宿主结构设备 回指
void *fwd_priv; // 物理网卡支持硬件加速时用到
struct vlan_pcpu_stats __percpu *pcpu_stats;
DECLARE_BITMAP(mc_filter, MACVLAN_MC_FILTER_SZ);
netdev_features_t set_features;
enum macvlan_mode mode;
u16 flags;
/* This array tracks active taps. */
struct macvtap_queue __rcu *taps[MAX_MACVTAP_QUEUES];
/* This list tracks all taps (both enabled and disabled) */
struct list_head queue_list;
int numvtaps;
int numqueues;
netdev_features_t tap_features;
int minor;
int nest_level;
#ifdef CONFIG_NET_POLL_CONTROLLER
struct netpoll *netpoll;
#endif
unsigned int macaddr_count;
};
宿主接口接收函数,macvlan_handle_frame。
/* called under rcu_read_lock() from netif_receive_skb */
static rx_handler_result_t macvlan_handle_frame(struct sk_buff **pskb)
{
struct macvlan_port *port;
struct sk_buff *skb = *pskb;
const struct ethhdr *eth = eth_hdr(skb);
const struct macvlan_dev *vlan;
const struct macvlan_dev *src;
struct net_device *dev;
unsigned int len = 0;
int ret;
rx_handler_result_t handle_res;
port = macvlan_port_get_rcu(skb->dev);
// 宿主接口接收到的广播报文,有可能是外部设备发送的,也可能是内部设备发送的,如外部设备hairpin模式返回的
if (is_multicast_ether_addr(eth->h_dest)) {
unsigned int hash;
skb = ip_check_defrag(dev_net(skb->dev), skb, IP_DEFRAG_MACVLAN);
if (!skb)
return RX_HANDLER_CONSUMED;
*pskb = skb;
eth = eth_hdr(skb);
// source模式,不管是广播还是单播,无脑根据smac匹配接收
macvlan_forward_source(skb, port, eth->h_source);
src = macvlan_hash_lookup(port, eth->h_source);
// private 模式不允许接收本地的接口(同一宿主接口派生出来的)发出的报文,passthru 模式只有一个派生接口,
// 这两种类型等同于只向自己发,不需要入队列了。
if (src && src->mode != MACVLAN_MODE_VEPA &&
src->mode != MACVLAN_MODE_BRIDGE) {
/* forward to original port. */
vlan = src;
ret = macvlan_broadcast_one(skb, vlan, eth, 0) ?:
netif_rx(skb);
handle_res = RX_HANDLER_CONSUMED;
goto out;
}
hash = mc_hash(NULL, eth->h_dest);
if (test_bit(hash, port->mc_filter))
macvlan_broadcast_enqueue(port, src, skb);
return RX_HANDLER_PASS;
}
/*source模式,不管是广播还是单播,无脑根据smac匹配接收。
而且不影响正常根据mac地址匹配做转发的流程。
所以这里如果smac 和 dmac同时和一个source类型的接口匹配了,岂不是接收了两份??但实际上没有,需要仔细看看什么原因。
*/
macvlan_forward_source(skb, port, eth->h_source);
if (port->passthru)
// passthru 模式只有一个派生接口,直接取链表中第一个数据
vlan = list_first_or_null_rcu(&port->vlans,
struct macvlan_dev, list);
else
// 其它在hash表中查
vlan = macvlan_hash_lookup(port, eth->h_dest);
if (vlan == NULL)
return RX_HANDLER_PASS;
dev = vlan->dev;
if (unlikely(!(dev->flags & IFF_UP))) {
kfree_skb(skb);
return RX_HANDLER_CONSUMED;
}
len = skb->len + ETH_HLEN;
skb = skb_share_check(skb, GFP_ATOMIC);
if (!skb) {
ret = NET_RX_DROP;
handle_res = RX_HANDLER_CONSUMED;
goto out;
}
*pskb = skb;
skb->dev = dev;
skb->pkt_type = PACKET_HOST;
ret = NET_RX_SUCCESS;
// 单播,修改了后skb->dev为macvlan接口,返回RX_HANDLER_ANOTHER,
// __netif_receive_skb_core 会重走自己的流程,等于在macvlan接口上再走一遍协议栈。
handle_res = RX_HANDLER_ANOTHER;
out:
macvlan_count_rx(vlan, len, ret == NET_RX_SUCCESS, false);
return handle_res;
}
macvlan接口发包流程
static netdev_tx_t macvlan_start_xmit(struct sk_buff *skb,
struct net_device *dev)
{
unsigned int len = skb->len;
int ret;
struct macvlan_dev *vlan = netdev_priv(dev);
if (unlikely(netpoll_tx_running(dev)))
return macvlan_netpoll_send_skb(vlan, skb);
// 硬件优化的代码,不用管
if (vlan->fwd_priv) {
skb->dev = vlan->lowerdev;
ret = dev_queue_xmit_accel(skb, vlan->fwd_priv);
} else {
// 实际调用 macvlan_queue_xmit
ret = macvlan_queue_xmit(skb, dev);
}
if (likely(ret == NET_XMIT_SUCCESS || ret == NET_XMIT_CN)) {
struct vlan_pcpu_stats *pcpu_stats;
pcpu_stats = this_cpu_ptr(vlan->pcpu_stats);
u64_stats_update_begin(&pcpu_stats->syncp);
pcpu_stats->tx_packets++;
pcpu_stats->tx_bytes += len;
u64_stats_update_end(&pcpu_stats->syncp);
} else {
this_cpu_inc(vlan->pcpu_stats->tx_dropped);
}
return ret;
}
static int macvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
{
const struct macvlan_dev *vlan = netdev_priv(dev);
const struct macvlan_port *port = vlan->port;
const struct macvlan_dev *dest;
// bridge模式,如果报文是发向同一宿主接口派生的bridge模式的macvlan接口,则转发,其它都被禁止。
if (vlan->mode == MACVLAN_MODE_BRIDGE) {
const struct ethhdr *eth = (void *)skb->data;
/* send to other bridge ports directly */
// 可以看到,只有bridge模式能向 bridge模式的派生口发广播报文
if (is_multicast_ether_addr(eth->h_dest)) {
macvlan_broadcast(skb, port, dev, MACVLAN_MODE_BRIDGE);
goto xmit_world;
}
dest = macvlan_hash_lookup(port, eth->h_dest);
// 可以看到,只有bridge模式能向 bridge模式的派生口发单播报文
if (dest && dest->mode == MACVLAN_MODE_BRIDGE) {
/* send to lowerdev first for its network taps */
dev_forward_skb(vlan->lowerdev, skb);
return NET_XMIT_SUCCESS;
}
}
xmit_world:
// 其它情况,直接走宿主物理口发送,所以macvlan接口的性能还是很高的,除了上面的简单的查表,不会做额外的封装,很接近物理口。
skb->dev = vlan->lowerdev;
return dev_queue_xmit(skb);
}
网友评论