├── PureSDN.py ├── PureSDN.pyc ├── README.md ├── __init__.py ├── fattree4.py ├── fattree8.py ├── network_awareness.py ├── network_awareness.pyc ├── network_monitor.py ├── network_monitor.pyc ├── setting.py └── setting.pyc /PureSDN.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Huang MaChi at Chongqing University 2 | # of Posts and Telecommunications, Chongqing, China. 3 | # Copyright (C) 2016 Li Cheng at Beijing University of Posts 4 | # and Telecommunications. www.muzixing.com 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 15 | # implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from ryu import cfg 20 | from ryu.base import app_manager 21 | from ryu.controller import ofp_event 22 | from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER 23 | from ryu.controller.handler import set_ev_cls 24 | from ryu.ofproto import ofproto_v1_3 25 | from ryu.lib.packet import packet 26 | from ryu.lib.packet import ethernet 27 | from ryu.lib.packet import arp 28 | from ryu.lib.packet import ipv4 29 | from ryu.lib.packet import tcp 30 | from ryu.lib.packet import udp 31 | 32 | import network_awareness 33 | import network_monitor 34 | import setting 35 | 36 | 37 | CONF = cfg.CONF 38 | 39 | 40 | class ShortestForwarding(app_manager.RyuApp): 41 | """ 42 | ShortestForwarding is a Ryu app for forwarding packets on shortest path. 43 | This App does not defined the path computation method. 44 | To get shortest path, this module depends on network awareness, 45 | network monitor and network delay detecttor modules. 46 | """ 47 | 48 | OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 49 | _CONTEXTS = { 50 | "network_awareness": network_awareness.NetworkAwareness, 51 | "network_monitor": network_monitor.NetworkMonitor} 52 | 53 | WEIGHT_MODEL = {'hop': 'weight', 'bw': 'bw'} 54 | 55 | def __init__(self, *args, **kwargs): 56 | super(ShortestForwarding, self).__init__(*args, **kwargs) 57 | self.name = "shortest_forwarding" 58 | self.awareness = kwargs["network_awareness"] 59 | self.monitor = kwargs["network_monitor"] 60 | self.datapaths = {} 61 | self.weight = self.WEIGHT_MODEL[CONF.weight] 62 | 63 | @set_ev_cls(ofp_event.EventOFPStateChange, [MAIN_DISPATCHER, DEAD_DISPATCHER]) 64 | def _state_change_handler(self, ev): 65 | """ 66 | Collect datapath information. 67 | """ 68 | datapath = ev.datapath 69 | if ev.state == MAIN_DISPATCHER: 70 | if not datapath.id in self.datapaths: 71 | self.logger.debug('register datapath: %016x', datapath.id) 72 | self.datapaths[datapath.id] = datapath 73 | elif ev.state == DEAD_DISPATCHER: 74 | if datapath.id in self.datapaths: 75 | self.logger.debug('unregister datapath: %016x', datapath.id) 76 | del self.datapaths[datapath.id] 77 | 78 | @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 79 | def _packet_in_handler(self, ev): 80 | ''' 81 | In packet_in handler, we need to learn access_table by ARP and IP packets. 82 | ''' 83 | msg = ev.msg 84 | pkt = packet.Packet(msg.data) 85 | arp_pkt = pkt.get_protocol(arp.arp) 86 | ip_pkt = pkt.get_protocol(ipv4.ipv4) 87 | 88 | if isinstance(arp_pkt, arp.arp): 89 | self.logger.debug("ARP processing") 90 | self.arp_forwarding(msg, arp_pkt.src_ip, arp_pkt.dst_ip) 91 | 92 | if isinstance(ip_pkt, ipv4.ipv4): 93 | self.logger.debug("IPV4 processing") 94 | if len(pkt.get_protocols(ethernet.ethernet)): 95 | eth_type = pkt.get_protocols(ethernet.ethernet)[0].ethertype 96 | self.shortest_forwarding(msg, eth_type, ip_pkt.src, ip_pkt.dst) 97 | 98 | def add_flow(self, dp, priority, match, actions, idle_timeout=0, hard_timeout=0): 99 | """ 100 | Send a flow entry to datapath. 101 | """ 102 | ofproto = dp.ofproto 103 | parser = dp.ofproto_parser 104 | inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)] 105 | mod = parser.OFPFlowMod(datapath=dp, priority=priority, 106 | idle_timeout=idle_timeout, 107 | hard_timeout=hard_timeout, 108 | match=match, instructions=inst) 109 | dp.send_msg(mod) 110 | 111 | def _build_packet_out(self, datapath, buffer_id, src_port, dst_port, data): 112 | """ 113 | Build packet out object. 114 | """ 115 | actions = [] 116 | if dst_port: 117 | actions.append(datapath.ofproto_parser.OFPActionOutput(dst_port)) 118 | 119 | msg_data = None 120 | if buffer_id == datapath.ofproto.OFP_NO_BUFFER: 121 | if data is None: 122 | return None 123 | msg_data = data 124 | 125 | out = datapath.ofproto_parser.OFPPacketOut( 126 | datapath=datapath, buffer_id=buffer_id, 127 | data=msg_data, in_port=src_port, actions=actions) 128 | return out 129 | 130 | def send_packet_out(self, datapath, buffer_id, src_port, dst_port, data): 131 | """ 132 | Send packet out packet to assigned datapath. 133 | """ 134 | out = self._build_packet_out(datapath, buffer_id, 135 | src_port, dst_port, data) 136 | if out: 137 | datapath.send_msg(out) 138 | 139 | def get_port(self, dst_ip, access_table): 140 | """ 141 | Get access port of dst host. 142 | access_table = {(sw,port):(ip, mac),} 143 | """ 144 | if access_table: 145 | if isinstance(access_table.values()[0], tuple): 146 | for key in access_table.keys(): 147 | if dst_ip == access_table[key][0]: # Use the IP address only, not the MAC address. (hmc) 148 | dst_port = key[1] 149 | return dst_port 150 | return None 151 | 152 | def get_port_pair_from_link(self, link_to_port, src_dpid, dst_dpid): 153 | """ 154 | Get port pair of link, so that controller can install flow entry. 155 | link_to_port = {(src_dpid,dst_dpid):(src_port,dst_port),} 156 | """ 157 | if (src_dpid, dst_dpid) in link_to_port: 158 | return link_to_port[(src_dpid, dst_dpid)] 159 | else: 160 | self.logger.info("Link from dpid:%s to dpid:%s is not in links" % 161 | (src_dpid, dst_dpid)) 162 | return None 163 | 164 | def flood(self, msg): 165 | """ 166 | Flood packet to the access ports which have no record of host. 167 | access_ports = {dpid:set(port_num,),} 168 | access_table = {(sw,port):(ip, mac),} 169 | """ 170 | datapath = msg.datapath 171 | ofproto = datapath.ofproto 172 | 173 | for dpid in self.awareness.access_ports: 174 | for port in self.awareness.access_ports[dpid]: 175 | if (dpid, port) not in self.awareness.access_table.keys(): 176 | datapath = self.datapaths[dpid] 177 | out = self._build_packet_out( 178 | datapath, ofproto.OFP_NO_BUFFER, 179 | ofproto.OFPP_CONTROLLER, port, msg.data) 180 | datapath.send_msg(out) 181 | self.logger.debug("Flooding packet to access port") 182 | 183 | def arp_forwarding(self, msg, src_ip, dst_ip): 184 | """ 185 | Send ARP packet to the destination host if the dst host record 186 | is existed, else flow it to the unknow access port. 187 | result = (datapath, port) 188 | """ 189 | datapath = msg.datapath 190 | ofproto = datapath.ofproto 191 | 192 | result = self.awareness.get_host_location(dst_ip) 193 | if result: 194 | # Host has been recorded in access table. 195 | datapath_dst, out_port = result[0], result[1] 196 | datapath = self.datapaths[datapath_dst] 197 | out = self._build_packet_out(datapath, ofproto.OFP_NO_BUFFER, 198 | ofproto.OFPP_CONTROLLER, 199 | out_port, msg.data) 200 | datapath.send_msg(out) 201 | self.logger.debug("Deliver ARP packet to knew host") 202 | else: 203 | # Flood is not good. 204 | self.flood(msg) 205 | 206 | def get_path(self, src, dst, weight): 207 | """ 208 | Get shortest path from network_awareness module. 209 | generator (nx.shortest_simple_paths( )) produces 210 | lists of simple paths, in order from shortest to longest. 211 | """ 212 | shortest_paths = self.awareness.shortest_paths 213 | # Create bandwidth-sensitive datapath graph. 214 | graph = self.awareness.graph 215 | 216 | if weight == self.WEIGHT_MODEL['hop']: 217 | return shortest_paths.get(src).get(dst)[0] 218 | elif weight == self.WEIGHT_MODEL['bw']: 219 | # Because all paths will be calculated when we call self.monitor.get_best_path_by_bw, 220 | # so we just need to call it once in a period, and then, we can get path directly. 221 | # If path is existed just return it, else calculate and return it. 222 | try: 223 | path = self.monitor.best_paths.get(src).get(dst) 224 | return path 225 | except: 226 | result = self.monitor.get_best_path_by_bw(graph, shortest_paths) 227 | # result = (capabilities, best_paths) 228 | paths = result[1] 229 | best_path = paths.get(src).get(dst) 230 | return best_path 231 | else: 232 | pass 233 | 234 | def get_sw(self, dpid, in_port, src, dst): 235 | """ 236 | Get pair of source and destination switches. 237 | """ 238 | src_sw = dpid 239 | dst_sw = None 240 | src_location = self.awareness.get_host_location(src) # src_location = (dpid, port) 241 | if in_port in self.awareness.access_ports[dpid]: 242 | if (dpid, in_port) == src_location: 243 | src_sw = src_location[0] 244 | else: 245 | return None 246 | dst_location = self.awareness.get_host_location(dst) # dst_location = (dpid, port) 247 | if dst_location: 248 | dst_sw = dst_location[0] 249 | if src_sw and dst_sw: 250 | return src_sw, dst_sw 251 | else: 252 | return None 253 | 254 | def send_flow_mod(self, datapath, flow_info, src_port, dst_port): 255 | """ 256 | Build flow entry, and send it to datapath. 257 | flow_info = (eth_type, src_ip, dst_ip, in_port) 258 | or 259 | flow_info = (eth_type, src_ip, dst_ip, in_port, ip_proto, Flag, L4_port) 260 | """ 261 | parser = datapath.ofproto_parser 262 | actions = [] 263 | actions.append(parser.OFPActionOutput(dst_port)) 264 | if len(flow_info) == 7: 265 | if flow_info[-3] == 6: 266 | if flow_info[-2] == 'src': 267 | match = parser.OFPMatch( 268 | in_port=src_port, eth_type=flow_info[0], 269 | ipv4_src=flow_info[1], ipv4_dst=flow_info[2], 270 | ip_proto=6, tcp_src=flow_info[-1]) 271 | elif flow_info[-2] == 'dst': 272 | match = parser.OFPMatch( 273 | in_port=src_port, eth_type=flow_info[0], 274 | ipv4_src=flow_info[1], ipv4_dst=flow_info[2], 275 | ip_proto=6, tcp_dst=flow_info[-1]) 276 | else: 277 | pass 278 | elif flow_info[-3] == 17: 279 | if flow_info[-2] == 'src': 280 | match = parser.OFPMatch( 281 | in_port=src_port, eth_type=flow_info[0], 282 | ipv4_src=flow_info[1], ipv4_dst=flow_info[2], 283 | ip_proto=17, udp_src=flow_info[-1]) 284 | elif flow_info[-2] == 'dst': 285 | match = parser.OFPMatch( 286 | in_port=src_port, eth_type=flow_info[0], 287 | ipv4_src=flow_info[1], ipv4_dst=flow_info[2], 288 | ip_proto=17, udp_dst=flow_info[-1]) 289 | else: 290 | pass 291 | elif len(flow_info) == 4: 292 | match = parser.OFPMatch( 293 | in_port=src_port, eth_type=flow_info[0], 294 | ipv4_src=flow_info[1], ipv4_dst=flow_info[2]) 295 | else: 296 | pass 297 | 298 | self.add_flow(datapath, 30, match, actions, 299 | idle_timeout=5, hard_timeout=10) 300 | 301 | def install_flow(self, datapaths, link_to_port, path, flow_info, buffer_id, data=None): 302 | ''' 303 | Install flow entries for datapaths. 304 | path=[dpid1, dpid2, ...] 305 | flow_info = (eth_type, src_ip, dst_ip, in_port) 306 | or 307 | flow_info = (eth_type, src_ip, dst_ip, in_port, ip_proto, Flag, L4_port) 308 | ''' 309 | if path is None or len(path) == 0: 310 | self.logger.info("Path error!") 311 | return 312 | in_port = flow_info[3] 313 | first_dp = datapaths[path[0]] 314 | out_port = first_dp.ofproto.OFPP_LOCAL 315 | 316 | # Install flow entry for intermediate datapaths. 317 | for i in range(1, (len(path) - 1) / 2): 318 | port = self.get_port_pair_from_link(link_to_port, path[i-1], path[i]) 319 | port_next = self.get_port_pair_from_link(link_to_port, path[i], path[i+1]) 320 | if port and port_next: 321 | src_port, dst_port = port[1], port_next[0] 322 | datapath = datapaths[path[i]] 323 | self.send_flow_mod(datapath, flow_info, src_port, dst_port) 324 | 325 | # Install flow entry for the first datapath. 326 | port_pair = self.get_port_pair_from_link(link_to_port, path[0], path[1]) 327 | if port_pair is None: 328 | self.logger.info("Port not found in first hop.") 329 | return 330 | out_port = port_pair[0] 331 | self.send_flow_mod(first_dp, flow_info, in_port, out_port) 332 | # Send packet_out to the first datapath. 333 | self.send_packet_out(first_dp, buffer_id, in_port, out_port, data) 334 | 335 | def get_L4_info(self, tcp_pkt, udp_pkt, ip_proto, L4_port, Flag): 336 | """ 337 | Get ip_proto and L4 port number. 338 | """ 339 | if tcp_pkt: 340 | ip_proto = 6 341 | if tcp_pkt.src_port: 342 | L4_port = tcp_pkt.src_port 343 | Flag = 'src' 344 | elif tcp_pkt.dst_port: 345 | L4_port = tcp_pkt.dst_port 346 | Flag = 'dst' 347 | else: 348 | pass 349 | elif udp_pkt: 350 | ip_proto = 17 351 | if udp_pkt.src_port: 352 | L4_port = udp_pkt.src_port 353 | Flag = 'src' 354 | elif udp_pkt.dst_port: 355 | L4_port = udp_pkt.dst_port 356 | Flag = 'dst' 357 | else: 358 | pass 359 | else: 360 | pass 361 | return (ip_proto, L4_port, Flag) 362 | 363 | def shortest_forwarding(self, msg, eth_type, ip_src, ip_dst): 364 | """ 365 | Calculate shortest forwarding path and Install them into datapaths. 366 | flow_info = (eth_type, src_ip, dst_ip, in_port) 367 | or 368 | flow_info = (eth_type, ip_src, ip_dst, in_port, ip_proto, Flag, L4_port) 369 | """ 370 | datapath = msg.datapath 371 | in_port = msg.match['in_port'] 372 | pkt = packet.Packet(msg.data) 373 | tcp_pkt = pkt.get_protocol(tcp.tcp) 374 | udp_pkt = pkt.get_protocol(udp.udp) 375 | ip_proto = None 376 | L4_port = None 377 | Flag = None 378 | # Get ip_proto and L4 port number. 379 | ip_proto, L4_port, Flag = self.get_L4_info(tcp_pkt, udp_pkt, ip_proto, L4_port, Flag) 380 | result = self.get_sw(datapath.id, in_port, ip_src, ip_dst) # result = (src_sw, dst_sw) 381 | if result: 382 | src_sw, dst_sw = result[0], result[1] 383 | if dst_sw: 384 | # Path has already been calculated, just get it. 385 | path = self.get_path(src_sw, dst_sw, weight=self.weight) 386 | if ip_proto and L4_port and Flag: 387 | if ip_proto == 6: 388 | L4_Proto = 'TCP' 389 | elif ip_proto == 17: 390 | L4_Proto = 'UDP' 391 | else: 392 | pass 393 | self.logger.info("[PATH]%s<-->%s(%s Port:%d): %s" % (ip_src, ip_dst, L4_Proto, L4_port, path)) 394 | flow_info = (eth_type, ip_src, ip_dst, in_port, ip_proto, Flag, L4_port) 395 | else: 396 | self.logger.info("[PATH]%s<-->%s: %s" % (ip_src, ip_dst, path)) 397 | flow_info = (eth_type, ip_src, ip_dst, in_port) 398 | # Install flow entries to datapaths along the path. 399 | self.install_flow(self.datapaths, 400 | self.awareness.link_to_port, 401 | path, flow_info, msg.buffer_id, msg.data) 402 | else: 403 | # Flood is not good. 404 | self.flood(msg) 405 | -------------------------------------------------------------------------------- /PureSDN.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Huangmachi/PureSDN/54a31af6fed10df5755c6092bc3b66d6e125fe90/PureSDN.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## PureSDN 2 | 3 | PureSDN is a SDN-based traffic schduling application. Except the routing paths for hosts under the same switch, routing paths are calculated and installed completely by the Ryu controller. 4 | It includes a set of Ryu applications collecting basic network information, such as topology and free bandwidth of links. PureSDN can achieve shortest path forwarding based on HOP or BANDWIDTH. 5 | You can specify the mode of computing shortest paths when starting Ryu by adding "weight" argument. Moreover, you can set "k_paths" argument to support K-Shortest paths computing. 6 | Fortunately, our application supports load balancing based on dynamic traffic information. 7 | 8 | The detailed information of the modules is shown below: 9 | 10 | * Fattree4 and Fattree8 are topology modules; 11 | 12 | * Network Awareness is the module for collecting network information; 13 | 14 | * Network Monitor is the module for collecting traffic information; 15 | 16 | * PureSDN is the main module of the application; 17 | 18 | * Setting is the module including common setting. 19 | 20 | We make use of networkx's data structure to store topology. Meanwhile, we also utilize networkx's built-in algorithm to calculate shortest paths. 21 | 22 | 23 | ### Prerequisites 24 | 25 | The following softwares should have been installed in your machine. 26 | * Mininet: git clone git://github.com/mininet/mininet; mininet/util/install.sh -a 27 | * Ryu: git clone git://github.com/osrg/ryu.git; cd ryu; pip install . 28 | * Networkx: pip install networkx 29 | 30 | 31 | ### Download 32 | 33 | Download files into Ryu directory, for instance, 'ryu/ryu/app/PureSDN' is OK. 34 | 35 | 36 | ### Make some change 37 | 38 | To register parsing parameters, you NEED to add the following code into the end of ryu/ryu/flags.py. 39 | 40 | CONF.register_cli_opts([ 41 | # k_shortest_forwarding 42 | cfg.IntOpt('k_paths', default=4, help='number of candidate paths of KSP.'), 43 | cfg.StrOpt('weight', default='bw', help='weight type of computing shortest path.'), 44 | cfg.IntOpt('fanout', default=4, help='switch fanout number.')]) 45 | 46 | 47 | ### Reinstall Ryu 48 | 49 | You must reinstall Ryu, so that you can run the new code. In the top directory of Ryu project: 50 | 51 | sudo python setup.py install 52 | 53 | 54 | ### Start 55 | 56 | Note: Before doing the experiment, you should change the controller's IP address from '192.168.56.101' to your own machine's eth0 IP address in the fattree.py module in each application, because '192.168.56.101' is my computer's eth0 IP address (Try 'ifconfig' in your Ubuntu to find out the eth0's IP address). Otherwise, the switches can't connect to the controller. 57 | 58 | Firstly, start up the network. An example is shown below: 59 | 60 | $ sudo python ryu/ryu/app/PureSDN/fattree4.py 61 | 62 | And then, go into the top directory of Ryu, and run the application. You are suggested to add arguments when starting Ryu. An example is shown below: 63 | 64 | $ cd ryu 65 | $ ryu-manager --observe-links ryu/app/PureSDN/PureSDN.py --k_paths=4 --weight=bw --fanout=4 66 | 67 | NOTE: After these, we should wait for the network to complete the initiation for several seconds, because LLDP needs some time to discovery the network topology. We can't operate the network until "[GET NETWORK TOPOLOGY]" is printed in the terminal of the Ryu controller, otherwise, some error will occur. It may be about 10 seconds for fattree4, and a little longer for fattree8. 68 | 69 | After that, test the correctness of PureSDN: 70 | 71 | mininet> pingall 72 | mininet> iperf 73 | 74 | If you want to show the collected information, you can set the parameters in setting.py. Also, you can change the setting as you like, such as the discovery period and monitor period. After that, you can see the information shown in the terminal. 75 | 76 | 77 | ### Authors 78 | 79 | Brought to you by Huang MaChi (Chongqing University of Posts and Telecommunications, Chongqing, China.) and Li Cheng (Beijing University of Posts and Telecommunications. www.muzixing.com). 80 | 81 | If you have any question, email me at huangmachi@foxmail.com. Don't forget to STAR this repository! 82 | 83 | Enjoy it! 84 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | "For loading module" 2 | -------------------------------------------------------------------------------- /fattree4.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Huang MaChi at Chongqing University 2 | # of Posts and Telecommunications, Chongqing, China. 3 | # Copyright (C) 2016 Li Cheng at Beijing University of Posts 4 | # and Telecommunications. www.muzixing.com 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 15 | # implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from mininet.net import Mininet 20 | from mininet.node import Controller, RemoteController 21 | from mininet.cli import CLI 22 | from mininet.log import setLogLevel 23 | from mininet.link import Link, Intf, TCLink 24 | from mininet.topo import Topo 25 | from mininet.util import dumpNodeConnections 26 | 27 | import logging 28 | import os 29 | 30 | 31 | class Fattree(Topo): 32 | """ 33 | Class of Fattree Topology. 34 | """ 35 | CoreSwitchList = [] 36 | AggSwitchList = [] 37 | EdgeSwitchList = [] 38 | HostList = [] 39 | 40 | def __init__(self, k, density): 41 | self.pod = k 42 | self.density = density 43 | self.iCoreLayerSwitch = (k/2)**2 44 | self.iAggLayerSwitch = k*k/2 45 | self.iEdgeLayerSwitch = k*k/2 46 | self.iHost = self.iEdgeLayerSwitch * density 47 | 48 | # Init Topo 49 | Topo.__init__(self) 50 | 51 | def createNodes(self): 52 | self.createCoreLayerSwitch(self.iCoreLayerSwitch) 53 | self.createAggLayerSwitch(self.iAggLayerSwitch) 54 | self.createEdgeLayerSwitch(self.iEdgeLayerSwitch) 55 | self.createHost(self.iHost) 56 | 57 | # Create Switch and Host 58 | def _addSwitch(self, number, level, switch_list): 59 | """ 60 | Create switches. 61 | """ 62 | for i in xrange(1, number+1): 63 | PREFIX = str(level) + "00" 64 | if i >= 10: 65 | PREFIX = str(level) + "0" 66 | switch_list.append(self.addSwitch(PREFIX + str(i))) 67 | 68 | def createCoreLayerSwitch(self, NUMBER): 69 | self._addSwitch(NUMBER, 1, self.CoreSwitchList) 70 | 71 | def createAggLayerSwitch(self, NUMBER): 72 | self._addSwitch(NUMBER, 2, self.AggSwitchList) 73 | 74 | def createEdgeLayerSwitch(self, NUMBER): 75 | self._addSwitch(NUMBER, 3, self.EdgeSwitchList) 76 | 77 | def createHost(self, NUMBER): 78 | """ 79 | Create hosts. 80 | """ 81 | for i in xrange(1, NUMBER+1): 82 | if i >= 100: 83 | PREFIX = "h" 84 | elif i >= 10: 85 | PREFIX = "h0" 86 | else: 87 | PREFIX = "h00" 88 | self.HostList.append(self.addHost(PREFIX + str(i), cpu=1.0/NUMBER)) 89 | 90 | def createLinks(self, bw_c2a=10, bw_a2e=10, bw_e2h=10): 91 | """ 92 | Add network links. 93 | """ 94 | # Core to Agg 95 | end = self.pod/2 96 | for x in xrange(0, self.iAggLayerSwitch, end): 97 | for i in xrange(0, end): 98 | for j in xrange(0, end): 99 | self.addLink( 100 | self.CoreSwitchList[i*end+j], 101 | self.AggSwitchList[x+i], 102 | bw=bw_c2a, max_queue_size=1000) # use_htb=False 103 | 104 | # Agg to Edge 105 | for x in xrange(0, self.iAggLayerSwitch, end): 106 | for i in xrange(0, end): 107 | for j in xrange(0, end): 108 | self.addLink( 109 | self.AggSwitchList[x+i], self.EdgeSwitchList[x+j], 110 | bw=bw_a2e, max_queue_size=1000) # use_htb=False 111 | 112 | # Edge to Host 113 | for x in xrange(0, self.iEdgeLayerSwitch): 114 | for i in xrange(0, self.density): 115 | self.addLink( 116 | self.EdgeSwitchList[x], 117 | self.HostList[self.density * x + i], 118 | bw=bw_e2h, max_queue_size=1000) # use_htb=False 119 | 120 | def set_ovs_protocol_13(self,): 121 | """ 122 | Set the OpenFlow version for switches. 123 | """ 124 | self._set_ovs_protocol_13(self.CoreSwitchList) 125 | self._set_ovs_protocol_13(self.AggSwitchList) 126 | self._set_ovs_protocol_13(self.EdgeSwitchList) 127 | 128 | def _set_ovs_protocol_13(self, sw_list): 129 | for sw in sw_list: 130 | cmd = "sudo ovs-vsctl set bridge %s protocols=OpenFlow13" % sw 131 | os.system(cmd) 132 | 133 | 134 | def set_host_ip(net, topo): 135 | hostlist = [] 136 | for k in xrange(len(topo.HostList)): 137 | hostlist.append(net.get(topo.HostList[k])) 138 | i = 1 139 | j = 1 140 | for host in hostlist: 141 | host.setIP("10.%d.0.%d" % (i, j)) 142 | j += 1 143 | if j == topo.density+1: 144 | j = 1 145 | i += 1 146 | 147 | def create_subnetList(topo, num): 148 | """ 149 | Create the subnet list of the certain Pod. 150 | """ 151 | subnetList = [] 152 | remainder = num % (topo.pod/2) 153 | if topo.pod == 4: 154 | if remainder == 0: 155 | subnetList = [num-1, num] 156 | elif remainder == 1: 157 | subnetList = [num, num+1] 158 | else: 159 | pass 160 | elif topo.pod == 8: 161 | if remainder == 0: 162 | subnetList = [num-3, num-2, num-1, num] 163 | elif remainder == 1: 164 | subnetList = [num, num+1, num+2, num+3] 165 | elif remainder == 2: 166 | subnetList = [num-1, num, num+1, num+2] 167 | elif remainder == 3: 168 | subnetList = [num-2, num-1, num, num+1] 169 | else: 170 | pass 171 | else: 172 | pass 173 | return subnetList 174 | 175 | def install_proactive(net, topo): 176 | """ 177 | Install direct flow entries for edge switches. 178 | """ 179 | # Edge Switch 180 | for sw in topo.EdgeSwitchList: 181 | num = int(sw[-2:]) 182 | 183 | # Downstream. 184 | for i in xrange(1, topo.density+1): 185 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 186 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,arp, \ 187 | nw_dst=10.%d.0.%d,actions=output:%d'" % (sw, num, i, topo.pod/2+i) 188 | os.system(cmd) 189 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 190 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,ip, \ 191 | nw_dst=10.%d.0.%d,actions=output:%d'" % (sw, num, i, topo.pod/2+i) 192 | os.system(cmd) 193 | 194 | # Aggregate Switch 195 | # Downstream. 196 | for sw in topo.AggSwitchList: 197 | num = int(sw[-2:]) 198 | subnetList = create_subnetList(topo, num) 199 | 200 | k = 1 201 | for i in subnetList: 202 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 203 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,arp, \ 204 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, topo.pod/2+k) 205 | os.system(cmd) 206 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 207 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,ip, \ 208 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, topo.pod/2+k) 209 | os.system(cmd) 210 | k += 1 211 | 212 | # Core Switch 213 | for sw in topo.CoreSwitchList: 214 | j = 1 215 | k = 1 216 | for i in xrange(1, len(topo.EdgeSwitchList)+1): 217 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 218 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,arp, \ 219 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, j) 220 | os.system(cmd) 221 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 222 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,ip, \ 223 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, j) 224 | os.system(cmd) 225 | k += 1 226 | if k == topo.pod/2 + 1: 227 | j += 1 228 | k = 1 229 | 230 | def iperfTest(net, topo): 231 | """ 232 | Start iperf test. 233 | """ 234 | h001, h015, h016 = net.get( 235 | topo.HostList[0], topo.HostList[14], topo.HostList[15]) 236 | # iperf Server 237 | h001.popen('iperf -s -u -i 1 > iperf_server_differentPod_result', shell=True) 238 | # iperf Server 239 | h015.popen('iperf -s -u -i 1 > iperf_server_samePod_result', shell=True) 240 | # iperf Client 241 | h016.cmdPrint('iperf -c ' + h001.IP() + ' -u -t 10 -i 1 -b 10m') 242 | h016.cmdPrint('iperf -c ' + h015.IP() + ' -u -t 10 -i 1 -b 10m') 243 | 244 | def pingTest(net): 245 | """ 246 | Start ping test. 247 | """ 248 | net.pingAll() 249 | 250 | def createTopo(pod, density, ip="127.0.0.1", port=6633, bw_c2a=10, bw_a2e=10, bw_e2h=10): 251 | """ 252 | Create network topology and run the Mininet. 253 | """ 254 | # Create Topo. 255 | topo = Fattree(pod, density) 256 | topo.createNodes() 257 | topo.createLinks(bw_c2a=bw_c2a, bw_a2e=bw_a2e, bw_e2h=bw_e2h) 258 | 259 | # Start Mininet. 260 | CONTROLLER_IP = ip 261 | CONTROLLER_PORT = port 262 | net = Mininet(topo=topo, link=TCLink, controller=None, autoSetMacs=True) 263 | net.addController( 264 | 'controller', controller=RemoteController, 265 | ip=CONTROLLER_IP, port=CONTROLLER_PORT) 266 | net.start() 267 | 268 | # Set OVS's protocol as OF13. 269 | topo.set_ovs_protocol_13() 270 | # Set hosts IP addresses. 271 | set_host_ip(net, topo) 272 | # Install proactive flow entries 273 | install_proactive(net, topo) 274 | # dumpNodeConnections(net.hosts) 275 | # pingTest(net) 276 | # iperfTest(net, topo) 277 | 278 | CLI(net) 279 | net.stop() 280 | 281 | if __name__ == '__main__': 282 | setLogLevel('info') 283 | if os.getuid() != 0: 284 | logging.debug("You are NOT root") 285 | elif os.getuid() == 0: 286 | createTopo(4, 2) 287 | # createTopo(8, 4) 288 | -------------------------------------------------------------------------------- /fattree8.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Huang MaChi at Chongqing University 2 | # of Posts and Telecommunications, Chongqing, China. 3 | # Copyright (C) 2016 Li Cheng at Beijing University of Posts 4 | # and Telecommunications. www.muzixing.com 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 15 | # implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from mininet.net import Mininet 20 | from mininet.node import Controller, RemoteController 21 | from mininet.cli import CLI 22 | from mininet.log import setLogLevel 23 | from mininet.link import Link, Intf, TCLink 24 | from mininet.topo import Topo 25 | from mininet.util import dumpNodeConnections 26 | 27 | import logging 28 | import os 29 | 30 | 31 | class Fattree(Topo): 32 | """ 33 | Class of Fattree Topology. 34 | """ 35 | CoreSwitchList = [] 36 | AggSwitchList = [] 37 | EdgeSwitchList = [] 38 | HostList = [] 39 | 40 | def __init__(self, k, density): 41 | self.pod = k 42 | self.density = density 43 | self.iCoreLayerSwitch = (k/2)**2 44 | self.iAggLayerSwitch = k*k/2 45 | self.iEdgeLayerSwitch = k*k/2 46 | self.iHost = self.iEdgeLayerSwitch * density 47 | 48 | # Init Topo 49 | Topo.__init__(self) 50 | 51 | def createNodes(self): 52 | self.createCoreLayerSwitch(self.iCoreLayerSwitch) 53 | self.createAggLayerSwitch(self.iAggLayerSwitch) 54 | self.createEdgeLayerSwitch(self.iEdgeLayerSwitch) 55 | self.createHost(self.iHost) 56 | 57 | # Create Switch and Host 58 | def _addSwitch(self, number, level, switch_list): 59 | """ 60 | Create switches. 61 | """ 62 | for i in xrange(1, number+1): 63 | PREFIX = str(level) + "00" 64 | if i >= 10: 65 | PREFIX = str(level) + "0" 66 | switch_list.append(self.addSwitch(PREFIX + str(i))) 67 | 68 | def createCoreLayerSwitch(self, NUMBER): 69 | self._addSwitch(NUMBER, 1, self.CoreSwitchList) 70 | 71 | def createAggLayerSwitch(self, NUMBER): 72 | self._addSwitch(NUMBER, 2, self.AggSwitchList) 73 | 74 | def createEdgeLayerSwitch(self, NUMBER): 75 | self._addSwitch(NUMBER, 3, self.EdgeSwitchList) 76 | 77 | def createHost(self, NUMBER): 78 | """ 79 | Create hosts. 80 | """ 81 | for i in xrange(1, NUMBER+1): 82 | if i >= 100: 83 | PREFIX = "h" 84 | elif i >= 10: 85 | PREFIX = "h0" 86 | else: 87 | PREFIX = "h00" 88 | self.HostList.append(self.addHost(PREFIX + str(i), cpu=1.0/NUMBER)) 89 | 90 | def createLinks(self, bw_c2a=10, bw_a2e=10, bw_e2h=10): 91 | """ 92 | Add network links. 93 | """ 94 | # Core to Agg 95 | end = self.pod/2 96 | for x in xrange(0, self.iAggLayerSwitch, end): 97 | for i in xrange(0, end): 98 | for j in xrange(0, end): 99 | self.addLink( 100 | self.CoreSwitchList[i*end+j], 101 | self.AggSwitchList[x+i], 102 | bw=bw_c2a, max_queue_size=1000) # use_htb=False 103 | 104 | # Agg to Edge 105 | for x in xrange(0, self.iAggLayerSwitch, end): 106 | for i in xrange(0, end): 107 | for j in xrange(0, end): 108 | self.addLink( 109 | self.AggSwitchList[x+i], self.EdgeSwitchList[x+j], 110 | bw=bw_a2e, max_queue_size=1000) # use_htb=False 111 | 112 | # Edge to Host 113 | for x in xrange(0, self.iEdgeLayerSwitch): 114 | for i in xrange(0, self.density): 115 | self.addLink( 116 | self.EdgeSwitchList[x], 117 | self.HostList[self.density * x + i], 118 | bw=bw_e2h, max_queue_size=1000) # use_htb=False 119 | 120 | def set_ovs_protocol_13(self,): 121 | """ 122 | Set the OpenFlow version for switches. 123 | """ 124 | self._set_ovs_protocol_13(self.CoreSwitchList) 125 | self._set_ovs_protocol_13(self.AggSwitchList) 126 | self._set_ovs_protocol_13(self.EdgeSwitchList) 127 | 128 | def _set_ovs_protocol_13(self, sw_list): 129 | for sw in sw_list: 130 | cmd = "sudo ovs-vsctl set bridge %s protocols=OpenFlow13" % sw 131 | os.system(cmd) 132 | 133 | 134 | def set_host_ip(net, topo): 135 | hostlist = [] 136 | for k in xrange(len(topo.HostList)): 137 | hostlist.append(net.get(topo.HostList[k])) 138 | i = 1 139 | j = 1 140 | for host in hostlist: 141 | host.setIP("10.%d.0.%d" % (i, j)) 142 | j += 1 143 | if j == topo.density+1: 144 | j = 1 145 | i += 1 146 | 147 | def create_subnetList(topo, num): 148 | """ 149 | Create the subnet list of the certain Pod. 150 | """ 151 | subnetList = [] 152 | remainder = num % (topo.pod/2) 153 | if topo.pod == 4: 154 | if remainder == 0: 155 | subnetList = [num-1, num] 156 | elif remainder == 1: 157 | subnetList = [num, num+1] 158 | else: 159 | pass 160 | elif topo.pod == 8: 161 | if remainder == 0: 162 | subnetList = [num-3, num-2, num-1, num] 163 | elif remainder == 1: 164 | subnetList = [num, num+1, num+2, num+3] 165 | elif remainder == 2: 166 | subnetList = [num-1, num, num+1, num+2] 167 | elif remainder == 3: 168 | subnetList = [num-2, num-1, num, num+1] 169 | else: 170 | pass 171 | else: 172 | pass 173 | return subnetList 174 | 175 | def install_proactive(net, topo): 176 | """ 177 | Install direct flow entries for edge switches. 178 | """ 179 | # Edge Switch 180 | for sw in topo.EdgeSwitchList: 181 | num = int(sw[-2:]) 182 | 183 | # Downstream. 184 | for i in xrange(1, topo.density+1): 185 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 186 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,arp, \ 187 | nw_dst=10.%d.0.%d,actions=output:%d'" % (sw, num, i, topo.pod/2+i) 188 | os.system(cmd) 189 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 190 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,ip, \ 191 | nw_dst=10.%d.0.%d,actions=output:%d'" % (sw, num, i, topo.pod/2+i) 192 | os.system(cmd) 193 | 194 | # Aggregate Switch 195 | # Downstream. 196 | for sw in topo.AggSwitchList: 197 | num = int(sw[-2:]) 198 | subnetList = create_subnetList(topo, num) 199 | 200 | k = 1 201 | for i in subnetList: 202 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 203 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,arp, \ 204 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, topo.pod/2+k) 205 | os.system(cmd) 206 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 207 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,ip, \ 208 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, topo.pod/2+k) 209 | os.system(cmd) 210 | k += 1 211 | 212 | # Core Switch 213 | for sw in topo.CoreSwitchList: 214 | j = 1 215 | k = 1 216 | for i in xrange(1, len(topo.EdgeSwitchList)+1): 217 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 218 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,arp, \ 219 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, j) 220 | os.system(cmd) 221 | cmd = "ovs-ofctl add-flow %s -O OpenFlow13 \ 222 | 'table=0,idle_timeout=0,hard_timeout=0,priority=10,ip, \ 223 | nw_dst=10.%d.0.0/16, actions=output:%d'" % (sw, i, j) 224 | os.system(cmd) 225 | k += 1 226 | if k == topo.pod/2 + 1: 227 | j += 1 228 | k = 1 229 | 230 | def iperfTest(net, topo): 231 | """ 232 | Start iperf test. 233 | """ 234 | h001, h015, h016 = net.get( 235 | topo.HostList[0], topo.HostList[14], topo.HostList[15]) 236 | # iperf Server 237 | h001.popen('iperf -s -u -i 1 > iperf_server_differentPod_result', shell=True) 238 | # iperf Server 239 | h015.popen('iperf -s -u -i 1 > iperf_server_samePod_result', shell=True) 240 | # iperf Client 241 | h016.cmdPrint('iperf -c ' + h001.IP() + ' -u -t 10 -i 1 -b 10m') 242 | h016.cmdPrint('iperf -c ' + h015.IP() + ' -u -t 10 -i 1 -b 10m') 243 | 244 | def pingTest(net): 245 | """ 246 | Start ping test. 247 | """ 248 | net.pingAll() 249 | 250 | def createTopo(pod, density, ip="127.0.0.1", port=6633, bw_c2a=10, bw_a2e=10, bw_e2h=10): 251 | """ 252 | Create network topology and run the Mininet. 253 | """ 254 | # Create Topo. 255 | topo = Fattree(pod, density) 256 | topo.createNodes() 257 | topo.createLinks(bw_c2a=bw_c2a, bw_a2e=bw_a2e, bw_e2h=bw_e2h) 258 | 259 | # Start Mininet. 260 | CONTROLLER_IP = ip 261 | CONTROLLER_PORT = port 262 | net = Mininet(topo=topo, link=TCLink, controller=None, autoSetMacs=True) 263 | net.addController( 264 | 'controller', controller=RemoteController, 265 | ip=CONTROLLER_IP, port=CONTROLLER_PORT) 266 | net.start() 267 | 268 | # Set OVS's protocol as OF13. 269 | topo.set_ovs_protocol_13() 270 | # Set hosts IP addresses. 271 | set_host_ip(net, topo) 272 | # Install proactive flow entries 273 | install_proactive(net, topo) 274 | # dumpNodeConnections(net.hosts) 275 | # pingTest(net) 276 | # iperfTest(net, topo) 277 | 278 | CLI(net) 279 | net.stop() 280 | 281 | if __name__ == '__main__': 282 | setLogLevel('info') 283 | if os.getuid() != 0: 284 | logging.debug("You are NOT root") 285 | elif os.getuid() == 0: 286 | # createTopo(4, 2) 287 | createTopo(8, 4) 288 | -------------------------------------------------------------------------------- /network_awareness.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Li Cheng at Beijing University of Posts 2 | # and Telecommunications. www.muzixing.com 3 | # Copyright (C) 2016 Huang MaChi at Chongqing University 4 | # of Posts and Telecommunications, China. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 15 | # implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | import networkx as nx 20 | import matplotlib.pyplot as plt 21 | import time 22 | 23 | from ryu import cfg 24 | from ryu.base import app_manager 25 | from ryu.controller import ofp_event 26 | from ryu.controller.handler import MAIN_DISPATCHER 27 | from ryu.controller.handler import CONFIG_DISPATCHER 28 | from ryu.controller.handler import set_ev_cls 29 | from ryu.ofproto import ofproto_v1_3 30 | from ryu.lib.packet import packet 31 | from ryu.lib.packet import ethernet 32 | from ryu.lib.packet import ipv4 33 | from ryu.lib.packet import arp 34 | from ryu.lib import hub 35 | from ryu.topology import event 36 | from ryu.topology.api import get_switch, get_link 37 | 38 | import setting 39 | 40 | 41 | CONF = cfg.CONF 42 | 43 | 44 | class NetworkAwareness(app_manager.RyuApp): 45 | """ 46 | NetworkAwareness is a Ryu app for discovering topology information. 47 | This App can provide many data services for other App, such as 48 | link_to_port, access_table, switch_port_table, access_ports, 49 | interior_ports, topology graph and shortest paths. 50 | """ 51 | OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 52 | 53 | # List the event list should be listened. 54 | events = [event.EventSwitchEnter, 55 | event.EventSwitchLeave, event.EventPortAdd, 56 | event.EventPortDelete, event.EventPortModify, 57 | event.EventLinkAdd, event.EventLinkDelete] 58 | 59 | def __init__(self, *args, **kwargs): 60 | super(NetworkAwareness, self).__init__(*args, **kwargs) 61 | self.topology_api_app = self 62 | self.name = "awareness" 63 | self.link_to_port = {} # {(src_dpid,dst_dpid):(src_port,dst_port),} 64 | self.access_table = {} # {(sw,port):(ip, mac),} 65 | self.switch_port_table = {} # {dpid:set(port_num,),} 66 | self.access_ports = {} # {dpid:set(port_num,),} 67 | self.interior_ports = {} # {dpid:set(port_num,),} 68 | self.switches = [] # self.switches = [dpid,] 69 | self.shortest_paths = {} # {dpid:{dpid:[[path],],},} 70 | self.pre_link_to_port = {} 71 | self.pre_access_table = {} 72 | 73 | # Directed graph can record the loading condition of links more accurately. 74 | # self.graph = nx.Graph() 75 | self.graph = nx.DiGraph() 76 | # Get initiation delay. 77 | self.initiation_delay = self.get_initiation_delay(CONF.fanout) 78 | self.start_time = time.time() 79 | 80 | # Start a green thread to discover network resource. 81 | self.discover_thread = hub.spawn(self._discover) 82 | 83 | def _discover(self): 84 | i = 0 85 | while True: 86 | self.show_topology() 87 | if i == 2: # Reload topology every 20 seconds. 88 | self.get_topology(None) 89 | i = 0 90 | hub.sleep(setting.DISCOVERY_PERIOD) 91 | i = i + 1 92 | 93 | def add_flow(self, dp, priority, match, actions, idle_timeout=0, hard_timeout=0): 94 | ofproto = dp.ofproto 95 | parser = dp.ofproto_parser 96 | inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, 97 | actions)] 98 | mod = parser.OFPFlowMod(datapath=dp, priority=priority, 99 | idle_timeout=idle_timeout, 100 | hard_timeout=hard_timeout, 101 | match=match, instructions=inst) 102 | dp.send_msg(mod) 103 | 104 | @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) 105 | def switch_features_handler(self, ev): 106 | """ 107 | Install table-miss flow entry to datapaths. 108 | """ 109 | datapath = ev.msg.datapath 110 | ofproto = datapath.ofproto 111 | parser = datapath.ofproto_parser 112 | self.logger.info("switch:%s connected", datapath.id) 113 | 114 | # Install table-miss flow entry. 115 | match = parser.OFPMatch() 116 | actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, 117 | ofproto.OFPCML_NO_BUFFER)] 118 | self.add_flow(datapath, 0, match, actions) 119 | 120 | @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 121 | def _packet_in_handler(self, ev): 122 | """ 123 | Handle the packet_in packet, and register the access info. 124 | """ 125 | msg = ev.msg 126 | datapath = msg.datapath 127 | in_port = msg.match['in_port'] 128 | pkt = packet.Packet(msg.data) 129 | arp_pkt = pkt.get_protocol(arp.arp) 130 | ip_pkt = pkt.get_protocol(ipv4.ipv4) 131 | 132 | if arp_pkt: 133 | arp_src_ip = arp_pkt.src_ip 134 | mac = arp_pkt.src_mac 135 | # Record the access infomation. 136 | self.register_access_info(datapath.id, in_port, arp_src_ip, mac) 137 | elif ip_pkt: 138 | ip_src_ip = ip_pkt.src 139 | eth = pkt.get_protocols(ethernet.ethernet)[0] 140 | mac = eth.src 141 | # Record the access infomation. 142 | self.register_access_info(datapath.id, in_port, ip_src_ip, mac) 143 | else: 144 | pass 145 | 146 | @set_ev_cls(events) 147 | def get_topology(self, ev): 148 | """ 149 | Get topology info and calculate shortest paths. 150 | Note: In looped network, we should get the topology 151 | 20 or 30 seconds after the network went up. 152 | """ 153 | present_time = time.time() 154 | if present_time - self.start_time < self.initiation_delay: 155 | return 156 | 157 | self.logger.info("[GET NETWORK TOPOLOGY]") 158 | switch_list = get_switch(self.topology_api_app, None) 159 | self.create_port_map(switch_list) 160 | self.switches = [sw.dp.id for sw in switch_list] 161 | links = get_link(self.topology_api_app, None) 162 | self.create_interior_links(links) 163 | self.create_access_ports() 164 | self.graph = self.get_graph(self.link_to_port.keys()) 165 | self.shortest_paths = self.all_k_shortest_paths( 166 | self.graph, weight='weight', k=CONF.k_paths) 167 | 168 | def get_host_location(self, host_ip): 169 | """ 170 | Get host location info ((datapath, port)) according to the host ip. 171 | self.access_table = {(sw,port):(ip, mac),} 172 | """ 173 | for key in self.access_table.keys(): 174 | if self.access_table[key][0] == host_ip: 175 | return key 176 | self.logger.info("%s location is not found." % host_ip) 177 | return None 178 | 179 | def get_graph(self, link_list): 180 | """ 181 | Get Adjacency matrix from link_to_port. 182 | """ 183 | _graph = self.graph.copy() 184 | for src in self.switches: 185 | for dst in self.switches: 186 | if src == dst: 187 | _graph.add_edge(src, dst, weight=0) 188 | elif (src, dst) in link_list: 189 | _graph.add_edge(src, dst, weight=1) 190 | else: 191 | pass 192 | return _graph 193 | 194 | def get_initiation_delay(self, fanout): 195 | """ 196 | Get initiation delay. 197 | """ 198 | if fanout == 4: 199 | delay = 20 200 | elif fanout == 8: 201 | delay = 30 202 | else: 203 | delay = 30 204 | return delay 205 | 206 | def create_port_map(self, switch_list): 207 | """ 208 | Create interior_port table and access_port table. 209 | """ 210 | for sw in switch_list: 211 | dpid = sw.dp.id 212 | self.switch_port_table.setdefault(dpid, set()) 213 | # switch_port_table is equal to interior_ports plus access_ports. 214 | self.interior_ports.setdefault(dpid, set()) 215 | self.access_ports.setdefault(dpid, set()) 216 | for port in sw.ports: 217 | # switch_port_table = {dpid:set(port_num,),} 218 | self.switch_port_table[dpid].add(port.port_no) 219 | 220 | def create_interior_links(self, link_list): 221 | """ 222 | Get links' srouce port to dst port from link_list. 223 | link_to_port = {(src_dpid,dst_dpid):(src_port,dst_port),} 224 | """ 225 | for link in link_list: 226 | src = link.src 227 | dst = link.dst 228 | self.link_to_port[(src.dpid, dst.dpid)] = (src.port_no, dst.port_no) 229 | # Find the access ports and interior ports. 230 | if link.src.dpid in self.switches: 231 | self.interior_ports[link.src.dpid].add(link.src.port_no) 232 | if link.dst.dpid in self.switches: 233 | self.interior_ports[link.dst.dpid].add(link.dst.port_no) 234 | 235 | def create_access_ports(self): 236 | """ 237 | Get ports without link into access_ports. 238 | """ 239 | for sw in self.switch_port_table: 240 | all_port_table = self.switch_port_table[sw] 241 | interior_port = self.interior_ports[sw] 242 | # That comes the access port of the switch. 243 | self.access_ports[sw] = all_port_table - interior_port 244 | 245 | def k_shortest_paths(self, graph, src, dst, weight='weight', k=5): 246 | """ 247 | Creat K shortest paths from src to dst. 248 | generator produces lists of simple paths, in order from shortest to longest. 249 | """ 250 | generator = nx.shortest_simple_paths(graph, source=src, target=dst, weight=weight) 251 | shortest_paths = [] 252 | try: 253 | for path in generator: 254 | if k <= 0: 255 | break 256 | shortest_paths.append(path) 257 | k -= 1 258 | return shortest_paths 259 | except: 260 | self.logger.debug("No path between %s and %s" % (src, dst)) 261 | 262 | def all_k_shortest_paths(self, graph, weight='weight', k=5): 263 | """ 264 | Creat all K shortest paths between datapaths. 265 | Note: We get shortest paths for bandwidth-sensitive 266 | traffic from bandwidth-sensitive switches. 267 | """ 268 | _graph = graph.copy() 269 | paths = {} 270 | # Find k shortest paths in graph. 271 | for src in _graph.nodes(): 272 | paths.setdefault(src, {src: [[src] for i in xrange(k)]}) 273 | for dst in _graph.nodes(): 274 | if src == dst: 275 | continue 276 | paths[src].setdefault(dst, []) 277 | paths[src][dst] = self.k_shortest_paths(_graph, src, dst, weight=weight, k=k) 278 | return paths 279 | 280 | def register_access_info(self, dpid, in_port, ip, mac): 281 | """ 282 | Register access host info into access table. 283 | """ 284 | if in_port in self.access_ports[dpid]: 285 | if (dpid, in_port) in self.access_table: 286 | if self.access_table[(dpid, in_port)] == (ip, mac): 287 | return 288 | else: 289 | self.access_table[(dpid, in_port)] = (ip, mac) 290 | return 291 | else: 292 | self.access_table.setdefault((dpid, in_port), None) 293 | self.access_table[(dpid, in_port)] = (ip, mac) 294 | return 295 | 296 | def show_topology(self): 297 | if self.pre_link_to_port != self.link_to_port and setting.TOSHOW_topo: 298 | # It means the link_to_port table has changed. 299 | _graph = self.graph.copy() 300 | print "\n---------------------Link Port---------------------" 301 | print '%6s' % ('switch'), 302 | for node in sorted([node for node in _graph.nodes()], key=lambda node: node): 303 | print '%6d' % node, 304 | print 305 | for node1 in sorted([node for node in _graph.nodes()], key=lambda node: node): 306 | print '%6d' % node1, 307 | for node2 in sorted([node for node in _graph.nodes()], key=lambda node: node): 308 | if (node1, node2) in self.link_to_port.keys(): 309 | print '%6s' % str(self.link_to_port[(node1, node2)]), 310 | else: 311 | print '%6s' % '/', 312 | print 313 | print 314 | self.pre_link_to_port = self.link_to_port.copy() 315 | 316 | if self.pre_access_table != self.access_table and setting.TOSHOW_topo: 317 | # It means the access_table has changed. 318 | print "\n----------------Access Host-------------------" 319 | print '%10s' % 'switch', '%10s' % 'port', '%22s' % 'Host' 320 | if not self.access_table.keys(): 321 | print " NO found host" 322 | else: 323 | for sw in sorted(self.access_table.keys()): 324 | print '%10d' % sw[0], '%10d ' % sw[1], self.access_table[sw] 325 | print 326 | self.pre_access_table = self.access_table.copy() 327 | 328 | # nx.draw(self.graph) 329 | # plt.savefig("/home/huangmc/exe/matplotlib/%d.png" % int(time.time())) 330 | -------------------------------------------------------------------------------- /network_awareness.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Huangmachi/PureSDN/54a31af6fed10df5755c6092bc3b66d6e125fe90/network_awareness.pyc -------------------------------------------------------------------------------- /network_monitor.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Li Cheng at Beijing University of Posts 2 | # and Telecommunications. www.muzixing.com 3 | # Copyright (C) 2016 Huang MaChi at Chongqing University 4 | # of Posts and Telecommunications, China. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 15 | # implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | from __future__ import division 20 | import copy 21 | from operator import attrgetter 22 | 23 | from ryu import cfg 24 | from ryu.base import app_manager 25 | from ryu.base.app_manager import lookup_service_brick 26 | from ryu.controller import ofp_event 27 | from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER 28 | from ryu.controller.handler import set_ev_cls 29 | from ryu.ofproto import ofproto_v1_3 30 | from ryu.lib import hub 31 | 32 | import setting 33 | 34 | 35 | CONF = cfg.CONF 36 | 37 | 38 | class NetworkMonitor(app_manager.RyuApp): 39 | """ 40 | NetworkMonitor is a Ryu app for collecting traffic information. 41 | """ 42 | OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 43 | 44 | def __init__(self, *args, **kwargs): 45 | super(NetworkMonitor, self).__init__(*args, **kwargs) 46 | self.name = 'monitor' 47 | self.datapaths = {} 48 | self.port_stats = {} 49 | self.port_speed = {} 50 | self.flow_stats = {} 51 | self.flow_speed = {} 52 | self.stats = {} 53 | self.port_features = {} 54 | self.free_bandwidth = {} # self.free_bandwidth = {dpid:{port_no:free_bw,},} unit:Kbit/s 55 | self.awareness = lookup_service_brick('awareness') 56 | self.graph = None 57 | self.capabilities = None 58 | self.best_paths = None 59 | 60 | # Start to green thread to monitor traffic and calculating 61 | # free bandwidth of links respectively. 62 | self.monitor_thread = hub.spawn(self._monitor) 63 | self.save_freebandwidth_thread = hub.spawn(self._save_bw_graph) 64 | 65 | def _monitor(self): 66 | """ 67 | Main entry method of monitoring traffic. 68 | """ 69 | while CONF.weight == 'bw': 70 | self.stats['flow'] = {} 71 | self.stats['port'] = {} 72 | for dp in self.datapaths.values(): 73 | self.port_features.setdefault(dp.id, {}) 74 | self._request_stats(dp) 75 | # Refresh data. 76 | self.capabilities = None 77 | self.best_paths = None 78 | hub.sleep(setting.MONITOR_PERIOD) 79 | if self.stats['flow'] or self.stats['port']: 80 | self.show_stat('flow') 81 | self.show_stat('port') 82 | hub.sleep(1) 83 | 84 | def _save_bw_graph(self): 85 | """ 86 | Save bandwidth data into networkx graph object. 87 | """ 88 | while CONF.weight == 'bw': 89 | self.graph = self.create_bw_graph(self.free_bandwidth) 90 | self.logger.debug("save free bandwidth") 91 | hub.sleep(setting.MONITOR_PERIOD) 92 | 93 | @set_ev_cls(ofp_event.EventOFPStateChange, 94 | [MAIN_DISPATCHER, DEAD_DISPATCHER]) 95 | def _state_change_handler(self, ev): 96 | """ 97 | Record datapath information. 98 | """ 99 | datapath = ev.datapath 100 | if ev.state == MAIN_DISPATCHER: 101 | if not datapath.id in self.datapaths: 102 | self.logger.debug('register datapath: %016x', datapath.id) 103 | self.datapaths[datapath.id] = datapath 104 | elif ev.state == DEAD_DISPATCHER: 105 | if datapath.id in self.datapaths: 106 | self.logger.debug('unregister datapath: %016x', datapath.id) 107 | del self.datapaths[datapath.id] 108 | else: 109 | pass 110 | 111 | @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) 112 | def _flow_stats_reply_handler(self, ev): 113 | """ 114 | Save flow stats reply information into self.flow_stats. 115 | Calculate flow speed and Save it. 116 | (old) self.flow_stats = {dpid:{(in_port, ipv4_dst, out-port):[(packet_count, byte_count, duration_sec, duration_nsec),],},} 117 | (old) self.flow_speed = {dpid:{(in_port, ipv4_dst, out-port):[speed,],},} 118 | (new) self.flow_stats = {dpid:{(priority, ipv4_src, ipv4_dst):[(packet_count, byte_count, duration_sec, duration_nsec),],},} 119 | (new) self.flow_speed = {dpid:{(priority, ipv4_src, ipv4_dst):[speed,],},} 120 | Because the proactive flow entrys don't have 'in_port' and 'out-port' field. 121 | Note: table-miss, LLDP and ARP flow entries are not what we need, just filter them. 122 | """ 123 | body = ev.msg.body 124 | dpid = ev.msg.datapath.id 125 | self.stats['flow'][dpid] = body 126 | self.flow_stats.setdefault(dpid, {}) 127 | self.flow_speed.setdefault(dpid, {}) 128 | for stat in sorted([flow for flow in body if ((flow.priority not in [0, 65535]) and (flow.match.get('ipv4_src')) and (flow.match.get('ipv4_dst')))], 129 | key=lambda flow: (flow.priority, flow.match.get('ipv4_src'), flow.match.get('ipv4_dst'))): 130 | key = (stat.priority, stat.match.get('ipv4_src'), stat.match.get('ipv4_dst')) 131 | value = (stat.packet_count, stat.byte_count, 132 | stat.duration_sec, stat.duration_nsec) 133 | self._save_stats(self.flow_stats[dpid], key, value, 5) 134 | 135 | # Get flow's speed and Save it. 136 | pre = 0 137 | period = setting.MONITOR_PERIOD 138 | tmp = self.flow_stats[dpid][key] 139 | if len(tmp) > 1: 140 | pre = tmp[-2][1] 141 | period = self._get_period(tmp[-1][2], tmp[-1][3], 142 | tmp[-2][2], tmp[-2][3]) 143 | speed = self._get_speed(self.flow_stats[dpid][key][-1][1], 144 | pre, period) 145 | self._save_stats(self.flow_speed[dpid], key, speed, 5) 146 | 147 | @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) 148 | def _port_stats_reply_handler(self, ev): 149 | """ 150 | Save port's stats information into self.port_stats. 151 | Calculate port speed and Save it. 152 | self.port_stats = {(dpid, port_no):[(tx_bytes, rx_bytes, rx_errors, duration_sec, duration_nsec),],} 153 | self.port_speed = {(dpid, port_no):[speed,],} 154 | Note: The transmit performance and receive performance are independent of a port. 155 | We calculate the load of a port only using tx_bytes. 156 | """ 157 | body = ev.msg.body 158 | dpid = ev.msg.datapath.id 159 | self.stats['port'][dpid] = body 160 | self.free_bandwidth.setdefault(dpid, {}) 161 | for stat in sorted(body, key=attrgetter('port_no')): 162 | port_no = stat.port_no 163 | if port_no != ofproto_v1_3.OFPP_LOCAL: 164 | key = (dpid, port_no) 165 | value = (stat.tx_bytes, stat.rx_bytes, stat.rx_errors, 166 | stat.duration_sec, stat.duration_nsec) 167 | self._save_stats(self.port_stats, key, value, 5) 168 | 169 | # Get port speed and Save it. 170 | pre = 0 171 | period = setting.MONITOR_PERIOD 172 | tmp = self.port_stats[key] 173 | if len(tmp) > 1: 174 | # Calculate only the tx_bytes, not the rx_bytes. (hmc) 175 | pre = tmp[-2][0] 176 | period = self._get_period(tmp[-1][3], tmp[-1][4], tmp[-2][3], tmp[-2][4]) 177 | speed = self._get_speed(self.port_stats[key][-1][0], pre, period) 178 | self._save_stats(self.port_speed, key, speed, 5) 179 | self._save_freebandwidth(dpid, port_no, speed) 180 | 181 | @set_ev_cls(ofp_event.EventOFPPortDescStatsReply, MAIN_DISPATCHER) 182 | def port_desc_stats_reply_handler(self, ev): 183 | """ 184 | Save port description info. 185 | """ 186 | msg = ev.msg 187 | dpid = msg.datapath.id 188 | ofproto = msg.datapath.ofproto 189 | 190 | config_dict = {ofproto.OFPPC_PORT_DOWN: "Down", 191 | ofproto.OFPPC_NO_RECV: "No Recv", 192 | ofproto.OFPPC_NO_FWD: "No Farward", 193 | ofproto.OFPPC_NO_PACKET_IN: "No Packet-in"} 194 | 195 | state_dict = {ofproto.OFPPS_LINK_DOWN: "Down", 196 | ofproto.OFPPS_BLOCKED: "Blocked", 197 | ofproto.OFPPS_LIVE: "Live"} 198 | 199 | ports = [] 200 | for p in ev.msg.body: 201 | ports.append('port_no=%d hw_addr=%s name=%s config=0x%08x ' 202 | 'state=0x%08x curr=0x%08x advertised=0x%08x ' 203 | 'supported=0x%08x peer=0x%08x curr_speed=%d ' 204 | 'max_speed=%d' % 205 | (p.port_no, p.hw_addr, 206 | p.name, p.config, 207 | p.state, p.curr, p.advertised, 208 | p.supported, p.peer, p.curr_speed, 209 | p.max_speed)) 210 | 211 | if p.config in config_dict: 212 | config = config_dict[p.config] 213 | else: 214 | config = "up" 215 | 216 | if p.state in state_dict: 217 | state = state_dict[p.state] 218 | else: 219 | state = "up" 220 | 221 | # Recording data. 222 | port_feature = (config, state, p.curr_speed) 223 | self.port_features[dpid][p.port_no] = port_feature 224 | 225 | @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) 226 | def _port_status_handler(self, ev): 227 | """ 228 | Handle the port status changed event. 229 | """ 230 | msg = ev.msg 231 | ofproto = msg.datapath.ofproto 232 | reason = msg.reason 233 | dpid = msg.datapath.id 234 | port_no = msg.desc.port_no 235 | 236 | reason_dict = {ofproto.OFPPR_ADD: "added", 237 | ofproto.OFPPR_DELETE: "deleted", 238 | ofproto.OFPPR_MODIFY: "modified", } 239 | 240 | if reason in reason_dict: 241 | print "switch%d: port %s %s" % (dpid, reason_dict[reason], port_no) 242 | else: 243 | print "switch%d: Illeagal port state %s %s" % (dpid, port_no, reason) 244 | 245 | def _request_stats(self, datapath): 246 | """ 247 | Sending request msg to datapath 248 | """ 249 | self.logger.debug('send stats request: %016x', datapath.id) 250 | ofproto = datapath.ofproto 251 | parser = datapath.ofproto_parser 252 | req = parser.OFPPortDescStatsRequest(datapath, 0) 253 | datapath.send_msg(req) 254 | req = parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY) 255 | datapath.send_msg(req) 256 | req = parser.OFPFlowStatsRequest(datapath) 257 | datapath.send_msg(req) 258 | 259 | def get_min_bw_of_links(self, graph, path, min_bw): 260 | """ 261 | Getting bandwidth of path. Actually, the mininum bandwidth 262 | of links is the path's bandwith, because it is the bottleneck of path. 263 | """ 264 | _len = len(path) 265 | if _len > 1: 266 | minimal_band_width = min_bw 267 | for i in xrange(_len-1): 268 | pre, curr = path[i], path[i+1] 269 | if 'bandwidth' in graph[pre][curr]: 270 | bw = graph[pre][curr]['bandwidth'] 271 | minimal_band_width = min(bw, minimal_band_width) 272 | else: 273 | continue 274 | return minimal_band_width 275 | else: 276 | return min_bw 277 | 278 | def get_best_path_by_bw(self, graph, paths): 279 | """ 280 | Get best path by comparing paths. 281 | Note: This function is called in EFattree module. 282 | """ 283 | capabilities = {} 284 | best_paths = copy.deepcopy(paths) 285 | 286 | for src in paths: 287 | for dst in paths[src]: 288 | if src == dst: 289 | best_paths[src][src] = [src] 290 | capabilities.setdefault(src, {src: setting.MAX_CAPACITY}) 291 | capabilities[src][src] = setting.MAX_CAPACITY 292 | else: 293 | max_bw_of_paths = 0 294 | best_path = paths[src][dst][0] 295 | for path in paths[src][dst]: 296 | min_bw = setting.MAX_CAPACITY 297 | min_bw = self.get_min_bw_of_links(graph, path, min_bw) 298 | if min_bw > max_bw_of_paths: 299 | max_bw_of_paths = min_bw 300 | best_path = path 301 | best_paths[src][dst] = best_path 302 | capabilities.setdefault(src, {dst: max_bw_of_paths}) 303 | capabilities[src][dst] = max_bw_of_paths 304 | 305 | # self.capabilities and self.best_paths have no actual utility in this module. 306 | self.capabilities = capabilities 307 | self.best_paths = best_paths 308 | return capabilities, best_paths 309 | 310 | def create_bw_graph(self, bw_dict): 311 | """ 312 | Save bandwidth data into networkx graph object. 313 | """ 314 | try: 315 | graph = self.awareness.graph 316 | link_to_port = self.awareness.link_to_port 317 | for link in link_to_port: 318 | (src_dpid, dst_dpid) = link 319 | (src_port, dst_port) = link_to_port[link] 320 | if src_dpid in bw_dict and dst_dpid in bw_dict: 321 | bw_src = bw_dict[src_dpid][src_port] 322 | bw_dst = bw_dict[dst_dpid][dst_port] 323 | bandwidth = min(bw_src, bw_dst) 324 | # Add key:value pair of bandwidth into graph. 325 | if graph.has_edge(src_dpid, dst_dpid): 326 | graph[src_dpid][dst_dpid]['bandwidth'] = bandwidth 327 | else: 328 | graph.add_edge(src_dpid, dst_dpid) 329 | graph[src_dpid][dst_dpid]['bandwidth'] = bandwidth 330 | else: 331 | if graph.has_edge(src_dpid, dst_dpid): 332 | graph[src_dpid][dst_dpid]['bandwidth'] = 0 333 | else: 334 | graph.add_edge(src_dpid, dst_dpid) 335 | graph[src_dpid][dst_dpid]['bandwidth'] = 0 336 | return graph 337 | except: 338 | self.logger.info("Create bw graph exception") 339 | if self.awareness is None: 340 | self.awareness = lookup_service_brick('awareness') 341 | return self.awareness.graph 342 | 343 | def _save_freebandwidth(self, dpid, port_no, speed): 344 | """ 345 | Calculate free bandwidth of port and Save it. 346 | port_feature = (config, state, p.curr_speed) 347 | self.port_features[dpid][p.port_no] = port_feature 348 | self.free_bandwidth = {dpid:{port_no:free_bw,},} 349 | """ 350 | port_state = self.port_features.get(dpid).get(port_no) 351 | if port_state: 352 | capacity = 10000 # The true bandwidth of link, instead of 'curr_speed'. 353 | free_bw = self._get_free_bw(capacity, speed) 354 | self.free_bandwidth[dpid].setdefault(port_no, None) 355 | self.free_bandwidth[dpid][port_no] = free_bw 356 | else: 357 | self.logger.info("Port is Down") 358 | 359 | def _save_stats(self, _dict, key, value, length=5): 360 | if key not in _dict: 361 | _dict[key] = [] 362 | _dict[key].append(value) 363 | if len(_dict[key]) > length: 364 | _dict[key].pop(0) 365 | 366 | def _get_speed(self, now, pre, period): 367 | if period: 368 | return (now - pre) / (period) 369 | else: 370 | return 0 371 | 372 | def _get_free_bw(self, capacity, speed): 373 | # freebw: Kbit/s 374 | return max(capacity - speed * 8 / 1000.0, 0) 375 | 376 | def _get_time(self, sec, nsec): 377 | return sec + nsec / 1000000000.0 378 | 379 | def _get_period(self, n_sec, n_nsec, p_sec, p_nsec): 380 | return self._get_time(n_sec, n_nsec) - self._get_time(p_sec, p_nsec) 381 | 382 | def show_stat(self, _type): 383 | ''' 384 | Show statistics information according to data type. 385 | _type: 'port' / 'flow' 386 | ''' 387 | if setting.TOSHOW_stat is False: 388 | return 389 | 390 | bodys = self.stats[_type] 391 | if _type == 'flow' and setting.TOSHOW_flow_stat: 392 | print('\ndatapath ' 393 | 'priority ip_src ip_dst ' 394 | ' packets bytes flow-speed(Kb/s)') 395 | print('-------- ' 396 | '-------- ------------ ------------ ' 397 | '--------- ----------- ----------------') 398 | for dpid in sorted(bodys.keys()): 399 | 400 | for stat in sorted([flow for flow in bodys[dpid] if ((flow.priority not in [0, 65535]) and (flow.match.get('ipv4_src')) and (flow.match.get('ipv4_dst')))], 401 | key=lambda flow: (flow.priority, flow.match.get('ipv4_src'), flow.match.get('ipv4_dst'))): 402 | print('%8d %8s %12s %12s %9d %11d %16.1f' % ( 403 | dpid, 404 | stat.priority, stat.match.get('ipv4_src'), stat.match.get('ipv4_dst'), 405 | stat.packet_count, stat.byte_count, 406 | abs(self.flow_speed[dpid][(stat.priority, stat.match.get('ipv4_src'), stat.match.get('ipv4_dst'))][-1])*8/1000.0)) 407 | print 408 | 409 | if _type == 'port' and setting.TOSHOW_port_stat: 410 | print('\ndatapath port ' 411 | ' rx-pkts rx-bytes '' tx-pkts tx-bytes ' 412 | ' port-bw(Kb/s) port-speed(b/s) port-freebw(Kb/s) ' 413 | ' port-state link-state') 414 | print('-------- ---- ' 415 | '--------- ----------- ''--------- ----------- ' 416 | '------------- --------------- ----------------- ' 417 | '---------- ----------') 418 | _format = '%8d %4x %9d %11d %9d %11d %13d %15.1f %17.1f %10s %10s' 419 | for dpid in sorted(bodys.keys()): 420 | for stat in sorted(bodys[dpid], key=attrgetter('port_no')): 421 | if stat.port_no != ofproto_v1_3.OFPP_LOCAL: 422 | print(_format % ( 423 | dpid, stat.port_no, 424 | stat.rx_packets, stat.rx_bytes, 425 | stat.tx_packets, stat.tx_bytes, 426 | 10000, 427 | abs(self.port_speed[(dpid, stat.port_no)][-1] * 8), 428 | self.free_bandwidth[dpid][stat.port_no], 429 | self.port_features[dpid][stat.port_no][0], 430 | self.port_features[dpid][stat.port_no][1])) 431 | print 432 | -------------------------------------------------------------------------------- /network_monitor.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Huangmachi/PureSDN/54a31af6fed10df5755c6092bc3b66d6e125fe90/network_monitor.pyc -------------------------------------------------------------------------------- /setting.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Li Cheng at Beijing University of Posts 2 | # and Telecommunications. www.muzixing.com 3 | # Copyright (C) 2016 Huang MaChi at Chongqing University 4 | # of Posts and Telecommunications, Chongqing, China. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 15 | # implied. 16 | # See the License for the specific language governing permissions and 17 | # limitations under the License. 18 | 19 | """ 20 | Common Setting of EFattree. 21 | """ 22 | 23 | DISCOVERY_PERIOD = 10 # For discovering topology. 24 | 25 | MONITOR_PERIOD = 5 # For monitoring traffic 26 | 27 | TOSHOW_topo = True # For showing network topology in terminal 28 | TOSHOW_stat = True # For showing statistics in terminal 29 | TOSHOW_flow_stat = True # For showing flow statistics in terminal 30 | TOSHOW_port_stat = False # For showing port statistics in terminal 31 | 32 | MAX_CAPACITY = 10000 # Max capacity of link 33 | -------------------------------------------------------------------------------- /setting.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Huangmachi/PureSDN/54a31af6fed10df5755c6092bc3b66d6e125fe90/setting.pyc --------------------------------------------------------------------------------