SDN學習之實現環路通信


在對OpenFlow協議有了一定了解以后,開始嘗試如何通過Ryu控制器實現網絡中的通信。根據協議,我們知道,當數據信息首次傳輸到交換機時,由於交換機不存在該數據信息所對應的流表,因此,會觸發PacketIn消息,即交換機會將數據信息打包后,通過相應的交換機-控制器的專用通道將數據信息描述之后,傳輸給控制器,控制器在對數據包進行解析之后,根據相應的邏輯(基於底層網絡協議),給交換機添加相應的流表,在這之后,數據包會根據新添加的流表傳輸給下一個交換機或者目的地址。

下面給出相應的交互流程:

從圖中可以看到,左邊的PC,假設為h1,右邊為h2。首先,下方的控制器與交換機進行了hello以及switch_features兩個事件消息的交換,這是交換機與控制器在初始階段就要做的,與當前是否有數據通過交換機不相關,通過這兩個消息事件的處理,控制器能知道交換機的特征信息,這是OpenFlow協議內的部分內容,基礎的交互順序在我之前寫的OpenFlow協議中有提到過。然后呢,當數據從h1發往h2的時候,在通信的初始階段,由於交換機中並沒有添加相應的流表以及對應的主機h2的地址,因此,交換機這個時候並不知道該數據要發往哪里,這個時候,就會觸發Packet_in消息,這個消息只在相應的數據包到達某台交換機時觸發的。然后控制器通過過數據的解析,得出該數據包要發往的方向,給交換機添加相應的流表,出發add_flow事件,這個時候,數據包就可以通過添加的流表,走向h2了。

在知道具體的通信原理之后,就可以撰寫代碼實現,無環路下的主機之間的通信了,這里,最具有代表性的就是ryu自帶的app中的simple_switch_13.py文件了,以下是該文件的代碼:

  1 from ryu.base import app_manager   #繼承ryu.base.app_manager
  2 from ryu.controller import ofp_event  #繼承ryu.controller.ofp_event
  3 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER  #繼承ryu.controller.handler.CONFIG_DISPATCHER, MAIN_DISPATCHER
  4 from ryu.controller.handler import set_ev_cls   #繼承ryu.controller.handler.set_ev_cls
  5 from ryu.ofproto import ofproto_v1_3    #繼承ryu.ofproto.ofproto_v1_3
  6 from ryu.lib.packet import packet    #繼承ryu.lib.packet.packet
  7 from ryu.lib.packet import ethernet   #繼承ryu.lib.packet.ethernet
  8 from ryu.lib.packet import ether_types  #繼承ryu.lib.packet.ether_types+63652020
  9 
 10 class SimpleSwitch13(app_manager.RyuApp):
 11     OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]   #用於指定OpenFlow的版本,這里指定OpenFlow的版本為1.3版
 12                                                 #定義版本以后,mac_to_port也已經被指定
 13     #初始化環境變量
 14     def __init__(self, *args, **kwargs):
 15         super(SimpleSwitch13, self).__init__(*args, **kwargs)
 16         self.mac_to_port = {}
 17     
 18     #Event Handler是一個擁有事件物件(Event Object)作為參數
 19     #並使用"ryu.controller.handler.set_ev_cls"來修飾decorator函數
 20     #set_ev_cls用於指定事件類別得以接受訊息和交換機狀態作為參數
 21     #時間類別命名名稱的規則為ryu.controller.ofp_event.EventOFP + <OpenFlow訊息名稱>
 22     #如Packet-in訊息的狀態下的時間為EventOFPPacketIn
 23     
 24     #部分名稱的作用
 25     #ryu.controller.handler.HANDSHAKE_DISPATCHER  交換HELLO信息
 26     #ryu.controller.handler.CONFIG_DISPATCHER   接收SwitchFeatures訊息
 27     #ryu.controller.handler.MAIN_DISPATCHER   一般狀態
 28     #ryu.controller.handler.DEAD_DISPATCHER  連線中斷
 29     @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
 30     def switch_features_handler(self, ev):
 31         datapath = ev.msg.datapath     #此訊息用於存儲OpenFlow交換機的ryu.controller.controller.Datapath類別所對應的實體
 32         ofproto = datapath.ofproto
 33         parser = datapath.ofproto_parser
 34         
 35         #ev.msg是用來存儲對應事件的OpenFlow訊息類別實體。在這個例子中,則是ryu.ofproto.ofproto_v1_3_parser.OFPSwitchFeatures
 36         #Datapath類別是用來處理OpenFlow交換機的重要訊息,例如執行與交換機的通信和觸發接收訊息的事件
 37         
 38         match = parser.OFPMatch()   #為了match所有封包,需要產生一個空的match
 39         actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
 40                                           ofproto.OFPCML_NO_BUFFER)]
 41         #為了將封包轉送到Controller連接埠,OFPActionOutput類別的實例也會被產生
 42         #指定OFPP_Controller為封包目的地
 43         #設定OFPCML_NO_BUFFER為max_len以便接下來的封包傳送
 44         self.add_flow(datapath, 0, match, actions)
 45         #設定Table-miss Flow Entry的優先權為0(最低優先權)
 46         #然后執行add_flow()方法以發送Flow Mod訊息
 47         
 48         #交換機本身不僅僅使用Switch features訊息,
 49         #還使用事件處理以取得新增Table-miss Flow Entry的時間點
 50         #Table-miss Flow Entry的優先權為0(最低優先權),而且此Entry可以match所有的封包
 51         #這個Entry的Instruction通常指定為output action
 52     
 53     #定義add_flow()函數,用於新增Flow Entry
 54     def add_flow(self, datapath, priority, match, actions, buffer_id=None):
 55         ofproto = datapath.ofproto
 56         parser = datapath.ofproto_parser
 57 
 58         inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
 59                                              actions)]
 60         #APPLY_ACTIONS是用來設定那些必須立即執行的action所使用的
 61         
 62         if buffer_id:
 63             mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
 64                                     priority=priority, match=match,
 65                                     instructions=inst)
 66         else:
 67             mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
 68                                     match=match, instructions=inst)
 69         #OFPFlowMod類別中參數的預設值
 70         #datapath:openflow交換機以及flow table的操作都是通過datapath類別的實體來進行。
 71         #在一般的情況下,會由事件傳遞給事件管理的訊息中取得,如Packet-In
 72         #cookie(0): controller所設定存儲的資料,在Entry的更新或者刪除時所需要使用的資料存放地
 73         #並作為過濾器使用,而且不可以作為封包處理的參數
 74         #cookie_mask():Entry的更新或刪除時,若是該值為非零,則作為指定Entry的cookie使用
 75         #table_id(0):使用Flow Entry的Table ID
 76         #idle_timeout:flow entry 的有效期限,以秒為單位
 77         #hard_timeout:flow entry的有效期限,但在超過時間限后不會重新歸零計算
 78         #priority:優先權,值越大,優先權限越高
 79         #out_put(0):OFPFC_DELETE 和 OFPFC_DELETE_STRICT 命令用來指定輸出位置的參數。
 80         #命令為 OFPFC_ADD、 OFPFC_MODIFY、OFPFC_MODIFY_STRICT 時可以忽略。
 81         #若要制定無效,指定輸出為OFPP_ANY
 82         #out_group(0):與上相同,作為一個輸出位置,但是轉到特定的group,若無效,使用OFPG_ANY
 83         
 84 
 85         #通過FlowMod訊息將Flow Entry新增到Flow table中
 86         datapath.send_msg(mod)
 87         #使用OFPFlowMod所產生的實體通過datapath,send_msg()來發送訊息至交換機
 88     
 89     #Packet-In事件接收處理位置目的地的封包
 90     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
 91     #OFPPacketIn類別的常用屬性
 92     #match: ryu.ofproto.ofproto_v1_3_parser.OFPMatch類別的實體,用來存儲接收封包的meta訊息
 93     #data:接收封包本身的binary資料
 94     #tatal_len:接收封包的資料長度
 95     #buffer_id:接受封包的內容。
 96     #若存在OpenFlow交換機上時所指定的ID,如果在沒有buffer的狀態下,則設定ryu.ofproto.ofproto_v1_3.OFP_NO_BUFFER
 97     
 98     #更新MAC地址表
 99     def _packet_in_handler(self, ev):
100         # If you hit this you might want to increase
101         # the "miss_send_length" of your switch
102         if ev.msg.msg_len < ev.msg.total_len:
103             self.logger.debug("packet truncated: only %s of %s bytes",
104                               ev.msg.msg_len, ev.msg.total_len)
105         msg = ev.msg
106         datapath = msg.datapath
107         ofproto = datapath.ofproto
108         parser = datapath.ofproto_parser
109         in_port = msg.match['in_port']
110         #從OFPPacketIn類別的match得到接收埠(in_port)的訊息。
111         #目的MAC地址和來源MAC地址分別使用Ryu的封包函數庫,從接收到封包的Ethernet header取得
112         
113         pkt = packet.Packet(msg.data)
114         eth = pkt.get_protocols(ethernet.ethernet)[0]
115 
116         if eth.ethertype == ether_types.ETH_TYPE_LLDP:
117             return
118         dst = eth.dst
119         src = eth.src
120 
121         dpid = datapath.id 
122         #是同datapath.id來確認MAC地址表和每個交換機之間的識別來應對連接到多個OpenFlow交換機
123         self.mac_to_port.setdefault(dpid, {})
124 
125         self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
126 
127         # learn a mac address to avoid FLOOD next time.
128         self.mac_to_port[dpid][src] = in_port
129         #借此得知目的MAC地址表和來源MAC地址,更新MAC地址表
130         
131         
132         if dst in self.mac_to_port[dpid]:
133             out_port = self.mac_to_port[dpid][dst]
134         else:
135             out_port = ofproto.OFPP_FLOOD
136 
137         actions = [parser.OFPActionOutput(ofproto.OFPP_IN_PORT)]
138         #判斷轉送封包的連接埠
139         #若目的MAC地址存在於MAC地址表,則判斷該連接埠的號碼作為輸出
140         #反之若不存在MAC地址表
141         #則ActionOutput類別的屍體並生成flooding(OFPP_FLOOD)給目的連接埠使用
142         
143         #轉送封包
144         #在MAC位置表中找尋目的MAC地址,若有則發送Packet-in訊息,並轉送封包
145         # install a flow to avoid packet_in next time
146         if out_port != ofproto.OFPP_FLOOD:
147             match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
148             # verify if we have a valid buffer_id, if yes avoid to send both
149             # flow_mod & packet_out
150             if msg.buffer_id != ofproto.OFP_NO_BUFFER:
151                 self.add_flow(datapath, 1, match, actions, msg.buffer_id)
152                 return
153             else:
154                 self.add_flow(datapath, 1, match, actions)
155         data = None
156         if msg.buffer_id == ofproto.OFP_NO_BUFFER:
157             data = msg.data
158 
159         out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
160                                   in_port=in_port, actions=actions, data=data)
161         datapath.send_msg(out)
162         
163         #buffer_id:指定openflow交換機上封包對應的緩沖區,若不需要,則指定為OFP_NO_BUFFER
164         #in_port:制定接收到的連接埠號,如果不想使用,就制定為OFPP_CONTROLLER

通過對該代碼進行分析,我們可以得到核心的內容,就是,代碼中的mac_to_port{}字典,是我們進行路由的依據,它記錄了數據包在網絡中傳輸時,經過的交換機的dpid、源目mac地址以及對應的in_port以及out_port的端口號。數據的路由,就是依據它來采取相應的轉發action的。

此外,在對sdn網絡進行基礎的實驗的時候,我們會遇到傳統網絡中最容易遇到的一個問題,網絡風暴。這個問題的產生,是由於數據包在網絡中的arp廣播產生的,對網絡的帶寬、資源的占有具有很大的損害。

如何解決這個問題,是我們實現環形網絡正常通信,所需要面對的基礎問題之一。

在此之前,我參考了李呈大神寫的arp代理http://www.sdnlab.com/2318.html ,但是,發現這個代碼無法正常的在我的環境下運行。不過,他的思路,卻給了我很大的啟發,結合simple_switch中的代碼,我發現,如果單一的實現換路通信,其實只需要在數據包到達每個交換機,進行mac學習的時候,判斷當前mac_to_port字典中是否存在過相應的交換機信息,若存在,則判斷其進入端口是否相同,若不相同,則發生環路風暴,對該數據包進行丟包操作,這樣就能做到環路中的正常通信。附上相應的代碼:

  1 from ryu.base import app_manager
  2 from ryu.controller import ofp_event
  3 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
  4 from ryu.controller.handler import set_ev_cls
  5 from ryu.ofproto import ofproto_v1_3
  6 from ryu.lib.packet import packet
  7 from ryu.lib.packet import ethernet
  8 from ryu.lib.packet import tcp
  9 from ryu.lib.packet import ether_types
 10 from ryu.lib.packet import arp
 11 
 12 class ARP_PROXY_13(app_manager.RyuApp):
 13     OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
 14 
 15     def __init__(self, *args, **kwargs):
 16         super(ARP_PROXY_13, self).__init__(*args, **kwargs)
 17         self.mac_to_port = {}
 18 
 19     @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
 20     def switch_features_handler(self, ev):
 21         datapath = ev.msg.datapath
 22         ofproto = datapath.ofproto
 23         parser = datapath.ofproto_parser
 24  
 25         match = parser.OFPMatch()
 26         actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
 27                                           ofproto.OFPCML_NO_BUFFER)]
 28         self.add_flow(datapath, 0, match, actions)
 29  
 30     def add_flow(self, datapath, priority, match, actions, buffer_id=None):
 31         ofproto = datapath.ofproto
 32         parser = datapath.ofproto_parser
 33  
 34         inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
 35                                              actions)]
 36  
 37         if buffer_id:
 38           mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
 39                              priority=priority, match=match,
 40                              instructions=inst)
 41         else:
 42             mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
 43                              match=match, instructions=inst)
 44         datapath.send_msg(mod)
 45 
 46 #mac learning
 47     def mac_learning(self, datapath, src, in_port):
 48         self.mac_to_port.setdefault((datapath,datapath.id), {})
 49         # learn a mac address to avoid FLOOD next time.
 50         if src in self.mac_to_port[(datapath,datapath.id)]:
 51             if in_port != self.mac_to_port[(datapath,datapath.id)][src]:
 52                 return False
 53         else:
 54             self.mac_to_port[(datapath,datapath.id)][src] = in_port
 55             return True
 56  
 57     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
 58     def _packet_in_handler(self, ev):
 59         msg = ev.msg
 60         datapath = msg.datapath
 61         ofproto = datapath.ofproto
 62         parser = datapath.ofproto_parser
 63         in_port = msg.match['in_port']
 64  
 65         pkt = packet.Packet(msg.data)
 66  
 67         eth = pkt.get_protocols(ethernet.ethernet)[0]
 68 
 69         if eth.ethertype == ether_types.ETH_TYPE_LLDP:
 70             match = parser.OFPMatch(eth_type=eth.ethertype)
 71             actions = []
 72             self.add_flow(datapath, 10, match, actions)
 73             return
 74 
 75         if eth.ethertype == ether_types.ETH_TYPE_IPV6:
 76             match = parser.OFPMatch(eth_type=eth.ethertype)
 77             actions = []
 78             self.add_flow(datapath, 10, match, actions)
 79             return
 80 
 81         dst = eth.dst
 82         src = eth.src
 83         dpid = datapath.id
 84 
 85 
 86         self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
 87         self.mac_learning(datapath, src, in_port)
 88 
 89         if dst in self.mac_to_port[(datapath,datapath.id)]:
 90             out_port = self.mac_to_port[(datapath,datapath.id)][dst]
 91         else:
 92             if self.mac_learning(datapath, src, in_port) is False:
 93                 out_port = ofproto.OFPPC_NO_RECV
 94             else:
 95                 out_port = ofproto.OFPP_FLOOD
 96  
 97         actions = [parser.OFPActionOutput(out_port)]
 98  
 99         if out_port != ofproto.OFPP_FLOOD:
100             match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
101             if msg.buffer_id != ofproto.OFP_NO_BUFFER:
102                 self.add_flow(datapath, 10, match, actions, msg.buffer_id)
103                 return
104             else:
105                 self.add_flow(datapath, 10, match, actions)
106  
107         data = None
108         if msg.buffer_id == ofproto.OFP_NO_BUFFER:
109             data = msg.data
110         out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
111                                   in_port=in_port, actions=actions, data=data)
112         datapath.send_msg(out)

重點在mac learning這個模塊的代碼,根據這個代碼,就可以實現網絡的環路通信。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM