OVS数据平面源码解析:从ovs_dp_process_packet到do_output的完整链路

发布时间:2026/10/24 22:17:07

OVS数据平面源码解析:从ovs_dp_process_packet到do_output的完整链路 简介本资源是一份面向SDN与网络虚拟化初学者的Open vSwitch源码深度解析笔记聚焦OVS核心机制与OpenFlow协议实现原理帮助读者突破用户态与内核态协同工作的理解瓶颈。文档基于OVS 2.3.90版本源码系统梳理了OVS网络架构ovs-vswitchd、ovsdb-server等六大组件职责、内部模块分层ofproto抽象层、ofproto-dpif实现层、netdev设备抽象、以及datapath内核模块的关键流程——包括模块初始化、vport收包路径、flow key提取、流表哈希桶结构设计与upcall机制。全书共30页PDF内容含完整数据流向图示、关键函数调用链分析及核心数据结构如sw_flow、flow_table、mask_array的组织说明技术细节扎实注释清晰。资源为单文件PDF大小1012KB结构紧凑便于精读。目前已有977人学习下载是理解OVS如何支撑SDN控制器动态编程、实现虚拟交换功能的优质入门指南。1. 这不是一份“源码注释合集”而是一份能让你在三天内看懂 OVS 数据平面核心链路的实战笔记你有没有试过打开datapath/目录看到flow_table.c、actions.c、upcall.c一堆文件点开第一行#include linux/module.h就卡住不是不会编译是根本不知道哪段代码真正决定了「这个包该转发还是上送」——更别说搞清ovs_flow_key_extract()为什么要把skb-mark和tun_key都塞进sw_flow_key或者mask_cache_entry的分段查询到底省了多少 cycles。这份《Open vSwitch 源码阅读笔记2.3.90》就是为这种状态写的。它不讲 OpenFlow 协议字段定义不列所有ovs-ofctl命令也不堆砌模块图。它只做一件事用真实函数调用链 关键数据结构现场还原 内核态/用户态协同视角把「一个报文从网卡进来到最终被do_output()发出去」这条主干路径一帧一帧拆给你看。适合刚跑通ovs-vsctl add-br br0 ovs-ofctl add-flow br0 in_port1,actionsoutput:2、但对背后dpif_netlink_operate()怎么把 action 翻译成 netlink 消息完全没概念的开发者。如果你正卡在「知道 OVS 要做什么但不知道它正在哪行代码里做」这份笔记就是你的调试器断点清单。2. 核心模块拆解从ovs_dp_process_packet()到do_output()的完整数据平面链路OVS 的数据平面datapath本质是一条高度定制化的 Linux 内核收包流水线。它不走ip_rcv()而是通过vport注册的netdev_frame_hook拦截报文全程绕过协议栈。要真正理解它必须抓住三个锚点key 提取 → 流表匹配 → action 执行。下面我们就沿着ovs_dp_process_packet()这个入口逐层下沉。2.1 收包起点ovs_dp_process_packet()如何接管 skb当报文到达vport比如systemeth0内核会触发netdev_frame_hook()回调最终进入ovs_dp_process_packet()。这个函数是整个 datapath 的心脏起搏器它的签名和关键逻辑如下// datapath/datapath.c void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key) { struct sw_flow *flow; struct dp_upcall_info upcall; /* Step 1: 提取 flow key —— 这是匹配的唯一依据 */ if (unlikely(ovs_flow_key_extract(skb, key))) { kfree_skb(skb); return; } /* Step 2: 在流表中查找匹配项 */ flow ovs_flow_tbl_lookup_stats(dp-table, key, key-hash); if (unlikely(!flow)) { /* 匹配失败构造 upcall 上送用户态 */ ovs_dp_upcall(dp, skb, key, upcall); consume_skb(skb); return; } /* Step 3: 匹配成功执行关联的 action 链 */ ovs_execute_actions(dp, skb, flow-sf_acts, key); }逻辑说明与参数说明skb原始网络报文缓冲区此时已脱离net_device的常规收包路径key输出参数ovs_flow_key_extract()会填充其所有字段见后文sw_flow_key结构这是后续哈希和匹配的唯一输入key-hashkey的哈希值由jhash2()计算用于快速定位哈希桶flow-sf_acts指向struct sw_flow_actions存储了OVS_ACTION_ATTR_OUTPUT等动作的二进制序列不是字符串也不是结构体数组而是紧凑的nlattr编码块。这个函数的精妙在于它把“查表”和“执行”彻底分离。匹配失败直接upcall绝不阻塞匹配成功则无条件执行ovs_execute_actions()把 action 解析和执行的复杂度交给下游。这正是 OVS 高性能的关键设计哲学——内核态只做确定性、低开销操作所有策略决策和复杂逻辑下沉到用户态。2.2 Key 提取ovs_flow_key_extract()如何构建 17 字段的匹配骨架sw_flow_key是 OVS 的灵魂数据结构它定义了“什么才算同一个流”。ovs_flow_key_extract()就是那个把千变万化的skb映射成标准化key的翻译官。我们来看它填充的核心字段基于 2.3.90 版本// datapath/flow.h struct sw_flow_key { struct { u16 in_port; /* 入端口 ID如 1,2,... */ u32 priority; /* 流表优先级影响匹配顺序 */ u32 skb_mark; /* skb-mark常用于标记策略路由 */ u32 recirc_id; /* Recirculation ID用于多阶段处理 */ u8 tun_opts[255]; /* 隧道选项如 VXLAN/GRE 头部 */ u8 tun_opts_len; struct ovs_key_ipv4_tunnel tun_key; /* 隧道元数据tun_id, ipv4_src/dst 等 */ } phy; /* 物理层信息 */ struct { u8 src[ETH_ALEN]; /* MAC SA */ u8 dst[ETH_ALEN]; /* MAC DA */ u16 tci; /* VLAN TCI (priority DEI VID) */ u16 type; /* ETH_P_IP, ETH_P_IPV6, etc. */ } eth; /* 以太网层 */ union { struct { u8 proto; /* IP protocol: IPPROTO_TCP, etc. */ u8 tos; /* IPv4 TOS */ u8 ttl; /* IPv4 TTL */ u8 frag; /* IPv4 fragment flags */ __be32 src; /* IPv4 saddr */ __be32 dst; /* IPv4 daddr */ } ip; struct { u8 proto; u8 tclass; /* IPv6 traffic class */ u32 label; /* IPv6 flow label */ struct in6_addr src; /* IPv6 saddr */ struct in6_addr dst; /* IPv6 daddr */ } ipv6; } ip; /* 网络层 */ union { struct { __be16 src; /* TCP/UDP sport */ __be16 dst; /* TCP/UDP dport */ __be16 flags; /* TCP flags */ } tp; struct { u8 sha[ETH_ALEN]; /* ARP SHA */ u8 tha[ETH_ALEN]; /* ARP THA */ __be32 spa; /* ARP SPA */ __be32 tpa; /* ARP TPA */ } arp; } tp; /* 传输层或 ARP */ };关键参数说明与踩坑提示in_port不是skb-dev-ifindex而是 OVS 内部分配的vport-port_no由ovs_vport_add()时分配tun_key的填充依赖skb-encapsulation标志位若未置位如普通 VLAN 报文tun_key全域为 0不影响匹配recirc_id是 OVS 实现“多阶段 pipeline”的核心例如resubmit(,1)会重置recirc_id并重新提取 key触发二次匹配skb_mark的来源通常是iptables的--set-mark或tc的action mirred它是连接 OVS 与 Linux 网络栈策略的桥梁。这个结构的设计直接决定了 OVS 的匹配能力边界。比如它没有包含TCP sequence number所以无法做 TCP 状态跟踪但它包含了tun_opts使得 VXLAN 头部的VNI可以直接参与匹配——这就是 SDN 灵活性的底层支撑。2.3 流表匹配flow_lookup()与mask_cache_entry的两级加速机制OVS 的流表不是线性遍历而是采用“哈希桶 掩码缓存”的双层加速结构。ovs_flow_tbl_lookup_stats()是对外接口其内部调用flow_lookup()而flow_lookup()又会先查mask_cache_entry。我们来拆解这个三级结构2.3.1 哈希桶struct table_instance的内存布局流表主体是一个struct table_instance其核心是hlist_head *buckets数组// datapath/flow_table.h struct table_instance { struct hlist_head *buckets; /* 指向哈希桶数组首地址 */ unsigned int n_buckets; /* 桶数量通常为 2^N */ struct rcu_head rcu; /* RCU 同步头 */ struct hlist_node node[]; /* 用于链入全局 table 实例链表 */ };每个buckets[i]是一个hlist_head挂载着所有哈希值hash % n_buckets i的struct sw_flow。sw_flow本身包含struct hlist_node flow_table_node用于链入桶中。为什么用hlist而不是listhlist是 Linux 内核专为哈希表设计的单向链表hlist_head只占 4 字节struct hlist_node占 8 字节比双向list_head16 字节更省内存。在百万级流表场景下节省的内存可达 MB 级别。2.3.2 掩码缓存mask_cache_entry如何让 90% 查询免于遍历哈希只能定位桶桶内仍需遍历sw_flow。OVS 引入mask_cache_entry缓存最近成功匹配的sw_flow_mask即流表项的掩码因为实际生产环境中大量流量具有相同模式如某 VPC 内所有 TCP 流共享同一组src/dst ipport掩码。mask_cache_entry的结构如下// datapath/flow_table.h struct mask_cache_entry { u32 hash; /* 该 mask 的哈希值 */ struct sw_flow_mask *mask; /* 指向实际掩码结构 */ struct hlist_node hlist; /* 链入 cache 哈希桶 */ };flow_lookup()的流程是计算key的哈希值hash用hash MASK_CACHE_HASH_MASK定位mask_cache的桶遍历桶内mask_cache_entry对每个mask调用masked_flow_lookup()若masked_flow_lookup()成功返回flow则命中缓存跳过所有其他桶的遍历若缓存未命中则 fallback 到全表哈希桶遍历。性能价值在典型 Web 服务场景大量短连接mask_cache命中率可达 85%~95%意味着绝大多数包只需一次哈希 一次掩码匹配而非平均O(N/2)次比较。这是 OVS 能做到 10M PPS 的关键之一。2.4 Action 执行ovs_execute_actions()如何解析并调度OVS_ACTION_ATTR_*匹配到flow后ovs_execute_actions()开始解析flow-sf_acts中的nlattr序列并按顺序执行。sf_acts是一个struct sw_flow_actions其actions字段是void *指向一块nlattr编码的内存// datapath/flow.h struct sw_flow_actions { struct rcu_head rcu; u32 actions_len; /* actions 字节数 */ struct nlattr actions[]; /* 紧凑的 nlattr 序列如 [OVS_ACTION_ATTR_OUTPUT][OVS_ACTION_ATTR_SET]... */ };ovs_execute_actions()的核心是__do_execute_actions()它使用nla_for_each_nested()遍历每个nlattr// datapath/actions.c static int __do_execute_actions(struct datapath *dp, struct sk_buff *skb, const struct nlattr *attr, int len, struct sw_flow_key *key) { const struct nlattr *a; int rem; nla_for_each_nested(a, attr, rem) { int type nla_type(a); switch (type) { case OVS_ACTION_ATTR_OUTPUT: do_output(dp, skb, nla_get_u32(a), key); // 发送到指定 port_no break; case OVS_ACTION_ATTR_USERSPACE: output_userspace(dp, skb, a, key); // 构造 netlink 消息上送 break; case OVS_ACTION_ATTR_PUSH_VLAN: push_vlan(skb, nla_data(a)); // 添加 VLAN header break; case OVS_ACTION_ATTR_POP_VLAN: pop_vlan(skb); // 移除最外层 VLAN break; case OVS_ACTION_ATTR_SET: execute_set_action(skb, key, nla_data(a)); // 修改 packet 字段 break; case OVS_ACTION_ATTR_RECIRC: execute_recirc(skb, key, nla_data(a)); // 设置 recirc_id 并重新入 pipeline break; default: return -ENOTSUPP; } } return 0; }关键细节与玄学经验OVS_ACTION_ATTR_OUTPUT的参数是u32 port_no不是ifindex必须是 OVS 内部端口编号可通过ovs-ofctl show br0查看OVS_ACTION_ATTR_SET的nla_data(a)指向一个struct ovs_key_ipv4或struct ovs_key_ethernetexecute_set_action()会根据key类型选择修改skb-ip_hdr()-daddr或eth_hdr()-h_destOVS_ACTION_ATTR_RECIRC是实现resubmit的关键它会修改key-recirc_id并调用ovs_dp_process_packet()重新走一遍流程这是 OVS 支持多级流表multi-table的唯一机制所有 action 函数都假设skb是线性skb_is_nonlinear(skb) false若遇到 GSO 分片包OVS 会在ovs_dp_process_packet()开头强制skb_linearize()这是性能损耗点。至此一条报文的内核态旅程就结束了从netdev_frame_hook进来经key提取、mask_cache加速匹配、action解析执行最终由do_output()调用dev_queue_xmit()发往目标vport的dev。整个过程在软中断上下文中完成无锁、无睡眠、无系统调用这就是 OVS datapath 的硬核所在。3. 用户态协同ovs-vswitchd如何通过udpif处理 upcall 并生成新流表内核 datapath 的upcall机制是 OVS 的“智慧中枢”——当内核找不到匹配流时它不丢包而是把原始报文 key 元数据打包发给用户态ovs-vswitchd由后者决定如何处理学习新流、发送 Packet-In、丢弃等。这个过程由udpifUserspace Datapath Interface模块驱动它是ovs-vswitchd与内核 datapath 通信的神经末梢。3.1udpif线程模型为何需要多个 handler 线程udpif的设计直面高并发upcall场景。单线程处理upcall会成为瓶颈尤其在控制器响应慢时upcall队列堆积导致延迟飙升。因此ovs-vswitchd启动时会创建多个udpif_handler线程// ofproto/ofproto-dpif-upcall.c void udpif_set_threads(struct udpif *udpif, int n_handlers) { udpif-n_handlers n_handlers; udpif-handlers xzalloc(n_handlers * sizeof *udpif-handlers); for (int i 0; i n_handlers; i) { struct udpif_handler *handler udpif-handlers[i]; handler-udpif udpif; ovs_thread_create(udpif, udpif_upcall_handler, handler); } }每个udpif_handler线程运行udpif_upcall_handler()其主循环是// ofproto/ofproto-dpif-upcall.c static void *udpif_upcall_handler(void *arg) { struct udpif_handler *handler arg; struct udpif *udpif handler-udpif; while (!latch_is_set(udpif-exit_latch)) { /* Step 1: 从内核接收 upcall */ struct dpif_upcall upcall; if (dpif_recv(udpif-dpif, upcall) ! 0) { continue; } /* Step 2: 解析 upcall构造 flow 和 xlate_in */ struct ofproto_dpif *ofproto; struct flow flow; struct xlate_in xlate_in; parse_odp_packet(upcall, flow, xlate_in); /* Step 3: 分类处理MISS / INVALID / SLOW_PATH */ switch (upcall.type) { case DPIF_UC_MISS: handle_miss(udpif, upcall, flow, xlate_in); break; case DPIF_UC_INVALID: handle_invalid(udpif, upcall, flow); break; case DPIF_UC_SLOW: handle_slow(udpif, upcall, flow, xlate_in); break; } } return NULL; }为什么是dpif_recv()而不是recv()dpif_recv()是dpif_class的虚函数对于dpif_netlink默认它最终调用nl_sock_recv()从 netlink socket 读取genl消息对于dpif_netdevuserspace datapath它从共享内存环形缓冲区读取。udpif层完全屏蔽了底层差异这是 OVS 架构解耦的典范。3.2handle_miss()从 Packet-In 到流表安装的完整闭环DPIF_UC_MISS是最常见的upcall类型表示内核 datapath 未找到匹配流。handle_miss()的任务是将报文转换为 OpenFlow Packet-In 消息发给 Controller并等待 Controller 返回 Flow-Mod 消息再将其安装到内核流表。这个过程涉及ofproto_dpif、connmgr、rconn多个模块协同// ofproto/ofproto-dpif-upcall.c static void handle_miss(struct udpif *udpif, struct dpif_upcall *upcall, struct flow *flow, struct xlate_in *xin) { struct ofproto_dpif *ofproto udpif-backer-ofproto; struct ofpbuf *packet_in; /* Step 1: 构造 OpenFlow Packet-In 消息 */ packet_in make_packet_in(ofproto, upcall, flow); /* Step 2: 通过 connmgr 发送给所有连接的 Controller */ connmgr_send_packet_in(ofproto-connmgr, packet_in); /* Step 3: 创建 ukeyunique key用于异步等待 Flow-Mod 响应 */ struct udpif_key *ukey ukey_create_from_upcall(upcall, flow); /* Step 4: 将 ukey 加入 pending 队列等待 controller 响应 */ ovs_mutex_lock(udpif-mutex); hmap_insert(udpif-ukeys, ukey-hmap_node, hash_int(ukey-hash, 0)); ovs_mutex_unlock(udpif-mutex); }ukey的作用是什么ukey是udpif的核心状态对象它记录了upcall的原始key、packet、hash以及等待的controllerID。当ovs-vswitchd收到Flow-Mod消息时会通过ukey_lookup()找到对应的ukey然后调用ukey_install()将新流安装到内核datapath。没有ukeyupcall和Flow-Mod就无法关联整个闭环就断了。3.3xlate_actions()用户态如何将 OpenFlow Actions 翻译成内核可执行指令ovs-vswitchd收到Flow-Mod后不会直接把OFPAT_OUTPUT等 OpenFlow action 发给内核。它必须先经过xlate_actions()翻译因为 OpenFlow action 和内核OVS_ACTION_ATTR_*是两套语义体系。xlate_actions()的输入是struct ofpact链表OpenFlow action输出是struct ofpbuf *ofpacts内核nlattr序列// ofproto/ofproto-dpif-xlate.c void xlate_actions(struct xlate_in *xin, struct xlate_out *xout) { struct ofpact *a; struct ofpbuf *ofpacts xout-ofpacts; OFPACT_FOR_EACH (a, xin-ofpacts, xin-ofpacts_len) { switch (a-type) { case OFPACT_OUTPUT: ofpact_put_OUTPUT(ofpacts, ofpact_get_OUTPUT(a)-port); break; case OFPACT_SET_FIELD: ofpact_put_SET_FIELD(ofpacts, ofpact_get_SET_FIELD(a)-field, ofpact_get_SET_FIELD(a)-value); break; case OFPACT_RESUBMIT: ofpact_put_RESUBMIT(ofpacts, ofpact_get_RESUBMIT(a)-table_id); break; case OFPACT_CONTROLLER: ofpact_put_CONTROLLER(ofpacts, ofpact_get_CONTROLLER(a)-max_len, ofpact_get_CONTROLLER(a)-reason); break; } } }关键映射关系OFPACT_OUTPUT→OVS_ACTION_ATTR_OUTPUT端口编号转换OFPACT_SET_FIELD→OVS_ACTION_ATTR_SETfield是OXM_OF_IPV4_DST等value是具体 IPOFPACT_RESUBMIT→OVS_ACTION_ATTR_RECIRCtable_id转换为recirc_idOFPACT_CONTROLLER→OVS_ACTION_ATTR_USERSPACE构造OVS_PACKET_ATTR_USERDATA用于 Packet-In。这个翻译层的存在使得 OVS 可以支持不同版本的 OpenFlow1.0/1.1/1.3/1.5而内核 datapath 保持稳定。这也是为什么ovs-vswitchd升级 OpenFlow 版本无需重启内核模块。4. 避坑指南5 个血泪经验总结的 OVS 源码调试高频问题读 OVS 源码不是看小说是真刀真枪调试。以下是我某开发者在某跨平台系统项目中为排查一个upcall丢失问题连续熬了 36 小时后总结的 5 个高频坑。每一条都对应真实翻车现场附带dmesg/ovs-appctl输出和解决方案。4.1 现象ovs-appctl dpif/show显示misses: 0但ovs-ofctl dump-flows br0为空且tcpdump看不到任何 Packet-In原因ovs-vswitchd未正确连接 Controllerconnmgr处于DISCONNECTED状态upcall被静默丢弃。ovs-appctl dpif/show的misses计数器只在connmgr处于CONNECTED或INACTIVE时递增DISCONNECTED下不计数。排查# 查看 connmgr 状态 $ ovs-appctl -t ovs-vswitchd connmgr/show # 输出示例 # Bridge br0: # Manager ptcp:6632: # State: DISCONNECTED # Since: 12345.678 seconds ago # Connection attempts: 0 # Messages sent: 0 # Messages received: 0解决检查ovs-vsctl set-controller br0 tcp:127.0.0.1:6632是否执行确认 Controller 进程是否启动防火墙是否放行6632端口。4.2 现象dmesg持续刷ovs_dp_upcall: failed to allocate skb for upcall原因内核sk_buff内存池耗尽。ovs_dp_upcall()需要为每个upcall分配一个skb来封装netlink消息若系统skbuff_head_cache内存不足常见于高并发upcall或内存泄漏分配失败。排查# 查看 skbuff 内存使用 $ cat /proc/slabinfo | grep skbuff # 关注 active_objs / num_objs 比值若接近 100% 且 ovs_dp_upcall 错误持续出现则确认解决增大skbuff内存池临时方案# 增加 slab 缓存大小需 root $ echo 10000 /proc/sys/net/core/skb_mem_limit # 或重启 ovs-vswitchd 释放旧内存 $ systemctl restart ovs-vswitchd4.3 现象ovs-ofctl dump-flows br0显示流表存在但tcpdump -i any port 6632看不到任何Flow-Mod消息且ovs-appctl dpif/show的hit计数器不增长原因flow的key与upcall的key不匹配。常见于recirc_id或skb_mark字段不一致。例如 Controller 下发的Flow-Mod中OXM_OF_IN_PORT值为1但upcall的key-phy.in_port是2因vport重编号或ovs-vsctl add-port顺序问题。排查# 开启 datapath 详细日志 $ ovs-appctl -t ovs-vswitchd dpif/set-log-level dbg # 触发流量查看 dmesg 中 key 字段 $ dmesg | tail -20 | grep key: # 输出示例key: in_port2, eth(src...,dst...), ip(src...,dst...)解决确保ovs-vsctl add-port br0 eth0 -- set Interface eth0 ofport_request1显式指定ofport_request避免自动分配导致in_port波动。4.4 现象ovs-ofctl add-flow br0 in_port1,actionsoutput:2后ovs-appctl dpif/show显示hit: 0且tcpdump -i eth2无包原因output:2的port_no不存在。ovs-ofctl dump-ports br0显示port 2对应eth1但eth1未up或未加入br0。ovs_dp_process_packet()在do_output()中会校验port_no是否有效无效则静默丢弃。排查# 查看所有 port 状态 $ ovs-ofctl dump-ports br0 # 确认 port 2 的 name 和 state # 查看 interface 状态 $ ovs-vsctl list interface | grep -E (name|ofport|admin_state|link_state)解决确保eth1已up且ovs-vsctl add-port br0 eth1已执行。4.5 现象ovs-appctl dpif/show的lost计数器持续增长dmesg有ovs_dp_process_packet: dropping packet on port X原因vport的netdev设备rx队列溢出。当vport绑定的net_device如eth0的rxring buffer 满且ovs_dp_process_packet()处理速度跟不上netdev_frame_hook()会返回RX_HANDLER_CONSUMED但skb未被及时处理最终被net_rx_action()丢弃。排查# 查看 eth0 rx 队列状态 $ ethtool -S eth0 | grep rx_ # 关注 rx_discards, rx_over_errors # 查看 ovs-vswitchd CPU 使用率 $ top -p $(pgrep ovs-vswitchd)解决降低ovs-vswitchd负载减少流表复杂度或增大netdev的rxring size$ ethtool -G eth0 rx 40965. 二次开发实战如何为 OVS 添加一个自定义 Action以OVS_ACTION_ATTR_LOG为例当你需要 OVS 在匹配到特定流时不仅转发还要记录日志如syslog或kmsg标准OVS_ACTION_ATTR_USERSPACE过重OVS_ACTION_ATTR_CONTROLLER又要走 OpenFlow 协议栈。此时添加一个轻量级内核态LOGaction 是最优解。下面我带你从零实现它覆盖内核模块修改、用户态适配、编译验证全流程。5.1 内核态在datapath/actions.c中注册OVS_ACTION_ATTR_LOG首先在datapath/actions.c的enum ovs_action_attr中添加新类型// datapath/linux/kbuild/ovs_kmod_compat.h enum ovs_action_attr { // ... existing attrs ... OVS_ACTION_ATTR_LOG, /* 新增日志 action */ __OVS_ACTION_ATTR_MAX };然后在do_execute_actions()的switch中添加处理分支// datapath/actions.c case OVS_ACTION_ATTR_LOG: { const struct ovs_action_log *log nla_data(a); char buf[256]; int len; /* 提取 log level 和 message */ u8 level log-level; // 0EMERG, 1ALERT, ..., 7DEBUG const char *msg log-message; /* 格式化日志[OVS] port%d, proto%d, src%pI4 */ len snprintf(buf, sizeof(buf), [OVS LOG] port%d, proto%d, src%pI4, key-phy.in_port, key-ip.proto, key-ip.src); /* 输出到 kernel log */ switch (level) { case 0: pr_emerg(%.*s\n, len, buf); break; case 1: pr_alert(%.*s\n, len, buf); break; case 2: pr_crit(%.*s\n, len, buf); break; case 3: pr_err(%.*s\n, len, buf); break; case 4: pr_warn(%.*s\n, len, buf); break; case 5: pr_notice(%.*s\n, len, buf); break; case 6: pr_info(%.*s\n, len, buf); break; case 7: pr_debug(%.*s\n, len, buf); break; default: pr_info(%.*s\n, len, buf); break; } break; }注意struct ovs_action_log需要在datapath/linux/ovs_kmod_compat.h中定义struct ovs_action_log { u8 level; /* syslog level */ u8 pad[3]; /* 保证 4 字节对齐 */ char message[0]; /* 可变长字符串 */ };5.2 用户态在lib/ofp-actions.c中支持OFPAT_LOGovs-ofctl需要能解析logaction。在lib/ofp-actions.c的ofpact_decode()中添加// lib/ofp-actions p a hrefhttps://download.csdn.net/download/zhang20080528yeahnet/12683349 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
延伸阅读

更多相关文章

2026/10/24 22:12:06

洗瓶机课程设计:凸轮机构如何实现三倍回程速度

简介:机械原理课程设计洗瓶机项目的完整PPT方案,面向机械类专业学生及课程设计人员,可用于理解洗瓶机从功能分解、运动方案设计到执行机构选型的全过程。资料共1个文件,为PPT演示文稿,压缩包仅527KB,内容精…

2026/10/24 22:12:06

计算机组成原理课后习题精析:CPU性能与数据表示机制

简介:计算机组成与系统结构(袁春风第三版)课后习题答案汇总,面向计算机专业本科生、考研复习者及自学计组、指令系统与汇编语言的人。内容紧密围绕性能评估、MIPS、CPI、时钟频率、性价比分析等高频考点,通过典型习题给…

2026/10/24 22:12:06

数字图像缩放算法对比:MATLAB实现与性能分析

简介:这份资源是一篇电子信息工程专业的本科毕业设计论文,题目为《数字图像缩放技术研究》,面向图像处理方向的学生与研究人员,尤其适合正在准备相关课题或需要参考完整论文结构的学习者。压缩包内仅含1个doc文档,大小…

2026/10/24 23:22:18

零基础配置远程桌面连接:服务器端与客户端操作及排错

简介:《远程桌面连接的操作步骤》面向需要掌握Windows XP远程桌面配置与使用的IT初学者、办公人员和技术支持人员,围绕远程访问另一台电脑桌面、收发邮件、查看报表、管理用户及维护更新系统等场景展开,帮助读者理解客户端与服务器端的角色划…

2026/10/24 23:22:18

财务共享服务中心落地技术方案:费用报销全流程与系统集成拆解

简介:这份PDF文档聚焦财务共享服务解决方案,面向企业财务管理者、集团管控信息化负责人及财务转型咨询从业者,系统讲解共享服务中心的建设思路与财务集中作业的落地路径。内容从共享服务概念体系切入,梳理财务共享与财务集中的异同…

2026/10/24 23:22:18

“Impeccable”不是技术项目:论标题空泛时的专业内容守则

项目标题是“impeccable”,但输入中未提供任何实质性项目正文、关键词列表或摘要描述——仅有一行标题和空置的热搜词区块。根据你设定的核心创作原则第一条:“忠于原料,合理演绎”,我必须严格以输入内容为唯一依据,不…

2026/10/24 23:22:18

Python面向对象编程:从脚本到工程的关键一跃

简介:本资源为2021年出版的《Python Object-Oriented Programming》第四版英文原版PDF,由Steven F. Lott与Dusty Phillips合著,面向希望系统掌握Python面向对象编程的开发者。全书依照“类与对象基础—封装与继承—多态与特殊方法—设计模式—…

2026/10/24 23:22:18

平板手写笔悬浮窗技术解析与应用实践

1. 平板手写笔悬浮窗的前世今生作为一名从2012年就开始使用Wacom数位板的老用户,我见证了手写输入技术从专业绘图领域走向大众消费市场的全过程。早期的笔输入设备需要依赖专用驱动和复杂设置,而如今iPad ProApple Pencil的组合已经让数字手写变得像在纸…

2026/10/24 23:17:18

MySQL日期与时间戳转换实战技巧

1. MySQL日期与时间戳转换实战指南作为数据库开发中最常遇到的数据类型处理场景之一,日期时间格式的转换几乎每个开发者都会碰到。特别是在数据迁移、报表生成和接口对接时,不同系统对日期时间的存储方式各异——有的用字符串"2023-07-15"&…

2026/10/23 22:56:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/24 0:02:40

MATLAB缩进快捷键与批量格式化实战指南

1. 被自动缩进折磨的那些年如果你写过MATLAB代码,大概率经历过这种场景:从某个文档、网页或者聊天记录里复制了一段代码,粘贴进编辑器之后,每一行前面都带着莫名其妙的空格,有的行首是四个空格,有的是一个T…

2026/10/24 0:02:40

用Poco实现Qt风格信号与槽的跨平台C++方案

简介:本资源是一份面向C中级开发者的技术实践工程,聚焦于在非Qt框架环境下模拟实现Qt核心通信机制——信号与槽,借助Poco网络库的事件系统完成解耦式对象通信。适用于需在跨平台C项目中引入松耦合回调机制、但受限于无法集成Qt依赖的开发场景…

2026/10/24 0:02:40

ESXi裸机虚拟化安装指南:从硬件兼容到虚拟机部署

1. 为什么还要折腾ESXi:从一台闲置机器说起手里攒了几台淘汰下来的迷你主机,配置不算差,i5加16G内存,扔了可惜,装个桌面系统又觉得浪费。这种场景下,ESXi就成了一个绕不开的选择。它是一套裸机虚拟化平台&a…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑