概述 为了构建一个可以实现互联互通、支持多种网络技术并可提供一定网络功能的中小型网络,通过分析讨论我们的实现规划如下:
确定拓扑,针对中小型网络,搭建拓扑结构 确定所需的硬件或软件设备,我们计划在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通的。
鸣谢:廉廉子、杰杰子、逸逸子、航航子、亮亮子