路由表分析,或者为什么还要Python网络工程师

哈勃! 这是我在哈布雷(Habré)上的第一篇文章,她是从一个专业论坛中的一个问题出生的。 这个问题看起来可以解释如下:


  • 有一组文本文件,包含来自各种网络设备的路由表的输出。
  • 每个文件都包含来自一台设备的信息;
  • 设备可以为路由表使用不同的输出格式。
  • 根据可用数据,根据要求,有必要显示每个设备到任意子网或IP地址的路径。
  • 输出应在路径的每个部分上包含有关路由表中条目的信息,数据包将沿着该表进行路由。

我发现这个任务很有趣,并且回想了我自己计划在将来使用的网络实用程序,因此,在一个免费的晚上,考虑它的解决方案,我用Python 2.7编写了适用于Cisco IOS,IOS-XE和ASA格式的概念验证实现。基本要求。


在本文中,我将尝试重现思路和评论要点。
该材料适用于已经从根本上熟悉网络和Python基础的人们。
欢迎有兴趣的大家来养猫!


免责声明


这篇文章的作者是网络工程师,不是专业的开发人员 可能还不知道接口和抽象类之间的区别 但始终欢迎建设性的批评和反馈。 欢迎有经验的同事对代码,选定的方法和算法发表评论。


本文中的所有代码均以MIT许可分发,包括 它按“原样”提供,不提供任何形式的保证。


澄清条件并选择求解算法


考虑到介绍性内容,该任务可分为两个主要部分:使用路由表解析源文件以及使用初始化数据直接搜索路径。
如果需要,这种分隔还将允许从设备导入路由以任意搜索路径(例如,通过SNMP或REST API)。


为了提高性能,在运行脚本后初始化文件一次是有意义的。
文件解析器必须识别来自各种操作系统的路由表的格式。 接下来,将考虑针对IPv4的Cisco IOS,IOS-XE和ASA选项。 稍后可能会添加对其他格式和IPv6的支持。


如您所知,根据路由表选择路由是根据与最大长度前缀(最长前缀匹配)重合的原理进行的。
作为快速搜索此类匹配项的解决方案之一,您可以根据源数据构建前缀树。 由于我们对外部依赖项没有任何限制,因此让我们来谈谈完成的SubnetTree模块。


分析的网络部分上可能存在路由循环,应将其检测出来,并且不应影响脚本。 此外,在任何节点上可能都没有通往所需子网的路由。 这也应该考虑。


如果设备上有VRF ,则每个实例的路由表必须存储在单独的文件中,因为从拓扑的角度来看,它们是单独的路由器。


条件中未说明运行脚本的熨斗性能的限制,所分析路由表的数量和大小,但请记住这些限制。
该应用程序的特殊性建议一台现代设备的路由表中平均限制为1,000,000个条目。 截至2018年6月,在具有BGP Full View的路由器上,实际上可能有724,000条以上的路由。
根据粗略的估计以及每个1,000,000个前缀在内存中存储和处理的测试结果,将需要大约500MB的RAM。 因此,具有8GB RAM(我们仍要牢记2018年)的普通生产力工作站将使我们能够分析拓扑,其总容量高达14-16.000.000路由。 也就是说,大约有18-20个路由器的一部分,每个路由器上都有完整的视图。
在大多数情况下,这已经足够了,但是对于大型网络(例如压力),您需要将分析分为多个部分,或者将逻辑转移到内存不足的数据库中。
640KB足够每个人使用。 让我们关注一下内存版本。


解析源文件并选择数据结构


我们将把带有路由表的文件放在一个单独的子目录中,并使其成为变量:


RT_DIRECTORY = "./routing_tables" 

对于Cisco IOS和IOS-XE,路由表可能如下所示:


显示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 

在Cisco ASA中,格式类似,但是子网掩码以十进制表示法显示,而不是前缀长度:


显示路线
 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 

从示例中可以看到,尽管路由具有很大的多样性,但它们具有相同且可预测的结构,这意味着可以使用正则表达式对其进行处理。
出现了两个全球性小组: 本地+连通路线以及其他所有路线。
使事情复杂化的可能性是,多线路线可能会有不同数量的下一个希望。 因此,逐行读取文件进行处理是有问题的。 一种解决方法是使用正则表达式的迭代器在完全加载到文本变量的文件中处理多个匹配项。


我们将考虑列出的要求和限制来编写正则表达式:


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

这两个表达式都包含命名组 ,以方便在搜索匹配项和维护代码时检索数据。
特别是,从每个路由中,您需要获取一个前缀( 子网 / 接口maskOrPrefixLength组 ),以及有关其路由位置的信息( viaPortion / 接口组)。


由于表达式考虑了表示前缀的几个选项,所以maskOrPrefixLength组可以包含子网掩码或前缀长度。 我们将在处理过程中将其简化为单一格式;我们将讨论前缀的长度:


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

viaPortion组添加正则表达式以进行下一跳逐行解析,检查IPv4地址的格式和用户输入:


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

现在,我们将网络的表示形式转换为Python数据结构。
解析路由表产生的前缀将用作前缀树中的键。 前缀树对象是从SubnetTree模块继承的。
树中带有前缀的搜索结果将从下一个希望列表中返回一个列表,并从原始文件中返回相应路径的完整文本表示。
此外,创建本地接口列表。
每个路由器由一个字典表示,我们将上面的数据放入其中。


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

我们通过单独的函数向路由表发出请求:


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

每个路由器将被分配一个唯一的标识符。 您可以通过多种方式进行分配,在当前示例中,基于原始文件的名称,这样做是明确允许的。
结果,我们将使用唯一标识符形式的关键字将生成的路由器对象添加到字典中。


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

我们还需要一种机制,该机制通过在路径搜索过程中从路由表获得的下一跳IP地址来搜索下一个路由器。 为此,我们使用拓扑中所有已知路由器的IP地址形式的密钥创建另一个全局前缀树,并返回带有路由器标识符和接口类型的表。


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

让我们将IOS / IOS-XE / ASA格式的解析器放在一起。 在输入处,将文本形式的路由表传递给它,在输出处,以上述指定的格式获得路由器字典。


 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 

我们将解析器包装在另一个函数中,以便随后添加其他格式的解析器(例如,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 

因此,仍然需要遍历目录中的文本文件:


 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 

而且,在将所有文件分解为有序数据结构之后,就可以承担任务的第二部分。


通过经过处理的路由表查找路径


通常,在此阶段,任务被简化为网络图的分析。 路由器是图的顶点,它们之间的L3链接是边。
ROUTERS词典将路由器的标识符存储在密钥中以及相应的值中-指向下一跳IP地址的链接。 也就是说,结合GLOBAL_INTERFACE_TREE (按IP地址返回路由器标识符),对于每个所需的子网,它定义了一个图邻接表。


如果我们与真实的路由器并行处理,则在处理从查找到路由表的数据包到ARP请求(在本例中为router_id )并进行路由时,要找到需要的方法来重现其工作的高级逻辑(不包括RIB / FIB / ASIC和其他优化方法)还是放下包装,取决于结果。


我们在路由器上实现了递归路径搜索。 路径的每个部分都将由一张router_idraw_route_string (原始路由字符串)表示。 当前路径将被写入路径元组。 到达目的地点,路由表中没有路由或研究的拓扑中没有下一跳时,当前路径将添加到生成的路径元组中,函数将返回该元组。


 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 

初始化文本文件后,添加一个以交互方式开始搜索的功能:


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

将这两个部分结合起来的最后一点:


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

而且我们有准备工作的代码。


代号
 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() 

检查脚本


配备了四个Cisco CSR-1000v的小型抽象拓扑:



它们通过GigabitEthernet 2和3接口成对连接,在它们之间有一个EIGRP邻居,通过它宣布所有已连接的网络,包括每个路由器后面的Loopback接口上的网络。
在csr1000v-01和csr1000v-04之间,还增加了两个GRE隧道,通过该隧道的远程IP地址注册了到10.0.0.0/8网络的静态路由以测试路由环路。


csr1000v-01#显示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#显示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#显示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.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地址的路径必须来自所有可用路由器。


输入目标子网或主机: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#显示ip路由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 . .


, , , .


结论


, . 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/zh-CN414043/


All Articles