def install_path_flow(out_port, duration=FLOW_INSTALL_DURATION): """ Instala un flujo selectivo a partir de un path """ log.info( "SWITCH_%s: Instalando flujo a partir de path %s@PUERTO_%i -> %s@PUERTO_%i de tipo %s" % (self.switch_id, src_mac, in_port, dst_mac, out_port, pkt_type_name)) msg = of.ofp_flow_mod() if udp_pkt: udp_dst_port = udp_pkt.dstport msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=UDP_nw_proto, tp_dst=udp_dst_port) elif tcp_pkt: tcp_dst_port = tcp_pkt.dstport msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=TCP_nw_proto, tp_dst=tcp_dst_port) else: msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=ICMP_nw_proto) str_dst_ip = ip_pkt.dstip msg.match.set_nw_dst(str_dst_ip, 32) msg.idle_timeout = duration msg.hard_timeout = duration msg.actions.append(of.ofp_action_output(port=out_port)) msg.data = packet_in # OFPFF_SEND_FLOW_REM indica al switch que debe notificar al controlador cuando un flujo haya sido dado de baja. Ver funcion handle_flow_removed # OFPFF_CHECK_OVERLAP pide al switch que verifique overlap de reglas de flujo msg.flags = of.OFPFF_SEND_FLOW_REM + of.OFPFF_CHECK_OVERLAP self.connection.send(msg)
def remove_connection(self, conn): dpid_removed = conn.dpid log.info("ConnectionDown message for Switch with DPID {}".format(dpid_removed)) self.dpid_dict.pop( dpid_removed , None) self.hosts = {k: v for k, v in self.hosts.iteritems() if v[0] != dpid_removed}
def _handle_LinkEvent(self, event): link = event.link dpid_source = link.dpid1 dpid_dest = link.dpid2 port_source = link.port1 port_dest = link.port2 if event.added: log.info( "Link added for switches S-{}:port-{} --> S-{}:port-{}".format( dpid_source, port_source, dpid_dest, port_dest)) self.dpid_dict[dpid_source]["links"].append(link) # Update our graph with new nodes/edges self.graph.add_edge(dpid_source, dpid_dest, ports={ dpid_source: port_source, dpid_dest: port_dest }) log.debug("Graph Edges") log.debug(self.graph.edges()) else: log.debug( "Link removed for switches S-{}:port-{} --> S-{}:port-{}". format(dpid_source, port_source, dpid_dest, port_dest))
def handle_flow_removed(event): """ Listener que maneja eliminaciones de flujos en switches. Escucha eventos tipo FlowRemoved """ switch_id = event.connection.dpid match = event.ofp.match packet_count = event.ofp.packet_count if is_udp(match): dst_ip = match.get_nw_dst( ) # Tupla IP , bits_mascara. Ejemplo: (IPAddr('10.0.0.2'), 32) log.info( 'SWITCH_%s: FLUJO REMOVIDO DE TIPO UDP CON DESTINO IP %s . packet_count: %s', switch_id, str(dst_ip[0]), packet_count) if packet_count > UDP_FIREWALL_THRESHOLD: # Si la cantidad de paquetes UDP supera el THRESHOLD establecido -> instalo un blackhole firewall blackhole_udp_packets(switch_id, FIREWALL_DURATION, dst_ip) else: # Si la cantidad de paquetes UDP para un destino baja luego de un tiempo, entonces se lo quita del blacklist de destinos bloqueados str_dst_ip = str(dst_ip[0]) switch = switches[switch_id] switch.remove_firewall_ip(str_dst_ip) elif is_icmp(match): log.debug('SWITCH_%s: FLUJO REMOVIDO DE TIPO ICMP . packet_count: %s', switch_id, packet_count) elif is_tcp(match): dst_ip = match.get_nw_dst( ) # Tupla IP , bits_mascara. Ejemplo: (IPAddr('10.0.0.2'), 32) log.debug( 'SWITCH_%s: FLUJO REMOVIDO DE TIPO TCP CON DESTINO IP %s . packet_count: %s', switch_id, dst_ip, packet_count) else: log.debug('SWITCH_%s: FLUJO REMOVIDO packet_count: %s', switch_id, packet_count)
def handle_flow_stats(event): """ Listener que muestra datos estadisticos de un switch. Captura eventos FlowStatsReceived """ switch_id = event.connection.dpid all_stats = { "tcp": { "packet_count": 0, "byte_count": 0 }, "udp": { "packet_count": 0, "byte_count": 0 }, "icmp": { "packet_count": 0, "byte_count": 0 } } for f in event.stats: if is_udp(f.match): all_stats["udp"]["packet_count"] += f.packet_count all_stats["udp"]["byte_count"] += f.byte_count if is_tcp(f.match): all_stats["tcp"]["packet_count"] += f.packet_count all_stats["tcp"]["byte_count"] += f.byte_count if is_icmp(f.match): all_stats["icmp"]["packet_count"] += f.packet_count all_stats["icmp"]["byte_count"] += f.byte_count log.info("SWITCH_%s stats: %s", switch_id, all_stats)
def remove_taken_path(): log.info("SWITCH_%s: ELIMINANDO PATH %s DE FLUJO %s", self.switch_id, path_str, flow_key) if path_str in taken_paths: taken_paths.remove(path_str) if flow_key in current_paths: current_paths.pop(flow_key) current_paths_load[path_str] -= 1
def launch (): import pox.log.color pox.log.color.launch() import pox.log pox.log.launch(format="[@@@bold@@@level%(name)-22s@@@reset] " + "@@@bold%(message)s@@@normal") core.openflow.addListenerByName("PacketIn", _handle_PacketIn) log.info("Pong component running.")
def build_flow_key(): """ Crea la clave de un flujo a partir de campos disponibles del paquete procesado """ flow_key = pkt_type_name + '#' if ip_pkt: flow_key += str(ip_pkt.srcip) + 'to' + str(ip_pkt.dstip) if tcp_pkt: flow_key += ':' + str(tcp_pkt.dstport) + "-" + str(tcp_pkt.ACK) # TODO ... PARA UDP SE DEBEN CONSTRUIR FLUJOS DISTINTOS PARA LA IDA Y LA VUELTA... CONSIDERAR USAR LA MAC if udp_pkt: flow_key += ':' + str(udp_pkt.dstport) if icmp_pkt: flow_key += '-' + str(icmp_pkt.type) log.info("SWITCH_%s: flow_key = %s", self.switch_id, flow_key) return flow_key
def handle_udp(): """ Maneja paquetes UDP. Si detecta que un destino esta bloqueado por un firewall, descarta el paquete """ dstip = ip_pkt.dstip str_dst_ip = str(dstip) if str_dst_ip in self.firewall_ips: log.info( 'SWITCH_%s PAQUETES CON DESTINO %s SIGUEN BLOQUEADOS... REALIZANDO DROP', self.switch_id, str_dst_ip) drop() return dstport = udp_pkt.dstport if dstport == DHCP_PORT: return handle_dhcp() handle_ip()
def add_connection(self, conn): dpid_joined = conn.dpid log.info("ConnectionUp message for Switch with DPID {}".format(dpid_joined)) conn.addListeners(self) self.dpid_dict.update({dpid_joined : {}}) self.dpid_dict[dpid_joined].update({"connection" : conn}) self.dpid_dict[dpid_joined].update({"mac_to_port" : {}}) self.dpid_dict[dpid_joined].update({"links" : []}) self.dpid_dict[dpid_joined].update({"hosts" : []})
def __init__(self, connection, dpid): # Guarda la conexion con el switch self.connection = connection # guardo el id del switch y un sinonimo del mismo self.switch_id = dpid self.dpid = dpid log.info("SWITCH %s CONECTADO" % self.switch_id) # TABLA Mac -> puerto_salida_switch self.mac_to_port = {} # Agrego listeners de conexion (como PacketIn) self.connection.addListeners(self) switches[dpid] = self # Cada switch tiene su propia BLACKLIST de firewalled-ips self.firewall_ips = set()
def __init__(self): log.info( "Initializing acn controller. Registering listeners for discovery module!" ) # Add listeners for LinkEvent published by the # openflow_discovery module. core.openflow_discovery.addListeners(self) # Dictionary that keeps connection/links/hosts/mac_to_port # and possibly name of the switch. This dictionary holds # whatever state we need to store per switch # switches are recognized with their datapath-ids # dpid { hosts : {}, # links : {}, # connection : {}, # mac_to_port: {} # } self.dpid_dict = {} # Hosts observed in the networks (IPs), for each # host only keep the dpid which connects on and the # port of the dpit that connects on. self.hosts = {} # contains the implemented policies for the controller # Policies are of the form: # (src_ip, dest_ip, dpid) # where: src_ip: The source host IP # dst_ip: The destination host IP # dpid : The dpid of the switch the traffic has to pass through # # The dictionary maps a hash (src_ip, dst_ip) to the dpid of the switch self.policies = {} # Network graph self.graph = nx.Graph() # Parses policies from files and returns a dictionary as described above # Sanity is assumed regarding the contents of the file (no checks). with open('policies.txt', 'r') as f: for line in f: if line[0] == '#' or line.strip() == '': continue src_ip, dst_ip, dpid = line.split() log.info("Parsed policy {} -> {} via {}".format( src_ip, dst_ip, dpid)) self.policies[(src_ip, dst_ip)] = int(dpid)
def install_flow(out_port, duration=FLOW_INSTALL_DURATION): """ Instala un flujo en el switch del tipo MAC_ORIGEN@PUERTO_ENTRADA -> MAC_DESTINO@PUERTO_SALIDA """ if not pkt_is_arp: log.info( "SWITCH_%s: Instale un flujo de %s@PUERTO_%i hacia %s@PUERTO_%i de tipo %s" % (self.switch_id, src_mac, in_port, dst_mac, out_port, pkt_type_name)) msg = of.ofp_flow_mod() msg.match = of.ofp_match.from_packet(packet, in_port) msg.idle_timeout = duration msg.hard_timeout = duration msg.actions.append(of.ofp_action_output(port=out_port)) msg.data = packet_in # OFPFF_SEND_FLOW_REM indica al switch que debe notificar al controlador cuando un flujo haya sido dado de baja. Ver funcion handle_flow_removed # OFPFF_CHECK_OVERLAP pide al switch que verifique overlap de reglas de flujo msg.flags = of.OFPFF_SEND_FLOW_REM + of.OFPFF_CHECK_OVERLAP self.connection.send(msg)
def show_stats(event): """ Create stats on h1 incoming/outoging traffic 3. Extend this program to count all traffic going to or leaving host 1 """ normal_flows = 0 normal_packets = 0 normal_bytes = 0 for flow in event.stats: # Check if ip addresses match the blocks normal_flows += 1 normal_packets += flow.packet_count normal_bytes += flow.byte_count log.info( " ==> {4} ALL TRAFFIC [dpid={0}]: {1} bytes / {2} packets / {3} flows" .format(dpidToStr(event.connection.dpid), normal_bytes, normal_packets, normal_flows, str(datetime.now())))
def handle_host_tracker_HostEvent(event): """ Listener de eventos tipo HOST NUEVO CONECTADO """ host_mac = str(event.entry.macaddr) switch_id = event.entry.dpid switch_port = event.entry.port # Supuesto codigo para obtener la ip de un host inmediatamente... no funciona #ipaddr_keys = event.entry.ipAddrs.keys() #ip = None #if len(ipaddr_keys) > 0 : # ip = ipaddr_keys[0].toStr() if host_mac not in hosts: hosts[host_mac] = {"switch_id": switch_id, "switch_port": switch_port} if switch_id in switches: log.info('NUEVO HOST %s CON SWITCH_%s@PORT_%s', host_mac, switch_id, switch_port) else: log.warn("Missing switch")
def show_stats(event): """ Create stats on h1 incoming/outoging traffic 3. Extend this program to count all traffic going to or leaving host 1 """ normal_flows = 0 normal_packets = 0 normal_bytes = 0 for flow in event.stats: # Check if ip addresses match the blocks normal_flows += 1 normal_packets += flow.packet_count normal_bytes += flow.byte_count log.info(" ==> {4} ALL TRAFFIC [dpid={0}]: {1} bytes / {2} packets / {3} flows".format( dpidToStr(event.connection.dpid), normal_bytes, normal_packets, normal_flows, str(datetime.now()) ))
def blackhole_udp_packets(switch_id, duration, udp_dst_ip, udp_dst_port=None): """ Instala un flujo de dopeo de paquetes UDP para un destino determinado """ # NOTA: ESTA FUNCION INSTALA UN FIREWALL TIPO BLACKHOLE EN UN SOLO SWITCH... SE DEBE CONSIDERAR SI ACASO EL FIREWALL DEBE INSTALARSE EN TODOS LOS SWITCHES AL MISMO TIEMPO... str_dst_ip = str(udp_dst_ip[0]) switch = switches[switch_id] switch.add_firewall_ip(str_dst_ip) log.info('SWITCH_%s: INSTALANDO FIREWALL DE PAQUETES CON DESTINO %s ', switch_id, str_dst_ip) msg = of.ofp_flow_mod() if USE_UDP_PORT_FOR_FIREWALL: msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=UDP_nw_proto, tp_dst=udp_dst_port) else: msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=UDP_nw_proto) msg.match.set_nw_dst(str_dst_ip, udp_dst_ip[1]) msg.idle_timeout = duration msg.hard_timeout = duration msg.actions.append( of.ofp_action_output(port=of.OFPP_NONE)) # Enviar el paquete a la NADA msg.flags = of.OFPFF_SEND_FLOW_REM # Generar evento FlowRemoved luego de que el flujo sea removido switches[switch_id].connection.send(msg)
def __init__(self): log.info("Initializing acn controller. Registering listeners for discovery module!") # add listeners for LinkEvent published by the # openflow_discovery module. core.openflow_discovery.addListeners(self) # dictionary that keeps connection/links/hosts/mac_to_port # and possibly name of the switch. This dictionary holds # whatever state we need to store per switch # switches are recognized with their datapath-ids # dpid { hosts : {}, # links : {}, # connection : {}, # mac_to_port: {} # } self.dpid_dict = {} # hosts observed in the networks (IPs), for each # host only keep the dpid which connects on and the # port of the dpit that connects on. self.hosts = {}
def launch(flow_duration=10, udp_fwall_pkts=100, fwall_duration=10): pox.log.color.launch() pox.log.launch(format="[@@@bold@@@level%(name)-22s@@@reset] " + "@@@bold%(message)s@@@normal") # Los parametros de configuracion pueden pasarse como --nombre_parametro=VALOR inmediatamente luego del nombre de ESTE MODULO. global FLOW_INSTALL_DURATION FLOW_INSTALL_DURATION = int(flow_duration) log.info("DURACION DE FLUJOS: %s SEGUNDOS", FLOW_INSTALL_DURATION) global UDP_FIREWALL_THRESHOLD UDP_FIREWALL_THRESHOLD = int(udp_fwall_pkts) log.info("CANTIDAD DE PAQUETES UDP LIMITE P/FIREWALL: %s PAQUETES", UDP_FIREWALL_THRESHOLD) # Duracion base del firewall. Se renueva cada FIREWALL_DURATION segundos (si es necesario). global FIREWALL_DURATION FIREWALL_DURATION = int(fwall_duration) log.info("DURACION DEL FIREWALL: %s SEGUNDOS", FIREWALL_DURATION) pox.openflow.discovery.launch() # no_flood: If True, we set ports down when a switch connects # hold_down: If True, don't allow turning off flood bits until a complete discovery cycle should have completed (mostly makes sense with _noflood_by_default). pox.openflow.spanning_tree.launch(no_flood=True, hold_down=True) # --arpAware=15 --arpSilent=45 --arpReply=1 --entryMove=4 host_tracker.launch(arpAware=15, arpSilent=45, entryMove=4) core.registerNew(ZgnFattreeController) # Estas lineas de abajo exponen las variables adj y switch_ids al modulo interactivo de pox 'PY' core.Interactive.variables['adj'] = adj core.Interactive.variables['switch_ids'] = switch_ids core.Interactive.variables['switches'] = switches core.Interactive.variables['stats'] = request_flow_stats core.Interactive.variables['all_stats'] = request_all_flow_stats core.Interactive.variables['hosts'] = hosts core.Interactive.variables['mac_entries'] = get_host_tracker_entries core.Interactive.variables['host_ip'] = get_host_ip core.Interactive.variables['host_mac'] = get_host_mac core.Interactive.variables['find_switch_path'] = find_switch_path core.Interactive.variables[ 'get_switch_switch_link'] = get_switch_switch_link core.Interactive.variables['set_ip_complex'] = set_ip_complex core.Interactive.variables['taken_paths'] = taken_paths core.Interactive.variables['current_paths'] = current_paths core.Interactive.variables['current_paths_load'] = current_paths_load core.Interactive.variables[ 'set_udp_firewall_thresh'] = set_udp_firewall_thresh
def setup_logging(test_mode=False, log_file=None, **kw): """ Launch and set parameters for logging. :param test_mode: use test mode logging (default: False) :type test_mode: bool :param log_file: log file path :type log_file: str :param kw: additional parameters for POX's logger :type kw: dict :return: None """ # Enable logging in specific logging level level.launch(**kw) # Launch colorful logging color.launch() if test_mode: # Define logger for test mode pox.log.launch(format=TEST_LOGGER_FORMAT) log.info("Setup Logger - formatter: %s, level: %s" % (pox.log.launch.__module__, logging.getLevelName(log.getEffectiveLevel()))) # Set default log_file to log in file in test mode else: # Define default logger pox.log.launch(format=DEFAULT_LOGGER_FORMAT) log.info("Setup logger - formatter: %s, level: %s" % (setup_logging.__module__, logging.getLevelName(log.getEffectiveLevel()))) log_file = log_file if log_file is not None else LOG_FILE if log_file: # Define additional logger for logging to file pox.log.launch(format=FILE_LOGGER_FORMAT, file=log_file + ',w') log.info("Setup Logger - formatter: %s, level: %s, file: %s" % (pox.log.launch.__module__, logging.getLevelName(log.getEffectiveLevel()), log_file))
def handle_ip_complex(): """ Maneja paquetes tipo ip """ dst_mac_str = str(dst_mac) # obtengo el string de mac destino log.info("SWITCH_%s: Mac destino es %s", self.switch_id, dst_mac_str) # si el host destino es desconocido, entonces me falta conocer a mas hosts y manejo el paquete como un switch bobo if dst_mac_str not in hosts: return handle_all() host_switch_port = get_host_switch_port(dst_mac_str, self.switch_id) # si la mac destino es de un host y este switch esta directamente conectado al mismo, entonces instalo un flujo inmediatamente if host_switch_port is not None: log.info( 'SWITCH_%s: La Mac destino %s corresponde a un host conectado a MI puerto %d!', self.switch_id, dst_mac_str, host_switch_port) return install_flow(host_switch_port) # TODOOOOOOOOOO : VERIFICAR SI ACASO SE DEBE USAR install_flow EN VEZ DE install_path_flow # verifico si ya existe un path asignado a este flujo flow_key = build_flow_key() if flow_key in current_paths: path = current_paths[flow_key] log.info('SWITCH_%s: el path %s esta asignado al flow_key %s', self.switch_id, str(path), flow_key) # instalo un flujo para forwardear el paquete switch_switch_link = get_switch_switch_link( self.switch_id, path) if switch_switch_link is not None: out_port = switch_switch_link.port1 log.info( "SWITCH_%s: El paquete debe salir por mi puerto %d", self.switch_id, out_port) return install_flow(out_port) #return install_path_flow(out_port) else: log.warn( 'SWITCH_%s: encontre un path... pero yo no tengo enlace ALGO ESTA MAL', self.switch_id) return handle_all() # si llegue a este punto es porque no hay un path asignado al camino indicado... probablemente este switch es de borde # debo solicitar un camino libre y asignarlo host = get_host_by_mac(dst_mac) if host is not None: end_switch_id = host[ 'switch_id'] # obtengo el id del switch al cual esta conectado el host destino # busco o bien un camino libre o cualquier camino en caso de no existir ninguno libre log.info("SWITCH_%s: Busco un path hacia switch %s", self.switch_id, end_switch_id) path = find_non_taken_path(self.switch_id, end_switch_id) if path is None: path = find_any_path(self.switch_id, end_switch_id) path_str = str(path) log.info( "SWITCH_%s: Voy a usar el path %s y se lo asigno al flujo %s", self.switch_id, path_str, flow_key) # guardo la asociacion entre la clave del flujo y el path encontrado current_paths[flow_key] = path # marco al path encontrado como TOMADO taken_paths.add(path_str) # incremento la cantidad de veces que el camino esta siendo usado current_paths_load[path_str] += 1 # instalo un flujo para forwardear el paquete switch_switch_link = get_switch_switch_link( self.switch_id, path) if switch_switch_link is not None: out_port = switch_switch_link.port1 install_flow(out_port) #return install_path_flow(out_port) def remove_taken_path(): log.info("SWITCH_%s: ELIMINANDO PATH %s DE FLUJO %s", self.switch_id, path_str, flow_key) if path_str in taken_paths: taken_paths.remove(path_str) if flow_key in current_paths: current_paths.pop(flow_key) current_paths_load[path_str] -= 1 # despues de un tiempo elimino el path de flujo instalado Timer(FLOW_INSTALL_DURATION, remove_taken_path) return True else: log.warn( 'SWITCH_%s: encontre un path... pero yo no tengo enlace ALGO ESTA MAL', self.switch_id) return handle_all() # condicion fallback ... manejo el paquete como puedo handle_all()
def remove_firewall_ip(self, ip_str): """ Elimina un ip string del set de ips bloqueads x firewall """ if ip_str in self.firewall_ips: log.info("SWITCH_%s: QUITANDO %s DE LISTA NEGRA DE IPs bloqueadas", self.switch_id, ip_str) self.firewall_ips.remove(ip_str)
def policy_controller(self, dpid, packet, packet_in): connection = self.dpid_dict[dpid]["connection"] mac_to_port = self.dpid_dict[dpid]["mac_to_port"] # if protocol is IP then implement policies # for all other traffic etc. ARP implement l2 switch if packet.type == packet.IP_TYPE: ip_packet = packet.payload src_ip = ip_packet.srcip dst_ip = ip_packet.dstip hosts = self.dpid_dict[dpid]["hosts"] links = self.dpid_dict[dpid]["links"] # This will be the path of switches that will be chosen chosen_path = [] src_switch, src_port = self.hosts[src_ip] dst_switch, dst_port = self.hosts[dst_ip] # Check to see if we have to apply policy if (src_ip.toStr(), dst_ip.toStr()) in self.policies: policy_dpid = self.policies[src_ip.toStr(), dst_ip.toStr()] log.debug( "Have to implement policy from {} to {} through switch {}". format(src_ip.toStr(), dst_ip.toStr(), policy_dpid)) # Get all simple paths between the switches connected to the hosts simple_paths = nx.all_simple_paths(self.graph, src_switch, dst_switch) for simple_path in simple_paths: if policy_dpid in simple_path: chosen_path = simple_path break if chosen_path == []: log.info( "Could not satisfy policy. Choosing shortest path!") chosen_path = nx.shortest_path(self.graph, src_switch, dst_switch) else: log.debug("Have to use shortest path!") chosen_path = nx.shortest_path(self.graph, src_switch, dst_switch) log.debug("Will use path:") log.debug(chosen_path) chosen_edges = [(chosen_path[i], chosen_path[i + 1]) for i in xrange(0, len(chosen_path) - 1)] edge_path = [] switch_order = chosen_path for edge in chosen_edges: port1 = self.graph[edge[0]][edge[1]]['ports'][edge[0]] port2 = self.graph[edge[0]][edge[1]]['ports'][edge[1]] edge_path.append((port1, port2)) edge_path.insert(0, (1, src_port)) edge_path.append((dst_port, 1)) # Install IP rules for all the switches in the path for i in xrange(0, len(edge_path) - 1): switch_dpid = switch_order[i] switch_connection = self.dpid_dict[switch_dpid]["connection"] src_port, dst_port = (edge_path[i][1], edge_path[i + 1][0]) log.debug( "Installing policy on switch {}. {} -> {} with ports {} -> {}" .format(switch_dpid, src_ip, dst_ip, src_port, dst_port)) self.install_ip_policy(switch_connection, switch_dpid, 1000, src_port, src_ip, dst_ip, dst_port, 1000) self.resend_packet(connection, packet_in, edge_path[1][0]) else: # Packet is not IP self.learning_microflow_controller(dpid, packet, packet_in)
def _handle_PacketIn(self, event): packet_in = event.ofp # objeto EVENTO de tipo PACKET_IN. packet = event.parsed src_mac = packet.src # MAC origen del paquete dst_mac = packet.dst # MAC destino del paquete in_port = packet_in.in_port # puerto de switch por donde ingreso el paquete # guardo la asociacion mac_origen -> puerto_entrada log.debug('SWITCH_%s: Asociando MAC %s a puerto de entrada %s', self.switch_id, src_mac, in_port) self.mac_to_port[src_mac] = in_port eth_getNameForType = pkt.ETHERNET.ethernet.getNameForType(packet.type) # Parseo tempranamente los tipos de datos conocidos pkt_is_ipv6 = eth_getNameForType == 'IPV6' icmp_pkt = packet.find('icmp') tcp_pkt = packet.find('tcp') udp_pkt = packet.find('udp') pkt_is_arp = packet.type == packet.ARP_TYPE ip_pkt = packet.find('ipv4') # Obtengo el nombre 'imprimible' del paquete pkt_type_name = eth_getNameForType if icmp_pkt: pkt_type_name = 'ICMP' if tcp_pkt: pkt_type_name = 'TCP' if udp_pkt: pkt_type_name = 'UDP' if pkt_is_arp: pkt_type_name = 'ARP' def build_flow_key(): """ Crea la clave de un flujo a partir de campos disponibles del paquete procesado """ flow_key = pkt_type_name + '#' if ip_pkt: flow_key += str(ip_pkt.srcip) + 'to' + str(ip_pkt.dstip) if tcp_pkt: flow_key += ':' + str(tcp_pkt.dstport) + "-" + str(tcp_pkt.ACK) # TODO ... PARA UDP SE DEBEN CONSTRUIR FLUJOS DISTINTOS PARA LA IDA Y LA VUELTA... CONSIDERAR USAR LA MAC if udp_pkt: flow_key += ':' + str(udp_pkt.dstport) if icmp_pkt: flow_key += '-' + str(icmp_pkt.type) log.info("SWITCH_%s: flow_key = %s", self.switch_id, flow_key) return flow_key def install_flow(out_port, duration=FLOW_INSTALL_DURATION): """ Instala un flujo en el switch del tipo MAC_ORIGEN@PUERTO_ENTRADA -> MAC_DESTINO@PUERTO_SALIDA """ if not pkt_is_arp: log.info( "SWITCH_%s: Instale un flujo de %s@PUERTO_%i hacia %s@PUERTO_%i de tipo %s" % (self.switch_id, src_mac, in_port, dst_mac, out_port, pkt_type_name)) msg = of.ofp_flow_mod() msg.match = of.ofp_match.from_packet(packet, in_port) msg.idle_timeout = duration msg.hard_timeout = duration msg.actions.append(of.ofp_action_output(port=out_port)) msg.data = packet_in # OFPFF_SEND_FLOW_REM indica al switch que debe notificar al controlador cuando un flujo haya sido dado de baja. Ver funcion handle_flow_removed # OFPFF_CHECK_OVERLAP pide al switch que verifique overlap de reglas de flujo msg.flags = of.OFPFF_SEND_FLOW_REM + of.OFPFF_CHECK_OVERLAP self.connection.send(msg) def drop(duration=None): """ Dropea el paquete y opcionalmente instala un flujo en el switch para que siga dropeando paquetes de este tipo. NOTA: PODRIA USARSE PARA EL FIREWALL """ if duration is not None: if not isinstance(duration, tuple): duration = (duration, duration) msg = of.ofp_flow_mod() msg.match = of.ofp_match.from_packet(packet) msg.idle_timeout = duration[0] msg.hard_timeout = duration[1] msg.buffer_id = packet_in.buffer_id msg.actions.append(of.ofp_action_output( port=of.OFPP_NONE)) # Enviar el paquete a la NADA self.connection.send(msg) elif packet_in.buffer_id is not None: msg = of.ofp_packet_out() msg.buffer_id = packet_in.buffer_id msg.in_port = in_port msg.actions.append(of.ofp_action_output( port=of.OFPP_NONE)) # Enviar el paquete a la NADA self.connection.send(msg) def flood(): """ Hace un flood de los paquetes UNICAMENTE por los puertos habilitados por SPT """ msg = of.ofp_packet_out() # se crea el mensaje de flood time_diff = time.time() - self.connection.connect_time flood_ok = time_diff >= _flood_delay if flood_ok: # Realizar flood solo despues de que venza el tiempo de prevencion de flood log.debug("SWITCH_%i: FLOOD %s -> %s", self.switch_id, src_mac, dst_mac) # Hacemos flood por todos los puertos excepto los bloqueados por el SPT msg.actions.append(of.ofp_action_output(port=of.OFPP_FLOOD)) else: log.debug("ESPERANDO FLOOD DE SWITCH %s , RESTAN %d SEGUNDOS" % (self.switch_id, int(_flood_delay - time_diff))) msg.data = packet_in msg.in_port = in_port self.connection.send(msg) def handle_all(): """ Maneja los paquetes de forma generica """ # TODO : MODIFICAR ESTE COMPORTAMIENTO PARA SOPORTAR ECMP # NOTA : dado que instalar un flujo demora tiempo, handle_all se esta llamando multiples veces... # si el puerto de salida se encuentra en la tabla de MACs entonces instalo un flujo en el switch if dst_mac in self.mac_to_port: out_port = self.mac_to_port[dst_mac] install_flow(out_port) else: flood() def handle_dhcp(): """ Maneja paquetes DHCP ... Pensar si acaso deberian dropearse... """ dstip = ip_pkt.dstip log.debug('MANEJANDO PAQUETE DHCP HACIA IP %s' % str(dstip)) handle_all() def install_path_flow(out_port, duration=FLOW_INSTALL_DURATION): """ Instala un flujo selectivo a partir de un path """ log.info( "SWITCH_%s: Instalando flujo a partir de path %s@PUERTO_%i -> %s@PUERTO_%i de tipo %s" % (self.switch_id, src_mac, in_port, dst_mac, out_port, pkt_type_name)) msg = of.ofp_flow_mod() if udp_pkt: udp_dst_port = udp_pkt.dstport msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=UDP_nw_proto, tp_dst=udp_dst_port) elif tcp_pkt: tcp_dst_port = tcp_pkt.dstport msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=TCP_nw_proto, tp_dst=tcp_dst_port) else: msg.match = of.ofp_match(dl_type=IP_dl_type, nw_proto=ICMP_nw_proto) str_dst_ip = ip_pkt.dstip msg.match.set_nw_dst(str_dst_ip, 32) msg.idle_timeout = duration msg.hard_timeout = duration msg.actions.append(of.ofp_action_output(port=out_port)) msg.data = packet_in # OFPFF_SEND_FLOW_REM indica al switch que debe notificar al controlador cuando un flujo haya sido dado de baja. Ver funcion handle_flow_removed # OFPFF_CHECK_OVERLAP pide al switch que verifique overlap de reglas de flujo msg.flags = of.OFPFF_SEND_FLOW_REM + of.OFPFF_CHECK_OVERLAP self.connection.send(msg) def handle_ip_complex(): """ Maneja paquetes tipo ip """ dst_mac_str = str(dst_mac) # obtengo el string de mac destino log.info("SWITCH_%s: Mac destino es %s", self.switch_id, dst_mac_str) # si el host destino es desconocido, entonces me falta conocer a mas hosts y manejo el paquete como un switch bobo if dst_mac_str not in hosts: return handle_all() host_switch_port = get_host_switch_port(dst_mac_str, self.switch_id) # si la mac destino es de un host y este switch esta directamente conectado al mismo, entonces instalo un flujo inmediatamente if host_switch_port is not None: log.info( 'SWITCH_%s: La Mac destino %s corresponde a un host conectado a MI puerto %d!', self.switch_id, dst_mac_str, host_switch_port) return install_flow(host_switch_port) # TODOOOOOOOOOO : VERIFICAR SI ACASO SE DEBE USAR install_flow EN VEZ DE install_path_flow # verifico si ya existe un path asignado a este flujo flow_key = build_flow_key() if flow_key in current_paths: path = current_paths[flow_key] log.info('SWITCH_%s: el path %s esta asignado al flow_key %s', self.switch_id, str(path), flow_key) # instalo un flujo para forwardear el paquete switch_switch_link = get_switch_switch_link( self.switch_id, path) if switch_switch_link is not None: out_port = switch_switch_link.port1 log.info( "SWITCH_%s: El paquete debe salir por mi puerto %d", self.switch_id, out_port) return install_flow(out_port) #return install_path_flow(out_port) else: log.warn( 'SWITCH_%s: encontre un path... pero yo no tengo enlace ALGO ESTA MAL', self.switch_id) return handle_all() # si llegue a este punto es porque no hay un path asignado al camino indicado... probablemente este switch es de borde # debo solicitar un camino libre y asignarlo host = get_host_by_mac(dst_mac) if host is not None: end_switch_id = host[ 'switch_id'] # obtengo el id del switch al cual esta conectado el host destino # busco o bien un camino libre o cualquier camino en caso de no existir ninguno libre log.info("SWITCH_%s: Busco un path hacia switch %s", self.switch_id, end_switch_id) path = find_non_taken_path(self.switch_id, end_switch_id) if path is None: path = find_any_path(self.switch_id, end_switch_id) path_str = str(path) log.info( "SWITCH_%s: Voy a usar el path %s y se lo asigno al flujo %s", self.switch_id, path_str, flow_key) # guardo la asociacion entre la clave del flujo y el path encontrado current_paths[flow_key] = path # marco al path encontrado como TOMADO taken_paths.add(path_str) # incremento la cantidad de veces que el camino esta siendo usado current_paths_load[path_str] += 1 # instalo un flujo para forwardear el paquete switch_switch_link = get_switch_switch_link( self.switch_id, path) if switch_switch_link is not None: out_port = switch_switch_link.port1 install_flow(out_port) #return install_path_flow(out_port) def remove_taken_path(): log.info("SWITCH_%s: ELIMINANDO PATH %s DE FLUJO %s", self.switch_id, path_str, flow_key) if path_str in taken_paths: taken_paths.remove(path_str) if flow_key in current_paths: current_paths.pop(flow_key) current_paths_load[path_str] -= 1 # despues de un tiempo elimino el path de flujo instalado Timer(FLOW_INSTALL_DURATION, remove_taken_path) return True else: log.warn( 'SWITCH_%s: encontre un path... pero yo no tengo enlace ALGO ESTA MAL', self.switch_id) return handle_all() # condicion fallback ... manejo el paquete como puedo handle_all() def handle_ip(): if HANDLE_IP_COMPLEX: return handle_ip_complex() else: return handle_all() def handle_udp(): """ Maneja paquetes UDP. Si detecta que un destino esta bloqueado por un firewall, descarta el paquete """ dstip = ip_pkt.dstip str_dst_ip = str(dstip) if str_dst_ip in self.firewall_ips: log.info( 'SWITCH_%s PAQUETES CON DESTINO %s SIGUEN BLOQUEADOS... REALIZANDO DROP', self.switch_id, str_dst_ip) drop() return dstport = udp_pkt.dstport if dstport == DHCP_PORT: return handle_dhcp() handle_ip() def handle_unknown(): """ Maneja un paquete de tipo desconocido """ log.debug('PAQUETE DESCONOCIDO DETECTADO DE TIPO %s::%s', eth_getNameForType, pkt_type_name) # TODO : VER QUE ES MEJOR , SI MANEJAR LOS PAQUETES DESCONOCIDOS O SI DROPEARLOS drop(30) #handle_all() # LOS PAQUETES DESCONOCIDOS SON DROPEADOS. POR AHORA IGNORAMOS LOS PAQUETES IPV6 # DADO QUE ESTAMOS USANDO host_tracker, DEBEMOS MANEJAR LOS PAQUETES ARP (NO DROPEAR) unknown_pkt = pkt_is_ipv6 or (icmp_pkt is None and tcp_pkt is None and udp_pkt is None and not pkt_is_arp) if unknown_pkt: return handle_unknown() # los paquetes ARP los despacho inmediatamente sin crear ni reservar flujos if pkt_is_arp: return handle_all() log.debug( 'SWITCH_%s@PORT_%d LLEGO PAQUETE TIPO %s::%s MAC_ORIGEN: %s MAC_DESTINO: %s' % (self.switch_id, in_port, eth_getNameForType, pkt_type_name, src_mac, dst_mac)) # por alguna razon bizarra... esta linea rompe # si la mac es ff:ff:ff:ff:ff:ff entonces hago un flood #if dst_mac.is_multicast: flood() # si el mac origen es igual al mac destino entonces dropeo if src_mac == dst_mac: log.info("SWITCH_%s: MAC ORIGEN ES IGUAL A MAC DESTINO!", self.switch_id) drop() if udp_pkt: return handle_udp() if ip_pkt: return handle_ip() handle_all()