一:自学习交换机(二层MAC交换机)的编程思路

(一)明确问题

如何实现软件定义的自学习交换机?

(二)设计解决方案

通过控制器来实现自学习交换算法,然后指导数据平面实现交换机操作

(三)确定具体的技术方案

控制器选用Ryu,数据平面通过Mininet模拟

(四)部署实施

在控制器上编程开发交换机应用,创建实验网络为验证方案做准备

(五)验证方案

运行程序,调试程序,验证程序

(六)优化

验证成功后,优化程序

二:自学习交换机原理

(一)普通交换机实现

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

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

(二)SDN中交换机实现

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

1.主机A向主机B发送信息,流表中只存在默认流表,告诉交换机将数据包发送给控制器。

2.控制器先进行MAC地址学习,记录主机A的MAC地址和其对应交换机端口,然后查询MAC地址表,查找主机B信息。没有则下发流表项告诉交换机先泛洪试试

3.泛洪后,主机C接收后丢弃数据包,不处理。主机B发现是寻找自己的,则进行消息回送,由于交换机流表中没有处理主机B到主机A的信息的流表项,所以只能向控制器发送数据包。控制器先学习主机B的MAC地址和对应交换机端口,之后查询MAC地址表,找到主机A的MAC信息,下发流表项,告诉交换机如何处理主机B->主机A的消息

4.注意:这里交换机的流表项中只存在主机B->主机A的流表项处理方案,不存在主机A->主机B的处理流表项(但是控制器MAC地址表中是存在主机B的信息),所以会在下一次数据传送中,控制器下发响应的流表项。但是其实可以实现(在3中一次下发两个流表项)

三:代码实现

(一)全部代码

from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller import ofp_event
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHER
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet class SelfLearnSwitch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] #set openflow protocol version while we support def __init__(self,*args,**kwargs):
super(SelfLearnSwitch,self).__init__(*args,**kwargs)
#set a data construction to save MAC Address Table
self.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.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser self.logger.info("datapath: %s link to controller",datapath.id) #secondly set match and action
match = ofp_parser.OFPMatch() #all data message match successful
actions = [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_flow
self.add_flow(datapath,,match,actions,"default flow entry") def add_flow(self,datapath,priority,match,actions,extra_info):
"""
add flow entry to switch
""" #get open flow protocol infomation
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser #set instruction infomation from openflow protocol 1.3
inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] #set flow entry mod
mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst) print("send "+extra_info)
#send flow entry to switch
datapath.send_msg(mod) @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
def packet_in_handler(self,ev):
'''
manage infomation from switch
''' #first parser openflow protocol
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser #get datapath id from datapath, and save dpid into MAC table (default)
dpid = datapath.id
self.Mac_Port_Table.setdefault(dpid, {}) #analysize packet, get ethernet data, get host MAC info
pkt = packet.Packet(msg.data)
eth_pkt = pkt.get_protocol(ethernet.ethernet)
dst = eth_pkt.dst
src = eth_pkt.src #get switch port where host packet send in
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) #save src data into dictionary---MAC address table
self.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 flood
if 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 status
actions = [ofp_parser.OFPActionOutput(Out_Port)] #add a new flow entry to switch by add_flow
if Out_Port != ofproto.OFPP_FLOOD: #if Out_port == ofproto.OFPP_FLOOD ---> flow entry == default flow entry, it already exist
match = ofp_parser.OFPMatch(in_port=in_port,eth_dst = dst)
self.add_flow(datapath, , 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_switch
Out = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,
in_port=in_port,actions=actions,data=msg.data) datapath.send_msg(Out)

(二)代码讲解(一)

from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller import ofp_event
from ryu.controller.handler import set_ev_cls
from ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHER
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet class SelfLearnSwitch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] #set openflow protocol version while we support def __init__(self,*args,**kwargs):
super(SelfLearnSwitch,self).__init__(*args,**kwargs)
#set a data construction to save MAC Address Table
self.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.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser self.logger.info("datapath: %s link to controller",datapath.id) #secondly set match and action
match = ofp_parser.OFPMatch() #all data message match successful
actions = [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_flow
self.add_flow(datapath,,match,actions,"default flow entry") def add_flow(self,datapath,priority,match,actions,extra_info):
"""
add flow entry to switch
""" #get open flow protocol infomation
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser #set instruction infomation from openflow protocol 1.3
inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,actions)] #set flow entry mod
mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,match=match,instructions=inst) print("send "+extra_info)
#send flow entry to switch
datapath.send_msg(mod)

以上代码同SDN实验---Ryu的应用开发(一)Hub实现,实现了设备与控制器初始连接,下发默认流表项,使得默认情况下,交换机在无法匹配到流表项时,直接去找控制器。一个一个公共函数add_flow实现流表下发。注意:在__init__方法中实现了数据结构《字典》去存储MAC地址表,为下面做准备

(三)代码讲解(二)

    @set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
def packet_in_handler(self,ev):
'''
manage infomation from switch
'''

#first parser openflow protocol    先解析OpenFlow协议信息

msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser #get datapath id from datapath, and save dpid into MAC table (default)   获取datapath(虚拟交换机的id),用dpid初始化一个键值
dpid = datapath.id
self.Mac_Port_Table.setdefault(dpid, {}) #analysize packet, get ethernet data, get host MAC info  分析packert数据包,因为转发的包,都是基于以太网协议的,所以我们需要用到以太网协议进行解析,获取源MAC和目的MAC
pkt = packet.Packet(msg.data)
eth_pkt = pkt.get_protocol(ethernet.ethernet)
dst = eth_pkt.dst
src = eth_pkt.src #get switch port where host packet send in  获取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)  #打印调试信息 #save src data into dictionary
---MAC address table  将源MAC地址保存,学习,放入MAC表中
self.Mac_Port_Table[dpid][src] = in_port #query MAC address table to get destinction host`s port from current datapath  查询MAC表,是否有目标MAC地址的键值
#
---first: find port to send packet  如果找到,我们则按照该端口发送
#---second: not find port,so send packet by flood  如果没有找到,我们需要泛洪发送给下一个(或者下几个)交换机,依次查询
if 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 status  开始设置match-actions匹配动作
actions = [ofp_parser.OFPActionOutput(Out_Port)] #add a new flow entry to switch by add_flow  进行对应的流表项下发  《重点》
if Out_Port != ofproto.OFPP_FLOOD:
match = ofp_parser.OFPMatch(in_port=in_port,eth_dst = dst)
self.add_flow(datapath, , 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_switch  最后我们将之前交换机发送上来的数据,重新发给交换机
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)

(四)实验演示

1.启动Ryu控制器

2.启动mininet

3.Ryu进行响应

注意:这里我一启动Mininet,就已经获取了所有的MAC信息,应该是主机接入网络后发送某些数据包,导致控制器获得了MAC表(需要使用wireshark抓包工具进行分析....后面进行补充)

网络可达,说明实现自学习交换机

四:补充知识

(一)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 = data
if protocols is None:
self.protocols = []
else:
self.protocols = protocols
if 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 calling
self.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 =
_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 = dst
self.src = src
self.ethertype = ethertype @classmethod
def 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 bytes long
pad_len = self._MIN_PAYLOAD_LEN - len(payload)
if pad_len > :
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) @classmethod
def get_packet_type(cls, type_):
"""Override method for the ethernet IEEE802.3 Length/Type
field (self.ethertype). If the value of Length/Type field is less than or equal to
decimal(05DC hexadecimal), it means Length interpretation
and be passed to the LLC sublayer."""
if type_ <= ether.ETH_TYPE_IEEE802_3:
type_ = ether.ETH_TYPE_IEEE802_3
return cls._TYPES.get(type_)

SDN实验---Ryu的应用开发(二)Learning Switch的更多相关文章

  1. SDN实验---Ryu的应用开发(四)北向接口RESTAPI

    一:推文 软件定义网络基础---REST API概述 软件定义网络基础---REST API的设计规范 二:掌握Ryu基本RESTAPI使用方法 (一)Ryu的RESTAPI (二) REST应用样例 ...

  2. SDN实验---Ryu的应用开发(一)Hub实现

    补充: (一)Ubuntu下安装Notepadqq 背景:为什么安装Notepadqq Notepad++ 不仅有语法高亮度显示,也有语法折叠功能,并且支持宏以及扩充基本功能的外挂模组.但是可惜的是N ...

  3. SDN实验---Ryu的应用开发(四)基于跳数的最短路径转发原理

    一:实现最短跳数转发 (一)原理 推文:迪杰斯特拉算法和弗洛伊德算法 二:代码实现 (一)全部代码 from ryu.base import app_manager from ryu.controll ...

  4. SDN实验---Ryu的应用开发(三)流量监控

    一:实现流量监控 (一)流量监控原理 其中控制器向交换机周期下发获取统计消息,请求交换机消息------是主动下发过程 流速公式:是(t1时刻的流量-t0时刻的流量)/(t1-t0) 剩余带宽公式:链 ...

  5. SDN实验---Ryu的源码分析

    一:安装Pycharm https://www.cnblogs.com/huozf/p/9304396.html(有可取之处) https://www.jetbrains.com/idea/buy/# ...

  6. SDN实验---Ryu的安装

    一:Ryu是主流SDN开源控制器之一 (一)推文(摘录自) https://ryu.readthedocs.io/en/latest/ https://www.sdnlab.com/1785.html ...

  7. SDN实验---Mininet实验(玩转流表)

    一:实验目的 (一)案例目的 (二)实验内容 (三)网络拓扑结构 二:OpenFlow流表实验准备 (一)使用Python设置网络拓扑 --- tree_topo.py from mininet.to ...

  8. 20145212 实验四《Andoid开发基础》

    20145212 实验四<Andoid开发基础> 实验内容 安装Android Studio 运行安卓AVD模拟器 使用Android运行出模拟手机并显示自己的学号 实验过程 一.安装An ...

  9. 20145212 实验三《敏捷开发与XP实践》

    20145212 实验三<敏捷开发与XP实践> 实验内容 使用git上传代码 与20145223同学一组,使用git相互更改代码 同组实验报告链接:http://www.cnblogs.c ...

随机推荐

  1. 目标检测论文解读5——YOLO v1

    背景 之前热门的目标检测方法都是two stage的,即分为region proposal和classification两个阶段,本文是对one stage方法的初次探索. 方法 首先看一下模型的网络 ...

  2. PAT 乙级 1009.说反话 C++/Java

    1009 说反话 (20 分) 题目来源 给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出. 输入格式: 测试输入包含一个测试用例,在一行内给出总长度不超过 80 的字符串.字符串由若干单词 ...

  3. Educational Codeforces Round 69 (Rated for Div. 2) E. Culture Code

    Educational Codeforces Round 69 (Rated for Div. 2) E. Culture Code 题目链接 题意: 给出\(n\)个俄罗斯套娃,每个套娃都有一个\( ...

  4. javascript学习5、JS面向对象

    创建对象的几种常用方式 1.使用Object或对象字面量创建对象 2.工厂模式创建对象 3.构造函数模式创建对象 4.原型模式创建对象 1.使用Object或对象字面量创建对象 JS中最基本创建对象的 ...

  5. luogu2900:Land Acquisition(斜率优化)

    题意:有N块地,每块地给出的宽和高,然后可以分批买,每次买的代价是所选择的地种最宽*最高. 问怎么买,使得代价和最小. 思路:显然,先去掉被包括的情况,即如果一个地的宽和高斗比另外一个小,那么久可以删 ...

  6. ie6下标签定义的高失效,显示的高不受设定的height值影响

    今天又碰到一个奇葩的ie6兼容bug,忍不住抱怨下这个后妈生的鬼东西!! 看图这个是在非ie6下的浏览器效果

  7. can总线的远程帧(遥控帧)—说的很形象

    所谓“远程帧”是一个传统翻译上的误区.Remote Frame实际上它的意义是“遥控帧”,发起方发起特定ID的远程帧,并且只发送ID部分,那么与其ID相符的终端设备就有义务在后半段的数据部分接管总线控 ...

  8. Numpy | 14 字符串函数

    本章函数用于对 dtype 为 numpy.string_ 或 numpy.unicode_ 的数组执行向量化字符串操作. 它们基于 Python 内置库中的标准字符串函数. 这些函数在字符数组类(n ...

  9. JS的ES6的Generator

    JS的ES6的Generator 1.Generator函数的概念: ES6提供的解决异步编程的方案之一,现在已经不怎么用了被淘汰了. Generator函数是一个状态机,内部封装了不同状态的数据. ...

  10. 框架入门经典项目TodoMVC

    一.项目介绍 ①地址:http://todomvc.com/ ②GitHub下载模板 ③通过npm下载模板的样式 ④通过npm下载Vuejs ⑤项目文件,主要修改app.js和index.html两个 ...