[python]Automating OSPF configuration of two routers

Network diagram
Screenshot 2019-08-28 at 2.29.23 AM
There are two vIOS which are R1 and R2, they have their gi0/2 and gi0/3 connected to the R5 switch, the configuration is pushed from the cloud through their gi0/2 and gi0/3 to configure their gi0/0 to become OSPF neighbours.

Objective
To configure the two routers to become ospf neighbors, the OSPF configuration is based on Cisco’s OSPF best practice:

  • Disable passive interface only on interface that is talking OSPF
  • Enable MD5 OSPF authentication
  • Enable router-id
  • Passive interface as default unless explicitly disabled for interface that needs to form OSPF adjacency
  • Use specific network statement to advertise the network

Requirements

  • The ip address is set on gi0/0 on both routers and are known and kept in network_list text file
  • The management ip addresses of interface gi0/2 and gi0/3 of respective routers are known, including login credentials.

In the code the username and password are hardcoded, this is a demo code, there are several ways to handle credentials outside the code, either keep the username and password as an external file and encrypt them with a public key, when in use decrypt with the private key and pass over to the code or you can try other means with your imagination.

Before code execution


both R1 and R2 are not OSPF neighbors.

both R1 and R2 gi0/0 interfaces do not have ip ospf authentication-key.

Demonstration


The script was doing its work and these were the output from the script.


Both R1 and R2 have formed OSPF neighbors. R2 is the designated router as it has the highest router-id.



Router ospf configuration of both R1 and R2

How was the automation done?
On previous post I have worked on a script that converts unstructured command output into structured dictionary/json format, with the dictionary type it is easy to evaluate the data i need and compare against.

To make the script work, I need to have a list of management ip address of the routers to be configured, and also the network ip address that I want the neigbors to be formed.

An OSPF best practice template, which the scripts will compare and choose the right data to be inserted into the template. The insertion of the template is done in memory the template is just taken as a reference. For this lab I used jinja2 template engine which comes installed together with Flask.

The script first looks for the device from the devices_list.txt, and configure device one by one, then on the device it checks the interface and compare the interface ip address against the ip addresses in the network_list, and finally after comparison the correct data is extracted and then inserted to the template. The command set then transferred to netmiko to push the set of command to the device, on subsequent device the same process repeats again.

Code with some comments

from netmiko import ConnectHandler
from jinja2 import FileSystemLoader, Environment
import re
from rstr import Rstr
from random import SystemRandom


# parse the raw show ip int brief output into dictionary.
def show_ip_int_brief(cmd_output):
    interfaces = []
    intf_pattern = "^[lLgGeEfFtT]\S+[0-9]/?[0-9]*"
    regex = re.compile(intf_pattern)
    for row in cmd_output.splitlines():
        if regex.search(row):
            interfaces.append(
                {'interface': row.split()[0],
                 'ip_address': row.split()[1],
                 'ok': row.split()[2],
                 'method': row.split()[3],
                 'status': row.split()[4],
                 'protocol': row.split()[5]}
            )
    return interfaces

# compile the device from the devices_list.txt
def device_info(device, username, password):
    return {
        'device_type': 'cisco_ios',
        'ip': device,
        'username': username,
        'password': password
    }


# random string generator for OSPF auth key
def ospf_auth_generator():
    rgen = Rstr(SystemRandom())
    pattern = r"[0-9a-zA-Z]{8}"
    return rgen.xeger(pattern)


# read and store the devices_list.txt
with open('devices_list.txt', "r") as file:
    devices_list = file.read()

# read and store the network_list.
with open('network_list', 'r') as file:
    network_list = file.read()

# testing starts from here...
if __name__ == "__main__":
    # tell jinja2 to look for a template file in templates folder.
    file_loader = FileSystemLoader('templates')
    env = Environment(loader=file_loader)
    # tell jinja2 to use the ospf.txt template file.
    config_template = env.get_template('ospf.txt')
    print("----loading ospf best practice template----\n")
    # pre-generate the ospf auth key for rendering...
    ospf_auth = ospf_auth_generator()
    print("----generating authentication key for ospf peers----\n")
    # configure OSPF for one device after another.
    for device in devices_list.splitlines():
        print(f"----Trying connecting to {device}, wish me luck...----\n")
        # this is a demo, in production username and password should be handled outside the code.
        ios = device_info(device, 'cisco', 'cisco')
        conn = ConnectHandler(**ios)
        print(f"----I have got in {device}----\n")
        print(f"----Trying hard to gather some information from {device}----\n")
        output = conn.send_command("show ip int brief")
        interfaces = show_ip_int_brief(output)
        print(f"----Interface information has been gathered---\n")
        for interface in interfaces:
            for network in network_list.splitlines():
                if interface.get('ip_address') == network:
                    print("----Preparing for the OSPF best practice, wish me luck----\n")
                    # insert all required values into the template.
                    cmd_set = config_template.render(router_id=interface.get('ip_address'),
                                           interface_ip=interface.get('ip_address'),
                                           intf_id=interface.get('interface'),
                                           random_string=ospf_auth)
                    print(cmd_set)
                    # use send_config_set if there is a huge list of commands.
                    # cannot use the ospf.txt with send_config_file as the ospf.txt is a template.
                    # rendering is done in memory and the template will still be unchanged.
                    conn.send_config_set(cmd_set, delay_factor=2)
                    print("Configured for {}".format(device))

Jinja2 template for ospf best practice
Screenshot 2019-08-28 at 3.27.01 AM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s