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?') |
(...skipping 10 matching lines...) Expand all Loading... |
50 hub.sleep(self.sample_interval) | 51 hub.sleep(self.sample_interval) |
51 #body = self.dm_server.get_data_from_dm('port_counters', 1) | 52 #body = self.dm_server.get_data_from_dm('port_counters', 1) |
52 params_dict = {'module': 'nfm', 'row': 1, 'table': 'port_counters'} | 53 params_dict = {'module': 'nfm', 'row': 1, 'table': 'port_counters'} |
53 body = self.dm_server.send_GET(params_dict) | 54 body = self.dm_server.send_GET(params_dict) |
54 #print body | 55 #print body |
55 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 |
56 print 'No NFM monitoring data received from DM' | 57 print 'No NFM monitoring data received from DM' |
57 continue | 58 continue |
58 # print body | 59 # print body |
59 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)] |
60 | 74 |
61 def _calculate_link_metric(self, data_dict): | 75 def _calculate_link_metric(self, data_dict): |
62 | 76 |
63 for switch in data_dict['switches']: | 77 for switch in data_dict['switches']: |
64 # each element is a dictionary as well | 78 # each element is a dictionary as well |
65 for port_counter in switch['port_counters']: | 79 for port_counter in switch['port_counters']: |
66 # each element is a dictionary as well | 80 # each element is a dictionary as well |
67 | 81 |
68 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) |
69 | 87 |
70 self.port_metric[(switch['switch_dpid'], | 88 self.port_metric[(switch['switch_dpid'], |
71 port_counter['port'])] = tx_metric | 89 port_counter['port'])] = tx_metric |
72 | 90 |
73 #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 |
74 | 92 |
75 def _calculate_hop_count(self, paths): | 93 def _calculate_hop_count(self, paths): |
76 paths_dict = {} | 94 paths_dict = {} |
77 | 95 |
78 for path in paths: | 96 for path in paths: |
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
155 self.port_metric[(switchA.dpid, switchA.port_no)] = 0 | 173 self.port_metric[(switchA.dpid, switchA.port_no)] = 0 |
156 #print 'Port Metric', (switchA.dpid, switchA.port_no), 'set to', 0 | 174 #print 'Port Metric', (switchA.dpid, switchA.port_no), 'set to', 0 |
157 | 175 |
158 # 2017-11-16 | 176 # 2017-11-16 |
159 # Inform DM about this link | 177 # Inform DM about this link |
160 new_topo_link_json_obj = {'src_switch': switchA.dpid, | 178 new_topo_link_json_obj = {'src_switch': switchA.dpid, |
161 'src_sw_port': switchA.port_no, | 179 'src_sw_port': switchA.port_no, |
162 'dst_switch': switchB.dpid, | 180 'dst_switch': switchB.dpid, |
163 'dst_sw_port': switchB.port_no, | 181 'dst_sw_port': switchB.port_no, |
164 'module': 'topology', | 182 'module': 'topology', |
165 'event': 'new_link'} | 183 'event': 'link_up', |
| 184 'bandwidth': 100000} |
166 self.dm_server_2.send_POST(new_topo_link_json_obj) | 185 self.dm_server_2.send_POST(new_topo_link_json_obj) |
167 | 186 |
168 | 187 |
169 # https://www.python.org/doc/essays/graphs/ | 188 # https://www.python.org/doc/essays/graphs/ |
170 def _find_all_paths(self, neighbors, start, end, path=[]): | 189 def _find_all_paths(self, neighbors, start, end, path=[]): |
171 path = path + [start] | 190 path = path + [start] |
172 if start == end: | 191 if start == end: |
173 return [path] | 192 return [path] |
174 if start not in neighbors.keys(): | 193 if start not in neighbors.keys(): |
175 return [] | 194 return [] |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
212 best_metric = 100000000000 # Just an Arbitrary high value. | 231 best_metric = 100000000000 # Just an Arbitrary high value. |
213 # Lowest metric means best metric! (0 is the best possible) | 232 # Lowest metric means best metric! (0 is the best possible) |
214 best_path = [] | 233 best_path = [] |
215 for path in all_paths: | 234 for path in all_paths: |
216 composite_metric = w1 * hop_count[path] | 235 composite_metric = w1 * hop_count[path] |
217 composite_metric += w2 * nfm_metric[path] | 236 composite_metric += w2 * nfm_metric[path] |
218 if composite_metric < best_metric: | 237 if composite_metric < best_metric: |
219 best_metric = composite_metric | 238 best_metric = composite_metric |
220 best_path = path | 239 best_path = path |
221 | 240 |
222 print 'BEST PATH between', (src_switch, dst_switch), 'is', best_path | 241 #print 'BEST PATH between', (src_switch, dst_switch), 'is', best_path |
223 print 'with Composite metric', best_metric | 242 #print 'with Composite metric', best_metric |
224 | 243 |
225 return list(best_path) | 244 #return list(best_path) |
| 245 return best_path |
226 | 246 |
227 def _add_network_uni_flow(self, path, pkt, buffer_id=None): | 247 def _add_network_uni_flow(self, path, pkt, buffer_id=None): |
228 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) | 248 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) |
229 ip_src = pkt_ipv4.src | 249 ip_src = pkt_ipv4.src |
230 | 250 |
231 # 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 |
232 # 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 |
233 if len(path) == 1: | 254 if len(path) == 1: |
234 print 'Hosts are on same switch', path[0] | 255 print 'Hosts are on same switch', path[0] |
235 return | 256 return |
236 | 257 |
237 # Install flows from A >> Z in the reverse order, i.e. | 258 # Install flows from A >> Z in the reverse order, i.e. |
238 # from End to Start. | 259 # from End to Start. |
239 path.reverse() | 260 path = path[::-1] # New tuple with reversed order of items |
| 261 |
240 ip_dst = pkt_ipv4.dst | 262 ip_dst = pkt_ipv4.dst |
241 hostZ = self.hosts[ip_dst] # dst host | 263 hostZ = self.hosts[ip_dst] # dst host |
242 out_port = hostZ['port'] | 264 out_port = hostZ['port'] |
243 | 265 |
244 for k, v in enumerate(path[:-1]): | 266 for k, v in enumerate(path[:-1]): |
245 (in_port, new_out_port) = self.neighbor_ports[( | 267 (in_port, new_out_port) = self.neighbor_ports[( |
246 v, path[k + 1])] | 268 v, path[k + 1])] |
247 | 269 |
248 switch = self.switches[v] | 270 switch = self.switches[v] |
249 | 271 |
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
380 pkt_out.add_protocol(ethernet.ethernet( | 402 pkt_out.add_protocol(ethernet.ethernet( |
381 ethertype=pkt_in_ether.ethertype, | 403 ethertype=pkt_in_ether.ethertype, |
382 dst=pkt_in_ether.src, src=src_mac)) | 404 dst=pkt_in_ether.src, src=src_mac)) |
383 pkt_out.add_protocol(arp.arp(opcode=arp.ARP_REPLY, | 405 pkt_out.add_protocol(arp.arp(opcode=arp.ARP_REPLY, |
384 src_mac=src_mac, | 406 src_mac=src_mac, |
385 src_ip=pkt_in_arp.dst_ip, | 407 src_ip=pkt_in_arp.dst_ip, |
386 dst_mac=pkt_in_arp.src_mac, | 408 dst_mac=pkt_in_arp.src_mac, |
387 dst_ip=pkt_in_arp.src_ip)) | 409 dst_ip=pkt_in_arp.src_ip)) |
388 self._send_packet(datapath, in_port, pkt_out) | 410 self._send_packet(datapath, in_port, pkt_out) |
389 | 411 |
390 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: |
391 parser = datapath.ofproto_parser | 437 parser = datapath.ofproto_parser |
392 match = parser.OFPMatch(eth_dst=host_mac) | 438 match = parser.OFPMatch(eth_dst=host_mac) |
393 actions = [parser.OFPActionOutput(switch_port)] | 439 actions = [parser.OFPActionOutput(switch_port)] |
394 self.add_flow(datapath, 50, match, actions) | 440 self.add_flow(datapath, 50, match, actions) |
395 | 441 |
396 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) | 442 @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) |
397 def _packet_in_handler(self, ev): | 443 def _packet_in_handler(self, ev): |
398 # If you hit this you might want to increase | 444 # If you hit this you might want to increase |
399 # the "miss_send_length" of your switch | 445 # the "miss_send_length" of your switch |
400 if ev.msg.msg_len < ev.msg.total_len: | 446 if ev.msg.msg_len < ev.msg.total_len: |
401 self.logger.debug("packet truncated: only %s of %s bytes", | 447 self.logger.debug("packet truncated: only %s of %s bytes", |
402 ev.msg.msg_len, ev.msg.total_len) | 448 ev.msg.msg_len, ev.msg.total_len) |
403 | 449 |
404 msg = ev.msg | 450 msg = ev.msg |
405 datapath = msg.datapath | 451 datapath = msg.datapath |
406 in_port = msg.match['in_port'] | 452 in_port = msg.match['in_port'] |
407 | 453 |
408 pkt = packet.Packet(msg.data) | 454 pkt = packet.Packet(msg.data) |
409 eth = pkt.get_protocols(ethernet.ethernet)[0] | 455 eth = pkt.get_protocols(ethernet.ethernet)[0] |
410 | 456 |
411 if eth.ethertype == ether_types.ETH_TYPE_LLDP: | 457 if eth.ethertype == ether_types.ETH_TYPE_LLDP: |
412 # ignore lldp packet | 458 # ignore lldp packet |
413 return | 459 return |
414 | 460 |
415 print 'PACKET IN >>', datapath.id, in_port | 461 #print 'PACKET IN >>', datapath.id, in_port |
416 self.logger.debug("packet-in %s" % (pkt,)) | 462 self.logger.debug("packet-in %s" % (pkt,)) |
417 | 463 |
418 dpid = datapath.id | 464 dpid = datapath.id |
419 | 465 |
420 # 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}, |
421 # '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}} |
422 | 468 |
423 # Handle ARP Request packets: | 469 # Handle ARP Request packets: |
424 pkt_arp = pkt.get_protocol(arp.arp) | 470 pkt_arp = pkt.get_protocol(arp.arp) |
425 if pkt_arp: | 471 if pkt_arp: |
426 # Inspired by Ryu book page 88 | 472 # Inspired by Ryu book page 88 |
427 if pkt_arp: | 473 if pkt_arp: |
428 if pkt_arp.opcode != arp.ARP_REQUEST: | 474 if pkt_arp.opcode != arp.ARP_REQUEST: |
429 print 'Expected an ARP Request but this is not it.' | 475 print 'Expected an ARP Request but this is not it.' |
430 return | 476 return |
431 | 477 |
432 # Learn new source host: | 478 # Learn new source host: |
433 if pkt_arp.src_ip not in self.hosts: | 479 if pkt_arp.src_ip not in self.hosts: |
434 self.hosts[pkt_arp.src_ip] = { | 480 self._handle_new_host(datapath, in_port, eth.src, pkt_arp.sr
c_ip) |
435 'mac': eth.src, 'switch': dpid, 'port': in_port} | |
436 print "DISCOVERED NEW HOST", pkt_arp.src_ip, self.hosts[pkt_
arp.src_ip] | |
437 # Add flow on this switch, point to NEW HOST: | |
438 self._handle_new_host(datapath, in_port, eth.src) | |
439 return | 481 return |
440 | 482 |
441 # Reply to ARP request: | 483 # Reply to ARP request: |
442 if pkt_arp.dst_ip in self.hosts: | 484 if pkt_arp.dst_ip in self.hosts: |
443 self._handle_arp_request(datapath, in_port, pkt) | 485 self._handle_arp_request(datapath, in_port, pkt) |
444 return | 486 return |
445 | 487 |
446 # Handle IPv4 packets: | 488 # Handle IPv4 packets: |
447 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) | 489 pkt_ipv4 = pkt.get_protocol(ipv4.ipv4) |
448 if not pkt_ipv4: | 490 if not pkt_ipv4: |
449 print 'NOT an IPv4 Packet:' | 491 print 'NOT an IPv4 Packet:' |
450 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,)
) |
451 return | 493 return |
452 else: | 494 else: |
453 # Block all similar future packets for 1 second: | 495 # Block all similar future packets for 1 second: |
454 self._deny_flow(datapath, in_port, pkt) | 496 # self._deny_flow(datapath, in_port, pkt) |
455 | 497 |
456 dst_switch = self.hosts[pkt_ipv4.dst]['switch'] | 498 dst_switch = self.hosts[pkt_ipv4.dst]['switch'] |
457 # Find best path between (src_switch, dst_switch) | 499 # Find best path between (src_switch, dst_switch) |
458 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) |
459 | 503 |
460 # Installing flows from A >> Z | 504 # Installing flows from A >> Z |
461 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 |