OVS在Hyper-V平台上的设计原则

原文 OVS-on-Hyper-V Design

声明:

  1. 本译文并未取得原作者授权,如有侵权行为,请发邮件到13581561959@163.com。我将立即删除。
  2. 翻译中,意译的地方较多。如有错误,或不准确的地方,欢迎邮件讨论,谢谢。

本文将详细说明Open vSwitch是如何在Microsoft的hyper-v平台上开发的。这些信息将帮助您理解整体的设计。

Note:
用户空间程序也已经被到了Hyper-V平台上并其提交到了openvswitch的代码库中,这是另一个小组完成的。本文着重阐述内核模块的移植,但是会涉及到用户空间程序。

背景(Background Info)

微软在其虚拟化平台 - Hyper-V 上实现了一个可扩展的虚拟交换机。因此,第三方厂商可以在虚拟交换机上开发新的功能。该扩展是通过将NDIS驱动绑定到可扩展虚拟交换机的驱动栈实现的。可扩展的功能包括监视、修改和转发数据报到Hyper-V交换机的端口。总的来说,可扩展的功能包括以下几种类型:

  • 捕获式扩展: 监视数据报
  • 过滤性扩展: 监视、修改数据报
  • 转发式扩展: 监视、修改、转发数据报

显而易见,在Hyper-V平台上,OVS的内核模块(datapath)是一个实现了转发功能的NDIS驱动。

在Hyper-V平台上,虚拟机被称为子工作区(Child Partition)。每一个虚拟网络接口或物理网卡通过一个端口(port)连接到虚拟交换机上。上行路径(ingress path)是指数据报从端口被发送出去;下行路径是指数据报在一个端口上被接收。NDIS驱动框架采用了分层接口(a layered interface)的设计模式。在这个分层接口的设计模式中,上行路径中,高层的接口调用其下一层的接口。在下行路径中,则与之相反。另外,控制操作是通过对象标识(OID,object identifier)接口完成的,例如,添加一个端口(port)。这些控制操作的工作流程与数据报的操作一样,高层接口调用底层接口。Hyper-V Extensible Switch Components中有这方面的详尽的示例图片。

Windows过滤平台(Windows Filtering Platform,WFP)提供了用于过滤数据报的APIs和服务。OVS使用WFP处理一些不能直接处理的数据报。后面将就该问题详细说明。

Hyper-V提供了一组用户获取主机的网络配置信息的接口,被称为IP Helpler。OVS使用这些接口获得必要的网络配置信息。

设计

上图中,包括了Windows平台上的OVS的各个模块(datapath、ovs-vswitchd和用户空间工具),NDIS驱动栈的相关模块,和虚拟机,以及它们之间的交互。从图中,可以看出一个数据包如何从一个虚拟接口被传输到另一个虚拟接口或物理网卡的。在本节稍后的部分,会从更高的层面阐述数据流。

通过该图,读者对于OVS的用户空间和内核组件有一个整体的认识,并且了解到它们是如何交互的。

在Hyper-V平台上,OVS的内核模块(datapath0)是一个转发扩展NDIS驱动,主要实现了下面的子模块/功能。关于它们的详细说明将在下面的部分说明。

  • 与NDIS驱动栈交互(Interfacing with the NDIS stack)
  • Netlink消息解析(Netlink message parser)
  • Netlink套接字(Netlink sockets)
  • 虚拟交换机管理(Switch/Datapath management)
  • 与用户空间程序交互完成特定的功能(Interfacing with userspace portion of the OVS solution to implement the necessary functionality that userspace needs)
  • 端口管理(Port management)
  • 数据报转发(Flowtable/Actions/packet forwarding)
  • 数据通信隧道(Tunneling)
  • 事件通知机制(Event notifications)

在Linux平台上,OVS的数据面的功能是在一个内核模块中实现的。然而,它不能被直接移植到Hyper-V平台,因为这两个平台从实现的功能上看市相似的,但在体系结构上市完全不同的。这些不同包括:

  • 在NDIS驱动栈中,NDIS的回调函数提供接收和发送数据报的功能,OIDs消息被用于处理事件,如在虚拟交换机上添加一个新的端口。
  • 用户空间与内核模块交互的接口
  • 事件通知的机制完全不同
  • DPIF和内核通信的接口没有必要按照Linux平台的方式实现。即便如此,考虑到带的可读性和维护,Hyper-V的OVS内核模块最好能够提供与Linux平台相似的接口。
  • 直接使用Linux内核代码设计到的授权许可

由于以上的这些差别,在Hyper-V平台上,从零开始开发OVS的datapath,而不是移植Linux内核模块的代码,是一个自然而然的决定。重新开发将集中完成下列的目标:

  • 根据OVS的用户空间程序(例如ovs-vswitchd)移植的需求,尽可能少地改变其工作流程。
  • 与Hyper-V的可扩展交换机的工作流程更好地融合

OVS的用户空间程序基本上是符合POSIX标准的代码,尽量少地使用Linux平台特有的功能。绝大部分的用户空间程序的代码不直接与内核模块交互,这部分代码的移植不依赖于内核模块的开发。

OVS移植指南中,对DPIF组件做了详细的说明。DPIF组件运行在用户空间,主要负责与内核模块交互,是用户空间程序与内核模块通信的桥梁。在dpif-provider.h中定义了一个DPIF provider必须实现的接口。虽然,在每一个平台上实现一个DPIF provider是被允许的,但是,从社区的反馈来看,更为理想的方式是尽可能地在不同的平台上使用通用的代码。所以,Hyper-V平台和Linux平台上的DPIF provider合用同一套代码逻辑。这些接口的代码在dpif-netlink.c文件中。

接下来,将在一个独立的小节中,详细阐述内核和用户空间接口。在此,你只需要知道在Windows平台上,DPIF provider是基于netlink的,并且它们合用代码。

内核模块 (Datapath)

NDIS驱动栈接口

在Hyper-V平台上,OVS交换机扩展模块能够被加载到任何一个虚拟交换机的驱动栈上。但是,OVS只支持被加载到一个虚拟交换机的NDIS驱动栈中。所以,与Linux平台一样,OVS在Hyper-V平台上也是一个数据平面。所有的物理网卡以外部网络适配器的方式连接到虚拟交换机上。

OVS交换机扩展驱动模块把自己注册为过滤驱动,同时也注册了交换机/端口管理和数据平面的回调函数。换而言之,当一个交换机在Hyper-V的根工作区(宿主机)上被创建后,OVS驱动模块在回调函数中初始化必须的数据结构。OVS驱动模块注册的回调函数包括,与端口管理相关的回调函数,以及外部物理网络适配器或者虚拟机的网络适配器与一个端口连接或断开连接的回调函数。另外,还有一组回调函数处理一个VIF(子工作区的网络适配器)发送数据报和一个外部网卡接收数据报。

如图所示,一个可扩展的交换机扩展模块会处理两次虚拟机发送的数据报 - 一次是在上行通道上,而另一次是在下行通道上。OVS的转发逻辑是基于上行通道设计的。因此,OVS模块实现以下的接口:

  • 上行发送数据:拦截数据报,并基于流表执行转发。这些动作包括,将数据报直接转发到端口。必要的数据报的修改,这可能会导致创建一个新的数据报。转发的动作按照流表中指定的动作执行。
  • 上行数据处理完成: 清理和释放在上行链路中OVS创建的数据报;放行上层模块创建的数据报。
  • 下行数据接收: 放行.
  • 下行数据接收完成: 放行.

内核模块与用户空间工具交互

为了内核模块与用户空间工具间通信,OVS开发实现了一个Windows的虚拟设备。该设备等同一个POSIX平台上经典的字符设备,提供读、写和ioctl接口。用户空间的netdev和DPIF组件使用虚拟设备的ioctls接口接收或发送消息。

Netlink消息解析

OVS用户空间工具和内核模块间的通信采用的是Netlink消息的方式。下面主要讨论这方面的细节。 在内核模块中,netlink消息解析器是依照用户空间工具的netlink消息解析器重写的。实际上,这部分代码中的大部分是从用户空间工具的代码移植过来的。

依照用户空间工具的struct ofpbuf的代码,为了更方便地解析和组装netlink消息,内核模块开发实现了一个可管理的缓存组件(a managed buffer)。

Netlink套接字

在Linux平台上,OVS用户空间工具使用netlink套接字发送和接收netlink消息。因此大部分的用户空间工具的代码包括DPIF provider被复用。OVS用户空间工具实现了一个虚拟的netlink套接字。众所周知,Windows平台不支持原生的netlink套接字,并且套接字的协议族也不支持扩展。所以,在Windows平台上实现一个原生的netlink套接字是不可能的。在lib/netlink-socket.c中实现了一个模拟的netlink套接字,并且支持所有的以nl开头的接口。每个netlink套接字会打开一个虚拟设备的句柄。更多的细节在用户空间工具一节的netlink套接字中被阐述。

Netlink套接字组件,支持典型的读消息、写消息、丢弃和事务的netlink原语。所以,上层逻辑不会因为netlink组件不是原生的而受到影响。

交换机管理

在前面解释过,在驱动加载时,内核模块向NDIS驱动栈注册了管理回掉函数,当这些回掉函数被调用时,OVS的数据结构,流表等被初始化。用户空间工具的一些操作也将通过ioctl接口调用到这些函数,例如,创建一个隧道接口等操作。

端口管理

我们已经知道,OVS内核模块通过向NDIS驱动栈注册的管理回掉函数获得端口被加入到Hyper-V虚拟交换机的信息。当这些回掉函数被调用时,与端口相关的数据结构将被初始化。另外,一些端口是隧道端口,这是OVS特有的,在Hyper-V交换机中并不存在,它们是OVS的用户空间工具创建的。

Hyper-V端口“frientlyName”域的值被用来标识端口是不是Hyper-V的接口。其被称之为“OVS-port-name”。用户空间工具设置Hyper-V端口“OVS-port-name”的值,这个值取自OVSDB的接口表name字段。当用户空间工具调用内核模块接口添加OVS端口时,通过OVS端口的名字和Hyper-V端口的“OVS-port-name”,OVS端口和Hyper-V端口被一一映射。

OVS端口,不论是由Hyper-V交换机添加的,还是OVS的用户工具添加的,都有独立的hash表和统计量。

流表/动作/数据报转发

基于流表及其动作的数据报转发是OVS内核模块的核心功能。上行链路的每一个数据报,会被匹配到相应的流表上,并执行流表中动作。这些动作可能只是简单的将数据报转发到对应的目的端口上,或者更为常见的修改数据报,如插入隧道的信息或VLAN的ID,然后通过外部端口把它们发送到目的主机上。

隧道

隧道技术的实现使用Hyper-V的内部端口。Hyper-V的内部端口是一个虚拟的网络适配器,其在Hyper-V主机上可见,并被连接到虚拟交换机上。Hyper-V内部端口是主机和虚拟交换机间的交互接口。内部端口的主要作用是隧道的终端节点(VTEP),其配置有隧道终端节点的IP地址。

在Hyper-V交换机上,并没有隧道端口。它们是由OVS维护的虚拟端口。在执行动作时,如果输出接口是隧道接口,OVS会立即基于隧道的信息对数据报执行封装操作。封装后的数据报被发送到外部端口上,当它们出现在外部的网络上时,这些数据报看起来是来自来VTEP的。

同样地,当一个隧道消息通过与内部端口(VTEP)绑定的外部端口被接收到时,如果是的话,被嵌入到隧道消息中的数据报被直接转发到目的端口上(大多数情况下是虚拟网络适配器,但是flow表决定是哪一个)。对于OVS无法处理的数据报,则交给Windows过滤平台(WFP)框架来处理。当前,OVS使用Windows的IP栈将被分片的IP数据报的重新组装以及去掉封装信息。

IP helper库函数提供相应的接口,通过它们,可以得到内部端口的IP地址和其他的信息。

事件通知

前面提到的虚拟设备被用来向用户空间工具发送事件通知。在这里使用了共享内存和重叠IO的方式。

用户空间组件

OVS的用户空间程序基本上是符合POSIX标准的代码,尽量少地使用Linux平台特有的功能。绝大部分的用户空间程序的代码不直接与内核模块交互,这部分代码的移植不依赖于内核模块的开发。

本小节,主要讨论与内核模块交互的用户空间组件。

我们已经知道,Hyper-V平台上的OVS复用了Linux平台上的DPIF provider的实现。在Linux平台上,DPIF provider通过netlink套接字和netlink消息在内核和用户空间间传递消息。在Linux平台上,这种方式被广泛地使用。为了解决这个问题,OVS在Hyper-V平台上实现了netlink套接字(虚拟的,非原生的Netlink socket)和netlink消息。

复用DPIF providr代码的主要的优势在于:

  • 易于维护:
    在dpif-provider.h中定义的接口任意的改变不会导致大量的修改。另外,熟悉DPIF provider的Linux平台代码的开发者能容易地在Hyper-V平台上继续开发。
  • Netlink消息的内在的优势
    Netlink消息的扩展性非常好。每个消息都有版本号,通过版本检查,用户空间工具能更好地与内核模块兼容。

Netlink套接字

我们已经知道,在windows平台上,OVS开发了一个模拟的netlink套接字,实现代码在lib/netlink-socket.c。Netlink套接字创建一个OVS虚拟设备的句柄,并且模拟netlink套接字的接收消息、发送消息、丢弃和事务的原语。大多数的nl开头的函数都被支持。

事实上,模拟的netlink套接字不是原生的在很多方面是显而易见的。一个例子是,netlink套接字的PID在OVS的虚拟设备被创建时,不是被自动赋值的。netlink的PID是通过一个额外的命令(在OvsDpInterfaceExt.h中定义)从内核中获得的。

DPIF Provider

前面已经提到过,在Linux上实现的基于netlink套接字和netlink消息的DPIF provider被移植到Windows平台上。

在两个平台上,大部分的代码是一样的。接收消息的部分是完全不同的。Linux平台上使用epoll函数监视套接字的状态,Windows平台不支持这样的机制。

netdev-windows

在lib/netdev-provider.h中定义的网络适配器接口的windows版本,提供了获得其很多属性信息的功能。与在Linux平台上实现的netdev provider相比,它的功能很简单;并且不支持创建任何的网络适配器接口,如tap设备,以及接收和发送数据报。在datapath-windows/include/OvsDpInterfaceExt.h中定义了被netdev-windows使用的内核模块接口。

设置OVS-port-name的Powershell脚本

在“端口管理”一节读者已经了解到,每个Hyper-V的虚拟交换机的端口有一个名字为‘FriendlyName’属性,在OVS中,其被称之为“OVS-port-name”。OVS的代码中包含一个powershell的命令,其实现了设置Hyper-V的端口的“OVS-port-name”属性的功能。

内核模块与用户空间工具接口

openvswitch.h and OvsDpInterfaceExt.h

因为DPIF provider组件复用了Linux平台的代码,Windows内核数据模块提供了与Linux数据模块同样的接口。在Linux平台上,这些接口定义在datapath/linux/compat/include/linux/openvswitch.h文件中。在Windwos平台上,在编译用户空间工具时,这些接口的定义和声明被导出到datapath-windows/include/OvsDpInterface.h文件中。

也就是说,新的接口文件中包含了一些Windows平台独有的特性。

数据报的处理流程

图1中用被标号的步骤描述了,一个数据报从一个虚拟网络适配器被发出,然后被转发到其他的虚拟网络适配器或物理网络适配器的过程。至此,读者已经了解到,每一个虚拟网络适配器通过一个端口被连接到一个虚拟交换机上,同时每个端口都可以从虚拟交换机的上行或下行路径上发送或接收数据报。在图1中,每个步骤n被标注为#n。

数据报的处理流程:

  1. 当一个数据报从虚拟网络适配器、物理网络适配器或一个虚拟交换机的内部端口被发送后,该数据报就出现在网络的上行路径上。
  2. OVS内核模块接收到数据报。
    1. OVS从流表中查找与数据报匹配的表项,然后执行相应的动作。
    2. 如果没有找到匹配的表项,该数据报被发送给用户空间程序,其将检查数据报,并确定相应的动作。
    3. 用户空间程序通过指定操作运行数据报,并且也许在将来对这样的数据报插入一个流表项。

      译者注:
      这一段是直译。Linux平台的处理过程中,这一步是基于数据报生成一个流表项,然后将该流表项注入到内核模块中。

    4. 目标端口被加入到数据报中,然后将其返还给Hyper-V虚拟交换机。
  3. Hyper-V虚拟交换机依据数据报中的目的端口转发数据报,也就是将数据报推送到下行路径上。
  4. 数据报被转发到目的虚拟网络适配器上。
  5. 如果物理网络适配器是数据报的目的端口,那么它就会被转发到物理网络适配器上。

编译和部署

Windows平台的用户空间组件可以使用autoconf工具来配置编译工具链,因此可以按照BUILD.Windows文件的步骤配置。剩余的工作就是使用make命令了。

OVS内核模块是一个Visual Studio 2013的解决方案,请使用VS2013来编译。计划在不久的将来,编译可以不依赖于IDE。

代码中包含一个用于加载内核驱动的脚本。一旦编译成功,它能帮助你完成其余的工作。