Análise de tabelas de roteamento ou por que outro engenheiro de rede Python

Olá Habr! Este é o meu primeiro artigo sobre Habré, e ela nasceu de uma pergunta em um dos fóruns profissionais. A questão parecia, parafraseando um pouco, da seguinte maneira:


  • Há um conjunto de arquivos de texto que contém a saída de tabelas de roteamento de vários dispositivos de rede;
  • Cada arquivo contém informações de um dispositivo;
  • Os dispositivos podem ter um formato de saída diferente para a tabela de roteamento;
  • Com base nos dados disponíveis, mediante solicitação, é necessário exibir o caminho para uma sub-rede ou endereço IP arbitrário de cada um dos dispositivos;
  • A saída deve incluir em cada seção das informações do caminho sobre a entrada da tabela de roteamento, ao longo da qual o pacote será roteado.

Achei a tarefa interessante e ecoei um dos meus próprios utilitários de rede, planejado no futuro.Portanto, em uma noite livre, pensando em sua solução, escrevi uma implementação de Prova de conceito em Python 2.7 para o formato Cisco IOS, IOS-XE e ASA, que atende requisitos básicos.


No artigo, tentarei reproduzir a linha de pensamento e comentar os pontos principais.
O material é destinado a pessoas que já estão familiarizadas fundamentalmente com o básico de redes e Python.
Todos os interessados ​​são bem-vindos ao gato!


Isenção de responsabilidade


O autor do artigo, sendo um engenheiro de rede, não é um desenvolvedor profissional e talvez ainda não saiba a diferença entre uma interface e uma classe abstrata mas sempre aberto a críticas e feedback construtivos. Comentários sobre o código, abordagens selecionadas e algoritmos de colegas mais experientes são bem-vindos.


Todo o código deste artigo é distribuído sob a licença MIT, incluindo É fornecido "como está" e não oferece nenhum tipo de garantia.


Esclarecimento das condições e escolha do algoritmo da solução


Considerando a introdução, a tarefa pode ser dividida em duas partes principais: análise de arquivos de origem com tabelas de roteamento e pesquisa direta do caminho usando dados inicializados.
Essa separação também permitirá, se necessário, importar rotas de dispositivos para procurar um caminho arbitrariamente (por exemplo, via SNMP ou REST API).


Para melhorar o desempenho, faz sentido inicializar arquivos uma vez quando o script é executado.
Um analisador de arquivos deve reconhecer o formato das tabelas de roteamento de vários sistemas operacionais. Em seguida, será considerada uma opção para Cisco IOS, IOS-XE e ASA para IPv4. Suporte para outros formatos e IPv6 pode ser adicionado posteriormente.


Como você sabe, a escolha de uma rota de acordo com a tabela de roteamento é feita de acordo com o princípio da coincidência com o prefixo de maior comprimento (correspondência de prefixo mais longa).
Como uma das soluções para uma pesquisa rápida para essa correspondência, você pode criar uma árvore de prefixos a partir dos dados de origem. Como não temos restrições às dependências externas, vamos nos concentrar no módulo SubnetTree finalizado.


Pode haver potencialmente loops de roteamento na seção de rede analisada, eles devem ser detectados e não devem afetar o script. Além disso, em qualquer um dos nós, pode não haver uma rota para a sub-rede desejada. Isso também deve ser considerado.


Se houver VRF no dispositivo, as tabelas de roteamento de cada instância devem ser armazenadas em arquivos separados, pois, do ponto de vista da topologia, são roteadores separados.


As limitações no desempenho do iron para executar o script, o número e o tamanho das tabelas de roteamento analisadas não são declaradas na condição, mas você deve mantê-las em mente.
A especificidade do aplicativo sugere um limite médio de 1.000.000 entradas na tabela de roteamento para um dispositivo moderno. Em um roteador com BGP Full View a partir de junho de 2018, pode haver mais de 724.000 rotas.
De acordo com uma estimativa aproximada e os resultados dos testes para armazenamento e processamento na memória de cada 1.000.000 prefixos, serão necessários cerca de 500 MB de RAM. Assim, uma estação de trabalho de produtividade média com 8 GB de RAM (ainda em mente em 2018) nos permitirá analisar a topologia com uma capacidade total de até 14 a 16.000.000 de rotas. Ou seja, um segmento de cerca de 18 a 20 roteadores com exibição completa em cada um.
Na maioria dos casos, isso é suficiente, mas para redes grandes (como estresse), é necessário dividir a análise em segmentos ou transferir a lógica para um banco de dados sem memória.
640KB é suficiente para todos. Vamos nos debruçar sobre a versão in-memory.


Analisando Arquivos de Origem e Selecionando Estruturas de Dados


Vamos colocar os arquivos com as tabelas de roteamento em um subdiretório separado e torná-lo uma variável:


RT_DIRECTORY = "./routing_tables" 

Para o Cisco IOS e o IOS-XE, a tabela de roteamento pode ser algo como isto:


mostre a rota do 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 

No Cisco ASA, o formato é semelhante, mas em vez dos comprimentos do prefixo, a máscara de sub-rede é exibida na notação decimal:


mostrar rota
 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 você pode ver no exemplo, as rotas, apesar da grande diversidade, têm a mesma estrutura e previsibilidade, o que significa que podem ser processadas por expressões regulares.
Dois grupos globais são divulgados: local + rotas conectadas e tudo mais.
Para complicar um pouco, é a possibilidade de ter rotas com várias linhas com um número variável de próximas esperanças. Por esse motivo, é problemático ler o arquivo para processar linha por linha. Uma saída é processar várias correspondências com um iterador de uma expressão regular em um arquivo carregado inteiramente em uma variável de texto.


Escreveremos expressões regulares levando em consideração os requisitos e restrições listados:


 # 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 as expressões contêm grupos nomeados para a conveniência de recuperar dados ao procurar correspondências e manter o código.
Em particular, em cada rota, você precisa obter um prefixo ( sub-rede / interface e grupos maskOrPrefixLength ) e informações sobre onde ele leva (via Portion / grupos de interface ).


Como as expressões levam em conta várias opções para representar o prefixo, o grupo maskOrPrefixLength pode conter uma máscara de sub-rede ou um comprimento de prefixo. Reduziremos isso para um único formato durante o processamento; nos deteremos no comprimento do prefixo:


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

Adicione expressões regulares para análise de linha a linha do próximo salto do grupo viaPortion , verificando o formato dos endereços IPv4 e a entrada do usuário:


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

Agora transferimos a representação de nossa rede para estruturas de dados Python.
Os prefixos resultantes da análise de tabelas de roteamento serão usados ​​como chaves na árvore de prefixos. O objeto da árvore de prefixo é herdado do módulo SubnetTree .
O resultado da pesquisa por prefixo na árvore retornará uma lista da lista de próximas esperanças e uma representação textual completa da rota correspondente do arquivo original.
Além disso, crie uma lista de interfaces locais.
Cada roteador é representado por um dicionário, no qual colocamos os dados acima.


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

Nós fazemos uma solicitação para a tabela de roteamento em uma função separada:


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

Cada roteador receberá um identificador exclusivo. Você pode atribuí-lo de várias maneiras; no exemplo atual, será permitido e claro fazer isso com base no nome do arquivo original.
Como resultado, adicionaremos os objetos do roteador resultantes ao dicionário com a chave na forma desse identificador exclusivo.


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

Também precisamos de um mecanismo para procurar o próximo roteador pelo endereço IP do próximo salto, obtido da tabela de roteamento durante a pesquisa de caminho. Para fazer isso, criamos outra árvore de prefixos global com chaves na forma de endereços IP de todos os roteadores conhecidos na topologia e nas folhas retornadas com o identificador do roteador e o tipo de 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] 

Vamos montar o analisador para o formato IOS / IOS-XE / ASA. Na entrada, passamos a tabela de roteamento em forma de texto; na saída, obtemos o dicionário do roteador no formato especificado acima.


 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 o analisador em outra função para permitir a adição subseqüente de analisadores de outros formatos (por exemplo, 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 

Portanto, resta examinar os arquivos de texto no diretório:


 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 

E, após decompor todos os arquivos em estruturas de dados ordenadas, é possível executar a segunda parte da tarefa.


Localizando um Caminho Através de Tabelas de Roteamento Processadas


Em geral, nesta fase, a tarefa é reduzida à análise do gráfico de rede. Roteadores são vértices do gráfico, links L3 entre eles são arestas.
O dicionário ROUTERS armazena os identificadores dos roteadores nas chaves e nos valores correspondentes - os links para os endereços IP dos saltos seguintes. Ou seja, em conjunto com GLOBAL_INTERFACE_TREE , que retorna identificadores de roteador por endereço IP, para cada sub-rede desejada, ele define uma tabela de adjacência de gráfico.


Se traçarmos paralelos com roteadores reais, para encontrar o caminho necessário para reproduzir a lógica de alto nível de seu trabalho (abstraindo do RIB / FIB / ASIC e outras otimizações) ao processar um pacote de uma consulta à tabela de roteamento para a solicitação ARP (ou router_id no nosso caso) e roteamento ou solte o pacote, dependendo do resultado.


Implementamos uma pesquisa de caminho recursivo nos roteadores. Cada seção do caminho será representada por uma folha de router_id e raw_route_string - a sequência original da rota. O caminho atual será gravado na tupla do caminho . Quando o destino é alcançado, não há rota na tabela de roteamento ou salto seguinte na topologia estudada, o caminho atual será adicionado às tuplas de caminhos resultantes, que a função retornará.


 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 

Adicione uma função para iniciar a pesquisa no modo interativo após a inicialização dos arquivos 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),) ) 

O toque final para combinar as duas partes:


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

E nós temos um código pronto 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() 

Verificando o script


Armado com uma pequena topologia abstrata dos quatro Cisco CSR-1000v :



Eles são conectados em pares pelas interfaces GigabitEthernet 2 e 3. Entre elas, existe um bairro EIGRP, através do qual todas as redes conectadas são anunciadas, incluindo redes nas interfaces Loopback atrás de cada roteador.
Entre o csr1000v-01 e o csr1000v-04, dois túneis GRE são aumentados adicionalmente, por meio dos endereços IP remotos dos quais as rotas estáticas para a rede 10.0.0.0/8 são registradas para testar o loop de roteamento.


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 

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

Os caminhos para esse endereço IP devem ser de todos os roteadores disponíveis.


Digite a sub-rede ou host de destino: 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 . .


, , , .


Conclusão


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


All Articles