在前面搭建好mininet的环境后。本文将进一步搭建ryu的环境,并基于此实现一个简单网络的负载均衡。
SDN(Software-Defined Networking)软件定义网络是一种网络架构和技术,通过将网络控制平面与数据转发平面分离,实现网络的集中管理和控制。传统的网络架构中,网络设备(如交换机和路由器)通常同时负责数据转发和网络控制,而SDN将这两个功能分开,将网络控制逻辑集中在一个中心控制器上。
而ryu是一个开源的软件定义网络控制器平台。它是一个用Python编写的SDN控制器框架,旨在简化SDN应用程序的开发。
去github上克隆对应的库并进行安装。
git clone https://github.com/osrg/ryu.git
cd ryu
pip install -r tools/pip-requires
python setup.py install
随后使用ryu-manager测试安装是否成功,如下则代表安装成功。
cd ryu/ryu/app
ryu-manager example_switch_13.py
loading app example_switch_13.py
loading app ryu.controller.ofp_handler
instantiating app example_switch_13.py of ExampleSwitch13
instantiating app ryu.controller.ofp_handler of OFPHandler
关于使用python编写ryu相关代码可参考如下链接:
https://ryu.readthedocs.io/en/latest/writing_ryu_app.html
https://github.com/knetsolutions/learn-sdn-with-ryu/blob/master/ryu_part1.md
关于使用python编写mininet相关代码可参考如下链接:
https://github.com/mininet/mininet/wiki/Introduction-to-Mininet
首先需要采用mininet搭建一个简单的拓扑网络,其中各个函数作用如下表所示。
api | 作用 |
---|---|
Topo | Mininet拓扑结构的基类 |
build() | 在自定义拓扑网络类中需要重写的方法。其中的参数n将会自动通过Topo.__init()__传递给Topo基类。 |
addSwitch() | 将一个交换机添加到拓扑中并返回交换机名称。 |
addHost() | 将一个主机添加到拓扑中并返回主机名称。 |
addLink() | 向拓扑中添加一个双向连接(并返回一个连接键link key)。在Mininet中,除非有额外的声明,链接都是双向的。 |
Mininet | 创建和管理网络的类。 |
pingAll() | 通过尝试让所有节点互相ping对方来测试连通性。 |
start() | 启动创建的网络。 |
stop() | 关闭创建的网络。 |
net.hosts | 网络中的所有主机。 |
dumpNodeConnections() | 导出拓扑结构。 |
setLogLevel( ‘info’ / ‘debug’ / ‘output’ ) | 设置Mininet日志的默认输出级别;建议使用’info’级别,它可以提供有用信息而不会太过冗杂。 |
根据上述函数,编写一个简单的线形拓扑网络,代码如下所示。通过Mininet搭建了一个3个主机节点的网络,并导出了网络结构,并通过节点之间相互ping来进行测试。
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.util import dumpNodeConnections
from mininet.log import setLogLevel
class SimpleTopo(Topo):
def __init__(self):
Topo.__init__(self)
host1 = self.addHost('h1')
host2 = self.addHost('h2')
host3 = self.addHost('h3')
switch1 = self.addSwitch("s1")
switch2 = self.addSwitch("s2")
switch3 = self.addSwitch("s3")
self.addLink(switch1, host1)
self.addLink(switch2, host2)
self.addLink(switch3, host3)
self.addLink(switch1, switch2)
self.addLink(switch2, switch3)
topos = {'mytopo': (lambda: SimpleTopo())}
其中采用该自定义网络结构进行启动的命令如下所示:
sudo mn --custom easytopo.py --topo mytopo
得到结果如下
*** Creating network
*** Adding controller
*** Adding hosts:
h1 h2 h3
*** Adding switches:
s1 s2 s3
*** Adding links:
(s1, h1) (s1, s2) (s2, h2) (s2, s3) (s3, h3)
*** Configuring hosts
h1 h2 h3
*** Starting controller
c0
*** Starting 3 switches
s1 s2 s3 ...
*** Starting CLI:
mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 h3
h2 -> h1 h3
h3 -> h1 h2
*** Results: 0% dropped (6/6 received)
随后再进行 ryu程序的编写,这里主要参考ryu里的例程进行说明。
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_2
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types
class SimpleSwitch12(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_2.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(SimpleSwitch12, self).__init__(*args, **kwargs)
self.mac_to_port = {}
def add_flow(self, datapath, port, dst, src, actions):
ofproto = datapath.ofproto
match = datapath.ofproto_parser.OFPMatch(in_port=port,
eth_dst=dst,
eth_src=src)
inst = [datapath.ofproto_parser.OFPInstructionActions(
ofproto.OFPIT_APPLY_ACTIONS, actions)]
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, cookie=0, cookie_mask=0, table_id=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=0, buffer_id=ofproto.OFP_NO_BUFFER,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
flags=0, match=match, instructions=inst)
datapath.send_msg(mod)
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
in_port = msg.match['in_port']
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
# ignore lldp packet
return
dst = eth.dst
src = eth.src
dpid = datapath.id
self.mac_to_port.setdefault(dpid, {})
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
if dst in self.mac_to_port[dpid]:
out_port = self.mac_to_port[dpid][dst]
else:
out_port = ofproto.OFPP_FLOOD
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
# install a flow to avoid packet_in next time
if out_port != ofproto.OFPP_FLOOD:
self.add_flow(datapath, in_port, dst, src, actions)
data = None
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data
out = datapath.ofproto_parser.OFPPacketOut(
datapath=datapath, buffer_id=msg.buffer_id, in_port=in_port,
actions=actions, data=data)
datapath.send_msg(out)
1.OFP_VERSIONS为设置的openflow的版本。
2.__init__为官方文档里所定义的构造方法。
3.其中_packet_in_handler函数的装饰器set_ev_cls的第一个参数表示调用该函数的事件类型。即每次收到packet_in 消息时,都会调用此函数。第二个参数指示开关的状态。使用“MAIN_DISPATCHER”作为第二个参数意味着仅在ryu与交换机协商完成后调用此函数。
4.ev.msg是一个表示packet_in的数据结构。
5.msg.dp代表一个数据路径(交换机)。
6.dp.ofproto 和 dp.ofproto_parser 代表 ryu 和交换机协商的 OpenFlow 协议的对象。7.actions代表动作列表。
8.OFPActionOutput指定将数据包发送出的交换机端口。如果使用 OFPP_FLOOD 标志则指示数据包应在所有端口上发送出去。
9.OFPPacketOut 用于构建 packet_out 消息。
10.对于datapath.send_msg 方法,ryu会构建数据格式并将其发送到交换机。
11.则整个_packet_in_handler函数先通过获取事件中的消息(msg),数据路径(datapath)和OpenFlow协议版本(ofproto)以及输入端口(in_port)。过程中判断以太网类型是否为LLDP类型,如果是则忽略该数据包。同时获取目的MAC地址(dst)和源MAC地址(src)并获取交换机的标识符(dpid),并将其作为键值存储在self.mac_to_port字典中。如果目的MAC地址在self.mac_to_port字典中存在,则输出端口为目的MAC地址对应的端口;否则,输出端口为所有端口(OFPP_FLOOD
)。再根据输出端口构建动作(actions)。如果输出端口不是洪泛端口,则调用add_flow方法,安装流表规则以避免下次数据包处理时再次触发PacketIn事件。
如果消息没有缓冲区ID,则直接获取消息里的数据。最后,发送数据包输出消息给交换机。
12.对于add_flow函数,则是根据协议类型与输入参数构建一个匹配条件,构建流表修改信息发送给交换机。
最后起两个终端对上述代码进行测试。首先启动ryu控制器
ryu-manager simple.py
loading app simple.py
loading app ryu.controller.ofp_handler
instantiating app simple.py of SimpleSwitch12
instantiating app ryu.controller.ofp_handler of OFPHandler
再启动mininet拓扑网络,控制器选项设置为remote
sudo mn --custom easytopo.py --topo mytopo --controller remote
*** Creating network
*** Adding controller
Connecting to remote controller at 127.0.0.1:6653
*** Adding hosts:
h1 h2 h3
*** Adding switches:
s1 s2 s3
*** Adding links:
(s1, h1) (s1, s2) (s2, h2) (s2, s3) (s3, h3)
*** Configuring hosts
h1 h2 h3
*** Starting controller
c0
*** Starting 3 switches
s1 s2 s3 ...
*** Starting CLI:
mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 h3
h2 -> h1 h3
h3 -> h1 h2
*** Results: 0% dropped (6/6 received)
可以看到成功ping通