其他资料
http://docs.openvswitch.org/en/latest/tutorials/ovs-conntrack/
OVS架构解析--dpdk datapath数据通路
1. 架构说明
1.1. 整体架构
OVS(openvswitch)是开源的虚拟交换机。也是当前市场上云环境中部署份额最大的交换机。支持 openflow协议,ovsdb协议管理。
一个OVS实例包括,ovsdb-server、ovs-vswitchd、datapath快转模块(linux内核中实现,可选的。dpdk模式是在用户态实现快转,并不需要内核态的datapath模块)。
- ovsdb-server:作用是对ovsdb操作。
- ovs-vswitchd:核心模块,作用是实现OpenFlow交换机、和controller通信、和db通信、实现用户态转发、和内核态快转路径通信。
- datapath:在内核空间实现报文快速转发。
![](http://img.e-com-net.com/image/info8/103bd3e47f2340c3b6cc6f38cd6720e6.jpg)
上图从整体架构说明了ovs的工作方式。ovs包括ovsdb配置管理方式和openflow流表转发控制方式。
- ovsdb配置管理方式:管理者通过OVSDB管理协议管理OVS交换机。OVSDB管理协议的规范文件是RFC 7047(The Open vSwitch Database Management Protocol)。RFC7047主要包括,定义OVSDB的结构、交互协议(JSON-RPC)、DB的操作类型。
- openflow流表控制方式:controller控制器通过OpenFlow协议给OVS交换机下发流表,控制交换机的转发行为。controller不通过OVSDB,而是直接向OVS交换机下发流表。
1.2. 核心组件及其关联关系
![](http://img.e-com-net.com/image/info8/26672b0cc942412ab1d99190672fbd00.jpg)
ovsdb-server和ovs-vswitchd之间是通过socket交互信息。
ovs-vswitchd通过netlink和内核态快转模块通信。
比如通过ovs-vsctl命令增加ovs交换机接口,ovs-vsctl会通过ovsdb-server向ovsdb更新数据,ovs-vswitchd监测到ovsdb变化时,会更新交换机配置,比如添加接口。
dpdk方式的ovs除了不使用内核模块外,架构和图中相同。
1.3. 内部模块及其关联关系
为了有个整体的了解,下面列几张整体的架构图,不做详细解释,后文会有流程解释。![](http://img.e-com-net.com/image/info8/2769b1f78b9c4213b5519e9285c5f46d.jpg)
![](http://img.e-com-net.com/image/info8/031280e145a946e1b2756868486e5bfa.jpg)
![](http://img.e-com-net.com/image/info8/552b54b720e44769a97773c79cb8b466.jpg)
2. vswitchd和ovsdb通信
IDL:接口描述语言。IDL是用来描述软件组件接口的一种计算机语言。IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流。也就是说,用一种通用的格式,比如下面数据就是描述一个Open_vSwitch表,最后生成C语言描述的表操作.c文件![](http://img.e-com-net.com/image/info8/fa461ea8c9dd48c9a77b26f3f44c269c.jpg)
2.1. 建立连接
ovsdb_idl_create建立一个ovsdb的连接session,形如:idl =ovsdb_idl_create(remote, &ovsrec_idl_class, true, true)。
remote类似:unix:/var/run/openvswitch/db.sock。
后续使用该idl(struct ovsdb_idl)和ovsdb通信,更新数据。
2.2. 接收数据
使用jsonrpc_session_recv函数接收数据,形如:msg = jsonrpc_session_recv(idl->session)。
接收消息的结构是:
struct jsonrpc_msg {
enum jsonrpc_msg_type type;
char *method; /* Request or notification only. */
struct json *params; /* Request or notification only. */
struct json *result; /* Successful reply only. */
struct json *error; /* Error reply only. */
struct json *id; /* Request or reply only. */
};
其中type是JSON-RPC 1.0定义的消息类型,包括request、notification、reply、error:
/* Messages. */
enum jsonrpc_msg_type {
JSONRPC_REQUEST, /* Request. */
JSONRPC_NOTIFY, /* Notification.*/
JSONRPC_REPLY, /* Successfulreply. */
JSONRPC_ERROR /* Error reply. */
};
params、result、error、id是不同类型的消息对应的消息数据。
当解析消息后,监测到需要更新本地数据时,调用ovsdb_idl_parse_update函数,解析消息并更新本地数据。2.3. json数据解析
/* A JSON value. */
struct json {
enum json_type type;
union {
struct shash *object; /* Contains "struct json *"s. */
struct json_array array;
long long int integer;
double real;
char *string;
} u;
};
type字段:说明了该json数据是联合体中的哪一种类型。
/* Type of a JSON value. */
enum json_type {
JSON_NULL, /* null */
JSON_FALSE, /* false */
JSON_TRUE, /* true */
JSON_OBJECT, /* {"a": b, "c": d, ...}*/
JSON_ARRAY, /* [1, 2, 3, ...] */
JSON_INTEGER, /* 123. */
JSON_REAL, /* 123.456. */
JSON_STRING, /* "..." */
JSON_N_TYPES
};
联合体u:表示数据可以解析的类型。
例如,当type是JSON_OBJECT类型时,且是update2更新操作,那么object字段保存着多个数据表,每张表数据中又携带着待更新的多个row数据,和每row对应的操作(modify、insert、delete、initial)。然后遍历每个表的每行数据,并执行对应的数据操作(modify、insert、delete、initial)对本地数据更新。
注:本地数据和远端的OVSDB并不相同,本地数据是vswitchd从数据库取出数据后临时放到本地结构中的。
2.4. 更新数据改变标记
当监听到ovsdb-server发来的rpc消息时,如果数据库发送改变就改变对应的change_seqno。后续vswitchd根据change_seqno的值是否发生了变化,决定是否重新配置ovs交换机。
3. dpdk加速处理
3.1. ovs dpdk加速配置
在使用dpdk 类型的datapath加速之前,需要设置dpdk-init=true启动参数。
设置方法:
ovs-vsctl --no-wait set Open_vSwitch .other_config:dpdk-init=true
设置dpdk的相关参数,都通过other_config选项完成。
主要的参数有:
- dpdk-init
指定ovs是否初始化和支持dpdk端口。
- dpdk-lcore-mask
指明dpdk使用的逻辑核。同dpdk的-c参数。
- dpdk-socket-mem
指明不同numa节点提前申请的大页内存。同dpdk的--socket-mem参数。
- dpdk-hugepage-dir
大页文件系统mount的目录。同dpdk的--huge-dir参数。
- vhost-sock-dir
设置vhost-user 套接字的路径。
- dpdk-extra
其他的dpdk配置参数。
3.2. dpdk初始化
主要包括:
1) dpdk eal初始化。
2) 启动dpdk接口状态监控线程(使用dpdk的库函数),如果状态发送变化,则更新netdev设备的变化标记。
3) 注册dpdk类型的netdev class。其中包括dpdk设备类型,ring类型、vhost、vhost client类型。
4. 数据结构抽象关系
4.1. 分层抽象
![](http://img.e-com-net.com/image/info8/0cfd4a830c9843fcaf4f1e063cf89e03.jpg)
- ofproto class
openflow交换机实现类,用来实现一个openflow交换机。主要包括创建、构造、操作openflowflow等相关方法。
- dpif class
datapath接口类,用来和datapath交互。主要包括datapath的open、run、端口操作、端口数据监听等相关方法。datapath是数据面的一种抽象。
- netdev class
网络设备抽象类,用来和网络设备交互。主要包括网卡的设置、网卡结构的抽象、数据包发送接收等相关方法。
- ofproto class
openflow交换机实现类,用来实现一个openflow交换机。主要包括创建、构造、操作openflowflow等相关方法。
- dpif class
datapath接口类,用来和datapath交互。主要包括datapath的open、run、端口操作、端口数据监听等相关方法。datapath是数据面的一种抽象。
- netdev class
网络设备抽象类,用来和网络设备交互。主要包括网卡的设置、网卡结构的抽象、数据包发送接收等相关方法。
bridge网桥通过ofproto class构造一个openflow交换机。再通过dpif class打开(如果没有对应类型的datapath,就创建一个)对应类型的datapath通路。使用netdev class构造一个抽象网络设备,上层抽象为端口,然后对网络设备收发包等操作。
需要说明的是,datapath是按类型分类的,现在ovs有两种类型的datapath:system(对应linux内核数据通路)、netdev(dpdk用户态数据通路)。所以使用同一种datapath类型的ovs网桥共享同一个datapath对象。如下图所示。
![](http://img.e-com-net.com/image/info8/792d13d4bdb94fe9905ef3e5a82a31a6.jpg)
4.2. 主要数据结构关系
该部分说明的内容是关于dpdk类型的datapath。
注:A B,表示A是一个变量而非指针,它的结构和内容是B。
流程解释如下:
1) bridge层
1.1 struct bridge对应每个ovs网桥最上层的结构。它通过成员指针变量ofproto,联系到struct ofproto交换机结构内容。
2) ofproto层
2.1 由于联系到的struct ofproto内容是struct ofproto_dpif结构的一个up成员变量。所以通过struct ofproto地址可以cast到struct ofproto_dpif结构内容,通过该内容和dpif层交互。
2.2 通过struct ofproto_dpif的成员指针变量dpif_backer,联系到struct dpif_backer结构内容。该结构是dpif操作的入口点。
3) dpif层
3.1 通过struct dpif_backer的成员指针变量dpif,联系到structdpif结构内容。
3.2 由于struct dpif结构内容是struct dpif_netdev结构的dpif成员变量,所以通过structdpif地址可以cast到struct dpif_netdev结构内容。
3.3 struct dpif_netdev结构内容中有指针成员变量dp,通过dp可以联系到struct dp_netdev结构内容。该内容是datapath的抽象结构内容。
4) datapath层
4.1 struct dp_netdev的成员指针变量ports是一个表结构,它联系着多个port端口内容。端口的结构是struct dp_netdev_port。struct dp_netdev另外一个成员指针变量netdev,联系着dp层的struct netdev结构内容,该结构内容是网卡的逻辑抽象。
4.2 struct dp_netdev_port的指针成员变量rxqs也是一个表结构,它联系着多个队列内容。队列的结构是struct dp_netdev_rxq.它的成员变量core_id表示该队列所在的core。
4.3 通过struct dp_netdev_rxq的成员指针变量rxq,联系到dp层的struct netdev_rxq结构内容。structdp_netdev_rxq的成员变量queue_id表示该队列id。
5) netdev层
5.1 由于struct netdev结构内容是struct netdev_dpdk结构的一个up成员变量。所以通过struct netdev地址可以cast到struct netdev_dpdk结构内容。
5.2 通过struct detdev_dpdk结构内容的成员指针变量netdev_class和dpdk类型网卡交互。
5.3 由于struct netdev_rxq结构内容是struct netdev_rxq_dpdk结构内容的一个up成员变量。所以通过struct netdev_rxq地址可以cast到struct netdev_rxq_dpdk结构内容。该内容是dpdk类型的netdev_rxq,它的成员变量port_id是dpdk网络设备的port。
5. vswitchd启动
简要的概括下vswitchd启动的内容。
1) 和ovsdb-server通信初始化,建立
2) dpdk初始化
3) ofproto(OpenFlow交换机)初始化
4) netdev初始化
5) 网桥配置
更新网桥(添加、删除)
更新端口
创建ofproto(OpenFlow交换机),创建或打开dpif_backer
添加端口,添加到pmd中
配置网桥、端口属性
datapath run
dp_netdev的pmd更新
dpif_run(运行dpif-netdev的datapath,配置netdev等,接收发送报文)
ofproto run,配置ofproto需要的参数和功能(netflow、sflow、ipfix、mac学习)
OpenFlow控制器连接相关run
6. 附录-数据结构查询
6.1. ofproto_class
是一个OpenFlow交换机的实现类结构,提供的类功能很多,主要分为:
1. 工厂函数功能。如初始化、枚举支持的datapath类型。
2. datapath类型处理。
3. OpenFlow交换机功能。如构造、运行等。
4. OpenFlow流表功能。
5. OpenFlow配置。
6. OpenFlow计量。
7. OpenFlow 1.1 group功能。
8. 获取datapath信息功能。
9. 连接表功能。
6.2. dpif_class
datapath接口实现类,每种datapath都有对应的接口实现实例。dpif有两种实现类:dpif_netlink_class、dpif_netdev_class。即system、netdev。比如,dpdk的netdevdatapath类型的实现实例dpif_netdev_class。
6.3. netdev_class
网络设备实现类。包括抽象的设备结构和收发包功能。
6.4. bridge
6.5. ofproto
6.6. port
6.7. iface
6.8. ofport
6.9. ofport_dpif
6.10. ofproto_port
6.11. dpif_port
dp向dpif返回的端口信息。
6.12. ofproto_dpif
6.13. dpif_backer
datapath类型共享实现实例。
6.14. udpif
6.15. dpif
datapath接口。
6.16. dpif_netdev
netdev类型的datapath的接口。
6.17. dp_netdev
基于网络设备接口的datapath。
6.18. dp_netdev_port
基于netdev类型的datapath的端口。
挂载在dp_netdev的ports成员中。
6.19. netdev
抽象的网络设备。
6.20. netdev_dpdk
dpdk类型的netdev结构,继承自netdev结构。
6.21. dp_netdev_rxq
用来表示core和队列的对应关系。
6.22. netdev_rxq
网络设备的收包队列结构,以队列区分。
6.23. netdev_rxq_dpdk
dpdk的收包队列结构,继承自netdev_rxq.
端口号和netdev_dpdk中的端口号相同。
使用dpdk的接口函数rte_zmalloc申请,受dpdk的内存管理控制。
6.24. dp_netdev_pmd_thread
6.25. rxq_poll
6.26. tx_port
OVS 架构
OVS 是产品级的虚拟交换机,大量应用在生产环境中,支撑整个数据中心虚拟网络的运转。OVS 基于 SDN 的思想,将整个核心架构分为控制面和数据面,数据面负责数据的交换工作,控制面实现交换策略,指导数据面工作。
从整体上看,OVS 可以划分为三大块,管理面、数据面和控制面。
数据面就是以用户态的 ovs-vswitchd 和内核态的 datapath 为主的转发模块,以及与之相关联的数据库模块 ovsdb-server,控制面主要是由 ovs-ofctl 模块负责,基于 OpenFlow 协议与数据面进行交互。而管理面则是由 OVS 提供的各种工具来负责,这些工具的提供也是为了方便用户对底层各个模块的控制管理,提高用户体验。下面就对这些工具进行一个逐一的阐述。
ovs-ofctl
:这个是控制面的模块,但本质上它也是一个管理工具,主要是基于 OpenFlow 协议对 OpenFlow 交换机进行监控和管理,通过它可以显示一个 OpenFlow 交换机的当前状态,包括功能、配置和表中的项。使用时,有很多参数,我们可以通过 ovs-ofctl --help 查看。
-
常用命令:
-
-
ovs-ofctl show switch-name :输出交换机信息,包括其流量表和端口信息。
-
-
ovs-ofctl dump-ports switch-name:输出交换机的端口统计信息,包括收发包、丢包、错误包等数量。
-
-
ovs-ofctl add-flow switch-name:为交换机配置流策略。
ovs-dpctl
:用来配置交换机的内核模块 datapath,它可以创建,修改和删除 datapath,一般,单个机器上的 datapath 有 256 条(0-255)。一条 datapath 对应一个虚拟网络设备。该工具还可以统计每条 datapath 上的设备通过的流量,打印流的信息等,更过参数通过 ovs-dpctl --help 查看。
-
常用命令:
-
-
ovs-dpctl show :显示所有 datapath 的基本信息。
-
-
ovs-dpctl dump-dps :显示所有 datapath 的名字。
-
-
ovs-dpctl dump-flows DP :显示一条 datapath DP 上的流信息。
ovs-appctl
:查询和控制运行中的 OVS 守护进程,包括 ovs-switchd,datapath,OpenFlow 控制器等,兼具 ovs-ofctl、ovs-dpctl 的功能,是一个非常强大的命令。ovs-vswitchd 等进程启动之后就以一个守护进程的形式运行,为了能够很好的让用户控制这些进程,就有了这个命令。详细可以 ovs-appctl --help 查看。
ovs-vsctl
:查询和更新 ovs-vswitchd 的配置,这也是一个很强大的命令,网桥、端口、协议等相关的命令都由它来完成。此外,还负责和 ovsdb-server 相关的数据库操作。
-
常用命令:
-
-
ovs-vsctl show :显示主机上已有的网桥及端口信息。
-
-
ovs-vsctl add-br br0:添加网桥 br0。
ovsdb-client
:访问 ovsdb-server 的客户端程序,通过 ovsdb-server 执行一些数据库操作。
-
常用命令:
-
-
ovsdb-client dump:用来查看ovsdb内容。
-
-
ovsdb-client transact :用来执行一条类 sql。
ovsdb-tool
:和 ovsdb-client 要借助 ovsdb-server 才能进行相关数据库操作不同,ovsdb-tool 可以直接操作数据库。
OVS 源码结构
OVS 源码结构中,主要包含以下几个主要的模块,数据交换逻辑在 vswitchd 和 datapath 中实现,vswitchd 是最核心的模块,OpenFlow 的相关逻辑都在 vswitchd 中实现,datapath 则不是必须的模块。ovsdb 用于存储 vswitch 本身的配置信息,如端口、拓扑、规则等。控制面部分采用的是 OVS 自家实现的 OVN,和其他控制器相比,OVN 对 OVS 和 OpenStack 有更好的兼容性和性能。
从图中可以看出 OVS 的分层结构,最上层 vswitchd 主要与 ovsdb 通信,做配置下发和更新等,中间层是 ofproto ,用于和 OpenFlow 控制器通信,并基于下层的 ofproto provider 提供的接口,完成具体的设备操作和流表操作等工作。
dpif 层实现对流表的操作。
netdev 层实现了对网络设备(如 Ethernet)的抽象,基于 netdev provider 接口实现多种不同平台的设备,如 Linux 内核的 system, tap, internal 等,dpdk 系的 vhost, vhost-user 等,以及隧道相关的 gre, vxlan 等。
数据转发流程
通过一个例子来看看 OVS 中数据包是如何进行转发的。
1)ovs 的 datapath 接收到从 ovs 连接的某个网络端口发来的数据包,从数据包中提取源/目的 IP、源/目的 MAC、端口等信息。
2)ovs 在内核态查看流表结构(通过 hash),如果命中,则快速转发。
3)如果没有命中,内核态不知道如何处置这个数据包,所以,通过 netlink upcall 机制从内核态通知用户态,发送给 ovs-vswitchd 组件处理。
4)ovs-vswitchd 查询用户态精确流表和模糊流表,如果还不命中,在 SDN 控制器接入的情况下,经过 OpenFlow 协议,通告给控制器,由控制器处理。
5)如果模糊命中, ovs-vswitchd 会同时刷新用户态精确流表和内核态精确流表,如果精确命中,则只更新内核态流表。
6)刷新后,重新把该数据包注入给内核态 datapath 模块处理。
7)datapath 重新发起选路,查询内核流表,匹配;报文转发,结束。
总结
OVS 为了方便用户操作,提供了很多管理工具,我们平常在使用过程中只需记住每个工具的作用,具体的命令可以使用 -h 或 --help 查看。
创建一个OVS 交换机
创建一个叫ovs-switch的交换机
-
$ ovs-vsctl add-br ovs-switch
-
创建一个端口 p0,设置端口 p0 的 OpenFlow 端口编号为 100(如果在创建端口的时候没有指定 OpenFlow 端口编号,OVS 会自动生成一个)。
-
$ ovs-vsctl add-port ovs-switch p0 -- set Interface p0 ofport_request=100
-
设置网络接口设备的类型为"internal"。对于 internal 类型的的网络接口,OVS 会同时在 Linux 系统中创建一个可以用来收发数据的模拟网络设备。我们可以为这个网络设备配置 IP 地址、进行数据监听等操作。
-
$ ovs-vsctl set Interface p0 type=internal
-
$ ethtool -i p0
-
driver: openvswitch
-
version:
-
firmware-version:
-
bus-info:
-
supports-statistics: no
-
supports-test: no
-
supports-eeprom-access: no
-
supports-register-dump: no
为了避免网络接口上的地址和本机已有网络地址冲突,我们可以创建一个虚拟网络空间 ns0,把 p0 接口移入网络空间 ns0,并配置 IP 地址为 192.168.1.100
-
$ ip netns add ns0
-
$ ip link set p0 netns ns0
-
$ ip netns exec ns0 ip addr add 192.168.1.100/24 dev p0
-
$ ip netns exec ns0 ifconfig p0 promisc up
使用同样的方法创建端口 p1、p2
创建的端口信息
端口 |
说明 |
p0 |
IP 地址: 192.168.1.100/24 |
p1 |
IP 地址: 192.168.1.101/24 |
p2 |
IP 地址: 192.168.1.102/24, |
创建所有的端口之后, 查看 OVS 交换机的信息
-
$ ovs-vsctl show
-
30282710-d401-4187-8e13-52388f693df7
-
Bridge ovs-switch
-
Port "p0"
-
Interface "p0"
-
type: internal
-
Port "p2"
-
Interface "p2"
-
type: internal
-
Port "p1"
-
Interface "p1"
-
type: internal
-
Port ovs-switch
-
Interface ovs-switch
-
type: internal
使用 ovs-ofctl 创建并测试 OpenFlow 命令
查看 Open vSwitch 中的端口信息。从输出结果中,可以获得交换机对应的 datapath ID (dpid),以及每个端口的 OpenFlow 端口编号,端口名称,当前状态等等。
-
$ ovs-ofctl show ovs-switch
-
OFPT_FEATURES_REPLY (xid= 0x2): dpid:00001232a237ea45
-
n_tables: 254, n_buffers:256
-
capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP
-
actions: OUTPUT SET_VLAN_VID SET_VLAN_PCP STRIP_VLAN SET_DL_SRC SET_DL_DST
-
SET_NW_SRC SET_NW_DST SET_NW_TOS SET_TP_SRC SET_TP_DST ENQUEUE
-
100(p0): addr:54:01:00:00:00:00
-
config: PORT_DOWN
-
state: LINK_DOWN
-
speed: 0 Mbps now, 0 Mbps max
-
101(p1): addr:54:01:00:00:00:00
-
config: PORT_DOWN
-
state: LINK_DOWN
-
speed: 0 Mbps now, 0 Mbps max
-
102(p2): addr:54:01:00:00:00:00
-
config: PORT_DOWN
-
state: LINK_DOWN
-
speed: 0 Mbps now, 0 Mbps max
-
LOCAL(ovs-switch): addr: 12:32:a2:37:ea:45
-
config: 0
-
state: 0
-
speed: 0 Mbps now, 0 Mbps max
-
OFPT_GET_CONFIG_REPLY (xid= 0x4): frags=normal miss_send_len=0
如果想获得网络接口的 OpenFlow 编号,也可以在 OVS 的数据库中查询
-
$ ovs-vsctl get Interface p0 ofport
-
100
查看 datapath 的信息
-
$ ovs-dpctl show
-
system@ovs-system:
-
lookups: hit: 12173 missed:712 lost:0
-
flows: 0
-
port 0: ovs-system (internal)
-
port 1: ovs-switch (internal)
-
port 2: p0 (internal)
-
port 3: p1 (internal)
-
port 4: p2 (internal)
屏蔽数据包
屏蔽所有进入 OVS 的以太网广播数据包
-
$ ovs-ofctl add-flow ovs-switch "table=0, dl_src=01:00:00:00:00:00/01:00:00:00:00:00, actions=drop"
-
屏蔽 STP 协议的广播数据包
-
$ ovs-ofctl add-flow ovs-switch "table=0, dl_dst=01:80:c2:00:00:00/ff:ff:ff:ff:ff:f0, actions=drop"
-
修改数据包
添加新的 OpenFlow 条目,修改从端口 p0 收到的数据包的源地址为 9.181.137.1
-
$ ovs-ofctl add-flow ovs-switch "priority=1 idle_timeout=0,\
-
in_port=100,actions=mod_nw_src:9.181.137.1,normal"
$ ip netns exec ns0 ping 192.168.1.101
在接收端口 p1 监控数据,发现接收到的数据包的来源已经被修改为 9.181.137.1
-
$ ip netns exec ns1 tcpdump -i p1 icmp
-
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
-
listening on p1, link-type EN10MB (Ethernet), capture size 65535 bytes
-
15:59:16.885770 IP 9.181.137.1 > 192.168.1.101: ICMP echo request, id 23111, seq 457, length 64
-
15:59:17.893809 IP 9.181.137.1 > 192.168.1.101: ICMP echo request, id 23111, seq 458, length 64
重定向数据包
添加新的 OpenFlow 条目,重定向所有的 ICMP 数据包到端口 p2
-
$ ovs-ofctl add-flow ovs-switch idle_timeout=0,dl_type=0x0800,nw_proto=1,actions=output:102
-
从端口 p0 (192.168.1.100)发送数据到端口 p1(192.168.1.101)
-
$ ip netns exec ns0 ping 192.168.1.101
-
在端口 p2 上监控数据,发现数据包已被转发到端口 p2
-
$ ip netns exec ns3 tcpdump -i p2 icmp
-
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
-
listening on p2, link-type EN10MB (Ethernet), capture size 65535 bytes
-
16:07:35.677770 IP 192.168.1.100 > 192.168.1.101: ICMP echo request, id 23147, seq 25, length 64
-
16:07:36.685824 IP 192.168.1.100 > 192.168.1.101: ICMP echo request, id 23147, seq 26, length 64
修改数据包的 VLAN Tag
除了使用"ping"、"tcpdump"和"iperf" 等 Linux 命令以外,我们也可以使用 OVS 提供的 ovs-appctl ofproto/trace 工具来测试 OVS 对数据包的转发状况。ovs-appctl ofproto/trace 可以用来生成测试用的模拟数据包,并一步步的展示 OVS 对数据包的流处理过程。在以下的例子中,我们演示一下如何使用这个命令:
修改端口 p1 的 VLAN tag 为 101,使端口 p1 成为一个隶属于 VLAN 101 的端口
-
$ ovs-vsctl set Port p1 tag=101
-
现在由于端口 p0 和 p1 属于不同的 VLAN,它们之间无法进行数据交换。我们使用 ovs-appctl ofproto/trace 生成一个从端口 p0 发送到端口 p1 的数据包,这个数据包不包含任何 VLAN tag,并观察 OVS 的处理过程
在第一行输出中,"Flow:"之后的字段描述了输入的流的信息。由于我们没有指定太多信息,所以多数字段 (例如 dl_type 和 vlan_tci)被 OVS 设置为空值。
在第二行的输出中,"Rule:" 之后的字段描述了匹配成功的流表项。
在第三行的输出中,"OpenFlow actions"之后的字段描述了实际执行的操作。
最后一段以"Final flow"开始的字段是整个处理过程的总结,"Datapath actions: 4,1"代表数据包被发送到 datapath 的 4 和 1 号端口。
-
$ ovs-appctl ofproto/trace ovs-switch in_port= 100,dl_src=66:4e:cc:ae:4d:20,
-
dl_dst= 46:54:8a:95:dd:f8 -generate
-
Flow:metadata= 0,in_port=100,vlan_tci=0x0000,dl_src=66:4e:cc:ae:4d:20,
-
dl_dst= 46:54:8a:95:dd:f8,dl_type=0x0000
-
Rule: table= 0 cookie=0 priority=0
-
OpenFlow actions=NORMAL
-
no learned MAC for destination, flooding
-
-
Final flow: unchanged
-
Relevant fields: skb_priority= 0,in_port=100,vlan_tci=0x0000/0x1fff,\
-
dl_src= 66:4e:cc:ae:4d:20,dl_dst=46:54:8a:95:dd:f8,dl_type=0x0000,nw_frag=no
-
Datapath actions: 4,1
创建一条新的 Flow:对于从端口 p0 进入交换机的数据包,如果它不包含任何 VLAN tag,则自动为它添加 VLAN tag 101
-
$ ovs-ofctl add-flow ovs-switch "priority=3,in_port=100,dl_vlan=0xffff,\
-
actions=mod_vlan_vid:101,normal"
再次尝试从端口 p0 发送一个不包含任何 VLAN tag 的数据包,发现数据包进入端口 p0 之后, 会被加上 VLAN tag101, 同时转发到端口 p1 上
-
$ ovs-appctl ofproto/trace ovs-switch in_port= 100,dl_src=66:4e:cc:ae:4d:20,
-
dl_dst= 46:54:8a:95:dd:f8 –generate
-
Flow: metadata= 0,in_port=100,vlan_tci=0x0000,dl_src=66:4e:cc:ae:4d:20,
-
dl_dst= 46:54:8a:95:dd:f8,dl_type=0x0000
-
Rule: table= 0 cookie=0 priority=3,in_port=100,vlan_tci=0x0000
-
OpenFlow actions=mod_vlan_vid: 101,NORMAL
-
forwarding to learned port
-
-
Final flow: metadata= 0,in_port=100,dl_vlan=101,dl_vlan_pcp=0,dl_src=66:4e:cc:ae:4d:20,
-
dl_dst= 46:54:8a:95:dd:f8,dl_type=0x0000
-
Relevant fields: skb_priority= 0,in_port=100,vlan_tci=0x0000/0x1fff,dl_src=66:4e:cc:ae:4d:20,
-
dl_dst= 46:54:8a:95:dd:f8,dl_type=0x0000,nw_frag=no
-
Datapath actions: 3
反过来从端口 p1 发送数据包,由于 p1 现在是带有 VLAN tag 101 的 Access 类型的端口,所以数据包进入端口 p1 之后,会被 OVS 添加 VLAN tag 101 并发送到端口 p0
$ ovs-appctl ofproto/trace ovs-switch in_port=101,dl_dst=66:4e:cc:ae:4d:20,
-
dl_src= 46:54:8a:95:dd:f8 -generate
-
Flow: metadata= 0,in_port=101,vlan_tci=0x0000,dl_src=46:54:8a:95:dd:f8,
-
dl_dst= 66:4e:cc:ae:4d:20,dl_type=0x0000
-
Rule: table= 0 cookie=0 priority=0
-
OpenFlow actions=NORMAL
-
forwarding to learned port
-
-
Final flow: unchanged
-
Relevant fields: skb_priority= 0,in_port=101,vlan_tci=0x0000,dl_src=46:54:8a:95:dd:f8,
-
dl_dst= 66:4e:cc:ae:4d:20,dl_type=0x0000,nw_frag=no
-
Datapath actions: push_vlan(vid= 101,pcp=0),2
其他 OpenFlow 常用的操作
查看交换机中的所有 Table
ovs-ofctl dump-tables ovs-switch
查看交换机中的所有流表项
ovs-ofctl dump-flows ovs-switch
删除编号为 100 的端口上的所有流表项
ovs-ofctl del-flows ovs-switch "in_port=100"
查看交换机上的端口信息
ovs-ofctl show ovs-switch
OVS架构解析--dpdk datapath数据通路
https://blog.csdn.net/chen98765432101/article/details/79835435
1. 架构说明
1.1. 整体架构
OVS(openvswitch)是开源的虚拟交换机。也是当前市场上云环境中部署份额最大的交换机。支持 openflow协议,ovsdb协议管理。
一个OVS实例包括,ovsdb-server、ovs-vswitchd、datapath快转模块(linux内核中实现,可选的。dpdk模式是在用户态实现快转,并不需要内核态的datapath模块)。
- ovsdb-server:作用是对ovsdb操作。
- ovs-vswitchd:核心模块,作用是实现OpenFlow交换机、和controller通信、和db通信、实现用户态转发、和内核态快转路径通信。
- datapath:在内核空间实现报文快速转发。
![](http://img.e-com-net.com/image/info8/103bd3e47f2340c3b6cc6f38cd6720e6.jpg)
上图从整体架构说明了ovs的工作方式。ovs包括ovsdb配置管理方式和openflow流表转发控制方式。
- ovsdb配置管理方式:管理者通过OVSDB管理协议管理OVS交换机。OVSDB管理协议的规范文件是RFC 7047(The Open vSwitch Database Management Protocol)。RFC7047主要包括,定义OVSDB的结构、交互协议(JSON-RPC)、DB的操作类型。
- openflow流表控制方式:controller控制器通过OpenFlow协议给OVS交换机下发流表,控制交换机的转发行为。controller不通过OVSDB,而是直接向OVS交换机下发流表。
1.2. 核心组件及其关联关系
![](http://img.e-com-net.com/image/info8/26672b0cc942412ab1d99190672fbd00.jpg)
ovsdb-server和ovs-vswitchd之间是通过socket交互信息。
ovs-vswitchd通过netlink和内核态快转模块通信。
比如通过ovs-vsctl命令增加ovs交换机接口,ovs-vsctl会通过ovsdb-server向ovsdb更新数据,ovs-vswitchd监测到ovsdb变化时,会更新交换机配置,比如添加接口。
dpdk方式的ovs除了不使用内核模块外,架构和图中相同。
1.3. 内部模块及其关联关系
为了有个整体的了解,下面列几张整体的架构图,不做详细解释,后文会有流程解释。![](http://img.e-com-net.com/image/info8/2769b1f78b9c4213b5519e9285c5f46d.jpg)
![](http://img.e-com-net.com/image/info8/031280e145a946e1b2756868486e5bfa.jpg)
![](http://img.e-com-net.com/image/info8/552b54b720e44769a97773c79cb8b466.jpg)
2. vswitchd和ovsdb通信
IDL:接口描述语言。IDL是用来描述软件组件接口的一种计算机语言。IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流。也就是说,用一种通用的格式,比如下面数据就是描述一个Open_vSwitch表,最后生成C语言描述的表操作.c文件![](http://img.e-com-net.com/image/info8/fa461ea8c9dd48c9a77b26f3f44c269c.jpg)
2.1. 建立连接
ovsdb_idl_create建立一个ovsdb的连接session,形如:idl =ovsdb_idl_create(remote, &ovsrec_idl_class, true, true)。
remote类似:unix:/var/run/openvswitch/db.sock。
后续使用该idl(struct ovsdb_idl)和ovsdb通信,更新数据。
2.2. 接收数据
使用jsonrpc_session_recv函数接收数据,形如:msg = jsonrpc_session_recv(idl->session)。
接收消息的结构是:
struct jsonrpc_msg {
enum jsonrpc_msg_type type;
char *method; /* Request or notification only. */
struct json *params; /* Request or notification only. */
struct json *result; /* Successful reply only. */
struct json *error; /* Error reply only. */
struct json *id; /* Request or reply only. */
};
其中type是JSON-RPC 1.0定义的消息类型,包括request、notification、reply、error:
/* Messages. */
enum jsonrpc_msg_type {
JSONRPC_REQUEST, /* Request. */
JSONRPC_NOTIFY, /* Notification.*/
JSONRPC_REPLY, /* Successfulreply. */
JSONRPC_ERROR /* Error reply. */
};
params、result、error、id是不同类型的消息对应的消息数据。
当解析消息后,监测到需要更新本地数据时,调用ovsdb_idl_parse_update函数,解析消息并更新本地数据。2.3. json数据解析
/* A JSON value. */
struct json {
enum json_type type;
union {
struct shash *object; /* Contains "struct json *"s. */
struct json_array array;
long long int integer;
double real;
char *string;
} u;
};
type字段:说明了该json数据是联合体中的哪一种类型。
/* Type of a JSON value. */
enum json_type {
JSON_NULL, /* null */
JSON_FALSE, /* false */
JSON_TRUE, /* true */
JSON_OBJECT, /* {"a": b, "c": d, ...}*/
JSON_ARRAY, /* [1, 2, 3, ...] */
JSON_INTEGER, /* 123. */
JSON_REAL, /* 123.456. */
JSON_STRING, /* "..." */
JSON_N_TYPES
};
联合体u:表示数据可以解析的类型。
例如,当type是JSON_OBJECT类型时,且是update2更新操作,那么object字段保存着多个数据表,每张表数据中又携带着待更新的多个row数据,和每row对应的操作(modify、insert、delete、initial)。然后遍历每个表的每行数据,并执行对应的数据操作(modify、insert、delete、initial)对本地数据更新。
注:本地数据和远端的OVSDB并不相同,本地数据是vswitchd从数据库取出数据后临时放到本地结构中的。
2.4. 更新数据改变标记
当监听到ovsdb-server发来的rpc消息时,如果数据库发送改变就改变对应的change_seqno。后续vswitchd根据change_seqno的值是否发生了变化,决定是否重新配置ovs交换机。
3. dpdk加速处理
3.1. ovs dpdk加速配置
在使用dpdk 类型的datapath加速之前,需要设置dpdk-init=true启动参数。
设置方法:
ovs-vsctl --no-wait set Open_vSwitch .other_config:dpdk-init=true
设置dpdk的相关参数,都通过other_config选项完成。
主要的参数有:
- dpdk-init
指定ovs是否初始化和支持dpdk端口。
- dpdk-lcore-mask
指明dpdk使用的逻辑核。同dpdk的-c参数。
- dpdk-socket-mem
指明不同numa节点提前申请的大页内存。同dpdk的--socket-mem参数。
- dpdk-hugepage-dir
大页文件系统mount的目录。同dpdk的--huge-dir参数。
- vhost-sock-dir
设置vhost-user 套接字的路径。
- dpdk-extra
其他的dpdk配置参数。
3.2. dpdk初始化
主要包括:
1) dpdk eal初始化。
2) 启动dpdk接口状态监控线程(使用dpdk的库函数),如果状态发送变化,则更新netdev设备的变化标记。
3) 注册dpdk类型的netdev class。其中包括dpdk设备类型,ring类型、vhost、vhost client类型。
4. 数据结构抽象关系
4.1. 分层抽象
![](http://img.e-com-net.com/image/info8/0cfd4a830c9843fcaf4f1e063cf89e03.jpg)
- ofproto class
openflow交换机实现类,用来实现一个openflow交换机。主要包括创建、构造、操作openflowflow等相关方法。
- dpif class
datapath接口类,用来和datapath交互。主要包括datapath的open、run、端口操作、端口数据监听等相关方法。datapath是数据面的一种抽象。
- netdev class
网络设备抽象类,用来和网络设备交互。主要包括网卡的设置、网卡结构的抽象、数据包发送接收等相关方法。
- ofproto class
openflow交换机实现类,用来实现一个openflow交换机。主要包括创建、构造、操作openflowflow等相关方法。
- dpif class
datapath接口类,用来和datapath交互。主要包括datapath的open、run、端口操作、端口数据监听等相关方法。datapath是数据面的一种抽象。
- netdev class
网络设备抽象类,用来和网络设备交互。主要包括网卡的设置、网卡结构的抽象、数据包发送接收等相关方法。
bridge网桥通过ofproto class构造一个openflow交换机。再通过dpif class打开(如果没有对应类型的datapath,就创建一个)对应类型的datapath通路。使用netdev class构造一个抽象网络设备,上层抽象为端口,然后对网络设备收发包等操作。
需要说明的是,datapath是按类型分类的,现在ovs有两种类型的datapath:system(对应linux内核数据通路)、netdev(dpdk用户态数据通路)。所以使用同一种datapath类型的ovs网桥共享同一个datapath对象。如下图所示。
![](http://img.e-com-net.com/image/info8/792d13d4bdb94fe9905ef3e5a82a31a6.jpg)
4.2. 主要数据结构关系
该部分说明的内容是关于dpdk类型的datapath。
注:A B,表示A是一个变量而非指针,它的结构和内容是B。
流程解释如下:
1) bridge层
1.1 struct bridge对应每个ovs网桥最上层的结构。它通过成员指针变量ofproto,联系到struct ofproto交换机结构内容。
2) ofproto层
2.1 由于联系到的struct ofproto内容是struct ofproto_dpif结构的一个up成员变量。所以通过struct ofproto地址可以cast到struct ofproto_dpif结构内容,通过该内容和dpif层交互。
2.2 通过struct ofproto_dpif的成员指针变量dpif_backer,联系到struct dpif_backer结构内容。该结构是dpif操作的入口点。
3) dpif层
3.1 通过struct dpif_backer的成员指针变量dpif,联系到structdpif结构内容。
3.2 由于struct dpif结构内容是struct dpif_netdev结构的dpif成员变量,所以通过structdpif地址可以cast到struct dpif_netdev结构内容。
3.3 struct dpif_netdev结构内容中有指针成员变量dp,通过dp可以联系到struct dp_netdev结构内容。该内容是datapath的抽象结构内容。
4) datapath层
4.1 struct dp_netdev的成员指针变量ports是一个表结构,它联系着多个port端口内容。端口的结构是struct dp_netdev_port。struct dp_netdev另外一个成员指针变量netdev,联系着dp层的struct netdev结构内容,该结构内容是网卡的逻辑抽象。
4.2 struct dp_netdev_port的指针成员变量rxqs也是一个表结构,它联系着多个队列内容。队列的结构是struct dp_netdev_rxq.它的成员变量core_id表示该队列所在的core。
4.3 通过struct dp_netdev_rxq的成员指针变量rxq,联系到dp层的struct netdev_rxq结构内容。structdp_netdev_rxq的成员变量queue_id表示该队列id。
5) netdev层
5.1 由于struct netdev结构内容是struct netdev_dpdk结构的一个up成员变量。所以通过struct netdev地址可以cast到struct netdev_dpdk结构内容。
5.2 通过struct detdev_dpdk结构内容的成员指针变量netdev_class和dpdk类型网卡交互。
5.3 由于struct netdev_rxq结构内容是struct netdev_rxq_dpdk结构内容的一个up成员变量。所以通过struct netdev_rxq地址可以cast到struct netdev_rxq_dpdk结构内容。该内容是dpdk类型的netdev_rxq,它的成员变量port_id是dpdk网络设备的port。
5. vswitchd启动
简要的概括下vswitchd启动的内容。
1) 和ovsdb-server通信初始化,建立
2) dpdk初始化
3) ofproto(OpenFlow交换机)初始化
4) netdev初始化
5) 网桥配置
更新网桥(添加、删除)
更新端口
创建ofproto(OpenFlow交换机),创建或打开dpif_backer
添加端口,添加到pmd中
配置网桥、端口属性
datapath run
dp_netdev的pmd更新
dpif_run(运行dpif-netdev的datapath,配置netdev等,接收发送报文)
ofproto run,配置ofproto需要的参数和功能(netflow、sflow、ipfix、mac学习)
OpenFlow控制器连接相关run
6. 附录-数据结构查询
6.1. ofproto_class
是一个OpenFlow交换机的实现类结构,提供的类功能很多,主要分为:
1. 工厂函数功能。如初始化、枚举支持的datapath类型。
2. datapath类型处理。
3. OpenFlow交换机功能。如构造、运行等。
4. OpenFlow流表功能。
5. OpenFlow配置。
6. OpenFlow计量。
7. OpenFlow 1.1 group功能。
8. 获取datapath信息功能。
9. 连接表功能。
6.2. dpif_class
datapath接口实现类,每种datapath都有对应的接口实现实例。dpif有两种实现类:dpif_netlink_class、dpif_netdev_class。即system、netdev。比如,dpdk的netdevdatapath类型的实现实例dpif_netdev_class。
6.3. netdev_class
网络设备实现类。包括抽象的设备结构和收发包功能。
6.4. bridge
6.5. ofproto
6.6. port
6.7. iface
6.8. ofport
6.9. ofport_dpif
6.10. ofproto_port
6.11. dpif_port
dp向dpif返回的端口信息。
6.12. ofproto_dpif
6.13. dpif_backer
datapath类型共享实现实例。
6.14. udpif
6.15. dpif
datapath接口。
6.16. dpif_netdev
netdev类型的datapath的接口。
6.17. dp_netdev
基于网络设备接口的datapath。
6.18. dp_netdev_port
基于netdev类型的datapath的端口。
挂载在dp_netdev的ports成员中。
6.19. netdev
抽象的网络设备。
6.20. netdev_dpdk
dpdk类型的netdev结构,继承自netdev结构。
6.21. dp_netdev_rxq
用来表示core和队列的对应关系。
6.22. netdev_rxq
网络设备的收包队列结构,以队列区分。
6.23. netdev_rxq_dpdk
dpdk的收包队列结构,继承自netdev_rxq.
端口号和netdev_dpdk中的端口号相同。
使用dpdk的接口函数rte_zmalloc申请,受dpdk的内存管理控制。
6.24. dp_netdev_pmd_thread
6.25. rxq_poll
6.26. tx_port
OVS常用命令与使用总结
说明
在平时使用ovs中,经常用到的ovs命令,参数,与举例总结,持续更新中…
进程启动
1.先准备ovs的工作目录,数据库存储路径等
mkdir -p /etc/openvswitch
mkdir -p /var/run/openvswitch
2.先启动ovsdb-server
ovsdb-server /etc/openvswitch/conf.db \
-vconsole:emer -vsyslog:err -vfile:info \ --remote=punix:/var/run/openvswitch/db.sock \ --private-key=db:Open_vSwitch,SSL,private_key \ --certificate=db:Open_vSwitch,SSL,certificate \ --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --no-chdir \ --log-file=/var/log/openvswitch/ovsdb-server.log \ --pidfile=/var/run/openvswitch/ovsdb-server.pid \ --detach --monitor
ps: 如果想清除配置,可以先删除/etc/openvswitch/*,然后再手动创建一个数据库:
ovsdb-tool create /etc/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
3.初始化ovsdb
针对于新创建的数据库才需要初始化
ovs-vsctl --no-wait init
4.启动vswitchd进程
ovs-vswitchd unix:/var/run/openvswitch/db.sock \ -vconsole:emer -vsyslog:err -vfile:info --mlockall --no-chdir \ --log-file=/var/log/openvswitch/ovs-vswitchd.log \ --pidfile=/var/run/openvswitch/ovs-vswitchd.pid \ --detach --monitor
ovs常用命令
控制管理类
1.查看网桥和端口
ovs-vsctl show
- 1
2.创建一个网桥
ovs-vsctl add-br br0
ovs-vsctl set bridge br0 datapath_type=netdev
3.添加/删除一个端口
# for system interfaces
ovs-vsctl add-port br0 eth1 ovs-vsctl del-port br0 eth1 # for DPDK ovs-vsctl add-port br0 dpdk1 -- set interface dpdk1 type=dpdk options:dpdk-devargs=0000:01:00.0 # for DPDK bonds ovs-vsctl add-bond br0 dpdkbond0 dpdk1 dpdk2 \ -- set interface dpdk1 type=dpdk options:dpdk-devargs=0000:01:00.0 \ -- set interface dpdk2 type=dpdk options:dpdk-devargs=0000:02:00.0
4.设置/清除网桥的openflow协议版本
ovs-vsctl set bridge br0 protocols=OpenFlow13
ovs-vsctl clear bridge br0 protocols
5.查看某网桥当前流表
ovs-ofctl dump-flows br0
ovs-ofctl -O OpenFlow13 dump-flows br0 ovs-appctl bridge/dump-flows br0
6.设置/删除控制器
ovs-vsctl set-controller br0 tcp:1.2.3.4:6633 ovs-vsctl del-controller br0
7.查看控制器列表
ovs-vsctl list controller
8.设置/删除被动连接控制器
ovs-vsctl set-manager tcp:1.2.3.4:6640 ovs-vsctl get-manager ovs-vsctl del-manager
9.设置/移除可选选项
ovs-vsctl set Interface eth0 options:link_speed=1G ovs-vsctl remove Interface eth0 options link_speed
10.设置fail模式,支持standalone或者secure
standalone(default):清除所有控制器下发的流表,ovs自己接管
secure:按照原来流表继续转发
ovs-vsctl del-fail-mode br0 ovs-vsctl set-fail-mode br0 secure ovs-vsctl get-fail-mode br0
11.查看接口id等
ovs-appctl dpif/show
12.查看接口统计
ovs-ofctl dump-ports br0
流表类
流表操作
1.添加普通流表
ovs-ofctl add-flow br0 in_port=1,actions=output:2
2.删除所有流表
ovs-ofctl del-flows br0
3.按匹配项来删除流表
ovs-ofctl del-flows br0 "in_port=1"
匹配项
1.匹配vlan tag,范围为0-4095
ovs-ofctl add-flow br0 priority=401,in_port=1,dl_vlan=777,actions=output:2
2.匹配vlan pcp,范围为0-7
ovs-ofctl add-flow br0 priority=401,in_port=1,dl_vlan_pcp=7,actions=output:2
3.匹配源/目的MAC
ovs-ofctl add-flow br0 in_port=1,dl_src=00:00:00:00:00:01/00:00:00:00:00:01,actions=output:2 ovs-ofctl add-flow br0 in_port=1,dl_dst=00:00:00:00:00:01/00:00:00:00:00:01,actions=output:2
4.匹配以太网类型,范围为0-65535
ovs-ofctl add-flow br0 in_port=1,dl_type=0x0806,actions=output:2
5.匹配源/目的IP
条件:指定dl_type=0x0800,或者ip/tcp
ovs-ofctl add-flow br0 ip,in_port=1,nw_src=10.10.0.0/16,actions=output:2 ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.20.0.0/16,actions=output:2
6.匹配协议号,范围为0-255
条件:指定dl_type=0x0800或者ip
# ICMP
ovs-ofctl add-flow br0 ip,in_port=1,nw_proto=1,actions=output:2
7.匹配IP ToS/DSCP,tos范围为0-255,DSCP范围为0-63
条件:指定dl_type=0x0800/0x86dd,并且ToS低2位会被忽略(DSCP值为ToS的高6位,并且低2位为预留位)
ovs-ofctl add-flow br0 ip,in_port=1,nw_tos=68,actions=output:2 ovs-ofctl add-flow br0 ip,in_port=1,ip_dscp=62,actions=output:2
8.匹配IP ecn位,范围为0-3
条件:指定dl_type=0x0800/0x86dd
ovs-ofctl add-flow br0 ip,in_port=1,ip_ecn=2,actions=output:2
9.匹配IP TTL,范围为0-255
ovs-ofctl add-flow br0 ip,in_port=1,nw_ttl=128,actions=output:2
10.匹配tcp/udp,源/目的端口,范围为0-65535
# 匹配源tcp端口179
ovs-ofctl add-flow br0 tcp,tcp_src=179/0xfff0,actions=output:2 # 匹配目的tcp端口179 ovs-ofctl add-flow br0 tcp,tcp_dst=179/0xfff0,actions=output:2 # 匹配源udp端口1234 ovs-ofctl add-flow br0 udp,udp_src=1234/0xfff0,actions=output:2 # 匹配目的udp端口1234 ovs-ofctl add-flow br0 udp,udp_dst=1234/0xfff0,actions=output:2
11.匹配tcp flags
tcp flags=fin,syn,rst,psh,ack,urg,ece,cwr,ns
ovs-ofctl add-flow br0 tcp,tcp_flags=ack,actions=output:2
12.匹配icmp code,范围为0-255
条件:指定icmp
ovs-ofctl add-flow br0 icmp,icmp_code=2,actions=output:2
13.匹配vlan TCI
TCI低12位为vlan id,高3位为priority,例如tci=0xf123则vlan_id为0x123和vlan_pcp=7
ovs-ofctl add-flow br0 in_port=1,vlan_tci=0xf123,actions=output:2
14.匹配mpls label
条件:指定dl_type=0x8847/0x8848
ovs-ofctl add-flow br0 mpls,in_port=1,mpls_label=7,actions=output:2
15.匹配mpls tc,范围为0-7
条件:指定dl_type=0x8847/0x8848
ovs-ofctl add-flow br0 mpls,in_port=1,mpls_tc=7,actions=output:2
16.匹配tunnel id,源/目的IP
# 匹配tunnel id
ovs-ofctl add-flow br0 in_port=1,tun_id=0x7/0xf,actions=output:2 # 匹配tunnel源IP ovs-ofctl add-flow br0 in_port=1,tun_src=192.168.1.0/255.255.255.0,actions=output:2 # 匹配tunnel目的IP ovs-ofctl add-flow br0 in_port=1,tun_dst=192.168.1.0/255.255.255.0,actions=output:2
一些匹配项的速记符
速记符 | 匹配项 |
---|---|
ip | dl_type=0x800 |
ipv6 | dl_type=0x86dd |
icmp | dl_type=0x0800,nw_proto=1 |
icmp6 | dl_type=0x86dd,nw_proto=58 |
tcp | dl_type=0x0800,nw_proto=6 |
tcp6 | dl_type=0x86dd,nw_proto=6 |
udp | dl_type=0x0800,nw_proto=17 |
udp6 | dl_type=0x86dd,nw_proto=17 |
sctp | dl_type=0x0800,nw_proto=132 |
sctp6 | dl_type=0x86dd,nw_proto=132 |
arp | dl_type=0x0806 |
rarp | dl_type=0x8035 |
mpls | dl_type=0x8847 |
mplsm | dl_type=0x8848 |
指令动作
1.动作为出接口
从指定接口转发出去
ovs-ofctl add-flow br0 in_port=1,actions=output:2
2.动作为指定group
group id为已创建的group table
ovs-ofctl add-flow br0 in_port=1,actions=group:666
3.动作为normal
转为L2/L3处理流程
ovs-ofctl add-flow br0 in_port=1,actions=normal
4.动作为flood
从所有物理接口转发出去,除了入接口和已关闭flooding的接口
ovs-ofctl add-flow br0 in_port=1,actions=flood
5.动作为all
从所有物理接口转发出去,除了入接口
ovs-ofctl add-flow br0 in_port=1,actions=all
6.动作为local
一般是转发给本地网桥
ovs-ofctl add-flow br0 in_port=1,actions=local
7.动作为in_port
从入接口转发回去
ovs-ofctl add-flow br0 in_port=1,actions=in_port
8.动作为controller
以packet-in消息上送给控制器
ovs-ofctl add-flow br0 in_port=1,actions=controller
9.动作为drop
丢弃数据包操作
ovs-ofctl add-flow br0 in_port=1,actions=drop
10.动作为mod_vlan_vid
修改报文的vlan id,该选项会使vlan_pcp置为0
ovs-ofctl add-flow br0 in_port=1,actions=mod_vlan_vid:8,output:2
11.动作为mod_vlan_pcp
修改报文的vlan优先级,该选项会使vlan_id置为0
ovs-ofctl add-flow br0 in_port=1,actions=mod_vlan_pcp:7,output:2
12.动作为strip_vlan
剥掉报文内外层vlan tag
ovs-ofctl add-flow br0 in_port=1,actions=strip_vlan,output:2
13.动作为push_vlan
在报文外层压入一层vlan tag,需要使用openflow1.1以上版本兼容
ovs-ofctl add-flow -O OpenFlow13 br0 in_port=1,actions=push_vlan:0x8100,set_field:4097-\>vlan_vid,output:2
ps: set field值为4096+vlan_id,并且vlan优先级为0,即4096-8191,对应的vlan_id为0-4095
14.动作为push_mpls
修改报文的ethertype,并且压入一个MPLS LSE
ovs-ofctl add-flow br0 in_port=1,actions=push_mpls:0x8847,set_field:10-\>mpls_label,output:2
15.动作为pop_mpls
剥掉最外层mpls标签,并且修改ethertype为非mpls类型
ovs-ofctl add-flow br0 mpls,in_port=1,mpls_label=20,actions=pop_mpls:0x0800,output:2
16.动作为修改源/目的MAC,修改源/目的IP
# 修改源MAC
ovs-ofctl add-flow br0 in_port=1,actions=mod_dl_src:00:00:00:00:00:01,output:2 # 修改目的MAC ovs-ofctl add-flow br0 in_port=1,actions=mod_dl_dst:00:00:00:00:00:01,output:2 # 修改源IP ovs-ofctl add-flow br0 in_port=1,actions=mod_nw_src:192.168.1.1,output:2 # 修改目的IP ovs-ofctl add-flow br0 in_port=1,actions=mod_nw_dst:192.168.1.1,output:2
17.动作为修改TCP/UDP/SCTP源目的端口
# 修改TCP源端口
ovs-ofctl add-flow br0 tcp,in_port=1,actions=mod_tp_src:67,output:2 # 修改TCP目的端口 ovs-ofctl add-flow br0 tcp,in_port=1,actions=mod_tp_dst:68,output:2 # 修改UDP源端口 ovs-ofctl add-flow br0 udp,in_port=1,actions=mod_tp_src:67,output:2 # 修改UDP目的端口 ovs-ofctl add-flow br0 udp,in_port=1,actions=mod_tp_dst:68,output:2
18.动作为mod_nw_tos
条件:指定dl_type=0x0800
修改ToS字段的高6位,范围为0-255,值必须为4的倍数,并且不会去修改ToS低2位ecn值
ovs-ofctl add-flow br0 ip,in_port=1,actions=mod_nw_tos:68,output:2
19.动作为mod_nw_ecn
条件:指定dl_type=0x0800,需要使用openflow1.1以上版本兼容
修改ToS字段的低2位,范围为0-3,并且不会去修改ToS高6位的DSCP值
ovs-ofctl add-flow br0 ip,in_port=1,actions=mod_nw_ecn:2,output:2
20.动作为mod_nw_ttl
修改IP报文ttl值,需要使用openflow1.1以上版本兼容
ovs-ofctl add-flow -O OpenFlow13 br0 in_port=1,actions=mod_nw_ttl:6,output:2
21.动作为dec_ttl
对IP报文进行ttl自减操作
ovs-ofctl add-flow br0 in_port=1,actions=dec_ttl,output:2
22.动作为set_mpls_label
对报文最外层mpls标签进行修改,范围为20bit值
ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_label:666,output:2
23.动作为set_mpls_tc
对报文最外层mpls tc进行修改,范围为0-7
ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_tc:7,output:2
24.动作为set_mpls_ttl
对报文最外层mpls ttl进行修改,范围为0-255
ovs-ofctl add-flow br0 in_port=1,actions=set_mpls_ttl:255,output:2
25.动作为dec_mpls_ttl
对报文最外层mpls ttl进行自减操作
ovs-ofctl add-flow br0 in_port=1,actions=dec_mpls_ttl,output:2
26.动作为move NXM字段
使用move参数对NXM字段进行操作
# 将报文源MAC复制到目的MAC字段,并且将源MAC改为00:00:00:00:00:01
ovs-ofctl add-flow br0 in_port=1,actions=move:NXM_OF_ETH_SRC[]-\>NXM_OF_ETH_DST[],mod_dl_src:00:00:00:00:00:01,output:2
ps: 常用NXM字段参照表
NXM字段 | 报文字段 |
---|---|
NXM_OF_ETH_SRC | 源MAC |
NXM_OF_ETH_DST | 目的MAC |
NXM_OF_ETH_TYPE | 以太网类型 |
NXM_OF_VLAN_TCI | vid |
NXM_OF_IP_PROTO | IP协议号 |
NXM_OF_IP_TOS | IP ToS值 |
NXM_NX_IP_ECN | IP ToS ECN |
NXM_OF_IP_SRC | 源IP |
NXM_OF_IP_DST | 目的IP |
NXM_OF_TCP_SRC | TCP源端口 |
NXM_OF_TCP_DST | TCP目的端口 |
NXM_OF_UDP_SRC | UDP源端口 |
NXM_OF_UDP_DST | UDP目的端口 |
NXM_OF_SCTP_SRC | SCTP源端口 |
NXM_OF_SCTP_DST | SCTP目的端口 |
27.动作为load NXM字段
使用load参数对NXM字段进行赋值操作
# push mpls label,并且把10(0xa)赋值给mpls label
ovs-ofctl add-flow br0 in_port=1,actions=push_mpls:0x8847,load:0xa-\>OXM_OF_MPLS_LABEL[],output:2 # 对目的MAC进行赋值 ovs-ofctl add-flow br0 in_port=1,actions=load:0x001122334455-\>OXM_OF_ETH_DST[],output:2
28.动作为pop_vlan
弹出报文最外层vlan tag
ovs-ofctl add-flow br0 in_port=1,dl_type=0x8100,dl_vlan=777,actions=pop_vlan,output:2
meter表
常用操作
由于meter表是openflow1.3版本以后才支持,所以所有命令需要指定OpenFlow1.3版本以上
ps: 在openvswitch-v2.8之前的版本中,还不支持meter
在v2.8版本之后已经实现,要正常使用的话,需要注意的是datapath类型要指定为netdev,band type暂时只支持drop,还不支持DSCP REMARK
1.查看当前设备对meter的支持
ovs-ofctl -O OpenFlow13 meter-features br0
2.查看meter表
ovs-ofctl -O OpenFlow13 dump-meters br0
3.查看meter统计
ovs-ofctl -O OpenFlow13 meter-stats br0
4.创建meter表
# 限速类型以kbps(kilobits per second)计算,超过20kb/s则丢弃
ovs-ofctl -O OpenFlow13 add-meter br0 meter=1,kbps,band=type=drop,rate=20 # 同上,增加burst size参数 ovs-ofctl -O OpenFlow13 add-meter br0 meter=2,kbps,band=type=drop,rate=20,burst_size=256 # 同上,增加stats参数,对meter进行计数统计 ovs-ofctl -O OpenFlow13 add-meter br0 meter=3,kbps,stats,band=type=drop,rate=20,burst_size=256 # 限速类型以pktps(packets per second)计算,超过1000pkt/s则丢弃 ovs-ofctl -O OpenFlow13 add-meter br0 meter=4,pktps,band=type=drop,rate=1000
5.删除meter表
# 删除全部meter表
ovs-ofctl -O OpenFlow13 del-meters br0
# 删除meter id=1 ovs-ofctl -O OpenFlow13 del-meter br0 meter=1
6.创建流表
ovs-ofctl -O OpenFlow13 add-flow br0 in_port=1,actions=meter:1,output:2
group表
由于group表是openflow1.1版本以后才支持,所以所有命令需要指定OpenFlow1.1版本以上
常用操作
group table支持4种类型
- all:所有buckets都执行一遍
- select: 每次选择其中一个bucket执行,常用于负载均衡应用
- ff(FAST FAILOVER):快速故障修复,用于检测解决接口等故障
- indirect:间接执行,类似于一个函数方法,被另一个group来调用
1.查看当前设备对group的支持
ovs-ofctl -O OpenFlow13 dump-group-features br0
2.查看group表
ovs-ofctl -O OpenFlow13 dump-groups br0
3.创建group表
# 类型为all
ovs-ofctl -O OpenFlow13 add-group br0 group_id=1,type=all,bucket=output:1,bucket=output:2,bucket=output:3
# 类型为select
ovs-ofctl -O OpenFlow13 add-group br0 group_id=2,type=select,bucket=output:1,bucket=output:2,bucket=output:3 # 类型为select,指定hash方法(5元组,OpenFlow1.5+) ovs-ofctl -O OpenFlow15 add-group br0 group_id=3,type=select,selection_method=hash,fields=ip_src,bucket=output:2,bucket=output:3
4.删除group表
ovs-ofctl -O OpenFlow13 del-groups br0 group_id=2
5.创建流表
ovs-ofctl -O OpenFlow13 add-flow br0 in_port=1,actions=group:2
goto table配置
数据流先从table0开始匹配,如actions有goto_table,再进行后续table的匹配,实现多级流水线,如需使用goto table,则创建流表时,指定table id,范围为0-255,不指定则默认为table0
1.在table0中添加一条流表条目
ovs-ofctl add-flow br0 table=0,in_port=1,actions=goto_table=1
2.在table1中添加一条流表条目
ovs-ofctl add-flow br0 table=1,ip,nw_dst=10.10.0.0/16,actions=output:2
tunnel配置
如需配置tunnel,必需确保当前系统对各tunnel的remote ip网络可达
gre
1.创建一个gre接口,并且指定端口id=1001
ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=1.1.1.1 ofport_request=1001
2.可选选项
将tos或者ttl在隧道上继承,并将tunnel id设置成123
ovs-vsctl set Interface gre1 options:tos=inherit options:ttl=inherit options:key=123
3.创建关于gre流表
# 封装gre转发
ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.10.0.0/16,actions=output:1001 # 解封gre转发 ovs-ofctl add-flow br0 in_port=1001,actions=output:1
vxlan
1.创建一个vxlan接口,并且指定端口id=2001
ovs-vsctl add-port br0 vxlan1 -- set Interface vxlan1 type=vxlan options:remote_ip=1.1.1.1 ofport_request=2001
2.可选选项
将tos或者ttl在隧道上继承,将vni设置成123,UDP目的端为设置成8472(默认为4789)
ovs-vsctl set Interface vxlan1 options:tos=inherit options:ttl=inherit options:key=123 options:dst_port=8472
3.创建关于vxlan流表
# 封装vxlan转发
ovs-ofctl add-flow br0 ip,in_port=1,nw_dst=10.10.0.0/16,actions=output:2001 # 解封vxlan转发 ovs-ofctl add-flow br0 in_port=2001,actions=output:1
sflow配置
1.对网桥br0进行sflow监控
- agent: 与collector通信所在的网口名,通常为管理口
- target: collector监听的IP地址和端口,端口默认为6343
- header: sFlow在采样时截取报文头的长度
- polling: 采样时间间隔,单位为秒
ovs-vsctl -- --id=@sflow create sflow agent=eth0 target=\"10.0.0.1:6343\" header=128 sampling=64 polling=10 -- set bridge br0 sflow=@sflow
2.查看创建的sflow
ovs-vsctl list sflow
3.删除对应的网桥sflow配置,参数为sFlow UUID
ovs-vsctl remove bridge br0 sflow 7b9b962e-fe09-407c-b224-5d37d9c1f2b3
4.删除网桥下所有sflow配置
ovs-vsctl -- clear bridge br0 sflow
QoS配置
ingress policing
1.配置ingress policing,对接口eth0入流限速10Mbps
ovs-vsctl set interface eth0 ingress_policing_rate=10000 ovs-vsctl set interface eth0 ingress_policing_burst=8000
2.清除相应接口的ingress policer配置
ovs-vsctl set interface eth0 ingress_policing_rate=0 ovs-vsctl set interface eth0 ingress_policing_burst=0
3.查看接口ingress policer配置
ovs-vsctl list interface eth0
4.查看网桥支持的Qos类型
ovs-appctl qos/show-types br0
端口镜像配置
1.配置eth0收到/发送的数据包镜像到eth1
ovs-vsctl -- set bridge br0 mirrors=@m \
-- --id=@eth0 get port eth0 \ -- --id=@eth1 get port eth1 \ -- --id=@m create mirror name=mymirror select-dst-port=@eth0 select-src-port=@eth0 output-port=@eth1
2.删除端口镜像配置
ovs-vsctl -- --id=@m get mirror mymirror -- remove bridge br0 mirrors @m
3.清除网桥下所有端口镜像配置
ovs-vsctl clear bridge br0 mirrors
4.查看端口镜像配置
ovs-vsctl get bridge br0 mirrors
https://www.sdnlab.com/20966.html
2.设计考虑
2.1.整体数据结构
ovs datapath classifier涉及的数据结构主要有如下。
网桥数据结构
1
2
3
4
5
6
7
8
9
10
|
struct datapath {
struct rcu_head rcu;
/* 该结构使用双链表组织 */
struct list_head list_node;
/* 从属该datapath的流表项 */
struct flow_table table;
/* 从属该datapath的vport信息 */
struct hlist_head *ports;
…
}
|
流表数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
struct flow_table {
/* 用key组织的流表实例 */
struct table_instance *ti;
/* 用unique flow identifier(ufid)组织的流表实例 */
struct table_instance *ufid_ti;
/* 掩码信息缓存表 */
struct mask_cache_entry *mask_cache;
/* 掩码信息列表 */
struct mask_array *mask_array;
/* 记录当前节拍数 */
unsigned long last_rehash;
/* ti流表实例中存储的流表项数目 */
unsigned int count;
/* ufid_ti流表实例中存储的流表项数目 */
unsigned int ufid_count;
}
|
流表实例数据结构
1
2
3
4
5
6
7
8
9
10
|
struct table_instance {
/* 哈希桶,用于组织各个流表项的具体信息. */
struct flex_array *buckets;
/* 哈希桶大小 */
unsigned int n_buckets;
struct rcu_head rcu;
int node_ver;
u32 hash_seed;
bool keep_flows;
}
|
掩码信息列表数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
struct mask_array {
struct rcu_head rcu;
/* 当前已存储的掩码信息数目 */
int count;
/* 一共可以存储掩码信息的总数目 */
int max;
/* 具体的掩码信息 */
struct sw_flow_mask *masks[];
}
struct sw_flow_mask {
/* 引用计数 */
int ref_count;
struct rcu_head rcu;
/* 匹配关键字的有效范围,下文会具体解释 */
struct sw_flow_key_range range;
/* 匹配关键字信息 */
struct sw_flow_key key;
}
|
掩码信息缓存表数据结构
1
2
3
4
5
6
|
struct mask_cache_entry {
/* hash值 */
u32 skb_hash;
/* 对应哪一个掩码信息,索引值 */
u32 mask_index;
}
|
哈希桶数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
struct flex_array {
union {
struct {
/* 实际等于sizeof(struct hlist_head) */
int element_size;
/* 一共有多少个struct hlist_head指针. */
int total_nr_elements;
/* 每个part(页)可以存储多少个struct hlist_head指针 */
int elems_per_part;
u32 reciprocal_value reciprocal_elems;
/* 具体存储struct hlist_head指针的缓冲区 */
struct flex_array_part *parts[];
};
/* 填充字段.代表了整个结构的大小=页大小(4096字节) */
char padding[FLEX_ARRAY_BASE_SIZE];
}
}
struct flex_array_part {
/* 缓冲区.一个页大小,即:4096字节 */
char elements[FLEX_ARRAY_PART_SIZE];
};
|
上述这些数据结构是在源码中的ovs_flow_tbl_init函数里面进行初始化操作的。初始化后以及上述这些数据结构之间的关系如下图所示。
2.2.关键信息范围
从上面2.1节可知,掩码信息(struct sw_flow_mask结构)中记录了一个范围(struct sw_flow_key_range结构),该范围用于标识需要匹配的关键信息最小偏移和最大偏移。为什么需要这样做?个人感觉和ovs-dpdk datapath classifier中描述的miniflow类似,即:匹配过程中并非使用报文的所有字段去匹配,而是使用部分字段,例如使用报文的五元组信息(源IP、目的IP、协议号、源端口、目的端口)。那么使用sw_flow_key_range结构来标识这五元组信息中最小偏移和最大偏移。实际源码中,关键信息是使用struct sw_flow_key结构来描述的。由于该结构字段较多,这里不详细给出。以报文五元组信息为例,这里给出的五元组信息所在sw_flow_key结构的位置和实际源码对应的位置是不相同的,这里只是给出计算最小偏移和最大偏移的概念,如下图所示,关键信息的有效范围为:
最小偏移=M,最大偏移=N
2.3.更新过程
在源码中,对应更新过程的入口函数是:ovs_flow_cmd_new。这个入口函数是处于内核模块中,在接收到报文时,通过下面2.4节所述的查找过程,查找失败时,会将报文的相关信息upcall到用户空间,在用户空间通过查找”慢路径”将对应的actions和mask信息下发到内核空间,在内核空间,处理的入口函数正是ovs_flow_cmd_new。下面将对这个函数的处理过程作详细描述。描述之前,先掌握一些相关的数据结构,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
struct sw_flow {
struct rcu_head rcu;
/* 哈希桶链表 */
struct {
struct hlist_node node[2];
u32 hash;
} flow_table, ufid_table;
int stats_last_writer;
/* 关键信息.经过掩码运算后的 */
struct sw_flow_key key;
/* 流id信息 */
struct sw_flow_id id;
/* 指向的掩码信息 */
struct sw_flow_mask *mask;
/* 指向的流动作信息 */
struct sw_flow_actions *sf_acts;
struct flow_stats *stats[];
}
struct sw_flow_id {
u32 ufid_len;
union {
/* ufid为128比特,共16字节 */
u32 ufid[4];
struct sw_flow_key *unmasked_key;
}
}
struct sw_flow_actions {
struct rcu_head rcu;
size_t orig_len;
u32 actions_len;
struct nlattr actions[];
}
struct sw_flow_match {
struct sw_flow_key *key;
struct sw_flow_key_range range;
struct sw_flow_mask *mask;
}
|
具体流程如下所示:
1)初始化struct sw_flow *new_flow。分配空间给new_flow,并设置部分的成员值,如下:
new_flow->sf_acts = null;
new_flow->mask = null;
new_flow->id.ufid_len=0;
new_flow->id.unmasked_key=null;
2)构建struct sw_flow_match match信息。根据接收到的关键信息和掩码信息,存储在match.key和match.mask中。
3)计算new_flow->key = match.key & match.mask。
4)将ufid信息提取出来存储在new_flow->id.ufid_len和new_flow->id.ufid中。如果未提供任何的UFID信息(此时的new_flow->id.ufid_len为0),则将match.key信息拷贝一份到new_flow->id.unmasked_key中存储。
5)将actions信息存储在变量struct sw_flow_actions acts中。
6)根据接收到的dp_ifindex获取struct datapath dp信息。
7)如果new_flow->id.ufid_len不为0,则使用new_flow->id.ufid信息去dp->table.ufid_ti指向的哈希桶中找到对应的struct sw_flow信息。假设找不到。
8)而如果new_flow->id.ufid_len为0,则需要在dp->table.ti,同时配合在2)中构建出来的match.key和dp->table.mask_array配合找出对应的struct sw_flow信息。这个详细过程可以参考下面的2.4节。这里也假设找不到。
9)经过上面7)和8)步都没找到对应的struct sw_flow信息。首先设置new_flow->sf_acts=acts(在5)步中获取)。然后使用match.mask去dp->table.mask_array中查找是否已存在该match.mask信息。如果不存在则接着分配一个struct sw_flow_mask *mask。设置mask->key=match.mask->key, mask->range=match.mask->range, mask->ref_count=1。并且在dp->table.mask_array.masks[i]中存储该mask的地址。添加完毕之后,如下图所示。变化的地方用绿色标注出来了。同时new_flow->mask=mask。
10)根据new_flow->key和new_flow->mask.range计算出new_flow->flow_table.hash值。
11)在dp->table.ti->buckets指向的哈希桶中插入new_flow。如下图所示(假设插入的位置为part[0]对应的第0个位置)。dp->table.count。
12)如果new_flow->id.ufid_len不为0,则使用new_flow->id.ufid信息计算出new_flow->ufid_table.hash。然后根据这个hash值在dp->table.ufid_ti->buckets中找到合适的哈希桶存储对应的new_flow信息。这里假设找到的位置为parts[0]的第0个位置。如下图所示。插入new_flow信息之后,dp->table.ufid_count。
到此为止,更新过程完毕。
2.4.查找过程
在源码中,对应查找过程的入口函数是:ovs_vport_receive。具体过程如下:
1)从接收到的报文信息中提取出关键信息。存储到struct sw_flow_key key。
2)从接收到的报文信息中获取struct vport信息,再从vport信息获取struct datapath信息,假设为dp。
3)如果接收到的报文中未带有skb_hash值信息,则执行:
遍历每一个掩码,即:dp->table.mask_array->masks[i]。这里简单记为mask。将1)提取出来的关键新key与这个掩码mask做逻辑与运算,得出掩码过的信息,记为masked_key。通过masked_key和mask.range信息计算出hash值在dp->table.ti->buckets中找到对应的哈希桶,并遍历该桶上的所有struct sw_flow信息,记为flow。执行比较:如果flow->mask指向的是mask,flow->flow_table.hash值和计算出的hash值相等,且flow->key和masked_key信息在mask.range指定的范围内完全相同,则认为成功匹配,返回flow信息。否则匹配失败,返回NULL。
4)如果接收到的报文中带有skb_hash值信息,则执行:
4)-1:根据旧的skb_hash值和key->recirc_id值重新计算出新的skb_hash值。
4)-2:将skb_hash值分成4段,每段1字节,记为hash。遍历每一段的hash值,执行:获取dp->table.mask_cache[hash]表项,记为entry,如果entry.skb_hash值和skb_hash值相等,则使用entry.mask_index指向的掩码信息去dp->table.ti->buckets中找到对应的struct sw_flow信息(过程和上述第3)步相同)。如果找到对应的flow,则返回flow,过程中会更新entry.mask_index值来实际指向具体的掩码信息索引。否则,匹配失败,将entry.skb_hash值置0,返回NULL。而如果每一个entry.skb_hash值和skb_hash值不相等,则遍历完每一段hash值之后,从中选择最佳候选entry,最佳候选的条件是:其skb_hash值最小者,假设记为ce。最后使用ce.mask_index指向的掩码信息去dp->table.ti->buckets中找到对应的struct sw_flow信息(过程和上述第3)步相同)。如果找到对应的flow,则返回flow,同时将ce->skb_hash值更新为skb_hash值。当然在查找的过程中(上述第3)步),也会更新ce->mask_index来指向实际的掩码信息索引。
5) 如果在上述第3)和第4)步中都匹配失败了,则需要将报文的信息upcall到用户空间,由用户空间负责查找对应的流动作信息,返回给内核。这个过程具体请参考上述2.3节。
到此为止,查找过程结束。
2.5.执行过程
在2.4节查找过程中,如果查找成功,则需要执行对应的流动作。入口函数是:ovs_execute_actions。
而如果查找失败,upcall到用户空间,找到对应的流动作之后,调用执行,到内核空间,入口函数对应的是:ovs_packet_cmd_execute。该函数最终会调用到ovs_execute_actions。
2.6.掩码信息比较
在上述2.3和2.4节所述的更新过程和查找过程中都会遇见用新构造的掩码信息与datapath结构的table.mask_array作比较,以检测掩码信息是否需要新增。而比较的方法如下:
假设新构造的掩码信息为mask,与table.mask_array->masksi比较:
1)mask.range.start和exist_mask.range.start相等。
2)mask.range.end和exist_mask.range.end相等。
3)mask和exist_mask在range范围内完全相同。
同时符合上面三个条件才认为两个掩码完全相同。
2.7.流信息比较
在上述2.4节所述的查找过程中,对接收到的报文进行匹配表项时,需要对流信息作比较。假设接收到的报文提取出来的关键信息为key,匹配的掩码信息为mask,通过mask & key计算出掩码后的报文关键信息,记为masked_key,通过masked_key和mask.range计算出hash值。根据这个hash值找到对应的哈希桶,遍历这个哈希桶中存储的每一个流信息,记为flow。现在需要比较flow信息进而找到匹配的流表项。比较如下:
1)flow->mask指向的是mask。
2)flow->flow_table.hash和计算出的hash值相等。
3)flow->key和masked_key在mask.range范围内完全相同。
同时符合上面三个条件才认为找到匹配的流表项。
2.8.mask_cache表项
在ovs_dp_process_packet函数中查找匹配的流表项时,如果报文的skb中已经带有skb_hash值,则将这个skb_hash值(32比特)分为4段,每段8比特,每段的哈希值暂记为hash,用这个hash值去datapath结构中的table.mask_cache缓存表中查找对应的掩码信息索引。每个表项(struct mask_cache_entry结构)存储了skb_hash和mask_index信息。初始化的时候,这个缓存表中所有表项都置0,因此,用报文的skb_hash值,分4段去查找,都无法找到合适的表项。这时需要从中选出最佳候选的表项,而最佳候选的表项为其skb_hash值最小。因此,初始化的时候,最终会选择table.mask_cache[0]为最佳候选表项。
接着根据上述2.4节所述的查找过程,找到合适的流表项信息。如果匹配成功,则最佳候选表项table.mask_cache[0].mask_index记录了掩码信息索引(index),即:table.mask_array->masks[index]。同时table.mask_cache[0].skb_hash赋值为skb_hash。假设index为0,则对应如下图所示。
后续如果收到的报文带的skb_hash值与table.mask_cache[0].skb_hash值相等时,则首先使用table.mask_cache[0].mask_index索引的掩码信息去找匹配的流表信息。当然,如果匹配成功,table.mask_cache[0].mask_index可能并非为0(之前存储的值),有可能更新为其他值。而如果匹配失败,则table.mask_cache[0].skb_hash置0。
2.9.mask_array扩充
当datapath结构中的table.mask_array->count >= table.mask_array->max时,则需要扩充mask_array空间。已当前table.mask_array->max * 2的大小进行扩充。扩充前后的效果如下图所示。橙色线指向的掩码信息在扩充之后,会释放掉old的掩码信息空间。扩充总是按照当前max数值的2倍大小进行扩充,例如:16 -> 32 -> 64 -> 128 -> …。从源码中暂时未看到这个扩充的最大值。
2.10.table_instance扩充
table_instance扩充有两个条件触发:
1)当datapath结构中的table.count > table.ti->n_buckets时,触发扩充。
2)datapath结构中的table.last_rehash记录了上次执行扩充或初始化table时系统的jiffies值。如果超过10分钟,则需要重新扩充,只是这次的扩充并非增大空间,而是以相同的大小重新分配空间。觉得这样做的意义是重新生成table_instance结构中的hash_seed值,重新安排哈希桶的链表长度,分散存储,减少匹配的比较次数。
我们主要以第一种条件为例,描述扩充的过程。这种扩充是在原有的哈希桶数目(n_buckets)基础之上,以2倍的大小进行扩充。扩充前后,table_instance结构的变化如下图所示。
根据上图可知,扩充之后,table_instance中的hash_seed更新为新的随机数了。这样,在从旧的table_instance将哈希桶中对应的各个流信息拷贝到新的table_instance时,需要重新计算哈希桶的位置,重新安排了。这样做的好处时:可以重新分散每个哈希桶中流信息链表的长度,减少在匹配时流的比较次数。如下图所示,之前Flow_A和Flow_B都位于第0个哈希桶,扩充之后,Flow_A处于了第0个哈希桶,而Flow_B则处于第1025个哈希桶了。这样在查找Flow_B的时候,比较的次数就减少了一次。在大规模查找的过程中,这种改变可以大大提高查找的效率。
2.11.示例
假设ovs用户空间的”慢路径”存储的流表信息如下所示:
1
2
3
|
table=0, src_ip=11.11.11.0/24, dst_ip=192.0.0.0/8, actions=output:1
table=0, src_ip=2.2.2.0/24, dst_ip=2.0.0.0/8, actions=output:2
table=0, dst_ip=8.0.0.0/8, actions=drop
|
初始化的时候,ovs内核空间的”快路径”没有存储任何的流路径信息。如下图所示:
收到第一个报文:src_ip=11.11.11.25, dst_ip=192.1.1.1
收到第一个报文,匹配结果Miss,upcall到用户空间,查表将结果发送回内核空间的datapath。根据上述2.3节所述的更新过程,会新增相应的掩码信息(mask_A)和流信息(flow_A)。如下图所示。
收到第二个报文:src_ip=11.11.11.63, dst_ip=192.168.7.8
根据上述2.4节所述的查找过程,匹配成功。
收到第三个报文:src_ip=2.2.2.4, dst_ip=2.7.7.7
根据上述2.4节所述的查找过程,匹配失败。因为掩码信息和mask_A完全相同,因此无需新增掩码信息,只需要将mask_A的ref_count引用计数加1即可。但是需要新增相应的流信息(flow_B),如下图所示。
收到第四个报文:dst_ip=8.12.34.56
根据上述2.4节所述的查找过程,匹配失败。需要新增相应的掩码信息(mask_B)和流信息(flow_C)。如下图所示
openstack的虚拟机网卡、网桥等(tap、qbr、qvb、qvo)mtu设置
vim /usr/lib/python2.7/site-packages/nova/network/linux_net.py
|
![](http://img.e-com-net.com/image/info8/f1daa31fa0e34a3f9c8da2bafc116e94.jpg)
![](http://img.e-com-net.com/image/info8/7b6ebb33b48e40b59dedf6442aefb153.jpg)
![](http://img.e-com-net.com/image/info8/d39859202e3c4d67a42cc159827715d6.jpg)
![](http://img.e-com-net.com/image/info8/bc03a2ec604c434a9a309e44ba231532.jpg)
![](http://img.e-com-net.com/image/info8/7f5843cdcf674ad09721d67566c2f1ef.png)
![](http://img.e-com-net.com/image/info8/2c36664284e446d1a3ca116ed16024cd.jpg)
![](http://img.e-com-net.com/image/info8/afd1b365b8e743319f1e5ef22a11add6.jpg)
![](http://img.e-com-net.com/image/info8/cb6b67e8dea94d48b8a25bb67001927d.png)
![](http://img.e-com-net.com/image/info8/c48410805a424b96ade0e13a1d255b83.jpg)
openstack底层技术-使用openvswitch
原文地址
www.isjian.com/openstack/openstack-base-use-openvswitch
openstack底层技术-使用openvswitch
- Open vSwitch介绍
- OVS架构
- ovs-vswitchd
- ovsdb-server
- OpenFlow
- Controller
- Kernel Datapath
- OVS概念
- Bridge
- Port
- Interface
- Controller
- datapath
- 流(flows)
- OVS中管理工具的使用及区别
Open vSwitch介绍
在过去,数据中心的服务器是直接连在硬件交换机上,后来VMware实现了服务器虚拟化技术,使虚拟服务器(VMs)能够连接在虚拟交换机上,借助这个虚拟交换机,可以为服务器上运行的VMs或容器提供逻辑的虚拟的以太网接口,这些逻辑接口都连接到虚拟交换机上,有三种比较流行的虚拟交换机: VMware virtual switch, Cisco Nexus 1000V,和Open vSwitch
Open vSwitch(OVS)是运行在虚拟化平台上的虚拟交换机,其支持OpenFlow协议,也支持gre/vxlan/IPsec等隧道技术。在OVS之前,基于Linux的虚拟化平台比如KVM或Xen上,缺少一个功能丰富的虚拟交换机,因此OVS迅速崛起并开始在Xen/KVM中流行起来,并且应用于越来越多的开源项目,比如openstack neutron中的网络解决方案
在虚拟交换机的Flow控制器或管理工具方面,一些商业产品都集成有控制器或管理工具,比如Cisco 1000V的Virtual Supervisor Manager(VSM)
,VMware的分布式交换机中的vCenter
。而OVS则需要借助第三方控制器或管理工具实现复杂的转发策略。例如OVS支持OpenFlow 协议,我们就可以使用任何支持OpenFlow协议的控制器来对OVS进行远程管理。OpenStack Neutron中的ML2插件也能够实现对OVS的管理。但这并不意味着OVS必须要有一个控制器才能工作。在不连接外部控制器情况下,OVS自身可以依靠MAC地址学习实现二层数据包转发功能,就像Linux Bridge
在基于Linux内核的系统上,应用最广泛的还是系统自带的虚拟交换机Linux Bridge
,它是一个单纯的基于MAC地址学习的二层交换机,简单高效,但同时缺乏一些高级特性,比如OpenFlow,VLAN tag,QOS,ACL,Flow等,而且在隧道协议支持上,Linux Bridge只支持vxlan,OVS支持gre/vxlan/IPsec等,这也决定了OVS更适用于实现SDN技术
OVS支持以下features
- 支持NetFlow, IPFIX, sFlow, SPAN/RSPAN等流量监控协议
- 精细的ACL和QoS策略
- 可以使用OpenFlow和OVSDB协议进行集中控制
- Port bonding,LACP,tunneling(vxlan/gre/Ipsec)
- 适用于Xen,KVM,VirtualBox等hypervisors
- 支持标准的802.1Q VLAN协议
- 基于VM interface的流量管理策略
- 支持组播功能
- flow-caching engine(datapath模块)
文章使用环境
centos7
openvswitch 2.5
OpenFlow 1.4`
OVS架构
先看下OVS整体架构,用户空间主要组件有数据库服务ovsdb-server和守护进程ovs-vswitchd。kernel中是datapath内核模块。最上面的Controller表示OpenFlow控制器,控制器与OVS是通过OpenFlow协议进行连接,控制器不一定位于OVS主机上,下面分别介绍图中各组件
ovs-vswitchd
ovs-vswitchd
守护进程是OVS的核心部件,它和datapath
内核模块一起实现OVS基于流的数据交换。作为核心组件,它使用openflow协议与上层OpenFlow控制器通信,使用OVSDB协议与ovsdb-server
通信,使用netlink
和datapath
内核模块通信。ovs-vswitchd
在启动时会读取ovsdb-server
中配置信息,然后配置内核中的datapaths
和所有OVS switches,当ovsdb中的配置信息改变时(例如使用ovs-vsctl工具),ovs-vswitchd
也会自动更新其配置以保持与数据库同步
# ps -ef |grep ovs-vs
root 22176 22175 0 Jan17 ? 00:16:56 ovs-vswitchd unix:/var/run/openvswitch/db.sock -vconsole:emer -vsyslog:err -vfile:info --mlockall --no-chdir --log-file=/var/log/openvswitch/ovs-vswitchd.log --pidfile=/var/run/openvswitch/ovs-vswitchd.pid --detach --monitor
ovs-vswitchd
需要加载datapath
内核模块才能正常运行。它会自动配置datapath
flows,因此我们不必再使用ovs-dpctl
去手动操作datapath
,但ovs-dpctl
仍可用于调试场合
在OVS中,ovs-vswitchd
从OpenFlow控制器获取流表规则,然后把从datapath
中收到的数据包在流表中进行匹配,找到匹配的flows并把所需应用的actions返回给datapath
,同时作为处理的一部分,ovs-vswitchd
会在datapath
中设置一条datapath flows用于后续相同类型的数据包可以直接在内核中执行动作,此datapath flows相当于OpenFlow flows的缓存。对于datapath
来说,其并不知道用户空间OpenFlow的存在,datapath内核模块信息如下
# modinfo openvswitch
filename: /lib/modules/3.10.0-327.el7.x86_64/kernel/net/openvswitch/openvswitch.ko license: GPL description: Open vSwitch switching datapath rhelversion: 7.2 srcversion: F75F2B83324DCC665887FD5 depends: libcrc32c intree: Y ...
ovsdb-server
ovsdb-server
是OVS轻量级的数据库服务,用于整个OVS的配置信息,包括接口/交换内容/VLAN等,OVS主进程ovs-vswitchd
根据数据库中的配置信息工作,下面是ovsdb-server
进程详细信息
ps -ef |grep ovsdb-server
root 22166 22165 0 Jan17 ? 00:02:32 ovsdb-server /etc/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/var/run/openvswitch/db.sock --private-key=db:Open_vSwitch,SSL,private_key --certificate=db:Open_vSwitch,SSL,certificate --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --no-chdir --log-file=/var/log/openvswitch/ovsdb-server.log --pidfile=/var/run/openvswitch/ovsdb-server.pid --detach --monitor
/etc/openvswitch/conf.db
是数据库文件存放位置,文件形式存储保证了服务器重启不会影响其配置信息,ovsdb-server
需要文件才能启动,可以使用ovsdb-tool create
命令创建并初始化此数据库文件 --remote=punix:/var/run/openvswitch/db.sock
实现了一个Unix sockets连接,OVS主进程ovs-vswitchd
或其它命令工具(ovsdb-client)通过此socket连接管理ovsdb /var/log/openvswitch/ovsdb-server.log
是日志记录
OpenFlow
OpenFlow是开源的用于管理交换机流表的协议,OpenFlow在OVS中的地位可以参考上面架构图,它是Controller和ovs-vswitched间的通信协议。需要注意的是,OpenFlow是一个独立的完整的流表协议,不依赖于OVS,OVS只是支持OpenFlow协议,有了支持,我们可以使用OpenFlow控制器来管理OVS中的流表,OpenFlow不仅仅支持虚拟交换机,某些硬件交换机也支持OpenFlow协议
OVS常用作SDN交换机(OpenFlow交换机),其中控制数据转发策略的就是OpenFlow flow。OpenStack Neutron中实现了一个OpenFlow控制器用于向OVS下发OpenFlow flows控制虚拟机间的访问或隔离。本文讨论的默认是作为SDN交换机场景下
OpenFlow flow的流表项存放于用户空间主进程ovs-vswitchd
中,OVS除了连接OpenFlow控制器获取这种flow,文章后面会提到的命令行工具ovs-ofctl
工具也可以手动管理OVS中的OpenFlow flow,可以查看man ovs-ofctl
了解
在OVS中,OpenFlow flow是最重要的一种flow, 然而还有其它几种flows存在,文章下面OVS概念部分会提到
Controller
Controller指OpenFlow控制器。OpenFlow控制器可以通过OpenFlow协议连接到任何支持OpenFlow的交换机,比如OVS。控制器通过向交换机下发流表规则来控制数据流向。除了可以通过OpenFlow控制器配置OVS中flows,也可以使用OVS提供的ovs-ofctl
命令通过OpenFlow协议去连接OVS,从而配置flows,命令也能够对OVS的运行状况进行动态监控。
Kernel Datapath
下面讨论场景是OVS作为一个OpenFlow交换机
datapath是一个Linux内核模块,它负责执行数据交换。关于datapath,The Design and Implementation of Open vSwitch中有描述
The datapath module in the kernel receives the packets first, from a physical NIC or a VM’s virtual NIC. Either ovs-vswitchd has instructed the datapath how to handle packets of this type, or it has not. In the former case, the datapath module simply follows the instructions, called actions, given by ovs-vswitchd, which list physical ports or tunnels on which to transmit the packet. Actions may also specify packet modifications, packet sampling, or instructions to drop the packet. In the other case, where the datapath has not been told what to do with the packet, it delivers it to ovs-vswitchd. In userspace, ovs-vswitchd determines how the packet should be handled, then it passes the packet back to the datapath with the desired handling. Usually, ovs-vswitchd also tells the datapath to cache the actions, for handling similar future packets.
为了说明datapath,来看一张更详细的架构图,图中的大部分组件上面都有提到
用户空间ovs-vswitchd
和内核模块datapath
决定了数据包的转发,首先,datapath
内核模块收到进入数据包(物理网卡或虚拟网卡),然后查找其缓存(datapath flows),当有一个匹配的flow时它执行对应的操作,否则datapath
会把该数据包送入用户空间由ovs-vswitchd
负责在其OpenFlow flows中查询(图1中的First Packet),ovs-vswitchd
查询后把匹配的actions返回给datapath
并设置一条datapath flows到datapath
中,这样后续进入的同类型的数据包(图1中的Subsequent Packets)因为缓存匹配会被datapath
直接处理,不用再次进入用户空间。
datapath
专注于数据交换,它不需要知道OpenFlow的存在。与OpenFlow打交道的是ovs-vswitchd
,ovs-vswitchd
存储所有Flow规则供datapath
查询或缓存.
虽然有ovs-dpctl
管理工具的存在,但我们没必要去手动管理datapath
,这是用户空间ovs-vswitchd
的工作
OVS概念
这部分说下OVS中的重要概念,使用OpenStack neutron+vxlan部署模式下网络节点OVS网桥作为例子
# ovs-vsctl show
e44abab7-2f65-4efd-ab52-36e92d9f0200
Manager "ptcp:6640:127.0.0.1" is_connected: true Bridge br-ext Controller "tcp:127.0.0.1:6633" is_connected: true fail_mode: secure Port br-ext Interface br-ext type: internal Port "eth1" Interface "eth1" Port phy-br-ext Interface phy-br-ext type: patch options: {peer=int-br-ext} Bridge br-tun Controller "tcp:127.0.0.1:6633" is_connected: true fail_mode: secure Port br-tun Interface br-tun type: internal Port patch-int Interface patch-int type: patch options: {peer=patch-tun} Port "vxlan-080058ca" Interface "vxlan-080058ca" type: vxlan options: {df_default="true", in_key=flow, local_ip="8.0.88.201", out_key=flow, remote_ip="8.0.88.202"} Bridge br-int Controller "tcp:127.0.0.1:6633" is_connected: true fail_mode: secure Port "qr-11591618-c4" tag: 3 Interface "qr-11591618-c4" type: internal Port patch-tun Interface patch-tun type: patch options: {peer=patch-int} Port int-br-ext Interface int-br-ext type: patch options: {peer=phy-br-ext}
Bridge
Bridge代表一个以太网交换机(Switch),一个主机中可以创建一个或者多个Bridge。Bridge的功能是根据一定规则,把从端口收到的数据包转发到另一个或多个端口,上面例子中有三个Bridge,br-tun
,br-int
,br-ext
添加一个网桥br0
ovs-vsctl add-br br0
Port
端口Port与物理交换机的端口概念类似,Port是OVS Bridge上创建的一个虚拟端口,每个Port都隶属于一个Bridge。Port有以下几种类型
- Normal
可以把操作系统中已有的网卡(物理网卡em1/eth0,或虚拟机的虚拟网卡tapxxx)挂载到ovs上,ovs会生成一个同名Port处理这块网卡进出的数据包。此时端口类型为Normal。
如下,主机中有一块物理网卡eth1
,把其挂载到OVS网桥br-ext
上,OVS会自动创建同名Port eth1
。
ovs-vsctl add-port br-ext eth1
#Bridge br-ext中出现Port "eth1"
有一点要注意的是,挂载到OVS上的网卡设备不支持分配IP地址,因此若之前eth1
配置有IP地址,挂载到OVS之后IP地址将不可访问。这里的网卡设备不只包括物理网卡,也包括主机上创建的虚拟网卡
- Internal
Internal类型是OVS内部创建的虚拟网卡接口,每创建一个Port,OVS会自动创建一个同名接口(Interface)挂载到新创建的Port上。接口的概念下面会提到。
下面创建一个网桥br0,并创建一个Internal类型的Port p0
ovs-vsctl add-br br0
ovs-vsctl add-port br0 p0 -- set Interface p0 type=internal #查看网桥br0 ovs-vsctl show br0 Bridge "br0" fail_mode: secure Port "p0" Interface "p0" type: internal Port "br0" Interface "br0" type: internal
可以看到有两个Port。当ovs创建一个新网桥时,默认会创建一个与网桥同名的Internal Port。在OVS中,只有”internal”类型的设备才支持配置IP地址信息,因此我们可以为br0
接口配置一个IP地址,当然p0
也可以配置IP地址
ip addr add 192.168.10.11/24 dev br0 ip link set br0 up #添加默认路由 ip route add default via 192.168.10.1 dev br0
上面两种Port类型区别在于,Internal类型会自动创建接口(Interface),而Normal类型是把主机中已有的网卡接口添加到OVS中
- Patch
当主机中有多个ovs网桥时,可以使用Patch Port把两个网桥连起来。Patch Port总是成对出现,分别连接在两个网桥上,从一个Patch Port收到的数据包会被转发到另一个Patch Port,类似于Linux系统中的veth
。使用Patch连接的两个网桥跟一个网桥没什么区别,OpenStack Neutron中使用到了Patch Port。上面网桥br-ext
中的Port phy-br-ext
与br-int
中的Port int-br-ext
是一对Patch Port
可以使用ovs-vsctl
创建patch设备,如下创建两个网桥br0,br1
,然后使用一对Patch Port
连接它们
ovs-vsctl add-br br0
ovs-vsctl add-br br1
ovs-vsctl \
-- add-port br0 patch0 -- set interface patch0 type=patch options:peer=patch1 \ -- add-port br1 patch1 -- set interface patch1 type=patch options:peer=patch0 #结果如下 #ovs-vsctl show Bridge "br0" Port "br0" Interface "br0" type: internal Port "patch0" Interface "patch0" type: patch options: {peer="patch1"} Bridge "br1" Port "br1" Interface "br1" type: internal Port "patch1" Interface "patch1" type: patch options: {peer="patch0"}
连接两个网桥不止上面一种方法,linux中支持创建Veth
设备对,我们可以首先创建一对Veth
设备对,然后把这两个Veth
分别添加到两个网桥上,其效果跟OVS中创建Patch Port一样,只是性能会有差别
#创建veth设备对veth-a,veth-b
ip link add veth-a type veth peer name veth-b #使用Veth连接两个网桥 ovs-vsctl add-port br0 veth-a ovs-vsctl add-port br1 veth-b
- Tunnel
OVS中支持添加隧道(Tunnel)端口,常见隧道技术有两种gre
或vxlan
。隧道技术是在现有的物理网络之上构建一层虚拟网络,上层应用只与虚拟网络相关,以此实现的虚拟网络比物理网络配置更加灵活,并能够实现跨主机的L2通信以及必要的租户隔离。不同隧道技术其大体思路均是将以太网报文使用隧道协议封装,然后使用底层IP网络转发封装后的数据包,其差异性在于选择和构造隧道的协议不同。Tunnel在OpenStack中用作实现大二层网络以及租户隔离,以应对公有云大规模,多租户的复杂网络环境。
OpenStack是多节点结构,同一子网的虚拟机可能被调度到不同计算节点上,因此需要有隧道技术来保证这些同子网不同节点上的虚拟机能够二层互通,就像他们连接在同一个交换机上,同时也要保证能与其它子网隔离。
OVS在计算和网络节点上建立隧道Port来连接各节点上的网桥br-int
,这样所有网络和计算节点上的br-int
互联形成了一个大的虚拟的跨所有节点的逻辑网桥(内部靠tunnel id或VNI隔离不同子网),这个逻辑网桥对虚拟机和qrouter是透明的,它们觉得自己连接到了一个大的br-int
上。从某个计算节点虚拟机发出的数据包会被封装进隧道通过底层网络传输到目的主机然后解封装。
上面网桥br-tun
中Port "vxlan-080058ca"
就是一个vxlan
类型tunnel端口。下面使用两台主机测试创建vxlan隧道
#主机192.168.7.21上
ovs-vsctl add-br br-vxlan
#主机192.168.7.23上 ovs-vsctl add-br br-vxlan #主机192.168.7.21上添加连接到7.23的Tunnel Port ovs-vsctl add-port br-vxlan tun0 -- set Interface tun0 type=vxlan options:remote_ip=192.168.7.23 #主机192.168.7.23上添加连接到7.21的Tunnel Port ovs-vsctl add-port br-vxlan tun0 -- set Interface tun0 type=vxlan options:remote_ip=192.168.7.21
然后,两个主机上桥接到br-vxlan
的虚拟机就像连接到同一个交换机一样,可以实现跨主机的L2连接,同时又完全与物理网络隔离。
Interface
Interface是连接到Port的网络接口设备,是OVS与外部交换数据包的组件,在通常情况下,Port和Interface是一对一的关系,只有在配置Port为 bond模式后,Port和Interface是一对多的关系。这个网络接口设备可能是创建Internal
类型Port时OVS自动生成的虚拟网卡,也可能是系统的物理网卡或虚拟网卡(TUN/TAP)挂载在ovs上。 OVS中只有”Internal”类型的网卡接口才支持配置IP地址
Interface
是一块网络接口设备,负责接收或发送数据包,Port是OVS网桥上建立的一个虚拟端口,Interface
挂载在Port上。
Controller
OpenFlow控制器。OVS可以同时接受一个或者多个OpenFlow控制器的管理。主要作用是下发流表(Flow Tables)到OVS,控制OVS数据包转发规则。控制器与OVS通过网络连接,不一定要在同一主机上
可以看到上面实例中三个网桥br-int
,br-ext
,br-tun
都连接到控制器Controller "tcp:127.0.0.1:6633
上
datapath
OVS内核模块,负责执行数据交换。其内部有作为缓存使用的flows,关于datapath,下面会细说
流(flows)
flows是OVS进行数据转发策略控制的核心数据结构,区别于Linux Bridge是个单纯基于MAC地址学习的二层交换机,flows的存在使OVS作为一款SDN交换机成为云平台网络虚拟机化主要组件
OVS中有多种flows存在,用于不同目的,但最主要的还是OpenFlow flows这种,文中未明确说明的flows都是指OpenFlow flows
- OpenFlow flows
OVS中最重要的一种flows,Controller控制器下发的就是这种flows,OVS架构部分已经简单介绍过,关于openflow的具体使用,会在另一篇文章中说明
- “hidden” flows
OVS在使用OpenFlow flow时,需要与OpenFlow控制器建立TCP连接,若此TCP连接不依赖OVS,即没有OVS依然可以建立连接,此时就是out-of-band control
模式,这种模式下不需要”hidden” flows
但是在in-band control
模式下,TCP连接的建立依赖OVS控制的网络,但此时OVS依赖OpenFLow控制器下发的flows才能正常工作,没法建立TCP连接也就无法下发flows,这就产生矛盾了,因此需要存在一些”hidden” flows,这些”hidden” flows保证了TCP连接能够正常建立。关于in-band control
详细介绍,参考OVS官方文档Design Decisions In Open vSwitch 中In-Band Control部分
“hidden” flows优先级高于OpenFlow flows,它们不需要手动设置。可以使用ovs-appctl
查看这些flows,下面命令输出内容包括OpenFlow flows
,"hidden" flows
ovs-appctl bridge/dump-flows
- datapath flows
datapath flows是datapath
内核模块维护的flows,由内核模块维护意味着我们并不需要去修改管理它。与OpenFlow flows不同的是,它不支持优先级,并且只有一个表,这些特点使它非常适合做缓存。与OpenFlow一样的是它支持通配符,也支持指令集(多个action)
datapath flows可以来自用户空间ovs-vswitchd
缓存,也可以是datapath内核模块进行MAC地址学习到的flows,这取决与OVS是作为SDN交换机,还是像Linux Bridge那样只是一个简单基于MAC地址学习的二层交换机
几种flows对比
我们可以修改和配置的是OpenFlow flows。datapath flow和”hidden” flows由OVS自身管理,我们不必去修改它。当然,调试场景下还是可以使用工具修改的
flows命令行工具
介绍下上面三种flows管理工具,不具体说明,具体使用可以查看相关man手册
-
ovs-ofctl dump-flows
打印指定网桥内的所有OpenFlow flows,可以存在多个流表(flow tables),按表顺序显示。不包括”hidden” flows。这是最常用的查看flows命令,当然这条命令对所有OpenFlow交换机都有效,不单单是OVS -
ovs-appctl bridge/dump-flows
打印指定网桥内所有OpenFlow flows,包括”hidden” flows,in-band control
模式下排错可以用到 -
ovs-dpctl dump-flows [dp]
打印内核模块中datapath flows,[dp]
可以省略,默认主机中只有一个datapathsystem@ovs-system
man手册可以找到非常详细的用法说明,注意ovs-ofctl
管理的是OpenFlow flows
OVS中管理工具的使用及区别
上面介绍了OVS用户空间进程以及控制器和OpenFlow协议,这里说下相关的命令行工具的使用及区别
ovs-vsctl
ovs-vsctl
是一个管理或配置ovs-vswitchd
的高级命令行工具,高级是说其操作对用户友好,封装了对数据库的操作细节。它是管理OVS最常用的命令,除了配置flows之外,其它大部分操作比如Bridge/Port/Interface/Controller/Database/Vlan等都可以完成
#添加网桥br0
ovs-vsctl add-br br0
#列出所有网桥 ovs-vsctl list-br #添加一个Port p1到网桥br0 ovs-vsctl add-port br0 p1 #查看网桥br0上所有Port ovs-vsctl list-ports br0 #获取br0网桥的OpenFlow控制器地址,没有控制器则返回空 ovs-vsctl get-controller br0 #设置OpenFlow控制器,控制器地址为192.168.1.10,端口为6633 ovs-vsctl set-controller br0 tcp:192.168.1.10:6633 #移除controller ovs-vsctl del-controller br0 #删除网桥br0 ovs-vsctl del-br br0 #设置端口p1的vlan tag为100 ovs-vsctl set Port p1 tag=100 #设置Port p0类型为internal ovs-vsctl set Interface p0 type=internal #添加vlan10端口,并设置vlan tag为10,Port类型为Internal ovs-vsctl add-port br0 vlan10 tag=10 -- set Interface vlan10 type=internal #添加隧道端口gre0,类型为gre,远端IP为1.2.3.4 ovs-vsctl add-port br0 gre0 -- set Interface gre0 type=gre options:remote_ip=1.2.3.4
ovsdb-tool
ovsdb-tool
是一个专门管理OVS数据库文件的工具,不常用,它不直接与ovsdb-server
进程通信
#可以使用此工具创建并初始化database文件
ovsdb-tool create [db] [schema]
#可以使用ovsdb-client get-schema [database]获取某个数据库的schema(json格式) #可以查看数据库更改记录,具体到操作命令,这个比较有用 ovsdb-tool show-log -m record 48: 2017-01-07 03:34:15.147 "ovs-vsctl: ovs-vsctl --timeout=5 -- --if-exists del-port tapcea211ae-10" table Interface row "tapcea211ae-10" (151f66b6): delete row table Port row "tapcea211ae-10" (cc9898cd): delete row table Bridge row "br-int" (fddd5e27): table Open_vSwitch row a9fc1666 (a9fc1666): record 49: 2017-01-07 04:18:23.671 "ovs-vsctl: ovs-vsctl --timeout=5 -- --if-exists del-port tap5b4345ea-d5 -- add-port br-int tap5b4345ea-d5 -- set Interface tap5b4345ea-d5 "external-ids:attached-mac=\"fa:16:3e:50:1b:5b\"" -- set Interface tap5b4345ea-d5 "external-ids:iface-id=\"5b4345ea-d5ea-4285-be99-0e4cadf1600a\"" -- set Interface tap5b4345ea-d5 "external-ids:vm-id=\"0aa2d71e-9b41-4c88-9038-e4d042b6502a\"" -- set Interface tap5b4345ea-d5 external-ids:iface-status=active" table Port insert row "tap5b4345ea-d5" (4befd532): table Interface insert row "tap5b4345ea-d5" (b8a5e830): table Bridge row "br-int" (fddd5e27): table Open_vSwitch row a9fc1666 (a9fc1666): ...
ovsdb-client
ovsdb-client
是ovsdb-server
进程的命令行工具,主要是从正在运行的ovsdb-server
中查询信息,操作的是数据库相关
#列出主机上的所有databases,默认只有一个库Open_vSwitch
ovsdb-client list-dbs
#获取指定数据库的schema信息 ovsdb-client get-schema [DATABASE] #列出指定数据库的所有表 ovsdb-client list-tables [DATABASE] #dump指定数据库所有数据,默认dump所有table数据,如果指定table,只dump指定table数据 ovsdb-client dump [DATABASE] [TABLE] #监控指定数据库中的指定表记录改变 ovsdb-client monitor DATABASE TABLE
ovs-ofctl
ovs-ofctl
是专门管理配置OpenFlow交换机的命令行工具,我们可以用它手动配置OVS中的OpenFlow flows,注意其不能操作datapath flows和”hidden” flows
#查看br-tun中OpenFlow flows
ovs-ofctl dump-flows br-tun
#查看br-tun端口信息
ovs-ofctl show br-tun
#添加新的flow:对于从端口p0进入交换机的数据包,如果它不包含任何VLAN tag,则自动为它添加VLAN tag 101 ovs-ofctl add-flow br0 "priority=3,in_port=100,dl_vlan=0xffff,actions=mod_vlan_vid:101,normal" #对于从端口3进入的数据包,若其vlan tag为100,去掉其vlan tag,并从端口1发出 ovs-ofctl add-flow br0 in_port=3,dl_vlan=101,actions=strip_vlan,output:1 #添加新的flow: 修改从端口p1收到的数据包的源地址为9.181.137.1,show 查看p1端口ID为100 ovs-ofctl add-flow br0 "priority=1 idle_timeout=0,in_port=100,actions=mod_nw_src:9.181.137.1,normal" #添加新的flow: 重定向所有的ICMP数据包到端口 p2 ovs-ofctl add-flow br0 idle_timeout=0,dl_type=0x0800,nw_proto=1,actions=output:102 #删除编号为 100 的端口上的所有流表项 ovs-ofctl del-flows br0 "in_port=100"
ovs-vsctl
是一个综合的配置管理工具,ovsdb-client
倾向于从数据库中查询某些信息,而ovsdb-tool
是维护数据库文件工具
文章地址http://www.isjian.com/openstack/openstack-base-use-openvswitch/
参考文章
https://www.sdxcentral.com/cloud/open-source/definitions/what-is-open-vswitch/
http://openvswitch.org/features/
https://www.ibm.com/developerworks/cn/cloud/library/1401_zhaoyi_openswitch/
http://openvswitch.org/slides/OpenStack-131107.pdf
http://horms.net/projects/openvswitch/2010-10/openvswitch.en.pdf
http://benpfaff.org/papers/ovs.pdf
https://networkheresy.com/category/open-vswitch/