一、数据包转发流程与 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 事件上报不是数据包转发流程(或者慢速路径)中的必要过程。
由于本人水平有限,以上内容如有不足之处欢迎大家指正(评论区/私信均可)。
参考资料:
2015 FOSDEM - OVS Stateful Services
Open vSwitch 守护进程的 upcall 处理-CSDN博客