Python run commands on a router/switch (SSH)

Started by dlots, June 06, 2016, 01:16:35 PM

Previous topic - Next topic

dlots

I was wondering if there is any standard way to run a command on a router/switch via SSH in python.  I am having issues finding a way that I like (or has lots of examples) to SSH to the router/switch.

Curretly I want
term len 0
ping 255.255.255.255
show arp table
sh mac address-table
sh cdp en *
show interface


wintermute000

Try netmiko for ease or paramiko for OG python way
I've also stabbed myself in nuts with exscript before.

that1guy15

yeah Netmiko will be the best way to go.

If you are looking for some good free training for python and networking programming in general that is targeted towards network engineers then check out Jason Edelmon's free series here. Its a hosted lab series in the cloud so no setup on your end. Its a fun series.
http://jedelman.com/home/on-demand-network-labs/

I also did his Network-to-Code corporate training through work and its was awesome! Good launching board.
That1guy15
@that1guy_15
blog.movingonesandzeros.net

dlots

Thank you

I am currently using paramiko, but with that I have to login to the router/switch for every command I want to run, which takes quite a while, I hadn't heard of Netmiko before, I'll give that a shot.

dlots

Incase someone finds this looking for the same question here is the answer.

import os
import re
import netmiko
import time

#Take the output of the command and put them in a file
def to_doc(file_name, varable):
f=open(file_name, 'w')
f.write(varable)
f.close()


net_connect = netmiko.ConnectHandler(device_type='cisco_ios', ip='IP', username='username', password='password')
#The commands we will run
commands = ['sh arp','sh mac address-table','sh int']

for command in commands:
output = net_connect.send_command(command)
#Sleep is needed if the command is going to take a long time, such as pinging to fill the arp table
if command == 'ping 255.255.255.255 repeat 2':
time.sleep(10)
#make the output a file, with the name of the command used to run it, in this example "output" is a string.
to_doc(command, output)


dlots

Got a program mostly working that reads in a file full of IPs, SSHs to each of them, pulls CDP neighbors, assuming it gets new IPs from the CDP info it adds those into a list of IPs to SSH to this program spits out a list of IPs and connections, another program makes that into a graphml map of the network.  It still has some issues, but if I can get those fixed I'll be putting it up on github.

It use to have only 1 starting point, this is my 1st try with having lots of IPs to work with, but the largest map I have done so far found and mapped 4.7k devices (REALLY wish I could put the map on here... it's soooo cool!!).

that1guy15

Bad ass dude. Would love to check out what you have going on.
That1guy15
@that1guy_15
blog.movingonesandzeros.net

dlots

#8
Please note this is my 2ed program ever, and it re-uses lots of code from my 1st, I make no claim to being a programer... not even a little.

I'll do another post once it's clean, but here is where it is now.
The 1st program looks for a file in the same folder called "ACS devices.txt" with some starter point IP addresses, then finds neighbors off those IPs, it spits out a file called "resultes.txt" (This currently has a blank line at the start you have to manually delete)

#Program starts off with a list of IP address in a file in the same folder as this file
#It pulls CDP info out of the IP addresses in the file, then adds those IPs to it's list to pull CDP info from.
#Currently it only does router/switches, no nexus support, and I don't have a wide varity of router/switches to test it on


import os
import re
import socket
import sys
import netmiko
import time

#Pulls the local int from CDP
def local_int(line):
tmp = line.split("#")[1]
return tmp.rstrip(',')

#Tries to Pull the local int from CDP
def remote_int(line):
try:
return remove_return(line.split("#")[6])
except:
return "broken"
#Gets the IP from CDP
def get_ip (line):
try:
return remove_return(line.split("#")[3])
except:
return "broken"
#Gets rid of the return on the line so instead of "IP address (new line)" you just get the IP address
def remove_return(entry):
tmp = entry.rstrip('\n')
return tmp
#gets rid of extra spaces and makes each space into #, this makes running the split command much easier
def no_extra_spaces(line):
lst = ""
for each_letter in line:
if each_letter != " ":
lst=lst+each_letter
if each_letter == " ":
lst=lst+"#"
for pound in lst:
lst = lst.replace("##", "#")
return lst
#If 1st char in a line is a space it gets rid of it, makes data more standard between system types
def no_inital_space(line):
    if line[0] == "#":
        return line[1:]
    else:
        return line
def cdp_entry (line):
return remove_return(line.split("#")[2])
#runs though the CDP info, and spits it out to a file you end up with something like
#This Devices IP 10.10.10.1 IP address 10.10.10.2 CDP-Entry dev-name local int GigabitEthernet0/1 remote int FastEthernet5/16,
#Each section is seperated by a tab (\t) to make the split command easy later on
#It has an empty line at the start you have to delete manually (need to fix this)

#This is it reading in the CDP info
def read_in_cdp_v2 (input):
line_number = 0
add_this=False
for line in open(input, 'r').readlines():
line = no_extra_spaces(line)
if "-----" in line and line_number != 0:
try:
if ip_address not in cdp_done:
q.write(",\nThis Devices IP\t")
q.write(other_devices[hard_stop])
q.write ("\tIP address\t")
q.write (ip_address)
q.write("\tCDP-Entry\t")
q.write(cdp_name)
q.write ("\tlocal int\t")
q.write (local_interface)
q.write ("\tremote int\t")
q.write (remote_interface)
cdp_done.append(ip_address)
if add_this == True:
other_devices.append(ip_address)
add_this=True
except:
print(other_devices[hard_stop],"Something didn't work, generally getting a CDP neighbor's IP address")
                #This is saying "don't add these CDP neighbors if they are something I don't support, like an AP,WLC,Nexus,etc
if "Device#ID:" in line and 'ap' in line:
add_this=False
print ("SSHing to: ",add_this)
if "Device#ID:" in line:
cdp_name = cdp_entry(line)
if "SEP" in line:
add_this=False
if "AIR" in line:
add_this=False
if "Interface:#" in line:
local_interface = (local_int(line))
remote_interface = (remote_int(line))
if "IP#address:" in line:
ip_address = (get_ip(line))
if "Platform: AIR" in line:
add_this=False
if "Cisco Nexus Operating System" in line:
add_this=False
line_number = line_number +1
#deletes old document with the file_name, and creates a new doc with the same name, and put a string into it
def to_doc(file_name, varable):
f=open(file_name, 'w')
f.write(varable)
f.close()
other_devices = []
#Read in the list of IP address used for a starting point, I need to fix this, the file name is hard coded, so it doesn't matter what I pass to it
def read_in_ACS(acs_name):
for line in open('ACS devices.txt', 'r').readlines():
tmp = line.rstrip('\n')
other_devices.append(tmp)
#The counter for how many devices you have tried to SSH to
hard_stop = 0
#List of devices you have already SSHed to
cdp_done = []

#These were used to put in a username/password, and a starting point, but it lost the account info after the 1st
#SSH and I needed multiple starting points

#device_to_check=input("IP to check: ")
#your_user_name = input("Username: ")
#your_password = input("Password: ")

#Where you store the CDP info this pulls down
q=open('resultes.txt', 'w')

#Just a text file with IP addresses in it, these IPs are the program's starting point
#just make sure you have IPs that will show up in CDP neighbors or you may end up with duplicates
read_in_ACS('ACS devices.txt')

#Stop if you have tried to login to 100k devices
while hard_stop < 100000:
print ("Devices SSHed to: ",hard_stop)
#Try to SSH into the router/switch
try:
#Put in your username and password so you can login to the router/switches
net_connect = netmiko.ConnectHandler(device_type='cisco_ios', ip=other_devices[hard_stop], username="username", password="Password")
#If you can't SSH correctly
except:
print('Could not SSH to ', other_devices[hard_stop])
#list of commands to run on the router/switch
commands = ['show cdp ent *']
for command in commands:
output = net_connect.send_command(command)
#Sleep is needed if the command is going to take a long time, such as pinging to fill the arp table
if command == 'ping 255.255.255.255 repeat 2':
time.sleep(10)
#make the output a file, with the name of the command used to run it
if command == 'show cdp ent *':
command = 'cdp neighbors'
#print (output)
to_doc("cdp neighbors", output)
read_in_cdp_v2("cdp neighbors")
hard_stop = hard_stop+1

#print (other_devices)


dlots

2ed program to make the graphml file out of the resultes.txt, generally I use yEd to open it, go under layout, tree, balloon to make it readable.



#This program has a ways to go still
#It opens the "resultes.txt" (in the same folder as this program) and pulls data out of it and starts making nodes
#note for some reason it makes nodes then puts in the start info, then makes the connections, you currently have to move the
#start info to the top.  Also if you run it 2x it will screw up the file as it doesn't delete the file, it just appends data onto
#the end.  Also it doesn't do redundant connections well.

#It's goal is to make a graphml file out of the resultes.txt file

#Goes at the start of the graphml file, currently goes in the middle after the nodes, I need to fix this.
start = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" xmlns:java=\"http://www.yworks.com/xml/yfiles-common/1.0/java\" xmlns:sys=\"http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0\" xmlns:x=\"http://www.yworks.com/xml/yfiles-common/markup/2.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:y=\"http://www.yworks.com/xml/graphml\" xmlns:yed=\"http://www.yworks.com/xml/yed/3\" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd\">\n  <!--Created by yEd 3.15.0.2-->\n  <key attr.name=\"Description\" attr.type=\"string\" for=\"graph\" id=\"d0\"/>\n  <key for=\"port\" id=\"d1\" yfiles.type=\"portgraphics\"/>\n  <key for=\"port\" id=\"d2\" yfiles.type=\"portgeometry\"/>\n  <key for=\"port\" id=\"d3\" yfiles.type=\"portuserdata\"/>\n  <key attr.name=\"url\" attr.type=\"string\" for=\"node\" id=\"d4\"/>\n  <key attr.name=\"description\" attr.type=\"string\" for=\"node\" id=\"d5\"/>\n  <key for=\"node\" id=\"d6\" yfiles.type=\"nodegraphics\"/>\n  <key for=\"graphml\" id=\"d7\" yfiles.type=\"resources\"/>\n  <key attr.name=\"url\" attr.type=\"string\" for=\"edge\" id=\"d8\"/>\n  <key attr.name=\"description\" attr.type=\"string\" for=\"edge\" id=\"d9\"/>\n  <key for=\"edge\" id=\"d10\" yfiles.type=\"edgegraphics\"/>\n  <graph edgedefault=\"directed\" id=\"G\">\n"
#Goes at the end of the document
end = "  </graph>\n  <data key=\"d7\">\n    <y:Resources/>\n  </data>\n</graphml>"
connection = 0

#find originator's IP from resultes.txt
def find_our_ip(line):
return line.split("\t")[1]
#find Neighbor's IP from resultes.txt
def find_neighbor_ip(line):
temp = line
return temp.split("\t")[3]

def find_our_int(line):
temp = line
return temp.split("\t")[7]
def find_their_int(line):
temp = line
return temp.split("\t")[9]

#Reads in the connections from the resultes.txt file
def read_in_conn(input,connection):
for line in open(input, 'r').readlines():
this_dev_ip =  find_our_ip(line)
neighbor_ip = find_neighbor_ip(line)
this_int = find_our_int(line)
their_int=find_their_int(line)
make_conn(neighbor_ip,this_dev_ip,this_int,their_int,connection)
connection = connection+1
#Reads in the individual nodes from the file
def read_node(input):
for line in open(input, 'r').readlines():
this_dev_ip =  find_our_ip(line)
neighbor_ip = find_neighbor_ip(line)
make_node(neighbor_ip)
make_node(this_dev_ip)

#the xml code to make the Connection
def make_conn(ip1,ip2,int_one,int_two,connection):
tmp = str(connection)
f.write("<edge id=\"e")
f.write(tmp)
f.write("\" source=\"")
#Node name: one side of the connection
f.write(ip1)
f.write("\" target=\"")
#Node name: other side of the connection
f.write(ip2)
f.write("\">")
f.write("<data key=\"d9\"/>\n")
f.write("  <data key=\"d10\">\n")
f.write("    <y:PolyLineEdge>\n")
f.write("      <y:Path sx=\"0.0\" sy=\"0.0\" tx=\"0.0\" ty=\"0.0\"/>\n")
f.write("      <y:LineStyle color=\"#000000\" type=\"line\" width=\"1.0\"/>\n")
f.write("      <y:Arrows source=\"none\" target=\"standard\"/>\n")
f.write("      <y:EdgeLabel alignment=\"center\" configuration=\"AutoFlippingLabel\" distance=\"2.0\" fontFamily=\"Dialog\" fontSize=\"12\" fontStyle=\"plain\" hasBackgroundColor=\"false\" hasLineColor=\"false\" height=\"18.701171875\" modelName=\"custom\" preferredPlacement=\"anywhere\" ratio=\"0.5\" textColor=\"#000000\" visible=\"true\" width=\"27.337890625\" x=\"44.115836688217826\" y=\"23.016682857619458\">")
#One of the interfaces so the connection is labled
f.write(int_one)
f.write("  ")
#the 2ed interface
f.write(int_two)
f.write("<y:LabelModel>\n")
f.write("          <y:SmartEdgeLabelModel autoRotationEnabled=\"false\" defaultAngle=\"0.0\" defaultDistance=\"10.0\"/>\n")
f.write("        </y:LabelModel>\n")
f.write("        <y:ModelParameter>\n")
f.write("          <y:SmartEdgeLabelModelParameter angle=\"0.0\" distance=\"30.0\" distanceToCenter=\"true\" position=\"right\" ratio=\"0.5\" segment=\"0\"/>\n")
f.write("        </y:ModelParameter>\n")
f.write("        <y:PreferredPlacementDescriptor angle=\"0.0\" angleOffsetOnRightSide=\"0\" angleReference=\"absolute\" angleRotationOnRightSide=\"co\" distance=\"-1.0\" frozen=\"true\" placement=\"anywhere\" side=\"anywhere\" sideReference=\"relative_to_edge_flow\"/>\n")
f.write("      </y:EdgeLabel>\n")
f.write("      <y:BendStyle smoothed=\"false\"/>\n")
f.write("    </y:PolyLineEdge>\n")
f.write("  </data>\n")
f.write("</edge>\n")
#XML to make the node (router/switch)
def make_node(ip):
#print (ip)
#Hard coded file name... BOOO!!
f=open('ugly.graphml', 'a')
f.write("<node id=\"")
#IP address/node name
f.write(ip)
f.write("\">\n")
f.write(" <data key=\"d5\"/>\n")
f.write(" <data key=\"d6\">\n")
f.write(" <y:ShapeNode>\n")
f.write(" <y:Geometry height=\"30.0\" width=\"128.0\" x=\"686.0\" y=\"448.0\"/>\n")
f.write(" <y:Fill color=\"#FFCC00\" transparent=\"false\"/>\n")
f.write(" <y:BorderStyle color=\"#000000\" type=\"line\" width=\"1.0\"/>\n")
f.write(" <y:NodeLabel alignment=\"center\" autoSizePolicy=\"content\" fontFamily=\"Dialog\" fontSize=\"12\" fontStyle=\"plain\" hasBackgroundColor=\"false\" hasLineColor=\"false\" height=\"18.701171875\" modelName=\"custom\" textColor=\"#000000\" visible=\"true\" width=\"10.673828125\" x=\"9.6630859375\" y=\"5.6494140625\">")
#This is what will show up as a label for the node
f.write(ip)
f.write("             <y:LabelModel>\n")
f.write(" <y:SmartNodeLabelModel distance=\"4.0\"/>\n")
f.write(" </y:LabelModel>\n")
f.write(" <y:ModelParameter>\n")
f.write(""" <y:SmartNodeLabelModelParameter labelRatioX=\"0.0\" labelRatioY=\"0.0\" nodeRatioX=\"0.0\" nodeRatioY=\"0.0\" offsetX=\"0.0\" offsetY=\"0.0\" upX=\"0.0\" upY=\"-1.0\"/>\n""")
f.write(" </y:ModelParameter>\n")
f.write(" </y:NodeLabel>\n")
f.write(" <y:Shape type=\"rectangle\"/>\n")
f.write(" </y:ShapeNode>\n")
f.write(" </data>\n")
f.write(" </node>\n")


f=open('ugly.graphml', 'a')
#f=open('ugly.graphml', 'w')
f.write(start)


read_node('resultes.txt')
read_in_conn('resultes.txt',connection)




#make_node("test")


f.write(end)

that1guy15

Quote from: dlots on June 28, 2016, 03:23:02 PM
Please note this is my 2ed program ever, and it re-uses lots of code from my 1st, I make no claim to being a programer... not even a little.

Im on and off with scripting so Im sure Im worse than you. Im taking on a project now that will really push me with automation and programming. We gotta start somewhere right?

Let me know when you get it up on gethub and Ill make sure to follow you.
That1guy15
@that1guy_15
blog.movingonesandzeros.net

dlots