OVS数据收发流程解析

ovs版本为 2.8.2。

OVS整体架构

OVS架构图如下,具体每个部件功能不具体分析,本文主要涉及内核部分。

OVS内部结构

OVS接口类型

执行命令ovs-vsctl show 或者 ovs-dpctl show(显示默认datapath),来查看ovs接口信息时,常会看到接口类型,以下对OVS中有哪些接口类型及不同接口类型的接口之间的区别进行分析。

在源码中有这么一个函数:

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
static const char *
get_vport_type(const struct dpif_netlink_vport *vport)
{
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);

switch (vport->type) {
case OVS_VPORT_TYPE_NETDEV: {
const char *type = netdev_get_type_from_name(vport->name);

return type ? type : "system";
}

case OVS_VPORT_TYPE_INTERNAL:
return "internal";

case OVS_VPORT_TYPE_GENEVE:
return "geneve";

case OVS_VPORT_TYPE_GRE:
return "gre";

case OVS_VPORT_TYPE_VXLAN:
return "vxlan";

case OVS_VPORT_TYPE_LISP:
return "lisp";

case OVS_VPORT_TYPE_STT:
return "stt";

case OVS_VPORT_TYPE_UNSPEC:
case __OVS_VPORT_TYPE_MAX:
break;
}

VLOG_WARN_RL(&rl, "dp%d: port `%s' has unsupported type %u",
vport->dp_ifindex, vport->name, (unsigned int) vport->type);
return "unknown";
}

可看出,ovs-vsctl show时的类型显示在内核中都有对应关系。宏定义如下:

1
2
3
4
5
6
7
8
9
10
11
enum ovs_vport_type {
OVS_VPORT_TYPE_UNSPEC,
OVS_VPORT_TYPE_NETDEV, /* network device */
OVS_VPORT_TYPE_INTERNAL, /* network device implemented by datapath */
OVS_VPORT_TYPE_GRE, /* GRE tunnel. */
OVS_VPORT_TYPE_VXLAN, /* VXLAN tunnel. */
OVS_VPORT_TYPE_GENEVE, /* Geneve tunnel. */
OVS_VPORT_TYPE_LISP = 105, /* LISP tunnel */
OVS_VPORT_TYPE_STT = 106, /* STT tunnel */
__OVS_VPORT_TYPE_MAX
};

下面,选取具有代表性的几个类型进行分析。

system

此类vport(ovs-dpctl show未显示类型的接口)是对设备原有接口的封装,内核类型为’OVS_VPORT_TYPE_NETDEV’。定义的vport操作变量为’ovs_netdev_vport_ops’。

1
2
3
4
5
6
static struct vport_ops ovs_netdev_vport_ops = {
.type = OVS_VPORT_TYPE_NETDEV,
.create = netdev_create,
.destroy = netdev_destroy,
.send = dev_queue_xmit,
};

此种接口创建内部比较特殊,因此需要特殊强调。在’netdev_create’中有一段如下代码

1
2
3
4
5
6
7
8
9
10
static struct vport *netdev_create(const struct vport_parms *parms)
{
struct vport *vport;

vport = ovs_vport_alloc(0, &ovs_netdev_vport_ops, parms);
if (IS_ERR(vport))
return vport;

return ovs_netdev_link(vport, parms->name);
}
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
struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops,
const struct vport_parms *parms)
{
struct vport *vport;
size_t alloc_size;
/*vport分配空间*/
alloc_size = sizeof(struct vport);
if (priv_size) {
alloc_size = ALIGN(alloc_size, VPORT_ALIGN);
alloc_size += priv_size;
}

vport = kzalloc(alloc_size, GFP_KERNEL);
if (!vport)
return ERR_PTR(-ENOMEM);
/*初始化*/
vport->dp = parms->dp;
vport->port_no = parms->port_no;
vport->ops = ops;
INIT_HLIST_NODE(&vport->dp_hash_node);

if (ovs_vport_set_upcall_portids(vport, parms->upcall_portids)) {
kfree(vport);
return ERR_PTR(-EINVAL);
}

return vport;
}
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
struct vport *ovs_netdev_link(struct vport *vport, const char *name)
{
int err;
/*此种类型接口是对系统原有口的映射,因此dev赋值为系统原有接口的dev*/
vport->dev = dev_get_by_name(ovs_dp_get_net(vport->dp), name);
if (!vport->dev) {
err = -ENODEV;
goto error_free_vport;
}
/*系统接口为以下情况是报错退出:
- loopback口
- 非ARPHRD_ETHER接口
- OVS_VPORT_TYPE_INTERNAL类型接口
*/
if (vport->dev->flags & IFF_LOOPBACK ||
vport->dev->type != ARPHRD_ETHER ||
ovs_is_internal_dev(vport->dev)) {
err = -EINVAL;
goto error_put;
}
/*所有system都是datapatch 接口的slave接口;
默认只要一个datapath接口(ovs-system),所有system类型的master都为此接口。
*/
rtnl_lock();
err = netdev_master_upper_dev_link(vport->dev,
get_dpdev(vport->dp), NULL, NULL);
if (err)
goto error_unlock;

err = netdev_rx_handler_register(vport->dev, netdev_frame_hook,
vport);
if (err)
goto error_master_upper_dev_unlink;
/*禁用接口的lro功能;*/
dev_disable_lro(vport->dev);
/*开启混杂模式*/
dev_set_promiscuity(vport->dev, 1);
/*设置接口私有类型*/
vport->dev->priv_flags |= IFF_OVS_DATAPATH;
rtnl_unlock();

return vport;
…………
…………
}

‘netdev_rx_handler_register’实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int netdev_rx_handler_register(struct net_device *dev,
rx_handler_func_t *rx_handler,
void *rx_handler_data)
{
ASSERT_RTNL();
if (dev->rx_handler)
return -EBUSY;
/* Note: rx_handler_data must be set before rx_handler */
/*定义dev收包处理私有数据,即 vport指针,此处完成系统dev到vport的对应。
定义接口收包处理函数。*/
rcu_assign_pointer(dev->rx_handler_data, rx_handler_data);
rcu_assign_pointer(dev->rx_handler, rx_handler);

return 0;
}

此类接口定义了 ‘rx_handler’,因此,在CPU报文处理函数’__netif_receive_skb_core’中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
……
rx_handler = rcu_dereference(skb->dev->rx_handler);
……
type = skb->protocol;
list_for_each_entry_rcu(ptype,
&ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {
if (ptype->type == type &&
(ptype->dev == null_or_dev || ptype->dev == skb->dev ||
ptype->dev == orig_dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev);
pt_prev = ptype;
}
}

也就是说此类接口处理报文在协议栈之前,因此netfilter对此类接口不起作用,所以在云环境(openstack)中,需要在虚拟机tap口与虚拟交换机之间增加Linux bridge设备来使报文经过协议栈(netfilter起作用)来实现security group。

internal

OVS内部创建的虚拟网卡接口。每创建一个ovs bridge,OVS会自动创建一个同名接口(Interface)挂载到新创建的bridge上。或者也可以通过type=internal把已经挂载的接口设置为‘internal’类型。

1
2
3
4
# ovs-vsctl add-br ovs-switch
/*OpenFlow 端口编号为 100*/
# ovs-vsctl add-port ovs-switch p0 -- set Interface p0 ofport_request=100
# ovs-vsctl set Interface p0 type=internal

对于 internal 类型的的网络接口,OVS 会同时在 Linux 系统中创建一个可以用来收发数据的模拟网络设备。我们可以为这个网络设备配置 IP 地址、进行数据监听等等。

内核中对’internal’接口的类型定义为’OVS_VPORT_TYPE_INTERNAL’(network device implemented by datapath,datapath实现的网络设备)。定义的vport操作变量为’ovs_internal_vport_ops’。

1
2
3
4
5
6
static struct vport_ops ovs_internal_vport_ops = {
.type = OVS_VPORT_TYPE_INTERNAL,
.create = internal_dev_create,
.destroy = internal_dev_destroy,
.send = internal_dev_recv,
};

接口创建时,调用’internal_dev_create’进行接口初始化。

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
46
47
48
49
50
static struct vport *internal_dev_create(const struct vport_parms *parms)
{
struct vport *vport;
struct internal_dev *internal_dev;
int err;

vport = ovs_vport_alloc(0, &ovs_internal_vport_ops, parms);
if (IS_ERR(vport)) {
err = PTR_ERR(vport);
goto error;
}
/*内核创建接口,初始之后调用do_setup 初始化其他变量*/
vport->dev = alloc_netdev(sizeof(struct internal_dev),
parms->name, NET_NAME_USER, do_setup);
if (!vport->dev) {
err = -ENOMEM;
goto error_free_vport;
}
vport->dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
if (!vport->dev->tstats) {
err = -ENOMEM;
goto error_free_netdev;
}

#ifdef HAVE_IFF_PHONY_HEADROOM
vport->dev->needed_headroom = vport->dp->max_headroom;
#endif
/*设置接口nd_net属性*/
dev_net_set(vport->dev, ovs_dp_get_net(vport->dp));
/*dev私有数据 映射*/
internal_dev = internal_dev_priv(vport->dev);
internal_dev->vport = vport;

/* Restrict bridge port to current netns. */
if (vport->port_no == OVSP_LOCAL)
vport->dev->features |= NETIF_F_NETNS_LOCAL;

rtnl_lock();
err = register_netdevice(vport->dev);
if (err)
goto error_unlock;
/*设置混杂模式*/
dev_set_promiscuity(vport->dev, 1);
rtnl_unlock();
netif_start_queue(vport->dev);

return vport;
…………
…………
}

vxlan

  • ‘ovs-vsctl show’显示的type 为’vxlan’类型,此种接口为ovs虚拟接口。
  • ‘ovs-dpctl show’显示的type 为’vxlan’类型,此种接口是对系统的封装,可看做系统口。

内核中对vxlan类型的接口定义为OVS_VPORT_TYPE_VXLAN。ovs vxlan创建在文件’vport-vxlan.c’中,定义 操作如下

1
2
3
4
5
6
7
8
9
10
static struct vport_ops ovs_vxlan_netdev_vport_ops = {
.type = OVS_VPORT_TYPE_VXLAN,
.create = vxlan_create,
.destroy = ovs_netdev_tunnel_destroy,
.get_options = vxlan_get_options,
#ifndef USE_UPSTREAM_TUNNEL
.fill_metadata_dst = vxlan_fill_metadata_dst,
#endif
.send = vxlan_xmit,
};

vxlan_create定义如下

1
2
3
4
5
6
7
8
9
10
static struct vport *vxlan_create(const struct vport_parms *parms)
{
struct vport *vport;
/*alloc 创建接口*/
vport = vxlan_tnl_create(parms);
if (IS_ERR(vport))
return vport;
/*link设置*/
return ovs_netdev_link(vport, parms->name);
}

‘ovs_netdev_link’ 函数上面已经分析过,值得注意的是,vxlan类型的接口收包函数也是 ‘netdev_frame_hook’ 。

基本上系统口都有master,而master为’ovs-system’。

系统接口master

对于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对原有接口的封装,也包括这类接口。

收包处理

  1. netdev_frame_hook
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Called with rcu_read_lock and bottom-halves disabled. */
static rx_handler_result_t netdev_frame_hook(struct sk_buff **pskb)
{
struct sk_buff *skb = *pskb;

if (unlikely(skb->pkt_type == PACKET_LOOPBACK))
return RX_HANDLER_PASS;
/*USE_UPSTREAM_TUNNEL管理不同的目的处理*/
#ifndef USE_UPSTREAM_TUNNEL
netdev_port_receive(skb, NULL);
#else
netdev_port_receive(skb, skb_tunnel_info(skb));
#endif
return RX_HANDLER_CONSUMED;
}
  1. 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
    23
    void 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);
    }
  2. 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
    32
    int 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;
    }
  3. 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
    46
    void 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);
    }
  4. 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;
    }
  5. do_output

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static 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
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
static netdev_tx_t internal_dev_recv(struct sk_buff *skb)
{
struct net_device *netdev = skb->dev;
struct pcpu_sw_netstats *stats;
/*接口没有管理UP,直接丢包*/
if (unlikely(!(netdev->flags & IFF_UP))) {
kfree_skb(skb);
netdev->stats.rx_dropped++;
return NETDEV_TX_OK;
}
/*初始化dst计数*/
skb_dst_drop(skb);
/*更改某些数据(接口或会话)使用计数*/
nf_reset(skb);
secpath_reset(skb);
/*更改skb接入类型、协议*/
skb->pkt_type = PACKET_HOST;
skb->protocol = eth_type_trans(skb, netdev);
skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);

stats = this_cpu_ptr(netdev->tstats);
u64_stats_update_begin(&stats->syncp);
stats->rx_packets++;
stats->rx_bytes += skb->len;
u64_stats_update_end(&stats->syncp);
/*此函数以前的文章分析过,此处不在重复*/
netif_rx(skb);
return NETDEV_TX_OK;
}

发包处理

netif_rx函数重新进入了本机协议栈的处理,而internal类型的接口没有设置’rx_handler’,因此进入正常协议栈流程,最后会进入正常转发流程。

1
dev_queue_xmit -> dev_hard_start_xmit -> ops->ndo_start_xmit(skb, dev);

而internal在接口创建的时候定义了 ndo_start_xmit。

1
2
3
4
5
6
7
8
9
10
11
static struct vport *internal_dev_create(const struct vport_parms *parms)
{
……
vport->dev = alloc_netdev(sizeof(struct internal_dev),
parms->name, NET_NAME_USER, do_setup);
if (!vport->dev) {
err = -ENOMEM;
goto error_free_vport;
}
……
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static const struct net_device_ops internal_dev_netdev_ops = {
.ndo_open = internal_dev_open,
.ndo_stop = internal_dev_stop,
.ndo_start_xmit = internal_dev_xmit,
.ndo_set_mac_address = eth_mac_addr,
.ndo_change_mtu = internal_dev_change_mtu,
.ndo_get_stats64 = internal_get_stats,
#ifdef HAVE_IFF_PHONY_HEADROOM
.ndo_set_rx_headroom = internal_set_rx_headroom,
#endif
};

static void do_setup(struct net_device *netdev)
{
……
netdev->netdev_ops = &internal_dev_netdev_ops;
……
}

继续走读函数

1
2
3
4
5
6
7
8
9
static int internal_dev_xmit(struct sk_buff *skb, struct net_device *netdev)
{
rcu_read_lock();
/*函数前面已经解析*/
ovs_vport_receive(internal_dev_priv(netdev)->vport, skb);
rcu_read_unlock();
……
return 0;
}

vxlan接口

vxlan接口的 收包处理(netdev_frame_hook) 和 发包处理(vxlan_xmit),在以前的文章已经分析过。

优秀资料

OVS中端口数据包收发流程

Openvswitch原理与代码分析(1):总体架构