OVS数据收发流程解析
ovs版本为 2.8.2。
OVS整体架构
OVS架构图如下,具体每个部件功能不具体分析,本文主要涉及内核部分。
OVS接口类型
执行命令ovs-vsctl show
或者 ovs-dpctl show
(显示默认datapath),来查看ovs接口信息时,常会看到接口类型,以下对OVS中有哪些接口类型及不同接口类型的接口之间的区别进行分析。
在源码中有这么一个函数:
1 | static const char * |
可看出,ovs-vsctl show
时的类型显示在内核中都有对应关系。宏定义如下:
1 | enum ovs_vport_type { |
下面,选取具有代表性的几个类型进行分析。
system
此类vport(ovs-dpctl show
未显示类型的接口)是对设备原有接口的封装,内核类型为’OVS_VPORT_TYPE_NETDEV’。定义的vport操作变量为’ovs_netdev_vport_ops’。
1 | static struct vport_ops ovs_netdev_vport_ops = { |
此种接口创建内部比较特殊,因此需要特殊强调。在’netdev_create’中有一段如下代码
1 | static struct vport *netdev_create(const struct vport_parms *parms) |
1 | struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops, |
1 | struct vport *ovs_netdev_link(struct vport *vport, const char *name) |
‘netdev_rx_handler_register’实现如下
1 | int netdev_rx_handler_register(struct net_device *dev, |
此类接口定义了 ‘rx_handler’,因此,在CPU报文处理函数’__netif_receive_skb_core’中
1 | …… |
也就是说此类接口处理报文在协议栈之前,因此netfilter对此类接口不起作用,所以在云环境(openstack)中,需要在虚拟机tap口与虚拟交换机之间增加Linux bridge设备来使报文经过协议栈(netfilter起作用)来实现security group。
internal
OVS内部创建的虚拟网卡接口。每创建一个ovs bridge,OVS会自动创建一个同名接口(Interface)挂载到新创建的bridge上。或者也可以通过type=internal
把已经挂载的接口设置为‘internal’类型。
1 | ovs-vsctl add-br ovs-switch |
对于 internal 类型的的网络接口,OVS 会同时在 Linux 系统中创建一个可以用来收发数据的模拟网络设备。我们可以为这个网络设备配置 IP 地址、进行数据监听等等。
内核中对’internal’接口的类型定义为’OVS_VPORT_TYPE_INTERNAL’(network device implemented by datapath,datapath实现的网络设备)。定义的vport操作变量为’ovs_internal_vport_ops’。
1 | static struct vport_ops ovs_internal_vport_ops = { |
接口创建时,调用’internal_dev_create’进行接口初始化。
1 | static struct vport *internal_dev_create(const struct vport_parms *parms) |
vxlan
- ‘ovs-vsctl show’显示的type 为’vxlan’类型,此种接口为ovs虚拟接口。
- ‘ovs-dpctl show’显示的type 为’vxlan’类型,此种接口是对系统的封装,可看做系统口。
内核中对vxlan
类型的接口定义为OVS_VPORT_TYPE_VXLAN
。ovs vxlan创建在文件’vport-vxlan.c’中,定义 操作如下
1 | static struct vport_ops ovs_vxlan_netdev_vport_ops = { |
vxlan_create定义如下
1 | static struct vport *vxlan_create(const struct vport_parms *parms) |
‘ovs_netdev_link’ 函数上面已经分析过,值得注意的是,vxlan类型的接口收包函数也是 ‘netdev_frame_hook’ 。
基本上系统口都有master,而master为’ovs-system’。
对于ovs-system作用,还没搞清楚。
patch
patch 类型的接口是ovs中比较特殊的类型,其官方定义为“A pair of virtual devices that act as a patch cable”,在系统中运行man 5 ovs-vswitchd.conf.db
可看到。
patch port类似于Linux系统中的veth
,总是成对出现,分别连接在两个网桥上,从一个patch port收到的数据包会被转发到另一个patch port。
OVS接口报文处理
system接口
虚拟机利用TUN/TAP端口来与宿主机通信,此种端口是通过命令’ip tuntap add’来创建。ovs对原有接口的封装,也包括这类接口。
收包处理
- netdev_frame_hook
1 | /* Called with rcu_read_lock and bottom-halves disabled. */ |
netdev_port_receive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void netdev_port_receive(struct sk_buff *skb, struct ip_tunnel_info *tun_info)
{
struct vport *vport;
/*获取vport数据*/
vport = ovs_netdev_get_vport(skb->dev);
if (unlikely(!vport))
goto error;
/*接口禁止了lro相关,因此skb需要lro相关则报错退出*/
if (unlikely(skb_warn_if_lro(skb)))
goto error;
/*user不唯一则进行clone*/
skb = skb_share_check(skb, GFP_ATOMIC);
if (unlikely(!skb))
return;
/*恢复二层头,下面用得到*/
skb_push(skb, ETH_HLEN);
/*重新计算校验和*/
skb_postpush_rcsum(skb, skb->data, ETH_HLEN);
ovs_vport_receive(vport, skb, tun_info);
return;
error:
kfree_skb(skb);
}ovs_vport_receive
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
32int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
const struct ip_tunnel_info *tun_info)
{
struct sw_flow_key key;
int error;
/*设置ovs私有数据*/
OVS_CB(skb)->input_vport = vport;
OVS_CB(skb)->mru = 0;
OVS_CB(skb)->cutlen = 0;
/*判断是否属于同一个网络空间;可参考 openstack 网络架构 */
if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
u32 mark;
mark = skb->mark;
skb_scrub_packet(skb, true);
skb->mark = mark;
tun_info = NULL;
}
/*初始化ovs内部协议号*/
ovs_skb_init_inner_protocol(skb);
skb_clear_ovs_gso_cb(skb);
/*此函数会解析skb内容,并给key中字段赋值*/
/*注意 input_vport->port_no 为`ovs-dpctl show`显示的port number*/
error = ovs_flow_key_extract(tun_info, skb, &key);
if (unlikely(error)) {
kfree_skb(skb);
return error;
}
//内核匹配流表路径,没有则上送。
ovs_dp_process_packet(skb, &key);
return 0;
}ovs_dp_process_packet
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
41
42
43
44
45
46void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
{
const struct vport *p = OVS_CB(skb)->input_vport;
struct datapath *dp = p->dp;
struct sw_flow *flow;
struct sw_flow_actions *sf_acts;
struct dp_stats_percpu *stats;
u64 *stats_counter;
u32 n_mask_hit;
stats = this_cpu_ptr(dp->stats_percpu);
/*根据key找flow表,没有的话进行upcall;
此处找的是内核流表,可用`ovs-dpctl dump-flows [dp]`查看。
本文暂不对这些功能函数进行具体分析*/
flow = ovs_flow_tbl_lookup_stats(&dp->table, key, skb_get_hash(skb),
&n_mask_hit);
if (unlikely(!flow)) {
struct dp_upcall_info upcall;
int error;
memset(&upcall, 0, sizeof(upcall));
upcall.cmd = OVS_PACKET_CMD_MISS;
upcall.portid = ovs_vport_find_upcall_portid(p, skb);
upcall.mru = OVS_CB(skb)->mru;
error = ovs_dp_upcall(dp, skb, key, &upcall, 0);
if (unlikely(error))
kfree_skb(skb);
else
consume_skb(skb);
stats_counter = &stats->n_missed;
goto out;
}
/*flow填充到skb私有数据,并执行action*/
ovs_flow_stats_update(flow, key->tp.flags, skb);
sf_acts = rcu_dereference(flow->sf_acts);
ovs_execute_actions(dp, skb, sf_acts, key);
stats_counter = &stats->n_hit;
out:
/* Update datapath statistics. */
u64_stats_update_begin(&stats->syncp);
(*stats_counter)++;
stats->n_mask_hit += n_mask_hit;
u64_stats_update_end(&stats->syncp);
}ovs_execute_actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/* Execute a list of actions against 'skb'. */
int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb,
const struct sw_flow_actions *acts,
struct sw_flow_key *key)
{
int err, level;
/*单个CPU同时处理(准确的说,应该是排队,可能进程调度)4条报文*/
level = __this_cpu_inc_return(exec_actions_level);
if (unlikely(level > OVS_RECURSION_LIMIT)) {
net_crit_ratelimited("ovs: recursion limit reached on datapath %s, probable configuration error\n",
ovs_dp_name(dp));
kfree_skb(skb);
err = -ENETDOWN;
goto out;
}
//执行action
err = do_execute_actions(dp, skb, key,
acts->actions, acts->actions_len);
…………
}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
41
42
43
44/* Execute a list of actions against 'skb'. */
static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
struct sw_flow_key *key,
const struct nlattr *attr, int len)
{
const struct nlattr *a;
int rem;
for (a = attr, rem = len; rem > 0;
a = nla_next(a, &rem)) {
int err = 0;
/*获取action type;nla*定义在内核 netlink.h文件中*/
switch (nla_type(a)) {
/*从某接口转发*/
case OVS_ACTION_ATTR_OUTPUT: {
//获取 out_port
int port = nla_get_u32(a);
struct sk_buff *clone;
/* 每个输出操作都需要一个单独的'skb'克隆,如果输出操作是最后一个操作,则可以避免克隆。
*/
if (nla_is_last(a, rem)) {
do_output(dp, skb, port, key);
return 0;
}
clone = skb_clone(skb, GFP_ATOMIC);
if (clone)
do_output(dp, clone, port, key);
OVS_CB(skb)->cutlen = 0;
break;
}
//其他actions 这里不详细介绍
…………
}
if (unlikely(err)) {
kfree_skb(skb);
return err;
}
}
consume_skb(skb);
return 0;
}do_output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static int do_output(struct datapath *dp, struct sk_buff *skb, int out_port)
{
struct vport *vport;
if (unlikely(!skb))
return -ENOMEM;
vport = ovs_vport_rcu(dp, out_port);
if (unlikely(!vport)) {
kfree_skb(skb);
return -ENODEV;
}
/*vport是flow找到的out_port的vport,因此,此处会调用vport的send函数*/
ovs_vport_send(vport, skb);
return 0;
}
发包处理
接口初始化注册的发包函数为’dev_queue_xmit’。
internal接口
收包处理
internal接口报文一般会从system类型接口传入,从system接口收包处理过程继续,internal类型接口定义的send函数为’internal_dev_recv’。
1 | static netdev_tx_t internal_dev_recv(struct sk_buff *skb) |
发包处理
netif_rx函数重新进入了本机协议栈的处理,而internal类型的接口没有设置’rx_handler’,因此进入正常协议栈流程,最后会进入正常转发流程。
1 | dev_queue_xmit -> dev_hard_start_xmit -> ops->ndo_start_xmit(skb, dev); |
而internal在接口创建的时候定义了 ndo_start_xmit。
1 | static struct vport *internal_dev_create(const struct vport_parms *parms) |
1 | static const struct net_device_ops internal_dev_netdev_ops = { |
继续走读函数
1 | static int internal_dev_xmit(struct sk_buff *skb, struct net_device *netdev) |
vxlan接口
vxlan接口的 收包处理(netdev_frame_hook) 和 发包处理(vxlan_xmit),在以前的文章已经分析过。