Implement HSRP using ciscoconfparse

Update 10th April 2016: I added the support to convert secondary IPv4 addresses to the python script on GitHub. The initial version of the script that was discussed in this post can be found here.


I like to show you this week a use case, which focuses on the work with existing configurations using the ciscoconfparse python module. The use case focuses on a quite common topic associated with campus networks: a later implementation of redundancy in the distribution (or aggregation) layer. At the end of this post, we’ll have created a configuration script for HSRP along with the readdressing of the VLAN switched virtual interface (SVI) interfaces based on the configuration from the existing switch.

As always, you can find the entire python script from this post in my python examples repository on GitHub in the folder create-hsrp-interface-configuration.

From a standalone to a redundant campus network

The scenario for this week is the following: You have an existing campus network that consists of a single distribution switch, which provides the routing functionality to the campus, and many access switches, which connect all users. During some redesign work on you network, you decided to add another distribution switch and you need to migrate your current environment from a standalone to a fully redundant topology (separate control planes). The following diagram describes our scenario.

When deploying such a topology, you need to handle many topics, including load-sharing strategies with spanning-tree, the routing scenario and first-hop redundancy. In this scenario we have Cisco Catalyst switches, therefore we choose the HSRP (Hot Standby Router Protocol) for the first-hop redundancy part. When implementing HSRP, you need to update the entire VLAN configuration from the current switch. If you have just six VLANs this is not a major deal, but if your campus is larger, that’s usually a time intensive, manual and error prone task.

Today we will have a look at a programmatic solution for this problem using the ciscoconfparse python module. We will create a primary and secondary configuration template from an existing Cisco IOS VLAN and SVI configuration. This will include the entire IP and HSRP configuration for the two switches, including the automatic assignment of the IP addresses for the virtual, primary and secondary switch.

Before we talk about the library that we will use today to solve the problem, lets have a quick look at the assumptions for our configuration template:

  • the current IP address of the switch will be the virtual HSRP IP in the target deployment
  • the current IP address is sometimes at the beginning, in the middle or at the end of an IP subnet
  • the next or previous two IP addresses are currently not in use (depending where the current gateway address is located), we prefer to use the next two addresses as physical HSRP addresses whenever possible
  • we need to authenticate the peers within the HSRP instances

Okay, now lets have a look at the ciscoconfparse library.

ciscoconfparse library

The ciscoconfparse library helps you to work with Cisco-styled configuration files in python. It is created and maintained by David Michael Pennington. The basic function of this library is to examine an IOS styled configuration and to break it into a set of parent/child relationships. After loading the configuration, you can query the configuration using regular expressions to retrieve information about it. We will use this component within our example to get all VLAN SVI interfaces, which are associated with an IP address and to retrieve information about the interface configuration.

You can use this library for any configuration that matches the Cisco format. It works also with Juniper configuration, when using the brace-delimited format. This library can be easily installed using pip in your python environment using the following command on you terminal/shell:

$ pip install ciscoconfparse

I added the library to the requirements within my python example repository on GitHub. It is installed automatically when using the bundled Vagrant machine that’s included in the repository. If you like to dive deeper into the ciscoconfparse library, you can read about it in the documentation or on the homepage of the author at http://www.pennington.net/py/ciscoconfparse/.

Parse the existing configuration

Before we can start to create the primary and secondary switch configuration, we need to extract the required information from the existing one. At first we need to import the module in our script and create an object to load the configuration:

from ciscoconfparse import CiscoConfParse
# […]
parsed_config = CiscoConfParse(input_configuration_file)

With this call, the entire configuration is loaded and the parent-child relationships are created. Now we need any SVI configuration, which has an ip address statement as a child object. To get these information, we will use the find_objects_w_child method from the CiscoConfParse instance, that we have created.

We will use two regular expressions along with this method call: the first one matches a VLAN SVI statement (parent statement) and the second one matches the IP address statement within the interface configuration (child). The following line is used within the example:

vlan_interfaces = parsed_config.find_objects_w_child("^interface Vlan", r"^ ip address.*")

This will return a python list with instances from the IOSCfgLine classes. When parsing a configuration, the CiscoConfParse will create an instance from this class for every configuration line. The result of our find_objects_w_child will return any interface statement along with the subcommands expressed in the IOSCfgLine instance.

After this query, we will use a for loop to iterate over every element within the vlan_interfaces list to get any information that is required to generate the new configuration files.

Get the required information from the current configuration

The first element that we need is the interface IP address. To get the IP address from the given vlan_interface element, we will use the re_match_iter_typed method. Purpose of this method is to search the children of the IOSCfgLine instance for the given regular expression, but the return value will have a specific type. The type can be strint, float or IPv4Obj. The IPv4Obj is exactly what we need to get the current IP address and to compute the new one. This is a ciscoconfparse specific data type which is a wrapper around the integrated IPv4Address class of the ipaddress module from python 3. To get the interface address, we will use the following lines in our example:

# […]
for vlan_interface in vlan_interfaces:
    # […]
    ipv4_addr = vlan_interface.re_match_iter_typed(r"ip\saddress\s(\S+\s+\S+)", result_type=IPv4Obj)
    virtual_ip = ipv4_addr.ip_object
    # […]

To get a string representation of the current IP address, just call the ip_object method. This will return a string representation of the IPv4 address, e.g. 10.1.1.1. We will use this address as our virtual IP for the HSRP process.

The next parameters are the interface name and VLAN ID. These are easily extracted from the interface command itself. The attribute text is available along with every IOSCfgLine instance. It contains the configuration command itself, therefore we can use the following two lines to get these two parameters:

# […]
for vlan_interface in vlan_interfaces:
    # […]
    vlan_interface_string = vlan_interface.text.lstrip("interface ")
    vlan_id = vlan_interface_string.lstrip("Vlan")
    # […]

The lstrip method on the string is bundled with the python language and will remove the given characters from the beginning of the given string.

The last two parameters that we need for the primary and secondary configuration script are the physical interface IP addresses of the switch within the VLAN. This is a little bit more complicated to generate. With the integrated IPv4 object, we can calculate with IP addresses as we would with integers, but there is no embedded mechanism to verify, that the new IP address is a host address. If we take a closer look to the documentation of the ipaddress module in the official python3 documentation, we will find a method called hosts(). This method returns any valid host address in a python list, which is available in the defined IPv4 network. To compute the physical addresses, we will use the following lines of code in our example:

# […]
for vlan_interface in vlan_interfaces:
    # […]
    ipv4_network = IPv4Network(ipv4_addr.network)
    if (ipv4_addr.ip_object + 1) in ipv4_network.hosts():
        primary_ip = ipv4_addr.ip_object + 1
        secondary_ip = ipv4_addr.ip_object + 2
    else:
        primary_ip = ipv4_addr.ip_object - 1
        secondary_ip = ipv4_addr.ip_object - 2
    # […]

We do the following with these lines of code: First, we need to convert the IPv4Obj from the ciscoconfparse library to a IPv4Network object from the ipaddress module, to get access to the hosts() method. Within the if-statement, we’ll check if the next IP address after the current IP is a valid host IP using the hosts-list. If the address is a valid host address, we will simply use this IP for the primary switch, and the next one for the secondary switch. If this is not the case, we simple use the previous IP addresses. Remember our assumption at the beginning, the next two or the previous two IP addresses must be unassigned.

At the if statement from the example code, you can see how easy it is to work with complex data structures in the python language.

Now we need just two more variables: the subnetmask and the HSRP authentication key. The subnet mask can be easily read from the IPv4Obj using the netmask attribute. The HSRP authentication key is generated using the VLAN ID with the prefix vl. It must not be very secure at this point. The key is just to avoid collisions with other potential HSRP processes within the segment. Within the configuration script, we will also set the HSRP priority values for the primary switch to 255 and for the secondary switch to 254.

Okay, now we have everything that’s required to generate our configuration script.

Create the primary and secondary script

I will not use Jinja2 to generate templates, as outlined in some earlier posts. Today, I will use the ciscoconfparse object to create the configuration.

The process to create a configuration for the primary and secondary switch is quite similar, therefore I will only describe the process for the primary switch. The first step is again to create a CiscoConfParse instance without any input file. Within the example code on GitHub, I add a list with some comments to the constructor of the primary and secondary configuration. After this, you can add the configuration commands as you would do it in notepad using the method “append_line”. The last step is to write these file to our _output directory using the method save_as.

All these steps are accomplished using the following lines of code:

# […]
primary_config = CiscoConfParse([
    "!",
    "! primary switch interface configuration",
    "!",
])
# […]
for vlan_interface in vlan_interfaces:
    # […]
    primary_config.append_line("interface %s" % vlan_interface_string)
    primary_config.append_line(" description *** VLAN SVI %s" % vlan_id)
    primary_config.append_line(" ip address %s %s" % (primary_ip, ipv4_addr.netmask))
    primary_config.append_line(" standby version 2")
    primary_config.append_line(" standby 1 ip %s" % virtual_ip)
    primary_config.append_line(" standby 1 priority 255")
    primary_config.append_line(" standby 1 authentication md5 key-string vl%s" % vlan_id)
    primary_config.append_line("!")
    # […]
primary_config.save_as(os.path.join(output_directory, primary_configuration_file))
# […]

Now you can find the result in the output directory, which is in case of the example code again a _output folder next to the python script.

Conclusion

This time, the script is again a bit larger, but it saved a lot of my time already. I tested it with a configuration that has more than 220 VLAN SVIs and it works perfect. At this point, I highly recommend that you take the time and have a look at the entire create-hsrp-interface-configuration.py python script on GitHub. This time I decide to dig deeper into in the details of the implementation to improve the understanding, how to work with it. The ciscoconfparse library is very powerful from my perspective when working with existing configurations and it is definitive one of my favorites when working with python.

If you need to use this script for your own configuration, you just need to replace the content of the “cisco_ios_vlans.txt” file with your current configuration. The output file is dropped in the _output folder within the repository, named primary_config.txt and secondary_config.txt. If you need to execute it without affecting your current environment, you can either use a python virtual environment or you have a look at my Vagrant post in the related posts at the bottom of this page.

As always, you can leave your feedback and comments in the comment section below. That’s it for this week. Thanks for reading.