目录
OpenFlow运行机制
1 OpenFlow信道建立
1.1 OpenFlow消息类型
1.2 信道建立过程解析
2 OpenFlow消息处理
2.1 OpenFlow流表下发与初始流表
2.2 OpenFlow报文上送控制器
2.3 控制器回应OpenFlow报文
3 OpenFlow交换机转发
3.1 单播报文转发流程
OpenFlow的实践分析
1、实验目的:
2、实验环境:
3、基础实验要求:
4、进阶实验要求:
OpenFlow运行机制
1 OpenFlow信道建立
1.1 OpenFlow消息类型
要了解OpenFlow信道的建立过程,首先需要了解OpenFlow协议目前支持的三种报文类型:
💡OpenFlow协议目前支持的三种报文类型:
1、Controller To Switch消息
2、异步(Asynchronous)消息
3、同步(Symmetric)消息
1、Controller To Switch消息
由Controller发起、Switch接收并处理的消息。这些消息主要用于Controller对Switch进行状态查询和修改配置等管理操作,可能不需要交换机响应。
💡Controller To Switch的6种消息:
1、Features:用于控制器发送请求来了解交换机的性能,交换机必须回应该报文。
2、Modify-State:用于管理交换机的状态,如流表项和端口状态。该命令主要用于增加、删除、修改OpenFlow交换机内的流表表项,组表表项以及交换机端口的属性。
3、Read-State:用于控制器收集交换机各方面的信息,例如当前配置,统计信息等 。
4、Flow-Mod: Flow-Mod消息用来添加、删除、修改OpenFlow交换机的流表信息。Flow-Mod消息共有五种类型:ADD、DELETE、DELETE-STRICT、MODIFY、MODIFY-STRICT。
5、Packet-out:用于通过交换机特定端口发送报文 ,这些报文是通过Packet-in消息接收到的。通常Packet-out消息包含整个之前接收到的Packet-in消息所携带的报文或者buffer ID(用于指示存储在交换机内的特定报文)。这个消息需要包含一个动作列表,当OpenFlow交换机收到该动作列表后会对Packet-out消息所携带的报文执行该动作列表。如果动作列表为空,Packet-out消息所携带的报文将被OpenFlow交换机丢弃。
6、Asynchronous-Configuration:控制器使用该报文设定异步消息过滤器来接收其只希望接收到的异步消息报文,或者向OpenFlow交换机查询该过滤器。该消息通常用于OpenFlow交换机和多个控制器相连的情况。
2、异步(Asynchronous)消息
由Switch发送给Controller,用来通知Switch上发生的某些异步事件的消息,主要包括Packet-in、Flow-Removed、Port-Status和Error等。例如,当某一条规则因为超时而被删除时,Switch将自动发送一条Flow-Removed消息通知Controller,以方便Controller作出相应的操作,如重新设置相关规则等。
💡异步消息具体包含以下几种类型:
1、Packet-in:转移报文的控制权到控制器。对于所有通过匹配流表项或者Table Miss后转发到Controller端口的报文均要通过Packet-in消息送到Controller。也有部分其他流程(如TTL检查等)也需要通过该消息和Controller交互。Packet-in既可以携带整个需要转移控制权的报文,也可以通过在交换机内部设置报文的Buffer来仅携带报文头以及其Buffer ID传输给Controller。Controller在接收到Packet-in消息后会对其接收到的报文或者报文头和Buffer ID进行处理,并发回Packet-out消息通知OpenFlow交换机如何处理该报文。
2、Flow-Removed:通知控制器将某个流表项从流表的移除。通常该消息在控制器发送删除流表项的消息或者流表项的定时器超时后产生。
3、Port-Status:通知控制器端口状态或设置的改变。
3、同步(Symmetric)消息
顾名思义,同步(Symmetric)消息是双向对称的消息,主要用来建立连接、检测对方是否在线等,是控制器和OpenFlow交换机都会在无请求情况下发送的消息,包括Hello、Echo和Experimenter三种消息,这里我们介绍应用最常见的前两种:
💡 最常见的两种同步消息:
1、Hello:当连接启动时交换机和控制器会发送Hello交互。
2、Echo:用于验证控制器与交换机之间连接的存活,控制器和OpenFlow交换机都会发送Echo Request/Reply消息。对于接收到的Echo Request消息必须能返回Echo Reply消息。Echo消息也可用于测量控制器与交换机之间链路的延迟和带宽。
1.2 信道建立过程解析
💡 OpenFlow控制器和交换机之间建立信道连接的基本过程:
1、OpenFlow交换机与OpenFlow控制器之间通过TCP三次握手过程建立连接,使用的TCP端口号为6633
2、TCP连接建立后,交换机和控制器就会互相发送hello报文。Hello报文负责在交换机和控制器之间进行版本协商,该报文中OpenFlow数据头的类型值为 0
3、功能请求(Feature Request):控制器发向交换机的一条OpenFlow 消息,目的是为了获取交换机性能,功能以及一些系统参数。该报文中OpenFlow 数据头的类型值为 5
4、功能响应(Feature Reply):由交换机向控制器发送的功能响应(Feature Reply)报文,描述了OpenFlow交换机的详细细节。控制器获得交换机功能信息后,OpenFlow协议相关的特定操作就可以开始了
5、Echo请求(Echo Request)和Echo响应(EchoReply)属于OpenFlow中的对称型报文,他们通常用于OpenFlow交换机和OpenFlow控制器之间的保活。通常echo请求报文中OpenFlow数据头的类型值为 2,echo响应的类型值为 3。不同厂商提供的不同设备中,echo请求和响应报文中携带的信息也会有所不同
1.3 信道连接断开模式
当OpenFlow设备与所有Controller断开连接后,设备进入Fail Open模式。
💡OpenFlow设备存在两种Fail Open模式:
1、Fail Secure mode交换机:在该模式下的OpenFlow交换机,流表项继续生效,直到流表项超时删除。OpenFlow交换机内的流表表项会正常老化。
2、Fail Standalone mode交换机:所有报文都会通过保留端口Normal处理。即此时的OpenFlow交换机变成传统的以太网交换机。Fail Standalone mode只适用于OpenFlow-Hybrid交换机
安全通道也有两种模式,不同模式下安全通道重连的机制不同。
💡安全通道的两种模式:
1、并行模式:并行模式下,Switch允许同时与多个Controller建立连接,Switch与每个Controller单独进行保活和重连,互相之间不影响。当且仅当Switch与所有Controller的连接断开后,Switch才进入Fail Open状态。
2、串行模式:串行模式下,Switch在同一时刻仅允许与一个Controller建立连接。一旦与该Controller连接断开后,Switch并不会进入Fail Open状态,而是立即根据Controller的ID顺序依次尝试与Controller连接。如果与所有Controller都无法建立连接,则等待重连时间后,继续遍历Controller尝试建立连接。在三次尝试后,仍然没有成功建立连接,则Switch进入Fail Open状态。
2 OpenFlow消息处理
2.1 OpenFlow流表下发与初始流表
OpenFlow流表下发分为主动和被动两种机制:
💡OpenFlow流表下发的两种模式:
1、主动模式:Controller将自己收集的流表信息主动下发给网络设备,随后网络设备可以直接根据流表进行转发
2、被动模式下:网络设备收到一个报文没有匹配的FlowTable记录时,会将该报文转发给Controller,由后者进行决策该如何转发,并下发相应的流表。被动模式的好处是网络设备无需维护全部的流表,只有当实际的流量产生时才向Controller获取流表记录并存储,当老化定时器超时后可以删除相应的流表,因此可以大大节省交换机芯片空间。
在实际应用中,通常是主动模式与被动模式结合使用
当OpenFlow交换机和Controller建立连接后,Controller需要主动给OpenFlow交换机下发初始流表,否则进入OpenFlow交换机的报文查找不到流表项,就会做丢弃处理。这里的初始流表保证了OpenFlow的未知报文能够上送控制器。而后续正常业务报文的转发流表,则在实际流量产生时,由主动下发的初始流表将业务报文的首包上送给控制器后,触发控制器以被动模式下发。
这里我们以H3C VCFC控制器给交换机下发的一个初始流表举例。
前面我们了解到,OpenFlow流表是分级匹配的,通常按0表、1表、2表这样依次匹配过去,每个级别的表中则由优先级高的表项先进行匹配。
如上图所示,0表优先级最高为65535的两条流表匹配到的是端口号为67、68的UDP报文,也就是DHCP报文,匹配动作为goto_table 1,剩下的其他所有报文也命中优先级最低为0的表项后goto_table 1。而在表1中,优先级最低的表项对应的动作为output controller,这保证了虚拟机的DHCP请求可以发送给控制器,由控制器作为网络中的DHCP Server,避免DHCP请求泛洪,同时还保证了交换机上所有未知的无流表匹配的报文都可以上送控制器,触发控制器被动下发流表给交换机指导转发。这里,我们把表1里优先级最低为0,匹配所有未知报文的表项叫做table-miss表项。
我们在OpenFlow交换机上同样可以观察到初始流表,这里以H3C S6800交换机上的一个初始流表举例。上图中的这条表项匹配报文类型为以太网报文,UDP端口67、68说明匹配DHCP请求报文,动作为上送控制器:
2.2 OpenFlow报文上送控制器
OpenFlow报文上送控制器详细过程如下:
💡OpenFlow报文上送控制器的详细过程:
1、控制器和交换机建立连接事件是Packet-in事件发生的前提。
2、当OpenFlow交换机收到数据包后,如果明细流表中与数据包没有任何匹配条目,就会命中table-miss表项,触发Packet-in事件,交换机会将这个数据包封装在OpenFlow协议报文中发送至控制器。
3、一旦交换机触发了Packet-in事件,Packet-in报文就将发送至控制器。
Packet-in数据头包括了:
- 缓冲ID
- 数据包长度
- 输入端口
- Packet-in的原因,分两种:
- 0: 无匹配
- 1: 流表中明确提到将数据包发送至控制器
2.3 控制器回应OpenFlow报文
控制器收到Packet-in消息后,可以发送Flow-Mod消息向交换机写一个流表项。并且将Flow-Mod消息中的buffer_id字段设置为Packet-in消息中的buffer_id值。从而控制器向交换机写入了一条与数据包相关的流表项,并且指定该数据包按照此流表项的action列表处理。
Controller根据报文的特征信息(如IP、mac等)下发一条新的流表项到OpenFlow交换机或者做其他处理之后下,发Packet-out消息动作为output到table,具体过程如下所示:
💡output到table的具体过程
1、控制器和交换机之间建立连接事件是Packet-out事件发生的前提;
2、控制器要发送数据包至交换机时,就会触发Packet-out事件将数据包发送至交换机。这一事件的触发可以看做是控制器主动通知交换机发送一些数据报文的操作。通常,当控制器想对交换机的某一端口进行操作时,就会使用Packet-out报文。
3、该数据包由控制器发往交换机,内部信息使用Packet-out,并由OpenFlow数据头封装。
💡OpenFlow Packet-out信息包括:
1、缓冲ID
2、入口端口编号
3、动作明细(添加为动作描述符)
4、输出动作描述符
5、VLAN VID动作描述符
6、VLAN PCP动作描述符
7、提取VLAN标签动作描述符
8、以太网地址动作描述符
9、IPv4地址动作描述符
10、IPv4 DSCP动作描述符
11、TCP/UDP端口动作描述
12、队列动作描述符
15、各厂商动作描述符
3 OpenFlow交换机转发
3.1 单播报文转发流程
当OpenFlow交换机接收到Flow-Mod消息,生成流表后,就可以按照流表转发接收到的Packet-out报文了,过程举例如下:
在本例中,OpenFlow 交换机需要转发一个从7.7.7.1到9.9.9.1的流量。当流量上送到OpenFlow交换机后,流量的第一个包会先进行Packet-in、Flow-Mod、Packet out的过程,之后同流量的报文就能匹配控制器已经下发的流表进行转发了。
3.2 组播报文转发
当终端发出的组播报文到达OpenFlow交换机后,OpenFlow交换机Packet-in给控制器,控制器会为网络下发指导查询组表的流表,并进行流表与组表关联。交换机参考流表,引用组表进行转发。举例如下:
上条流表的动作为引用组表4096,组表4096详细内容如下:
OpenFlow的实践分析
1、实验目的:
- 能够运用 wireshark 对 OpenFlow 协议数据交互过程进行抓包;
- 能够借助包解析工具,分析与解释 OpenFlow协议的数据包交互过程与机制。
2、实验环境:
- VMware Workstion Pro 17
- Ubuntu22.04 Desktop amd64,且完整安装了Mininet;
3、基础实验要求:
- 在Mininet可视化界面搭建如下拓扑;
- 完成相关IP地址的配置,实现主机与主机之间的IP通信;
- 使用抓包软件Wireshark获取控制器与交换机之间的通信数据包进行分析;
1、配置网段
2、修改各主机IP地址并保存为py脚本文件
在Test-1.py最下放添加以下代码,不添加的话无法运行:
CLI(net)
net.stop()
if __name__ == '__main__':
setLogLevel('info')
myNetwork()
注意:net.stop()要和CLI(net)对齐,否则无法运行,因为在Python中对于代码的缩进有着极其严格的要求
3、先启动wireshark,再运行脚本
wireshark &
& 这个符号的意思是:将本行命令的运行结果放在后台执行,不占用命令行,运行脚本文件时也就无需再多开启一个终端界面了
运行wireshark &命令,并选择any模式进行抓包,开启另一个终端,命令行运行Test-1.py文件,运行pingall
python3 Test-1.py
pingall
4、过滤抓包结果,分析OpenFlow协议中交换机与控制器的消息交互过程
OFPT_HELLO 源端口6633 -> 目的端口38552,从控制器到交换机,控制器与交换机建立连接,使用的OpenFlow版本为1.0
也有源端口38552 -> 目的端口6633的,即交换机到控制器的另一个包,此处协议为openflow1.5,此时是交换机和控制器协商协议版本阶段,最后交换机向下兼容控制器的OpenFlow版本(1.0)
OFPT_FEATURES_REQUEST 源端口6633 -> 目的端口38552,从控制器到交换机,控制器请求交换器的特征信息,使用的是OpenFlow 1.0版本
OFPT_SET_CONFIG 源端口6633 -> 目的端口38552,从控制器到交换机,控制器要求交换机按照所给出的信息进行配置
OFPT_PORT_STATUS 源端口49032 -> 目的端口6633,从交换机到控制器,当交换机端口发生变化时,交换机告知控制器相应的端口状态
OFPT_FEATURES_REPLY 源端口38552 -> 目的端口6633,从交换机到控制器,交换机告知控制器它的特征信息
OFPT_PACKET_IN 源端口38556 -> 目的端口6633,从交换机到控制器,交换机告知控制器有数据包进来,请求控制器指示
OFPT_PACKET_OUT 源端口6633 -> 目的端口38556,从控制器到交换机交换机告知控制器有数据包进来,控制器要求交换机按照所给出的action进行处理
OFPT_FLOW_MOD 源端口6633 -> 目的端口53712,从控制器到交换机,控制器对交换机进行流表的添加、删除、变更等操作(这里重新做了一下所以端口变了)
从抓到的包协议字段可以看出,交换机与控制键建立通信时使用的是TCP协议
4、进阶实验要求:
将抓包结果对照OpenFlow源码,了解OpenFlow主要消息类型对应的数据结构定义。相关数据结构可在openflow安装目录openflow/include/openflow当中的openflow.h头文件中查询到
1. HELLO
/* Header on all OpenFlow packets. */ `OpenFlow包的所有头部`
struct ofp_header {
uint8_t version; /* OFP_VERSION. */ `版本`
uint8_t type; /* One of the OFPT_ constants. */ `类型`
uint16_t length; /* Length including this ofp_header. */ `头部长度`
uint32_t xid; /* Transaction id associated with this packet.
Replies use the same id as was in the request
to facilitate pairing. */ `配对ID`
2. FEATURES_REQUEST
源码参数格式与HELLO相同,与上述ofp_header结构体中数据相同,不同的只有TYPE的值
3.SET_CONFIG
/* Switch configuration. */ `交换机配置
`
struct ofp_switch_config {
struct ofp_header header; `调用ofp_header `
uint16_t flags; /* OFPC_* flags. */ `标签`
uint16_t miss_send_len; /* Max bytes of new flow that datapath should
send to the controller. */ `数据路径应发送到控制器的新流的最大字节数`
};
4. PORT_STATUS
/* A physical port has changed in the datapath */ `数据路径中物理端口的更改`
struct ofp_port_status {
struct ofp_header header; `调用ofp_header`
uint8_t reason; /* One of OFPPR_*. */ `发生原因`
uint8_t pad[7]; /* Align to 64-bits. */ `将长度调整对其到64位`
struct ofp_phy_port desc;
};
5. FEATURES_REPLY
struct ofp_switch_features {
struct ofp_header header; `调用ofp_header`
uint64_t datapath_id; /* Datapath unique ID. The lower 48-bits are for `数据路径唯一 ID`
a MAC address, while the upper 16-bits are
implementer-defined. */
uint32_t n_buffers; /* Max packets buffered at once. */ `一次缓冲的最大数据包数`
uint8_t n_tables; /* Number of tables supported by datapath. */ `数据路径支持的表数`
uint8_t pad[3]; /* Align to 64-bits. */ `将长度调整对其到64位`
/* Features. */ `特征`
uint32_t capabilities; /* Bitmap of support "ofp_capabilities". */ `支持"ofp_capabilities"`
uint32_t actions; /* Bitmap of supported "ofp_action_type"s. */ `支持"ofp_action_type"`
/* Port info.*/ `端口信息`
struct ofp_phy_port ports[0]; /* Port definitions. The number of ports `端口定义 端口数是从标头中的长度字段推断出来的`
is inferred from the length field in
the header. */
};
/* Description of a physical port */
struct ofp_phy_port {
uint16_t port_no;
uint8_t hw_addr[OFP_ETH_ALEN];
char name[OFP_MAX_PORT_NAME_LEN]; /* Null-terminated */
uint32_t config; /* Bitmap of OFPPC_* flags. */
uint32_t state; /* Bitmap of OFPPS_* flags. */
/* Bitmaps of OFPPF_* that describe features. All bits zeroed if
* unsupported or unavailable. */
uint32_t curr; /* Current features. */
uint32_t advertised; /* Features being advertised by the port. */
uint32_t supported; /* Features supported by the port. */
uint32_t peer; /* Features advertised by peer. */
};
6.PACKET_IN
有两种情况:
1.交换机查找流表,发现没有匹配条目
enum ofp_packet_in_reason {
OFPR_NO_MATCH, /* No matching flow. */
OFPR_ACTION /* Action explicitly output to controller. */
};
2.有匹配条目,对应的action是OUTPUT=CONTROLLER,固定收到向控制器发送包
struct ofp_packet_in {
struct ofp_header header;
uint32_t buffer_id; /* ID assigned by datapath. */
uint16_t total_len; /* Full length of frame. */
uint16_t in_port; /* Port on which frame was received. */
uint8_t reason; /* Reason packet is being sent (one of OFPR_*) */
uint8_t pad;
uint8_t data[0]; /* Ethernet frame, halfway through 32-bit word,
so the IP header is 32-bit aligned. The
amount of data is inferred from the length
field in the header. Because of padding,
offsetof(struct ofp_packet_in, data) ==
sizeof(struct ofp_packet_in) - 2. */
};
7. PACKET_OUT
struct ofp_packet_out {
struct ofp_header header;
uint32_t buffer_id; /* ID assigned by datapath (-1 if none). */
uint16_t in_port; /* Packet's input port (OFPP_NONE if none). */
uint16_t actions_len; /* Size of action array in bytes. */
struct ofp_action_header actions[0]; /* Actions. */
/* uint8_t data[0]; */ /* Packet data. The length is inferred
from the length field in the header.
(Only meaningful if buffer_id == -1.) */
};
8. FLOW_MOD
struct ofp_flow_mod {
struct ofp_header header;
struct ofp_match match; /* Fields to match */
uint64_t cookie; /* Opaque controller-issued identifier. */
/* Flow actions. */
uint16_t command; /* One of OFPFC_*. */
uint16_t idle_timeout; /* Idle time before discarding (seconds). */
uint16_t hard_timeout; /* Max time before discarding (seconds). */
uint16_t priority; /* Priority level of flow entry. */
uint32_t buffer_id; /* Buffered packet to apply to (or -1).
Not meaningful for OFPFC_DELETE*. */
uint16_t out_port; /* For OFPFC_DELETE* commands, require
matching entries to include this as an
output port. A value of OFPP_NONE
indicates no restriction. */
uint16_t flags; /* One of OFPFF_*. */
struct ofp_action_header actions[0]; /* The action length is inferred
from the length field in the
header. */
};
struct ofp_action_header {
uint16_t type; /* One of OFPAT_*. */
uint16_t len; /* Length of action, including this
header. This is the length of action,
including any padding to make it
64-bit aligned. */
uint8_t pad[4];
};