..

SDN Exp#1 log

第一次實験任務在於完成在仅有四個結點的 1969 年 ARPANET 網絡中的自學習交換機以及解決網絡中環路引起的ARP廣播風暴。實験通過 Mininet 構建網絡拓扑,Ryu app 編程實現控制器。

自學習交換機

正如 RIP 協議一般,此處交換機轉發數據包必須依據一定的規則,否則祇使用洪泛的方式將產生許多不必要的數據包,浪費帶寛。本實験的實現也非常容易,其思想在於對於每個到達的數據包,交換機都根據該包的源地址 $src$ 學習,若下次有目的地為 $src$ 的數據包就能直接通過此次記錄的端口進行發送。

但是需要注意的是,SDN 網絡不同於 RIP,對於每次非洪泛發包需要更新流表。因為在 SDN 中,數據包通過在流表中進行匹配而確定轉發規則,若匹配失敗,需要向控制器發送一個名為 packet-in 的信息請求控制器來處理當前包。

packet_in_handler() 中實現如下:

@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 
def packet_in_handler(self, ev): 
	msg = ev.msg 
    dp = msg.datapath 
    ofp = dp.ofproto 
    parser = dp.ofproto_parser 

    # the identity of switch 
    dpid = dp.id 
    self.mac_to_port.setdefault(dpid,{}) 
    # the port that receive the packet 
    in_port = msg.match['in_port']
    pkt = packet.Packet(msg.data) 
    eth_pkt = pkt.get_protocol(ethernet.ethernet) 
    # get the mac 
    dst = eth_pkt.dst 
    src = eth_pkt.src 
    # we can use the logger to print some useful information 
    self.logger.info('packet: %s %s %s %s', dpid, src, dst, in_port)

    # you need to code here to avoid the direct flooding 
    # having fun 
    # :)
    self.mac_to_port[dpid][src] = in_port
    if dst in self.mac_to_port[dpid]:
        # out_port = ofp.OFPP_FLOOD
        out_port = self.mac_to_port[dpid][dst]
    else:
        out_port = ofp.OFPP_FLOOD
    
    if out_port != ofp.OFPP_FLOOD:
		match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
    	self.add_flow(dp, 1, match, actions)

	data = None
	if msg.buffer_id == ofp.OFP_NO_BUFFER:
  		data = msg.data

	actions = [dp.ofproto_parser.OFPActionOutput(out_port)]

	out = dp.ofproto_parser.OFPPacketOut(datapath=dp, buffer_id=msg.buffer_id, in_port=in_port, actions=actions, data=msg.data)

	dp.send_msg(out)

正如上方所示,我需要補充的實際也祇有 TODO 部分。

廣播風暴問題

問题在於拓扑中出現了環路,而這將導致廣播的 ARP 請求包肆虐無度。指導書中提到了傳統的生成樹協議,並給出一種待實現的策略:強制使某个源主機發出的 ARP 請求包仅能由交換機的某個特定端口接收(例如:首次由洪泛接收的端口),稍微一想就能發現,對於特定的 ARP 請求包,其在拓扑中能傳播的途徑顯然構成了一棵生成樹,不過其不同於傳統的生成樹,在不同的數據包發出时會動熊生成可能不同的樹,魯棒性較強。

實現如下:

if dst == ETHERNET_MULTICAST and ARP in header_list:
	# you need to code here to avoid broadcast loop to finish mission 2
	self.sw.setdefault((dpid, src, dst), None)
	if self.sw.get((dpid, src, dst)) == None:
    	self.sw[(dpid, src, dst)] = in_port
	elif self.sw[(dpid, src, dst)] != in_port:
    	return

另外,實験附加題要求實現另一種方法。根據 ARP 緩存在相當長一段時間不會清除,可以對 ARP 數據包標記一个過期時間 TIMEOUT,在過期時間未到之時段,不在接收相同包。

實現如下:

if dst == ETHERNET_MULTICAST and ARP in header_list:
    ticks = time.time()
    self.sw.setdefault((dpid, src, dst), 0)
    if self.sw[(dpid, src, dst)] < ticks:
        self.sw[(dpid, src, dst)] = ticks + TIMEOUT
    else:
        return

注意,這樣考慮基於網絡中不會發生丢包,若丢包發生,則源主機重發的有效包可能被拒收,直到記錄過期,是有概率降低效率的。

参考