def __init__(self, *args, **kwargs): super(SDNTrace, self).__init__(*args, **kwargs) # Configuration File self.config = ConfigReader() # Topology self.switches = Switches() self.links = Links() # Topology Discovery App self.topo_disc = TopologyDiscovery() # Graph Coloring App self.graph_coloring = GraphColoring() # Trace_Manager App self.tracer = TraceManager() print('SDNTrace Ready!')
def _topology_discovery(self): """ Keeps looping Switches() every PACKET_OUT_INTERVAL seconds Send a packet_out w/ LLDP to every port found """ if self.active: vlan = ConfigReader().topo.vlan_discovery while True: # Only send PacketOut + LLDP when more than one switch exists if len(Switches()) > 1: for switch in Switches().get_switches(): for port in switch.ports: pkt = prepare_lldp_packet(switch, port, vlan) switch.send_packet_out(port, pkt.data, lldp=True) hub.sleep(ConfigReader().topo.packet_out_interval)
def switch_info(dpid): """ /sdntrace/switches/00004af7b0f68749/info { "datapath_id": "00004af7b0f68749", "switch_color": "char", "tcp_port": integer, "openflow_version": "string", "switch_vendor": "string", "ip_address": "ip_address", "switch_name": "string", "number_flows": integer, "distance": integer } or {} if not found """ info = dict() # in case user requests before switch appears for switch in Switches().get_switches(): if switch.name == dpid: info = { 'switch_name': switch.switch_name, 'switch_vendor': switch.switch_vendor, 'datapath_id': switch.datapath_id, 'switch_color': switch.color, 'openflow_version': switch.version_name, 'ip_address': switch.addr[0], 'tcp_port': switch.addr[1], 'number_flows': len(switch.flows), 'distance': switch.distance } break return json.dumps(info)
def tracepath(self): """ Do the trace path The logic is very simple: 1 - Generate the probe packet using entries provided 2 - Results a result and the packet_in (used to generate new probe) Possible results: 'timeout' meaning the end of trace or the trace step {'dpid', 'port'} Some networks do vlan rewrite, so it is important to get the packetIn msg with the header 3 - If result is a trace step, send PacketOut to the switch that originated the PacketIn. Repeat till reaching timeout """ print("Starting Trace Path for ID %s" % self.id) entries = self.init_entries color = self.init_switch.color switch = self.init_switch # Add initial trace step self.rest.add_trace_step(self.trace_result, trace_type='starting', dpid=switch.datapath_id, port=entries['trace']['switch']['in_port']) # A loop waiting for trace_ended. It changes to True when reaches timeout while not self.trace_ended: in_port, probe_pkt = generate_trace_pkt(entries, color, self.id, self.mydomain) result, packet_in = self.send_trace_probe(switch, in_port, probe_pkt) if result == 'timeout': self.rest.add_trace_step(self.trace_result, trace_type='last') print("Intra-Domain Trace Completed!") self.trace_ended = True else: self.rest.add_trace_step(self.trace_result, trace_type='trace', dpid=result['dpid'], port=result['port']) if self.check_loop(): self.rest.add_trace_step(self.trace_result, trace_type='last', reason='loop') self.trace_ended = True break # If we got here, that means we need to keep going. # Prepare next packet prepare = prepare_next_packet entries, color, switch = prepare(Switches(), entries, result, packet_in) # Check if the last switch has inter-domain neighbors # if so, infer is current flows go through the interdomain port if switch.is_inter_domain: out_port = switch.match_flow(in_port, probe_pkt) if out_port in switch.inter_domain_ports.keys(): neighbor_conf = switch.inter_domain_ports[out_port] self.trace_interdomain(switch, neighbor_conf.color_value, entries, in_port) # Add final result to trace_results_queue # if inter_domain, add a status to the result, f. i, 'running' t_result = {"request_id": self.id, "result": self.trace_result, "start_time": str(self.rest.start_time), "total_time": self.rest.get_time()} self.trace_mgr.add_result(self.id, t_result)
def get_init_switch(self): """ Returns: """ dpid = self.init_entries['trace']['switch']['dpid'] return Switches().get_switch(dpid, by_name=True)
def _update_topology(self): """ Update topology """ self._topology = {} # Collect all inter-domain info from the configuration file inter_conf = ConfigReader().interdomain.locals inter_names = ConfigReader().interdomain.neighbors # Create a temporary dictionary with all inter-domain ports adding # the remote domain's name to it inter = dict() for node in inter_conf: sw_dpid, sw_port = node.split(':') inter[sw_dpid] = {} for neighbor in inter_names: local = ConfigReader().interdomain.get_local_sw(neighbor) if local == sw_dpid: inter[sw_dpid][sw_port] = { 'type': 'interdomain', 'domain_name': neighbor } # Create the final dictionary with all switches and ports # Uses the inter dict to add inter-domain info. If no inter-domain # is found, assume it is a host port - for now. switches = dict() for switch in Switches().get_switches(): switches[switch.name] = {} for port in switch.ports: try: switches[switch.name][port] = inter[switch.name][str(port)] except (KeyError, ValueError): switches[switch.name][port] = { 'type': 'host', 'host_name': 'no_name' } try: # Now, update the switches dictionary with the link info from the # SDNTrace.links, which is the Links class. for link in Links().links: switches[link.switch_a][link.port_a] = { 'type': 'link', 'neighbor_dpid': link.switch_z, 'neighbor_port': link.port_z } switches[link.switch_z][link.port_z] = { 'type': 'link', 'neighbor_dpid': link.switch_a, 'neighbor_port': link.port_a } except KeyError: pass self._topology = switches
def create_adjacencies(links): """ Everytime Links() is updated, update all adjacencies between switches Args: links: Links() """ for switch in Switches().get_switches(): switch.create_adjacencies(links)
def install_colored_flows(self): """ First define colors for each node Delete old flows Then push new flows """ # TODO: Break this method into smaller ones colors = self.define_colors(Switches().get_switches()) # Compare received colors with self.old_colors # If the same, ignore if colors is not None: self.colors = colors if len(self.old_colors) is 0: self.old_colors = self.colors else: if self.colors == self.old_colors: return # Check all colors in use # For each switch: # 1 - Delete colored flows # 2 - For each switch, check colors of neighbors # 3 - Install all neighbors' colors for switch in Switches().get_switches(): # 1 - Delete old colored flows switch.delete_colored_flows() # 2 - Check colors of all other switches neighbor_colors = [] for color in self.colors: # Get Dict Key. Just one Key for key in color: if key != switch.name: neighbor = Switches().get_switch(key, by_name=True) if neighbor in switch.adjacencies_list: neighbor_colors.append(color[key]) # 3 - Install all colors from other switches # in some cases, if two neighbors have the same color, the same flow # will be installed twice. It is not an issue. for color in neighbor_colors: switch.install_color(color) del neighbor_colors switch.old_color = switch.color self.old_colors = self.colors
def save_current_colors(self): """ Save all current colors If the coloring flow needs to be replaced, it is important to know the last color to use as a match for deleting old flows Just copy current color for old_color variable """ for switch in Switches().get_switches(): switch.old_color = switch.color
def list_flows(dpid): """ /sdntrace/switches/{dpid}/flows { "dpid": "0000000000000001", "number_flows": 5, "flows": [ { "idle_timeout": 0, "cookie": 2000002, "priority": 50001, "hard_timeout": 0, "byte_count": 0, "duration_nsec": 71000000, "packet_count": 0, "duration_sec": 4, "table_id": 0, "match": { "wildcards": 3678458, "dl_src": "ee:ee:ee:11:11:11", "in_port": 1 ... }, "actions | instructions": [ { "max_len": 65509, "type": "OFPActionOutput(0)", "port": 65533 } ... ], } ... ] } or {} if not found """ body = dict() # in case user requests before switch appears flows = list() for switch in Switches().get_switches(): if switch.name == dpid: for flow in sorted(sorted(switch.flows, key=lambda f: f.duration_sec, reverse=True), key=lambda f: f.priority, reverse=True): flow_stats = process_flow_stats(switch, flow) flows.append(flow_stats) # Finished loop, process output final = { "dpid": dpid, "number_flows": len(flows), "flows": flows } body = json.dumps(final) break return body
def __init__(self, trace_manager, r_id, initial_entries): self.switches = Switches() self.trace_mgr = trace_manager self.id = r_id self.init_entries = initial_entries self.trace_result = [] self.trace_ended = False self.init_switch = self.get_init_switch() self.rest = FormatRest() self.config = ConfigReader() # Support for inter-domain self.inter_domain = self.trace_mgr.is_interdomain(self.id) self.mydomain = self.config.interdomain.my_domain
def _push_colors(self): """ This routine will run every PUSH_COLORS_INTERVAL interval and process the Links() to associate colors to OFSwitches. Flows will be pushed to switches with the dl_src field set to the defined color outputting to controller Args: self """ while True: if len(Switches()) > 1: if len(Links()) is not 0: self.install_colored_flows() hub.sleep(ConfigReader().trace.push_color_interval)
def entry_validation(self, entries): """ Make sure the switch selected by the user exists. In fact, this method has to validate all params inputed. Returns: True: all set False: switch requested doesn't exist """ # TODO: improve with more tests dpid = entries['trace']['switch']['dpid'] init_switch = Switches().get_switch(dpid, by_name=True) if not isinstance(init_switch, bool): return True return False
def add_trace_step(self, trace_result, trace_type, reason='done', dpid=None, port=None, msg="none"): """ Used to define the new REST interface. Use docs/trace_results.txt for examples. Only this method should write to self.trace_result Args: trace_result: variable with results trace_type: type of trace reason: reason in case trace_type == last dpid: switch's dpid port: switch's OpenFlow port_no msg: message in case of reason == error """ step = dict() step["type"] = trace_type # Get port name instead of port_no if dpid: new_switch = Switches().get_switch(dpid, by_name=True) try: port_name = new_switch.ports[port]["name"] except: raise Exception('Restart Mininet: port Disappeared ') if trace_type == 'starting': step["dpid"] = new_switch.name step["port"] = port_name step["time"] = str(self.start_time) elif trace_type == 'trace': step["dpid"] = new_switch.name step["port"] = port_name step["time"] = self.get_time() elif trace_type == 'last': step["reason"] = reason step["msg"] = msg step["time"] = self.get_time() elif trace_type == 'intertrace': pass # Add to trace_result array trace_result.append(step)
def switch_ports(dpid): """ { "1": { "speed": "10GB_FD", "name": "s1-eth1", "port_no": 1, "status": "down|up" }, "2": { "speed": "10GB_FD", "name": "s1-eth2", "port_no": 2, "status": "down|up" } } """ body = dict() for switch in Switches().get_switches(): if switch.name == dpid: ports = switch.ports body = json.dumps(ports) break return body
def list_switches(): switches = [switch.name for switch in Switches().get_switches()] return json.dumps(switches)
def list_colors(): colors = {} for switch in Switches().get_switches(): colors[switch.name] = {'color': switch.color, 'old_color': switch.old_color} return json.dumps(colors)
def switch_neighbors(dpid): neighbors = list() for switch in Switches().get_switches(): if switch.name == dpid: neighbors = [neighbor.name for neighbor in switch.adjacencies_list] return json.dumps(neighbors)
class SDNTrace(app_manager.RyuApp): OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION, ofproto_v1_3.OFP_VERSION] def __init__(self, *args, **kwargs): super(SDNTrace, self).__init__(*args, **kwargs) # Configuration File self.config = ConfigReader() # Topology self.switches = Switches() self.links = Links() # Topology Discovery App self.topo_disc = TopologyDiscovery() # Graph Coloring App self.graph_coloring = GraphColoring() # Trace_Manager App self.tracer = TraceManager() print('SDNTrace Ready!') @set_ev_cls(event.EventSwitchEnter) def get_topology_data(self, ev): """ Get switches' IPs and ports. This method is detected after EventOFPSwitchFeatures, so we just update the switch address. Args: ev: EventSwitchEnter received """ self.switches.update_switch_address(ev.switch.dp) @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER) def switch_features_handler(self, ev): """ FeatureReply - For each new switch that connects, add to the switches dictionary. This dict will be used for sending packetOut and generate topology and colors. When instantiating a switch, clears old colored flows and adds the default LLDP flow Args: ev: EventOFPSwitchFeatures received """ self.switches.add_switch(ev) @set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER) def port_status(self, ev): """ Process OFP_Port_Status Add or Remove ports from OFSwitch.ports Args: ev: EventOFPPortStatus received """ switch = self.switches.get_switch(ev.msg.datapath) switch.port_status(ev) @set_ev_cls(ofp_event.EventOFPStateChange, DEAD_DISPATCHER) def remove_switch(self, ev): """ If DEAD_DISPATCHER received, remove switch from self.switches Args: ev: packet captured """ self.switches.del_switch(ev) @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def packet_in_handler(self, ev): """ Process PacketIn PacketIN messages are used for topology discovery and traces Args: ev: EventOFPPacketIn message """ switch = self.switches.get_switch('%016x' % ev.msg.datapath.id, by_name=True) action, result, in_port = switch.process_packetIn(ev) if action is 1: # LLDP self.topo_disc.handle_packet_in_lldp(link=result) elif action is 2: # Trace packets self.tracer.process_probe_packet(ev, result, in_port, switch) @set_ev_cls(ofp_event.EventOFPErrorMsg, MAIN_DISPATCHER) def openflow_error(self, ev): """ Print Error Received. Useful for troubleshooting Args: ev: EventOFPErrorMsg """ print('OFPErrorMsg received: type=0x%02x code=0x%02x message=%s' % (ev.msg.type, ev.msg.code, utils.hex_array(ev.msg.data))) @set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER) def handle_flow_stats(self, ev): """ Process OFPFlowStatsReply saving all the flows associated with a switch. These flows will be used for the inter-domain trace Args: ev: EventOFPFlowStatsReply message """ switch = self.switches.get_switch(ev.msg.datapath) switch.save_flows(flows=ev.msg.body) @set_ev_cls(ofp_event.EventOFPPortDescStatsReply, MAIN_DISPATCHER) def port_desc_stats_reply_handler(self, ev): """ Multipart Port Stats Description Only used for OF1.3 Args: ev: EventOFPPortDescStatsReply received """ switch = self.switches.get_switch(ev.msg.datapath) switch.process_port_desc_stats_reply(ev) @set_ev_cls(ofp_event.EventOFPDescStatsReply, MAIN_DISPATCHER) def description_stats_reply_handler(self, ev): """ Multipart Description Stats Description Only used for OF1.3 Args: ev: EventOFPDescStatsReply received """ switch = self.switches.get_switch(ev.msg.datapath) switch.process_description_stats_reply(ev) @set_ev_cls(ofp_event.EventOFPEchoReply, MAIN_DISPATCHER) def echo_reply_handler(self, ev): """ Echo Res Message Args: ev: EchoReply message received """ now = float(time.time()) switch = self.switches.get_switch(ev.msg.datapath) switch.process_echo_reply_timestamp(now, ev.msg.data)