Analyse des tables de routage, ou pourquoi un ingénieur réseau Python

Bonjour Habr! Ceci est mon premier article sur Habré, et elle est née d'une question dans l'un des forums professionnels. La question ressemblait, pour paraphraser quelque peu, comme suit:


  • Il existe un ensemble de fichiers texte contenant la sortie des tables de routage de divers pĂ©riphĂ©riques rĂ©seau;
  • Chaque fichier contient des informations d'un appareil;
  • Les pĂ©riphĂ©riques peuvent avoir un format de sortie diffĂ©rent pour la table de routage;
  • Sur la base des donnĂ©es disponibles, sur demande, il est nĂ©cessaire d'afficher le chemin vers un sous-rĂ©seau arbitraire ou une adresse IP Ă  partir de chacun des appareils;
  • La sortie doit inclure sur chaque section du chemin des informations sur l'entrĂ©e de la table de routage, le long de laquelle le paquet sera routĂ©.

J'ai trouvé la tùche intéressante et j'ai fait écho à l'un de mes propres utilitaires de réseau, qui sont prévus à l'avenir. Par conséquent, lors d'une soirée libre, aprÚs avoir réfléchi à sa solution, j'ai écrit une implémentation de preuve de concept en Python 2.7 pour les formats Cisco IOS, IOS-XE et ASA, qui répond exigences de base.


Dans l'article, je vais essayer de reproduire le train de pensée et commenter les points principaux.
Le matériel est destiné aux personnes qui connaissent déjà fondamentalement les bases du réseautage et de Python.
Bienvenue à tous les chats intéressés!


Clause de non-responsabilité


L'auteur de l'article, étant un ingénieur réseau, n'est pas un développeur professionnel et peut ne pas encore connaßtre la différence entre une interface et une classe abstraite mais toujours ouvert à la critique et à la rétroaction constructives. Les commentaires sur le code, les approches sélectionnées et les algorithmes de collÚgues plus expérimentés sont les bienvenus.


Tout le code de cet article est distribué sous la licence MIT, y compris Il est fourni «tel quel» et ne fournit aucune sorte de garantie.


Clarification des conditions et choix de l'algorithme de solution


Compte tenu de l'introduction, la tĂąche peut ĂȘtre divisĂ©e en deux parties principales: analyse des fichiers source avec des tables de routage et recherche directe du chemin Ă  l'aide de donnĂ©es initialisĂ©es.
Une telle séparation permettra également, si nécessaire, d'importer des itinéraires à partir d'appareils pour rechercher un chemin arbitrairement (par exemple, via SNMP ou REST API).


Pour améliorer les performances, il est judicieux d'initialiser les fichiers une fois lors de l'exécution du script.
Un analyseur de fichiers doit reconnaĂźtre le format des tables de routage de divers systĂšmes d'exploitation. Ensuite, une option pour Cisco IOS, IOS-XE et ASA pour IPv4 sera considĂ©rĂ©e. La prise en charge d'autres formats et IPv6 peut ĂȘtre ajoutĂ©e ultĂ©rieurement.


Comme vous le savez, le choix d'un itinéraire selon la table de routage se fait selon le principe de coïncidence avec le préfixe de la plus grande longueur (correspondance de préfixe la plus longue).
Comme l'une des solutions pour une recherche rapide d'une telle correspondance, vous pouvez créer une arborescence de préfixes à partir des données source. Puisque nous n'avons pas de restrictions sur les dépendances externes, laissez-nous nous attarder sur le module SubnetTree terminé.


Il peut y avoir des boucles de routage sur la section rĂ©seau analysĂ©e, elles doivent ĂȘtre dĂ©tectĂ©es et ne pas affecter le script. De plus, sur aucun des nƓuds, il ne peut y avoir de route vers le sous-rĂ©seau souhaitĂ©. Cela devrait Ă©galement ĂȘtre pris en considĂ©ration.


S'il y a VRF sur le pĂ©riphĂ©rique, les tables de routage de chaque instance doivent ĂȘtre stockĂ©es dans des fichiers sĂ©parĂ©s, car du point de vue de la topologie, ce sont des routeurs distincts.


Les limitations des performances de iron pour l'exécution du script, le nombre et la taille des tables de routage analysées ne sont pas indiqués dans la condition, mais vous devez les garder à l'esprit.
La spécificité de l'application suggÚre une limite moyenne de 1 000 000 d'entrées dans la table de routage pour un appareil moderne. Sur un routeur avec BGP Full View en juin 2018, il peut y avoir 724 000+ itinéraires.
Selon une estimation approximative et les résultats des tests de stockage en mémoire et de traitement de 1 000 000 de préfixes, environ 500 Mo de RAM seront nécessaires. Ainsi, une station de travail à productivité moyenne avec 8 Go de RAM (nous gardons toujours à l'esprit en 2018) nous permettra d'analyser la topologie avec une capacité totale allant jusqu'à 14-16.000.000 de routes. Autrement dit, un segment d'environ 18-20 routeurs avec une vue complÚte sur chacun.
Dans la plupart des cas, cela suffit, mais pour les grands réseaux (comme le stress), vous devez soit diviser l'analyse en segments, soit transférer la logique vers une base de données insuffisante.
640 Ko suffisent Ă  tout le monde. ArrĂȘtons-nous sur la version en mĂ©moire.


Analyser les fichiers source et sélectionner les structures de données


Nous allons mettre les fichiers avec les tables de routage dans un sous-répertoire séparé et en faire une variable:


RT_DIRECTORY = "./routing_tables" 

Pour Cisco IOS et IOS-XE, la table de routage pourrait ressembler Ă  ceci:


montrer la route 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 

Dans Cisco ASA, le format est similaire, mais au lieu des longueurs de préfixe, le masque de sous-réseau est affiché en notation décimale:


montrer l'itinéraire
 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 

Comme vous pouvez le voir sur l'exemple, les routes, malgrĂ© la grande diversitĂ©, ont la mĂȘme structure prĂ©visible, ce qui signifie qu'elles peuvent ĂȘtre traitĂ©es par des expressions rĂ©guliĂšres.
Deux groupes mondiaux apparaissent: les itinéraires locaux + connectés et tout le reste.
Pour compliquer un peu les choses, il y a la possibilité d'avoir des routes multi-lignes avec un nombre variable d'espoirs prochains. Pour cette raison, il est problématique de lire le fichier pour le traitement ligne par ligne. Une solution consiste à traiter plusieurs correspondances avec un itérateur d'une expression réguliÚre dans un fichier chargé entiÚrement dans une variable de texte.


Nous écrirons des expressions réguliÚres en tenant compte des exigences et restrictions énumérées:


 # 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 ) 

Les deux expressions contiennent des groupes nommés pour faciliter la récupération des données lors de la recherche de correspondances et de la gestion du code.
En particulier, Ă  partir de chaque route, vous devez obtenir un prĂ©fixe ( sous - rĂ©seau / interface et groupes maskOrPrefixLength ) et des informations sur oĂč il mĂšne ( via des groupes de proportion / interface ).


Étant donnĂ© que les expressions prennent en compte plusieurs options pour reprĂ©senter le prĂ©fixe, le groupe maskOrPrefixLength peut contenir un masque de sous-rĂ©seau ou une longueur de prĂ©fixe. Nous allons rĂ©duire cela Ă  un seul format pendant le traitement; nous nous attarderons sur la longueur du prĂ©fixe:


 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 "" 

Ajoutez des expressions réguliÚres pour l'analyse ligne par ligne du saut suivant à partir du groupe viaPortion , en vérifiant le format des adresses IPv4 et l'entrée utilisateur:


 # 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?)?$") 

Nous transférons maintenant la représentation de notre réseau vers des structures de données Python.
Les préfixes résultant de l'analyse des tables de routage seront utilisés comme clés dans l'arborescence des préfixes. L'objet arborescence de préfixe est hérité du module SubnetTree .
Le résultat de la recherche par préfixe dans l'arborescence renverra une liste de la liste des prochains espoirs et une représentation textuelle complÚte de l'itinéraire correspondant à partir du fichier d'origine.
En outre, créez une liste d'interfaces locales.
Chaque routeur est représenté par un dictionnaire, dans lequel nous mettons les données ci-dessus.


 # 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, } 

Nous faisons une demande Ă  la table de routage dans une fonction distincte:


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

Chaque routeur se verra attribuer un identifiant unique. Vous pouvez l'affecter de plusieurs façons, dans l'exemple actuel, il sera permis et clair de le faire en fonction du nom du fichier d'origine.
En conséquence, nous ajouterons les objets de routeur résultants au dictionnaire avec la clé sous la forme de cet identifiant unique.


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

Nous avons également besoin d'un mécanisme pour rechercher le prochain routeur par l'adresse IP du saut suivant, obtenue à partir de la table de routage lors de la recherche de chemin. Pour ce faire, nous créons une autre arborescence de préfixes globale avec des clés sous forme d'adresses IP de tous les routeurs connus dans la topologie et des feuilles retournées avec l'identifiant du routeur et le type d'interface.


 # 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] 

Mettons ensemble l'analyseur pour le format IOS / IOS-XE / ASA. À l'entrĂ©e, nous lui passons la table de routage sous forme de texte, Ă  la sortie nous obtenons le dictionnaire du routeur dans le format spĂ©cifiĂ© ci-dessus.


 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 

Nous enveloppons l'analyseur dans une autre fonction pour permettre l'ajout ultérieur d'analyseurs d'autres formats (par exemple, 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 

Reste donc à parcourir les fichiers texte du répertoire:


 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 

Et, aprÚs avoir décomposé tous les fichiers en structures de données ordonnées, on peut assumer la deuxiÚme partie de la tùche.


Recherche d'un chemin à travers les tables de routage traitées


En général, à ce stade, la tùche est réduite à l'analyse du graphe de réseau. Les routeurs sont des sommets du graphique, les liens L3 entre eux sont des bords.
Le dictionnaire ROUTERS stocke les identifiants des routeurs dans les clés et dans les valeurs correspondantes - les liens vers les adresses IP du prochain bond. Autrement dit, en conjonction avec GLOBAL_INTERFACE_TREE , qui renvoie les identifiants de routeur par adresse IP, pour chaque sous-réseau souhaité, il définit une table d'adjacence de graphe.


Si nous établissons des parallÚles avec de vrais routeurs, pour trouver la façon dont vous devez reproduire la logique de haut niveau de leur travail (abstrait de RIB / FIB / ASIC et d'autres optimisations) lors du traitement d'un paquet d'une recherche à la table de routage à la demande ARP (ou router_id dans notre cas) et au routage ou déposer le colis, selon le résultat.


Nous implémentons une recherche de chemin récursive sur les routeurs. Chaque section du chemin sera représentée par une feuille de router_id et raw_route_string - la chaßne de route d'origine dessus. Le chemin actuel sera écrit dans le tuple de chemin . En atteignant le point de destination, l'absence de route dans la table de routage ou de saut suivant dans la topologie étudiée, le chemin actuel sera ajouté au tuple de chemins résultant, que la fonction retournera.


 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 

Ajoutez une fonction pour démarrer la recherche en mode interactif aprÚs l'initialisation des fichiers texte:


 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),) ) 

La touche finale pour combiner les deux parties:


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

Et nous avons un code prĂȘt Ă  fonctionner.


Code
 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() 

Vérification du script


Armé d'une petite topologie abstraite des quatre Cisco CSR-1000v :



Ils sont connectés par paires via les interfaces GigabitEthernet 2 et 3. Entre eux, il y a un quartier EIGRP, à travers lequel tous les réseaux connectés sont annoncés, y compris les réseaux sur les interfaces Loopback derriÚre chaque routeur.
Entre csr1000v-01 et csr1000v-04, deux tunnels GRE sont également levés, via les adresses IP distantes dont les routes statiques vers le réseau 10.0.0.0/8 sont enregistrées pour tester la boucle de routage.


csr1000v-01 # show route ip
 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 route ip
 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 

show ip route - ./routing_tables/ :


$ 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: 

, , , IP- .
.


Loopback204 192.168.204.204 csr1000v-04

IP- .


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'].


10.10.10.0/24 ( )
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 . .


, , , .


Conclusion


, . 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/fr414043/


All Articles