Docker 网络基础 | 虚拟网络设备对(Veth)原理

Docker 网络基础 | 虚拟网络设备对(Veth)原理

作者: songsong001 2021-05-30 07:55:26

云计算

虚拟化 在容器化大行其道的今天,Docker 可谓是容器界的宠儿。比起笨重的虚拟机,Docker 可谓是身轻如燕。

 [[402397]]

本文转载自微信公众号「Linux内核那些事」,作者songsong001。转载本文请联系Linux内核那些事公众号。

在容器化大行其道的今天,Docker 可谓是容器界的宠儿。比起笨重的虚拟机,Docker 可谓是身轻如燕。当然,本文不是介绍虚拟机与 Docker 之间的优缺点,而是介绍 Docker 网络中重要的组成部分之一:

虚拟网络设备对:veth

在介绍 veth 前,我们先来介绍一下 网络命名空间(network namespace)。

网络命名空间

网络命名空间 是 Linux 内核用来隔离不同容器间的网络资源(每个 Docker 容器都拥有一个独立的网络命名空间),网络命名空间主要隔离的资源包括:

  • iptables规则表
  • 路由规则表
  • 网络设备列表

如下图所示,当系统中拥有 3 个网络命名空间:

由于不同的网络命名空间之间是相互隔离的,所以不同的网络命名空间之间并不能直接通信。比如在 网络命名空间A 配置了一个 IP 地址为 172.17.42.1 的设备,但在 网络命名空间B 里却不能访问,如下图所示:

就好比两台电脑,如果没有任何网线连接,它们之间是不能通信的。所以,Linux 内核提供了 虚拟网络设备对(veth) 这个功能,用于解决不同网络命名空间之间的通信。

虚拟网络设备对(veth)

虚拟网络设备对 用于解决不同网络命名空间之间的通信,可以将其看成是两块有网线连接的网卡。只要将其中一块网卡放置到网络命名空间A,另外一块网卡放置到网络命名空间B,那么两个不同的网络命名空间就能够通信,如下图所示:

如上图所示,veth0 与 veth1 组成一个虚拟网络设备对。虚拟网络设备对 就像管道一样,只要向其中一端发送数据,就可以从另外一端接收到数据。

Docker 就是使用 虚拟网络设备对 来实现不同容器之间的通信,其原理如下图:

从上图可以看出,每个容器之间并不是直接通过 虚拟网络设备对 来进行连接的,而是在主机上创建一个名为 docker0 的 网桥,然后通过 虚拟网络设备对 来将各个容器连接到 网桥 上。网桥 有将多个 网络设备 连接起来的能力,就如现实中的 交换机 一样。

当然,本文的主题是 veth 的实现,而不是 网桥 的现实,所以对 网桥 的介绍就此结束,有兴趣可以参考《Linux网桥工作原理与实现》一文。

虚拟网络设备对实现

在 Linux 内核中,使用 net_device 对象来表示一个网络设备。由于 veth 提供双向通信的功能,所以需要使用两个 net_device 对象来实现。由于 net_device 对象比较庞大,所以这里只列出本文相关的字段:

  1. struct net_device 
  2.     char name[IFNAMSIZ]; 
  3.     ... 
  4.     const struct net_device_ops *netdev_ops; 
  5.     ... 

下面介绍一下这两个字段的作用:

  • name:用来存储设备的名称,如 eth0。
  • netdev_ops:设备相关的操作接口列表,如初始化设备的接口、关闭设备的接口和发送数据的接口等。

由于 veth 由两个 net_device 对象组成的,所以这两个 net_device 对象应该有指向对方的指针。但通过查阅代码,并没发现有指向对方的指针,那么内核是怎么实现 veth 的呢?

虽然 Linux 内核使用 net_device 对象来表示一个网络设备,但由于不同厂商的网络设备可能存在各种差异,所以为了让 Linux 内核能够适应各种网络设备,故为不同的网络设备提供私有数据的存储空间。

也就是说,一个网络设备除了拥有 net_device 部分外,还有其私有数据部分。不同的网络设备其私有数据部分不同,而网络设备的私有数据部分存一般放在 net_device 对象的结束位置,如下图所示:

上图展示了 PCMCIA网卡 和 RTL-8139网卡 对应的私有数据部分存储的位置,PCMCIA网卡 的私有数据部分对应的是 pcnet_dev_t 结构,而 RTL-8139网卡 的私有数据部分对应的是 rtl8139_private 结构。

回到我们的主题,虚拟网络设备对 的私有数据部分由 veth_priv 结构表示,其定义如下:

  1. struct veth_priv { 
  2.     struct net_device *peer; 
  3.     struct veth_net_stats *stats; 
  4.     ... 
  5. }; 

下面介绍一下 veth_priv 结构各个字段的作用:

  • peer:由于 虚拟网络设备对 是由一对网络设备组成,所以此字段用于指向设备对的另外一个设备。
  • stats:用于保存统计信息。

从 veth_priv 结构可以看出,虚拟网络设备对 所属的两个设备对象是由 peer 字段来关联起来的,如下图所示:

1. 创建虚拟网络设备对

当使用 ip 命令创建一对 虚拟网络设备对 时,会触发调用 veth_newlink 函数来完成创建工作,其实现如下:

  1. static int 
  2. veth_newlink(struct net_device *dev, struct nlattr *tb[], struct nlattr *data[]) 
  3.     int err; 
  4.     struct net_device *peer; 
  5.     struct veth_priv *priv; 
  6.     char ifname[IFNAMSIZ]; 
  7.     ... 
  8.  
  9.     // 由于虚拟网络设备对是由两个网络设备组成, 
  10.     // dev 是虚拟网络设备对的其中一个网络设备, 
  11.     // 所以需要调用 rtnl_create_link() 函数创建的另外一个网络设备并保存到 peer 变量中. 
  12.     peer = rtnl_create_link(dev_net(dev), ifname, &veth_link_ops, tbp); 
  13.     ... 
  14.  
  15.     priv = netdev_priv(dev);  // 获取 dev 的私有数据部分 
  16.     priv->peer = peer;        // 将其 peer 字段指向 peer 
  17.  
  18.     priv = netdev_priv(peer); // 获取 peer 的私有数据部分 
  19.     priv->peer = dev;         // 将其 peer 字段指向 dev 
  20.  
  21.     return 0; 

上面代码经过精简后,保留了主要逻辑,所以 veth_newlink 主要完成以下工作:

  • 由于虚拟网络设备对是由两个网络设备组成,而 dev 是虚拟网络设备对的其中一个网络设备,所以需要调用 rtnl_create_link 函数创建的另外一个网络设备,并保存到 peer 变量中。
  • 将其 dev 设备对象的 peer 字段指向 peer 设备对象。
  • 将其 peer 设备对象的 peer 字段指向 dev 设备对象。

就这样,一对 虚拟网络设备对 的创建就完成了。

2. 初始化虚拟网络设备对

当然,在创建 虚拟网络设备对 时还需要对其进行初始化,初始化过程由 veth_setup 函数完成,其实现如下:

  1. static const struct net_device_ops veth_netdev_ops = { 
  2.     ... 
  3.     .ndo_start_xmit = veth_xmit, 
  4.     ... 
  5. }; 
  6.  
  7. static void veth_setup(struct net_device *dev) 
  8.     ... 
  9.     dev->netdev_ops = &veth_netdev_ops; 
  10.     ... 

在初始化 虚拟网络设备对 时,最重要的是设置其操作函数集。而 net_device_ops 结构是网络设备的操作函数集结构,当向设备发送数据时,将会触发调用设备操作函数集的 ndo_start_xmit 方法。

而 veth_setup 函数将此方法设置为 veth_xmit,也就是说,当向 虚拟网络设备对 的其中一端发送数据时,将会调用 veth_xmit 函数来发送数据。

3. 向虚拟网络设备对发送数据

当向 虚拟网络设备对 的其中一端发送数据时,将会调用 veth_xmit 函数来完成发送过程,其实现如下:

  1. static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev) 
  2.     struct net_device *rcv = NULL
  3.     struct veth_priv *priv, *rcv_priv; 
  4.     ... 
  5.  
  6.     // 获取发送数据设备的对端设备 
  7.     priv = netdev_priv(dev);  
  8.     rcv = priv->peer; 
  9.     ... 
  10.  
  11.     skb->tstamp.tv64 = 0; 
  12.     skb->pkt_type = PACKET_HOST; 
  13.     // 将数据包的接收设备设置为对端设备 
  14.     skb->protocol = eth_type_trans(skb, rcv); 
  15.     ... 
  16.  
  17.     // 将数据包上送给内核协议栈 
  18.     netif_rx(skb); 
  19.  
  20.     return NETDEV_TX_OK; 

我们先来介绍一下 veth_xmit 函数各个参数的意义:

  • skb:要发送的数据包对象。
  • dev:发送数据的设备。

veth_xmit 函数的实现比较简单,主要完成以下工作:

  • 获取发送数据设备的对端设备。
  • 将数据包的接收设备设置为对端设备。
  • 将数据包上送给内核协议栈。

我们通过下图来展示发送数据的过程:

如上图所示,当一个数据包从 虚拟网络设备对 的一端发送出去,会从其另外一端被接收,并上送到内核协议栈处理。

总结

由于 虚拟网络设备对 的出现,解决了容器间的通信问题。而本文主要分析了 虚拟网络设备对 的实现原理,但是有些细节并没有详细分析,如果有不懂的地方可以加我微信一起探讨。

 

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/252248.html<

(0)
管理的头像管理
上一篇2025-04-29 08:40
下一篇 2025-04-29 08:41

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注