
简介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。它们解决的问题完全不同选型时看场景。组类型必选/可选语义典型用途allRequired执行组内所有行动存储段数据包被复制后逐个段处理多播、广播转发selectOptional从多个存储段中选一个执行算法在 OpenFlow 外部配置负载均衡、等价多路径indirectRequired只支持一个行动存储段多个流表项共用同一组IP 下一跳汇聚、快速重路由的中间汇聚层fast failoverOptional执行第一个“有效”的行动存储段端口或组失效时自动切换链路故障时的快速切换无需控制器参与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 三个。保留端口必选/可选含义ALLRequired复制数据包并发往所有标准端口排除入端口和 OFPPC_NO_FWD 端口CONTROLLERRequired控制通道数据包封装进 Packet-in 消息发给控制器TABLERequired重新进入流水线从第一张表开始处理仅用于 Packet-out 的行动列表IN_PORTRequired数据包的进入端口仅作为输出端口使用ANYRequired端口通配符仅用于命令中表示“未指定端口”不能做入口或出口LOCALOptional交换机的本地管理协议栈可用来做带内控制器连接NORMALOptional传统非 OpenFlow 流水线处理仅 OpenFlow-hybrid 交换机支持FLOODOptional用普通流水线做泛洪与 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-ActionsWrite-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-miss6.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)和actionsoutput:2的差异——前者只是写入行动集后者才是立即执行或作为最终行动执行。如果控制器下发的表项没有出现在 dump 里优先查消息版本和交换机能力协商。6.2 复现 table-miss 和组表语义# 下发 table-miss全通配、优先级 0、转发到控制器 ovs-ofctl -O OpenFlow13 add-flow br0 \ table0,priority0,actionsCONTROLLER:65535 # 下发普通流匹配目的 IP 段输出到端口 1 ovs-ofctl -O OpenFlow13 add-flow br0 \ table0,priority100,ip,nw_dst192.168.10.0/24,actionsoutput:1 # 添加 all 组复制到端口 2 和端口 3 ovs-ofctl -O OpenFlow13 add-group br0 \ group_id1,typeall,bucketoutput:2,bucketoutput:3 # 把去往 192.168.20.0/24 的流量交给组 1 ovs-ofctl -O OpenFlow13 add-flow br0 \ table0,priority100,ip,nw_dst192.168.20.0/24,actionsgroup:1下发完用ovs-ofctl -O OpenFlow13 dump-groups br0查看组表能看到组类型和每个 bucket。用ovs-appctl fdb/show br0之类看 MAC 学习结果或用 tcpdump 在出口抓包确认行为。加-O OpenFlow13这个参数很重要不加时 ovs-ofctl 可能用 OpenFlow 1.0 编码group 相关的关键字会解析失败翻车现场比想象的常见。验证行动集顺序也有一个笨办法在流表项里同时写actionsdec_ttl,output:2再对比只写actionsoutput:2的两条流用 tcpdump 抓包看 IP TTL 字段变化。实践几次之后你对“行动集顺序和行动列表顺序是两套逻辑”这句话的感受会远比只看文档来得深。从那以后我每次拿到一份协议文档都强制先标一遍 Required 和 Optional再打开 OVS 逐个复现关键语义把规范里的字面意思变成自己亲手验证过的行为。这份 1.3.0 中文版作为对照手册放在手边配合实机操作比单独通读十遍都管用。希望帮到你。本文还有配套的精品资源点击获取