简介:OpenFlow 1.3.0中文版是SDN控制器与交换机通信的核心规范文档,适合网络工程师、SDN开发者和相关专业学生阅读。文档共91页,整个资源为一个PDF文件,压缩包大小约1.52MB,便于离线查阅与打印。正文从交换机部件、端口分类到流表与组表机制展开,详细说明匹配字段、优先级、计数器、指令集、行动集,以及漏表时的三种处理方式,帮助读者理解数据包在流水线中的完整流转。其中组表项由行动存储段构成,可将多路径转发、快速重路由、链路聚合等策略复用到多个流表项,提升转发效率;保留端口和逻辑端口则补充了泛洪、发送至控制器、隧道及环回等能力。目前已有534人学习,对学习SDN原理、配置OpenFlow交换机或进行协议二次开发都有参考价值。
1. 初读 OpenFlow 1.3.0 中文版:从一份 PDF 到能动手配置的协议认知
OpenFlow 协议 1.3.0 是 SDN 里绕不开的一份规范,控制器怎么往交换机下发策略、交换机按什么顺序处理数据包、哪些端口和行动是必选哪些只是可选,全写在这份文档里。我最早啃英文原版,流表项、行动集、组表这些概念来回对照着看,后来拿到这份 91 页的中文完整版,通读速度确实快了不少,尤其是 5.1 到 5.12 这几节,基本把交换机内部处理路径讲透了。不过要提前提醒一句:这份规范定义的是“要求”,不是“教程”,通读之后如果不去控制器和交换机上实际对一遍,你很难真正理解 table-miss 的默认行为和行动集的执行顺序。适合刚接手 SDN 开发、对接过 OVS 或硬件交换机但发现行为对不上的工程师,带着“流表怎么建、table-miss 怎么配、行动集到底按什么顺序跑”这三个问题去读,收获会大得多。
2. 交换机三张表是骨架:流表、组表、计量表在 1.3.0 里怎么协作
2.1 流水线:匹配只能前进,不能回头
OpenFlow 1.3.0 把交换机内部定义成一条“流表流水线”:所有流表从 0 开始编号,数据包从第一张表进入,匹配命中后根据流表项里的指令决定是否跳到后续表。规范明确写死了一条规则——Goto-Table 指令只能指向编号更大的表,流水线处理只能前进,不能后退。这意味着流表编号本身就是一种优先级设计:越靠近 0 号表的规则越偏向粗粒度分类,越往后的表越适合做细粒度处理。
我一般会把流水线设计成“分层”的:0 号表按入端口或 VLAN 做分类,1 号表做 L2 转发,2 号表再做 ACL 和 QoS 策略,最后一张表不再包含 Goto 指令。这样设计的好处是每一层只关心一件事,流量变更时只需修改对应层的表项,不会影响到其他层。如果你把 Goto 写在最后一张表里,交换机不会容忍,直接拒绝这条流表项并返回 unsupported flow error,这一点在对接硬件交换机时尤其常见。
数据包在某张表里没有命中任何普通流表项时,后面的处理不由交换机默认逻辑决定,而由 table-miss 流表项决定。table-miss 是一个匹配字段全部通配、优先级为 0 的表项,它的可选行为包括把包发送给控制器、丢弃或者导入后续表。这里存在一个非常容易误判的默认值:规范里写明,如果流表中不存在 table-miss 表项,漏表的数据包默认是丢掉的,而不是默认上报控制器。后面避坑章节会专门展开这条。
2.2 流表项结构:六个要素一个都不能少
流表项是流表的基本单元,规范 5.2 节列出了六个组成部分:匹配字段、优先级、计数器、指令、超时和 cookie。它们各自承担不同职责,其中匹配字段和优先级共同决定一条流表项在表中的唯一性。
| 流表项元素 | 作用 | 关键细节 |
|---|---|---|
| 匹配字段 | 决定数据包是否命中 | 支持任意字段省略(即通配 ALL),部分字段可做位掩码匹配 |
| 优先级 | 同表内的匹配顺序 | 高优先级先匹配;同优先级重复项未定义 |
| 计数器 | 命中后更新,用于统计与老化 | 无符号数,缺失时读回 0xFFFFFFFF |
| 指令 | 修改行动集或控制流水线跳转 | 共有 Meter/Apply-Actions/Clear-Actions/Write-Actions/Write-Metadata/Goto-Table 六种(原规范中按 5.9 节列出的顺序) |
| 超时 | idle_timeout 与 hard_timeout | 单位为秒,非零时触发流表项删除 |
| cookie | 控制器回读用的不透明数值 | 数据包处理时不可见,只用于过滤统计与删除请求 |
匹配字段的覆盖面非常广:入端口、以太网源地址、以太网目的地址、以太网类型、VLAN ID、VLAN 优先级、IPv4 源地址、IPv4 目的地址、IPv4 协议号、IPv6 源地址、IPv6 目的地址、IPv6 Flow Label、TCP/UDP 源端口、TCP/UDP 目的端口、MPLS 标签、MPLS 流量类别等等。任意字段可以不写,省略就表示匹配所有取值。这带来一个实用的编码习惯:能精确匹配的字段尽量精确,不需要的字段一律不写,让交换机硬件查表更轻松。
需要注意的是,字段支持范围和位掩码能力不是所有交换机都一样的。控制器可以通过 Features 请求查询交换机的基本能力,再决定下发什么样的匹配项。有些硬件交换机的物理表是精确匹配表,不支持通配符;但规范要求所有交换机至少要支持 table-miss 这种全通配表项,哪怕其他流表项不支持通配。所以你看表项设计时,要把“交换机能力”当成一个动态变量,而不是规范里固定的常量。
2.3 组表与计量表:转发复杂度和 QoS 的入口
组表是 1.3.0 里容易被低估的一个组件。每个组表项由 32 位无符号组编号唯一标识,内部包含组编号、组类型、计数器和有序行动存储段。流表项可以把数据包交给某个组,由组决定具体怎么转发,这样多条流表项可以共享同一个组编号,比如多个目的地都指向同一个 IP 下一跳时,用组抽象就能避免每条流都重复写一遍输出行动。
规范定义了四种组类型:all、select、indirect、fast failover。它们解决的问题完全不同,选型时看场景。
| 组类型 | 必选/可选 | 语义 | 典型用途 |
|---|---|---|---|
| all | Required | 执行组内所有行动存储段,数据包被复制后逐个段处理 | 多播、广播转发 |
| select | Optional | 从多个存储段中选一个执行,算法在 OpenFlow 外部配置 | 负载均衡、等价多路径 |
| indirect | Required | 只支持一个行动存储段,多个流表项共用同一组 | IP 下一跳汇聚、快速重路由的中间汇聚层 |
| fast failover | Optional | 执行第一个“有效”的行动存储段,端口或组失效时自动切换 | 链路故障时的快速切换,无需控制器参与 |
fast failover 是我在实际中觉得最值得研究的一种类型:它把存储段按顺序排列,交换机会选择与有效端口或有效组关联的第一个存储段执行。链路断了之后,交换机自己就能切换转发路径,完全不需要等控制器重新下发流表,这在需要毫秒级收敛的场景里价值很大。前提是控制器的失效监控机制要先配好,比如通过 OFPFC_GROUP_MOD 维护组的状态;关于失效检查的细节在规范 6.5 节有说明,中文版的翻译也在那里。
计量表和组表是两回事。计量器直接挂在流表项的指令集里,用于测量和控制数据包速率,计量带定义了触发阈值和处理动作。可选带类型有两种:drop 和 dscp remark。drop 就是限速器,超出速率的包直接丢弃;dscp remark 会把超速数据包的 IP DSCP 字段改成更低的优先级,适合做简单 DiffServ 策略。设计 QoS 时,我习惯把流量分类放在前面的表,把计量器放在分类之后的表里,这样统计的是同一条策略下的总量,而不是零散的转发条目。
3. 端口与 OpenFlow 通道:三类端口和三类消息怎么配合
3.1 端口三分:物理、逻辑、保留
OpenFlow 端口是数据包进出流水线的接口,规范把端口分成三类:物理端口、逻辑端口和保留端口。物理端口直接对应交换机的硬件接口,比如以太网口;在硬件虚拟化场景下,一个物理端口也可以代表硬件接口的某个虚拟切片。逻辑端口不对应任何硬件接口,它是交换机定义的更高层抽象,链路聚合组、隧道接口、环回接口都属于这一类。逻辑端口的数据包可能带一个叫做 Tunnel ID 的额外元数据字段,把包发给控制器时,逻辑端口和底层物理端口会一起上报。
保留端口是协议规范直接定义的,用来表达通用转发行为,但并不是所有保留端口都是必选的。规范里标 Required 的有五个:ALL、CONTROLLER、TABLE、IN_PORT、ANY;标 Optional 的有 LOCAL、NORMAL、FLOOD 三个。
| 保留端口 | 必选/可选 | 含义 |
|---|---|---|
| ALL | Required | 复制数据包并发往所有标准端口,排除入端口和 OFPPC_NO_FWD 端口 |
| CONTROLLER | Required | 控制通道,数据包封装进 Packet-in 消息发给控制器 |
| TABLE | Required | 重新进入流水线从第一张表开始处理,仅用于 Packet-out 的行动列表 |
| IN_PORT | Required | 数据包的进入端口,仅作为输出端口使用 |
| ANY | Required | 端口通配符,仅用于命令中表示“未指定端口”,不能做入口或出口 |
| LOCAL | Optional | 交换机的本地管理协议栈,可用来做带内控制器连接 |
| NORMAL | Optional | 传统非 OpenFlow 流水线处理,仅 OpenFlow-hybrid 交换机支持 |
| FLOOD | Optional | 用普通流水线做泛洪,与 ALL 的语义不同,不保证打满所有端口 |
区分 NORMAL 端口和 ALL 端口很重要。ALL 端口是纯 OpenFlow 语义下的“去重广播”,它复制数据包发往所有标准端口;FLOOD 端口则由交换机用普通泛洪逻辑处理,可能根据 VLAN 选择泛洪范围。OpenFlow-only 交换机只支持 OpenFlow 处理,不支持 NORMAL 和 FLOOD;而 OpenFlow-hybrid 交换机在 OpenFlow 流水线之外还有传统以太网交换能力,NORMAL 端口就是让 OpenFlow 决定的包走传统 L2/L3 处理的那道闸门。
3.2 OpenFlow 通道:三类消息一手抓
OpenFlow 通道是交换机连接控制器的唯一接口。规范里写明通道通常使用 TLS 加密,但也允许直接在 TCP 上跑。通道建立后,控制器和交换机用三类消息交互:controller-to-switch、async、symmetric,每类下面又有对应子类型。
| 消息类别 | 发起方 | 常见子类型 | 用途 |
|---|---|---|---|
| Controller-to-Switch | 控制器 | Features、Configuration、Modify-State、Read-State、Packet-out、Barrier、Role-Request | 查询能力、设置参数、增删流表项、下发数据包、同步依赖 |
| Async | 交换机 | Packet-in、Flow-Removed、Port-Status、Error | 上报未匹配数据包、流表项老化、端口状态变化、错误信息 |
| Symmetric | 双方 | Hello、Echo、Experimenter | 握手、探测连通性、厂商扩展 |
Packet-out 消息值得单独说清楚:它发送数据包到交换机特定端口,消息里必须包含一个完整数据包,或者一个指明交换机缓冲区内存储位置的 Buffer ID,同时必须携带一个行动列表并按顺序应用。如果行动列表为空,数据包就直接被丢弃。另一个容易被忽略的消息是 Barrier——控制器下发 Barrier 请求后,交换机会等待此前所有消息都处理完毕才回复;调试时如果发现下发的流表没有按预期生效,在批量下发之间插一条 Barrier 请求,能帮助确认是哪一条命令执行出了问题。
在用这份 1.3.0 中文版文档做开发时,我建议先抓 Messages 这一章看,再回头对照流水线和匹配那几章。因为消息类型决定了控制器和交换机之间的交互边界,很多东西在流表和组表里看不出来,只有看到 Packet-in 是怎么把未匹配包交上去的,你才能真正理解 table-miss 为什么要配 CONTROLLER 这个保留端口。
4. 指令与行动集:从 Write-Actions 到 Goto-Table 的执行顺序
4.1 六种指令:执行先后是写死的
流表项中的指令是数据包匹配后要执行的操作,1.3.0 定义了六种指令:Meter、Apply-Actions、Clear-Actions、Write-Actions、Write-Metadata、Goto-Table。规范里强调,指令不能随意排序执行,同一个流表项内不管你怎么写,最终执行顺序是固定的:Meter 先执行,接着 Apply-Actions,然后 Clear-Actions,之后 Write-Actions,再写 Write-Metadata,最后才处理 Goto-Table。
| 指令 | 作用 | 执行阶段 |
|---|---|---|
| Meter | 将数据包交给指定计量器处理 | 最先 |
| Apply-Actions | 立即执行行动列表,不改动行动集 | 第二 |
| Clear-Actions | 清空当前行动集中的所有行动 | 第三 |
| Write-Actions | 向行动集中追加或覆盖行动 | 第四 |
| Write-Metadata | 把掩码后的元数据写入寄存器 | 第五 |
| Goto-Table | 跳转到编号更大的下一张表 | 最后 |
这里有个容易误解的地方:Apply-Actions 和 Write-Actions 的差别。Apply-Actions 是“立即生效的行动列表”,比如你想给数据包压一个 VLAN 头再继续查下一张表,就用 Apply-Actions;Write-Actions 只是把行动记到数据包的行动集里,并不会马上执行,等流水线处理停止后行动集里的行动才统一执行。行动集默认是空的,从第一张表开始,流表项可以用 Write-Actions 往里加东西,也可以在后续某张表里用 Clear-Actions 全部清掉。一个实用习惯是:同一条流路径上只保留最后一次写入的同一类型行动,避免重复动作。
4.2 行动集与行动列表:执行顺序完全不同
行动集和行动列表虽然都包含“行动”,但执行语义在 1.3.0 里是两个体系。行动集是和数据包绑定的一个集合,跨表传递,所有行动不管以什么顺序被写入,最终执行时都严格按照规范固定顺序:先复制 TTL 到内部,弹出所有标记,压入 MPLS、压入 PBB、压入 VLAN,复制 TTL 到外部,TTL 减 1,执行所有 set_field,执行所有 QoS 行动,然后处理组行动,最后才是 output 行动。
我把这个顺序看成一条约定俗成的“改造流水线”:先处理标记相关的行动,再改 TTL,再改字段,最后决定把包发给谁。规范特意强调,如果行动集里同时存在组行动和输出行动,组行动优先于输出行动执行;只有行动集里没有组行动时,output 才生效。实际配置时,很多人习惯把 output 写在其他行动前面,以为会先发出去,结果方向完全反了——这在硬件交换机上尤其明显,因为转发永远发生在所有修改完成后。
行动列表则是 Apply-Actions 指令和 Packet-out 消息里携带的行动序列,按列表中的先后顺序立即依次作用到数据包上。行动结果会累积:行动列表里有两个 push-VLAN 行动,数据包最终就被压上两层 VLAN 头。输出行动如果在列表中间,会把当前状态下的数据包复制一份转发出去,然后列表继续执行。区分这两套机制,是调试“为什么转发后的包和预期的不同”这类问题的基础。
4.3 行动列表里的压入默认值:很容易漏看的彩蛋
规范 5.12 节讲输出和 Set-Field 行动,5.12.1 节专门讲字段压入的默认值。执行 push 行动时,新建头部的某些字段会从已有外部头复制,比如压入 VLAN 后新头部的 TPID 从哪个字段继承、哪些字段初始化为 0,这些细节在中文版里有表格列出。我见过不止一次因为压入 VLAN 后没有调整内部字段,导致下游交换机匹配不到流量的问题。压入后如果需要修改,可以在同一行动列表里通过 Set-Field 行动补上,规范也明确说“set VLAN ID”行动永远作用到最外侧的 VLAN 标记。所以配置 VLAN 标签栈时,压入顺序和 set 顺序要对应着看,不能只写一个 push 就完事。
5. 避坑指南:读 OpenFlow 1.3.0 时最容易翻车的五个细节
5.1 五个常见翻车场景与对策
踩坑记录一:同优先级流表项重复下发。现象是数据包在两个匹配项之间被随机处理,转发路径时通时断,控制器查询流表却看不出异常。原因是控制端下发了两个匹配字段和优先级完全相同的流表项,而规范规定此时所选表项是未定义行为,除非控制器在 Flow Mod 里设置 OFPFF_CHECK_OVERLAP 标志要求交换机拒绝重叠。解决方式是在控制器代码里统一开启重叠检查,把重复表项拦截在下发之前,不要让交换机去猜。
踩坑记录二:新流量全部消失,控制器收不到任何 Packet-in。现象是数据面看上去一切正常,但新流无法初始化。原因是流表的 table-miss 表项没有被创建,规范默认漏表行为是丢弃数据包,而不是上报控制器。解决方式是交换机上线后先下发一条 table-miss 流表项:匹配字段全通配、优先级 0、行动为 CONTROLLER,保证未知流量能到控制器做首包决策。
踩坑记录三:下发 Goto-Table 时报 unsupported flow error。现象是控制器批量下发流表时某几条被交换机拒绝。原因是这些流表项写在最后一张表里,或者 Goto 指向了编号小于等于当前表的表,规范禁止流水线后退。解决方式是检查表编号顺序,Goto-Table 的表 ID 必须严格大于当前表,最后一张表不要写 Goto 指令。
踩坑记录四:行动集里同时写了 group 和 output,结果流量走了组而不是端口。现象是你期望数据包直接被 output 到指定端口,但实际走了组里的行动。原因就是规范明确组行动优先于 output 行动,output 只在没有组行动时才执行。解决方式是重新梳理行动集,不需要组转发时不要在同一条流里同时设置两种行动。
踩坑记录五:ALL 端口泛洪时,数据包没到某些端口。现象是广播包在部分端口缺失,检查端口状态又都是 UP。原因是 ALL 端口泛洪明确排除两个集合:数据包的入端口,以及被配置为 OFPPC_NO_FWD 的端口。解决方式是先查端口属性是否被控制器设置为不可转发,再确认泛洪范围,不要把它当成传统二层的“全网广播”。
5.2 判断交换机语义时要注意的边界
除了上面的具体场景,这份规范里还有几个容易被忽略的边界条件。第一个是 OpenFlow-only 和 OpenFlow-hybrid 的差别:OpenFlow-only 交换机不支持 NORMAL 和 FLOOD 端口,所有流量必须由 OpenFlow 流水线处理;OpenFlow-hybrid 交换机必须提供一种外部分类机制决定数据包进入哪条流水线,这种机制不受规范约束。你写控制器时要先查询交换机能力,不能默认所有保留端口都能用。
第二个是计数器的回绕问题。端口、流表项、组、计量器的计数器都是无符号数,环回后没有溢出指示;没有对应计数器时,读回值是字段最大值。做流量监控系统时,必须对样本做防绕处理,否则跨环回点的统计会出现大幅跳变。第三个是 IP 分片重组的开关:交换机如果配置了 OFPC_FRAG_REASM 标志,那么 IP 分片会在流水线处理之前被重新组装,这个开关决定了匹配字段里能否出现完整 L4 头部。最后一个容易踩的是版本差异——1.3.0 里的行动列表延续了 1.0 的“按序执行”语义,但新增的 set_field、meter 等能力不能直接套用旧版本的实现逻辑,读这份规范时最好把它当成一份独立的新协约来看,而不是 1.0 的增量补丁。
6. 把规范落到 OVS 上验证:用 ovs-ofctl 复现流表、组表和 table-miss
6.1 用 ovs-ofctl 对拍协议行为
读协议文档最怕“看过就以为自己懂了”,我习惯每读完一个大节就去 OVS 上复现一遍。Open vSwitch 自带 ovs-ofctl 命令,支持直接下发 OpenFlow 1.3 流表。需要提醒的是,OVS 默认的 ofctl 兼容模式可能是 OpenFlow 1.0,下发 1.3 表项前要显式指定版本:
# 查看当前网桥上的所有流表项,重点看 table、priority、actions ovs-ofctl -O OpenFlow13 dump-flows br0这条命令的输出会显示每张表的表号、匹配字段、计数器和指令集。看输出时能直观感受到行动集顺序,比如write_actions(output:2)和actions=output:2的差异——前者只是写入行动集,后者才是立即执行或作为最终行动执行。如果控制器下发的表项没有出现在 dump 里,优先查消息版本和交换机能力协商。
6.2 复现 table-miss 和组表语义
# 下发 table-miss:全通配、优先级 0、转发到控制器 ovs-ofctl -O OpenFlow13 add-flow br0 \ 'table=0,priority=0,actions=CONTROLLER:65535' # 下发普通流:匹配目的 IP 段,输出到端口 1 ovs-ofctl -O OpenFlow13 add-flow br0 \ 'table=0,priority=100,ip,nw_dst=192.168.10.0/24,actions=output:1' # 添加 all 组:复制到端口 2 和端口 3 ovs-ofctl -O OpenFlow13 add-group br0 \ 'group_id=1,type=all,bucket=output:2,bucket=output:3' # 把去往 192.168.20.0/24 的流量交给组 1 ovs-ofctl -O OpenFlow13 add-flow br0 \ 'table=0,priority=100,ip,nw_dst=192.168.20.0/24,actions=group:1'下发完用ovs-ofctl -O OpenFlow13 dump-groups br0查看组表,能看到组类型和每个 bucket。用ovs-appctl fdb/show br0之类看 MAC 学习结果,或用 tcpdump 在出口抓包确认行为。加-O OpenFlow13这个参数很重要,不加时 ovs-ofctl 可能用 OpenFlow 1.0 编码,group 相关的关键字会解析失败,翻车现场比想象的常见。
验证行动集顺序也有一个笨办法:在流表项里同时写actions=dec_ttl,output:2,再对比只写actions=output:2的两条流,用 tcpdump 抓包看 IP TTL 字段变化。实践几次之后,你对“行动集顺序和行动列表顺序是两套逻辑”这句话的感受,会远比只看文档来得深。从那以后,我每次拿到一份协议文档,都强制先标一遍 Required 和 Optional,再打开 OVS 逐个复现关键语义,把规范里的字面意思变成自己亲手验证过的行为。这份 1.3.0 中文版作为对照手册放在手边,配合实机操作,比单独通读十遍都管用。希望帮到你。
本文还有配套的精品资源,点击获取