LEFT | RIGHT |
1 # ~/ryu$ PYTHONPATH= ./bin/ryu-manager ryu/app/cpm_13.py --observe-links | 1 # ~/ryu$ PYTHONPATH= ./bin/ryu-manager ryu/app/cpm_13.py --observe-links |
2 | 2 |
3 from ryu.ofproto import ofproto_v1_3 | 3 from ryu.ofproto import ofproto_v1_3 |
4 from ryu.app import simple_switch_13 | 4 from ryu.app import simple_switch_13 |
5 from ryu.controller.handler import set_ev_cls | 5 from ryu.controller.handler import set_ev_cls |
6 from ryu.topology import event, switches | 6 from ryu.topology import event, switches |
7 from ryu.topology.api import get_switch, get_link | 7 from ryu.topology.api import get_switch, get_link |
8 from ryu.controller import ofp_event | 8 from ryu.controller import ofp_event |
9 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER | 9 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER |
10 from ryu.lib.packet import packet | 10 from ryu.lib.packet import packet |
11 from ryu.lib.packet import arp | 11 from ryu.lib.packet import arp |
12 from ryu.lib.packet import ethernet | 12 from ryu.lib.packet import ethernet |
13 from ryu.lib.packet import ether_types | 13 from ryu.lib.packet import ether_types |
14 from ryu.lib.packet import ipv4 | 14 from ryu.lib.packet import ipv4 |
15 from ryu.lib.packet import icmp | 15 from ryu.lib.packet import icmp |
16 from ryu.lib.packet import udp | 16 from ryu.lib.packet import udp |
17 from ryu.lib.packet import tcp | 17 from ryu.lib.packet import tcp |
18 from ryu.lib import hub | 18 from ryu.lib import hub |
19 import time | 19 import time |
20 from my_http_client import MyHTTPClient | 20 from my_http_client import MyHTTPClient |
21 | 21 |
22 | 22 |
23 class CPM13(simple_switch_13.SimpleSwitch13): | 23 class CPM13(simple_switch_13.SimpleSwitch13): |
24 "CPM module for topology discovery and SPF" | 24 "CPM module for topology discovery and SPF" |
25 | 25 |
26 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] | 26 OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION] |
27 | 27 |
28 def __init__(self, *args, **kwargs): | 28 def __init__(self, *args, **kwargs): |
29 super(CPM13, self).__init__(*args, **kwargs) | 29 super(CPM13, self).__init__(*args, **kwargs) |
| 30 self.fib = {} # 2017-11-18 |
30 self.topo_links = {} | 31 self.topo_links = {} |
31 self.switches = {} | 32 self.switches = {} |
32 self.neighbor_ports = {} # Temp 2017-10-02 | 33 self.neighbor_ports = {} # Temp 2017-10-02 |
33 self.port_metric = {} # 2017-10-16 | 34 self.port_metric = {} # 2017-10-16 |
34 self.hosts = {} | 35 self.hosts = {} |
35 self.sample_interval = 30 # How often to query the DM | 36 self.sample_interval = 30 # How often to query the DM |
36 self.ref_bw = 100000000.0 # 100 Mbps; Reference Bandwidth in Bps | 37 self.ref_bw = 100000000.0 # 100 Mbps; Reference Bandwidth in Bps |
37 self.ref_hc = 30.0 # 30 Hops; Reference Hop Count | 38 self.ref_hc = 30.0 # 30 Hops; Reference Hop Count |
38 # Connect with DM: | 39 # Connect with DM: |
39 self.dm_server = MyHTTPClient('http://192.16.125.183/API_REST/request.ph
p/get?') | 40 self.dm_server = MyHTTPClient('http://192.16.125.183/API_REST/request.ph
p/get?') |
40 self.port_counters_snapshot = {'object_index': 1000, 'switches': []} | 41 self.port_counters_snapshot = {'object_index': 1000, 'switches': []} |
41 hub.spawn(self._get_dm_data) | 42 hub.spawn(self._get_dm_data) |
| 43 |
| 44 # Connect with DM#2; need a second object of MyHTTPClient as |
| 45 # URL for POST requests is different. |
| 46 self.dm_server_2 = MyHTTPClient('http://192.16.125.183/API_REST/request.
php')· |
42 | 47 |
43 def _get_dm_data(self): | 48 def _get_dm_data(self): |
44 while True: | 49 while True: |
45 # Ask for DM data every self.sample_interval seconds: | 50 # Ask for DM data every self.sample_interval seconds: |
46 hub.sleep(self.sample_interval) | 51 hub.sleep(self.sample_interval) |
47 #body = self.dm_server.get_data_from_dm('port_counters', 1) | 52 #body = self.dm_server.get_data_from_dm('port_counters', 1) |
48 params_dict = {'module': 'nfm', 'row': 1, 'table': 'port_counters'} | 53 params_dict = {'module': 'nfm', 'row': 1, 'table': 'port_counters'} |
49 body = self.dm_server.send_GET(params_dict) | 54 body = self.dm_server.send_GET(params_dict) |
50 #print body | 55 #print body |
51 if body is None: # This is True if NO json in HTTP reponse | 56 if body is None: # This is True if NO json in HTTP reponse |
52 print 'No NFM monitoring data received from DM' | 57 print 'No NFM monitoring data received from DM' |
53 continue | 58 continue |
54 # print body | 59 # print body |
55 self._calculate_link_metric(body) | 60 self._calculate_link_metric(body) |
| 61 self._fib_update() |
| 62 |
| 63 def _fib_update(self): |
| 64 for (a, z) in self.fib.keys(): |
| 65 best_path = self._find_best_path(a, z) |
| 66 if self.fib[(a, z)] != best_path: |
| 67 self.fib[(a, z)] = best_path |
| 68 print '[UPDATED BEST PATH] ', a, '>', z, ':', self.fib[(a, z)] |
| 69 |
| 70 def _fib_lookup(self, src_switch, dst_switch): |
| 71 print self.fib |
| 72 print '[FIB LOOKUP]', (src_switch, dst_switch), self.fib[(src_switch, ds
t_switch)] |
| 73 return self.fib[(src_switch, dst_switch)] |
56 | 74 |
57 def _calculate_link_metric(self, data_dict): | 75 def _calculate_link_metric(self, data_dict): |
58 | 76 |
59 for switch in data_dict['switches']: | 77 for switch in data_dict['switches']: |
60 # each element is a dictionary as well | 78 # each element is a dictionary as well |
61 for port_counter in switch['port_counters']: | 79 for port_counter in switch['port_counters']: |
62 # each element is a dictionary as well | 80 # each element is a dictionary as well |
63 | 81 |
64 tx_metric = float(port_counter['tx_bw']) / self.ref_bw | 82 tx_metric = float(port_counter['tx_bw']) / self.ref_bw |
| 83 |
| 84 # Round to 3 decimal points |
| 85 # https://docs.python.org/2.7/library/functions.html#round |
| 86 tx_metric = round(tx_metric, 3) |
65 | 87 |
66 self.port_metric[(switch['switch_dpid'], | 88 self.port_metric[(switch['switch_dpid'], |
67 port_counter['port'])] = tx_metric | 89 port_counter['port'])] = tx_metric |
68 | 90 |
69 #print '[CALC] Port Metric', (switch['switch_dpid'], port_counte
r['port']), 'set to', tx_metric | 91 #print '[CALC] Port Metric', (switch['switch_dpid'], port_counte
r['port']), 'set to', tx_metric |
70 | 92 |
71 def _calculate_hop_count(self, paths): | 93 def _calculate_hop_count(self, paths): |
72 paths_dict = {} | 94 paths_dict = {} |
73 | 95 |
74 for path in paths: | 96 for path in paths: |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
143 # print 'Neighbors', self.topo_links | 165 # print 'Neighbors', self.topo_links |
144 | 166 |
145 # Temp 2017-10-02 | 167 # Temp 2017-10-02 |
146 self.neighbor_ports[(switchA.dpid, switchB.dpid)] = ( | 168 self.neighbor_ports[(switchA.dpid, switchB.dpid)] = ( |
147 switchA.port_no, switchB.port_no) | 169 switchA.port_no, switchB.port_no) |
148 # print 'Ports', self.neighbor_ports | 170 # print 'Ports', self.neighbor_ports |
149 | 171 |
150 # 2017-10-16 | 172 # 2017-10-16 |
151 self.port_metric[(switchA.dpid, switchA.port_no)] = 0 | 173 self.port_metric[(switchA.dpid, switchA.port_no)] = 0 |
152 #print 'Port Metric', (switchA.dpid, switchA.port_no), 'set to', 0 | 174 #print 'Port Metric', (switchA.dpid, switchA.port_no), 'set to', 0 |
| 175 |
| 176 # 2017-11-16 |
| 177 # Inform DM about this link |
| 178 new_topo_link_json_obj = {'src_switch': switchA.dpid, |
| 179 'src_sw_port': switchA.port_no, |
| 180 'dst_switch': switchB.dpid, |
| 181 'dst_sw_port': switchB.port_no, |
| 182 'module': 'topology', |
| 183 'event': 'link_up', |
| 184 'bandwidth': 100000} |
| 185 self.dm_server_2.send_POST(new_topo_link_json_obj) |
| 186 |
153 | 187 |
154 # https://www.python.org/doc/essays/graphs/ | 188 # https://www.python.org/doc/essays/graphs/ |
155 def _find_all_paths(self, neighbors, start, end, path=[]): | 189 def _find_all_paths(self, neighbors, start, end, path=[]): |
156 path = path + [start] | 190 path = path + [start] |
157 if start == end: | 191 if start == end: |
158 return [path] | 192 return [path] |
159 if start not in neighbors.keys(): | 193 if start not in neighbors.keys(): |
160 return [] | 194 return [] |
161 paths = [] | 195 paths = [] |
162 for node in neighbors[start]: | 196 for node in neighbors[start]: |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
197 best_metric = 100000000000 # Just an Arbitrary high value. | 231 best_metric = 100000000000 # Just an Arbitrary high value. |
198 # Lowest metric means best metric! (0 is the best possible) | 232 # Lowest metric means best metric! (0 is the best possible) |
199 best_path = [] | 233 best_path = [] |
200 for path in all_paths: | 234 for path in all_paths: |
201 composite_metric = w1 * hop_count[path] | 235 composite_metric = w1 * hop_count[path] |
202 composite_metric += w2 * nfm_metric[path] | 236 composite_metric += w2 * nfm_metric[path] |
203 if composite_metric < best_metric: | 237 if composite_metric < best_metric: |
204 best_metric = composite_metric | 238 best_metric = composite_metric |
205 best_path = path | 239 best_path = path |
206 | 240 |
207 print 'BEST PATH between', (src_switch, dst_switch), 'is', best_path | 241 #print 'BEST PATH between', (src_switch, dst_switch), 'is', best_path |
208 print 'with Composite metric', best_metric | 242 #print 'with Composite metric', best_metric |
209 | 243 |
210 return list(best_path) | 244 #return list(best_path) |
| 245 return best_path |
211 | 246 |
212 def _add_network_uni_flow(self, path, pkt, buffer_id=None): | 247 def _add_network_uni_flow(self, path, pkt, buffer_id=None): |
213 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) | 248 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) |
214 ip_src = pkt_ipv4.src | 249 ip_src = pkt_ipv4.src |
215 | 250 |
216 # input argument "path" is a list of switches; each item is a dpid | 251 # input argument "path" is a tuple of switches; each item is a dpid |
217 # First I need to find the ports of the two switches: | 252 # First I need to find the ports of the two switches: |
| 253 print 'path', path |
218 if len(path) == 1: | 254 if len(path) == 1: |
219 print 'Hosts are on same switch', path[0] | 255 print 'Hosts are on same switch', path[0] |
220 return | 256 return |
221 | 257 |
222 # Install flows from A >> Z in the reverse order, i.e. | 258 # Install flows from A >> Z in the reverse order, i.e. |
223 # from End to Start. | 259 # from End to Start. |
224 path.reverse() | 260 path = path[::-1] # New tuple with reversed order of items |
| 261 |
225 ip_dst = pkt_ipv4.dst | 262 ip_dst = pkt_ipv4.dst |
226 hostZ = self.hosts[ip_dst] # dst host | 263 hostZ = self.hosts[ip_dst] # dst host |
227 out_port = hostZ['port'] | 264 out_port = hostZ['port'] |
228 | 265 |
229 for k, v in enumerate(path[:-1]): | 266 for k, v in enumerate(path[:-1]): |
230 (in_port, new_out_port) = self.neighbor_ports[( | 267 (in_port, new_out_port) = self.neighbor_ports[( |
231 v, path[k + 1])] | 268 v, path[k + 1])] |
232 | 269 |
233 switch = self.switches[v] | 270 switch = self.switches[v] |
234 | 271 |
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
365 pkt_out.add_protocol(ethernet.ethernet( | 402 pkt_out.add_protocol(ethernet.ethernet( |
366 ethertype=pkt_in_ether.ethertype, | 403 ethertype=pkt_in_ether.ethertype, |
367 dst=pkt_in_ether.src, src=src_mac)) | 404 dst=pkt_in_ether.src, src=src_mac)) |
368 pkt_out.add_protocol(arp.arp(opcode=arp.ARP_REPLY, | 405 pkt_out.add_protocol(arp.arp(opcode=arp.ARP_REPLY, |
369 src_mac=src_mac, | 406 src_mac=src_mac, |
370 src_ip=pkt_in_arp.dst_ip, | 407 src_ip=pkt_in_arp.dst_ip, |
371 dst_mac=pkt_in_arp.src_mac, | 408 dst_mac=pkt_in_arp.src_mac, |
372 dst_ip=pkt_in_arp.src_ip)) | 409 dst_ip=pkt_in_arp.src_ip)) |
373 self._send_packet(datapath, in_port, pkt_out) | 410 self._send_packet(datapath, in_port, pkt_out) |
374 | 411 |
375 def _handle_new_host(self, datapath, switch_port, host_mac): | 412 def _handle_new_host(self, datapath, switch_port, host_mac, host_ip): |
| 413 # Update fib 2017-11-18 |
| 414 switches_with_hosts = [] |
| 415 for ip, params in self.hosts.items(): |
| 416 if params['switch'] not in switches_with_hosts: |
| 417 switches_with_hosts.append(params['switch']) |
| 418 for switch in switches_with_hosts: |
| 419 if switch == datapath.id: |
| 420 # Same switch, no need to add to FIB |
| 421 continue |
| 422 if (switch, datapath.id) not in self.fib: |
| 423 self.fib[(switch, datapath.id)] = self._find_best_path(switch, d
atapath.id) |
| 424 print self.fib |
| 425 if (datapath.id, switch) not in self.fib: |
| 426 self.fib[(datapath.id, switch)] = self._find_best_path(datapath.
id, switch) |
| 427 print self.fib |
| 428 |
| 429 # Add new host to host dict |
| 430 self.hosts[host_ip] = {'mac': host_mac, |
| 431 'switch': datapath.id, |
| 432 'port': switch_port} |
| 433 |
| 434 print "[DISCOVERED NEW HOST]", host_ip, self.hosts[host_ip] |
| 435 |
| 436 # Add flow on this switch, point to NEW HOST: |
376 parser = datapath.ofproto_parser | 437 parser = datapath.ofproto_parser |
377 match = parser.OFPMatch(eth_dst=host_mac) | 438 match = parser.OFPMatch(eth_dst=host_mac) |
378 actions = [parser.OFPActionOutput(switch_port)] | 439 actions = [parser.OFPActionOutput(switch_port)] |
379 self.add_flow(datapath, 50, match, actions) | 440 self.add_flow(datapath, 50, match, actions) |
380 | 441 |
381 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) | 442 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) |
382 def _packet_in_handler(self, ev): | 443 def _packet_in_handler(self, ev): |
383 # If you hit this you might want to increase | 444 # If you hit this you might want to increase |
384 # the "miss_send_length" of your switch | 445 # the "miss_send_length" of your switch |
385 if ev.msg.msg_len < ev.msg.total_len: | 446 if ev.msg.msg_len < ev.msg.total_len: |
386 self.logger.debug("packet truncated: only %s of %s bytes", | 447 self.logger.debug("packet truncated: only %s of %s bytes", |
387 ev.msg.msg_len, ev.msg.total_len) | 448 ev.msg.msg_len, ev.msg.total_len) |
388 | 449 |
389 msg = ev.msg | 450 msg = ev.msg |
390 datapath = msg.datapath | 451 datapath = msg.datapath |
391 in_port = msg.match['in_port'] | 452 in_port = msg.match['in_port'] |
392 | 453 |
393 pkt = packet.Packet(msg.data) | 454 pkt = packet.Packet(msg.data) |
394 eth = pkt.get_protocols(ethernet.ethernet)[0] | 455 eth = pkt.get_protocols(ethernet.ethernet)[0] |
395 | 456 |
396 if eth.ethertype == ether_types.ETH_TYPE_LLDP: | 457 if eth.ethertype == ether_types.ETH_TYPE_LLDP: |
397 # ignore lldp packet | 458 # ignore lldp packet |
398 return | 459 return |
399 | 460 |
400 print 'PACKET IN >>', datapath.id, in_port | 461 #print 'PACKET IN >>', datapath.id, in_port |
401 self.logger.debug("packet-in %s" % (pkt,)) | 462 self.logger.debug("packet-in %s" % (pkt,)) |
402 | 463 |
403 dpid = datapath.id | 464 dpid = datapath.id |
404 | 465 |
405 # self.hosts = {'10.0.0.1': {'mac': '00:00:00:00:00:01', 'switch': 11, '
port': 1}, | 466 # self.hosts = {'10.0.0.1': {'mac': '00:00:00:00:00:01', 'switch': 11, '
port': 1}, |
406 # '10.0.0.2': {'mac': '00:00:00:00:00:02', 'switch': 13, 'p
ort': 2}} | 467 # '10.0.0.2': {'mac': '00:00:00:00:00:02', 'switch': 13, 'p
ort': 2}} |
407 | 468 |
408 # Handle ARP Request packets: | 469 # Handle ARP Request packets: |
409 pkt_arp = pkt.get_protocol(arp.arp) | 470 pkt_arp = pkt.get_protocol(arp.arp) |
410 if pkt_arp: | 471 if pkt_arp: |
411 # Inspired by Ryu book page 88 | 472 # Inspired by Ryu book page 88 |
412 if pkt_arp: | 473 if pkt_arp: |
413 if pkt_arp.opcode != arp.ARP_REQUEST: | 474 if pkt_arp.opcode != arp.ARP_REQUEST: |
414 print 'Expected an ARP Request but this is not it.' | 475 print 'Expected an ARP Request but this is not it.' |
415 return | 476 return |
416 | 477 |
417 # Learn new source host: | 478 # Learn new source host: |
418 if pkt_arp.src_ip not in self.hosts: | 479 if pkt_arp.src_ip not in self.hosts: |
419 self.hosts[pkt_arp.src_ip] = { | 480 self._handle_new_host(datapath, in_port, eth.src, pkt_arp.sr
c_ip) |
420 'mac': eth.src, 'switch': dpid, 'port': in_port} | |
421 print "DISCOVERED NEW HOST", pkt_arp.src_ip, self.hosts[pkt_
arp.src_ip] | |
422 # Add flow on this switch, point to NEW HOST: | |
423 self._handle_new_host(datapath, in_port, eth.src) | |
424 return | 481 return |
425 | 482 |
426 # Reply to ARP request: | 483 # Reply to ARP request: |
427 if pkt_arp.dst_ip in self.hosts: | 484 if pkt_arp.dst_ip in self.hosts: |
428 self._handle_arp_request(datapath, in_port, pkt) | 485 self._handle_arp_request(datapath, in_port, pkt) |
429 return | 486 return |
430 | 487 |
431 # Handle IPv4 packets: | 488 # Handle IPv4 packets: |
432 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) | 489 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) |
433 if not pkt_ipv4: | 490 if not pkt_ipv4: |
434 print 'NOT an IPv4 Packet:' | 491 print 'NOT an IPv4 Packet:' |
435 self.logger.info("TABLE MISS: switch %s, packet-in %s", dpid, (pkt,)
) | 492 self.logger.info("TABLE MISS: switch %s, packet-in %s", dpid, (pkt,)
) |
436 return | 493 return |
437 else: | 494 else: |
438 # Block all similar future packets for 1 second: | 495 # Block all similar future packets for 1 second: |
439 self._deny_flow(datapath, in_port, pkt) | 496 # self._deny_flow(datapath, in_port, pkt) |
440 | 497 |
441 dst_switch = self.hosts[pkt_ipv4.dst]['switch'] | 498 dst_switch = self.hosts[pkt_ipv4.dst]['switch'] |
442 # Find best path between (src_switch, dst_switch) | 499 # Find best path between (src_switch, dst_switch) |
443 best_path = self._find_best_path(dpid, dst_switch) | 500 # best_path = self._find_best_path(dpid, dst_switch) |
| 501 print datapath.id, in_port, pkt_ipv4.src, pkt_ipv4.dst |
| 502 best_path = self._fib_lookup(dpid, dst_switch) |
444 | 503 |
445 # Installing flows from A >> Z | 504 # Installing flows from A >> Z |
446 self._add_network_uni_flow(best_path, pkt, msg.buffer_id) | 505 self._add_network_uni_flow(best_path, pkt, msg.buffer_id) |
LEFT | RIGHT |