openvswitch 系列第二篇 API接口

openvswitch 系列第二篇 API接口

1. openvswitch

1.1 基本描述

datapath为ovs内核模块,负责执行数据交换,也就是把从接收端口收到的数据包在流表中进行匹配,并执行匹配到的动作。内核中可以实现多个datapath(可以理解为桥,就是我们用ovs-vsctl看到的br0/br1之类), 一个datapath类似一个物理交换机,它可以对应多个vport(vport类似物理交换机的端口概念)。一个datapth关联一个flow table,一个flow table包含多个条目,每个条目包括两个内容:一个是flow的 match/key; 另一个是对应的action. 最常见的action是在不同vport中进行转发。

当一个数据报到达vport, 内核首先将它的flow key解析出来,之后在内核模块datapath的flow cache (大小为(sizeof(struct sw_flow)+ (nr_cpu_ids * sizeof(struct flow_stats ) = ( 1248 + 68 )=1296, 根据系统实际的CUP的数量多少会稍有不同)中的flow table中查找. 如果找到匹配的flow 规则,则去执行对应的action. 如果没有找到匹配的flow规则,则将数据报通过netlink的方式发送到用户空间的ovs-vswitchd处理,而用户空间的流表空间(65536)大的多,所以匹配到的几率也更高,通过在用户空间查找,并执行对应的action;如果找到,会通过netlink把用户空间的流表推送到datapath的flow cache中,后续的报文就可以直接在内核态处理.如果最后还是没有找到,那就使用默认的流表规则(丢掉这个包,或者其他).

用户空间有两个进程组成:ovs-vswitchd和ovsdb-server。
ovsdb-server保存了ovs-vswitchd的配置信息,ovsdb通常是一个文件,并且保存在文件系统中,通常来说是/etc/openvswitch/conf.db
ovs-vswitchd是一个daemon,北向与Controller通过OpenFlow协议通信.南向与openvswitch内核模块通过netlink通信.东西向过OVSDB协议与ovsdb-server通信.

2 openvswitch API的分类

在Linux中,可以使用Netlink来实现用户空间与内核的通信.而openvswitch也正是使用了netlink机制,使用其中的generic netlink来用来实现通信. openvswitch一共有4大类API: datapath, virtual port, flow, 和packet. 通常在使用这些时,我们需要CAP_NET_ADMIN的权限.

2.1 datapath API

有5种对应的操作NEW ,DEL,GET,SET和UNSPEC(预留的,现在不用).通过这些命令,用户可以建立,删除,查询和修改datapath.
定义为枚举类型,其实对应过来也就是操作码与数字的键值对.
enum ovs_datapath_cmd {
OVS_DP_CMD_UNSPEC,
OVS_DP_CMD_NEW,
OVS_DP_CMD_DEL,
OVS_DP_CMD_GET,
OVS_DP_CMD_SET
};

2.1.1 OVS_DP_CMD_NEW

OVS_DP_CMD_NEW通过static int ovs_dp_cmd_new(struct sk_buff *skb, struct genl_info *info)实现.
当执行NEW操作的时候,ovs_dp_cmd_new 函数会被调用,此函数用于建立一个datapath.

2.1.2 OVS_DP_CMD_DEL

OVS_DP_CMD_DEL通过static int ovs_dp_cmd_del(struct sk_buff *skb, struct genl_info info)实现.
当执行DEL操作的时候,ovs_dp_cmd_del函数会被调用,此函数用于删除一个datapath. 至于删除哪一个datapath的信息则是由
info获取的.

2.1.3 OVS_DP_CMD_GET

OVS_DP_CMD_GET通过static int ovs_dp_cmd_get(struct sk_buff *skb, struct genl_info info)实现.
当执行GET操作的时候,ovs_dp_cmd_get函数会被调用.通过从
info获取到的信息,最终通过调用ovs_dp_cmd_fill_info实现并通过genlmsg_reply返回.

2.1.4 OVS_DP_CMD_SET

OVS_DP_CMD_SET通过static int ovs_dp_cmd_set(struct sk_buff *skb, struct genl_info *info)实现.
当执行SET操作的时候,ovs_dp_cmd_set函数会被调用.然后通过调用ovs_dp_change来进行属性的修改.

2.2 virtual port API

有5种对应的操作NEW,DEL,GET,SET和UNSPEC(预留的,现在不用).通过这些命令,用户可以建立,删除,查询和修改vport.
定义为枚举类型,其实对应过来也就是操作码与数字的键值对.
enum ovs_vport_cmd {
OVS_VPORT_CMD_UNSPEC,
OVS_VPORT_CMD_NEW,
OVS_VPORT_CMD_DEL,
OVS_VPORT_CMD_GET,
OVS_VPORT_CMD_SET
};

2.2.1 OVS_VPORT_CMD_NEW

OVS_VPORT_CMD_NEW通过static int ovs_vport_cmd_new(struct sk_buff *skb, struct genl_info info)实现.
当执行NEW操作的时候,ovs_vport_cmd_new函数会被调用,然后给从
info获取到的datapath信息,给指定的datapath新建指定的端口.

2.2.2 OVS_VPORT_CMD_DEL

OVS_VPORT_CMD_DEL通过static int ovs_vport_cmd_del(struct sk_buff *skb, struct genl_info info)实现.
当执行DEL操作的时候,ovs_vport_cmd_del函数会被调用,然后从
info中获取到指定的ports进行删除操作.

2.2.3 OVS_VPORT_CMD_GET

OVS_VPORT_CMD_GET通过static int ovs_vport_cmd_get(struct sk_buff *skb, struct genl_info info)实现.
当执行GET操作的时候,ovs_vport_cmd_get函数会被调用,然后从
info获取到指定端口的信息后,最后通过genlmsg_reply返回.

2.2.4 OVS_VPORT_CMD_SET

OVS_VPORT_CMD_SET通过static int ovs_vport_cmd_set(struct sk_buff *skb, struct genl_info info)实现.
当执行SET操作的时候,ovs_vport_cmd_set函数会被调用,然后从
info获取到指定的端口,然后调用ovs_vport_set_options来更新对应的vport属性.

2.3 flow API

有5种对应的操作NEW,DEL,GET,SET和UNSPEC(预留的,现在不用).通过这些命令,用户可以建立,删除,查询和修改flow.
定义为枚举类型,其实对应过来也就是操作码与数字的键值对.
enum ovs_flow_cmd {
OVS_FLOW_CMD_UNSPEC,
OVS_FLOW_CMD_NEW,
OVS_FLOW_CMD_DEL,
OVS_FLOW_CMD_GET,
OVS_FLOW_CMD_SET
};

2.3.1 OVS_FLOW_CMD_NEW

OVS_FLOW_CMD_NEW通过static int ovs_flow_cmd_new(struct sk_buff *skb, struct genl_info *info)实现.
当执行NEW操作的时候,ovs_flow_cmd_new会被调用然后一系列函数会被调用,比如ovs_flow_alloc,ovs_flow_tbl_insert,等等. 因为建立流表需要判断action是否有效,流表是否重复,而且还需要涉及同步加锁解锁等等.

2.3.2 OVS_FLOW_CMD_DEL

OVS_FLOW_CMD_DEL通过static int ovs_flow_cmd_del(struct sk_buff *skb, struct genl_info info)实现.
当执行DEL操作的时候,ovs_flow_cmd_del会被调用,然后通过
info获取对应属性,找到对应datapath,对应的流表uuid,然后通过ovs_flow_tbl_remove,ovs_flow_free等等将对应流表删除.当然了,这个过程也得加锁解锁.

2.3.3 OVS_FLOW_CMD_GET

OVS_FLOW_CMD_GET通过static int ovs_flow_cmd_get(struct sk_buff *skb, struct genl_info info)实现.
当执行GET操作的时候,ovs_flow_cmd_get会被调用,然后通过
info获取对应属性,找到对应datapath,对应的流表uuid
最后通过ovs_flow_cmd_build_info获取到需要的信息,并返回.

2.3.4 OVS_FLOW_CMD_SET

OVS_FLOW_CMD_SET通过static int ovs_flow_cmd_set(struct sk_buff *skb, struct genl_info info)实现.
当执行SET操作的时候,ovs_flow_cmd_set会被调用,然后通过
info获取对应属性,找到对应datapath,对应的流表uuid,最后调用一系列函数,如ovsl_dereference更新action,用ovs_flow_stats_clear清除对应状态等等.当然,这个过程也得加锁解锁.

2.4 packet API

有4种对应的操作MISS,ACTION,EXECUTE和和UNSPEC(预留的,现在不用),用于与用户空间应用进行数据包交互(发送,接收).
定义为枚举类型,其实对应过来也就是操作码与数字的键值对.
enum ovs_packet_cmd {
OVS_PACKET_CMD_UNSPEC,
OVS_PACKET_CMD_MISS, /* 内核发向用户空间, 流表没有命中 /
OVS_PACKET_CMD_ACTION, /
内核发向用户空间, OVS_ACTION_ATTR_USERSPACE的操作. /
OVS_PACKET_CMD_EXECUTE /
用户空间命令,给对应的packet执行指定的动作 */
};

2.4.1 OVS_PACKET_CMD_MISS

内核函数ovs_dp_process_packet在处理datapath的packet的时候,如果出现流表没有匹配的话,ovs_flow_stats_update, u64_stats_update_begin等函数就会被调用,然后对应的datapath计数器会被更新.

2.4.2 OVS_PACKET_CMD_ACTION

内核函数output_userspace通过初始化结构dp_upcall_indo将OVS_PACKET_CMD_ACTION传进去,然后调用ovs_dp_updacall实现对用户空间OVS_ACTION_ATTR_USERSPACE的操作.

2.4.3 OVS_PACKET_CMD_EXECUTE

OVS_PACKET_CMD_EXECUTE通过static int ovs_packet_cmd_execute(struct sk_buff *skb, struct genl_info *info)实现. 当执行EXECUTE操作的时候,其实就是packet的发送(转发)过程. 一系列内核函数会被使用, __dev_alloc_skb建立一个skb, 通过ovs_flow_alloc建立一个sw_flow, 以及使用ovs_execute_actions来执行这个发送操作.

3 OVS在Openshift和OpenStack的使用.

3.1 Openshift在使用OVS时,并不是直接调用上面介绍到的API接口,而是使用OVS封装好的二进制工具比如ovs-appctl”,”ovs-dpctl”,”ovs-ofctl”,”ovs-vsctl”之类,通过”golang”调用的. 例子如下:

1
2
3
4
5
6
7
func (ovsif *ovsExec) AddPort(port string, ofportRequest int, properties ...string) (int, error) {
args := []string{"--may-exist", "add-port", ovsif.bridge, port}
if ofportRequest > 0 || len(properties) > 0 {
args = append(args, "--", "set", "Interface", port)
<truncated>
_, err := ovsif.exec(OVS_VSCTL, args...)
<truncated>

3.2 OpenStack在使用OVS时,同样也不是直接调用上面介绍到的API接口,而是使用OVS封装好的二进制工具比如ovs-appctl”,”ovs-dpctl”,”ovs-ofctl”,”ovs-vsctl”之类,通过”python”调用的.例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
<truncated>
_ovs_vsctl(['--', '--may-exist', 'add-port', bridge, dev,
'--', 'set', 'Interface', dev, 'type=internal',
'--', 'set', 'Interface', dev,
'external-ids:iface-id=%s' % dev,
'--', 'set', 'Interface', dev,
'external-ids:iface-status=active',
'--', 'set', 'Interface', dev,
'external-ids:attached-mac=%s' % mac_address])
<truncated>
_execute('ovs-ofctl',
'add-flow', bridge, 'priority=1,actions=drop',
run_as_root=True)

4 到此,openvswitch的API就是简要地介绍完毕了.