Open vSwitch 中的 vswitchd 事件上报

 一、数据包转发流程与 vswitchd 事件上报

        Open vSwitch 的数据包转发流程如下图所示:

        在数据包的转发流程中,提到过慢速路径的概念:即当数据包在内核空间无法完全处理时,会产生 upcall 调用,将数据包从内核空间转发到用户空间进行处理,对应上图中的步骤(1→2→3→4→5→6→7)。

        这里图中的步骤(3)对应的是 vswitchd 守护进程与控制器的通信过程,可以细分为两个部分:vswitchd 事件上报和 OpenFlow 流表下发。所谓的 vswitchd 事件上报指的是 vswitchd 守护进程也无法完全处理数据包,需要将信息上报给控制器进行决策;所谓的 OpenFlow 流表下发指的是控制器在收到  vswitchd 上报的信息之后,将相应流表传输到 vswitchd 守护进程的过程。

Tips:对于 OpenFlow 流表下发这个过程而言,Open vSwitch 或者说是整个领域有一套广泛约定的流程,即 OpenFlow 协议,这里不做展开。本文重点关注 vswitchd 事件上报的过程。

二、vswitchd 事件上报的应用场景

        按照数据包的处理流程,当 Datapath 模块无法完全处理数据包时,会产生 upcall 调用,将数据包交给 vswitchd 守护进程。于是这里就存在一个问题:

vswitchd 守护进程在收到数据包后一定会上报给控制器吗?

答:不一定。原因如下:

        在 Open vSwitch 守护进程的 upcall 处理 和 Open vSwitch 中 upcall 消息的类型 这两篇文章中详细讨论过:

  • 对于 upcall 消息而言,只有 MISS 和 ACTION 这两种主要类型,而其他类型都是这两种基本类型的细化。
  • 对于不同的 upcall 类型,vswitchd 守护进程中的 upcall 消息处理函数 process_upcall() 会执行不同的处理逻辑。

        其中,当内核空间的 Datapath 模块匹配不到流表项时,将会产生 MISS 类型的 upcall 消息,在处理过程中相应地调用 upcall_xlate(),并不涉及和控制器通信的部分。

        当消息类型为 CONTROLLER_UPCALL 类型时(注意这是 ACTION 类型的细化),则 vswitchd 守护进程会与控制器进行通信。

        

Tips:细心的你可能早就注意到了,在数据包转发的流程图中,所有步骤的数字,只有第三步是带括号的,而其他步骤是不带括号的。这就是因为并不是所有的 upcall 调用都会产生 vswitchd 守护进程与控制器的通信,而是根据需要或者说是设定的规则来进行的。

三、vswitchd 事件上报机制实现

        在 Open vSwitch 守护进程的 upcall 处理 中讨论过 process_upcall() 对于 CONTROLLER_UPCALL 类型 upcall 消息的处理:

    case CONTROLLER_UPCALL:
        {
            struct user_action_cookie *cookie = &upcall->cookie;

            ......

            const struct frozen_state *state = &recirc_node->state;

            struct ofproto_async_msg *am = xmalloc(sizeof *am);
            *am = (struct ofproto_async_msg) {
                .controller_id = cookie->controller.controller_id,
                .oam = OAM_PACKET_IN,
                .pin = {
                    .up = {
                        .base = {
                            .packet = xmemdup(dp_packet_data(packet), dp_packet_size(packet)),
                            .packet_len = dp_packet_size(packet),
                            .reason = cookie->controller.reason,
                            .table_id = state->table_id,
                            .cookie = get_32aligned_be64(&cookie->controller.rule_cookie),
                            .userdata = (recirc_node->state.userdata_len ? xmemdup(recirc_node->state.userdata, recirc_node->state.userdata_len) : NULL),
                            .userdata_len = recirc_node->state.userdata_len,
                        },
                    },
                    .max_len = cookie->controller.max_len,
                },
            };

            if (cookie->controller.continuation) {
                am->pin.up.stack = (state->stack_size ? xmemdup(state->stack, state->stack_size) : NULL),
                am->pin.up.stack_size = state->stack_size,
                am->pin.up.mirrors = state->mirrors,
                am->pin.up.conntracked = state->conntracked,
                am->pin.up.actions = (state->ofpacts_len ? xmemdup(state->ofpacts, state->ofpacts_len) : NULL),
                am->pin.up.actions_len = state->ofpacts_len,
                am->pin.up.action_set = (state->action_set_len ? xmemdup(state->action_set, state->action_set_len) : NULL),
                am->pin.up.action_set_len = state->action_set_len,
                am->pin.up.bridge = upcall->ofproto->uuid;
                am->pin.up.odp_port = upcall->packet->md.in_port.odp_port;
            }

            ......

            ofproto_dpif_send_async_msg(upcall->ofproto, am);
        }
        break;

        这段代码主要用于构造 ofproto_async_msg 信息,并通过 ofproto_dpif_send_async_msg(upcall->ofproto, am) 函数将构造好的 ofproto_async_msg 消息发送给控制器。其中 ofproto_dpif_send_async_msg() 函数存储在 ovs-main/ofproto/ofproto-dpif.c 文件中:

/* Appends 'am' to the queue of asynchronous messages to be sent to the controller. 
 * Takes ownership of 'am' and any data it points to. */
void ofproto_dpif_send_async_msg(struct ofproto_dpif *ofproto, struct ofproto_async_msg *am) {
    if (!guarded_list_push_back(&ofproto->ams, &am->list_node, 1024)) {
        COVERAGE_INC(packet_in_overflow);
        ofproto_async_msg_free(am);
    }

    /* Wakes up main thread for packet-in I/O. */
    seq_change(ofproto->ams_seq);
}

        通过观察可以发现,这个函数主要用于向异步消息队列中添加消息,并唤醒主线程进行处理。函数首先使用 guarded_list_push_back() 函数将 am->list_node 添加到 ofproto->ams 中,相应的 guarded_list_push_back() 函数存储在 ovs-main/lib/guarded-list.c 文件中:

/* If 'list' has fewer than 'max' elements, adds 'node' at the end of the list and returns the number of elements now on the list.
 * If 'list' already has at least 'max' elements, returns 0 without modifying the list. */
size_t guarded_list_push_back(struct guarded_list *list, struct ovs_list *node, size_t max) {
    size_t retval = 0;

    ovs_mutex_lock(&list->mutex);
    if (list->n < max) {
        ovs_list_push_back(&list->list, node);
        retval = ++list->n;
    }
    ovs_mutex_unlock(&list->mutex);

    return retval;
}

        如果队列已满,则使用 COVERAGE_INC(packet_in_overflow) 维护溢出统计信息,并通过 ofproto_async_msg_free(am) 释放相应资源,相应的 ofproto_async_msg_free() 函数存储在 ovs-main/ofproto/connmgr.c 文件中:

void ofproto_async_msg_free(struct ofproto_async_msg *am) {
    free(am->pin.up.base.packet);
    free(am->pin.up.base.userdata);
    free(am->pin.up.stack);
    free(am->pin.up.actions);
    free(am->pin.up.action_set);
    free(am);
}

        在进行接收后(无论成功与否),都会通过 seq_change(ofproto->ams_seq) 唤醒主线程进行数据包处理,相应的 seq_change() 函数存储在 ovs-main/lib/seq.c 文件中:

/* Increments 'seq''s sequence number, waking up any threads that are waiting on 'seq'. */
void seq_change(struct seq *seq)
    OVS_EXCLUDED(seq_mutex) {
    ovs_mutex_lock(&seq_mutex);
    seq_change_protected(seq);
    ovs_mutex_unlock(&seq_mutex);
}

Tips:通过上面的讨论,可以发现交换机和控制器的通信过程其实是在 ofproto 层实现的。

总结:

        最后列举一些交换机不需要走控制器的场景:

  • DHCP、DNS 等
  • upcall 调用类型为 sflow 时走 sflow 相应的流程
  • upcall 调用类型为 ipfix 时走 ipfix 相应的流程

总之就是说 vswitchd 事件上报不是数据包转发流程(或者慢速路径)中的必要过程

        由于本人水平有限,以上内容如有不足之处欢迎大家指正(评论区/私信均可)。

参考资料:

Open vSwitch 官网

Open vSwitch 源代码 GitHub

2015 FOSDEM - OVS Stateful Services

Open vSwitch 守护进程的 upcall 处理-CSDN博客

Open vSwitch 中 upcall 消息的类型-CSDN博客

Open vSwitch v2.17.10 LTS 源代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值