[go: up one dir, main page]

0% found this document useful (0 votes)
15 views34 pages

Connection To Network Devices

Uploaded by

fersd2018
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views34 pages

Connection To Network Devices

Uploaded by

fersd2018
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 34

18.

Connection to network devices


This section discusses how to connect to network devices via:
• SSH
• Telnet

Python has several modules that allow you to connect to network devices and execute
commands:
• pexpect - an implementation of expect in Python
– this module allows working with any interactive session: ssh, telnet, sftp,
etc.
– in addition, it makes possible to execute different commands in OS (this
can also be done with other modules)
– while pexpect may be less user-friendly than other modules, it implements
a more general functionality and allows it to be used in situations where other
modules do not work

• telnetlib - this module allows you connecting via Telnet


– netmiko version >= 1.0 also has Telnet support, so if netmiko supports the
network devices you use, it is more convenient to use it

• paramiko - this module allows you connecting via SSHv2


– it is more convenient to use than pexpect but with narrower functionality
(only supports SSH)

• netmiko - module that simplifies the use of paramiko for network devices
– netmiko is a “wrapper” which is oriented to work with network devices

• scrapli - is a module that allows you to connect to network equipment using


Telnet, SSH or NETCONF

This section covers all five modules and describes how to connect to several devices in
parallel. Three routers are used in section examples. There are no requirements for them,
only configured SSHv2 and Telnet.

Parameters used in these section:


• user: cisco
• password: cisco
• password for enable mode: cisco
• SSH version 2, Telnet
• IP addresses: 192.168.100.1, 192.168.100.2, 192.168.100.3

96
Password input
During manual connection to device the password is also manually entered.

When automating connection it is necessary to decide how password will be transmitted:


• Request password at start of the script and read user input. Disadvantage is that
you can see which characters user is typing
• Write login and password in some file (it’s not secure).

As a rule, the same user uses the same login and password to connect to devices. And
usually it’s enough to request login and password at the start of the script and then use
them to connect to different devices.

Unfortunately, if you use input the typed password will be visible. But it is better if no
characters are displayed when entering a password.

Module getpass
Module getpass allows you to request a password without displaying input characters:
import getpass

password = getpass.getpass() # Password:

print(password) # yourpass

Environment variables
Another way to store a password (or even a username) is by environment variables.
For example, login and password are written in variables:
$ export SSH_USER=user
$ export SSH_PASSWORD=userpass

And then Python reads values to variables in the script:


import os

USERNAME = os.environ.get('SSH_USER')
PASSWORD = os.environ.get('SSH_PASSWORD')

97
Module pexpect
Module pexpect allows to automate interactive connections such as:
• telnet
• ssh
• ftp

First, pexpect module needs to be installed:


pip install pexpect

The logic of pexpect is:


• some program is running
• pexpect expects a certain output (prompt, password request, etc.)
• after receiving the output, it sends commands/data
• last two actions are repeated as many times as necessary

At the same time, pexpect does not implement utilities but uses ready-made ones.

pexpect.spawn
Class spawn allows you to interact with called program by sending data and waiting for a
response.

For example, you can initiate SSH connecton:


ssh = pexpect.spawn('ssh cisco@192.168.100.1')

After executing this line, connection is established. Now you must specify which line to
expect. In this case, wait for password request:
ssh.expect('[Pp]assword')

Note how line that pexpect expects is written as [Pp]assword. This is a regex that
describes a password or Password string. That is, expect method can be used to pass a
regex as an argument.

Method expect returned number 0 as a result of the work. This number indicates that a
match has been found and that this element with index zero. Index appears here because
you can pass a list of strings. For example, you can pass a list with two elements:
ssh = pexpect.spawn('ssh cisco@192.168.100.1')

ssh.expect(['password', 'Password']) # 1

Note that it now returns 1. This means that Password word matched.

98
Now you can send password using sendline method:
ssh.sendline('cisco') # 6

Method sendline sends a string, automatically adds a new line character to it based on
the value of os.linesep and then returns a number indicating how many bytes were written.

To get into enable mode expect-sendline cycle repeats:


ssh.expect('[>#]') # 0
ssh.sendline('enable') # 7
ssh.expect('[Pp]assword') # 0
ssh.sendline('cisco') # 6
ssh.expect('[>#]') # 0

Now we can send a command:


ssh.sendline('sh ip int br') # 13

After sending the command, pexpect must be told until what point to read the output. We
specify that it should read untill #:
ssh.expect('#') # 0

Command output is in before attribute:


ssh.before

"""
b'sh ip int br\r\nInterface IP-Address OK? Method Status ␣
,→ Protocol\r\nEthernet0/0 192.168.100.1 YES NVRAM up ␣
,→ up \r\nEthernet0/1 192.168.200.1 YES NVRAM up ␣
,→ up \r\nEthernet0/2 19.1.1.1 YES NVRAM up ␣
,→ up \r\nEthernet0/3 192.168.230.1 YES NVRAM up ␣
,→ up \r\nEthernet0/3.100 10.100.0.1 YES NVRAM up ␣
,→ up \r\nEthernet0/3.200 10.200.0.1 YES NVRAM up ␣
,→ up \r\nEthernet0/3.300 10.30.0.1 YES NVRAM up ␣
,→ up \r\nR1'
"""

Since the result is displayed as a sequence of bytes you should convert it to a string:
show_output = ssh.before.decode('utf-8')
print(show_output)

Session ends with a close call:


ssh.close()

99
Example with sandbox-iosxe-latest-1.cisco.com
import pexpect

print("The current working directory: \n%s"


%pexpect.run('pwd').decode("utf-8"))

ssh = pexpect.spawn('ssh
developer@sandbox-iosxe-latest-1.cisco.com -y')

ssh.expect('Password:')
ssh.sendline('C1sco12345')
ssh.expect('#')
ssh.sendline('enable')
ssh.expect('#')
ssh.sendline('sh ip int br')
ssh.expect('--More--')
print(ssh.before.decode('utf-8'))
ssh.close()

100
Special characters in shell
Pexpect does not interpret special shell characters such as >, |, *.

For example, in order make command ls -ls | grep SUMMARY work, shell must be
run as follows:
import pexpect

p = pexpect.spawn('/bin/bash -c "ls -ls | grep pexpect"')


p.expect(pexpect.EOF) # 0
print(p.before)
# b'4 -rw-r--r-- 1 vagrant vagrant 3203 Jul 14 07:15
1_pexpect.py\r\n'

print(p.before.decode('utf-8'))
# 4 -rw-r--r-- 1 vagrant vagrant 3203 Jul 14 07:15 1_pexpect.py

pexpect.EOF
In the previous example we met pexpect.EOF.

This is a special value that allows you to react to the end of a command or session that
has been run in spawn.

When calling ls -ls command, pexpect does not receive an interactive session.
Command is simply executed and that ends its work.

Method pexpect.expect
In pexpect.expect as a value can be used:
• regex
• EOF - this template allows you to react to EOF exception
• TIMEOUT - timeout exception (default timeout = 30 seconds)
• compiled regex

Another very useful feature of pexpect.expect is that you can pass not a single value, but a
list.
For example:
p = pexpect.spawn('/bin/bash -c "ls -ls | grep netmiko"')

p.expect(['py3_convert', pexpect.TIMEOUT, pexpect.EOF]) # 2

101
Here are some important points:
• when pexpect.expect is called with a list, you can specify different expected strings
• apart strings, exceptions also can be specified
• pexpect.expect returns number of element that matched
– in this case number 2 because EOF exception is number two in the list
• with this format you can make branches in the program depending on the element
which had a match

Example of pexpect use


Example of using pexpect when connecting to equipment and passing show command:
import pexpect
import re
from pprint import pprint

def send_show_command(ip, username, password, enable, commands, prompt="#"):


with pexpect.spawn(f"ssh {username}@{ip}", timeout=10, encoding="utf-8") as ssh:
ssh.expect("[Pp]assword")
ssh.sendline(password)
enable_status = ssh.expect([">", "#"])
if enable_status == 0:
ssh.sendline("enable")
ssh.expect("[Pp]assword")
ssh.sendline(enable)
ssh.expect(prompt)

ssh.sendline("terminal length 0")


ssh.expect(prompt)

result = {}
for command in commands:
ssh.sendline(command)
match = ssh.expect([prompt, pexpect.TIMEOUT, pexpect.EOF])
if match == 1:
print(f"Symbol {prompt} is not found in output. Resulting
output is written to dictionary"
if match == 2:
print("Connection was terminated by server")
return result
else:
output = ssh.before
result[command] = output.replace("\r\n", "\n")
return result

if __name__ == "__main__":
devices = ["192.168.100.1", "192.168.100.2", "192.168.100.3"]
commands = ["sh clock", "sh int desc"]
for ip in devices:
result = send_show_command(ip, "cisco", "cisco", "cisco", commands)
pprint(result, width=120)
Output after script execution:

102
{'sh clock': 'sh clock\n*13:13:47.525 UTC Sun Jul 19 2022\n',
'sh int desc': 'sh int desc\n'
'Interface Status Protocol Description\n'
'Et0/0 up up \n'
'Lo0 up up \n'}
{'sh clock': 'sh clock\n*13:13:50.450 UTC Sun Jul 19 2022\n',
'sh int desc': 'sh int desc\n'
'Interface Status Protocol Description\n'
'Et0/0 up up \n'
'Et0/1 up up \n'}
{'sh clock': 'sh clock\n*13:13:53.360 UTC Sun Jul 19 2022\n',
'sh int desc': 'sh int desc\n'
'Interface Status Protocol Description\n'
'Et0/0 up up \n'
'Lo100 up up \n'}

Working with pexpect without disabling commands pagination


Sometimes the output of a command is very large and cannot be read completely or
device is not makes it possible to disable pagination. In this case, a slightly different
approach is needed.

Example of using pexpect to work with paginated output of show command:


import pexpect
import re from pprint
import pprint

def send_show_command(ip, username, password, enable, command, prompt="#"):


with pexpect.spawn(f"ssh {username}@{ip}", timeout=10, encoding="utf-8") as ssh:
ssh.expect("[Pp]assword")
ssh.sendline(password)
enable_status = ssh.expect([">", "#"])
if enable_status == 0:
ssh.sendline("enable")
ssh.expect("[Pp]assword")
ssh.sendline(enable)
ssh.expect(prompt)

ssh.sendline(command)
output = ""

while True:
match = ssh.expect([prompt, "--More--", pexpect.TIMEOUT])
page = ssh.before.replace("\r\n", "\n")
page = re.sub(" +\x08+ +\x08+", "\n", page)
output += page
if match == 0:
break
elif match == 1:
ssh.send(" ")
else:

103
print("Error: timeout")
break
output = re.sub("\n +\n", "\n", output)
return output

if __name__ == "__main__":
devices = ["192.168.100.1", "192.168.100.2", "192.168.100.3"]
for ip in devices:
result = send_show_command(ip, "cisco", "cisco", "cisco", "sh run")
with open(f"{ip}_result.txt", "w") as f:
f.write(result)

Now after sending the command, expect method waits for another option --More-- -
sign, that there will be one more page further. Since it’s not known in advance how many
pages will be in the output, reading is performed in a loop while True. Loop is interrupted if
prompt is met # or no prompt appears within 10 seconds or --More--.

If --More-- is met, pages are not over yet and you have to scroll through the next one. In
Cisco, you need to press space bar to do this (without new line). Therefore, send method
is used here, not sendline - sendline automatically adds a new line character.

This string page = re.sub(" +\x08+ +\x08+", "\n", page) removes backspace
symbols which are around --More-- so they don’t end up in the final output.

104
Module telnetlib
Module telnetlib is part of standard Python library. This is telnet client implementation.

Note: It is also possible to connect via telnet using pexpect. The advantage of telnetlib is
that this module is part of standard Python library.

Telnetlib resembles pexpect but has several differences. The most notable difference is
that telnetlib requires a pass of a byte string, rather than normal one.

Connection is performed as follows:


telnet = telnetlib.Telnet('192.168.100.1')

Method read_until
Method read_until specifies till which line the output should be read. However, as an
argument, it is necessary to pass bytes, not the usual string:
telnet.read_until(b'Username')
# b'\r\n\r\nUser Access Verification\r\n\r\nUsername'

Method read_until returns everything it has read before specified string.

Method write
The write method is used to transmit data. You must pass a byte string as an argument:
telnet.write(b'cisco\n')

Read output till Password and pass the password:


telnet.read_until(b'Password') # b': cisco\r\nPassword'
telnet.write(b'cisco\n')

You can now specify what should be read untill prompt and then send the command:
telnet.read_until(b'>') # b': \r\nR1>'

telnet.write(b'sh ip int br\n')

After sending a command, you can continue to use read_until method:


telnet.read_until(b'>')
# b'sh ip int br\r\nInterface IP-Address OK? Method Status
Protocol\r\nEthernet0/0 192.168.100.1 YES NVRAM up
up \r\nEthernet0/1 192.168.200.1 YES NVRAM up…

105
Method read_very_eager
Or use another read method read_very_eager. When using read_very_eager method,
you can send multiple commands and then read all available output:
telnet.write(b'sh arp\n')
telnet.write(b'sh clock\n')
telnet.write(b'sh ip int br\n')
all_result = telnet.read_very_eager().decode('utf-8')
print(all_result)

Warning: You should always set time.sleep(n) before using read_very_eager.

read_until vs read_very_eager
An important difference between read_until and read_very_eager is how they react
to the lack of output.

Method read_until waits for a certain string. By default, if it does not exist, method will
“freeze”.
Timeout option allows you to specify how long to wait for the desired string:
telnet.read_until(b'>', timeout=5) # b''

If no string appears during the specified time, an empty string is returned.

Method read_very_eager simply returns an empty string if there is no output:

Method expect
Method expect allows you to specify a list with regular expressions. It works like
pexpect but telnetlib always has to pass a list of regular expressions.

You can then pass byte strings or compiled regular expressions:


telnet.write(b'sh clock\n')
telnet.expect([b'[>#]'])
# (0,
<_sre.SRE_Match object; span=(46, 47), match=b'>'>,
b'sh clock\r\n*19:35:10.984 UTC Fri JUL 3 2012\r\nR1>')

Method expect returns tuple of their three elements:


• index of matched expression
• object Match
• byte string that contains everything read till regular expression including regular
expression

106
Accordingly, if necessary you can continue working with these elements:
telnet.write(b'sh clock\n')
regex_idx, match, output = telnet.expect([b'[>#]'])
regex_idx # 0
match.group() # b'>'

Method close
Method close closes connection but it’s better to open and close connection using context
manager:
telnet.close()

Telnetlib usage example


Working principle of telnetlib resembles pexpect, so the example below should be clear:
import telnetlib
import time
from pprint import pprint

def to_bytes(line):
return f"{line}\n".encode("utf-8")
def send_show_command(ip, username, password, enable, commands):
with telnetlib.Telnet(ip) as telnet:
telnet.read_until(b"Username")
telnet.write(to_bytes(username))
telnet.read_until(b"Password")
telnet.write(to_bytes(password))
index, m, output = telnet.expect([b">", b"#"])
if index == 0:
telnet.write(b"enable\n")
telnet.read_until(b"Password")
telnet.write(to_bytes(enable))
telnet.read_until(b"#", timeout=5)
telnet.write(b"terminal length 0\n")
telnet.read_until(b"#", timeout=5)
time.sleep(3)
telnet.read_very_eager()

result = {}
for command in commands:
telnet.write(to_bytes(command))
output = telnet.read_until(b"#", timeout=5).decode("utf-8")
result[command] = output.replace("\r\n", "\n")
return result

if __name__ == "__main__":
devices = ["192.168.100.1", "192.168.100.2", "192.168.100.3"]
commands = ["sh ip int br", "sh arp"]
for ip in devices:
result = send_show_command(ip, "cisco", "cisco", "cisco", commands)
pprint(result, width=120)

107
Since bytes need to be passed to write method and new line character should be added
each time, a small function to_bytes is created that does the conversion to bytes and adds
a new line.

108
Module paramiko
Paramiko is an implementation of SSHv2 protocol on Python. Paramiko provides
client-server functionality.

Since Paramiko is not part of standard Python module library, it needs to be installed:
pip install paramiko

Connection is established in this way: first, client is created and client configuration is set,
then connection is initiated and an interactive session is returned:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

client.connect(hostname="192.168.100.1", username="cisco",
password="cisco", ...: look_for_keys=False, allow_agent=False) 7
ssh = client.invoke_shell()

SSHClient is a class that represents a connection to SSH server. It performs client


authentication.

String set_missing_host_key_policy is optional, it indicates which policy to use


when connecting to a server whose key is unknown. Policy
paramiko.AutoAddPolicy() automatically add new hostname and key to local
HostKeys object.

Method connect connects to SSH server and authenticates the connection. Parameters:
• look_for_keys - by default paramiko performs key authentication. To disable
this, put the flag in False
• allow_agent - paramiko can connect to a local SSH agent. This is necessary
when working with keys and since in this case authentication is done by
login/password, it should be disabled.

After execution of previous command there is already a connection to server. Method


invoke_shell allows to set an interactive SSH session with server.

Method send
Method send - sends specified string to session and returns amount of sent bytes.
ssh.send("enable\n") # 7
ssh.send("cisco\n") # 6
ssh.send("sh ip int br\n") # 13

109
Warning: In code, after send you will need to put time.sleep, especially between send and
recv. Since this is an interactive session and commands are slow to type, everything works
without pauses.

Method recv
Method recv receives data from session. In parentheses, the maximum value in bytes
that can be obtained is indicated. This method returns a received string
ssh.recv(3000)

Method close
ssh.close()

Example of paramiko use


import paramiko
import time
import socket
from pprint import pprint

def send_show_command(
ip,
username,
password,
enable,
command,
max_bytes=60000,
short_pause=1,
long_pause=5,
):
cl = paramiko.SSHClient()
cl.set_missing_host_key_policy(paramiko.AutoAddPolicy())
cl.connect(
hostname=ip,
username=username,
password=password,
look_for_keys=False,
allow_agent=False,
)
with cl.invoke_shell() as ssh:
ssh.send("enable\n")

110
ssh.send(f"{enable}\n")
time.sleep(short_pause)
ssh.send("terminal length 0\n")
time.sleep(short_pause)
ssh.recv(max_bytes)

result = {}
for command in commands:
ssh.send(f"{command}\n")
ssh.settimeout(5)

output = ""
while True:
try:
part = ssh.recv(max_bytes).decode("utf-8")
output += part
time.sleep(0.5)
except socket.timeout:
break
result[command] = output

return result

if __name__ == "__main__":
devices = ["192.168.100.1", "192.168.100.2", "192.168.100.3"]
commands = ["sh clock", "sh arp"]
result = send_show_command("192.168.100.1", "cisco", "cisco",
"cisco", commands)
pprint(result, width=120)

111
Module netmiko
Netmiko is a module that makes it easier to use paramiko for network devices. Netmiko
uses paramiko but also creates interface and methods needed to work with network
devices.

First you need to install netmiko:


pip install netmiko

Supported device types


Netmiko supports several types of devices:
• Arista vEOS
• Cisco ASA
• Cisco IOS
• Cisco IOS-XR
• Cisco SG300
• HP Comware7
• HP ProCurve
• Juniper Junos
• Linux
• and other

Dictionary for defining device parameters


Dictionary may have the next parameters:
cisco_router = {
'device_type': 'cisco_ios',
'host': '192.168.1.1',
'username': 'user',
'password': 'userpass',
'secret': 'enablepass',
'port': 20022,
}

Connect via SSH


ssh = ConnectHandler(**cisco_router)

Enable mode
Switch to enable mode:

112
ssh.enable()

Exit enable mode:


ssh.exit_enable_mode()

Sending commands
Netmiko has several ways to send commands:
• send_command - send one command
• send_config_set - send list of commands or command in configuration mode
• send_config_from_file - send commands from the file (uses
send_config_set method inside)
• send_command_timing - send command and wait for the output based on timer

send_command
Method send_command allows you to send one command to device.
For example:
result = ssh.send_command('show ip int br')

Method works as follows:


• sends command to device and gets the output until string with prompt or until
specified string
– prompt is automatically determined
– if your device does not determine it, you can simply specify a string till
which to read the output
– send_command_expect method previously worked this way, but since
version 1.0.0 this is how send_command works and send_command_expect
method is left for compatibility
• method returns command output
• the following parameters can be passed to method:
– command_string - command
– expect_string - to which substring to read the output
– delay_factor - option allows to increase delay before the start of string
search
– max_loops - number of iterations before method gives out an error
(exception). By default 500
– strip_prompt - remove prompt from the output. Removed by default
– strip_command - remove command from output
In most cases, only command will be sufficient to specify.

113
send_config_set
Method send_config_set allows you to send command or multiple commands in
configuration mode.
Example:
commands = ['router ospf 1',
'network 10.0.0.0 0.255.255.255 area 0',
'network 192.168.100.0 0.0.0.255 area 1']

result = ssh.send_config_set(commands)

Method works as follows:


• goes into configuration mode,
• then passes all commands
• and exits configuration mode
• depending on device type, there may be no exit from configuration mode. For
example, there will be no exit for IOS-XR because you first have to commit changes

send_config_from_file
Method send_config_from_file sends commands from specified file to configuration
mode.
Example of use:
result = ssh.send_config_from_file('config_ospf.txt')

Method opens a file, reads commands and passes them to send_config_set method.

Additional methods
Besides the above methods for sending commands, netmiko supports such methods:
• config_mode - switch to configuration mode: ssh.config_mode
• exit_config_mode - exit configuration mode: ssh.exit_config_mode
• check_config_mode - check whether netmiko is in configuration mode (returns
True if in configuration mode and False if not): ssh.check_config_mode
• find_prompt - returns the current prompt of device: ssh.find_prompt
• commit - commit on IOS-XR and Juniper: ssh.commit
• disconnect - terminate SSH connection

114
Telnet support
Since version 1.0.0 netmiko supports Telnet connections, so far only for Cisco IOS
devices. Inside netmiko uses telnetlib to connect via Telnet. But, at the same time, it
provides the same interface for work as for SSH connection.

In order to connect via Telnet, it is enough in the dictionary that defines connection
parameters specify device type cisco_ios_telnet:
device = {
"device_type": "cisco_ios_telnet",
"host": "192.168.100.1",
"username": "cisco",
"password": "cisco",
"secret": "cisco",
}

Otherwise, methods that apply to SSH apply to Telnet.


An example similar to SSH :
from pprint import pprint
import yaml
from netmiko import (
ConnectHandler,
NetmikoTimeoutException,
NetmikoAuthenticationException,
)

def send_show_command(device, commands):


result = {}
try:
with ConnectHandler(**device) as ssh:
ssh.enable()
for command in commands:
output = ssh.send_command(command)
result[command] = output
return result
except (NetmikoTimeoutException, NetmikoAuthenticationException) as error:
print(error)

if __name__ == "__main__":
device = {
"device_type": "cisco_ios_telnet",
"host": "192.168.100.1",
"username": "cisco",
"password": "cisco",
"secret": "cisco",
}
result = send_show_command(device, ["sh clock", "sh ip int br"])
pprint(result, width=120)
* collects all the positional arguments in a tuple.
** collects all the keyword arguments in a dictionary.

115
Example of netmiko use
from pprint import pprint
import yaml
from netmiko import (
ConnectHandler,
NetmikoTimeoutException,
NetmikoAuthenticationException,
)

def send_show_command(device, commands):


result = {}
try:
with ConnectHandler(**device) as ssh:
ssh.enable()
for command in commands:
output = ssh.send_command(command)
result[command] = output
return result
except (NetmikoTimeoutException, NetmikoAuthenticationException) as error:
print(error)

if __name__ == "__main__":
with open("devices.yaml") as f:
devices = yaml.safe_load(f)
for device in devices:
result = send_show_command(device, ["sh clock", "sh ip int br"])
pprint(result, width=120)

- devices.yaml content:
- device_type: cisco_ios
host: 192.168.100.1
username: cisco
password: cisco
secret: cisco
timeout: 5
fast_cli: true
- device_type: cisco_ios
host: 192.168.100.2
username: cisco
password: cisco
secret: cisco
timeout: 5
fast_cli: true
- device_type: cisco_ios
host: 192.168.100.3
username: cisco
password: cisco
secret: cisco
timeout: 5
fast_cli: true

116
Paginated command output
Example of using netmiko with paginated output of show command:
from netmiko import ConnectHandler, NetmikoTimeoutException
import yaml

def send_show_command(device_params, command):


with ConnectHandler(**device_params) as ssh:
ssh.enable()
prompt = ssh.find_prompt()
ssh.send_command("terminal length 100")
ssh.write_channel(f"{command}\n")
output = ""
while True:
try:
page = ssh.read_until_pattern(f"More|{prompt}")
output += page
if "More" in page:
ssh.write_channel(" ")
elif prompt in output:
break
except NetmikoTimeoutException:
break
return output

if __name__ == "__main__":
with open("devices.yaml") as f:
devices = yaml.safe_load(f)
print(send_show_command(devices[0], "sh run"))

117
Module scrapli
scrapli is a module that allows you to connect to network equipment using Telnet, SSH or
NETCONF.

Just like netmiko, scrapli can use paramiko or telnetlib (and other modules) for the
connection itself, but it provides the same interface for different types of connections and
different equipment.

Installing scrapli:
pip install scrapli

The three main components of scrapli are:


• transport is a specific way to connect to equipment
• channel - the next level above the transport, which is responsible for sending
commands, receiving output and other interactions with equipment
• driver is the interface for working with scrapli. There are both specific drivers, for
example, IOSXEDriver, which understands how to interact with a specific type of
equipment, and the basic Driver, which provides a minimal interface for working via
SSH/Telnet.

Available transport options:


• system - the built-in SSH client, it is assumed that the client is used on
Linux/MacOS
• paramiko - the paramiko module
• ssh2 - the ssh2-python module (wrapper around the C library libssh2)
• telnet - telnetlib
• asyncssh - asyncssh module
• asynctelnet - async telnet client

Most of the examples will be using the system transport. Since the module interface is the
same for all synchronous transport options, to use a different transport, you just need to
specify it (for telnet transport, you must also specify the port).

Supported platforms:
• Cisco IOS-XE
• Cisco NX-OS
• Juniper JunOS
• Cisco IOS-XR
• Arista EOS

In addition to these platforms, there are also scrapli community platforms. And one of the
advantages of scrapli is that it is relatively easy to add new platforms.

118
There are two connection options in scrapli: using the general Scrapli class, which selects
the required driver by the platform parameter, or a specific driver, for example,
IOSXEDriver. The same parameters are passed to the specific driver and Scrapli.

Note: In addition to these options, there are also generic (base) drivers.

Connection parameters
Basic connection parameters:
• host - IP address or hostname
• auth_username - username for authentication
• auth_password - password for authentication
• auth_secondary - enable password
• auth_strict_key - strict key checking (True by default)
• platform - must be specified when using Scrapli
• transport - which transport to use
• transport_options - options for a specific transport

The connection process is slightly different depending on whether you are using a context
manager or not. When connecting without a context manager, you first need to pass
parameters to the driver or Scrapli, and then call the open method:
from scrapli import Scrapli

r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
"platform": "cisco_iosxe"
}

ssh = Scrapli(**r1)
ssh.open()
ssh.get_prompt() # 'R1#'
ssh.close()

When using a context manager, you don’t need to call open:


with Scrapli(**r1) as ssh:
print(ssh.get_prompt())

119
Using the driver
Available drivers:

Network equipment Driver platform parameter

Cisco IOS-XE IOSXEDriver cisco_iosxe

Cisco NX-OS NXOSDriver cisco_nxos

Cisco IOS-XR IOSXRDriver cisco_iosxr

Arista EOS EOSDriver arista_eos

Juniper JunOS JunosDriver juniper_junos

Example of connection using the IOSXEDriver driver (connecting to Cisco IOS):


from scrapli.driver.core import IOSXEDriver

r1_driver = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
}

with IOSXEDriver(**r1_driver) as ssh:


print(ssh.get_prompt())

Sending commands
Scrapli has several methods for sending commands:
• send_command - send one show command
• send_commands - send a list of show commands
• send_commands_from_file - send show commands from a file
• send_config - send one command in configuration mode
• send_configs - send a list of commands in configuration mode
• send_configs_from_file - send commands from file in configuration mode

All of these methods return a Response object, not the output of the command as a string.

120
Response object
The send_command method and other methods for sending commands return a
Response object (not the output of the command). Response allows you to get not only
the output of the command, but also such things as the execution time of the command,
whether there were errors during the execution of the command, structured output using
textfsm, and so on.
reply = ssh.send_command("sh clock") # Response <Success: True>

You can get the output of the command by accessing the result attribute:
print(reply.result) # '*17:31:54.232 UTC Wed Mar 31 2021'

The raw_result attribute contains a byte string with complete output:


print(reply.raw_result) # b'\n*17:31:54.232 UTC Wed Mar 31 2021\nR1#'

For commands that take longer than normal show, it may be necessary to know the
command execution time:
r = ssh.send_command("ping 10.1.1.1")
r.result
# 'Type escape sequence to abort.\nSending 5, 100-byte ICMP Echos to
10.1.1.1,␣ ,→timeout is 2 seconds:\n.....\nSuccess rate is 0 percent
(0/5)'

r.elapsed_time # 10.047594
r.start_time # datetime.datetime(2022, 7, 1, 7, 10, 56, 63697)
r.finish_time # datetime.datetime(2022, 7, 1, 7, 11, 6, 111291)

The channel_input attribute returns the command that was sent to the equipment:
r.channel_input # 'ping 10.1.1.1'

send_command method
The send_command method allows you to send one command to a device.

Method parameters (all these parameters must be passed as keyword arguments):


• strip_prompt - remove a prompt from the output. Deleted by default
• failed_when_contains - if the output contains the specified line or one of the
lines in the list, the command will be considered as completed with an error
• timeout_ops - maximum time to execute a command, by default it is 30 seconds
for IOSXEDriver

121
The timeout_ops parameter specifies how long to wait for the command to execute:
ssh.send_command("ping 8.8.8.8", timeout_ops=20)
# Response <Success: True>

If the command does not complete within the specified time, a ScrapliTimeout exception
will be raised (output is truncated).

In addition to receiving normal command output, scrapli also allows you to receive
structured output, for example using the textfsm_parse_output method:
reply = ssh.send_command("sh ip int br")
reply.textfsm_parse_output()
# [{'intf': 'Ethernet0/0',
'ipaddr': '192.168.100.1',
'status': 'up',
'proto': 'up'},
{'intf': 'Ethernet0/1',
'ipaddr': '192.168.200.1',
'status': 'up',
'proto': 'up'}]

Note: Scrapli uses ready-made templates in order to receive structured output and in basic
cases does not require knowledge of TextFSM.

Error detection
Methods for sending commands automatically check the output for errors. For each
vendor/type of equipment, these are different errors, plus you can specify which lines in
the output will be considered an error. By default, IOSXEDriver will consider the following
lines as errors:
ssh.failed_when_contains
# ['% Ambiguous command',
'% Incomplete command',
'% Invalid input detected',
'% Unknown command']

The failed attribute of the Response object returns False if the command finished without
error and True if it failed.
reply = ssh.send_command("sh clck")
reply.result # " ^\n% Invalid input detected at '^' marker."
reply # Response <Success: False>
reply.failed # True

122
send_config method
The send_config method allows you to send one configuration mode command.

Example:
r = ssh.send_config("username user1 password password1")

Since scrapli removes the command from the output, by default, when using send_config,
the result attribute will contain an empty string (if there was no error while executing the
command).

send_commands, send_configs
The send_commands, send_configs methods differ from send_command,
send_config in that they can send several commands. In addition, these methods do not
return a Response, but a MultiResponse object, which can generally be thought of as a list
of Response objects, one for each command.
reply = ssh.send_commands(["sh clock", "sh ip int br"])
reply # MultiResponse <Success: True; Response Elements: 2>

for r in reply:
print(r)
print(r.result)

When sending multiple commands, it is also very convenient to use the stop_on_failed
parameter. By default, it is False, so all commands are executed, but if you specify
stop_on_failed=True, after an error occurs in some command, the following
commands will not be executed:
reply = ssh.send_commands(["ping 192.168.100.2", "sh clck", "sh ip int
br"], stop_on_failed=True)

reply # MultiResponse <Success: False; Response Elements: 2>

123
Telnet connection
To connect to equipment via Telnet, you must specify transport equal to telnet and be sure
to specify the port parameter equal to 23 (or the port that you use to connect via Telnet):
from scrapli.driver.core import IOSXEDriver
from scrapli.exceptions import ScrapliException
import socket

r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco2",
"auth_secondary": "cisco",
"auth_strict_key": False,
"transport": "telnet",
"port": 23, # must be specified when connecting telnet
}

def send_show(device, show_command):


try:
with IOSXEDriver(**r1) as ssh:
reply = ssh.send_command(show_command)
return reply.result
except socket.timeout as error:
print(error)
except ScrapliException as error:
print(error, device["host"])

if __name__ == "__main__":
output = send_show(r1, "sh ip int br")
print(output)

Scrapli examples
Basic example of sending show command:
from scrapli.driver.core import IOSXEDriver
from scrapli.exceptions import ScrapliException

r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,

124
"timeout_socket": 5, # timeout for establishing socket/initial
connection
"timeout_transport": 10, # timeout for ssh|telnet transport
}

def send_show(device, show_command):


try:
with IOSXEDriver(**r1) as ssh:
reply = ssh.send_command(show_command)
return reply.result
except ScrapliException as error:
print(error, device["host"])

if __name__ == "__main__":
output = send_show(r1, "sh ip int br")
print(output)

Basic example of sending config commands:


from pprint import pprint
from scrapli import Scrapli

r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
"platform": "cisco_iosxe",
}

def send_show(device, show_commands):


if type(show_commands) == str:
show_commands = [show_commands]
cmd_dict = {}
with Scrapli(**r1) as ssh:
for cmd in show_commands:
reply = ssh.send_command(cmd)
cmd_dict[cmd] = reply.result
return cmd_dict

if __name__ == "__main__":
print("show".center(20, "#"))
output = send_show(r1, ["sh ip int br", "sh ver | i uptime"])
pprint(output, width=120)

125
An example of sending configuration commands with error checking:
from pprint import pprint
from scrapli import Scrapli
r1 = {
"host": "192.168.100.1",
"auth_username": "cisco",
"auth_password": "cisco",
"auth_secondary": "cisco",
"auth_strict_key": False,
"platform": "cisco_iosxe",
}

def send_cfg(device, cfg_commands, strict=False):


output = ""
if type(cfg_commands) == str:
cfg_commands = [cfg_commands]
with Scrapli(**r1) as ssh:
reply = ssh.send_configs(cfg_commands, stop_on_failed=strict)
for cmd_reply in reply:
if cmd_reply.failed:
print(
f"An error occurred while executing the command::\n
{reply.result}\n"
)
output = reply.result
return output

if __name__ == "__main__":
output_cfg = send_cfg(
r1, ["interfacelo11", "ip address 11.1.1.1 255.255.255.255"],
strict=True
)
print(output_cfg)

126
Tasks

Task 18.1
Create send_show_command function.
The function connects via SSH (using netmiko) to ONE device and executes the specified
command.
Function parameters:
• device - a dictionary with parameters for connecting to a device
• command - the command to be executed
The function should return a string with the command output.
The script should send command to all devices from the devices.yaml file using the
send_show_command function (this part of the code is written).
import yaml

if __name__ == "__main__":
command = "sh ip int br"
with open("devices.yaml") as f:
devices = yaml.safe_load(f)

for dev in devices:


print(send_show_command(dev, command))

Task 18.1a
Copy the send_show_command function from task 18.1 and rewrite it to handle the
exception that is thrown on authentication failure on the device.

When an error occurs, an exception message should be printed to stdout.

To verify, change the password on the device or in the devices.yaml file.

Task 18.1b
Copy the send_show_command function from task 18.1a and rewrite it to handle not only
the exception that is raised when authentication fails on the device, but also the exception
that is raised when the IP address of the device is not available.

When an error occurs, an exception message should be printed to standard output.

To check, change the IP address on the device or in the devices.yaml file.

127
Task 18.2
Create send_config_commands function.

The function connects via SSH (using netmiko) to ONE device and executes a list of
commands in configuration mode based on the passed arguments.

Function parameters:
• device - a dictionary with parameters for connecting to a device
• config_commands - list of configuration commands to be executed

The function should return a string with the results of the command:
print(r1)
# {'device_type': 'cisco_ios',
'ip': '192.168.100.1',
'username': 'cisco',
'password': 'cisco',
'secret': 'cisco'}

print(commands)
# ['logging 10.255.255.1', 'logging buffered 20010', 'no logging
console']

result = send_config_commands(r1, commands)


print(result)
'''
config term
Enter configuration commands, one per line. End with CNTL/Z.
R1(config)#logging 10.255.255.1
R1(config)#logging buffered 20010
R1(config)#no logging console
R1(config)#end
R1#
'''

The script should send command command to all devices from the devices.yaml file using
the send_config_commands function.
commands = [
'logging 10.255.255.1', 'logging buffered 20010', 'no logging
console'
]

128
Task 18.2a
Copy the send_config_commands function from job 18.2 and add the log parameter. The
log parameter controls whether information is displayed about which device the connection
is to:
• if log is equal to True - information is printed
• if log is equal to False - information is not printed

By default, log is equal to True.

An example of how the function works:


result = send_config_commands(r1, commands)
# Connecting to 192.168.100.1…

result = send_config_commands(r1, commands, log=False)


#

The script should send command to all devices from the devices.yaml file using the
send_config_commands function.

129

You might also like