Skip to content
Homepage » Blog » Cisco WLC 9800 Python Script: devices with the APIPA address

Cisco WLC 9800 Python Script: devices with the APIPA address

When it comes to networking and IP addresses, there’s a good chance you’ve come across the term ‘APIPA’ at some point, especially if you’ve ever faced connectivity issues. Standing for Automatic Private IP Addressing, APIPA is a feature of Windows-based operating systems (though similar mechanisms exist in other systems) designed to allocate IP addresses automatically when a DHCP server isn’t available.

As my Wi-Fi client’s network expanded to thousands of users, an intermittent issue arose: some were consistently assigned APIPA addresses despite a functional DHCP server. This sporadic problem posed significant challenges. To tackle it, I developed a script that swiftly identified clients with APIPA addresses, streamlining the troubleshooting process.

What is needed?

First of all, we need a Python 3.3 or higher environment and the following files which are part of the proper functioning of the script. These files should be placed in a single folder:

In addition, it is necessary to ensure the availability of the following libraries:

  • netmiko
  • time
  • json
  • textfsm

Modules description

Before I start describing the script, let’s see how the individual modules work.

Module: connecthandlerc9800

This Python script is designed to automate interactions with a Wireless Controller, using the Netmiko library. It’s a simple yet effective tool for network engineers to execute commands on network devices and manage them remotely.

2. Importing Libraries and Functions: The script begins by importing necessary modules – ConnectHandler from netmiko for handling network connections and time to track the duration of operations.

from netmiko import ConnectHandler
import time

2. Function Definition – connecthandlerc9800:

This function takes two parameters: device (containing details about the network device) and send_command (the command to be executed on the device). The function first records the start time, then establishes a connection to the network device using the details provided in the device dictionary. It sends the specified command to the device and waits for the output (with a generous read timeout of 320 seconds to accommodate slower responses). After executing the command, the script disconnects from the device, records the end time, and calculates the total operation time. Finally, it returns the output of the executed command.

def connecthandlerc9800(device, send_command):
    # Record the start time of the operation.
    start_time = time.time()
    # Establish a connection to the network device using Netmiko.
    connect = ConnectHandler(**device)
    # Send the command to the device and store the output.
    send_command_output = connect.send_command(send_command, read_timeout=320)
    # Disconnect from the device.
    connect.disconnect()
    # Record the end time of the operation.
    end_time = time.time()
    # Calculate the total time taken for the operation.
    total_time = end_time - start_time
    if __name__ == '__main__':
        print(f'\n Total time script execution: {round(total_time, 3)} \n')
    # Return the output of the sent command.
    return send_command_output

3. Main Execution Block:

This part of the script runs if the script is executed as the main program (not imported as a module). It prompts the user to input details of the network device (IP address, device type, credentials, port, and secret password). These details are stored in a device dictionary. The script then calls the connecthandlerc9800 function with the device details and a command provided by the user. The output of the command executed on the network device is then displayed.

if __name__ == '__main__':
    # Collect device details and credentials from the user.
    device = {
        'host': input("Provide the device's IP address: "),
        'device_type': 'cisco_ios',
        'username': input("Enter your username: "),
        'password': input("Enter your password: "),
        'port': 22,
        'secret': input("Enter your secret password: "),
    }
    # Execute the connect handler to send a command to the device and receive output.
    send_command_output = connecthandlerc9800(device, input('Provide the command to send: '))
    print(f'Object created: \n \n {send_command_output}')

Module: parsedtextfsmc9800

This module is particularly useful for turning complex command outputs into more structured and readable formats.

1. Importing Required Modules: The script imports the textfsm module, which is essential for parsing text into structured data.

#!/usr/bin/env python3
import textfsm

2. Function parsedtextfsm:

This function takes two arguments: template (the path to a TextFSM template file) and send_command_output (the raw output from a network device command).
It uses TextFSM to parse the command output according to the specified template. The function converts the parsed output into a list of dictionaries, where each dictionary represents a structured interpretation of a portion of the original output. This parsed data is returned in a list format, making it easier to read and process.

def parsedtextfsm(template, send_command_output):
    # Initialize an empty list to hold dictionaries of parsed text.
    parsed_text_list_dict = []

    # Open the provided TextFSM template file.
    with open(template, 'r') as temp:
        # Create a TextFSM object for parsing the command output.
        fsm = textfsm.TextFSM(temp)
        # Parse the command output using the TextFSM template.
        parsed_text = fsm.ParseText(send_command_output)

    # Convert the parsed output into a list of dictionaries.
    for element in parsed_text:
        parsed_text_dict = dict(zip(fsm.header, element))
        parsed_text_list_dict.append(parsed_text_dict)

    # Return the list of dictionaries containing the parsed data.
    return parsed_text_list_dict

3. Main Execution Block:

This part of the script runs if the script is executed as the main program (not imported as a module). The script prompts the user for details about a network device (IP address, device type, credentials, etc.) and stores these details in a device dictionary. It records the time before and after executing a command on the network device (using the connecthandlerc9800 function from another module) to measure the operation’s duration.
After receiving the raw command output, the script asks the user for the name of a TextFSM template file to use for parsing. The raw output is then parsed using the parsedtextfsm function and the specified TextFSM template.
The parsed data is saved into a JSON file, providing a structured and easily readable format.

if __name__ == '__main__':
    import connecthandlerc9800
    import json
    import time

    # Collect device details and credentials from the user.
    device = {
        'host': input("Provide the device's IP address: "),
        'device_type': 'cisco_ios',
        'username': input("Enter your username: "),
        'password': input("Enter your password: "),
        'port': 22,
        'secret': input("Enter your secret password: "),
    }

    # Record the start time of the operation.
    start_time = time.time()
    # Execute the connect handler to send a command to the device and receive output.
    send_command_output = connecthandlerc9800.connecthandlerc9800(device, input('Provide the command to send: '))
    # Record the end time of the operation.
    end_time = time.time()
    # Calculate the total time taken for the operation.
    total_time = end_time - start_time
    print(f'Output received:\n{send_command_output}')
    print(f'Time needed to get the output: {round(total_time, 3)}')

    # Get the name of the TextFSM template from the user.
    template_name = input('Provide the template name: ')
    # Create a filename for the JSON output.
    parsed_text_json = f'parsed_text_json_{template_name}'

    # Parse the command output using the provided TextFSM template.
    parsed_text_list_dict = parsedtextfsm(template_name, send_command_output)

    # Save the parsed output as a JSON file.
    with open(parsed_text_json, 'wt') as file:
        json.dump(parsed_text_list_dict, file, indent=4)

    print(f'File created and saved: {parsed_text_json}')

TextFSM template description

Since the topic of templates used with the TextFSM library is extensive, I will focus only on the general structure of these templates and how they work using the example of the template that is used for this script.

By using the template with TextFSM, the script can convert a complex, multi-line text output from a network device into a structured list of records, where each record in my example below is a dictionary containing key-value pairs of data points defined in the template. This makes it easier to process, analyze, and use the data programmatically.

In the template, we can distinguish several keypoints such as:

  • Values: Each Value line in the template specifies a key data point to extract from the text output. For example, Value MAC_Address ([a-fA-F0-9:.]+) defines a pattern to match a MAC address in the output. The pattern inside the parentheses is a regular expression that describes what the MAC address looks like.
  • Patterns: The template uses regular expressions to identify and extract specific pieces of data. For example, IP_Address captures a standard IP address format, and SSID captures the SSID names, which could include both alphanumeric characters and spaces.
  • State Definitions: The Start and Clients are state definitions. They control the flow of parsing and tell TextFSM when to start applying the template and how to identify different sections of the output.
  • Record Creation: The line under Clients beginning with ^${MAC_Address} is a rule that tells TextFSM how to recognize a line of output that contains all the defined values. When such a line is found, TextFSM extracts each value according to the defined patterns and stores them in a dictionary.

Below is the template that is used to convert the output of the command on the Cisco 9800 controller – show wireless client summary detail ipv4 – into a more readable and organized format, which can then be used for various purposes.

Value MAC_Address ([a-fA-F0-9:\.]+)
Value SSID ([\S ]+?)
Value AP_Name (\S+)
Value State (\S+)
Value IP_Address (\d+\.\d+\.\d+\.\d+)
Value Device_type ([\S ]+?)
Value VLAN (\d+)
Value BSSID ([a-fA-F0-9:\.]+)
Value Auth_Method (\[.+?\])
Value Created ([\d+:]+)
Value Connected ([\d+:]+)
Value Protocol (\S+)
Value Channel (\d+)
Value Width ([\d+/]+)
Value SGI ([A-Z/]+)
Value NSS ([\d/]+)
Value Rate ((m\d+\s+ss\d+)|(m\d+)|(\d+\.\d+)|())
Value CAP (EVRK|EV\sK|EV|E)
Value Username (.*?)
Value Rx_packets (\d+)
Value Tx_packets (\d+)
Value Rx_bytes (\d+)
Value Tx_bytes (\d+)

Start
  ^-+ -> Clients

Clients
  ^${MAC_Address}\s+${SSID}\s+${AP_Name}\s+${State}\s+${IP_Address}\s+${Device_type}\s+${VLAN}\s+${BSSID}\s+${Auth_Method}\s+${Created}\s+${Connected}\s+${Protocol}\s+${Channel}\s+${Width}\s+${SGI}\s+${NSS}\s+${Rate}\s+${CAP}\s+${Username}\s+${Rx_packets}\s+${Tx_packets}\s+${Rx_bytes}\s+${Tx_bytes} -> Record

APIPA_report script description

Finally, we can move on to the description of the script, now knowing what each module is responsible for. As I wrote, this script is useful for wireless network administrators who need to quickly identify and document clients with APIPA addresses in a network. By automating this process and structuring the output is in a JSON file. This is what the components of this script look like:

1. Importing modules:

The script imports several modules: connecthandlerc9800, parsedtextfsmc9800, json, and time.
connecthandlerc9800 and parsedtextfsmc9800 are custom modules for connecting to network devices and parsing text outputs, respectively.
json is used for handling JSON data, and time is for tracking the script’s execution time.

#!/usr/bin/env python3
import connecthandlerc9800
import parsedtextfsmc9800
import json
import time

2. Collecting Device Information:

The script prompts the user for details about a network device, storing this information in the device dictionary.
The details include the device’s IP address, type, username, password, port, and secret password.

device = {
    'host': (input("Provide the device's IP address: ")),
    'device_type': 'cisco_ios',
    'username': (input("Enter your username: ")),
    'password': (input("Enter your password: ")),
    'port': 22,
    'secret': (input("Enter your secret password: ")),
}

3. Executing Network Commands:

The command to be sent to the network device (show wireless client summary detail ipv4) and the TextFSM template name (wlc_c9800_show_wireless_client_summary_detail_ipv4) are defined. The connecthandlerc9800 module is used to send the command to the device and capture the output.

# Define the command and the TextFSM template for parsing its output.
send_command = 'show wireless client summary detail ipv4'
client_summary_detail_ipv4_template = 'wlc_c9800_show_wireless_client_summary_detail_ipv4.txt'

# Execute the command and get its output.
send_command_output = connecthandlerc9800.connecthandlerc9800(device, send_command)

4. Parsing Command Output:

The script uses the parsedtextfsmc9800 module to parse the raw command output using the specified TextFSM template. The parsed data is structured for further analysis.

# Parse the command output using the specified TextFSM template.
parsed_text_client_summary_detail_ipv4 = parsedtextfsmc9800.parsedtextfsm(client_summary_detail_ipv4_template, send_command_output)

5. Filtering APIPA Addresses:

The script identifies clients with APIPA addresses (beginning with ‘169.254’) from the parsed data. These clients are added to the apipa list for further actions.

# Filter out clients with APIPA addresses and store them in a list.
apipa = []
for el in parsed_text_client_summary_detail_ipv4:
    if el['IP_Address'].startswith('169.254'):
        apipa.append(el)

6. Saving Data to JSON File:

The clients with APIPA addresses are saved to a JSON file, providing a structured and accessible format for the data.

# Save the list of clients with APIPA addresses to a JSON file.
with open("apipa_file_json", 'wt') as f:
    json.dump(apipa, f, indent=4)

7. Execution Time Measurement:

The script calculates the total time taken for execution, from start to end, and prints this information.
This helps in assessing the script’s efficiency.

# Calculate and print the total execution time of the script.
end_time = time.time()
minutes = (end_time - start_time) // 60
seconds = (end_time - start_time) % 60
print("Script time execution:", int(minutes), "minutes", round(seconds, 1), 'seconds')
print(f'File created and saved: apipa_file_json')

Summary

This script is an efficient automation tool for wireless network administrators, designed to identify clients with Automatic Private IP Addressing (APIPA) in a network. It starts by collecting network device details and executing a specified command, then parses the output using TextFSM template to structure the data. Ultimately, it filters and saves the information of clients with APIPA addresses to a JSON file, providing a clear and accessible record of these specific network clients.

5 1 vote
Article Rating
Subscribe
Notify of
guest

0 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x