0%

浣熊学RYU

前言

最近在搞SDN大赛,我们的初赛题目是使用SDN网络实现对SYN Flood 攻击的防御,因此我就开始了SDN的学习之旅,根据旗神的推荐,我们是使用的控制器是RYU,那我们就先看看一个简单的样例吧——simple_switch.py(这个文件在ryu/app/simple_switch_13.py,在不同的版本中,最后的数字是不一样的)。

本文内容分来自于RYUbook这本书,是台湾RYU团队翻译的。

类别的定义和初始化

为了实现Ryu程序,需要继承ryu.base.app_manager.RyuApp这个类,然后将OFP_VERSION设定为OpenFlow 1.3.
之后定义MAC地址表mac_port。
在OpenFlow协议中,有些程序需要定义在OpenFlow交换机和Controller之间的链接,比如握手(handshank),这些东西我们会在后面讲解,暂时先不用管。

1
2
3
4
5
6
7
class SimpleSwitch13(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

def __init__(self, *args, **kwargs):
super(SimpleSwitch13, self).__init__(*args, **kwargs)
self.mac_to_port = {}
# ...

事件管理(Event handler)

对于RYU来说,接受到的任何OpenFlow消息都会立即产生一个相对应得事件(Event),而我们的RYU程序需要对这个事件进行相应的处理。
事件管理(Event handler)需要一个事件对象(Event Object)作为参数,并且使用ryu.controller.handler.set_ev_cls修饰(Decorator)函数。不懂得可以去看Python教程了。

set_ev_cls需要指定事件的类别和交换机的状态参数。

事件的类别为ryu.controller.ofp_event.EventOFP+<OpenFlow讯息名称>,例如Pack-in事件,就是EventOFPPacketIn。具体的事件定义,请查看Ryu的API手册。
对于状态来说,需要指定下面的表格中的一项。

名称 说明
ryu.controller.handler.HANDSHAKE_DISPATCHER 交换Hello消息
ryu.controller.handler.CONFIG_DISPATCHER 接收SwitchFeatures消息
ryu.controller.handler.MAIN_DISPATCHER 一般状态
ryu.controller.handler.DEAD_DISPATCHER 连接中断

添加缺省流(Table-miss Flow Entry)

OpenFlow交换机与Controller握手完成后,会在流表中添加缺省流(Table-miss Flow Entry),为接收Packet-In消息做准备。

1
2
3
4
5
6
7
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# ...

ev.msg是用来存储对应事件的OpenFlow消息类的对象,在这个例子中指的是ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures。

msg.datapath是用来存储OpenFlow交换机ryu.controller.controller.Datapath类所对应的对象。
Datapath类是用来处理OpenFlow交换机的重要信息,例如:执行交换机的通讯和触发接收消息的相关事件。

Ryu应用程序使用的主要属性如下:

名称 说明
id 连接的OpenFlow交换机的ID
ofproto 表示当前使用的OpenFlow版本的对应ofproto module。目前可用的参数如下:
ryu.ofproto.ofproto_v1_0
ryu.ofproto.ofproto_v1_2
ryu.ofproto.ofproto_v1_3
ryu.ofproto.ofproto_v1_4
ofproto_parser 和ofproto一样,表示ofproto_parser module。目前可用的参数如下:
ryu.ofproto.ofproto_v1_0_parser
ryu.ofproto.ofproto_v1_2_parser
ryu.ofproto.ofproto_v1_3_parser
ryu.ofproto.ofproto_v1_4_parser

Ryu应用程序中Datapath类的主要方法是send_msg(msg),发送OpenFlow消息。
msg是将要发送的OpenFlow消息,是ryu.ofproto.ofproto_parser.MsgBox的子类。

交换机本身不仅仅使用收到的Switch features消息,还会在事件被处理时添加Table-miss Flow Entry。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def switch_features_handler(self, ev):

# ...

# install table-miss flow Entry
#
# We specify NO BUFFER to max_len of the output action due to
# OVS bug. At this moment, if we specify a lesser number, e.g.,
# 128, OVS will send Packet-In with invalid buffer_id and
# truncated packet data. In that case, we cannot output packets
# correctly.
match = parser.OFPMatch() # 定义一个match(属于OFPMatch类),它默认可以匹配所有的包
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, # 定义一个动作,将类型设置为Output Action(OFPActionOutput类)
ofproto.OFPCML_NO_BUFFER)] # 将包的目的地设为Controller, OFPCML_NO_BUFFER被定义为max_len
self.add_flow(datapath, 0, match, actions) # 将优先级设为0,并用add_flow()发送Flow Mod消息

Table-miss Flow Entry的优先级为0(最低),并且这个流表项可以匹配所有的包,而且这个流表项所的动作通常是output action,并且输出端指向Controller。因此,当某个包没有匹配到其他的流表项的时候,就会触发Pack-In事件。
即:当交换机收到Packet后,匹配流表失败,会将这个Packet发送给Table-miss Flow Entry表项所对应的端口,也就是控制器。

Packet-In 消息

为了接收未知包,需要对Packet-In事件管理。

1
2
3
4
5
6
7
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

# ...

OFPPacketIn类的常用属性如下:

名称 说明
match 用来存储接收包的Match信息,是ryu.ofproto.ofproto_v1_3_parser.OFPMatch类
data 收到的包的二进制数据
total_len 收到的包的长度
buffer_id 如果收到的包是在OpenFlow交换机的Buffer中,则需要指定交换机的ID,如果不在Buffer中,则指定为ryu.ofproto.ofproto_v1_3.OFP_NO_BUFFER

更新MAC地址表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def _packet_in_handler(self, ev):

# ...

in_port = msg.match['in_port'] # 从OFPPacketIn类的match中得到in_port信息

pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0] # 从Ethernet头中获取数据

dst = eth.dst # 目的MAC地址
src = eth.src # 源MAC地址

dpid = datapath.id
self.mac_to_port.setdefault(dpid, {}) # 通过datapath ID区分不同的OpenFlow交换机

self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
# learn a mac address to avoid FLOOD next time.
self.mac_to_port[dpid][src] = in_port # 更新MAC地址表

# ...

判断包的输出端口

若目的MAC地址在MAC地址表中,则应该判断这个包的输出端口;如果不在MAC地址表中,则OUTPUT action类应该指定flooding(OFPP_FLOOD)为输出端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def _packet_in_handler(self, ev):
# ...

if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD

actions = [parser.OFPActionOutput(out_port)]

# 若找到了目的MAC地址,则添加到交换机的流表中
if out_port != ofproto.OFPP_FLOOD:
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
self.add_flow(datapath, 1, match, actions)

# ...

不同于缺省流表项(Table-miss Flow Entry),在添加新的流表项的时候,需要指明优先级,优先级数值越大,优先级越高。同时还要指明match条件,在样例中,是指定了in_port和目的MAC地址,只要满足这个条件,就会调用这个流。

添加对流的处理

1
2
3
4
5
6
7
8
def add_flow(self, datapath, priority, match, actions):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]

# ...

这一块没怎么看懂,看懂了再更新。。。