-
Notifications
You must be signed in to change notification settings - Fork 0
/
topo_1.py
454 lines (416 loc) · 21 KB
/
topo_1.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types
from ryu.topology import switches
from ryu.topology import event
from ryu.topology import dhcps
from scipy import sparse
import time
import copy
from ryu.lib.packet import ether_types, arp
from ryu.topology.short_path import get_all_short_path_sequence, INF
class Topo(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
# z指定该应用所需要的app实例
_CONTEXTS = {
'switches': switches.Switches,
'dhcp': dhcps.DHCPResponder
}
_EVENTS = [event.EventTopoChange]
def __init__(self, *args, **kwargs):
super(Topo, self).__init__()
self.switchMap = {} # 记录所有交换机id对应在switches列表中的序号,便于对应查找交换机
self.switches = [] # 记录所有的{switch},方便计算邻接矩阵
self.links = {} # 记录交换机之间的连接 {port1:port2,...} 最好这么记录
self.host = {} # 记录所有主机的信息 {mac:(dpid, port_no,ip)}
@set_ev_cls(event.EventSwitchEnter)
def switch_enter_handler(self, ev):
sw = ev.switch
self.switches.append(sw)
self.switchMap[sw.dp.id] = len(self.switches) - 1
print("switch " + str(sw.dp.id) + " enter")
@set_ev_cls(event.EventSwitchLeave)
def switch_leave_handler(self, ev):
sw = ev.switch
if sw in self.switches:
self.switches.remove(sw)
del self.switchMap[sw.dp.id]
print("switch " + str(sw.dp.id) + " leave")
@set_ev_cls(event.EventLinkAdd)
def link_add_handler(self, ev):
l = ev.link
if l.src.dpid not in self.links:
self.links[l.src] = l.dst
self.send_event_to_observers(event.EventTopoChange('link add'))
print("link s%d %d and s%d %d up" % (l.src.dpid, l.src.port_no, l.dst.dpid, l.dst.port_no))
@set_ev_cls(event.EventLinkDelete)
def link_del_handler(self, ev):
l = ev.link
if l.src in self.links:
del self.links[l.src]
self.send_event_to_observers(event.EventTopoChange('link delete'))
print("link s%d %d and s%d %d down" % (l.src.dpid, l.src.port_no, l.dst.dpid, l.dst.port_no))
@set_ev_cls(event.EventHostAdd)
def Host_Add_Handler(self, ev):
h = ev.host
if h.mac not in self.host:
self.host[h.mac] = (h.port.split(':')[0], h.port.split(':')[1], h.ipv4)
self.send_event_to_observers(event.EventTopoChange('host add'))
print("host %s %s add" % (h.mac, h.ipv4))
@set_ev_cls(event.EventHostDelete)
def Host_Delete_Handler(self, ev):
h = ev.host
if h.mac in self.host:
self.host[h.mac] = (h.port.split(':')[0], h.port.split(':')[1], h.ipv4)
del self.host[h.mac]
self.send_event_to_observers(event.EventTopoChange('host delete'))
print("host %s %s delete" % (h.mac, h.ipv4))
def getAdjMatrix(self):
if len(self.switches) == 0:
return []
r = sparse.dok_matrix((len(self.switches), len(self.switches)))
for i in range(len(self.switches)):
for j in range(len(self.switches)):
if i != j:
for port1 in self.switches[i].ports:
for port2 in self.switches[j].ports:
if self.links.get(port1) and self.links[port1] == port2:
r[i, j] = 1
r[j, i] = 1
return r.toarray()
# 判断;两个交换机是否相连,相连返回连接端口,不想连返回空
def isConnect(self, sw1, sw2):
assert isinstance(sw1, switches.Switch)
assert isinstance(sw2, switches.Switch)
for port1 in sw1.ports:
for port2 in sw2.ports:
if self.links.get(port1) and self.links[port1] == port2:
return (port1.dpid, port1.port_no, port2.dpid, port2.port_no)
return None
# 捕获拓扑改变的函数
@set_ev_cls(event.EventTopoChange)
def topoChangeHandler(self, ev):
"""
当拓扑发生变化时,首先重新计算新的最短路径,删除之前的流表(table-miss和packet_in消息处理流表项除外)
重新下发流表
:param ev:
:return:
"""
print('topoChangeHandler: topo changed !')
print('==============getAdjMatrix=================')
print(self.getAdjMatrix())
print('===============switchMap==================')
print(self.switchMap)
print('=================host=====================')
print(self.host)
print('==================links====================')
# for item in self.links:
# print('in_port: {0}, out_port: {1}'.format(item, self.links[item]))
# # print(item.__dict__)
# print('===================links end======================')
# a = self.isConnect(self.switches[0], self.switches[2])
# if a is not None:
# print(a[0],a[1],a[2],a[3])
# print(ev.msg)
time.sleep(30)
ip_port_dict = self.compute_path_between_all_hosts()
print('I am going to delete old flow tables.I am going to delete old flow tables')
self.drop_all_flow_entities()
print('I am wating here')
time.sleep(120) # 为了观察流表真的被删除了,后面可以删除代码
self.add_flow_table_item(ip_port_dict)
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
"""
:param actions: 对满足过滤条件的流做的动作列表
:param command: 表示对流表的操作,包括增加(Add)、删除(Delete)、修改(Modify)
"""
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
if buffer_id:
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
priority=priority, match=match,
instructions=inst, idle_timeout=60, hard_timeout=60)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
match=match, instructions=inst
# ,idle_timeout=60, hard_timeout=60
)
datapath.send_msg(mod)
def drop_flow(self, datapath, match):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
mod = parser.OFPFlowMod(datapath=datapath,
command=ofproto.OFPFC_DELETE,
out_port=ofproto.OFPP_ANY,
out_group=ofproto.OFPG_ANY,
match=match)
datapath.send_msg(mod)
def drop_all_flow_entities(self):
"""
删除所有流表
:return:
"""
for switch in self.switches:
datapath = switch.dp
parser = datapath.ofproto_parser
for mac in self.host:
print('mac: {0}'.format(mac))
match = parser.OFPMatch(eth_dst=mac) # 必须保留table-miss和packet_in的流表项
self.drop_flow(datapath, match)
def index2switch_id(self, switch_index_seq):
"""
把由交换机在交换机列表中的编号表示的路径序列转换为由交换机的编号表示的路径序列
:param switch_index_seq: 由交换机在交换机列表中的编号表示的最短路径序列
:return:由交换机编号表示的最短路径序列
"""
path_seq = []
index2switch = dict(zip(self.switchMap.values(), self.switchMap.keys()))
for switch_index in switch_index_seq:
switch_id = index2switch.get(switch_index)
path_seq.append(switch_id)
return path_seq
def handle_matrix(self):
"""
self.getAdjMatrix()返回的数据形如: [[0. 1. 0.], [1. 0. 1.], [0. 1. 0.]]
把它转化为:[[0.e+00 1.e+00 1.e+04], [1.e+00 0.e+00 1.e+00], [1.e+04 1.e+00 0.e+00]]
"""
dis_matrix = self.getAdjMatrix()
shape = self.getAdjMatrix().shape
n_pathes = shape[0]
for i in range(n_pathes):
for j in range(n_pathes):
if i != j and dis_matrix[i, j] == 0:
dis_matrix[i, j] = INF
return dis_matrix
def get_switch_id_path_sequence(self):
"""
switchMap中记录所有交换机id对应在switches列表中的序号,便于对应查找交换机,
总之,需要将path_sequence中的switch列表中的编号转换成交换机的id
获得网络中任意两个交换机结点之间的最短路径序列,路径序列由交换机编号来表示
如:<1,3,4,7>表示从id为1的交换机到id为7的交换机的最短路径是从交换机1到交换机3到交换机4到交换机7
"""
n_switches = len(self.switches)
dis_matrix = self.handle_matrix()
# index_path_sequence中是以switch列表中的编号给出交换机之间的路径上的结点序列
index_path_sequence = get_all_short_path_sequence(n_switches, dis_matrix)
id_path_sequence = []
for index_path in index_path_sequence:
id_path = self.index2switch_id(index_path)
id_path_sequence.append(id_path)
# print('index_path: {0}'.format(index_path))
# print('id_path: {0}'.format(id_path))
return id_path_sequence
def id_path_sequence2dict(self):
"""
获取所有交换机(交换机由交换机的id来表示)的最短路径序列,并转化为字典
如(0,4): [0,3,2,4] 键是表示路径的起点和终点,值是整个路径中经过的所有交换机结点组成的序列
:return:
"""
path_sequence = self.get_switch_id_path_sequence() # 获得所有交换机的最短路径序列
path_dict = {}
for path in path_sequence:
# 若path为空会发生什么
if len(path) == 0:
continue
src_switch = path[0]
dst_switch = path[-1]
if src_switch != dst_switch:
path_dict[(src_switch, dst_switch)] = path
return path_dict
def get_port0(self, src_switch, dst_switch, in_or_out):
"""
输入两个主机,源交换机连接目的交换机的出端口
in_or_out == 1: 求目的交换机的入端口;in_or_out == 2: 求源交换机的出端口
:param src_switch:
:param dst_switch:
:return:
"""
port = None
for src_dpid, item in self.links.items():
dst_dpid = item[1]
if src_switch == src_dpid and dst_switch == dst_dpid:
if in_or_out == 1:
port = item[2]
else:
port = item[0]
# print('src_switch:{0}, dst_switch:{1}, port: {2}'.format(src_switch, dst_switch, port))
return port
elif src_switch == dst_dpid and dst_switch == src_dpid:
if in_or_out == 1:
port = item[0]
else:
port = item[2]
# print('src_switch:{0}, dst_switch:{1}, port: {2}'.format(src_switch, dst_switch, port))
return port
def port_maps(self):
"""
通过link转换为交换机端口之间的连接关系
:param src_switch:
:param dst_switch:
:param in_or_out:
:return:
"""
src_dst_port_map = dict()
for src, dst in self.links.items(): # src,dst是Port对象
src_dpid = src.dpid
dst_dpid = dst.dpid
src_port_no = src.port_no
dst_port_no = dst.port_no
# 将src_dpid作为源交换机,dst_dpid作为目的交换机,则src_port_no是源交换机的出端口,dst_port_no是目的交换机的入端口
src_dst_port_map[(src_dpid, dst_dpid)] = (src_port_no, dst_port_no)
src_dst_port_map[(dst_dpid, src_dpid)] = (dst_port_no, src_port_no)
return src_dst_port_map
def get_port(self, src_switch, dst_switch, in_or_out, src_dst_port_map):
"""
输入两个主机,源交换机连接目的交换机的出端口
in_or_out == 1: 求目的交换机的入端口;in_or_out == 2: 求源交换机的出端口
"""
if in_or_out == 1:
return src_dst_port_map[(src_switch, dst_switch)][1]
elif in_or_out == 2:
return src_dst_port_map[(src_switch, dst_switch)][0]
else:
return None
def get_ports_with_path(self, path, src_dst_port_map):
"""
给定路径序列,返回这条路径上每个交换机对应的端口
"""
ports_list = []
for i in range(len(path)): # 将路径中的每个交换机转化为入端口和出端口
port_dict = dict()
if i == 0: # 说明是连接源主机的那个交换机
port_dict["in_port"] = "unknow"
out_port = self.get_port(path[i], path[i + 1], 2, src_dst_port_map)
port_dict["out_port"] = out_port
elif i == len(path) - 1: # 说明是连接目的主机的那个交换机
in_port = self.get_port(path[i - 1], path[i], 1, src_dst_port_map)
port_dict["in_port"] = in_port
port_dict["out_port"] = "unknow"
else:
in_port = self.get_port(path[i - 1], path[i], 1, src_dst_port_map)
out_port = self.get_port(path[i], path[i + 1], 2, src_dst_port_map)
port_dict["in_port"] = in_port
port_dict["out_port"] = out_port
ports_list.append(port_dict)
# print('path: {0}'.format(path))
# print('ports_list: {0}'.format(ports_list))
return ports_list
def get_port_seq(self):
"""
对网络中的任意两台交换机,计算出这两台交换机之间的结点交换机序列
如<1,3,4,2>表示从交换机1到达交换机2需要经过交换机1,3,4,2,
若1:1->3:1,3:2->4:2,4:1->2:2表示交换机1的端口1连接交换机3的端口1,交换机3的端口2连接交换机4的端口2,剩下以此类推
则转换后的端口序列为:[{"i_port":"unknown","out_port":1},{"i_port":1,"out_port":2},{"i_port":2,"out_port":1},{"i_port":2,"out_port":"unknown"}]
其中交换机1和交换机2分别连接源主机和目的主机,因此,in_port和out_port分别为unknown
:return:
"""
switch_ids = [id for id in self.switchMap.keys()] # 获得所有交换机的id,即获取所有交换机
path_dict = self.id_path_sequence2dict()
src_dst_port_map = self.port_maps()
# print('src_dst_port_map: {0}'.format(src_dst_port_map))
for src_id in switch_ids:
for dst_id in switch_ids:
# 当源结点交换机和目的结点交换机不是同一个交换机时,将交换结点序列转换为入、出端口序列
if src_id != dst_id:
path = path_dict[(src_id, dst_id)] # 获得从源结点到目的结点的最短路径序列
ports_list = self.get_ports_with_path(path, src_dst_port_map)
path_dict[(src_id, dst_id)] = [path, ports_list]
return path_dict
def compute_path_between_all_hosts(self):
"""
遍历网络中的所有IP对,返回每一对IP之间的最短路径上的交换机结点,以及每个结点的入端口和出端口
:return:
"""
ip_port_dict = {}
path_dict = self.get_port_seq() # path_dict[(src_id, dst_id)] = [path, ports_list]
# 测试每条路径上的端口序列是否正确
# print('*********************379 379*********************')
# for key, val in path_dict.items():
# if key[0] and key[1]:
# path = val[0]
# ports = val[1]
# print('path: {0}, ports: {1}'.format(path, ports))
# print('******************************************')
# 对每一对IP,返回它们之间的最短路径上的交换机结点及每个结点的入端口和出端口
for host_mac0, host_info0 in self.host.items():
host_ip0 = host_info0[2]
nearest_switch0 = host_info0[0] # 注意:host_info0[0]是字符类型
switch_port0 = host_info0[1] # 连接交换机的端口
for host_mac1, host_info1 in self.host.items():
if host_mac0 == host_mac1:
continue
host_ip1 = host_info1[2]
nearest_switch1 = host_info1[0]
switch_port1 = host_info1[1]
nearest_switch0 = int(nearest_switch0)
nearest_switch1 = int(nearest_switch1)
mac_addr_dict = [host_mac0, host_mac1]
if nearest_switch0 == nearest_switch1: # 若两台主机连接着同一台交换机
path = [nearest_switch0, ]
ports = [{"in_port": switch_port0, "out_port": switch_port1}, ]
ip_port_dict[(host_ip0[0], host_ip1[0])] = [path, ports, mac_addr_dict]
else:
# copy.deepcopy 浅拷贝导致的错误
path = copy.deepcopy(path_dict[(nearest_switch0, nearest_switch1)][0]) # path是个列表,ports是一个嵌套了字典的列表
ports = copy.deepcopy(path_dict[(nearest_switch0, nearest_switch1)][1]) # path是个列表,ports是一个嵌套了字典的列表
# print('412 choice 2222222222')
# print('host_ip0: {0}, switch_port0: {1}, host_ip1: {2}, switch_port1: {3}'.format(
# host_ip0, switch_port0, host_ip1, switch_port1))
# print('before path: {0}, ports: {1}'.format(path, ports))
# ports_new = ports
ports[0]["in_port"] = switch_port0 # 入/出端口序列中的第一项的入端口连接着源主机的入端口
ports[-1]["out_port"] = switch_port1 # 入/出端口序列中的最后一项的出端口连接着目的主机的出端口
ip_port_dict[(host_ip0[0], host_ip1[0])] = [path, ports, mac_addr_dict]
# print('真正下发的端口序列: {0}'.format(ports))
# print('after path: {0}, ports: {1}'.format(path, ports))
return ip_port_dict
def add_flow_table_item(self, ip_port_dict):
"""
最短路径计算完毕,下发流表
:return:
"""
# ip_port_dict = self.compute_path_between_all_hosts()
# 打印出
# print('=******************* ip_port_dict ********************=')
# for item in ip_port_dict:
# print('{0}: {1}'.format(item, ip_port_dict[item]))
# print('=******************* ip_port_dict end********************=')
# 每一个host2host,对其所经过路径上的所有交换机下发流表
for item in ip_port_dict:
# src_ip = item[0]
# dst_ip = item[1]
src_ip, dst_ip = item
# print('146 items: {0}, src_ip: {1}, dst_ip: {2}'.format(item, src_ip, dst_ip))
connection = ip_port_dict[item]
path, ports, macs = connection
src_mac, dst_mac = macs
# print('path: {0}, ports: {1}, macs: {2}'.format(path, ports, macs))
# print('src_mac: {0}, dst_mac: {1}'.format(src_mac, dst_mac))
for i in range(len(path)):
switch_dpid = path[i]
# print('type of self.switchMap[switch_dpid]: {0}'.format(type(self.switchMap[switch_dpid])))
# print(self.switches[0].__dict__)
datapath = self.switches[self.switchMap[switch_dpid]].dp
parser = datapath.ofproto_parser
in_port = int(ports[i]['in_port'])
out_port = int(ports[i]['out_port'])
actions = [parser.OFPActionOutput(out_port)] # 转发动作
match = parser.OFPMatch(
ipv4_src=src_ip, ipv4_dst=dst_ip, eth_src=src_mac,
eth_dst=dst_mac, eth_type=ether_types.ETH_TYPE_IP,
in_port=in_port
)
match_drop = parser.OFPMatch(
ipv4_src=src_ip, ipv4_dst=dst_ip, eth_src=src_mac,
eth_dst=dst_mac, eth_type=ether_types.ETH_TYPE_IP,
)
actions_drop = []
self.add_flow(datapath, 2, match, actions)
self.add_flow(datapath, 1, match_drop, actions_drop)