cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
1440
Views
2
Helpful
2
Comments
Tagir Temirgaliyev
Spotlight
Spotlight

Network engineers often manage a complex web of routers interconnected by intricate routing protocols, such as OSPF (Open Shortest Path First) and BGP (Border Gateway Protocol). In such environments, troubleshooting or verifying the network's health can be time-consuming. Engineers must typically log into each router individually, execute diagnostic commands, and interpret the results manually. This process, repeated across dozens or even hundreds of devices, becomes not only tedious but also error-prone.

Thankfully, Python offers an efficient solution by automating repetitive tasks. With Python, it’s possible to connect to a large number of routers, issue predefined commands, and compile the results in a structured format, all without manual intervention. This approach not only saves time but also enhances accuracy and scalability.

In one example, I developed a Python program that remotely connects to multiple routers and executes the commands 'show ip interface' and 'show interface' on each device. The program extracts essential details and consolidates them into a single CSV file, making it easier to analyze the network’s status at a glance.

The output CSV includes critical columns like:

 

  • Switch and Switch IP: Identifying the device
  • Interface, Link Status, Protocol Status: Details on each interface’s connectivity
  • IP Address and Prefix Length: Configuration details of each interface
  • VRF, MTU, IP Helper: Relevant network settings
  • Outbound/Inbound ACL: Applied access control lists
  • Input/Output Rate and Errors: Traffic statistics and error counts

excel.png

One of the program’s advantages is its flexibility; it allows columns to be easily added or removed based on monitoring needs. For example, engineers can expand the CSV output with additional fields as required or streamline it by removing less critical data.

Additionally, this program is designed to provide daily insights into network performance. Each time it runs, it reads and resets all interface counters. By running this program daily, network engineers gain a rolling, day-by-day view of metrics like error counts, allowing them to detect and troubleshoot issues that arise overnight or on a day-by-day basis.

With Python automation, network engineers can transition from repetitive tasks to a more efficient, data-driven approach to network management, enabling quicker identification of issues and long-term performance insights.

In my network automation program, I leverage an Ansible inventory file for device management. This file, which was already in place, lists all routers and their details, simplifying the setup process. However, alternative inventories like Cisco’s Genie or even a simple text file can be used based on network needs.

 

 

r4#show ip interface
FastEthernet0/0 is up, line protocol is up
  Internet protocol processing disabled
FastEthernet0/0.10 is up, line protocol is up
  Internet address is 10.1.34.4/24
  Broadcast address is 255.255.255.255
  Address determined by non-volatile memory
  MTU is 1500 bytes
  Helper address is not set
  Directed broadcast forwarding is disabled
  Outgoing access list is not set
  Inbound  access list is not set
  Proxy ARP is enabled
  Local Proxy ARP is disabled
  Security level is default
  Split horizon is enabled
  ICMP redirects are always sent
  ICMP unreachables are always sent
  ICMP mask replies are never sent
  IP fast switching is enabled
  IP fast switching on the same interface is enabled
  IP Flow switching is disabled
  IP CEF switching is enabled
  IP CEF Fast switching turbo vector
  IP multicast fast switching is enabled
  IP multicast distributed fast switching is disabled
  IP route-cache flags are Fast, CEF
  Router Discovery is disabled
  IP output packet accounting is disabled
  IP access violation accounting is disabled
  TCP/IP header compression is disabled
 --More--

-------------- after TEXTFSM parsing  'show ip interface'  <class 'list'>


[{'interface': 'FastEthernet0/0', 'link_status': 'up', 'protocol_status': 'up', 'ip_address': '', 'prefix_length': '', 'vrf': '', 'mtu': '', 'ip_helper': [], 'outgoing_acl': '', 'inbound_acl': ''}, 
 
 {'interface':'FastEthernet0/0.10', 'link_status': 'up', 'protocol_status': 'up', 'ip_address': '10.1.34.4', 'prefix_length': '24', 'vrf': '', 'mtu': '1500', 'ip_helper': [], 'outgoing_acl': '', 'inbound_acl': ''}, 
 
 {'interface': 'FastEthernet0/1', 'link_status': 'up', 'protocol_status': 'up', 'ip_address': '192.168.38.104', 'prefix_length': '24', 'vrf': '', 'mtu': '1500', 'ip_helper': [], 'outgoing_acl': '', 'inbound_acl': ''}, 
 
 {'interface': 'Loopback0', 'link_status': 'up', 'protocol_status': 'up', 'ip_address': '4.4.4.4', 'prefix_length': '32', 'vrf': '', 'mtu':'1514', 'ip_helper': [], 'outgoing_acl': '', 'inbound_acl': ''}]
                                     

 

To facilitate connections and data gathering from network devices, the program uses the netmiko library, which simplifies SSH management across a range of network devices. Additionally, I employ textfsm, a library initially developed by Google, to parse command-line output into structured data in the form of a list of dictionaries. This parsing is crucial for generating a consistent CSV output, where each row represents a device and its interface statistics.

For those looking to understand or adapt this program, familiarity with lists and dictionaries in Python, as well as the netmiko and textfsm libraries, is essential. With this approach, network engineers can automate device monitoring, achieve daily updates on error counters, and easily scale their automation across various network environments.

Ansible inventory file in YAML format:

 

all:
  vars:
    ansible_user: admin
    ansible_password: admin
    ansible_connection: ansible.netcommon.network_cli
    ansible_network_os: ios
    ansible_become: yes
    ansible_become_method: enable
    ansible_become_password: cisco
    ansible_host_key_auto_add: yes

routers:
  hosts:
    r1:
      ansible_host: 192.168.38.101
    r2:
      ansible_host: 192.168.38.102
    r3:
      ansible_host: 192.168.38.103
    r4:
      ansible_host: 192.168.38.104

 

python:

 

#!/usr/bin/python3
#   Copyright (c) 2023-2024 T.Temirgaliyev  The MIT License
#   usage " python cisco_.py --hosts_file hosts --group sw1 "         define set of switches

import yaml, argparse, csv, subprocess
from netmiko import ConnectHandler

def parse_arguments():                                     # to parse command-line arguments
    parser = argparse.ArgumentParser(description = ' Netmiko Script to Connect to Routers and Run Commands ')
    parser.add_argument('--hosts_file', required=True, help = ' Path to the Ansible hosts file ')
    parser.add_argument('--group', required=True, help = ' Group of routers to connect to from Ansible hosts file ')
    return parser.parse_args()

def ping_ip(ip_address):                                   # Use ping command to check if switch alive
    param = '-c'                                           # for linux os
    command = ['ping', param, '2', ip_address]             # Build the ping command
    try:
        subprocess.check_output(command, stderr=subprocess.STDOUT, universal_newlines=True)    # Execute the ping command
        return "yes"
    except subprocess.CalledProcessError:
        return "no"

###########         Main function
def main():
    args = parse_arguments()                               # Parse command-line arguments
    with open(args.hosts_file, 'r') as file:               # Load ansible hosts file in yaml format
        hosts_data = yaml.safe_load(file)
    global_vars = hosts_data['all']['vars']                # Extract global variables
    # Extract router details for the specified group
    if args.group not in hosts_data:
        print(f"Group {args.group} not found in hosts file.")
        return
    routers = hosts_data[args.group]['hosts']              # Extract group of devices

    output_list  = []

    # Connect to each switch and execute the specified commands
    for router_name, router_info in routers.items():                 # loop for each switch in group
        if ping_ip(router_info['ansible_host']) == "no":             # check if host alive 
            print( ' switch offline --------- ', router_name,'  ',router_info['ansible_host'])
            continue
        else: 
            print( ' switch  online --------- ', router_name,'  ',router_info['ansible_host'])

        de_type = ''
        if global_vars['ansible_network_os'] == 'ios':               # check if cisco ios
            de_type = 'cisco_ios'
        netmiko_connection = {                                       # Create Netmiko connection dictionary
            'device_type': de_type,'host': router_info['ansible_host'],
            'username': global_vars['ansible_user'],
            'password': global_vars['ansible_password'],
            'secret': global_vars['ansible_become_password'],
        }

        connection = ConnectHandler(**netmiko_connection)                  # Establish SSH connection
        connection.enable()                                                # Enter enable mode 

        outputd1 = connection.send_command('show interface',  use_textfsm=True)     # Execute the specified command
        outputd2 = connection.send_command('show ip interface ',  use_textfsm=True) 

        connection.send_command("clear counters", expect_string="\[confirm\]")      # clear interface counters
        connection.send_command("\n", expect_string="#")

        connection.disconnect()                                            # Disconnect from device

        for entry in outputd2:                                             # Remove square brackets
            if entry['ip_address'] > []:
                entry['ip_address'] = entry['ip_address'][0]
            else:
                entry['ip_address'] = ''
            if entry['prefix_length'] > []:
                entry['prefix_length'] = entry['prefix_length'][0]
            else:
                entry['prefix_length'] = ''

        for port_entry in outputd2:                            # Loop through the ports_list
            input_errors, output_errors, input_rate, output_rate  = '', '', '', ''                 # Initialize as an empty string
            for i_entry in outputd1:
                if port_entry['interface'] == i_entry['interface']:
                    input_errors , output_errors = i_entry['input_errors'] , i_entry['output_errors']     # Assign the matching info if found
                    input_rate , output_rate  = i_entry['input_rate'] , i_entry['output_rate']
                    break
            port_entry['input_rate'] , port_entry['output_rate'] = input_rate , output_rate              # Add info to the port entry
            port_entry['input_errors'] , port_entry['output_errors'] = input_errors , output_errors 

        outputd2_uswip =[{'switch_ip': router_info['ansible_host'], **d} for d in outputd2]
        outputd2_usw   =[{'switch':    router_name,                 **d} for d in outputd2_uswip]
        output_list.extend(outputd2_usw)                                 # add switch info to total output file

    output_filem = args.group + '_merg.csv'                    #    merge 2 in 1 file
    with open(output_filem, mode='w', newline='') as file:     # Write the updated data to a new CSV file
        fieldnames = output_list[0].keys()
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(output_list)
    print("New CSV file  has been created as ", args.group , '_merg.csv')

####################    Entry point of the main
if __name__ == '__main__':
    main()

 

Comments
Rich R
VIP
VIP

This is pretty much exactly what the RADKit tool is designed to do: https://radkit.cisco.com/

Tagir Temirgaliyev
Spotlight
Spotlight

People might wonder why it’s necessary to generate this CSV file and whether it’s truly beneficial. The answer lies in the program's next functionality: verifying the correctness of routing across the infrastructure.

Routing setups can be highly complex, often involving multiple routing protocols and redistribution. After making changes on some specific routers, it's crucial to ensure that routing remains accurate across the network. To address this, the program includes an automated availability check between routers.

This check uses two nested loops to iterate through each router, issuing a Ping command from each router's loopback interface to the loopback interfaces of all other routers. This allows an immediate verification of connectivity across the network. If every router successfully pings each other router, we know that routing is functioning as expected.

        for entry in output_list:
            if (entry['interface'] == 'Loopback0') and (entry['switch'] == router_name):
                source_ip , swi = entry['ip_address'], entry['switch']
            else:
                continue
            for entry2 in output_list2:
                if (entry2['interface'] == 'Loopback0') and (swi != entry2['switch']):
                    dest_ip = entry2['ip_address'] 
                else:
                    continue
                comm = 'ping '+dest_ip+' repeat 2 source '+source_ip
                outputd11 = connection.send_command(comm, use_textfsm=True)
                print(router_name,comm,outputd11[0]['response_stream'][0])
Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: