2.04.2013

Cisco VOIP phone device information gathering

Our network team needed to true-up maintenance support, and validate inventory for our 6000+ Cisco IP Phones.


So I wrote a python script to provide the following information, via switch CDP discovery and dumping the phone's device details via http from the phones themselves:
  • Switch, Port
  • PhoneName
  • IP
  • Platform(From CDP)
  • SerialNumber
  • MACAddress
  • ModelNumber 

In a nutshell, the script performs the following:
  • logs into each of the access switches provided (fed with file input, one per line) 
  • via CDP neighbor discovery grabs all devices named SEP* (standard Cisco convention)
  • from that list, runs "cdp neighbor <int> detail" to get Platform and IP info for each discovered phone
  • from the IP information, grabs the devices extended information via http (xml output)
  • parses the xml and returns output as a csv file suitable for importing to a spreadsheet



#!/usr/bin/env python
"""
Mike Orstead
01/2013
http://stewpid-litterbox.blogspot.com/

Utility script to get Cisco IP phone device information through a combination 

of Cisco switch info (CDP) and http grabs from the device themselves (xml).

Pre-reqs:

-switch access with a common username/password.  User needs ability to run 
cdp neighbor discover and "term len 0" (so command output is not run 
through a pager) 
-Update inputfile(s) below with site specific info (or modify to accept file location via user input)

NOTES and WARNINGS:-This script instance assumes that the CDP discover finds phones with 

device names SEP*, script may be easily modified for other conventions.
-We have Cisco Video Conferencing devs that need to be handled, as IP address 
information is not returned with CDP (CTS_CODEC). Other devices may need to 
be handled similarly in your environment. Search reCODEC in the code below.


"""

# Import Python Modules
import os, datetime
import sys
import pexpect
import re
import getpass
import urllib
from lxml import etree

date = datetime.datetime.now()

datestamp = date.strftime("%Y%m%d") 

def main():

    source_choices = 'Run script against Site1 or Site2 switches? '
    while True:
        source_answer = raw_input(source_choices).lower()
        if source_answer == 'site1':
            source = 'Site1'
            break
        elif source_answer =='site2':
            source = 'Site2'
            break
        else:
            print "Invalid choice" + "\n"

    userName = raw_input('Username: ')

    userPassword = getpass.getpass('Password: ')

    opspath = '/example-path/'

    outputpath = opspath+'CIPphoneinfo_Output/'

    # get the switches from proper inputfile

    if source == 'Site1':
        # TODO: should have a 'try' here
        inputfile = open(opspath+'Site2_ACCESS_SWITCHES.txt')
        ofile = 'cipinfo_Output_Site1.' + datestamp + '.csv'
        outputfile = file(outputpath+ofile,'w')
    elif source == 'Site2':
        inputfile = open(opspath+'Site2_ACCESS_SWITCHES.txt')
        ofile = 'cipinfo_Output_Site2.' + datestamp + '.csv'
        outputfile = file(outputpath+ofile,'w')

    output = {}

    for hostname in inputfile:
        phonelist = []
        hostname = hostname.rstrip('\n')
        logfile = sys.stdout
        tmpf = os.tmpfile()
        print 'Connecting to switch: ' +  hostname + ' and discovering phones'
        # TODO:  should catch a TIMEOUT here in the pexpect
        router = pexpect.spawn('ssh '+userName+'@'+hostname)
        router.expect('word:')
        router.sendline(userPassword)
        router.expect('#')
        router.sendline('term len 0')
        router.expect('#')

        # Gather device name / port 

        router.logfile = tmpf
        router.sendline('show cdp neighbor | include SEP')
        router.expect('#')
        router.logfile = None

        rePrompt = re.compile(hostname)


        #output = {}

        # iterate through the tmpfile, build the output dict entries
        reEndLine = re.compile('\r\n')
        reShowCDP = re.compile(r'show cdp neighbor*')
        reCODEC = re.compile(r'CTS-CODEC')
        tmpf.seek(0)
        buf = tmpf.readlines()

        for line in buf:

            line = reEndLine.sub('', line)
            # Skip these conditions from further discovery:
            if re.match(reShowCDP,line): continue
            if re.match(rePrompt,line): continue
            if re.search(reCODEC,line): continue

            output[line.split()[0]] = {

                "switch":   hostname, 
                "name":     line.split()[0],
                "port":     line.split()[1] + " " + line.split()[2]
                }
            phonelist.append(line.split()[0])
        tmpf.close()
        
        i = 0
        for x in output:
            if output[x]['switch'] == hostname:
                i += 1
        numberphones = i
        
        print "Found " + str(numberphones) + " phones on " + hostname
    
        print "Grabbing phone details from switch " + hostname + ", and connecting to phones web interfaces for extended info"
        for phone in phonelist:
            tmpf2 = os.tmpfile()
            router.logfile = tmpf2
            router.sendline('show cdp neighbor '+output[phone]['port']+' detail')
            router.expect("#")
            router.logfile = None

            reEndLine2 = re.compile('\r\n')

            reShowCDP2 = re.compile(r'show cdp neighbor*')
            rePrompt2 = re.compile("^.*#")
            tmpf2.seek(0)
            buf = tmpf2.readlines()
            for line in buf:
                line = reEndLine2.sub('', line)
                if re.match(reShowCDP2,line): continue
                if re.match(rePrompt2,line): continue
                findIP = re.search('\s+IP address:\s+(\d+\.\d+\.\d+\.\d+)',line)
                findPlat = re.search('Platform:\s+(.+),',line)
                if findPlat:
                    output[phone]['platform'] = findPlat.group(1)
                if findIP:
                    output[phone]['ipaddy'] = findIP.group(1)
                    # connect to http://<ipaddy>/DeviceInformationX to grab xml
                    url = 'http://' + output[phone]['ipaddy'] + '/DeviceInformationX'
                    try:
                        x = urllib.urlopen(url)
                    except IOError:
                        output[phone]['serialnum'] = "UNKNOWN - can't connect to " + url
                        output[phone]['modelnum'] = "UNKNOWN - can't connect to " + url
                        output[phone]['MAC'] = "UNKNOWN - can't connect to " + url
                        continue

                    try:

                        xmltree = etree.parse(x)
                    except:
                        # General Error catchall
                        output[phone]['serialnum'] = "UNKNOWN - can't parse xml"
                        output[phone]['modelnum'] = "UNKNOWN - can't parse xml"
                        output[phone]['MAC'] = "UNKNOWN - can't parse xml"
                        continue
                    else:
                        for ele in xmltree.iter("serialNumber"):
                            output[phone]['serialnum'] = ele.text
                        for ele in xmltree.iter("MACAddress"):
                            output[phone]['MAC'] = ele.text
                        for ele in xmltree.iter("modelNumber"):
                            output[phone]['modelnum'] = ele.text
            tmpf2.close()
            
    # outfile header row
    outputfile.write("Switch,Port,PhoneName,IP,Platform(From CDP),SerialNumber,MACAddress,ModelNumber\n")  
    # Populate UNKNOWN where keys are missing. 
    for key in sorted(output.iterkeys()):
        if not output[key].has_key('serialnum'): output[key]['serialnum'] = "UNKNOWN"
        if not output[key].has_key('platform'): output[key]['platform'] = "UNKNOWN"
        if not output[key].has_key('ipaddy'): output[key]['ipaddy'] = "UNKNOWN"
        if not output[key].has_key('modelnum'): output[key]['modelnum'] = "UNKNOWN"
        if not output[key].has_key('MAC'): output[key]['MAC'] = "UNKNOWN"
        outputfile.write(output[key]['switch']+","+output[key]['port']+","+output[key]['name']+","+output[key]['ipaddy']+","+output[key]['platform']+","+output[key]['serialnum']+","+output[key]['MAC']+","+output[key]['modelnum']+"\n")

    print "script complete - results can be found in: "+opspath+'CIPphoneinfo_Output/'+ofile

    router.sendline ('exit')
    sys.exit(0)

if __name__ == "__main__":

    # We are running this module directly, rather than via import
    main()

2 comments:

Unknown said...

even though there are a lot of VoIP devices in the market today, cisco products are still on top and one of the most trusted brand

Unknown said...

Csico is one of the top products as there are so many Voip hardware with lot more styles and different shapes, but still I love my cisco SPA 502g with great features and call forwarding.