天天看点

通信网实验_Kruskal算法_Mininet_Ryu

本人

如果学院那边改了project内容的话,估计这篇文章也不会有人看了。

如果还有人看,啊挪,最好是思考了之后再来看这篇文章。

先放福利:点我点我

这个project可能会比较重要,所以我会讲的详细些(自我感觉,甚至过于详细)。

项目内容如下:

1、用Kruskal算法给给定的网络计算一棵最小生成树;

2、用计算出的最小生成树完成广播业务;

3、将业务可视化展示。

网络如下:(数据都是助教大大给的。数据改变或拓扑结构改变的话,生成树可能会变。之后的project都会以这个网络为例。学院那边肯定会给你个更加复杂的拓扑,但是,这个例子懂了的fa,不管网络怎么变,只要过程没问题的话,结果还会是我们想要的)

7个结点

通信网实验_Kruskal算法_Mininet_Ryu

我们当时做project真正用到的是24个结点的。

24结点

通信网实验_Kruskal算法_Mininet_Ryu

上面那个网络中,每个交换机(switch)下面只下挂一个主机(host)哦。

网络知道了,怎么让Mininet创建了这个网络呢?

答一:B站是个学习网站。参考https://www.bilibili.com/video/BV1hT4y1374f。(视频里面小姐姐的声音好好听)

答二:什么你不喜欢这样拉?因为太麻烦了?emmm......还是看上面那个链接的视频,只要看懂这样拉来拉去之后,miniedit会创建出怎么样的py文件,然后根据创建出来的py文件中代码的格式进行自主的删减就行了。

答三:什么,你觉得上面的回答都不够帅气?代码量贼大贼麻烦??emmm......回忆一下当年的C/C++实验,我们是怎么拉取某个文件里面的信息的?以此类推,给实验的网络创建一个json文件,里面记录该网络的拓扑信息。大致长这样:
           
通信网实验_Kruskal算法_Mininet_Ryu

然后写一个可以拉取json文件的mininet创建网络脚本(我管它叫tp777.py)。

from mininet.topo import Topo
from collections import Counter
from mininet.link import TCLink
from mininet.log import setLogLevel
import os
import json
from mininet.net import Mininet


class Tp777(Topo):
	def __init__(self):
		Topo.__init__(self)
		topoinfo=json.load(open('./topo7.json','r',encoding='utf-8-sig'))
		num_host=topoinfo["host_no"]
		num_switch=topoinfo["switch_no"]
		link_list=topoinfo["links"]

		for h in range(1,num_host+1):
			self.addHost('h'+str(h))
		for s in range(1,num_switch+1):
			self.addSwitch('s'+str(s))
		for link in link_list:
			v=link["vertexs"]
			d=link["delay"]
			b=link["bw"]
			self.addLink(v[0],v[1],delay=str(d)+'ms',bw=b)
topos={'mytopo':(lambda:Tp777())}
           

相信后浪们都懂上面写了啥。这个py文件要和对应的json放在一起哦。

json敲完了,py敲完了,文件怎么启动呢,阿巴阿巴。在tp777.py目录下叫出终端,然后

sudo mn --custom tp777.py --topo=mytopo --controller=remote,ip=127.0.0.1,port=6653 --mac
           

Mininet那边搞完了,开始搞Ryu的。

编写Ryu控制器逻辑脚本

学习那么久了,先看个视频放松放松吧。https://www.bilibili.com/video/BV1VJ41117vJ?p=17

照着敲一遍估计能大概了解Ryu是怎么工作的了。

如果俺没记错,上面那个视频中up主是把网络放在networkx中然后用networkx中的最短路算法。这个项目中我们也会用到networkx,但是仅仅只是用到它的可视化功能,不会使用到它自带的算法(u1s1,这样做助教肯定喂你吃鸡蛋,虽然视频没有错)

先告诉Ryu我们的网络长啥样。

这一步可以手敲二维列表来存网络:

也可以借鉴之前的方法,通过拉取json文件中的信息来存储网络信息:

self.Matrix就是用来存网络信息的二维列表。

之后编写Kruskal算法。这一步相信老师讲过,网上也有很多资料,这部分就不多说了。

上面所说的都是偏理论的部分,但是怎么把这些理论落实到仿真网络上面呢?

参考我之前分享的那个讲Ryu的视频。该视频讲到的

都不用修改。紫玛里,事件SwitchEnter和PachetIn是需要我们改写的。

SwitchEnter在检测到交换机加入时触发行为。那么,有人可能会问了,MIKU她老公呀,到底触发甚么行为了?

刚刚那个视频里面在这个事件下所做的事是拉取网络中的链路信息。其中我认为最重要的信息是端口信息,就是某个交换机是用哪个端口和另几台交换机连接的。除此之外,为了完成这个project我们还需在这个事件下调用我们刚刚写好的Kruskal算法(Kruskal算法所用到的网络信息通过json文件拉取)。Kruskal算法结束后,使用networkx的可视化功能画图(画图功能实现可以参考文章一开始的“福利”)。

下面写PacketIn事件下的行为。在SDN中交换机在没有对应流表项的时候,交换机会把接收到的数据包递给控制器,让控制器根据上传上来的包的信息给交换机下发对应的流表,这样交换机才知道这个数据包应该怎么走。他们的“对话”(该内容纯属虚构,如有雷同,纯属转生到了异世界)大致如下:

一号交换机:阿巴阿巴阿巴阿巴,嗯?!从西面飞来了一个包裹,让我看看哈,发送地址22号主机,目的地址33号主机。懵…不认识啊,33号主机在哪呀?交给控制器大佬,他肯定知道的。

控制器:哦,原来是佐田是33啊。根据我刚刚算出来的MST,你这个包应该交给你的西北面那个交换机。记住哦,以后收到这个目的地址的包都要这样做哦。

一号交换机:哇嘎哒!

之前不是在SwitchEnter里面算出了最小生成树和知道了网络中交换机之间的连接端口了吗。那么只要我们根据MST的信息,不就是可以知道某个交换机的哪些端口能用,哪些不能用了嘛。所以我们命令交换机,把收到的数据包,往所有没有被禁止使用的端口发送(数据包的入端口也算在本次行为的禁止端口中哦,不然数据包又回到之前的地方,这不就成环了嘛)

嗯?mouse写完了。那就运行一下吧。

通信网实验_Kruskal算法_Mininet_Ryu
通信网实验_Kruskal算法_Mininet_Ryu

也许你注意到了,打印出来的路径,怎么会有些是缺失了的?原因在于我们下发的流表所包含的信息不够多——里面只描述了是这个目的地址就应该这么走。但是如果我们把源地址也写进流表,打印的路径就不会缺失啦(这部分就交给聪明的你来写了)。

一些我遇到的坑:

1)Ryu逻辑文件放在桌面然后运行时候报错说,没有找到这个py文件:

把这个py文件放在这里试试
           
通信网实验_Kruskal算法_Mininet_Ryu
还是不行的话参考百度里面的其余做法。
           

2)怎么老是有这个报错?

通信网实验_Kruskal算法_Mininet_Ryu
1、是不是哪里多了或少了括号,冒号?多了分号?
2、多了或少了空格?多了或少了Tab?Python对缩进要求很高的哦。建议在你的文本编写器中开启空格与制表的可视化。
           

3)为什么拉取到的端口信息会缺失?

试试在SwitchEnter事件中加入sleep,并调试sleep的参数。
           

参考资料:

https://blog.csdn.net/qq_43734019/article/details/104334722?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-6&spm=1001.2101.3001.4242

https://www.bilibili.com/video/BV1hT4y1374f

https://www.bilibili.com/video/BV1VJ41117vJ?p=17

附录:

部分代码参考文章一开始的“福利”。

# encoding:utf-8
from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller.handler import set_ev_cls
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER,MAIN_DISPATCHER
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.topology import event
from ryu.topology.api import get_switch,get_link
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
from collections import defaultdict
import time
import string
import json

class Topo(object):                                                     # algorithm
    def __init__(self,logger):
        self.Matrix = np.array(np.ones((7,7))*999999)
        self.host_to_switch = np.array(np.ones((7,1))*8848)
        self.logger=logger

    def get_graph(self):
        topoinfo=json.load(open('./topo7.json','r'))
        link_list=topoinfo["links"]
        for link in link_list:                                          # get delay info and build a matrix to store delay info
            v=link["vertexs"]
            d=link["delay"]
            if v[0][0]=='s' and v[1][0]=='s':
                self.Matrix[int(v[0][1])-1][int(v[1][1])-1] = d
                self.Matrix[int(v[1][1])-1][int(v[0][1])-1] = d
            if v[0][0] == 'h':
                self.host_to_switch[int(v[0][1])-1] = d
        return self.Matrix

    def get_nodenum(self,Matrix):
        return len(Matrix)

    def get_edgenum(self,Matrix):
        count = 0
        for j in range(len(Matrix)):
            for i in range(j):
                if Matrix[i][j] > 0 and Matrix[i][j] < 999999:
                    count += 1
        return count

    def kruskal(self, Matrix):
		#get basic info of the matrix,which contains the topology
        nodenum = self.get_nodenum(Matrix)
        edgenum = self.get_edgenum(Matrix)

		#result_list contains the edges (node1,node2,info of delay) on the mst
        result_list = []
        if nodenum <= 0 or edgenum < nodenum - 1:
            return result_list

        #however they are not sorted
        edge_list = []

        for i in range(nodenum):
            for j in range(i+1, nodenum):
                #the info of delay != 999999 means it is an edge in the topo
                if Matrix[i][j] < 999999:
                    #(s1,s2,weight)
                    edge_list.append([i, j, Matrix[i][j]])

        # sort by comparing the info of delay
        edge_list.sort(key=lambda a: a[2])
        a = [[i] for i in range(nodenum)]
        # to make sure the edge in result_list cannot build a loop
        for edge in edge_list:
            for i in range(len(a)):
                if edge[0] in a[i]:
                    m = i
                if edge[1] in a[i]:
                    n = i
            if m != n:
                result_list.append(edge)
                a[m] = a[m] + a[n]
                a[n] = []

        return result_list

	#if we have edge(0,1) and (0,2) then links[0] = [1,2]
    def comput_tree(self,src,result_list):                              # it returns adj-nodes
        list1 = result_list
        links = [[] for i in range(len(result_list) + 1)]
        for i in range(len(result_list) + 1):
            for edge in list1:
                if i == edge[0]:
                    links[i].append(edge[1])
                elif i == edge[1]:
                    links[i].append(edge[0])
        return links

	#it returns a list which cotains (previous node, node here we are, an adjacent node)
    def compute_links(self,src,pre_src,links):
        result = []
        if len(links[src]) < 1:
            return result

        for node in links[src]:
            if node != pre_src:
                result.append((pre_src,src,node))
                newresult = self.compute_links(node,src,links)          # recursion
                result.extend(newresult)

        return result

     # in this part we want to figure out which next switch is
     #result from compute_links
    def compute_next_switch(self,result):
        next_switch = []
        for i in range(7):
            for j in range(6):
                if result[j][1] == i:
                    k = result[j][2]
                    next_switch.append((i+1,k+1))
        return next_switch


class KruskalTwo(app_manager.RyuApp):                                   # event

	OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

	def __init__(self, *args, **kwargs):
		super (KruskalTwo, self).__init__(*args, **kwargs)
		self.mac_to_port = {}
		self.network = nx.DiGraph()
		self.topology_api_app = self
		self.paths = {}
		self.topo = Topo(self.logger)
		self.isKruskal_done = False
		self.Matrix = np.array(np.ones((7,7))*999999)
		self.linkList1 = []
		self.links_node2node = []
		self.next_switch  = []
		self.tree = [[] for i in range(7)]                   #if we have edge(0,1) and (0,2) then links[0] = [1,2]
		self.port_can_be_used = [[] for i in range(7)]
		self.count1 = 0
		self.all_port = [[] for i in range(7)]
		self.forbidden_port = [[] for i in range(7)]
		self.passerby = {}

	# handle switch features info
	@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
	def switch_features_handler(self,ev):
		datapath = ev.msg.datapath
		ofproto = datapath.ofproto
		ofp_parser = datapath.ofproto_parser

		# install a table-miss flow entry for each datapath
		match = ofp_parser.OFPMatch()
		actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
												ofproto.OFPCML_NO_BUFFER)]

		self.add_flow(datapath,0,match,actions)

	def add_flow(self,datapath,priority,match,actions):
		ofproto = datapath.ofproto
		ofp_parser = datapath.ofproto_parser

		# construct a flow_mod msg and send it to datapath
		inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
												actions)]

		mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,
									match=match,instructions=inst)
		datapath.send_msg(mod)

	# get topology and store topology info into networkx
	@set_ev_cls(event.EventSwitchEnter,[CONFIG_DISPATCHER,MAIN_DISPATCHER])     ## whlie a switch enters the net
	def get_topology(self,ev):
		# get links and add links      now we can know that ,for example,src is 1, dst is 2, the port.no is the port in src that packet should get out
		# get outports
		links_list = get_link(self.topology_api_app,None)                               ### !!!!!!!!!
		links = [(link.src.dpid,link.dst.dpid,link.src.port_no) for link in links_list] ### !!!!!!!!!
	#	print('links:',links)
	#	self.network.add_edges_from(links)
	#	print('tree:',self.tree)
		if self.isKruskal_done == False:                            # the function in Topo should be done once then store all result
			self.Matrix = self.topo.get_graph()
			self.linkList1 = self.topo.kruskal(self.Matrix)
			print 'edges on the tree:',self.linkList1
			self.tree = self.topo.comput_tree(1-1,self.linkList1)
			print 'adj-switch:',self.tree
			print 'please wait......'                                    # adj-switch
			self.links_node2node = self.topo.compute_links(1-1,None,self.tree)
		#	print('links_node2node:',links_node2node)
			self.next_switch = self.topo.compute_next_switch(self.links_node2node)    # next_switch has 23 edges
		#	print('next_switch:',self.next_switch)                      # this is a list containing you-fang-xiang-de-edges
			self.isKruskal_done = True
		# in self.tree we get nodes' adj-nodes that in the tree, by using this message
		#and links = [(link.src.dpid,link.dst.dpid,link.src.port_no) for link in links_list] , we can know the ports used

		port_can_be_used = [[] for i in range(7)]
		all_port = [[] for i in range(7)]
		for m in range(7):
			for s in range(len(links)):
				if links[s][0] == m+1:
					for t in range(len(self.tree[m])):
						if links[s][1] == 1+self.tree[m][t]:
							port_can_be_used[m].append(links[s][2])
	#	print('port_can_be_used:',port_can_be_used)
		for m in range(7):
			for s in range(len(links)):
				if links[s][0] == m+1:
					all_port[m].append(links[s][2])
		self.count1 = self.count1+1
		if self.count1 == 7:
			self.port_can_be_used = port_can_be_used
			self.all_port = all_port
			for m in range(7):
				for t in self.all_port[m]:
					if t not in self.port_can_be_used[m]:
						self.forbidden_port[m].append(t)
			print 'self.forbidden_port:',self.forbidden_port
			print 'Exit the networkx, if you would like to go on.'

			#draw
			G=nx.Graph()
			weight={}
			graph=[]
			for i in range(len(self.Matrix)):
				for j in range(i):
					if self.Matrix[i][j] > 0 and self.Matrix[i][j] < 999999:
						graph.append((i+1,j+1))
						graph.append((j+1,i+1))
						weight[(i+1,j+1)]=self.Matrix[i][j]
						weight[(j+1,i+1)]=self.Matrix[i][j]
			path=[]
			for link1 in self.linkList1:
				path.append((link1[0]+1,link1[1]+1))
				path.append((link1[1]+1,link1[0]+1))
			for gra in graph:
				G.add_edge(gra[0],gra[1],color='black')
			for pat in path:
				G.add_edge(pat[0],pat[1],color='r')
			pos=nx.spring_layout(G)
			edges = G.edges()
			colors = [G[u][v]['color'] for u,v in edges]
			nx.draw_networkx_nodes(G,pos,node_size=233)
			nx.draw_networkx_edges(G,pos,width=3,edge_color=colors)
			nx.draw_networkx_labels(G,pos,font_size=10)
			nx.draw_networkx_edge_labels(G,pos,weight,font_size=10)
			plt.show()
			#draw end

			print 'Done!'
		time.sleep(1.0)                                                 #mininet's add_nodes and add_links are not that fast,this function runs only if a switch enters,we may finish this function when all
																		#links have not been added

	# handle packet_in msg
	@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
	def packet_in_handler(self,ev):
		msg = ev.msg
		datapath = msg.datapath
		ofproto = datapath.ofproto
		ofp_parser = datapath.ofproto_parser
		dpid = datapath.id

		self.mac_to_port.setdefault(dpid,{})

		pkt = packet.Packet(msg.data)
		eth = pkt.get_protocol(ethernet.ethernet)

		dst = eth.dst
		src = eth.src
	#	print src
		in_port = msg.match['in_port']
		self.mac_to_port[dpid][src] = in_port
	#	print('mac_to_port',self.mac_to_port)
		if dst in self.mac_to_port[dpid]:
			out_port = self.mac_to_port[dpid][dst]
			actions = [ofp_parser.OFPActionOutput(out_port)]
			match = ofp_parser.OFPMatch(in_port=in_port,eth_dst=dst)
			self.add_flow(datapath,1,match,actions)
			self.passerby.setdefault(src,{})
			self.passerby[src].setdefault(dst,[])
			self.passerby[src][dst].append(dpid)
			print("from h{} to h{}".format(int(src.split(':')[-1],16),int(dst.split(':')[-1],16)))
			print 'switches passed by:',self.passerby[src][dst]        #why sometimes the switches passby do not be printed?because
																		#the switches have been installed flow ,they donot have to
																		#ask the controller, and the event won't happen
		#	print('mac_to_port',self.mac_to_port[dpid])
		else:      #flood
			actions = []
			for i in datapath.ports:
				if ((i not in self.forbidden_port[dpid-1]) and (i != in_port)):
					actions.append(ofp_parser.OFPActionOutput(i))

		out = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=ofproto.OFP_NO_BUFFER,in_port=in_port,
									actions=actions,data=msg.data)
		datapath.send_msg(out)