├── README.md ├── images ├── coderun1.png ├── coderun2.png ├── dijkstra.png ├── protocols.png └── topology.png ├── import_multipath.py ├── import_topology.py ├── multipath.py └── topology.py /README.md: -------------------------------------------------------------------------------- 1 | # SDN Ryu-Controller -- Load-Balancing with Dynamic-Routing 2 | 3 | ## Project Details: 4 | 5 | ## OpenFlow version used: 6 | 7 | OpenFlow 1.3 8 | 9 | 10 | ## Description: 11 | 12 | This project is created using Ryu controller which performs DIJKSTRA algorithm to find best paths, based on traffic flowing through links. Optimal path is being choosen from possible paths. The costs are being calculated in the background (action performed by thread and the path cost is calculated via network bandwidth calculation.) and optimal path is being updated every second based on the gathered stats. Discover of topology is done automatically so we don't have to have specially prepared topology. 13 | 14 | 15 | ## DIJKSTRA ALOGRITHM USED: 16 | 17 | We have used Dijkstra algorithm to find the shortest available path from H1 to H2. The algorithm uses min heap functionality to calculate the smallest path cost. Following is the screenshot of the code. 18 | 19 | ![Screenshot](./images/dijkstra.png) 20 | 21 | ## TOPOLOGY USED: 22 | 23 | We have created 2 controllers, one controller is for backup. We have created 9 switches for the connection part. All the links are connected as shown in the screenshot. There are 2 hosts in the network topology. 24 | 25 | ![Screenshot](./images/topology.png) 26 | 27 | ## Protocols Configured: 28 | 29 | Our Code is configured to use 4 types of protocol. The type of protocol can be found using the header values. The protocols are ``TCP`` , ``UDP`` , ``ICMP`` , ``ARP``. Tcp is used for normal connection. UDP will be used for service packets for buffering. ICMP will be used when the link gets terminated to send a ping regarding link destruction. ARP is used when the host IP is known but MAC is not known. 30 | 31 | ![Screenshot](./images/protocols.png) 32 | 33 | ## Screenshot of Running Code: 34 | 35 | As you can see in the screenshot the possible paths are calculated and the path will minimum cost is the final cost of the packet transmission. 36 | 37 | ![Screenshot](./images/coderun1.png) 38 | 39 | ![Screenshot](./images/coderun2.png) 40 | 41 | ## Commands AND Requirements 42 | - ryu-manager --observe-links multipath.py (for initializing the RYU controller) 43 | 44 | - sudo mn -c 45 | - sudo python topology.py (For initializing the topology of the network) 46 | - Virtual Machine 47 | - Mininet 2.3.0 48 | - RYU controller 49 | -------------------------------------------------------------------------------- /images/coderun1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshayxml/SDN-based-Load-Balancing/a0e116369aac7d7fbb6361596402ed7cac85148a/images/coderun1.png -------------------------------------------------------------------------------- /images/coderun2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshayxml/SDN-based-Load-Balancing/a0e116369aac7d7fbb6361596402ed7cac85148a/images/coderun2.png -------------------------------------------------------------------------------- /images/dijkstra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshayxml/SDN-based-Load-Balancing/a0e116369aac7d7fbb6361596402ed7cac85148a/images/dijkstra.png -------------------------------------------------------------------------------- /images/protocols.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshayxml/SDN-based-Load-Balancing/a0e116369aac7d7fbb6361596402ed7cac85148a/images/protocols.png -------------------------------------------------------------------------------- /images/topology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshayxml/SDN-based-Load-Balancing/a0e116369aac7d7fbb6361596402ed7cac85148a/images/topology.png -------------------------------------------------------------------------------- /import_multipath.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import threading 4 | import os 5 | import random 6 | import time 7 | import heapq 8 | 9 | from collections import defaultdict 10 | from operator import itemgetter 11 | from dataclasses import dataclass 12 | 13 | from ryu.base import app_manager 14 | from ryu.controller import mac_to_port 15 | from ryu.controller import ofp_event 16 | from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER 17 | from ryu.controller.handler import set_ev_cls 18 | from ryu.ofproto import ofproto_v1_3 19 | from ryu.lib.mac import haddr_to_bin 20 | from ryu.lib.packet import packet 21 | from ryu.lib.packet import arp 22 | from ryu.lib.packet import ethernet 23 | from ryu.lib.packet import ipv4 24 | from ryu.lib.packet import ipv6 25 | from ryu.lib.packet import ether_types 26 | from ryu.lib.packet import udp 27 | from ryu.lib.packet import tcp 28 | from ryu.lib import mac, ip 29 | from ryu.lib import hub 30 | from ryu.ofproto import inet 31 | from ryu.topology.api import get_switch, get_link, get_host 32 | from ryu.app.wsgi import ControllerBase 33 | from ryu.topology import event, switches 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /import_topology.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from mininet.node import Controller, OVSKernelSwitch, RemoteController 3 | from mininet.log import setLogLevel, info 4 | from mininet.cli import CLI 5 | from mininet.net import Mininet 6 | from time import sleep 7 | 8 | -------------------------------------------------------------------------------- /multipath.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from import_multipath import * 4 | 5 | REFERENCE_BW = 10000000 6 | DEFAULT_BW = 10000000 7 | MAX_PATHS = 2 8 | 9 | @dataclass 10 | class Paths: 11 | ''' Paths container''' 12 | path: list() 13 | cost: float 14 | 15 | class Controller13(app_manager.RyuApp): 16 | OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] 17 | 18 | def __init__(self, *args, **kwargs): 19 | super(Controller13, self).__init__(*args, **kwargs) 20 | self.mac_to_port = {} 21 | self.neigh = defaultdict(dict) 22 | self.bw = defaultdict(lambda: defaultdict( lambda: DEFAULT_BW)) 23 | self.prev_bytes = defaultdict(lambda: defaultdict( lambda: 0)) 24 | self.hosts = {} 25 | self.switches = [] 26 | self.arp_table = {} 27 | self.path_table = {} 28 | self.paths_table = {} 29 | self.path_with_ports_table = {} 30 | self.datapath_list = {} 31 | self.path_calculation_keeper = [] 32 | 33 | def get_bandwidth(self, path, port, index): 34 | return self.bw[path[index]][port] 35 | 36 | def find_path_cost(self, path): 37 | ''' arg path is a list with all nodes in our route ''' 38 | path_cost = [] 39 | i = 0 40 | while(i < len(path) - 1): 41 | port1 = self.neigh[path[i]][path[i + 1]] 42 | bandwidth_between_two_nodes = self.get_bandwidth(path, port1, i) 43 | path_cost.append(bandwidth_between_two_nodes) 44 | i += 1 45 | return sum(path_cost) 46 | 47 | def find_paths_and_costs(self, src, dst): 48 | ''' 49 | Implementation of Breath-First Search Algorithm (BFS) 50 | Output of this function returns an list on class Paths objects 51 | ''' 52 | if src == dst: 53 | return [Paths(src,0)] 54 | queue = [(src, [src])] 55 | possible_paths = list() 56 | while queue: 57 | (edge, path) = queue.pop() 58 | for vertex in set(self.neigh[edge]) - set(path): 59 | if vertex == dst: 60 | path_to_dst = path + [vertex] 61 | cost_of_path = self.find_path_cost(path_to_dst) 62 | possible_paths.append(Paths(path_to_dst, cost_of_path)) 63 | else: 64 | queue.append((vertex, path + [vertex])) 65 | return possible_paths 66 | 67 | def find_n_optimal_paths(self, paths, number_of_optimal_paths = MAX_PATHS): 68 | '''arg paths is an list containing lists of possible paths''' 69 | costs = [path.cost for path in paths] 70 | optimal_paths_indexes = list(map(costs.index, heapq.nsmallest(number_of_optimal_paths,costs))) 71 | optimal_paths = [paths[op_index] for op_index in optimal_paths_indexes] 72 | return optimal_paths 73 | 74 | def add_ports_to_paths(self, paths, first_port, last_port): 75 | ''' 76 | Add the ports to all switches including hosts 77 | ''' 78 | paths_n_ports = list() 79 | bar = dict() 80 | in_port = first_port 81 | for s1, s2 in zip(paths[0].path[:-1], paths[0].path[1:]): 82 | out_port = self.neigh[s1][s2] 83 | bar[s1] = (in_port, out_port) 84 | in_port = self.neigh[s2][s1] 85 | bar[paths[0].path[-1]] = (in_port, last_port) 86 | paths_n_ports.append(bar) 87 | return paths_n_ports 88 | 89 | def install_paths(self, src, first_port, dst, last_port, ip_src, ip_dst, type, pkt): 90 | 91 | if (src, first_port, dst, last_port) not in self.path_calculation_keeper: 92 | self.path_calculation_keeper.append((src, first_port, dst, last_port)) 93 | self.topology_discover(src, first_port, dst, last_port) 94 | self.topology_discover(dst, last_port, src, first_port) 95 | 96 | 97 | for node in self.path_table[(src, first_port, dst, last_port)][0].path: 98 | 99 | dp = self.datapath_list[node] 100 | ofp = dp.ofproto 101 | ofp_parser = dp.ofproto_parser 102 | 103 | actions = [] 104 | 105 | in_port = self.path_with_ports_table[(src, first_port, dst, last_port)][0][node][0] 106 | out_port = self.path_with_ports_table[(src, first_port, dst, last_port)][0][node][1] 107 | 108 | actions = [ofp_parser.OFPActionOutput(out_port)] 109 | 110 | if type == 'UDP': 111 | nw = pkt.get_protocol(ipv4.ipv4) 112 | l4 = pkt.get_protocol(udp.udp) 113 | match = ofp_parser.OFPMatch(in_port = in_port, eth_type=ether_types.ETH_TYPE_IP, ipv4_src=ip_src, ipv4_dst = ip_dst, 114 | ip_proto=inet.IPPROTO_UDP, udp_src = l4.src_port, udp_dst = l4.dst_port) 115 | self.logger.info(f"Installed path in switch: {node} out port: {out_port} in port: {in_port} ") 116 | self.add_flow(dp, 33333, match, actions, 10) 117 | self.logger.info("UDP Flow added ! ") 118 | 119 | elif type == 'TCP': 120 | nw = pkt.get_protocol(ipv4.ipv4) 121 | l4 = pkt.get_protocol(tcp.tcp) 122 | match = ofp_parser.OFPMatch(in_port = in_port,eth_type=ether_types.ETH_TYPE_IP, ipv4_src=ip_src, ipv4_dst = ip_dst, 123 | ip_proto=inet.IPPROTO_TCP,tcp_src = l4.src_port, tcp_dst = l4.dst_port) 124 | self.logger.info(f"Installed path in switch: {node} out port: {out_port} in port: {in_port} ") 125 | self.add_flow(dp, 44444, match, actions, 10) 126 | self.logger.info("TCP Flow added ! ") 127 | 128 | elif type == 'ICMP': 129 | nw = pkt.get_protocol(ipv4.ipv4) 130 | match = ofp_parser.OFPMatch(in_port=in_port, 131 | eth_type=ether_types.ETH_TYPE_IP, 132 | ipv4_src=ip_src, 133 | ipv4_dst = ip_dst, 134 | ip_proto=inet.IPPROTO_ICMP) 135 | self.logger.info(f"Installed path in switch: {node} out port: {out_port} in port: {in_port} ") 136 | self.add_flow(dp, 22222, match, actions, 10) 137 | self.logger.info("ICMP Flow added ! ") 138 | 139 | elif type == 'ARP': 140 | match_arp = ofp_parser.OFPMatch(in_port = in_port,eth_type=ether_types.ETH_TYPE_ARP, arp_spa=ip_src, arp_tpa=ip_dst) 141 | self.logger.info(f"Install path in switch: {node} out port: {out_port} in port: {in_port} ") 142 | self.add_flow(dp, 1, match_arp, actions, 10) 143 | self.logger.info("ARP Flow added ! ") 144 | 145 | return self.path_with_ports_table[(src, first_port, dst, last_port)][0][src][1] 146 | 147 | def add_flow(self, datapath, priority, match, actions, idle_timeout, buffer_id = None): 148 | ''' Method Provided by the source Ryu library.''' 149 | 150 | ofproto = datapath.ofproto 151 | parser = datapath.ofproto_parser 152 | 153 | inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, 154 | actions)] 155 | if buffer_id: 156 | mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id, 157 | priority=priority, match=match, idle_timeout = idle_timeout, 158 | instructions=inst) 159 | else: 160 | mod = parser.OFPFlowMod(datapath=datapath, priority=priority, 161 | match=match, idle_timeout = idle_timeout, instructions=inst) 162 | datapath.send_msg(mod) 163 | 164 | def run_check(self, ofp_parser, dp): 165 | threading.Timer(1.0, self.run_check, args=(ofp_parser, dp)).start() 166 | 167 | req = ofp_parser.OFPPortStatsRequest(dp) 168 | dp.send_msg(req) 169 | 170 | def topology_discover(self, src, first_port, dst, last_port): 171 | threading.Timer(1.0, self.topology_discover, args=(src, first_port, dst, last_port)).start() 172 | paths = self.find_paths_and_costs(src, dst) 173 | path = self.find_n_optimal_paths(paths) 174 | path_with_port = self.add_ports_to_paths(path, first_port, last_port) 175 | 176 | self.logger.info(f"Possible paths: {paths}") 177 | self.logger.info(f"Optimal Path with port: {path_with_port}") 178 | 179 | self.paths_table[(src, first_port, dst, last_port)] = paths 180 | self.path_table[(src, first_port, dst, last_port)] = path 181 | self.path_with_ports_table[(src, first_port, dst, last_port)] = path_with_port 182 | 183 | 184 | @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) 185 | def _packet_in_handler(self, ev): 186 | if ev.msg.msg_len < ev.msg.total_len: 187 | self.logger.debug("packet truncated: only %s of %s bytes", ev.msg.msg_len, ev.msg.total_len) 188 | msg = ev.msg 189 | datapath = msg.datapath 190 | ofproto = datapath.ofproto 191 | parser = datapath.ofproto_parser 192 | in_port = msg.match['in_port'] 193 | 194 | pkt = packet.Packet(msg.data) 195 | eth = pkt.get_protocols(ethernet.ethernet)[0] 196 | arp_pkt = pkt.get_protocol(arp.arp) 197 | ip_pkt = pkt.get_protocol(ipv4.ipv4) 198 | 199 | if eth.ethertype == ether_types.ETH_TYPE_LLDP: 200 | return 201 | 202 | dst = eth.dst 203 | src = eth.src 204 | dpid = datapath.id 205 | 206 | if src not in self.hosts: 207 | self.hosts[src] = (dpid, in_port) 208 | 209 | out_port = ofproto.OFPP_FLOOD 210 | 211 | if eth.ethertype == ether_types.ETH_TYPE_IP: 212 | nw = pkt.get_protocol(ipv4.ipv4) 213 | if nw.proto == inet.IPPROTO_UDP: 214 | l4 = pkt.get_protocol(udp.udp) 215 | elif nw.proto == inet.IPPROTO_TCP: 216 | l4 = pkt.get_protocol(tcp.tcp) 217 | 218 | if eth.ethertype == ether_types.ETH_TYPE_IP and nw.proto == inet.IPPROTO_UDP: 219 | src_ip = nw.src 220 | dst_ip = nw.dst 221 | 222 | self.arp_table[src_ip] = src 223 | h1 = self.hosts[src] 224 | h2 = self.hosts[dst] 225 | 226 | self.logger.info(f" IP Proto UDP from: {nw.src} to: {nw.dst}") 227 | 228 | out_port = self.install_paths(h1[0], h1[1], h2[0], h2[1], src_ip, dst_ip, 'UDP', pkt) 229 | self.install_paths(h2[0], h2[1], h1[0], h1[1], dst_ip, src_ip, 'UDP', pkt) 230 | 231 | elif eth.ethertype == ether_types.ETH_TYPE_IP and nw.proto == inet.IPPROTO_TCP: 232 | src_ip = nw.src 233 | dst_ip = nw.dst 234 | 235 | self.arp_table[src_ip] = src 236 | h1 = self.hosts[src] 237 | h2 = self.hosts[dst] 238 | 239 | self.logger.info(f" IP Proto TCP from: {nw.src} to: {nw.dst}") 240 | 241 | out_port = self.install_paths(h1[0], h1[1], h2[0], h2[1], src_ip, dst_ip, 'TCP', pkt) 242 | self.install_paths(h2[0], h2[1], h1[0], h1[1], dst_ip, src_ip, 'TCP', pkt) 243 | 244 | elif eth.ethertype == ether_types.ETH_TYPE_IP and nw.proto == inet.IPPROTO_ICMP: 245 | src_ip = nw.src 246 | dst_ip = nw.dst 247 | 248 | self.arp_table[src_ip] = src 249 | h1 = self.hosts[src] 250 | h2 = self.hosts[dst] 251 | 252 | self.logger.info(f" IP Proto ICMP from: {nw.src} to: {nw.dst}") 253 | 254 | out_port = self.install_paths(h1[0], h1[1], h2[0], h2[1], src_ip, dst_ip, 'ICMP', pkt) 255 | self.install_paths(h2[0], h2[1], h1[0], h1[1], dst_ip, src_ip, 'ICMP', pkt) 256 | 257 | elif eth.ethertype == ether_types.ETH_TYPE_ARP: 258 | src_ip = arp_pkt.src_ip 259 | dst_ip = arp_pkt.dst_ip 260 | 261 | if arp_pkt.opcode == arp.ARP_REPLY: 262 | self.arp_table[src_ip] = src 263 | h1 = self.hosts[src] 264 | h2 = self.hosts[dst] 265 | 266 | self.logger.info(f" ARP Reply from: {src_ip} to: {dst_ip} H1: {h1} H2: {h2}") 267 | 268 | out_port = self.install_paths(h1[0], h1[1], h2[0], h2[1], src_ip, dst_ip, 'ARP', pkt) 269 | self.install_paths(h2[0], h2[1], h1[0], h1[1], dst_ip, src_ip, 'ARP', pkt) 270 | 271 | elif arp_pkt.opcode == arp.ARP_REQUEST: 272 | if dst_ip in self.arp_table: 273 | self.arp_table[src_ip] = src 274 | dst_mac = self.arp_table[dst_ip] 275 | h1 = self.hosts[src] 276 | h2 = self.hosts[dst_mac] 277 | 278 | self.logger.info(f" ARP Reply from: {src_ip} to: {dst_ip} H1: {h1} H2: {h2}") 279 | 280 | out_port = self.install_paths(h1[0], h1[1], h2[0], h2[1], src_ip, dst_ip, 'ARP', pkt) 281 | self.install_paths(h2[0], h2[1], h1[0], h1[1], dst_ip, src_ip, 'ARP', pkt) 282 | 283 | actions = [parser.OFPActionOutput(out_port)] 284 | 285 | data = None 286 | 287 | if msg.buffer_id == ofproto.OFP_NO_BUFFER: 288 | data = msg.data 289 | 290 | out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id, 291 | in_port=in_port, actions=actions, data=data) 292 | datapath.send_msg(out) 293 | 294 | @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) 295 | def _switch_features_handler(self, ev): 296 | ''' 297 | To send packets for which we dont have right information to the controller 298 | Method Provided by the source Ryu library. 299 | ''' 300 | 301 | datapath = ev.msg.datapath 302 | ofproto = datapath.ofproto 303 | parser = datapath.ofproto_parser 304 | 305 | match = parser.OFPMatch() 306 | actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER, 307 | ofproto.OFPCML_NO_BUFFER)] 308 | self.add_flow(datapath, 0, match, actions, 10) 309 | 310 | @set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER) 311 | def _port_stats_reply_handler(self, ev): 312 | '''Reply to the OFPPortStatsRequest, visible beneath''' 313 | switch_dpid = ev.msg.datapath.id 314 | for p in ev.msg.body: 315 | self.bw[switch_dpid][p.port_no] = (p.tx_bytes - self.prev_bytes[switch_dpid][p.port_no])*8.0/1000000 316 | self.prev_bytes[switch_dpid][p.port_no] = p.tx_bytes 317 | 318 | @set_ev_cls(event.EventSwitchEnter) 319 | def switch_enter_handler(self, ev): 320 | switch_dp = ev.switch.dp 321 | switch_dpid = switch_dp.id 322 | ofp_parser = switch_dp.ofproto_parser 323 | 324 | self.logger.info(f"Switch has been plugged in PID: {switch_dpid}") 325 | 326 | if switch_dpid not in self.switches: 327 | self.datapath_list[switch_dpid] = switch_dp 328 | self.switches.append(switch_dpid) 329 | 330 | self.run_check(ofp_parser, switch_dp) 331 | 332 | @set_ev_cls(event.EventSwitchLeave, MAIN_DISPATCHER) 333 | def switch_leave_handler(self, ev): 334 | switch = ev.switch.dp.id 335 | if switch in self.switches: 336 | try: 337 | self.switches.remove(switch) 338 | del self.datapath_list[switch] 339 | del self.neigh[switch] 340 | except KeyError: 341 | self.logger.info(f"Switch has been already pulged off PID{switch}!") 342 | 343 | 344 | @set_ev_cls(event.EventLinkAdd, MAIN_DISPATCHER) 345 | def link_add_handler(self, ev): 346 | self.neigh[ev.link.src.dpid][ev.link.dst.dpid] = ev.link.src.port_no 347 | self.neigh[ev.link.dst.dpid][ev.link.src.dpid] = ev.link.dst.port_no 348 | self.logger.info(f"Link between switches has been established, SW1 DPID: {ev.link.src.dpid}:{ev.link.dst.port_no} SW2 DPID: {ev.link.dst.dpid}:{ev.link.dst.port_no}") 349 | 350 | @set_ev_cls(event.EventLinkDelete, MAIN_DISPATCHER) 351 | def link_delete_handler(self, ev): 352 | try: 353 | del self.neigh[ev.link.src.dpid][ev.link.dst.dpid] 354 | del self.neigh[ev.link.dst.dpid][ev.link.src.dpid] 355 | except KeyError: 356 | self.logger.info("Link has been already pluged off!") 357 | pass 358 | -------------------------------------------------------------------------------- /topology.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | 'This is the topology for ACN project' 4 | from import_topology import * 5 | 6 | def topology(): 7 | 'Create a network and controller' 8 | net = Mininet(controller=RemoteController, switch=OVSKernelSwitch) 9 | protocolName = "OpenFlow13" 10 | 11 | c0 = net.addController('c0', controller=RemoteController, ip='127.0.0.1', port=6653) 12 | c1 = net.addController('c1', controller=RemoteController, ip='127.0.0.2', port=6654) 13 | 14 | info("*** Creating the nodes\n") 15 | 16 | h1 = net.addHost('h1', ip='10.0.0.1/24', position='10,10,0') 17 | h2 = net.addHost('h2', ip='10.0.0.2/24', position='20,10,0') 18 | 19 | switch1 = net.addSwitch('switch1', protocols=protocolName, position='12,10,0') 20 | switch2 = net.addSwitch('switch2', protocols=protocolName, position='15,20,0') 21 | switch3 = net.addSwitch('switch3', protocols=protocolName, position='18,10,0') 22 | switch4 = net.addSwitch('switch4', protocols=protocolName, position='14,10,0') 23 | switch5 = net.addSwitch('switch5', protocols=protocolName, position='16,10,0') 24 | switch6 = net.addSwitch('switch6', protocols=protocolName, position='14,0,0') 25 | switch7 = net.addSwitch('switch7', protocols=protocolName, position='16,0,0') 26 | switch8 = net.addSwitch('switch8', protocols=protocolName, position='16,0,2') 27 | switch9 = net.addSwitch('switch9', protocols=protocolName, position='16,0,3') 28 | 29 | 30 | info("*** Adding the Link\n") 31 | net.addLink(h1, switch1) 32 | net.addLink(switch1, switch2) 33 | net.addLink(switch1, switch4) 34 | net.addLink(switch1, switch6) 35 | net.addLink(switch2, switch3) 36 | net.addLink(switch4, switch5) 37 | net.addLink(switch5, switch3) 38 | net.addLink(switch6, switch7) 39 | net.addLink(switch7, switch3) 40 | net.addLink(switch7, switch9) 41 | net.addLink(switch8, switch5) 42 | net.addLink(switch3, h2) 43 | 44 | 45 | info("*** Starting the network\n") 46 | net.build() 47 | c0.start() 48 | switch1.start([c0]) 49 | switch2.start([c0]) 50 | switch3.start([c0]) 51 | switch4.start([c0]) 52 | switch5.start([c0]) 53 | switch6.start([c0]) 54 | switch7.start([c0]) 55 | switch8.start([c1]) 56 | switch9.start([c1]) 57 | 58 | 59 | net.pingFull() 60 | 61 | info("*** Running the CLI\n") 62 | CLI( net ) 63 | 64 | info("*** Stopping network\n") 65 | net.stop() 66 | 67 | 68 | if __name__ == '__main__': 69 | setLogLevel('info') 70 | topology() 71 | --------------------------------------------------------------------------------