When using Cisco SD-WAN on IOS-XE, it uses tunnel interfaces to configure parameters of the implementation. There is a mapping between what interface the tunnel is sourcing from and the name of the tunnel interface. For example, if the tunnel source is GigabitEthernet0, the tunnel interface is Tunnel0, if the tunnel source is GigabitEthernet0.100, the tunnel interface is Tunnel100000. When provisioning a router and not using Zero Touch Provisioning (ZTP), you build a small bootstrap configuration that configures mandatory parameters such as Site ID, System IP, Organization Name, but also a tunnel interface to be able to connect to the controllers. It is possible to create this configuration in vManage, and hence find out the tunnel interface name, but I thought it would be interesting to do this with code and not be dependent on vManage.

In this post, I will describe the code I used and what my logic was when creating different parts of the code. In this first post I will use the code that I came up with. In the second part, my friend Rodrigo who runs an excellent Python blog ,analyzed my code and came up with improvements, which I will describe in that post, the third post will be how Rodrigo could do the same thing I did in the first post with a lot less code.

Before we start, we need to understand how to calculate the tunnel number. I hadn’t seen this described in the documentation until I saw it in training material for the ENSDWI course. The formula is this:

As can be seen in the formula, the tunnel interface name is dependent on the amount of numbers in the interface name, their position, and if using a subinterface.

What I found interesting when trying to turn this into code is how a simple formula like this was still challenging for me, a beginner in Python, to express in code. The first thing that we need to do is to take an interface name and separate the numbers from the letters. That is, if the interface name is GigabitEthernet1/0/0, it’s only 1, 0, and 0 that are interesting. I started out doing this manually by trying to use different builtin functions like split, trying to separate on a slash or dot. I also got suggestions to use regex and many other great suggestions. Then someone suggested that I use the Python netutils package developed by Network To Code. As it turns out, this package has a function to do exactly this. As my focus is not on parsing text, and these people are definitely better coders than I am, I decided to use their code. Below is an example how to use the split_interface function:

In [3]: split_interface(interface)
Out[3]: ('GigabitEthernet', '0/0/0')

In [4]: split_interface("GigabitEthernet0.400")
Out[4]: ('GigabitEthernet', '0.400')

The function returns a list where the letters are at index 0 and the numberic part, including slashes and dots, is at index 1.

Now let’s back to the script. The first part is defining the function, including type hints, and a doc string:

def calculate_tunnel_number(interface_name:str) -> int:
    """Calculate the tunnel number in Cisco SD-WAN
    The number in the interface is multiplied by
    1, 10, or 100 depending on position. If subinterface
    is used, subinterface number is multiplied by 1000"""

We expect to get an interface name, in the form of a string, and will return an integer.

To calculate the tunnel number, we need to keep track of the “score”, that is, in an interface name like GigabitEthernet0/0/1.100, one “point” is added for the first one and then 100 000 for the subinterface, adding up to 100001. This is done below:

# Keep track of total score for tunnel number
    total_score = 0

The next part is to get the interesting part of the interface name using the split_interface function. As I mentioned before, this will be at index 1 in the list returned by the function:

# First get interface number by splitting the interface name
interface_number = split_interface(interface_name)[1]

If we are working with a subinterface, the logic is different than for a physical interface. For that reason, I’m looking for a dot in the interface_number variable:

 # Check if it is a subinterface by looking for a dot
 # If dot is found, take number after dot and multiply by 1000
 if "." in interface_number:

If it is a subinterface, then split the string based on the dot, multiply what comes after the dot with 1000 and add to the score. Note that the string needs to be converted to an integer first:

# Split string to get subinterface number and convert to integer
subinterface = int(interface_number.split(".")[1])
total_score += subinterface * 1000

After the calculation is done, I want to do the following:

  • Remove the subinterface part from interface name, for example going from GigabitEthernet0/0/0.100 to GigabitEthernet0/0/0
  • Create a list with the numbers from the interface name, 0, 0, and 0 in the example above
  • Convert the strings to integers
# If subinterface, need to create a list that does not have subif in it
# First element is interface number, second is subinterface
interface_number_list_temp = interface_number.split(".")
# Create new list with only interface number and not subinterface
interface_number_list = interface_number_list_temp[0].split("/")
interface_number_list_int = [int(number) for number in interface_number_list]

The last row above is a list comprehension which returns a list of integers for every string in interface_number_list.

If not using a subinterface, we don’t need code to handle subinterfaces:

else: 
        # Split interface number based on / and keep in list
        interface_number_list = interface_number.split("/")
        # Convert list containing strings to integers using list comprehension
        interface_number_list_int = [int(number) for number in interface_number_list]

Then we do calculations based on the length of the list:

# Depending on length of list, perform different calculations
if len(interface_number_list_int) == 3:
    total_score += 1 * interface_number_list_int[2] + 10 * interface_number_list_int[1] + 100 * interface_number_list_int[0]
elif len(interface_number_list) == 2:
     total_score += 1 * interface_number_list_int[1] + 10 * interface_number_list_int[0]
else:
    total_score += 1 * interface_number_list_int[0]
return total_score

Finally, we run the script:

tunnel_number = calculate_tunnel_number("TenGigabitEthernet0/0/6")
print(f"The tunnel name is Tunnel{tunnel_number}")

This will produce the following output:

The tunnel name is Tunnel6

Here is the complete code:

from netutils.interface import split_interface

def calculate_tunnel_number(interface_name:str) -> int:
    """Calculate the tunnel number in Cisco SD-WAN
    The number in the interface is multiplied by
    1, 10, or 100 depending on position. If subinterface
    is used, subinterface number is multiplied by 1000"""
    # Keep track of total score for tunnel number
    total_score = 0
    # First get interface number by splitting the interface name
    interface_number = split_interface(interface_name)[1] 
    # Check if it is a subinterface by looking for a dot
    # If dot is found, take number after dot and multiply by 1000
    if "." in interface_number:
        # Split string to get subinterface number and convert to integer
        subinterface = int(interface_number.split(".")[1])
        total_score += subinterface * 1000
        # If subinterface, need to create a list that does not have subif in it
        # First element is interface number, second is subinterface
        interface_number_list_temp = interface_number.split(".")
        # Create new list with only interface number and not subinterface
        interface_number_list = interface_number_list_temp[0].split("/")
        interface_number_list_int = [int(number) for number in interface_number_list]
    else: 
        # Split interface number based on / and keep in list
        interface_number_list = interface_number.split("/")
        # Convert list containing strings to integers using list comprehension
        interface_number_list_int = [int(number) for number in interface_number_list]
    # Depending on length of list, perform different calculations
    if len(interface_number_list_int) == 3:
        total_score += 1 * interface_number_list_int[2] + 10 * interface_number_list_int[1] + 100 * interface_number_list_int[0]
    elif len(interface_number_list) == 2:
         total_score += 1 * interface_number_list_int[1] + 10 * interface_number_list_int[0]
    else:
        total_score += 1 * interface_number_list_int[0]
    return total_score

tunnel_number = calculate_tunnel_number("TenGigabitEthernet0/0/6")
print(f"The tunnel name is Tunnel{tunnel_number}")

It’s always interesting solving these small challenges with code. I’m a beginner in Python and made many mistakes but the code runs! In the next part, we’ll look at some of the improvements as suggested by a more experienced coder.

Using Python to Calculate Cisco SD-WAN Tunnel Numbers – Part 1
Tagged on:     

3 thoughts on “Using Python to Calculate Cisco SD-WAN Tunnel Numbers – Part 1

Leave a Reply

Your email address will not be published. Required fields are marked *