一、采集网络拓扑的链路信息

下面这段ryu代码link_collector.py用来采集链路的信息,具体就是链路上的两个交换机的端口的 端口统计信息,主要考虑接受转发字节数。

运行:

ryu-manager --observe-links link_collector.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
import json
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.lib import hub
from ryu.ofproto import ofproto_v1_3
from ryu.topology import event, switches


class LinkCollector(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

def __init__(self, *args, **kwargs):
super(LinkCollector, self).__init__(*args, **kwargs)
self.topology_api_app = self
self.links = {}
self.switches = {}
self.datapaths = {}
self.link_counter = 1
self.monitor_thread = hub.spawn(self._monitor)

@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

self.datapaths[datapath.id] = datapath

match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
mod = parser.OFPFlowMod(datapath=datapath, priority=0, match=match,
instructions=inst)
datapath.send_msg(mod)

@set_ev_cls(event.EventSwitchEnter)
def get_topology_data(self, ev):
switch = ev.switch
self.switches[switch.dp.id] = switch

@set_ev_cls(event.EventSwitchLeave)
def switch_leave_handler(self, ev):
switch = ev.switch
if switch.dp.id in self.switches:
del self.switches[switch.dp.id]

@set_ev_cls(event.EventLinkAdd)
def link_add_handler(self, ev):
link = ev.link
# 判断添加的链路是否是交换机与交换机之间的链路还是交换机与主机之间的链路
if link.src.dpid not in self.switches or link.dst.dpid not in self.switches:
# 这是交换机与主机之间的链路
return
else:
# 这是交换机与交换机之间的链路,生成链路ID并将其保存在links字典中
link_id = self.link_counter
self.link_counter += 1
self.links[link] = {'id': link_id, 'ports': (link.src.port_no, link.dst.port_no)}

# 保存链路的目的交换机编号到txt文件中
with open("link_dest_sw.txt", "a") as f:
f.write(f"Link ID: {link_id}, Destination Switch: {link.dst.dpid}\n")

@set_ev_cls(event.EventLinkDelete)
def link_delete_handler(self, ev):
link = ev.link
if link in self.links:
del self.links[link]

def _monitor(self):
i=21
while i>=0:
for link, info in self.links.items():
src_sw, dst_sw = link.src.dpid, link.dst.dpid
src_port, dst_port = info['ports']
for datapath_id, datapath in self.datapaths.items():
if datapath_id == src_sw:
self._request_stats(datapath, src_port)
elif datapath_id == dst_sw:
self._request_stats(datapath, dst_port)
i=i-1
hub.sleep(10)
self.logger.info('finish!!!!')

def _request_stats(self, datapath, port_no):
parser = datapath.ofproto_parser
req = parser.OFPPortStatsRequest(datapath, 0, port_no)
datapath.send_msg(req)

@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
def _port_stats_reply_handler(self, ev):
body = ev.msg.body
datapath = ev.msg.datapath
# 只关心发送和接收的字节数
for stat in body:
port_no = stat.port_no
rx_bytes = stat.rx_bytes
tx_bytes = stat.tx_bytes
# 保存链路统计信息至文件
link_info = self._get_link_info(datapath.id, port_no)
if link_info:
link_id, remote_dpid = link_info
with open("link_stats.txt", "a") as f:
f.write(f"Link ID: {link_id}, Switch {datapath.id} Port {port_no}: "
f"RX bytes: {rx_bytes}, TX bytes: {tx_bytes}\n")

def _get_link_info(self, dpid, port_no):
for link, info in self.links.items():
src_sw, dst_sw = link.src.dpid, link.dst.dpid
src_port, dst_port = info['ports']
if src_sw == dpid and src_port == port_no:
return info['id'], dst_sw
elif dst_sw == dpid and dst_port == port_no:
return info['id'], src_sw
return None

采集的mininet创建的网络拓扑:

image-20230508164845037

可以看到一共有6条(不包括交换机与主机之间的链路)交换机与交换机之间的链路:

采集的信息:

image-20230509022556614

可以看到确实采集到了6条链路信息,但是Link ID编号没有按照递增的顺序。

这里修改ryu的代码发现也不可以,所以采用另一种方式,写一个python脚本来处理txt文件。

代码如下:

Link_sort.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
#这是清理数据的第一步,将链路重新编号(递增的顺序)

# 首先,我们定义一个函数来读取txt文件并提取"Link ID"的值
filename="link_stats.txt"
def extract_link_id(filename):
link_ids = []
with open(filename, "r") as f:
for line in f:
if "Link ID:" in line:
link_ids.append(int(line.split("Link ID: ")[1].split(",")[0]))
return link_ids


# 接下来,提取Link Id并将它们存储在一个列表中
link_ids = extract_link_id(filename)

# 然后删除重复项并按升序对列表进行排序
link_ids = sorted(set(link_ids), key=link_ids.index)

# 最后,我们再次遍历txt文件,并用相应的索引替换每个“Link ID”值
count = 1
with open(filename, "r") as f:
lines = f.readlines()

with open("filename1.txt", "w") as f:
for line in lines:
if "Link ID:" in line:
link_id = int(line.split("Link ID: ")[1].split(",")[0])
index = link_ids.index(link_id) + 1
line = line.replace(f"Link ID: {link_id}", f"Link ID: {index}")
f.write(line)
else:
f.write(line)

处理之后的txt文件:

image-20230509113256463

成功编号,但是代码原因一条链路会重复输出两次链路 因为一条源交换机和一条目的交换机,再写一段代码去掉重复采集的信息。

Link_Deduplication.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 这是清理数据的第二步,只保留源交换机的链路,也就是去掉一半重复的链路数据。
with open('filename1.txt', 'r') as file:
# 读取所有行放在列表中
lines = file.readlines()

# 创建一个空列表来保存已过滤的行
filtered_lines = []

# 定义要检查的字符串元素列表
string_elements = ['Switch 1 Port 2', 'Switch 1 Port 3', 'Switch 1 Port 4', 'Switch 5 Port 1', 'Switch 5 Port 4',
'Switch 3 Port 2']

# 遍历每一行
for line in lines:
# 检查该行是否包含其中要检查的字符串元素
if any(element in line for element in string_elements):
# 如果filtered_lines包含字符串元素,则将该行追加到该行
filtered_lines.append(line)

# 以写入模式打开文件
with open('filename2.txt', 'w') as file:
# 将所有行写入文件
file.writelines(filtered_lines)

因为它重复采集,所以还需要去掉重复的行。

Link_finally.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
#这是清理数据的第三步,去掉重复行

from collections import OrderedDict



filename="filename2.txt"
# 以读取模式打开文件
with open(filename, 'r') as f:
# 读取文件每一行
lines = f.readlines()

#下面是字符串匹配列表,用来判断是哪一条链路

string_elements = ['Link ID: 1', 'Link ID: 2', 'Link ID: 3', 'Link ID: 4', 'Link ID: 5',
'Link ID: 6']
list1=[]
list2=[]
list3=[]
list4=[]
list5=[]
list6=[]
#把每一个链路放在对应的列表中,方便后续去重复的行
for line in lines:
if string_elements[0] in line:
list1.append(line)
elif string_elements[1] in line:
list2.append(line)
elif string_elements[2] in line:
list3.append(line)
elif string_elements[3] in line:
list4.append(line)
elif string_elements[4] in line:
list5.append(line)
elif string_elements[5] in line:
list6.append(line)
#下面是对每一个列表去掉重复的行得到新的列表
new_list1 = list(OrderedDict.fromkeys(list1))
new_list2 = list(OrderedDict.fromkeys(list2))
new_list3 = list(OrderedDict.fromkeys(list3))
new_list4 = list(OrderedDict.fromkeys(list4))
new_list5 = list(OrderedDict.fromkeys(list5))
new_list6 = list(OrderedDict.fromkeys(list6))
#对多个列表中的元素按照顺序一个一个取出来,然后放到一个新的列表里
new_list = [item for sublist in zip(new_list1, new_list2, new_list3,new_list4,new_list5,new_list6) for item in sublist]
with open('filename3.txt', 'w') as file:
#把所有的行写入文件
file.writelines(new_list)
#下面代码是我输出打印,调试用的。
print(list1)
print(list2)
print(list3)
print(list4)
print(list5)
print(list6)
print(len(list1))
print(len(list2))
print(len(list3))
print(len(list4))
print(len(list5))
print(len(list6))

print(new_list1)
print(new_list2)
print(new_list3)
print(new_list4)
print(new_list5)
print(new_list6)
print(len(new_list1))
print(len(new_list2))
print(len(new_list3))
print(len(new_list4))
print(len(new_list5))
print(len(new_list6))

print(new_list)

print(len(new_list))

最后处理结果:

image-20230509113504105