概述 为了构建一个可以实现互联互通、支持多种网络技术并可提供一定网络功能的中小型网络,通过分析讨论我们的实现规划如下:
确定拓扑,针对中小型网络,搭建拓扑结构 确定所需的硬件或软件设备,我们计划在Ubuntu系统中,使用Mininet完成拓扑搭建同时完成各项要求 实现网络设备的正确的安装和配置,实现各个网络设备之间的互联互通,同时在此基础上,考虑设备之间的冗余、鲁棒性问题 完成各项网络技术的实现,如:使用SDN网络控制器进行联通、区分子网、使用NAT技术实现外网联通等 配置网络的安全设置,包括防火墙、访问控制列表等 安装和配置必要的软件,包括服务器软件、数据库软件等 SDN网络介绍 软件定义网络SDN(Software Defined Network)是由美国斯坦福大学CLean Slate研究组提出的一种新型网络创新架构,可通过软件编程的形式定义和控制网络,其控制平面和转发平面分离及开放性可编程的特点,被认为是网络领域的一场革命,为新型互联网体系结构研究提供了新的实验途径,也极大地推动了下一代互联网的发展。
SDN网络架构 SDN的整体架构由下到上(由南到北)分为数据平面、控制平面和应用平面,具体如图2-1所示。其中,数据平面由交换机等网络通用硬件组成,各个网络设备之间通过不同规则形成的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。
网络路由设计方案 在我们设计的网络中,我们将网段分别为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网络中的跨子网通信。
其中我们所设计的路由协议流程如下:
当网络中主机发送报文的时候首先判断目的IP是否为自己所在为同一个网络,如果是则按照同一子网进行交互,如果不是则请求网关mac,并发向网关。 当网关收到报文后,判断是否有流表项匹配可以转发该报文,如果有则按照流表修改mac并进行转发,如果没有则发送给控制器。 控制器接收到报文后,判断目的地址是否有arp记录,如果没有则,从每个边界交换机向边界发送arp请求,并缓存报文。如果有则跳向5)。 当控制器收到arp回复报文之后记录源地址的mac和ip对应情况,以及端口、交换机信息。跳向5) 控制器根据报文的源信息和目的信息(mac地址、交换机、端口号、链路的负载情况),利用Dijkstra算法进行寻路计算,计算出当前的最优路径。 根据寻路结果进行寻路流表和mac地址更改流表的下发,使得下一次报文可以直接依靠流表进行转发。 将报文从目的地址记录的交换机和端口送出。 其中在步骤6,下发流表的情况如下所述:
路径中的第一个openflow交换机:匹配为报文的目的IP地址,且目的mac为网关mac地址;作用为修改报文的mac地址为其IP对应的mac地址以及转发该报文。 路径中的后续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='0.0.0.0' ) h7 = net.addHost('h7' , cls=Host,ip='0.0.0.0' ) 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' ) _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(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 ))                           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]                  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 ):                                    bucket = CircleBucket(self.max_weight + 1 )           bucket.updateBucket(0 , src_sw)                    pre = {}                  dis = {}         for  sw in  self.switches:             pre[sw] = None              dis[sw] = 9999999          dis[src_sw] = 0            flag = 1            while  flag == 1  and  not  bucket.checkBucketEmpty():               sw = bucket.getFirst()               if  sw == dst_sw:                   flag = 0                  break              for  u in  self.switches:                 if  u in  (self.get_adjacent(sw)).keys():                                                               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 :               sw = pre[sw]             spath.append(sw)         spath.reverse()                                               cpath = []           inport = src_port         for  i in  range (len (spath) - 1 ):             s1 = spath[i]             s2 = spath[i + 1 ]                          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 """ 定义一个循环桶的类 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                  self.buckets = [[] for  i in  range (self.buckets_num)]                  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()          def  updateBucket (self, w, u ):                  self.buckets[w % self.buckets_num].append(u)         self.data_num += 1      def  checkListEmpty (self, w ):                  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 ):         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)]                           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 ]                                                               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) :                          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:                  arp_dst_ip = arp_pkt.dst_ip     if  arp_pkt:                  opcode = arp_pkt.opcode                  arp_src_ip = arp_pkt.src_ip                  arp_dst_ip = arp_pkt.dst_ip                  if  opcode == arp.ARP_REQUEST:             if  arp_dst_ip == self.nat_ip or  self.ipINvpn(arp_dst_ip, in_port, datapath.id ):                                  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)]))                                  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))                                  arp_reply.serialize()                                  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:                                  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))                                  arp_reply.serialize()                                  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:                                  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)]))                                  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))                                  arp_reply.serialize()                                  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 def  stor (self, 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 = copy.copy(pkt_ipv4.src)          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)))):                  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))          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)]))                  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))                  arp_send.serialize()                  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)                  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)                                  eth.dst = self.arp_table[self.net_getaway_ip][0 ]                                  pkt_ipv4.src = self.nat_ip                 if  pkt_icmp:                                          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:                                          pkt_tcp.src_port = self.tcp_out[(src_ip, pkt_tcp.src_port)]                     pkt_tcp.csum = 0                  elif  pkt_udp:                                          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]                                  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          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                  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 = ''                   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.configure_path(shortest_path, msg, origin_mac, dst_mac, dst_ip)                           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          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 = event.msg     datapath = msg.datapath          parser = datapath.ofproto_parser          in_port = msg.match ['in_port' ]               pkt = packet.Packet(msg.data)     eth = pkt.get_protocols(ethernet.ethernet)[0 ]     dpid = datapath.id           if  eth.ethertype == ether_types.ETH_TYPE_LLDP:                  return           dst_mac = eth.dst     src_mac = eth.src          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)          if  pkt_dhcp:         if  dpid != self.dhcp_switch:             self.dhcp_relay_handler(msg)         return           if  arp_pkt:                  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  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                   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               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显示两点间连通性正常:
此时再查看流表项可以得到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通的。
鸣谢:廉廉子、杰杰子、逸逸子、航航子、亮亮子