Análisis de tablas de enrutamiento, o por qué un ingeniero de red de Python

Hola habr Este es mi primer artículo sobre Habré, y ella nació de una pregunta en uno de los foros profesionales. La pregunta parecía, parafraseando un poco, de la siguiente manera:


  • Hay un conjunto de archivos de texto que contienen la salida de tablas de enrutamiento de varios dispositivos de red;
  • Cada archivo contiene información de un dispositivo;
  • Los dispositivos pueden tener un formato de salida diferente para la tabla de enrutamiento;
  • Según los datos disponibles, previa solicitud, es necesario mostrar la ruta a una subred o dirección IP arbitraria de cada uno de los dispositivos;
  • La salida debe incluir en cada sección de la información de ruta sobre la entrada de la tabla de enrutamiento, a lo largo del cual se enrutará el paquete.

Encontré la tarea interesante y me hice eco de una de mis propias utilidades de red, planeada en el futuro. Por lo tanto, en una tarde libre, pensando en su solución, escribí una implementación de prueba de concepto en Python 2.7 para el formato Cisco IOS, IOS-XE y ASA, que cumple requisitos básicos


En el artículo intentaré reproducir el tren de pensamiento y comentar los puntos principales.
El material está destinado a personas que ya están fundamentalmente familiarizadas con los conceptos básicos de redes y Python.
Todos los interesados ​​bienvenidos a cat!


Descargo de responsabilidad


El autor del artículo, siendo ingeniero de redes, no es un desarrollador profesional. y puede que aún no sepa la diferencia entre una interfaz y una clase abstracta pero siempre abierto a críticas constructivas y comentarios. Comentarios sobre el código, enfoques seleccionados y algoritmos de colegas más experimentados son bienvenidos.


Todo el código en este artículo se distribuye bajo la licencia MIT, incluyendo Se proporciona "tal cual" y no ofrece ningún tipo de garantía.


Aclaración de las condiciones y la elección del algoritmo de solución.


Considerando la introducción, la tarea se puede dividir en dos partes principales: análisis de archivos fuente con tablas de enrutamiento y búsqueda directa de la ruta utilizando datos inicializados.
Tal separación también permitirá, si es necesario, importar rutas desde dispositivos para buscar una ruta arbitrariamente (por ejemplo, a través de SNMP o REST API).


Para mejorar el rendimiento, tiene sentido inicializar archivos una vez cuando se ejecuta el script.
Un analizador de archivos debe reconocer el formato de las tablas de enrutamiento de varios sistemas operativos. A continuación, se considerará una opción para Cisco IOS, IOS-XE y ASA para IPv4. La compatibilidad con otros formatos e IPv6 se puede agregar más adelante.


Como sabe, la elección de una ruta de acuerdo con la tabla de enrutamiento se realiza de acuerdo con el principio de coincidencia con el prefijo de la mayor longitud (coincidencia de prefijo más larga).
Como una de las soluciones para una búsqueda rápida de dicha coincidencia, puede crear un árbol de prefijos a partir de los datos de origen. Como no tenemos restricciones en dependencias externas, detengámonos en el módulo SubnetTree terminado.


Es posible que haya bucles de enrutamiento en la sección de red analizada, deben detectarse y no afectar el script. Además, en cualquiera de los nodos puede que no haya una ruta a la subred deseada. Esto también debe ser considerado.


Si hay VRF en el dispositivo, las tablas de enrutamiento de cada instancia deben almacenarse en archivos separados, ya que desde el punto de vista de la topología son enrutadores separados.


Las limitaciones en el rendimiento de iron para ejecutar el script, el número y el tamaño de las tablas de enrutamiento analizadas no se indican en la condición, pero debe tenerlas en cuenta.
La especificidad de la aplicación sugiere un límite promedio de 1,000,000 de entradas en la tabla de enrutamiento para un dispositivo moderno. En un enrutador con BGP Full View a partir de junio de 2018, en realidad puede haber más de 724,000 rutas.
Según una estimación aproximada y los resultados de las pruebas para el almacenamiento y el procesamiento en memoria de cada 1,000,000 de prefijos, se requerirán aproximadamente 500 MB de RAM. Por lo tanto, una estación de trabajo de productividad promedio con 8 GB de RAM (que aún tenemos en cuenta en 2018) nos permitirá analizar la topología con una capacidad total de hasta 14-16.000.000 de rutas. Es decir, un segmento de aproximadamente 18-20 enrutadores con vista completa en cada uno.
Para la mayoría de los casos, esto es suficiente, pero para redes grandes (como el estrés), debe dividir el análisis en segmentos o transferir la lógica a una base de datos sin memoria.
640 KB es suficiente para todos. Detengámonos en la versión en memoria.


Analizando archivos fuente y seleccionando estructuras de datos


Colocaremos los archivos con las tablas de enrutamiento en un subdirectorio separado y lo convertiremos en una variable:


RT_DIRECTORY = "./routing_tables" 

Para Cisco IOS e IOS-XE, la tabla de enrutamiento podría verse así:


muestre la ruta del ip
 S* 0.0.0.0/0 [1/0] via 10.220.88.1 10.0.0.0/8 is variably subnetted, 2 subnets, 2 masks C 10.220.88.0/24 is directly connected, FastEthernet4 L 10.220.88.20/32 is directly connected, FastEthernet4 1.0.0.0/32 is subnetted, 1 subnets S 1.1.1.1 [1/0] via 212.0.0.1 [1/0] via 192.168.0.1 D EX 10.1.198.0/24 [170/1683712] via 172.16.209.47, 1w2d, Vlan910 [170/1683712] via 172.16.60.33, 1w2d, Vlan60 [170/1683712] via 10.25.20.132, 1w2d, Vlan220 [170/1683712] via 10.25.20.9, 1w2d, Vlan20 4.0.0.0/16 is subnetted, 1 subnets O E2 4.4.0.0 [110/20] via 194.0.0.2, 00:02:00, FastEthernet0/0 5.0.0.0/24 is subnetted, 1 subnets D EX 5.5.5.0 [170/2297856] via 10.0.1.2, 00:12:01, Serial0/0 6.0.0.0/16 is subnetted, 1 subnets B 6.6.0.0 [200/0] via 195.0.0.1, 00:00:04 172.16.0.0/26 is subnetted, 1 subnets i L2 172.16.1.0 [115/10] via 10.0.1.2, Serial0/0 172.20.0.0/32 is subnetted, 3 subnets O 172.20.1.1 [110/11] via 194.0.0.2, 00:05:45, FastEthernet0/0 O 172.20.3.1 [110/11] via 194.0.0.2, 00:05:45, FastEthernet0/0 O 172.20.2.1 [110/11] via 194.0.0.2, 00:05:45, FastEthernet0/0 10.0.0.0/8 is variably subnetted, 5 subnets, 3 masks C 10.0.1.0/24 is directly connected, Serial0/0 D 10.0.5.0/26 [90/2297856] via 10.0.1.2, 00:12:03, Serial0/0 D 10.0.5.64/26 [90/2297856] via 10.0.1.2, 00:12:03, Serial0/0 D 10.0.5.128/26 [90/2297856] via 10.0.1.2, 00:12:03, Serial0/0 D 10.0.5.192/27 [90/2297856] via 10.0.1.2, 00:12:03, Serial0/0 192.168.0.0/32 is subnetted, 1 subnets D 192.168.0.1 [90/2297856] via 10.0.1.2, 00:12:03, Serial0/0 O IA 195.0.0.0/24 [110/11] via 194.0.0.2, 00:05:45, FastEthernet0/0 O E2 212.0.0.0/8 [110/20] via 194.0.0.2, 00:05:35, FastEthernet0/0 C 194.0.0.0/16 is directly connected, FastEthernet0/0 

En Cisco ASA, el formato es similar, pero en lugar de las longitudes de los prefijos, la máscara de subred se muestra en notación decimal:


mostrar ruta
 S 10.1.1.0 255.255.255.0 [3/0] via 10.86.194.1, outside C 10.86.194.0 255.255.254.0 is directly connected, outside S* 0.0.0.0 0.0.0.0 [1/0] via 10.86.194.1, outside 

Como puede ver en el ejemplo, las rutas, a pesar de la gran diversidad, tienen la misma estructura predecible, lo que significa que pueden ser procesadas por expresiones regulares.
Surgen dos grupos globales: rutas locales + conectadas y todo lo demás.
Para complicar un poco las cosas es la posibilidad de tener rutas de varias líneas con un número variable de las próximas esperanzas. Debido a esto, es problemático leer el archivo para procesar línea por línea. Una salida es procesar múltiples coincidencias con un iterador de una expresión regular en un archivo cargado completamente en una variable de texto.


Escribiremos expresiones regulares teniendo en cuenta los requisitos y restricciones enumerados:


 # Local and Connected route strings matching. REGEXP_ROUTE_LOCAL_CONNECTED = re.compile( '^(?P<routeType>[L|C])\s+' + '((?P<ipaddress>\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)' + '\s?' + '(?P<maskOrPrefixLength>(\/\d\d?)?' + '|(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)?))' + '\ is\ directly\ connected\,\ ' + '(?P<interface>\S+)', re.MULTILINE ) # Static and dynamic route strings matching. REGEXP_ROUTE = re.compile( '^(\S\S?\*?\s?\S?\S?)' + '\s+' + '((?P<subnet>\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)' + '\s?' + '(?P<maskOrPrefixLength>(\/\d\d?)?' +'|(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)?))' + '\s*' + '(?P<viaPortion>(?:\n?\s+(\[\d\d?\d?\/\d+\])\s+' + 'via\s+(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)(.*)\n?)+)', re.MULTILINE ) 

Ambas expresiones contienen grupos con nombre para la conveniencia de recuperar datos al buscar coincidencias y mantener el código.
En particular, de cada ruta debe obtener un prefijo ( subred / interfaz y grupos maskOrPrefixLength ) e información sobre a dónde conduce (a través de grupos de puertos / interfaz ).


Como las expresiones tienen en cuenta varias opciones para representar el prefijo, el grupo maskOrPrefixLength puede contener una máscara de subred o una longitud de prefijo. Reduciremos esto a un solo formato durante el procesamiento; nos detendremos en la longitud del prefijo:


 def convert_netmask_to_prefix_length(mask_or_pref): if not mask_or_pref: return "" if re.match("^\/\d\d?$", mask_or_pref): return mask_or_pref if re.match("^\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?$", mask_or_pref): return ( "/" + str(sum([bin(int(x)).count("1") for x in mask_or_pref.split(".")])) ) return "" 

Agregue expresiones regulares para el análisis de línea por línea del siguiente salto desde el grupo viaPortion , verificando el formato de las direcciones IPv4 y la entrada del usuario:


 # Route string VIA portion matching. REGEXP_VIA_PORTION = re.compile( '.*via\s+(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?).*' ) # RegEx template string for IPv4 address matching. REGEXP_IPv4_STR = ( '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))' ) # IPv4 CIDR notation matching in user input. REGEXP_INPUT_IPv4 = re.compile("^" + REGEXP_IPv4_STR + "(\/\d\d?)?$") 

Ahora transferimos la representación de nuestra red a las estructuras de datos de Python.
Los prefijos resultantes del análisis de las tablas de enrutamiento se utilizarán como claves en el árbol de prefijos. El objeto del árbol de prefijos se hereda del módulo SubnetTree .
El resultado de búsqueda por prefijo en el árbol devolverá una lista de la lista de las próximas esperanzas y una representación textual completa de la ruta correspondiente del archivo original.
Además, cree una lista de interfaces locales.
Cada enrutador está representado por un diccionario, en el que ponemos los datos anteriores.


 # Example data structures route_tree = SubnetTree.SubnetTree() route_tree['subnet'] = ((next_hop_1, next_hop_n), raw_route_string) interface_list = ((interface_1, ip_address_1), (interface_n, ip_address_n)) connected_networks = ((interface_1, subnet_1), (interface_n, subnet_n)) router = { 'routing_table': route_tree, 'interface_list': interface_list, 'connected_networks': connected_networks, } 

Hacemos una solicitud a la tabla de enrutamiento en una función separada:


 def route_lookup(destination, router): if destination in router['routing_table']: return router['routing_table'][destination] else: return (None, None) 

A cada enrutador se le asignará un identificador único. Puede asignarlo de muchas maneras, en el ejemplo actual será permisible y claro hacer esto en función del nombre del archivo original.
Como resultado, agregaremos los objetos de enrutador resultantes al diccionario con la clave en forma de este identificador único.


 ROUTERS = { 'router_id_1': router_1, 'router_id_n': router_n, } 

También necesitamos un mecanismo para buscar el siguiente enrutador por la dirección IP del siguiente salto, obtenida de la tabla de enrutamiento durante la búsqueda de ruta. Para hacer esto, creamos otro árbol de prefijos global con claves en forma de direcciones IP de todos los enrutadores conocidos en la topología y hojas devueltas con el identificador del enrutador y el tipo de interfaz.


 # Example GLOBAL_INTERFACE_TREE = SubnetTree.SubnetTree() GLOBAL_INTERFACE_TREE['ip_address'] = (router_id, interface_type) # Returns RouterID by Interface IP address which it belongs to. def get_rid_by_interface_ip(interface_ip): if interface_ip in GLOBAL_INTERFACE_TREE: return GLOBAL_INTERFACE_TREE[interface_ip][0] 

Armemos juntos el analizador para el formato IOS / IOS-XE / ASA. En la entrada, le pasamos la tabla de enrutamiento en forma de texto, en la salida obtenemos el diccionario del enrutador en el formato especificado anteriormente.


 def parse_show_ip_route_ios_like(raw_routing_table): router = {} route_tree = SubnetTree.SubnetTree() interface_list = [] # Parse Local and Connected route strings in text. for raw_route_string in REGEXP_ROUTE_LOCAL_CONNECTED.finditer(raw_routing_table): subnet = ( raw_route_string.group('ipaddress') + convert_netmask_to_prefix_length( raw_route_string.group('maskOrPrefixLength') ) ) interface = raw_route_string.group('interface') route_tree[subnet] = ((interface,), raw_route_string.group(0)) if raw_route_string.group('routeType') == 'L': interface_list.append((interface, subnet,)) if not interface_list: print('Failed to find routing table entries in given output') return None # parse static and dynamic route strings in text for raw_route_string in REGEXP_ROUTE.finditer(raw_routing_table): subnet = ( raw_route_string.group('subnet') + convert_netmask_to_prefix_length( raw_route_string.group('maskOrPrefixLength') ) ) via_portion = raw_route_string.group('viaPortion') next_hops= [] if via_portion.count('via') > 1: for line in via_portion.splitlines(): if line: next_hops.append(REGEXP_VIA_PORTION.match(line).group(1)) else: next_hops.append(REGEXP_VIA_PORTION.match(via_portion).group(1)) route_tree[subnet] = (next_hops, raw_route_string.group(0)) router = { 'routing_table': route_tree, 'interface_list': interface_list, } return router 

Envolvemos el analizador en otra función para permitir la posterior adición de analizadores de otros formatos (por ejemplo, NX-OS):


 def parse_text_routing_table(raw_routing_table): """ Parser functions wrapper. Add additional parsers for alternative routing table syntaxes here. """ router = parse_show_ip_route_ios_like(raw_routing_table) if router: return router 

Entonces, queda por recorrer los archivos de texto en el directorio:


 def do_parse_directory(rt_directory): new_routers = {} if not os.path.isdir(rt_directory): print("{} directory does not exist.".format(rt_directory) + "Check rt_directory variable value." ) return None start_time = time() print("Initializing files...") for FILENAME in os.listdir(rt_directory): if FILENAME.endswith('.txt'): file_init_start_time = time() with open(os.path.join(rt_directory, FILENAME), 'r') as f: print ('Opening {}'.format(FILENAME)) raw_table = f.read() new_router = parse_text_routing_table(raw_table) router_id = FILENAME.replace('.txt', '') if new_router: new_routers[router_id] = new_router if new_router['interface_list']: for iface, addr in new_router['interface_list']: GLOBAL_INTERFACE_TREE[addr]= (router_id, iface,) else: print ('Failed to parse ' + FILENAME) print (FILENAME + " parsing has been completed in %s sec".format( "{:.3f}".format(time() - file_init_start_time)) ) else: if not new_routers: print ("Could not find any valid .txt files with routing tables" + " in {} directory".format(rt_directory) ) else: print ("\nAll files have been initialized" + " in {} sec".format("{:.3f}".format(time() - start_time)) ) return new_routers 

Y, después de descomponer todos los archivos en estructuras de datos ordenadas, uno puede asumir la segunda parte de la tarea.


Encontrar una ruta a través de tablas de enrutamiento procesadas


En general, en esta etapa, la tarea se reduce al análisis del gráfico de red. Los enrutadores son vértices del gráfico, los enlaces L3 entre ellos son bordes.
El diccionario ROUTERS almacena los identificadores de los enrutadores en las claves y en los valores correspondientes: los enlaces a las direcciones IP de los siguientes saltos. Es decir, junto con GLOBAL_INTERFACE_TREE , que devuelve identificadores de enrutador por dirección IP, para cada subred deseada, define una tabla de adyacencia de gráfico.


Si traza paralelos con enrutadores reales, para encontrar la forma en que necesita reproducir la lógica de alto nivel de su trabajo (abstracción de RIB / FIB / ASIC y otras optimizaciones) al procesar un paquete desde una búsqueda a la tabla de enrutamiento a la solicitud ARP (o router_id en nuestro caso) y enrutamiento o soltar el paquete, dependiendo del resultado.


Implementamos una búsqueda de ruta recursiva en los enrutadores. Cada sección de la ruta estará representada por una hoja de router_id y raw_route_string , la cadena de ruta original en ella. La ruta actual se escribirá en la tupla de ruta . Al llegar al punto de destino, la ausencia de una ruta en la tabla de enrutamiento o el siguiente salto en la topología estudiada, la ruta actual se agregará a la tupla de rutas resultante, que la función devolverá.


 def trace_route(source_router_id, target_ip, path=[]): if not source_router_id: return [path + [(None, None)]] current_router = ROUTERS[source_router_id] next_hop, raw_route_string = route_lookup(target_ip, current_router) path = path + [(source_router_id, raw_route_string)] paths = [] if next_hop: if nexthop_is_local(next_hop[0]): return [path] for nh in next_hop: next_hop_rid = get_rid_by_interface_ip(nh) if not next_hop_rid in [r[0] for r in path]: inner_path = trace_route(next_hop_rid, target_ip, path) for p in inner_path: paths.append(p) else: path = path + [(next_hop_rid+"<<LOOP DETECTED", None)] return [path] else: return [path] return paths def nexthop_is_local(next_hop): interface_types = ('Eth', 'Fast', 'Gig', 'Ten', 'Port', 'Serial', 'Vlan', 'Tunn', 'Loop', 'Null' ) for type in interface_types: if next_hop.startswith(type): return True 

Agregue una función para iniciar la búsqueda en modo interactivo después de la inicialización de los archivos de texto:


 def do_user_interactive_search(): while True: print ('\n') target_subnet = raw_input('Enter Target Subnet or Host: ') if not target_subnet: continue if not REGEXP_INPUT_IPv4.match(target_subnet.replace(' ', '')): print ("incorrect input") continue lookup_start_time = time() for rtr in ROUTERS.keys(): subsearch_start_time = time() result = trace_route(rtr, target_subnet) if result: print ("\n") print ("PATHS TO {} FROM {}".format(target_subnet, rtr)) n = 1 print ('Detailed info:') for r in result: print ("Path {}:".format(n)) print ([h[0] for h in r]) for hop in r: print ("ROUTER: {}".format(hop[0])) print ("Matched route string: \n{}".format(hop[1])) else: print ('\n') n+=1 else: print ("Path search on {} has been completed in {} sec".format( rtr, "{:.3f}".format(time() - subsearch_start_time)) ) else: print ("\nFull search has been completed in {} sec".format( "{:.3f}".format(time() - lookup_start_time),) ) 

El toque final para combinar las dos partes:


 def main(): global ROUTERS ROUTERS = do_parse_directory(RT_DIRECTORY) if ROUTERS: do_user_interactive_search() if __name__ == "__main__": main() 

Y tenemos un código listo para funcionar.


Código
 import os import re import SubnetTree from time import time # Path to directory with routing table files. # Each routing table MUST be in separate .txt file. RT_DIRECTORY = "./routing_tables" # RegEx template string for IPv4 address matching. REGEXP_IPv4_STR = ( '((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.' + '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))' ) # IPv4 CIDR notation matching in user input. REGEXP_INPUT_IPv4 = re.compile("^" + REGEXP_IPv4_STR + "(\/\d\d?)?$") # Local and Connected route strings matching. REGEXP_ROUTE_LOCAL_CONNECTED = re.compile( '^(?P<routeType>[L|C])\s+' + '((?P<ipaddress>\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)' + '\s?' + '(?P<maskOrPrefixLength>(\/\d\d?)?' + '|(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)?))' + '\ is\ directly\ connected\,\ ' + '(?P<interface>\S+)', re.MULTILINE ) # Static and dynamic route strings matching. REGEXP_ROUTE = re.compile( '^(\S\S?\*?\s?\S?\S?)' + '\s+' + '((?P<subnet>\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)' + '\s?' + '(?P<maskOrPrefixLength>(\/\d\d?)?' +'|(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)?))' + '\s*' + '(?P<viaPortion>(?:\n?\s+(\[\d\d?\d?\/\d+\])\s+' + 'via\s+(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?)(.*)\n?)+)', re.MULTILINE ) # Route string VIA portion matching. REGEXP_VIA_PORTION = re.compile( '.*via\s+(\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?).*' ) # Store for 'router' objects generated from input routing table files. # Each file is represented by single 'router' object. # Router is referenced by Router ID (RID). # RID is filename by default. # Format: # # ROUTERS = { # 'RID1': {'routing_table': {}, 'interface_list': ()}, # 'RID_N': {'routing_table': {}, 'interface_list': ()}, # } # ROUTERS = {} # Global search tree for Interface IP address to Router ID (RID) resolving. # Stores Interface IP addresses as keys. # Returns (RID, interfaceID) list. # Interface IP addresses SHOULD be globally unique across inspected topology. GLOBAL_INTERFACE_TREE = SubnetTree.SubnetTree() def parse_show_ip_route_ios_like(raw_routing_table): """ Parser for routing table text output. Compatible with both Cisco IOS(IOS-XE) 'show ip route' and Cisco ASA 'show route' output format. Processes input text file and writes into Python data structures. Builds internal SubnetTree search tree in 'route_tree'. Generates local interface list for router in 'interface_list' Returns 'router' dictionary object with parsed data. """ router = {} route_tree = SubnetTree.SubnetTree() interface_list = [] # Parse Local and Connected route strings in text. for raw_route_string in REGEXP_ROUTE_LOCAL_CONNECTED.finditer(raw_routing_table): subnet = ( raw_route_string.group('ipaddress') + convert_netmask_to_prefix_length( raw_route_string.group('maskOrPrefixLength') ) ) interface = raw_route_string.group('interface') route_tree[subnet] = ((interface,), raw_route_string.group(0)) if raw_route_string.group('routeType') == 'L': interface_list.append((interface, subnet,)) if not interface_list: print('Failed to find routing table entries in given output') return None # parse static and dynamic route strings in text for raw_route_string in REGEXP_ROUTE.finditer(raw_routing_table): subnet = ( raw_route_string.group('subnet') + convert_netmask_to_prefix_length( raw_route_string.group('maskOrPrefixLength') ) ) via_portion = raw_route_string.group('viaPortion') next_hops= [] if via_portion.count('via') > 1: for line in via_portion.splitlines(): if line: next_hops.append(REGEXP_VIA_PORTION.match(line).group(1)) else: next_hops.append(REGEXP_VIA_PORTION.match(via_portion).group(1)) route_tree[subnet] = (next_hops, raw_route_string.group(0)) router = { 'routing_table': route_tree, 'interface_list': interface_list, } return router def parse_text_routing_table(raw_routing_table): """ Parser functions wrapper. Add additional parsers for alternative routing table syntaxes here. """ router = parse_show_ip_route_ios_like(raw_routing_table) if router: return router def convert_netmask_to_prefix_length(mask_or_pref): """ Gets subnet_mask (XXX.XXX.XXX.XXX) of /prefix_length (/XX). For subnet_mask, converts it to /prefix_length and returns result. For /prefix_length, returns as is. For empty input, returns "" string. """ if not mask_or_pref: return "" if re.match("^\/\d\d?$", mask_or_pref): return mask_or_pref if re.match("^\d\d?\d?\.\d\d?\d?\.\d\d?\d?\.\d\d?\d?$", mask_or_pref): return ( "/" + str(sum([bin(int(x)).count("1") for x in mask_or_pref.split(".")])) ) return "" def route_lookup(destination, router): """ Performs route_tree lookup in passed router object for passed destination subnet. Returns list of next_hops with original route strings or (None,None) depending on lookup result. """ if destination in router['routing_table']: return router['routing_table'][destination] else: return (None, None) def get_rid_by_interface_ip(interface_ip): """Returns RouterID by Interface IP address which it belongs to.""" if interface_ip in GLOBAL_INTERFACE_TREE: return GLOBAL_INTERFACE_TREE[interface_ip][0] def nexthop_is_local(next_hop): """ Check if nexthop points to local interface. Will be True for Connected and Local route strings on Cisco devices. """ interface_types = ('Eth', 'Fast', 'Gig', 'Ten', 'Port', 'Serial', 'Vlan', 'Tunn', 'Loop', 'Null' ) for type in interface_types: if next_hop.startswith(type): return True def trace_route(source_router_id, target_ip, path=[]): """ Performs recursive path search from source Router ID (RID) to target subnet. Returns tuple of path tuples. Each path tuple contains a sequence of Router IDs with matched route strings. Multiple paths are supported. """ if not source_router_id: return [path + [(None, None)]] current_router = ROUTERS[source_router_id] next_hop, raw_route_string = route_lookup(target_ip, current_router) path = path + [(source_router_id, raw_route_string)] paths = [] if next_hop: if nexthop_is_local(next_hop[0]): return [path] for nh in next_hop: next_hop_rid = get_rid_by_interface_ip(nh) if not next_hop_rid in [r[0] for r in path]: inner_path = trace_route(next_hop_rid, target_ip, path) for p in inner_path: paths.append(p) else: path = path + [(next_hop_rid+"<<LOOP DETECTED", None)] return [path] else: return [path] return paths def do_parse_directory(rt_directory): """ Go through specified directory and parse all .txt files. Generate router objects based on parse result if any. Populate new_routers with those router objects. Default key for each router object is FILENAME. Return new_routers. """ new_routers = {} if not os.path.isdir(rt_directory): print("{} directory does not exist.".format(rt_directory) + "Check rt_directory variable value." ) return None start_time = time() print("Initializing files...") for FILENAME in os.listdir(rt_directory): if FILENAME.endswith('.txt'): file_init_start_time = time() with open(os.path.join(rt_directory, FILENAME), 'r') as f: print ('Opening {}'.format(FILENAME)) raw_table = f.read() new_router = parse_text_routing_table(raw_table) router_id = FILENAME.replace('.txt', '') if new_router: new_routers[router_id] = new_router if new_router['interface_list']: for iface, addr in new_router['interface_list']: GLOBAL_INTERFACE_TREE[addr]= (router_id, iface,) else: print ('Failed to parse ' + FILENAME) print (FILENAME + " parsing has been completed in {} sec".format( "{:.3f}".format(time() - file_init_start_time)) ) else: if not new_routers: print ("Could not find any valid .txt files with routing tables" + " in {} directory".format(rt_directory) ) else: print ("\nAll files have been initialized" + " in {} sec".format("{:.3f}".format(time() - start_time)) ) return new_routers def do_user_interactive_search(): """ Provides interactive search dialog for user. Asks user for target subnet or host in CIDR notation. Validates input. Prints error and goes back to start for invalid input. Executes path search to given target from each router in global ROUTERS. Prints formatted path search result. Goes back to start. """ while True: print ('\n') target_subnet = raw_input('Enter Target Subnet or Host: ') if not target_subnet: continue if not REGEXP_INPUT_IPv4.match(target_subnet.replace(' ', '')): print ("incorrect input") continue lookup_start_time = time() for rtr in ROUTERS.keys(): subsearch_start_time = time() result = trace_route(rtr, target_subnet) if result: print ("\n") print ("PATHS TO {} FROM {}".format(target_subnet, rtr)) n = 1 print ('Detailed info:') for r in result: print ("Path {}:".format(n)) print ([h[0] for h in r]) for hop in r: print ("ROUTER: {}".format(hop[0])) print ("Matched route string: \n{}".format(hop[1])) else: print ('\n') n+=1 else: print ("Path search on {} has been completed in {} sec".format( rtr, "{:.3f}".format(time() - subsearch_start_time)) ) else: print ("\nFull search has been completed in {} sec".format( "{:.3f}".format(time() - lookup_start_time),) ) def main(): global ROUTERS ROUTERS = do_parse_directory(RT_DIRECTORY) if ROUTERS: do_user_interactive_search() if __name__ == "__main__": main() 

Comprobando el guión


Armado con una pequeña topología abstracta de los cuatro Cisco CSR-1000v :



Están conectados en pares a través de las interfaces GigabitEthernet 2 y 3. Entre ellas hay un vecindario EIGRP, a través del cual se anuncian todas las redes conectadas, incluidas las redes en las interfaces Loopback detrás de cada enrutador.
Entre csr1000v-01 y csr1000v-04, se levantan adicionalmente dos túneles GRE, a través de las direcciones IP remotas cuyas rutas estáticas a la red 10.0.0.0/8 se registran para probar el bucle de enrutamiento.


csr1000v-01 # show ip route
 Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2 i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, * - candidate default, U - per-user static route o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP a - application route + - replicated route, % - next hop override, p - overrides from PfR Gateway of last resort is not set S 10.0.0.0/8 [1/0] via 192.168.142.2 [1/0] via 192.168.141.2 172.16.0.0/16 is variably subnetted, 2 subnets, 2 masks C 172.16.114.0/24 is directly connected, GigabitEthernet2 L 172.16.114.5/32 is directly connected, GigabitEthernet2 192.168.2.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.2.0/24 is directly connected, GigabitEthernet1 L 192.168.2.201/32 is directly connected, GigabitEthernet1 192.168.12.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.12.0/24 is directly connected, GigabitEthernet2 L 192.168.12.201/32 is directly connected, GigabitEthernet2 192.168.13.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.13.0/24 is directly connected, GigabitEthernet3 L 192.168.13.201/32 is directly connected, GigabitEthernet3 D 192.168.24.0/24 [90/3072] via 192.168.12.202, 00:06:56, GigabitEthernet2 D 192.168.34.0/24 [90/3072] via 192.168.13.203, 00:06:56, GigabitEthernet3 192.168.141.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.141.0/30 is directly connected, Tunnel141 L 192.168.141.1/32 is directly connected, Tunnel141 192.168.142.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.142.0/30 is directly connected, Tunnel142 L 192.168.142.1/32 is directly connected, Tunnel142 192.168.201.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.201.0/24 is directly connected, Loopback201 L 192.168.201.201/32 is directly connected, Loopback201 D 192.168.202.0/24 [90/130816] via 192.168.12.202, 00:05:44, GigabitEthernet2 D 192.168.203.0/24 [90/130816] via 192.168.13.203, 00:06:22, GigabitEthernet3 D 192.168.204.0/24 [90/131072] via 192.168.13.203, 00:06:56, GigabitEthernet3 [90/131072] via 192.168.12.202, 00:06:56, GigabitEthernet2 

csr1000v-02 # show ip route
 Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2 i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, * - candidate default, U - per-user static route o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP a - application route + - replicated route, % - next hop override, p - overrides from PfR Gateway of last resort is not set 192.168.2.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.2.0/24 is directly connected, GigabitEthernet1 L 192.168.2.202/32 is directly connected, GigabitEthernet1 192.168.12.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.12.0/24 is directly connected, GigabitEthernet2 L 192.168.12.202/32 is directly connected, GigabitEthernet2 D 192.168.13.0/24 [90/3072] via 192.168.12.201, 00:46:17, GigabitEthernet2 192.168.24.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.24.0/24 is directly connected, GigabitEthernet3 L 192.168.24.202/32 is directly connected, GigabitEthernet3 D 192.168.34.0/24 [90/3072] via 192.168.24.204, 00:46:15, GigabitEthernet3 D 192.168.201.0/24 [90/130816] via 192.168.12.201, 00:36:59, GigabitEthernet2 192.168.202.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.202.0/24 is directly connected, Loopback202 L 192.168.202.202/32 is directly connected, Loopback202 D 192.168.203.0/24 [90/131072] via 192.168.24.204, 00:06:31, GigabitEthernet3 [90/131072] via 192.168.12.201, 00:06:31, GigabitEthernet2 D 192.168.204.0/24 [90/130816] via 192.168.24.204, 00:37:26, GigabitEthernet3 

csr1000v-03 # show ip route
 Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2 i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, * - candidate default, U - per-user static route o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP a - application route + - replicated route, % - next hop override, p - overrides from PfR Gateway of last resort is not set 192.168.2.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.2.0/24 is directly connected, GigabitEthernet1 L 192.168.2.203/32 is directly connected, GigabitEthernet1 D 192.168.12.0/24 [90/3072] via 192.168.13.201, 00:46:12, GigabitEthernet3 192.168.13.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.13.0/24 is directly connected, GigabitEthernet3 L 192.168.13.203/32 is directly connected, GigabitEthernet3 D 192.168.24.0/24 [90/3072] via 192.168.34.204, 00:46:12, GigabitEthernet2 192.168.34.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.34.0/24 is directly connected, GigabitEthernet2 L 192.168.34.203/32 is directly connected, GigabitEthernet2 D 192.168.201.0/24 [90/130816] via 192.168.13.201, 00:36:56, GigabitEthernet3 D 192.168.202.0/24 [90/131072] via 192.168.34.204, 00:05:51, GigabitEthernet2 [90/131072] via 192.168.13.201, 00:05:51, GigabitEthernet3 192.168.203.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.203.0/24 is directly connected, Loopback203 L 192.168.203.203/32 is directly connected, Loopback203 D 192.168.204.0/24 [90/130816] via 192.168.34.204, 00:37:22, GigabitEthernet2 

csr1000v-04 # show ip route
 Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2 E1 - OSPF external type 1, E2 - OSPF external type 2 i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2 ia - IS-IS inter area, * - candidate default, U - per-user static route o - ODR, P - periodic downloaded static route, H - NHRP, l - LISP a - application route + - replicated route, % - next hop override, p - overrides from PfR Gateway of last resort is not set S 10.0.0.0/8 [1/0] via 192.168.142.1 [1/0] via 192.168.141.1 192.168.2.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.2.0/24 is directly connected, GigabitEthernet1 L 192.168.2.204/32 is directly connected, GigabitEthernet1 D 192.168.12.0/24 [90/3072] via 192.168.24.202, 00:46:17, GigabitEthernet3 D 192.168.13.0/24 [90/3072] via 192.168.34.203, 00:46:19, GigabitEthernet2 192.168.24.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.24.0/24 is directly connected, GigabitEthernet3 L 192.168.24.204/32 is directly connected, GigabitEthernet3 192.168.34.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.34.0/24 is directly connected, GigabitEthernet2 L 192.168.34.204/32 is directly connected, GigabitEthernet2 192.168.141.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.141.0/30 is directly connected, Tunnel141 L 192.168.141.2/32 is directly connected, Tunnel141 192.168.142.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.142.0/30 is directly connected, Tunnel142 L 192.168.142.2/32 is directly connected, Tunnel142 D 192.168.201.0/24 [90/131072] via 192.168.34.203, 00:37:02, GigabitEthernet2 [90/131072] via 192.168.24.202, 00:37:02, GigabitEthernet3 D 192.168.202.0/24 [90/130816] via 192.168.24.202, 00:05:57, GigabitEthernet3 D 192.168.203.0/24 [90/130816] via 192.168.34.203, 00:06:34, GigabitEthernet2 192.168.204.0/24 is variably subnetted, 2 subnets, 2 masks C 192.168.204.0/24 is directly connected, Loopback204 L 192.168.204.204/32 is directly connected, Loopback204 

Guarde estos resultados de show ip route en archivos separados en el subdirectorio ./routing_tables/ y ejecute el script:


$ python2.7 traceRouteByShowIPRoute.py
 $ python2.7 traceRouteByShowIPRoute.py Initializing files... Opening csr1000v-01.txt csr1000v-01.txt parsing has been completed in 0.001 sec Opening csr1000v-02.txt csr1000v-02.txt parsing has been completed in 0.001 sec Opening csr1000v-03.txt csr1000v-03.txt parsing has been completed in 0.001 sec Opening csr1000v-04.txt csr1000v-04.txt parsing has been completed in 0.001 sec All files have been initialized in 0.003 sec Enter Target Subnet or Host: 

Todos los archivos se inicializan, el script, como se esperaba, solicita una dirección IP o subred para encontrar la ruta desde cada uno de los enrutadores.
Introducimos varias alternativas y comparamos el resultado con la información de los enrutadores.


Encontrar una manera de Loopback204 192.168.204.204 en csr1000v-04

Las rutas a esta dirección IP deben ser de todos los enrutadores disponibles.


Enter Target Subnet or Host: 192.168.204.204
 Enter Target Subnet or Host: 192.168.204.204 PATHS TO 192.168.204.204 FROM csr1000v-04 Detailed info: Path 1: ['csr1000v-04'] ROUTER: csr1000v-04 Matched route string: L 192.168.204.204/32 is directly connected, Loopback204 Path search on csr1000v-04 has been completed in 0.000 sec PATHS TO 192.168.204.204 FROM csr1000v-03 Detailed info: Path 1: ['csr1000v-03', 'csr1000v-04'] ROUTER: csr1000v-03 Matched route string: D 192.168.204.0/24 [90/130816] via 192.168.34.204, 00:37:22, GigabitEthernet2 ROUTER: csr1000v-04 Matched route string: L 192.168.204.204/32 is directly connected, Loopback204 Path search on csr1000v-03 has been completed in 0.000 sec PATHS TO 192.168.204.204 FROM csr1000v-02 Detailed info: Path 1: ['csr1000v-02', 'csr1000v-04'] ROUTER: csr1000v-02 Matched route string: D 192.168.204.0/24 [90/130816] via 192.168.24.204, 00:37:26, GigabitEthernet3 ROUTER: csr1000v-04 Matched route string: L 192.168.204.204/32 is directly connected, Loopback204 Path search on csr1000v-02 has been completed in 0.000 sec PATHS TO 192.168.204.204 FROM csr1000v-01 Detailed info: Path 1: ['csr1000v-01', 'csr1000v-03', 'csr1000v-04'] ROUTER: csr1000v-01 Matched route string: D 192.168.204.0/24 [90/131072] via 192.168.13.203, 00:06:56, GigabitEthernet3 [90/131072] via 192.168.12.202, 00:06:56, GigabitEthernet2 ROUTER: csr1000v-03 Matched route string: D 192.168.204.0/24 [90/130816] via 192.168.34.204, 00:37:22, GigabitEthernet2 ROUTER: csr1000v-04 Matched route string: L 192.168.204.204/32 is directly connected, Loopback204 Path 2: ['csr1000v-01', 'csr1000v-02', 'csr1000v-04'] ROUTER: csr1000v-01 Matched route string: D 192.168.204.0/24 [90/131072] via 192.168.13.203, 00:06:56, GigabitEthernet3 [90/131072] via 192.168.12.202, 00:06:56, GigabitEthernet2 ROUTER: csr1000v-02 Matched route string: D 192.168.204.0/24 [90/130816] via 192.168.24.204, 00:37:26, GigabitEthernet3 ROUTER: csr1000v-04 Matched route string: L 192.168.204.204/32 is directly connected, Loopback204 Path search on csr1000v-01 has been completed in 0.000 sec Full search has been completed in 0.001 sec 

, . :


csr1000v-01#show ip route 192.168.204.204
 csr1000v-01#show ip route 192.168.204.204 Routing entry for 192.168.204.0/24 Known via "eigrp 200", distance 90, metric 131072, type internal Redistributing via eigrp 200 Last update from 192.168.13.203 on GigabitEthernet3, 00:02:15 ago Routing Descriptor Blocks: 192.168.13.203, from 192.168.13.203, 00:02:15 ago, via GigabitEthernet3 Route metric is 131072, traffic share count is 1 Total delay is 5020 microseconds, minimum bandwidth is 1000000 Kbit Reliability 255/255, minimum MTU 1500 bytes Loading 1/255, Hops 2 * 192.168.12.202, from 192.168.12.202, 00:02:15 ago, via GigabitEthernet2 Route metric is 131072, traffic share count is 1 Total delay is 5020 microseconds, minimum bandwidth is 1000000 Kbit Reliability 255/255, minimum MTU 1500 bytes Loading 1/255, Hops 2 

csr1000v-01 equal-cost EIGRP csr1000v-02 csr1000v-03.
, : ['csr1000v-01', 'csr1000v-03', 'csr1000v-04'] ['csr1000v-01', 'csr1000v-02', 'csr1000v-04'].


csr1000v-02#show ip route 192.168.204.204
 csr1000v-02#show ip route 192.168.204.204 Routing entry for 192.168.204.0/24 Known via "eigrp 200", distance 90, metric 130816, type internal Redistributing via eigrp 200 Last update from 192.168.24.204 on GigabitEthernet3, 00:08:48 ago Routing Descriptor Blocks: * 192.168.24.204, from 192.168.24.204, 00:08:48 ago, via GigabitEthernet3 Route metric is 130816, traffic share count is 1 Total delay is 5010 microseconds, minimum bandwidth is 1000000 Kbit Reliability 255/255, minimum MTU 1500 bytes Loading 1/255, Hops 1 

csr1000v-03#show ip route 192.168.204.204
 csr1000v-3#show ip route 192.168.204.204 Routing entry for 192.168.204.0/24 Known via "eigrp 200", distance 90, metric 130816, type internal Redistributing via eigrp 200 Last update from 192.168.34.204 on GigabitEthernet2, 00:08:45 ago Routing Descriptor Blocks: * 192.168.34.204, from 192.168.34.204, 00:08:45 ago, via GigabitEthernet2 Route metric is 130816, traffic share count is 1 Total delay is 5010 microseconds, minimum bandwidth is 1000000 Kbit Reliability 255/255, minimum MTU 1500 bytes Loading 1/255, Hops 1 

csr1000v-2 csr1000v-3 EIGRP csr1000v-4.
, : ['csr1000v-02', 'csr1000v-04'] ['csr1000v-03', 'csr1000v-04'] .


csr1000v-04#show ip route 192.168.204.204
 csr1000v-04#show ip route 192.168.204.204 Routing entry for 192.168.204.204/32 Known via "connected", distance 0, metric 0 (connected) Routing Descriptor Blocks: * directly connected, via Loopback204 Route metric is 0, traffic share count is 1 

csr1000v-4 Connnected Loopback204.
Local : ['csr1000v-04'].


Encontrar la ruta a la subred 10.10.10.0/24 (no en la topología)
Enter Target Subnet or Host: 10.10.10.0/24
 Enter Target Subnet or Host: 10.10.10.0/24 PATHS TO 10.10.10.0/24 FROM csr1000v-04 Detailed info: Path 1: ['csr1000v-04', 'csr1000v-01', 'csr1000v-04<<LOOP DETECTED'] ROUTER: csr1000v-04 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.1 [1/0] via 192.168.141.1 ROUTER: csr1000v-01 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.2 [1/0] via 192.168.141.2 ROUTER: csr1000v-04<<LOOP DETECTED Matched route string: None Path 2: ['csr1000v-04', 'csr1000v-01', 'csr1000v-04<<LOOP DETECTED'] ROUTER: csr1000v-04 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.1 [1/0] via 192.168.141.1 ROUTER: csr1000v-01 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.2 [1/0] via 192.168.141.2 ROUTER: csr1000v-04<<LOOP DETECTED Matched route string: None Path search on csr1000v-04 has been completed in 0.000 sec PATHS TO 10.10.10.0/24 FROM csr1000v-03 Detailed info: Path 1: ['csr1000v-03'] ROUTER: csr1000v-03 Matched route string: None Path search on csr1000v-03 has been completed in 0.000 sec PATHS TO 10.10.10.0/24 FROM csr1000v-02 Detailed info: Path 1: ['csr1000v-02'] ROUTER: csr1000v-02 Matched route string: None Path search on csr1000v-02 has been completed in 0.000 sec PATHS TO 10.10.10.0/24 FROM csr1000v-01 Detailed info: Path 1: ['csr1000v-01', 'csr1000v-04', 'csr1000v-01<<LOOP DETECTED'] ROUTER: csr1000v-01 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.2 [1/0] via 192.168.141.2 ROUTER: csr1000v-04 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.1 [1/0] via 192.168.141.1 ROUTER: csr1000v-01<<LOOP DETECTED Matched route string: None Path 2: ['csr1000v-01', 'csr1000v-04', 'csr1000v-01<<LOOP DETECTED'] ROUTER: csr1000v-01 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.2 [1/0] via 192.168.141.2 ROUTER: csr1000v-04 Matched route string: S 10.0.0.0/8 [1/0] via 192.168.142.1 [1/0] via 192.168.141.1 ROUTER: csr1000v-01<<LOOP DETECTED Matched route string: None Path search on csr1000v-01 has been completed in 0.003 sec Full search has been completed in 0.004 sec 

.
:


csr1000v-01#show ip route 10.10.10.0 255.255.255.0
 csr1000v-01#show ip route 10.10.10.0 255.255.255.0 Routing entry for 10.0.0.0/8 Known via "static", distance 1, metric 0 Routing Descriptor Blocks: * 192.168.142.2 Route metric is 0, traffic share count is 1 192.168.141.2 Route metric is 0, traffic share count is 1 

csr1000v-04#show ip route 10.10.10.0 255.255.255.0
 csr1000v-04#show ip route 10.10.10.0 255.255.255.0 Routing entry for 10.0.0.0/8 Known via "static", distance 1, metric 0 Routing Descriptor Blocks: 192.168.142.1 Route metric is 0, traffic share count is 1 * 192.168.141.1 Route metric is 0, traffic share count is 1 

csr1000v-01 csr1000v-04 GRE- 10.0.0.0/8. .
:


 PATHS TO 10.10.10.0/24 FROM csr1000v-01 Path 1: ['csr1000v-01', 'csr1000v-04', 'csr1000v-01<<LOOP DETECTED'] Path 2: ['csr1000v-01', 'csr1000v-04', 'csr1000v-01<<LOOP DETECTED'] PATHS TO 10.10.10.0/24 FROM csr1000v-04 Path 1: ['csr1000v-04', 'csr1000v-01', 'csr1000v-04<<LOOP DETECTED'] Path 2: ['csr1000v-04', 'csr1000v-01', 'csr1000v-04<<LOOP DETECTED'] 

csr1000v-02#show ip route 10.10.10.0 255.255.255.0
 csr1000v-02#show ip route 10.10.10.0 255.255.255.0 % Network not in table 

csr1000v-3#show ip route 10.10.10.0 255.255.255.0
 csr1000v-3#show ip route 10.10.10.0 255.255.255.0 % Network not in table 

csr1000v-02 csr1000v-03 . .


, , , .


Conclusión


, . MacBook Pro Intel Core i5 8GB RAM 700.000+ 6.85 ( 100 ). 320-350.
( ).


. IPv6 SubnetTree .
Python3, , raw_input input .


, , — , " " , " ", ..


Policy Based Routing ( PBR ) . , .


, - .
, .

Source: https://habr.com/ru/post/es414043/


All Articles