实验目标

  • 在Ryu上开发二层MAC自学习交换(以下简称二层交换)应用
  • 二层交换应用是最基础的计算机网络应用,通过开发二层交换应用,使用Ryu控制器开发网络应用

    实验原理

  • 模拟实现了二层交换机的运作原理:

    • 交换机具有自学习能力,能记录源MAC地址和入端口的对应关系。
    • 当一个数据包从某端口进入交换机,交换机将记录其源MAC地址和入端口的对应关系,然后查询对应的目的MAC地址对应的端口记录是否存在。
    • 若查询成功,则将数据包转发到查询所得端口,若查询失败,则进行泛洪处理。
  • 二层交换应用被广泛应用于局域网环境,是基础的计算机网络应用。

  • 但是二层交换应用无法单独应用在有回环的网络拓扑中,还需要其他协议或者算法来辅助,解决广播数据包在环路中泛洪产生的广播风暴。
  • 所以在测试本应用时,需搭建无环拓扑。
  • 若搭建有环拓扑,则需运行STP(Spanning Tree Protocol)协议或者其他算法来解决环路风暴问题。
  • 示例应用使用OpenFlow1.3版本协议,详细代码如下:
  1. #coding:utf-8
  2. from ryu.base import app_manager
  3. from ryu.controller import ofp_event
  4. from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
  5. from ryu.controller.handler import set_ev_cls
  6. from ryu.ofproto import ofproto_v1_3
  7. from ryu.lib.packet import packet
  8. from ryu.lib.packet import ethernet
  9. class SimpleSwitch13(app_manager.RyuApp):
  10. # 继承RyuApp父类,RyuApp是Ryu应用的抽象功能描述类。RyuApp类完成了Ryu应用所需要完成的基本操作
  11. # 从而使得开发者在开发应用时专注于业务逻辑的实现
  12. OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
  13. def __init__(self, *args, **kwargs):
  14. super(SimpleSwitch13, self).__init__(*args,**kwargs)
  15. # mac_to_port字典用于保存MAC地址和交换机端口的对应关系
  16. self.mac_to_port = {}
  17. @set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
  18. def switch_features_handler(self, ev):
  19. # 当datapath(此处的datapath等同于交换网桥,可以大致理解为一台交换机)与控制器建立连接之后,会通过features消息回复datapath的特性。
  20. # switch_features_handler方法通过@set_ev_cls装饰器注册了EventOFPSwitchFeatures 事件。
  21. # 当EventOFPSwitchFeatures事件在CONFIG_DISPATCHER阶段生成时,就会被分发到switch_features_handler方法,
  22. # 从而执行处理方法,完成事件处理。
  23. # @set_ev_cls装饰器的第一个参数是事件类型,第二个参数为产生事件的状态。
  24. # Ryu的ofp_event模块定义了系统使用到的事件,包括OpenFlow事件和一些内部使用的自定义事件。
  25. # Ryu定义了HANDSHAKE_DISPATCHER、CONFIG_DISPATCHER、MAIN_DISPATCHER和DEAD_DISPATCHER四种状态,
  26. # 用于描述datapath 的OpenFlow 通道连接的通信状态。
  27. # switch_features_handler方法给datapath下发一条table-miss流表项
  28. # 用于指导交换机将匹配流表项失败的数据包用Packet-in的方式发送到控制器。
  29. datapath = ev.msg.datapath
  30. ofproto = datapath.ofproto
  31. parser = datapath.ofproto_parser
  32. match = parser.OFPMatch()
  33. actions =[parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, ofproto.OFPCML_NO_BUFFER)]
  34. self.add_flow(datapath, 0, match, actions)
  35. def add_flow(self, datapath, priority,match, actions):
  36. ofproto = datapath.ofproto
  37. parser = datapath.ofproto_parser
  38. inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
  39. mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
  40. idle_timeout=0, hard_timeout=0,
  41. match=match, instructions=inst)
  42. datapath.send_msg(mod)
  43. @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
  44. def _packet_in_handler(self, ev):
  45. # _packet_in_handler 方法注册了EventOFPPacketIn 事件
  46. # 当EventOFPPacketIn事件在MAIN_DISPATCHER状态下产生时,就会被分发给_packet_in_handler方法处理。
  47. # 处理方法首先对数据包进行必要的解析
  48. # 然后按照交换机dpid的入端口号为键值,将源MAC地址记录到mac_to_port字典中:mac_to_port[dpid][in_port]=SRC_MAC。
  49. # 然后查看其目的MAC地址是否在mac_to_port字典数据项的值中。
  50. # 若目的MAC地址不在表中,则将出端口设置为ofproto.OFPP_FLOOD,并通过Packetout报文指示交换机将数据包泛洪;
  51. # 若查询成功,则将出端口设置为查询所得端口;最后调用add_flow方法将流表项下发到datapath,从而完成二层交换应用。
  52. msg = ev.msg
  53. datapath = msg.datapath
  54. ofproto = datapath.ofproto
  55. parser = datapath.ofproto_parser
  56. in_port = msg.match['in_port']
  57. pkt = packet.Packet(msg.data)
  58. eth = pkt.get_protocols(ethernet.ethernet)[0]
  59. dst = eth.dst
  60. src = eth.src
  61. dpid = datapath.id
  62. self.mac_to_port.setdefault(dpid,{})
  63. self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
  64. # learn a mac address to avoid FLOOD next time.
  65. self.mac_to_port[dpid][src] = in_port
  66. if dst in self.mac_to_port[dpid]:
  67. out_port = self.mac_to_port[dpid][dst]
  68. else:
  69. out_port = ofproto.OFPP_FLOOD
  70. actions = [parser.OFPActionOutput(out_port)]
  71. # install a flow to avoid packet_in next time
  72. if out_port != ofproto.OFPP_FLOOD:
  73. match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
  74. self.add_flow(datapath, 1, match, actions)
  75. data = None
  76. if msg.buffer_id == ofproto.OFP_NO_BUFFER:
  77. data = msg.data
  78. out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
  79. in_port=in_port, actions=actions, data=data)
  80. datapath.send_msg(out)

运行实验

  • 写完以上代码之后将其保存为Python 文件, 如simple_switch_13_test.py,然后保存到ryu/app目录下
  • 由于这个文件是新加的,Ryu编译文件中并没有,所以还需要将Ryu重新编译安装
  • 重新安装之后,通过ryu-manager命令启动Ryu控制器并加载新写的应用即可
  • 具体命令如下

    1. cd ryu
    2. python setup.py install
    3. ryu-manager ryu/app/simple_switch_13_test.py
  • 启动Ryu之后,通过Mininet启动任意无环拓扑并连接到控制器。

    1. sudo mn --controller remote
  • 连接成功之后,在Mininet中进行pingall测试

  • 具体测试结果如图所示

image.png