guanjunjian

「十七」Linux虚拟网络设备veth veth_xmit源码分析

2018-01-03
guanjunjian

基于内核4.14.5

目的是了解veth的数据包转发流程,了解classid是在何处被丢弃的

1. 使用方法

//使用veth  
//1.创建两块虚拟网卡veth1、veth2,然后点对点连接,此后两块网卡的数据会互相发送到对方  
$ ip link add veth1 type veth peer name veth2  
  
//2.创建网络命名空间t1  
$ ip netns add t1  
  
//3.将veth0加入t1,此时veth1便看不到了,因为被加入到其他命名空间中了  
$ ip link set veth1 netns t1  
  
//4.配置veth1的ip地址  
$ ip netns exec t1 ifconfig veth1 192.168.1.200/24  
  
//5.设置t1网络的默认路由  
$ ip netns exec t1 route add default gw 192.168.1.1  
  
//6.此时将veth2加入本地网桥中,便可以实现veth1在t1中访问外部网络了,过程略,可参考docker中的网络配置步骤 

2. 源码分析

源码位于drivers\net\veth.c

2.1 向ip link注册服务

static __init int veth_init(void)
{
	return rtnl_link_register(&veth_link_ops);
}

其中veth_link_ops是与ip link一一对应的

static struct rtnl_link_ops veth_link_ops = {
	.kind		= DRV_NAME,
	.priv_size	= sizeof(struct veth_priv),
	.setup		= veth_setup,
	.validate	= veth_validate,
	.newlink	= veth_newlink,
	.dellink	= veth_dellink,
	.policy		= veth_policy,
	.maxtype	= VETH_INFO_MAX,
	.get_link_net	= veth_get_link_net,
};

2.2 新加入一条veth链接

static int veth_newlink(struct net *src_net, struct net_device *dev,
			struct nlattr *tb[], struct nlattr *data[],
			struct netlink_ext_ack *extack)
{
	int err;
	struct net_device *peer;
	struct veth_priv *priv;
	char ifname[IFNAMSIZ];
	struct nlattr *peer_tb[IFLA_MAX + 1], **tbp;
	unsigned char name_assign_type;
	struct ifinfomsg *ifmp;
	struct net *net;

	//首先创建并注册peer
	...
	//创建peer
	peer = rtnl_create_link(net, ifname, name_assign_type,
				&veth_link_ops, tbp);
	...
 	//注册peer
	err = register_netdevice(peer);
    ...
	//互相将peer保存到private中,在xmit的时候使用
	priv = netdev_priv(dev);
	rcu_assign_pointer(priv->peer, peer);

	priv = netdev_priv(peer);
	rcu_assign_pointer(priv->peer, dev);
	return 0;
}

上面的代码中涉及到了netdev_priv(),返回的是net设备的private数据,现在来看看这个数据是什么,根据参考2

而根据veth_newlink()可以找到,veth的private数据被映射为veth_priv结构体。

struct veth_priv {
	struct net_device __rcu	*peer;
	atomic64_t		dropped;
	unsigned		requested_headroom;
};

因此上面代码中priv = netdev_priv(dev)就是将dev的私有数据映射为一个veth_priv结构体,再把这个结构体的peer设为新生成的peer。同理对priv = netdev_priv(peer)将新生成的peer的peer设为dev。

2.3 初始化并启动veth设备

static void veth_setup(struct net_device *dev)
{
	//以太网设备的通用初始化 
	ether_setup(dev);

	dev->priv_flags &= ~IFF_TX_SKB_SHARING;
	dev->priv_flags |= IFF_LIVE_ADDR_CHANGE;
	dev->priv_flags |= IFF_NO_QUEUE;
	dev->priv_flags |= IFF_PHONY_HEADROOM;
    
	//veth的操作列表,其中包括veth的发送函数veth_xmit
	dev->netdev_ops = &veth_netdev_ops;
	dev->ethtool_ops = &veth_ethtool_ops;
	dev->features |= NETIF_F_LLTX;
	dev->features |= VETH_FEATURES;
	dev->vlan_features = dev->features &
			     ~(NETIF_F_HW_VLAN_CTAG_TX |
			       NETIF_F_HW_VLAN_STAG_TX |
			       NETIF_F_HW_VLAN_CTAG_RX |
			       NETIF_F_HW_VLAN_STAG_RX);
	dev->needs_free_netdev = true;
	dev->priv_destructor = veth_dev_free;
	dev->max_mtu = ETH_MAX_MTU;

	dev->hw_features = VETH_FEATURES;
	dev->hw_enc_features = VETH_FEATURES;
	dev->mpls_features = NETIF_F_HW_CSUM | NETIF_F_GSO_SOFTWARE;
}

2.4 通过veth发送skb

static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)
{
	//获取veth的私有数据
	struct veth_priv *priv = netdev_priv(dev);
	struct net_device *rcv;
	int length = skb->len;

	rcu_read_lock();
	//获得dev的peer
	rcv = rcu_dereference(priv->peer);
	//如果dev的peer为空,则将该数据包丢弃
	if (unlikely(!rcv)) {
		kfree_skb(skb);
		goto drop;
	}
	//调用dev_forward_skb向该veth的peer发送数据包
	if (likely(dev_forward_skb(rcv, skb) == NET_RX_SUCCESS)) {
	    //如果发送成功,则更新veth的数据包统计
		struct pcpu_vstats *stats = this_cpu_ptr(dev->vstats);

		u64_stats_update_begin(&stats->syncp);
		stats->bytes += length;
		stats->packets++;
		u64_stats_update_end(&stats->syncp);
	} else {
drop:
		atomic64_inc(&priv->dropped);
	}
	rcu_read_unlock();
	return NETDEV_TX_OK;
}

以上代码涉及到了rcu机制,可以阅读参考3来了解

  • 1.宽期限(rcu_read_lock()、rcu_read_unlock()、synchronize_rcu())
  • 2.订阅—发布机制(rcu_assign_pointer()、rcu_dereference())
  • 3.数据读取的完整性。

从代码中看到,veth_xmit()调用了dev_forward_skb(rcv, skb)将数据包发送给veth peer,这部分将在study18. dev_forward_skb分析中继续分析。

参考


Content