SDN实验(四)——Learning Switch自学习交换机

发布时间:2025-12-10 11:22:38 浏览次数:10

SDN实验(四)——Learning Switch自学习交换机

  • 一、自学习交换机原理
    • (一)普通交换机实现
    • (二)SDN交换机实现
  • 二、自学习交换机代码实现
    • (一)代码
    • (二)代码讲解
    • (三)实验
  • 三、扩展
    • (一)pkt = packet.Packet(msg.data)
    • (二)eth_pkt = pkt.get_protocol(ethernet.ethernet)
    • (三)eth_pkt = pkt.get_protocol(ethernet.ethernet)

一、自学习交换机原理

(一)普通交换机实现

  交换机MAC地址表记录了统一网段中的各个主机对应交换机的端口和主机的MAC地址。
  当主机A要和主机B通信时,初始交换机MAC表是空的,会先记录主机A的MAC地址和对应的交换机端口,然后查找交换机MAC中是否有目标MAC地址,没有找到,会向其他所有端口泛洪查找

  泛洪,通知其他主机。主机C接收到数据包,发现不是自己的,则不处理,丢弃数据包。当主机B接收后,发现是找自己的,则可以进行消息通信。交换机先进行MAC学习,记录主机B的MAC信息,再进行查表转发,单播发送给主机A

(二)SDN交换机实现

  SDN中交换机不存储MAC表,(datapath)只存流表(flow table)。其地址学习操作由控制器(控制器中包含MAC 地址表)实现,之后控制器下发流表项给交换机

  • 初始状态

      Flow table 为空白的状况。
      将 host A 接到端口 1,host B 接到端口 4,host C 接到端口 3。

  • host A -->host B
      当 host A 向 host B 发送数据包。这时后会触发 Packet-In 事件。host A 的 MAC 地址会被端口 1 给记录下来。由于 host B 的 MAC 地址尚未被学习,因此会进行 Flooding 并将数据包往 host B 和 host C 发送。

    Packet-In:
      in-port:1
      eth-dst:host B
      eth-src:host A
    Packet-Out:
      action:OUTPUT:Flooding

  • host B–>host A
      数据包从 host B 向 host A 返回时,在 Flow table 中新增一笔 Flow Entry,并将封包转送到端口 1。因此该封包并不会被 host C 收到。

    Packet-In:
      in-port:4
      eth-dst:host A
      eth-src:host B
    Packet-Out:
      action:OUTPUT:port 1

  • host A–>host B
      再一次,host A 向 host B 发送数据包,在 Flow table 中新增一个 Flow Entry 接着转送封包到端口 4。

    Packet-In:
      in-port:1
      eth-dst:host B
      eth-src:host A
    Packet-Out:
      action:OUTPUT:port 4

  • 二、自学习交换机代码实现

    (一)代码

    from ryu.base import app_managerfrom ryu.ofproto import ofproto_v1_3from ryu.controller import ofp_eventfrom ryu.controller.handler import set_ev_clsfrom ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHERfrom ryu.lib.packet import packetfrom ryu.lib.packet import ethernetclass SelfLearnSwitch(app_manager.RyuApp):OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] #set openflow protocol version while we supportdef __init__(self,*args,**kwargs):super(SelfLearnSwitch,self).__init__(*args,**kwargs)#set a data construction to save MAC Address Tableself.Mac_Port_Table={}@set_ev_cls(ofp_event.EventOFPSwitchFeatures)def switch_features_handler(self,ev):'''manage the initial link, from switch to controller'''#first parse event to get datapath and openflow protocol msg = ev.msgdatapath = msg.datapathofproto = datapath.ofprotoofp_parser = datapath.ofproto_parserself.logger.info("datapath: %s link to controller",datapath.id)#secondly set match and actionmatch = ofp_parser.OFPMatch() #all data message match successfulactions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)] #set receive port and buffer for switch#add flow and send it to switch in add_flowself.add_flow(datapath,0,match,actions,"default flow entry")def add_flow(self,datapath,priority,match,actions,extra_info):"""add flow entry to switch"""#get open flow protocol infomationofproto = datapath.ofprotoofp_parser = datapath.ofproto_parser#set instruction infomation from openflow protocol 1.3inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]#set flow entry modmod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst)print("send "+extra_info)#send flow entry to switchdatapath.send_msg(mod)@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)def packet_in_handler(self,ev):'''manage infomation from switch'''#first parser openflow protocolmsg = ev.msgdatapath = msg.datapathofproto = datapath.ofprotoofp_parser = datapath.ofproto_parser#get datapath id from datapath, and save dpid into MAC table (default)dpid = datapath.idself.Mac_Port_Table.setdefault(dpid, {})#analysize packet, get ethernet data, get host MAC infopkt = packet.Packet(msg.data)eth_pkt = pkt.get_protocol(ethernet.ethernet)dst = eth_pkt.dstsrc = eth_pkt.src#get switch port where host packet send inin_port = msg.match['in_port']self.logger.info("Controller %s get packet, Mac address from: %s send to: %s , send from datapath: %s,in port is: %s",dpid,src,dst,dpid,in_port)#save src data into dictionary---MAC address tableself.Mac_Port_Table[dpid][src] = in_port#query MAC address table to get destinction host`s port from current datapath#---first: find port to send packet#---second: not find port,so send packet by floodif dst in self.Mac_Port_Table[dpid]:Out_Port = self.Mac_Port_Table[dpid][dst]else:Out_Port = ofproto.OFPP_FLOOD#set match-action from above statusactions = [ofp_parser.OFPActionOutput(Out_Port)]#add a new flow entry to switch by add_flowif Out_Port != ofproto.OFPP_FLOOD: #if Out_port == ofproto.OFPP_FLOOD ---> flow entry == default flow entry, it already existmatch = ofp_parser.OFPMatch(in_port=in_port,eth_dst = dst)self.add_flow(datapath, 1, match, actions,"a new flow entry by specify port")self.logger.info("send packet to switch port: %s",Out_Port)#finally send the packet to datapath, to achive self_learn_switchOut = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,in_port=in_port,actions=actions,data=msg.data)datapath.send_msg(Out)

    (二)代码讲解

      前半部分的代码和集线器部分的异曲同工,就不多加赘述了,重点在讲解packet_in_handler之后的代码。

    from ryu.base import app_managerfrom ryu.ofproto import ofproto_v1_3from ryu.controller import ofp_eventfrom ryu.controller.handler import set_ev_clsfrom ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHERfrom ryu.lib.packet import packetfrom ryu.lib.packet import ethernetclass SelfLearnSwitch(app_manager.RyuApp):OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] #设置OpenFlow协议版本def __init__(self,*args,**kwargs):super(SelfLearnSwitch,self).__init__(*args,**kwargs)#设置保存MAC地址表的数据结构self.Mac_Port_Table={}@set_ev_cls(ofp_event.EventOFPSwitchFeatures)def switch_features_handler(self,ev):'''交换机和控制器的初始连接其实这个和之前的集线器的代码是相似的'''#解析OpenFlow协议信息msg = ev.msgdatapath = msg.datapathofproto = datapath.ofprotoofp_parser = datapath.ofproto_parserself.logger.info("datapath: %s link to controller",datapath.id)#设置匹配信息和动作列表match = ofp_parser.OFPMatch() #all data message match successfulactions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)] #set receive port and buffer for switch#下发默认流表self.add_flow(datapath,0,match,actions,"default flow entry")def add_flow(self,datapath,priority,match,actions,extra_info):"""add flow entry to switch"""#get open flow protocol infomationofproto = datapath.ofprotoofp_parser = datapath.ofproto_parser#set instruction infomation from openflow protocol 1.3inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)]#set flow entry modmod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst)print("send "+extra_info)#send flow entry to switchdatapath.send_msg(mod)@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)def packet_in_handler(self,ev):'''manage infomation from switch'''#解析OpenFlow协议信息msg = ev.msgdatapath = msg.datapathofproto = datapath.ofprotoofp_parser = datapath.ofproto_parser#获取datapath(虚拟交换机的id),用dpid初始化一个键值dpid = datapath.idself.Mac_Port_Table.setdefault(dpid, {})#分析packert数据包,因为转发的包,都是基于以太网协议的,所以我们需要用到以太网协议进行解析,获取源MAC和目的MACpkt = packet.Packet(msg.data)eth_pkt = pkt.get_protocol(ethernet.ethernet)dst = eth_pkt.dstsrc = eth_pkt.src#获取datapath的数据输入端口in_port = msg.match['in_port']self.logger.info("Controller %s get packet, Mac address from: %s send to: %s , send from datapath: %s,in port is: %s",dpid,src,dst,dpid,in_port)#将源MAC地址保存,学习,放入MAC表中self.Mac_Port_Table[dpid][src] = in_port#查询MAC表,是否有目标MAC地址的键值#如果找到,我们则按照该端口发送#如果没有找到,我们需要泛洪发送给下一个(或者下几个)交换机,依次查询if dst in self.Mac_Port_Table[dpid]:Out_Port = self.Mac_Port_Table[dpid][dst]else:Out_Port = ofproto.OFPP_FLOOD#开始设置match-actions匹配动作actions = [ofp_parser.OFPActionOutput(Out_Port)]#进行对应的流表项下发  《重点》if Out_Port != ofproto.OFPP_FLOOD: #if Out_port == ofproto.OFPP_FLOOD ---> flow entry == default flow entry, it already existmatch = ofp_parser.OFPMatch(in_port=in_port,eth_dst = dst)self.add_flow(datapath, 1, match, actions,"a new flow entry by specify port")self.logger.info("send packet to switch port: %s",Out_Port)#最后我们将之前交换机发送上来的数据,重新发给交换机Out = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,in_port=in_port,actions=actions,data=msg.data)#我们必须加上这个data,才可以将packet数据包发送回去《重点》不然会出错××××××datapath.send_msg(Out)

    (三)实验

  • 启动Ryu

  • 启动Mininet

  • Ryu进行响应

  • 网络可达,自学习交换机实现成功

  • 三、扩展

    (一)pkt = packet.Packet(msg.data)

      一个类,在Ryu/lib/packet/模块下,用于包的解码/编码。

    class Packet(StringifyMixin):"""A packet decoder/encoder class.An instance is used to either decode or encode a single packet.*data* is a bytearray to describe a raw datagram to decode.  data是一个未加工的报文数据, 即msg.data直接从事件的msg中获取的数据When decoding, a Packet object is iteratable.Iterated values are protocol (ethernet, ipv4, ...) headers and the payload.Protocol headers are instances of subclass of packet_base.PacketBase.The payload is a bytearray. They are iterated in on-wire order.*data* should be omitted when encoding a packet."""# Ignore data field when outputting json representation._base_attributes = ['data']def __init__(self, data=None, protocols=None, parse_cls=ethernet.ethernet):  协议解析,默认是按照以太网协议super(Packet, self).__init__()  self.data = dataif protocols is None:self.protocols = []else:self.protocols = protocolsif self.data:self._parser(parse_cls)

    (二)eth_pkt = pkt.get_protocol(ethernet.ethernet)

      返回与指定协议匹配的协议列表。从packet包中获取协议信息(协议包含我们需要的dst,src等)

    class Packet(StringifyMixin):def add_protocol(self, proto):"""Register a protocol *proto* for this packet.This method is legal only when encoding a packet.When encoding a packet, register a protocol (ethernet, ipv4, ...)header to add to this packet.Protocol headers should be registered in on-wire order before callingself.serialize."""self.protocols.append(proto)def get_protocols(self, protocol):"""Returns a list of protocols that matches to the specified protocol."""if isinstance(protocol, packet_base.PacketBase):protocol = protocol.__class__assert issubclass(protocol, packet_base.PacketBase)return [p for p in self.protocols if isinstance(p, protocol)]

    (三)eth_pkt = pkt.get_protocol(ethernet.ethernet)

      一个类,也在Ryu/lib/packet/模块下,用于以太网报头编码器/解码器类。

    class ethernet(packet_base.PacketBase):"""Ethernet header encoder/decoder class.An instance has the following attributes at least.MAC addresses are represented as a string like '08:60:6e:7f:74:e7'.__init__ takes the corresponding args in this order.============== ==================== =====================Attribute Description Example============== ==================== =====================dst destination address 'ff:ff:ff:ff:ff:ff'src source address '08:60:6e:7f:74:e7'ethertype ether type 0x0800============== ==================== ====================="""_PACK_STR = '!6s6sH'_MIN_LEN = struct.calcsize(_PACK_STR)_MIN_PAYLOAD_LEN = 46_TYPE = {'ascii': ['src', 'dst']}def __init__(self, dst='ff:ff:ff:ff:ff:ff', src='00:00:00:00:00:00',ethertype=ether.ETH_TYPE_IP):super(ethernet, self).__init__()self.dst = dstself.src = srcself.ethertype = ethertype@classmethoddef parser(cls, buf):dst, src, ethertype = struct.unpack_from(cls._PACK_STR, buf)return (cls(addrconv.mac.bin_to_text(dst),addrconv.mac.bin_to_text(src), ethertype),ethernet.get_packet_type(ethertype),buf[ethernet._MIN_LEN:])def serialize(self, payload, prev):# Append padding if the payload is less than 46 bytes longpad_len = self._MIN_PAYLOAD_LEN - len(payload)if pad_len > 0:payload.extend(b'\x00' * pad_len)return struct.pack(ethernet._PACK_STR,addrconv.mac.text_to_bin(self.dst),addrconv.mac.text_to_bin(self.src),self.ethertype)@classmethoddef get_packet_type(cls, type_):"""Override method for the ethernet IEEE802.3 Length/Typefield (self.ethertype).If the value of Length/Type field is less than or equal to1500 decimal(05DC hexadecimal), it means Length interpretationand be passed to the LLC sublayer."""if type_ <= ether.ETH_TYPE_IEEE802_3:type_ = ether.ETH_TYPE_IEEE802_3return cls._TYPES.get(type_)

    参考:

  • 山上有风景:Ryu应用开发:https://www.cnblogs.com/ssyfj/p/11731565.html
  • Ryubook:http://osrg.github.io/ryu-book/en/html/
  • Ryu官方文档:https://ryu.readthedocs.io/en/latest/

  • 关注微信公众号:“程序员小菜鸡”免费获取资源



    欢迎查看我的github博客:Welcome To Ryan’s Home


    需要做网站?需要网络推广?欢迎咨询客户经理 13272073477