概述

为了构建一个可以实现互联互通、支持多种网络技术并可提供一定网络功能的中小型网络,通过分析讨论我们的实现规划如下:

  1. 确定拓扑,针对中小型网络,搭建拓扑结构
  2. 确定所需的硬件或软件设备,我们计划在Ubuntu系统中,使用Mininet完成拓扑搭建同时完成各项要求
  3. 实现网络设备的正确的安装和配置,实现各个网络设备之间的互联互通,同时在此基础上,考虑设备之间的冗余、鲁棒性问题
  4. 完成各项网络技术的实现,如:使用SDN网络控制器进行联通、区分子网、使用NAT技术实现外网联通等
  5. 配置网络的安全设置,包括防火墙、访问控制列表等
  6. 安装和配置必要的软件,包括服务器软件、数据库软件等

SDN网络介绍

软件定义网络SDN(Software Defined Network)是由美国斯坦福大学CLean Slate研究组提出的一种新型网络创新架构,可通过软件编程的形式定义和控制网络,其控制平面和转发平面分离及开放性可编程的特点,被认为是网络领域的一场革命,为新型互联网体系结构研究提供了新的实验途径,也极大地推动了下一代互联网的发展。

SDN网络架构

SDN的整体架构由下到上(由南到北)分为数据平面、控制平面和应用平面,具体如图2-1所示。其中,数据平面由交换机等网络通用硬件组成,各个网络设备之间通过不同规则形成的SDN数据通路连接;控制平面包含了逻辑上为中心的SDN控制器,它掌握着全局网络信息,负责各种转发规则的控制;应用平面包含着各种基于SDN的网络应用,用户无需关心底层细节就可以编程、部署新应用。

SDN架构图

控制平面与数据平面之间通过SDN控制数据平面接口(control-data-plane interface,简称CDPI)进行通信,它具有统一的通信标准,主要负责将控制器中的转发规则下发至转发设备,最主要应用的是OpenFlow协议。控制平面与应用平面之间通过SDN北向接口(northbound interface,简称NBI)进行通信,而NBI并非统一标准,它允许用户根据自身需求定制开发各种网络管理应用。

SDN中的接口具有开放性,以控制器为逻辑中心,南向接口负责与数据平面进行通信,北向接口负责与应用平面进行通信,东西向接口负责多控制器之间的通信。最主流的南向接口CDPI采用的是OpenFlow协议。OpenFlow最基本的特点是基于流(Flow)的概念来匹配转发规则,每一个交换机都维护一个流表(Flow Table),依据流表中的转发规则进行转发,而流表的建立、维护和下发都是由控制器完成的。针对北向接口,应用程序通过北向接口编程来调用所需的各种网络资源,实现对网络的快速配置和部署。东西向接口使控制器具有可扩展性,为负载均衡和性能提升提供了技术保障。

SDN的优势

SDN具有传统网络无法比拟的优势:首先,数据控制解耦合使得应用升级与设备更新换代相互独立,加快了新应用的快速部署;其次,网络抽象简化了网络模型,将运营商从繁杂的网络管理中解放出来,能够更加灵活地控制网络;最后,控制的逻辑中心化使用户和运营商等可以通过控制器获取全局网络信息,从而优化网络,提升网络性能。


SDN设计方案

需求分析

在我们设计的网络中,为了更加方便的管理网络并且利用编程的方式去设计网络,因此我们需要选择利用SDN网络技术去搭建我们的网络。并且我们所设计的网络是一个划分子网的网络架构,因此我们需要一个路由协议,对我们该网络的报文进行转发处理。而由于原本的OSPF以及RIP动态路由协议不适合部署在SDN网络下,因此我们选择自己构建私有的路由协议实现路由功能,使得转发自动寻路。

而在路由协议的过程中,我们需要考虑到拓扑的带宽是否充足,是否会发生拥塞,避免某一条链路负载过大的现象发生,因此我们还需要设计流量监控的功能,便于我们分析网络中的链路负载信息,从而实现路由协议选择路径的时候,能够动态的选择最优路径,从而减轻对于我们网络中链路中的负载。

SDN网络规划

我们需要将网络划分网段,并且构建核心交换部分,其中主干网拓扑图所示,在我们设计的网络中,包括DHCP服务器、NAPT服务节点S7、web服务器、ftp服务器、以及各个网段,所属网段分别为192.168.1.0/24、192.168.2.0/24、192.168.3.0/24、192.168.4.0/24。

SDN主干网络设计

网络路由设计方案

在我们设计的网络中,我们将网段分别为192.168.1.0/24、192.168.2.0/24、192.168.3.0/24、192.168.4.0/24。而其中每个子网的网关分别为子网中的openflow交换机。其中所用架构为SDN网络,并没有传统路由器,因此无法使用如OSPF、RIP等动态路由协议,因此为了实现跨子网的联通和交互自动寻路过程,我们选择设计私有的路由协议,从而现实我们的SDN网络中的跨子网通信。

路由协议流程图

其中我们所设计的路由协议流程如下:

  1. 当网络中主机发送报文的时候首先判断目的IP是否为自己所在为同一个网络,如果是则按照同一子网进行交互,如果不是则请求网关mac,并发向网关。
  2. 当网关收到报文后,判断是否有流表项匹配可以转发该报文,如果有则按照流表修改mac并进行转发,如果没有则发送给控制器。
  3. 控制器接收到报文后,判断目的地址是否有arp记录,如果没有则,从每个边界交换机向边界发送arp请求,并缓存报文。如果有则跳向5)。
  4. 当控制器收到arp回复报文之后记录源地址的mac和ip对应情况,以及端口、交换机信息。跳向5)
  5. 控制器根据报文的源信息和目的信息(mac地址、交换机、端口号、链路的负载情况),利用Dijkstra算法进行寻路计算,计算出当前的最优路径。
  6. 根据寻路结果进行寻路流表和mac地址更改流表的下发,使得下一次报文可以直接依靠流表进行转发。
  7. 将报文从目的地址记录的交换机和端口送出。

其中在步骤6,下发流表的情况如下所述:

  1. 路径中的第一个openflow交换机:匹配为报文的目的IP地址,且目的mac为网关mac地址;作用为修改报文的mac地址为其IP对应的mac地址以及转发该报文。
  2. 路径中的后续openflow交换机:匹配为报文的目的IP地址;作用为转发报文。

采用以上的路由协议可以实现我们的SDN网络的划分子网的通信,并且依靠下发流表的方式,并且采用Dijkstra算法进行寻路计算,可以使得在一次寻路之后,短时间内依靠流表进行转发,并且转发路径为当前最优,提高我们的减少我们的报文传输延迟和降低我们的丢包率。

流量监控设计方案

在上述我们设计的网络以及路由协议中,我们所用的寻路方法为掌网络拓扑信息后利用Dijkstra算法进行寻路计算,而这之中,需要知道每条链路的权重信息。因此我们这里选择利用控制器定期请求每个交换机的每个端口的byte接收总数,并与历史记录中的byte数相减,从而算出近段时间内的链路负载情况,随后将链路负载情况归一化计算得出每条链路的权重,从而使得我们可以利用Dijkstra算法算出我们的最优报文转发数据。

流量监控流程图


SDN网络的实现

拓扑搭建

网络和控制器的生成
1
2
3
4
5
6
7
8
9
10
11
net = Mininet(topo=None,
build=False,
ipBase='192.168.0.0/20')

info('*** Adding controller\n')
c0 = net.addController(name='c0',
controller=RemoteController,
ip='127.0.0.1',
protocol='tcp',
port=6633)

交换机的生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
info('*** Add switches\n')
s9 = net.addSwitch('s9', cls=OVSKernelSwitch, failMode='standalone')
s5 = net.addSwitch('s5', cls=OVSKernelSwitch, dpid='0000000000000005')
s4 = net.addSwitch('s4', cls=OVSKernelSwitch, dpid='0000000000000004')
s11 = net.addSwitch('s11', cls=OVSKernelSwitch, failMode='standalone')
s3 = net.addSwitch('s3', cls=OVSKernelSwitch, dpid='0000000000000003')
s6 = net.addSwitch('s6', cls=OVSKernelSwitch, dpid='0000000000000006')
s10 = net.addSwitch('s10', cls=OVSKernelSwitch, failMode='standalone')
s7 = net.addSwitch('s7', cls=OVSKernelSwitch, dpid='0000000000000007')
s8 = net.addSwitch('s8', cls=OVSKernelSwitch, failMode='standalone')
s1 = net.addSwitch('s1', cls=OVSKernelSwitch, dpid='0000000000000001')
s2 = net.addSwitch('s2', cls=OVSKernelSwitch, dpid='0000000000000002')
dhcp = net.addSwitch('dhcp', cls=OVSKernelSwitch, dpid='0000000000000008')

主机的生成
1
2
3
4
5
6
7
8
9
10
11
12
info('*** Add hosts\n')
h8 = net.addHost('h8', cls=Host,ip='0.0.0.0')
h9 = net.addHost('h9', cls=Host,ip='0.0.0.0')
#h4 = net.addHost('h4', cls=Host, ip='192.168.2.60/24', defaultRoute='via 192.168.2.254')
h4 = net.addHost('h4', cls=Host,ip='0.0.0.0')
h7 = net.addHost('h7', cls=Host,ip='0.0.0.0')
#h1 = net.addHost('h1', cls=Host, ip='192.168.1.60/24', defaultRoute='via 192.168.1.254')
h1 = net.addHost('h1', cls=Host,ip='0.0.0.0')
h3 = net.addHost('h3', cls=Host,ip='0.0.0.0')
h2 = net.addHost('h2', cls=Host,ip='0.0.0.0')
h5 = net.addHost('h5', cls=Host,ip='0.0.0.0')
h6 = net.addHost('h6', cls=Host,ip='0.0.0.0')
NAPT和服务器的网卡接入
1
2
3
4
info('*** Add NAT\n')
# nat = net.addNAT('nat', connect=False, ip='192.168.5.1/20', inNamespace = False)
_intf_1 = Intf(intfName1, node=s7, port=1)
_intf_2 = Intf(intfName2, node=s11, port=5)
链路连接的生成
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
info('*** Add links\n')
# net.addLink(nat, s7, 1, 1)
net.addLink(s10, s3, 1, 1)
net.addLink(s9, s2, 1, 1)
net.addLink(s8, s1, 1, 1)
net.addLink(s11, s6, 1, 1)
net.addLink(s6, s7, 2, 2)
net.addLink(s7, s4, 3, 1)
net.addLink(s7, s5, 4, 1)
net.addLink(s4, s5, 2, 2)
net.addLink(s4, s1, 3, 2)
net.addLink(s4, s2, 4, 2)
net.addLink(s4, s3, 5, 2)
net.addLink(s1, s5, 3, 3)
net.addLink(s5, s2, 4, 3)
net.addLink(s5, s3, 5, 3)
net.addLink(h1, s8, 1, 2)
net.addLink(s8, h2, 3, 1)
net.addLink(s8, h3, 4, 1)
net.addLink(s9, h4, 2, 1)
net.addLink(s9, h5, 3, 1)
net.addLink(s9, h6, 4, 1)
net.addLink(s10, h7, 2, 1)
net.addLink(s10, h8, 3, 1)
net.addLink(s10, h9, 4, 1)
net.addLink(dhcp, s4, 1, 6)
net.addLink(dhcp, s5, 2, 6)
交换机与网络的启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
info('*** Starting network\n')
net.build()
net.start()
info('*** Starting controllers\n')
for controller in net.controllers:
controller.start()

info('*** Starting switches\n')
net.get('s9').start([])
net.get('s5').start([c0])
net.get('s4').start([c0])
net.get('s11').start([])
net.get('s3').start([c0])
net.get('s6').start([c0])
net.get('s10').start([])
net.get('s7').start([c0])
net.get('s8').start([])
net.get('s1').start([c0])
net.get('s2').start([c0])
net.get('dhcp').start([c0])
NAPT和DHCP中继的脚本运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    h1.cmd("chmod 777 /etc/resolv.conf")
h1.cmd("echo $'nameserver 8.8.8.8\noptions edns0\nsearch localdomain example.org' > /etc/resolv.conf")

net.get('dhcp').cmd('ifconfig dhcp-eth1 192.168.0.1 netmask 255.255.255.0')
net.get('dhcp').cmd('ifconfig dhcp-eth2 192.168.0.2 netmask 255.255.255.0')
net.get('dhcp').cmd(" ip route add 192.168.0.0/21 dev dhcp-eth1 via 192.168.0.254")
dhcp.cmd(" arp -s 192.168.0.254 00:00:00:00:11:00")
dhcp.cmd("rm -r /var/lib/dhcp/dhclient.leases")
dhcp.cmd("rm -r /var/lib/dhcp/dhcpd.leases")
dhcp.cmd("service isc-dhcp-server restart &")
input("------------Waiting to start controller and enter space to continue------------")
info('*** Post configure switches and hosts\n')

for host in net.hosts:
if host.params['ip'] == '0.0.0.0':
get_ip(host)
info('*** Post configure switches and hosts\n')

CLI(net)
net.stop()
def get_ip(host):
info('*** get ip for {}\n'.format(host))
host.cmd("dhclient {}-eth1".format(host.name))

控制器的初始化

在控初始阶段制器需要进行初始化,才能知晓全局的交换拓扑信息,并且交换机才有默认发给控制器的流表项,随后才能实现我们项目中的报文交互过程,并且这个初始化也能实现我们项目中某条链路断开后的重新掌握信息,增加网络的健壮性。

检测链路的加入和离开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@set_ev_cls(event.EventLinkAdd)
def _link_add_handler(self, event):
self.logger.info("!!!A link add.Topology rediscovery...")
self.switch_status_handler(event, 'add')

@set_ev_cls(event.EventLinkDelete)
def _link_delete_handler(self, event):
self.logger.info("!!!A link leaved.Topology rediscovery...")
self.switch_status_handler(event, 'dele')
self.dele_flow(event)

def dele_flow(self, event):
for s in self.all_switches:
datapath = s.dp
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
match = parser.OFPMatch(eth_type=ether_types.ETH_TYPE_IP)
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)
记录链路和交换机并初始化
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
def switch_status_handler(self, event, type):
if type == 'dele':
self.topo.init()
elif type == 'add':
pass

all_switches = copy.copy(get_switch(self, None))
self.all_switches = all_switches
# 获取交换机的端口地址
self.switch_adds = {}
for s in all_switches:
for port in s.ports:
self.switch_adds[(s.dp.id, port.port_no)] = port.hw_addr
self.nat_switch = copy.copy(get_switch(self, self.nat_switch_id))[0]
self.logger.info('-----------------------------nat switch is: {}----------------'.format(self.nat_switch.dp.id))

# get all datapathid
# 获取交换机的ID值
self.topo.switches = [s.dp.id for s in all_switches]

self.logger.info("switches {}".format(self.topo.switches))

self.datapaths = [s.dp for s in all_switches]

# get link and get port

all_link_stats = [(l.src.dpid, l.dst.dpid, l.src.port_no, l.dst.port_no) for l in
copy.copy(get_link(self, None))]
self.all_links = dict(
[[(l.src.dpid, l.src.port_no), [l.dst.dpid, l.dst.port_no]] for l in copy.copy(get_link(self, None))])

self.logger.info("Number of links {}".format(len(all_link_stats)))

all_link_repr = ''
dhcp_links = copy.copy(get_link(self, self.dhcp_switch))
self.dhcp_link_stats = [(l.src.dpid, l.dst.dpid, l.src.port_no, l.dst.port_no) for l in dhcp_links]
print("dhcp links: {}".format(self.dhcp_link_stats))
for s1, s2, p1, p2 in all_link_stats:
# 如果之前没有记录才新增邻居记录,并初始化
if not (self.topo.find_adjacent(s1, s2)) and not (self.topo.find_adjacent(s2, s1)):
self.topo.set_adjacent(s1, s2, p1)
self.topo.set_adjacent(s2, s1, p2)
all_link_repr += 's{}p{}--s{}p{}\n'.format(s1, p1, s2, p2)
self.logger.info("All links:\n" + all_link_repr)

# 获取边界交换机
self.edge_switch = []
intra_port = []
for l in all_link_stats:
intra_port.append((l[0], l[2]))
intra_port.append((l[1], l[3]))
for s in all_switches:
for port in s.ports:
if (s.dp.id, port.port_no) not in intra_port:
self.edge_switch.append((s, port.port_no))
self.logger.info("edge_switch:{} port:{}".format(s.dp.id, port.port_no))

交换机初始化
1
2
3
4
5
6
7
8
9
10
11
@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

match = parser.OFPMatch()
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath, 0, match, actions)

路由协议的RYU实现

对于我们网络中的路由协议的设计,我们需要首先实现寻路算法的封装,其次再是对与寻路后流表项的添加,而当其中可能未记录目的信息的时候会涉及报文的缓存以及arp报文的发送和处理,最终我们还要实现一个报文进入控制器的一个主要处理的功能函数,从而实现我们网络中划分子网的一个报文传输的路由协议。

寻路算法
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
    def Dijkstra(self, src_sw, dst_sw, src_port, dst_port):
# print("now we begin to find the shortest paths from sw:{} port:{} to sw:{} port:{} ".format(src_sw, src_port,
# dst_sw,
# dst_port))
bucket = CircleBucket(self.max_weight + 1) # 创建循环桶对象
bucket.updateBucket(0, src_sw) # 将源点先加入桶
# pre用于存储路径 并将所有交换机在路径的前置结点初始化为None
pre = {}
# dis用于存储距离 并将所有交换机离源交换机距离初始化为9999999
dis = {}
for sw in self.switches:
pre[sw] = None
dis[sw] = 9999999
dis[src_sw] = 0 # 将源结点的距离初始化为0
flag = 1 # flag 用于判断是否找到目的交换机 因为这是一个单源单宿问题
while flag == 1 and not bucket.checkBucketEmpty(): # 若还没找到目的交换机或桶内结点数不为空则继续循环
sw = bucket.getFirst() # 取出现在离源交换机最近的交换机
if sw == dst_sw: # 判断取出的交换机是不是目的交换机 如果是将flag置0并退出循环
flag = 0
break
for u in self.switches:
if u in (self.get_adjacent(sw)).keys():
# print(self.get_Adjdict(sw)[u])
# 遍历取出交换机的所有邻接交换机 并更新循环桶内数据
if dis[sw] + self.get_adjacent(sw)[u][1] < dis[u]: # 判断邻接交换机离源交换机的距离有没有缩短
dis[u] = dis[sw] + self.get_adjacent(sw)[u][1]
pre[u] = sw # 设置前置交换机
bucket.updateBucket(dis[u], u) # 将桶数据数据更新
spath = [dst_sw]
sw = dst_sw
while pre[sw] != None: # 通过pre找出最短路径
sw = pre[sw]
spath.append(sw)
spath.reverse() # 将路径反转为从到源交换机到目的交换机
# print("Find done.The shortest path :", end=" ")
# self.printPath(spath) # 输出最短路径

# 现在我们已经通过Dijkstra算法找到最短路径了,接下来把该路径转化为控制器配置的格式
# 交换机之间路径的配置格式如右 (src_sw,inport,outport)->.......->(dst_sw,inport,outport) 我们要把所有路经的交换机都记录为左边的格式
cpath = [] # configure path
inport = src_port
for i in range(len(spath) - 1):
s1 = spath[i]
s2 = spath[i + 1]
# get s1->s2 outport
outport = self.get_adjacent(s1)[s2][0]
cpath.append((s1, inport, outport))
inport = self.get_adjacent(s2)[s1][0]
cpath.append((dst_sw, inport, dst_port))
# return cpath can configure switch's path 返回可以配置的路径
return cpath

"""

定义一个循环桶的类

1. 建一个指定容量的桶,存放(w[u],u)类型数据并能根据w[u]的大小放入相应的位置。 w[u]为结点u离源点的位置

2. 能对桶内的数据进行更新修改,并重新放置。

3. 在取出一个数据后,能自动将桶的头指针位置转移到桶内w[u]最小的桶。

"""
class CircleBucket(object):
def __init__(self, buckets_num):
# 桶中存放数据的个数
self.buckets_num = buckets_num
# 创建桶数组 用于存放(w[u],[u])类型数据 因为距离源点同一距离的点不一定只有一个
self.buckets = [[] for i in range(self.buckets_num)]
# 桶的头指针位置 默认为0
self.first_bucket = 0
# 桶中所以数据个数
self.data_num = 0

# 更新桶的头指针位置
def updateFirst(self):
# 如果现在头指针位置的列表为空
if self.checkListEmpty(self.first_bucket):
self.first_bucket = (self.first_bucket + 1) % self.buckets_num
while self.checkListEmpty(self.first_bucket):
self.first_bucket = (self.first_bucket + 1) % self.buckets_num

# 获得桶的头指针的数据
def getFirst(self):
# 先进行头指针的更新
self.updateFirst()
# 如果列表不为空才进行操作
self.data_num -= 1
return self.buckets[self.first_bucket].pop()

# 向桶内更新数据 将w位置的桶内加入u 即源结点到u的距离为w
def updateBucket(self, w, u):
# 向桶内加入数据 每个桶里面都是一个列表
self.buckets[w % self.buckets_num].append(u)
self.data_num += 1

def checkListEmpty(self, w):
# 如果列表为空 返回true
if not self.buckets[w]:
return True
return False

# 判断桶内是否有数据
def checkBucketEmpty(self):
return self.data_num == 0

添加流表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def add_flow(self, datapath, priority, match, actions, buffer_id=None, idle_timeout=0, hard_timeout=0):
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, idle_timeout=idle_timeout,
hard_timeout=hard_timeout,
instructions=inst)
else:
mod = parser.OFPFlowMod(datapath=datapath, priority=priority, idle_timeout=idle_timeout,
hard_timeout=hard_timeout,
match=match, instructions=inst)
datapath.send_msg(mod)

配置路径的流表
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
def configure_path(self, shortest_path, msg, origin_mac, dst_mac, dst_ip):
# configure shortest path to switches
recv_datapath = msg.datapath
for switch, inport, outport in shortest_path:
flow_list = []
datapath = self._find_dp(int(switch))
actions = [datapath.ofproto_parser.OFPActionOutput(outport)]
# 目的IP为外网报文
# 第一个交换机
pkt = packet.Packet(msg.data)
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
if int(switch) == recv_datapath.id:
if dst_ip == self.nat_ip:


pkt_tcp = pkt.get_protocol(tcp.tcp)
pkt_udp = pkt.get_protocol(udp.udp)
if pkt_tcp:
tcp_match = datapath.ofproto_parser.OFPMatch(in_port=inport, ipv4_dst=dst_ip,
tcp_dst=pkt_tcp.dst_port,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_TCP)
tcp_actions = [datapath.ofproto_parser.OFPActionSetField(
ipv4_dst=self.tcp_in[pkt_tcp.dst_port][0]), datapath.ofproto_parser.OFPActionSetField(
tcp_dst=self.tcp_in[pkt_tcp.dst_port][1]),
datapath.ofproto_parser.OFPActionSetField(eth_dst=dst_mac)] + actions
dst_ip = self.tcp_in[pkt_tcp.dst_port][0]

flow_list.append((tcp_actions, tcp_match))

if pkt_udp:
udp_match = datapath.ofproto_parser.OFPMatch(in_port=inport, ipv4_dst=dst_ip,

udp_dst=pkt_udp.dst_port,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_UDP)

udp_actions = [datapath.ofproto_parser.OFPActionSetField(
ipv4_dst=self.udp_in[pkt_udp.dst_port][0]), datapath.ofproto_parser.OFPActionSetField(
udp_dst=self.udp_in[pkt_udp.dst_port][1]),
datapath.ofproto_parser.OFPActionSetField(eth_dst=dst_mac)] + actions
dst_ip = self.udp_in[pkt_udp.dst_port][0]
# print(self.udp_in)
# print(self.udp_out)
flow_list.append((udp_actions, udp_match))
elif int(switch) == self.nat_switch_id and not self.ipINvpn(pkt_ipv4.dst, msg.match['in_port'], datapath.id):

pkt_tcp = pkt.get_protocol(tcp.tcp)
pkt_udp = pkt.get_protocol(udp.udp)
if pkt_tcp:
tcp_match = datapath.ofproto_parser.OFPMatch(in_port=inport, ipv4_src=pkt_ipv4.src,
tcp_src=pkt_tcp.src_port, ipv4_dst=dst_ip,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_TCP)
tcp_actions = [datapath.ofproto_parser.OFPActionSetField(ipv4_src=self.nat_ip),
datapath.ofproto_parser.OFPActionSetField(
tcp_src=self.tcp_out[(pkt_ipv4.src, pkt_tcp.src_port)])] + actions
flow_list.append((tcp_actions, tcp_match))

if pkt_udp:
udp_match = datapath.ofproto_parser.OFPMatch(in_port=inport, ipv4_src=pkt_ipv4.src,
udp_src=pkt_udp.src_port, ipv4_dst=dst_ip,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_UDP)
udp_actions = [datapath.ofproto_parser.OFPActionSetField(ipv4_src=self.nat_ip),
datapath.ofproto_parser.OFPActionSetField(
udp_src=self.udp_out[(pkt_ipv4.src, pkt_udp.src_port)])] + actions
flow_list.append((udp_actions, udp_match))
else:

match = datapath.ofproto_parser.OFPMatch(in_port=inport, eth_dst=origin_mac, ipv4_dst=dst_ip,
eth_type=ether_types.ETH_TYPE_IP)
actions.insert(0, datapath.ofproto_parser.OFPActionSetField(eth_dst=dst_mac))
flow_list.append((actions, match))

elif not self.ipInSubnet(dst_ip, self.net_ip) :
# 如果是nat交换机 下发更改IP和端口的流表
if switch == self.nat_switch_id:
pkt_tcp = pkt.get_protocol(tcp.tcp)
pkt_udp = pkt.get_protocol(udp.udp)
if pkt_tcp:
tcp_match = datapath.ofproto_parser.OFPMatch(in_port=inport, ipv4_src=pkt_ipv4.src,
tcp_src=pkt_tcp.src_port, ipv4_dst=dst_ip,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_TCP)
tcp_actions = [datapath.ofproto_parser.OFPActionSetField(ipv4_src=self.nat_ip),
datapath.ofproto_parser.OFPActionSetField(
tcp_src=self.tcp_out[(pkt_ipv4.src, pkt_tcp.src_port)])] + actions
flow_list.append((tcp_actions, tcp_match))

if pkt_udp:
udp_match = datapath.ofproto_parser.OFPMatch(in_port=inport, ipv4_src=pkt_ipv4.src,
udp_src=pkt_udp.src_port, ipv4_dst=dst_ip,
eth_type=ether_types.ETH_TYPE_IP,
ip_proto=in_proto.IPPROTO_UDP)
udp_actions = [datapath.ofproto_parser.OFPActionSetField(ipv4_src=self.nat_ip),
datapath.ofproto_parser.OFPActionSetField(
udp_src=self.udp_out[(pkt_ipv4.src, pkt_udp.src_port)])] + actions
flow_list.append((udp_actions, udp_match))


else:
match = datapath.ofproto_parser.OFPMatch(in_port=inport, eth_dst=dst_mac,
eth_type=ether_types.ETH_TYPE_IP)
flow_list.append((actions, match))
else:
match = datapath.ofproto_parser.OFPMatch(in_port=inport, ipv4_dst=dst_ip,
eth_type=ether_types.ETH_TYPE_IP)
flow_list.append((actions, match))

assert datapath is not None
for actions, match in flow_list:
self.add_flow(datapath, 1, match, actions, hard_timeout=self.hard_timeout)

ARP报文处理
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
def arp_handler(self, msg):

datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
in_port = msg.match['in_port']

pkt = packet.Packet(msg.data)

eth = pkt.get_protocols(ethernet.ethernet)[0]
arp_pkt = pkt.get_protocol(arp.arp)

if eth:
eth_dst = eth.dst
eth_src = eth.src

if eth_dst == mac.BROADCAST_STR and arp_pkt:
# target ip
arp_dst_ip = arp_pkt.dst_ip

if arp_pkt:

# specify the operation that the sender is performing:1 for request,2 for reply
opcode = arp_pkt.opcode

# src ip
arp_src_ip = arp_pkt.src_ip
# dst ip
arp_dst_ip = arp_pkt.dst_ip

# arp请求
if opcode == arp.ARP_REQUEST:
if arp_dst_ip == self.nat_ip or self.ipINvpn(arp_dst_ip, in_port, datapath.id):
# send arp reply from in port
actions = [parser.OFPActionOutput(in_port)]
arp_reply = packet.Packet()

arp_reply.add_protocol(ethernet.ethernet(
ethertype=eth.ethertype,
dst=eth_src,
src=self.switch_adds[(self.nat_switch_id, self.nat_switch_port)]))

# add arp protocol
arp_reply.add_protocol(arp.arp(
opcode=arp.ARP_REPLY,
src_mac=self.switch_adds[(self.nat_switch_id, self.nat_switch_port)],
src_ip=arp_dst_ip,
dst_mac=eth_src,
dst_ip=arp_src_ip))

# serialize the packet to binary format 0101010101
arp_reply.serialize()
# arp reply
out = parser.OFPPacketOut(
datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions, data=arp_reply.data)
datapath.send_msg(out)
return

if arp_dst_ip in self.arp_table:
# send arp reply from in port
actions = [parser.OFPActionOutput(in_port)]
arp_reply = packet.Packet()

arp_reply.add_protocol(ethernet.ethernet(
ethertype=eth.ethertype,
dst=eth_src,
src=self.arp_table[arp_dst_ip][0]))

arp_reply.add_protocol(arp.arp(
opcode=arp.ARP_REPLY,
src_mac=self.arp_table[arp_dst_ip][0],
src_ip=arp_dst_ip,
dst_mac=eth_src,
dst_ip=arp_src_ip))
# serialize the packet to binary format 0101010101
arp_reply.serialize()
# arp reply
out = parser.OFPPacketOut(
datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions, data=arp_reply.data)
datapath.send_msg(out)
return

elif in_port != self.nat_switch_port or datapath.id != self.nat_switch_id:
# send arp reply from in port
actions = [parser.OFPActionOutput(in_port)]
arp_reply = packet.Packet()

arp_reply.add_protocol(ethernet.ethernet(
ethertype=eth.ethertype,
dst=eth_src,
src=self.switch_adds[(datapath.id, in_port)]))

# add arp protocol
arp_reply.add_protocol(arp.arp(
opcode=arp.ARP_REPLY,
src_mac=self.switch_adds[(datapath.id, in_port)],
src_ip=arp_dst_ip,
dst_mac=eth_src,
dst_ip=arp_src_ip))

# serialize the packet to binary format 0101010101
arp_reply.serialize()
# arp reply
out = parser.OFPPacketOut(
datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions, data=arp_reply.data)
datapath.send_msg(out)
return

缓存报文
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
# 存储并发送arp
def stor(self, msg):
# msg = ev.msg
datapath = msg.datapath
port = msg.match['in_port']
pkt = packet.Packet(msg.data)
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
if not pkt_ethernet:
return
eth = pkt.get_protocols(ethernet.ethernet)[0]
eth_dst = eth.dst
eth_src = eth.src
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
# src ip
src_ip = copy.copy(pkt_ipv4.src)
# dst ip
dst_ip = copy.copy(pkt_ipv4.dst)
parser = datapath.ofproto_parser
# 不在私网内
if not self.ipInSubnet(dst_ip, self.net_ip) and not (
self.ipInSubnet(pkt_ipv4.dst, "/".join((self.nat_ip, self.nat_mask)))):
# 修改原ip
dst_ip = self.net_getaway_ip
# 存储报文
self.buffer.append([src_ip, dst_ip, 20, msg])
self.logger.info("\n----------------Stor a Packet and Send ARP in edge_switch for {}----------------".format(dst_ip))

# 发送arp
for s, port_no in self.edge_switch:
if s.dp.id == datapath.id and port == port_no:
continue
self.logger.info("Send ARP in edge_switch:{} port:{}".format(s.dp.id, port_no))

actions = [parser.OFPActionOutput(port_no)]
arp_send = packet.Packet()

arp_send.add_protocol(ethernet.ethernet(
ethertype=2054,
dst="ff:ff:ff:ff:ff:ff",
src=eth_src if s.dp.id!=self.nat_switch_id else self.switch_adds[(self.nat_switch_id, self.nat_switch_port)]))

# add arp protocol
arp_send.add_protocol(arp.arp(
opcode=arp.ARP_REQUEST,
src_mac=eth_src if s.dp.id!=self.nat_switch_id else self.switch_adds[(self.nat_switch_id, self.nat_switch_port)],
src_ip=src_ip,
dst_mac="00:00:00:00:00:00",
dst_ip=dst_ip))

# serialize the packet to binary format 0101010101
arp_send.serialize()
# arp reply
out = parser.OFPPacketOut(
datapath=s.dp,
buffer_id=s.dp.ofproto.OFP_NO_BUFFER,
in_port=s.dp.ofproto.OFPP_CONTROLLER,
actions=actions, data=arp_send.data)
# print(dst_ip)
s.dp.send_msg(out)
print("\n")

有ARP回复后查询缓存数据并发送
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
# 查寻发送
def find_send(self):
for i in range(len(self.buffer) - 1, -1, -1):
self.logger.info(
"Find a packet in buffer can be sent from {} to {}".format(self.buffer[i][0], self.buffer[i][1]))
curr_switch = self.buffer[i][3].datapath.id
in_port = self.buffer[i][3].match['in_port']
if self.buffer[i][0] in self.arp_table and self.buffer[i][1] in self.arp_table:
if self.buffer[i][1] == self.net_getaway_ip:
pkt = packet.Packet(self.buffer[i][3].data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
parser = self.nat_switch.dp.ofproto_parser
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
pkt_icmp = pkt.get_protocol(icmp.icmp)
pkt_tcp = pkt.get_protocol(tcp.tcp)
pkt_udp = pkt.get_protocol(udp.udp)
src_ip = copy.copy(pkt_ipv4.src)

# 修改mac
eth.dst = self.arp_table[self.net_getaway_ip][0]
# 修改原ip
pkt_ipv4.src = self.nat_ip

if pkt_icmp:
# 修改id
pkt_icmp.data.id = self.icmp_out[(src_ip, pkt_icmp.data.id)]
self.ICMPTTLinit(pkt_icmp.data.id)
pkt_icmp.csum = 0
elif pkt_tcp:
# 修改id
pkt_tcp.src_port = self.tcp_out[(src_ip, pkt_tcp.src_port)]
pkt_tcp.csum = 0
elif pkt_udp:
# 修改id
pkt_udp.src_port = self.udp_out[(src_ip, pkt_udp.src_port)]
pkt_udp.csum = 0

pkt.serialize()

actions = [parser.OFPActionOutput(self.nat_switch_port)]
self.send_out(self.nat_switch, actions, pkt.data)

elif in_port == self.nat_switch_port and curr_switch == self.nat_switch_id:
pkt = packet.Packet(self.buffer[i][3].data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
parser = self.nat_switch.dp.ofproto_parser
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
print(pkt_ipv4.dst)
dst_mac, dst_switch_id, final_port = self.arp_table[pkt_ipv4.dst]
# print("find switch {} port {} ".format(dst_switch_id, final_port))
eth.dst = dst_mac
print(eth.dst)
pkt.serialize()
actions = [parser.OFPActionOutput(final_port)]
dst_switch = copy.copy(get_switch(self, dst_switch_id))[0]
out = parser.OFPPacketOut(
datapath=dst_switch.dp,
buffer_id=dst_switch.dp.ofproto.OFP_NO_BUFFER,
in_port=dst_switch.dp.ofproto.OFPP_CONTROLLER,
actions=actions, data=pkt.data)
dst_switch.dp.send_msg(out)
else:
self.default_handler(curr_switch, in_port, self.buffer[i][0], self.buffer[i][1], self.buffer[i][3])
self.buffer.pop(i)

内网报文默认处理
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
def default_handler(self, curr_switch, in_port, src_ip, dst_ip, msg):
datapath = msg.datapath
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
# 寻路并转发,要修改mac
dst_mac, dst_switch, final_port = self.arp_table[dst_ip]
pkt = packet.Packet(msg.data)
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
eth = pkt.get_protocols(ethernet.ethernet)[0]
origin_mac = eth.dst
eth.dst = dst_mac
if origin_mac == eth.dst and (curr_switch,in_port) not in self.edge_switch:
actions = [parser.OFPActionOutput(OFPP_IN_PORT)]
else:
pkt.serialize()
msg.data = pkt.data
# 找到源和目的主机的mac及连接的虚拟机
self.logger.info("Received a Packet from intranet {} to intranet {} change MAC from {} to "
"{} \n".format(pkt_ipv4.src,pkt_ipv4.dst,origin_mac,dst_mac))

shortest_path = self.topo.Dijkstra(
curr_switch,
dst_switch,
in_port,
final_port)
assert len(shortest_path) > 0

path_str = ''

# (s1,inport,outport)->(s2,inport,outport)->...->(dest_switch,inport,outport)
for s, ip, op in shortest_path:
path_str = path_str + "--{}-{}-{}--".format(ip, s, op)
self.logger.info(
"Configure the shortset path from {} to {} —— {}\n".format(src_ip, dst_ip, path_str))

# self.logger.info("Now configuring switches of interest")
self.configure_path(shortest_path, msg, origin_mac, dst_mac, dst_ip)
# self.logger.info("Configure done")
# sleep(1)
out_port = shortest_path[-1][2]
in_port = shortest_path[-1][1]
actions = [parser.OFPActionOutput(out_port)]
out_switch = get_switch(self, shortest_path[-1][0])[0]
datapath = out_switch.dp
parser = datapath.ofproto_parser

if msg.buffer_id == ofproto.OFP_NO_BUFFER:
data = msg.data
# send the packet out to avoid packet loss
out = parser.OFPPacketOut(
datapath=datapath,
buffer_id=msg.buffer_id,
actions=actions,
in_port=in_port,
data=data
)
datapath.send_msg(out)

报文直接发往交换机端口
1
2
3
4
5
6
7
8
9
10
@staticmethod
def send_out(switch, actions, data):
parser = switch.dp.ofproto_parser
out = parser.OFPPacketOut(
datapath=switch.dp,
buffer_id=switch.dp.ofproto.OFP_NO_BUFFER,
in_port=switch.dp.ofproto.OFPP_CONTROLLER,
actions=actions, data=data)
switch.dp.send_msg(out)

报文进入控制器后的处理
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
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def packet_in_handler(self, event):

# msg is an object which desicribes the corresponding OpenFlow message
msg = event.msg
datapath = msg.datapath
# object for the negotiated Openflow version
parser = datapath.ofproto_parser
# through which port the packet comes in
in_port = msg.match['in_port']
# self.logger.info("From datapath {} port {} come in a packet".format(datapath.id,in_port))
# get src_mac and dest mac
pkt = packet.Packet(msg.data)
eth = pkt.get_protocols(ethernet.ethernet)[0]
dpid = datapath.id
# drop lldp
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
# self.logger.info("LLDP")
return
# get source and destination mac address
dst_mac = eth.dst
src_mac = eth.src
# check if this is an arp packet
arp_pkt = pkt.get_protocol(arp.arp)
self.mac_to_port.setdefault(dpid, {})
self.mac_to_port[dpid][src_mac] = in_port
self.TTLdes()
pkt_ipv4 = pkt.get_protocol(ipv4.ipv4)
if pkt_ipv4:
src_ip = pkt_ipv4.src
dst_ip = pkt_ipv4.dst
if pkt.get_protocol(ipv6.ipv6):
match = parser.OFPMatch(eth_type=eth.ethertype)
actions = []
self.add_flow(datapath, 1, match, actions)
return None in_port))

pkt_dhcp = pkt.get_protocol(dhcp.dhcp)
# 判断是否为dhcp报文

if pkt_dhcp:
if dpid != self.dhcp_switch:
self.dhcp_relay_handler(msg)
return
# 判断是否为arp报文
if arp_pkt:
# 记录ip mac 设备号 端口号
if arp_pkt.src_ip not in self.arp_table or self.arp_table[arp_pkt.src_ip] != (
src_mac, dpid, in_port) and src_mac != self.switch_adds[(self.nat_switch_id, self.nat_switch_port)]:
self.arp_table[arp_pkt.src_ip] = (src_mac, dpid, in_port)
# 查询发送
self.find_send()
self.arp_handler(msg)
return
else:
# if pkt_ipv4.src not in self.arp_table
# 发送到外网:目的为外网,且源不是
if not self.ipInSubnet(dst_ip, self.net_ip) and self.ipInSubnet(src_ip, self.net_ip):
self.NAT_out(msg)
return
# 外网发送到内网:目的为外网,且源也是外网
if not (self.ipInSubnet(src_ip, self.net_ip)):
if dst_ip == self.nat_ip:
self.NAT_in(msg)
return
# 查询到目的IP在ARP表中
if self.find(dst_ip):
self.default_handler(dpid, in_port, src_ip, dst_ip, msg)
# 未查询到结果
else:
self.stor(msg)

流量检测的RYU实现

实现网络中的流量监控,需要周期的让控制器请求交换机的端口信息,并做出计算和更新,因此需要RYU实现的函数有如下。

周期请求端口信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def _monitor(self):
while True:
for s in self.all_switches:
self._request_stats(s.dp)
hub.sleep(self.monitor_time)

def _request_stats(self, datapath):
self.logger.debug('send stats request: {}'.format(datapath.id))
ofproto = datapath.ofproto
parser = datapath.ofproto_parser

req = parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY)
datapath.send_msg(req)

返回端口信息处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
def _port_stats_reply_handler(self, ev):
body = ev.msg.body
# 更新流量差和全流量
# print(self.all_links)
for stat in sorted(body, key=attrgetter('port_no')):
if (ev.msg.datapath.id, stat.port_no) in self.all_links:
# 计算流量差
traffic_sub = stat.rx_bytes - self.topo.get_adjacent(ev.msg.datapath.id)[
self.all_links[(ev.msg.datapath.id, stat.port_no)][0]][3]
self.topo.update_adjacent(ev.msg.datapath.id, self.all_links[(ev.msg.datapath.id, stat.port_no)][0],
cycle_traffic=traffic_sub, all_traffic=stat.rx_bytes)
# 更新完后更新权重
self.topo.update_weight()

链路权重更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def update_adjacent(self, s1, s2, port=None, weight=None, cycle_traffic=None, all_traffic=None):
if port:
self.adjacent[s1][s2][0] = port
if weight:
self.adjacent[s1][s2][1] = weight
if cycle_traffic:
self.adjacent[s1][s2][2] = cycle_traffic
if all_traffic:
self.adjacent[s1][s2][3] = all_traffic

def update_weight(self):
all_traffic = [list(edge.values()) for edge in self.adjacent.values()]
max_cycle_traffic = max([max(i, key=lambda item: item[2])[2] for i in all_traffic])
min_cycle_traffic = min([min(i, key=lambda item: item[2])[2] for i in all_traffic])
self.update_times += 1
for s1, data in self.adjacent.items():
for s2, [_, _, cycle_traffic, _] in data.items():
# 计算权重并更新
weight = math.ceil(((cycle_traffic - min_cycle_traffic) * 100 / (
max_cycle_traffic - min_cycle_traffic) if max_cycle_traffic != min_cycle_traffic and cycle_traffic != min_cycle_traffic else 1) )
self.update_adjacent(s1, s2, weight=weight)
if self.update_times % len(self.switches) ==0:
self.print_weight()


效果展示

拓扑展示

从图中可以得到h1至h4有s1-s4-s2以及s1-s5-s2两条路线。

拓扑图

寻路展示

在h1 ping h4之前查看流表项如下可以看出流表中没有寻路的表。

寻路前流表

h1 ping h4显示两点间连通性正常:

ping操作

此时再查看流表项可以得到s1与s2间已经产生对应的转发表,以及可以将对应IP地址的mac地址修改正确的流表项。

查看流表

同时可以看到s4已经产生两条转发流表,证明寻路选择了s1-s4-s2这条路径。

查看流表

抓包分析

通过对s1的1端口与s2的1端口进行抓包分析可知:其均向对应的网关发送arp请求以得到相应的mac地址。

抓包分析

同时可知,icmp报文在经过s1前后其mac地址也得到了相应的更改,此处的修改即将s1处经过的该icmp报文mac地址修改为s4处,使其按照预定的寻路路径s1-s4-s2到达目的地h4。

抓包分析

内网的连通性得以验证,此时pingall可以发现内网主机是可以相互ping通的。

pingall操作

鸣谢:廉廉子、杰杰子、逸逸子、航航子、亮亮子