
简介《OpenvSwitch源码阅读笔记.pdf》面向刚开始阅读 OVS 源码的网络开发与 SDN 学习者围绕虚拟交换机实现、OpenFlow 流表下发与数据面转发建立从架构到代码的入门路径。压缩包仅含 1 个 PDF 文件大小约 1012KB便于在本地检索和批注内容以 OVS 2.3.90 版本为参照梳理 ovs-vswitchd、ovsdb-server、ofproto、ofproto-dpif、netdev 等组件关系并深入到 datapath 初始化、收包处理、流表哈希桶与流表创建流程解释匹配、动作和 upcall 的协作方式。笔记还结合数据流向图说明 Linux 协议栈与 OVS 转发路径的差异适合作为阅读源码时的对照提纲和排错思路参考。目前已有 977 人学习适合具备一定 Linux 网络基础、希望系统理解 OVS 与 OpenFlow 实现细节的读者。1. 知识地图OVS 源码阅读笔记先抓哪几条主线把 OVS 源码阅读笔记当成一张地图先别急着从main()逐行啃。OVS 同时牵着 SDN 控制面和 OpenFlow 协议面又在内核 datapath 与用户态 vswitchd 之间来回穿梭如果一上来就陷进函数调用很容易把“配置如何落库、流表如何下发、报文如何 upcall”三条线搅在一起。更稳的切入方式是先记住一张四层地图内核 datapath 负责快路径匹配与动作执行ovs-vswitchd 负责配置与编排ofproto/ofproto-dpif 负责把 OpenFlow 语义翻译成 datapath 能执行的规则ovsdb-server 与各类 ovs-* 工具负责配置落库和查询。这份笔记最适合已经懂一点 Linux 网络、准备系统读源码或复习 SDN 交换面的人零基础读者先把网桥、流表、端口这三个词弄明白再回来按模块推进才不会把“配置面”和“数据面”混成一条线。2. datapath 内核路径从收包、建流到 upcall 的复习主线2.1 datapath、vport 与流表的最小模型读内核模块先建立最小模型一个 datapath 可以挂多个 vportvport 类似物理交换机的端口一个 datapath 关联一张流表流表条目由 match/key 和 action 两部分组成。这个模型看起来简单却决定了后面所有调用链的解释方式收包从 vport 进来提取 key 后去流表匹配匹配成功执行 action匹配失败才走 upcall 送到用户态。复习时如果只记函数名不记这个对象关系看到ovs_vport_receive()、ovs_flow_key_extract()、ovs_dp_process_packet()就会散成点把 datapath、vport、flow table、action 四者画成一张对象关系图再往函数上挂记忆会稳很多。这里最容易忽略的是“流表不是用户态独有”的错觉。内核 datapath 维护的是快路径流表用户态 ofproto 层维护的是 OpenFlow 规则与翻译结果两边通过 netlink 和 upcall 交互但职责不同。复习 datapath 时要把“内核流表条目”和“OpenFlow 流表项”分开看前者关注 key 与 action 的可执行形式后者关注 match 字段、instruction 和 action 集合的协议语义。把这两个层次分开后面看ovs_flow_cmd_new()和 flow_mod 解析才不会互相打架。2.2 收包到 ovs_dp_process_packet 的调用链收包主线可以先用一段伪调用链固定顺序再回到源码补参数和错误分支。下面这段只保留复习主线省略了参数、锁和错误处理目的是让大脑先记住“谁调用谁”而不是一上来被复杂入参拖住。/* 调用链仅用于复习主线省略参数和错误分支 */ netdev_frame_hook(...) - netdev_port_receive(...) - ovs_vport_receive(...) - ovs_flow_key_extract(...) /* 从 skb 提取匹配 key */ - ovs_dp_process_packet(...) /* 进入流表匹配 */这段链里netdev_frame_hook是 vport 注册的回调入口skb是内核网络报文ovs_flow_key_extract()负责把报文中的以太网头、IP 头、传输层字段等提取成sw_flow_keyovs_dp_process_packet()才是真正进入 OVS 数据包处理的地方。参数不需要在第一遍背全但要知道 key 的生成发生在匹配之前action 的执行发生在匹配之后如果 key 提取错后面流表再正确也匹配不上。复习时可以把“收包入口、key 提取、流表处理”三段分别标在调用链旁边看到任何函数都先问它属于哪一段。再往下看流表匹配成功就执行 action匹配失败就通过 upcall 送用户态。这个分界是 datapath 复习的重点快路径追求少切换慢路径负责把未命中报文交给用户态决策。很多初学者会把 upcall 当成异常路径其实它是 OVS 学习流表、下发流表的关键机制没有 upcall控制器和用户态就没有机会补规则。把“命中执行 action、未命中 upcall”当成一条固定判断再去看ovs_dp_upcall()和queue_userspace_packet()的构造过程思路会清楚很多。2.3 流表哈希桶与 sw_flow_key 的匹配逻辑流表用哈希桶组织笔记里提到alloc_buckets()初始化、hlist_head、flex_array、reciprocal_elems等结构。复习时不必一上来背所有字段但要知道哈希桶解决的是“在大量流表条目里快速定位候选”的问题真正匹配仍然要拿 key 和 mask 做比较。sw_flow_key里的in_port、skb_mark、recirc_id、priority、以太网字段、IPv4/IPv6 字段、传输层端口等都是匹配域的一部分字段越多key 提取和 mask 比较越要小心。重点难点在 mask 缓存和分段查询。笔记里提到先查mask_cache_entry查询成功后形成 cache并采用分段查询方式加速。复习时要抓住“缓存的是 mask 查询结果不是最终 action”这个点缓存能减少重复找 mask 的开销但最终仍要确认 key 与 mask 的匹配关系。把flow_lookup()、masked_flow_lookup()的职责分开看一个负责整体查找流程一个负责带 mask 的比较才不会把缓存命中当成流表命中。提示复习哈希桶时先画“桶数组—元素空间—parts 数组”的层级再回到字段名比直接背结构体有效。2.4 action 执行与 upcall 的分界action 处理入口是do_execute_actions()笔记列了OVS_ACTION_ATTR_OUTPUT、OVS_ACTION_ATTR_USERSPACE、OVS_ACTION_ATTR_HASH、OVS_ACTION_ATTR_PUSH_VLAN、OVS_ACTION_ATTR_POP_VLAN、OVS_ACTION_ATTR_RECIRC、OVS_ACTION_ATTR_SET、OVS_ACTION_ATTR_SAMPLE等类型。复习时不要只记名字要把 action 分成四类转发类OUTPUT、USERSPACE、报文修改类PUSH_VLAN、POP_VLAN、SET、元数据类HASH、控制类RECIRC、SAMPLE。分类之后再看每个分支调用的函数记忆负担会小很多。upcall 的数据格式也值得单独过一遍netlink 消息头、genlmsghdr、ovs_header再加上OVS_PACKET_ATTR_KEY、OVS_PACKET_ATTR_USERDATA、OVS_PACKET_ATTR_PACKET等属性key 部分对应sw_flow_key字段原始数据包部分用skb_zerocopy()拷贝。复习时要问三个问题这条 upcall 为什么发生带了哪些匹配信息用户态拿到后如何转成 flow 并继续分类。把这三个问题答顺datapath 到用户态的桥就算搭起来了。3. vswitchd 与 ofproto配置同步、对象族和 udpif 线程3.1 vswitchd 主循环daemon、数据库 IDL 与 bridge_runovs-vswitchd 是用户态守护进程主循环里同时处理数据库配置、OpenFlow 连接、ofproto 运行和 netdev 状态更新。笔记中的主线包括daemonize_start()、unixctl_server_create()、bridge_init()、ovsdb_idl_create()、bridge_run()、poll_block()等。复习时可以把主循环拆成“初始化—同步配置—运行 ofproto—处理事件—阻塞等待”五段初始化阶段建立数据库 IDL、桥模型和 unixctl同步配置阶段由bridge_reconfigure()把 ovsdb 中的网桥、端口、控制器配置落到 ofproto运行阶段调用ofproto_run()处理 OpenFlow 消息事件阶段处理 unixctl 命令和 netdev 状态最后poll_block()等待事件或超时。重点在于“配置不是直接写内核”。ovs-vsctl改的是 ovsdb-server 中的配置vswitchd 通过 IDL 感知变化再经bridge_reconfigure()同步到 ofproto最后才通过 dpif 和 netlink 落到内核 datapath。复习时如果能把这条链按“工具—数据库—vswitchd—ofproto—dpif—datapath”排出来很多对象就不会乱。下面这段命令只示意工具与对象的对应关系不依赖具体环境br0和eth0都是示意名。# 只示意工具与对象的对应关系不依赖具体环境 ovs-vsctl add-br br0 # 配置写入 ovsdb-server ovs-vsctl add-port br0 eth0 # 端口对象进入配置树 ovs-ofctl dump-flows br0 # 通过 OpenFlow 侧查看流表ovs-vsctl走的是数据库配置面ovs-ofctl走的是 OpenFlow 协议面前者改变 vswitch 配置后者查询或修改流表。参数上要知道网桥名和端口名只是本地标识真正落到 datapath 还要经过 ofproto 端口创建和 dpif 端口添加。复习时把“配置面”和“协议面”分开看到bridge_reconfigure()就不会误以为它在直接处理每一个数据包。3.2 ofproto 对象族ofproto、ofport、ofrule、ofgroupofproto 层是 OpenFlow 接口层核心对象可以整理成一张表。复习时先记对象职责再记结构体字段比反过来轻松。对象职责复习抓手ofproto代表一个 OpenFlow 交换机实现关联 ofproto_class、oftable、connmgrofport代表一个 OpenFlow 端口关联 netdev 设备和 dpif 端口ofrule代表一条 OpenFlow 规则包含 match、priority、actionsofgroup代表一组 action 组合OpenFlow 1.1 的 group 语义ofproto是整体结构ofproto_class指向具体实现笔记里对应ofproto_dpif_classofport把 OpenFlow 端口和 netdev 设备连起来ofrule里放规则和动作ofgroup处理行为组合。复习时最容易漏的是“接口对象”和“实现对象”的成对关系ofproto对ofproto_dpifofport对ofport_dpifofrule对rule_dpifofgroup对group_dpif。把这四对关系写在一张纸上再看ofproto_class的回调就不会把抽象层和实现层混在一起。重点难点在创建流程。bridge_init_ofproto()先做 ofproto 初始化bridge_reconfigure()触发ofproto_create()找到ofproto_dpif_class后依次走alloc()、construct()、init_ports()其中construct()里会打开 dpif_backer创建 udpif初始化 ofproto 表再进入 ofport 的打开和安装。复习时可以把创建流程拆成“找类—分配对象—构造对象—打开 datapath—初始化表—打开端口—安装端口”七步每一步问“创建的是抽象对象还是实现对象”思路会清晰。3.3 ofproto 创建流程与 dpif_backerofproto_create()的主线里ofproto_class_find__()找到具体类alloc()分配ofproto_dpif对象其中包含ofprotoconstruct()回调初始化ofproto_dpif再通过open_dpif_backer()打开 datapath。dpif_backer里挂着udpif和dpifdpif的具体实现可能是dpif_netlink_class或dpif_netdev_class。复习时要把dpif_backer当成“用户态和内核 datapath 之间的桥”它既持有 dpif又持有 udpif很多 upcall 线程和流表操作都从这里找到入口。ofproto_init_tables()和add_internal_flows()负责初始化 ofproto 表并添加内部流init_ports()走到ofport_open()、netdev_open()以 system 类型为例会进入netdev_linux_alloc()、netdev_linux_construct()最后ofport_install()创建ofport_dpif并完成端口构造。复习时不要把这些函数当成平铺清单而要按“datapath 先打开端口后安装”的顺序理解没有 dpif_backer端口安装没有落点没有 netdevofport 也没有设备可关联。注意ofproto创建和ofport创建不是同一层前者是交换机整体后者是端口对象复习时分开记。3.4 udpif 多线程与 upcall 处理udpif 负责用多个线程处理内核送来的 upcall。udpif_set_threads()进入udpif_start_threads()分配 handlers 空间再通过ovs_thread_create()创建n_handlers个线程回调是udpif_upcall_handler()。每个线程里recv_upcalls()调dpif_recv()再走dpif_netlink_recv()、epoll_wait()、nl_sock_recv()收数据parse_odp_packet()把 ofpbuf 转成dpif_upcallodp_flow_key_to_flow()把 key 转成 flow最后process_upcall()做分类。重点在classify_upcall()和upcall_xlate()MISS_UPCALL进入xlate_in_init()、xlate_actions()生成 dp 需要的xlate_outrule_dpif_lookup()查规则rule_dpif_get_actions()取动作do_xlate_actions()执行不同 action 的翻译。若fail_open使能会调用ofproto_dpif_send_packet_in()把 packet-in 发给控制器若存在慢路径compose_slow_path()会增加慢路径 action如果是DPIF_UC_MISSukey_create_from_upcall()创建udpif_key。复习时把“收 upcall—转 flow—查规则—翻译动作—决定 packet-in 或慢路径”当成一条链比单记线程数更有用。4. OpenFlow 连接与消息ofconn、ofservice、flow_mod 解析重点4.1 主动连接 ofconn 与被动监听 ofserviceOpenFlow 连接处理在bridge_configure_remotes()里展开核心对象是ofconn和ofservice。ofconn作为客户端主动连接远端控制器ofservice作为服务器提供被动监听。笔记里的主动连接链是connmgr_set_controllers()、add_controller()、ofconn_create()、rconn_connect()、reconnect()、vconn_open()再往下到vconn_stream_open()、stream_open()、tcp_open()、inet_open_active()、socket()、connect()。被动监听链是ofservice_create()、pvconn_open()、pvconn_pstream_listen()再走pstream_open()、punix_open()、make_unix_socket()、inet_open_passive()、bind_unix_socket()、listen()。复习时抓住两条线的差异主动连接有重连逻辑被动监听有接受连接逻辑。connmgr统一管理all_conns和controllersofconn里挂rconnrconn再挂vconnvconn下面是streamstream下面是stream_fd和具体stream_class。这串对象看起来长但按“管理器—连接—虚拟连接—流—文件描述符”分层就能把 TCP 和 Unix 两种路径统一起来。tcp_vconn_class、unix_vconn_class、tcp_stream_class、punix_pstream_class只是不同层的实现类复习时先认层再认类。4.2 flow_mod 的四段结构flow_mod 是 OpenFlow 流表项处理里最重要的一类消息。笔记把它拆成四部分OpenFlow 头部、flow_mod 固定字段、match 字段和 instruction 字段。match 可以是OFPMT_STANDARD或OFPMT_OXM可以包含多个 OXMinstruction 可以包含多个 action也可以没有。复习时用一张表把四段固定下来比抓包后临时猜字段更稳。消息段作用易错点OpenFlow 头部标识消息类型和长度长度字段与后续 TLV 边界要一致flow_mod 固定字段表 ID、优先级、超时等表 ID 和优先级影响匹配顺序match 字段描述匹配域OXM 的 class、field、mask 要合法instruction 字段描述动作和跳转instruction 类型与 action 解码要对应复习 flow_mod 时不要只背字段名要记住解析入口handle_openflow()是 OpenFlow 消息处理入口match 解析走ofputil_pull_ofp11_match()核心是nx_pull_raw()instruction 解析走ofpacts_pull_openflow_instructions()其中decode_openflow11_instructions()把不同 instruction 放入ofp11_instruction *insts[N_OVS_INSTRUCTIONS]数组。N_OVS_INSTRUCTIONS由OVS_INSTRUCTIONS推导笔记里对应值为 6。这个数字不是死记点重点是知道 instruction 类型和后续处理函数有一一对应关系。4.3 match 字段OXM TLV 与合法性检查match 的 OXM 是 TLV 格式头里有 class、field、length、hasmask 等信息后面跟 value可能还跟 mask。解析函数nx_pull_match_entry()先在all_nxm_fields里找 header 相等的nxm_field再根据mf_field_id找到mf_field然后做四类检查mf_are_prereqs_ok()检查依赖条件mf_is_all_wild()检查重复性mf_is_mask_valid()和mf_is_value_valid()检查 mask 与 value 的合法性最后mf_set()根据 value 和 mask 设置 match。复习时要把“依赖检查、重复检查、有效性检查、写入 match”四步按顺序记因为顺序错了就容易把非法字段放进去。重点难点是 prerequisites。比如某些字段只有在特定协议层存在时才有意义mf_are_prereqs_ok()就是防止在错误的 flow 条件下匹配不该出现的字段。复习时不要只看 value 对不对还要问“这个字段在当前 match 上下文中允许出现吗”。另一个易漏点是 mask 与 value 的关系mf_is_mask_valid()和mf_is_value_valid()分别检查不同合法性不能用一个检查代替另一个。把这两类检查分开再看nx_pull_entry__()的整体流程match 解析就不会只剩字段名。4.4 instruction 与 action 的解码instruction 解码的核心是OVS_INSTRUCTIONS宏展开后形成的类型与处理函数对应关系。笔记里列出OVSINST_OFPIT13_METER、OVSINST_OFPIT11_APPLY_ACTIONS、OVSINST_OFPIT11_CLEAR_ACTIONS、OVSINST_OFPIT11_WRITE_ACTIONS、OVSINST_OFPIT11_WRITE_METADATA、OVSINST_OFPIT11_GOTO_TABLE等类型对应ofpact_put_METER()、ofpacts_decode()、ofpact_put_CLEAR_ACTIONS()、ofpacts_decode_for_action_set()、ofpact_put_WRITE_METADATA()、ofpact_put_GOTO_TABLE()等处理。复习时把 instruction 分成“动作类、元数据类、跳转类、计量类”四组再看每组落到 ofpact 的方式会比逐条背宏轻松。action 解码和 instruction 解码要连起来看APPLY_ACTIONS和WRITE_ACTIONS都涉及 action 集合但语义不同CLEAR_ACTIONS是清空WRITE_METADATA是写元数据GOTO_TABLE是跳表。复习时最容易漏的是“instruction 可以没有 action也可以包含多个 action”所以解析代码不能假设 action 一定存在。把“instruction 类型—是否带 action—落到哪个 ofpact 构造函数”做成三列表复习时一眼就能看出边界。5. 易错点复盘datapath、ofproto 与 OpenFlow 解析里最容易翻车的五处5.1 路径类易错快路径与慢路径混在一起易漏点 → 把 datapath 的流表匹配和用户态 ofproto 的 OpenFlow 规则当成同一张表。为什么漏 → 两边都叫“流表”函数名里也都有 flow复习时很容易把ovs_flow_cmd_new()和 flow_mod 解析混成一条线。怎么补 → 先在纸上写“内核快路径表”和“用户态 OpenFlow 规则表”两个框再把ovs_dp_process_packet()、ovs_dp_upcall()放左边把handle_openflow()、ofproto_run()放右边中间只留 netlink 和 upcall 两条箭头。每次看到 flow 相关函数先问它在哪一边再问它处理的是 key/action 还是 match/instruction。易漏点 → 把 upcall 当成错误分支。为什么漏 → 命中流表时路径很短未命中才进 upcall初学者容易把 upcall 归到异常处理。怎么补 → 把 upcall 改叫“补规则入口”在笔记里标出MISS_UPCALL、xlate_actions()、ukey_create_from_upcall()三个点说明未命中报文如何进入用户态、如何查规则、如何生成 udpif_key。这样再看queue_userspace_packet()的报文构造就不会觉得它只是错误上报。5.2 对象类易错配置对象与实现对象错位易漏点 → 把ofproto和ofproto_dpif当成同一个对象。为什么漏 → 结构体里有struct ofproto up这种嵌入关系代码里又经常用ofproto指针访问复习时容易忽略具体实现。怎么补 → 记住四对关系ofproto对ofproto_dpifofport对ofport_dpifofrule对rule_dpifofgroup对group_dpif。在创建流程里标出alloc()分配的是实现对象construct()初始化的是实现对象ofproto_class是回调入口。易漏点 → 把ovs-vsctl的配置修改当成直接改内核 datapath。为什么漏 → 命令行执行后很快能看到网桥和端口容易误以为工具直接操作了数据面。怎么补 → 把链路写成“ovs-vsctl—ovsdb-server—vswitchd IDL—bridge_reconfigure—ofproto—dpif—netlink—datapath”并在每一步旁边写“改的是配置、对象还是内核端口”。复习bridge_reconfigure()时重点看它如何同步 cfg如何调用ofproto_create()、bridge_add_ports()、iface_create()、dpif_port_add()这样就不会把配置面和数据面压成一层。5.3 协议类易错match 与 instruction 的边界易漏点 → 只检查 match 的 value不检查 prerequisites。为什么漏 → OXM 字段看起来就是“类型 长度 值”初学者容易直接取值比较忽略字段依赖的协议层条件。怎么补 → 在nx_pull_match_entry()旁边标四步检查mf_are_prereqs_ok()、mf_is_all_wild()、mf_is_mask_valid()/mf_is_value_valid()、mf_set()。每读一个字段先问“当前 flow 上下文允许它出现吗”再问“mask 和 value 是否合法”最后才看写入结果。易漏点 → 把 instruction 当成 action 列表。为什么漏 →APPLY_ACTIONS和WRITE_ACTIONS里确实带 action复习时容易忽略 instruction 还有GOTO_TABLE、WRITE_METADATA、CLEAR_ACTIONS、METER等类型。怎么补 → 用OVS_INSTRUCTIONS宏展开后的六种类型做一张对照表左边写 instruction 类型中间写是否带 action右边写落到哪个ofpact_put_*或ofpacts_decode*函数。每次解析 flow_mod先分解出 instruction 数组再分别处理不要从 action 反推 instruction。5.4 线程类易错udpif 线程数与 upcall 处理混淆易漏点 → 把udpif_set_threads()当成 upcall 处理本身。为什么漏 → 函数名里有 threads复习时容易只记“创建 n_handlers 个线程”忽略线程里真正跑的是udpif_upcall_handler()和recv_upcalls()。怎么补 → 把 udpif 拆成“线程创建”和“upcall 处理”两段创建段记udpif_start_threads()、ovs_thread_create()处理段记dpif_recv()、parse_odp_packet()、odp_flow_key_to_flow()、upcall_receive()、process_upcall()、classify_upcall()、upcall_xlate()。线程数只是并发度处理链才是复习重点。易漏点 → 忽略fail_open和慢路径动作。为什么漏 → 正常路径看多了容易只记xlate_actions()生成xlate_out忘记还有 packet-in 和慢路径。怎么补 → 在upcall_xlate()旁边写三个分支fail_open使能时发ofproto_packet_in存在慢路径时compose_slow_path()增加 actionDPIF_UC_MISS时ukey_create_from_upcall()创建 udpif_key。把这三个分支当成固定检查项复习 upcall 时逐个确认。6. 复习清单按调用链、对象表和协议字段做三轮查漏第一轮只画调用链不抠参数。datapath 画“收包—key 提取—流表处理—action/upcall”vswitchd 画“初始化—IDL 同步—bridge_reconfigure—ofproto_run—poll_block”ofproto 画“找类—alloc—construct—open_dpif_backer—init_ports—ofport_install”OpenFlow 画“ofconn/ofservice—rconn/vconn—stream—fd”。每画完一条链用一句话说出它的起点和终点说不出来就回到笔记对应章节补箭头不要急着看结构体字段。第二轮补对象表把ofproto、ofport、ofrule、ofgroup和它们的实现对象成对写出来再把dpif_backer、udpif、dpif三者的关系补上。第三轮只攻协议字段拿 flow_mod 的四段结构做检查表match 看 prerequisites、mask 合法性、重复性instruction 看六种类型和 action 解码分支。轮次动作检查点产出第一轮画调用链起点、终点、分界点四张主线图第二轮列对象表抽象对象与实现对象成对一张对象关系表第三轮查协议字段match 四步检查、instruction 六类一张 flow_mod 检查表查漏时优先看三类地方一是函数名相近但层次不同的比如ovs_flow_cmd_new()和 flow_mod 解析二是对象名相近但职责不同的比如ofproto和ofproto_dpif三是路径名相近但方向不同的比如主动连接和被动监听。每找到一处就在笔记旁边写“它属于哪条链、哪一层、哪一侧”写不出来就说明还没串起来。复习时间紧的时候先保证三条链能口述收包到 upcall、配置到端口安装、flow_mod 到 instruction 解码这三条顺了细节再补也不会散。从那以后我每次读 OVS 源码都先画调用链再开编辑器看到新函数先问它挂在哪条链上实在串不起来就退回对象表重画一遍这个习惯帮我少走了很多“只看懂局部、整体还是糊”的弯路。希望帮到你。需要对照原笔记的读者按获取入口自取即可把它当成路线图和查漏表来用比从头逐行硬啃更稳。本文还有配套的精品资源点击获取