Scapy QOS Ping

Example below uses Python Scapy module to generate an ICMP Request with the TOS value 184 (DSCP 46/EF) which is useful for testing QOS.

from scapy.all import *

send(IP(dst=’192.168.1.200′,tos=184)/ICMP(id=1,length=256,seq=57))

Can run from Python 3 in Windows. Open two Python 3 instances and send an ICMP packet with tos=184.

In the second session use the following to see sniff and filter on the interface and check the TOS field of the ICMP response.

from scapy.all import *

sniff(iface=’eth0′, filter=’icmp’, prn=lambda x: x.show())

Type show_interfaces() to list the network adapter names and replace “eth0” with the description of the adapter you wish to sniff.

If you get an undefined literal error it maybe due to the way the above example is copied and pasted so pay attention to the use of double or single quotes.

 

Ansible VOSS Config Idempotency

It is useful to know when a task has made a change and it should be possible to re-run the same task again and no changes are made. Some commands sent to the switch may return a changed state even though the configuration has changed before. This can be the case when using abbreviated commands. There are also different ways to apply the same configuration and it is worth inspecting the running configuration to see the syntax of the command in there which might be different to what was sent.

For example, you can create a new VLAN with one line and then give the VLAN a name with another. If you do this the playbook will always come back as changed=1 even though it made no changes on subsequent plays. By checking the running config you can see that the two command lines are converted to a single line and also includes quotes around the VLAN name.

Altering the Jinga2 source file used as a template to match the expected result in the running config will change the behaviour. Running the playbook for the first time will change the config and state will be changed=1. Subsequent runs of the playbook will not change anything as the configuration is found in the running config with the exact same string and syntax. The state will be changed=0.

$ ansible-playbook voss_config_vlan.yml

PLAY [PLAY 1: Manage VLANs with voss_config and jinja2] ********************************************

TASK [TASK 1: Apply config via SSH] ****************************************************************

changed: [r1]

RUNNING HANDLER [HANDLER 1: Display changes] *******************************************************

ok: [r1] => {

“msg”: [

“vlan create 300 name \”Sales\” type port-mstprstp 0″,

“vlan create 400 name \”IT\” type port-mstprstp 0″

]

}

PLAY RECAP *****************************************************************************************

r1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

$ ansible-playbook voss_config_vlan.yml

PLAY [PLAY 1: Manage VLANs with voss_config and jinja2] ********************************************

TASK [TASK 1: Apply config via SSH] ****************************************************************

ok: [r1]

PLAY RECAP *****************************************************************************************

r1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

###

VLAN extract from running config:

vlan create 300 name “Sales” type port-mstprstp 0
vlan create 400 name “IT” type port-mstprstp 0

Jinga2 template:

$ cat templates/vlans.j2

{% for vlan in vlans %}

vlan create {{ vlan.vid }} name “{{ vlan.description }}” type port-mstprstp 0

{% endfor %}

 

YAML error checking

Add site-package yamllint to inspect YAML/YML files for errors.

pip install –user yamllint

Yamllint has two default configuration files: default.yaml and relaxed.yaml with pre-set rules. Can extend a configuration file and alter one of the rules.

Mylint.yaml:

# Mylint.yaml is my own configuration file for yamllint
# It extends the default.yaml by adjusting some options.

extends: default

rules:
new-lines: disable

###

Did this to avoid the error about the EOL character on the first line (—).

1:4 error wrong new line character: expected \n (new-lines)

 

 

Netmiko Running-Config

from netmiko import ConnectHandler
voss1 = {‘device_type’: ‘extreme_vsp’, ‘host’: ‘192.168.1.11’, ‘username’: ‘rwa’, ‘password’: ‘rwa’}
net_connect = ConnectHandler(**voss1)
net_connect.find_prompt()
net_connect.enable()
net_connect.send_command(‘terminal more disable’)
output = net_connect.send_command(‘show run’)
print(output)
net_connect.send_command(‘terminal more enable’)
savedoutput = open(“switch” + voss1[‘host’], “w”)
savedoutput.write(output)
savedoutput.close

Parsing Running Configs

https://pypi.org/project/ciscoconfparse/

Example 1:
from ciscoconfparse import CiscoConfParse
parse = CiscoConfParse(‘voss1run.log’, syntax=’ios’)
for intf_obj in parse.find_objects(‘^interface GigabitEthernet’):
print(“Interfaces: ” + intf_obj.text)
Interfaces: interface GigabitEthernet 1/1
Interfaces: interface GigabitEthernet 1/8
Example 2:
from ciscoconfparse import CiscoConfParse
parse = CiscoConfParse(‘exampleswitch.conf’, sytax=’ios’)
global_obj = parse.find_objects(r’^prompt’)[0]
hostname = global_obj.re_match_typed(r’^prompt\s+(\S+)’, default=”)
hostname
‘”VOSS1″‘

Ansible and Templates

Building configuration files from a template…

VOSS.J2

prompt {{ item.value.hostname }}
boot config flags tftpd
{% if item.value.sflow_enable %}
sflow agent-ip 192.168.211.10
sflow enable
{% endif %}
###
Playbook…

– name: Template Looping
hosts: localhostvars:
voss_devices: {
“vsp1”: {
“hostname”: “vsp1”,
“sflow_enable”: True
},
“vsp2”: {
“hostname”: “vsp2”,
“sflow_enable”: False
}
}
tasks:
– name: create switch config file
template:
src=/cygdrive/c/cygwin64/bin/voss.j2
dest=/cygdrive/c/cygwin64/bin/{{ item.key }}.config
with_dict: “{{ voss_devices }}”

-bash-4.4$ cat vsp1.config
prompt vsp1
boot config flags tftpd
sflow agent-ip 192.168.211.10
sflow enable
-bash-4.4$ cat vsp2.config
prompt vsp2
boot config flags tftpd
-bash-4.4$

Ansible and VOSS

This took me some time to get to work using Windows / Cygwin and my GNS3 VOSS simulated switch.

I struggled with getting Ansible + SSH to work from within Cygwin using key based authentication. But I wanted to test Ansible and Ansible-Playbook against VOSS so I persevered with simple user and password authentication which I got to work using a combination of files (ansible.cfg, inventory file = hosts and a test.yml file to run a single show command and display the output).

Hosts file

-bash-4.4$ cat ./inventory/hosts
[voss]
192.168.211.10
[voss:vars]
ansible_ssh_common_args=’-o StrictHostKeyChecking=no’
ansible_network_os=voss
ansible_connection=network_cli
ansible_ssh_pass=rwa
Ansible.cfg
-bash-4.4$ cat ansible.cfg
[defaults]
inventory = inventory
host_key_checking = False
[ssh_connection]
ansible_connection=network_cli
ssh_args = -o ControlMaster=no

First stage, was to get a ‘Pong’ response to my ‘Ping’:

-bash-4.4$ ansible -i ./inventory/hosts voss -u rwa -m ping -c network_cli -e ansible_network_os=voss

192.168.211.10 | SUCCESS => {
“ansible_facts”: {
“discovered_interpreter_python”: “/usr/bin/python”
},
“changed”: false,
“ping”: “pong”

Next, I wanted to run Ansible which would login to the switch and run a simple command:

-bash-4.4$ ansible -i ./inventory/hosts voss -u rwa -m voss_command -a “commands=’show clock'”

192.168.211.10 | SUCCESS => {
“ansible_facts”: {
“discovered_interpreter_python”: “/usr/bin/python”
},
“changed”: false,
“stdout”: [
“Sat Jun 13 13:02:15 2020 UTC”
],
“stdout_lines”: [
[
“Sat Jun 13 13:02:15 2020 UTC”
]
]
}

Now that Ansible could login and carry out a task I progressed to put a task into a Playbook:

-bash-4.4$ cat test.yml

– hosts: voss
  tasks:
– name: run show clock on remote devices
voss_command:
commands: show clock
           register: output
      – name: show output
debug:
var: output
-bash-4.4$ ansible-playbook test.yml
PLAY [voss] *********************************************************************
TASK [Gathering Facts] **********************************************************
ok: [192.168.211.10]
TASK [run show clock on remote devices] *****************************************
ok: [192.168.211.10]
TASK [show output] **************************************************************
ok: [192.168.211.10] => {
“output”: {
“changed”: false,
“failed”: false,
“stdout”: [
“Sat Jun 13 15:00:37 2020 UTC”
],
“stdout_lines”: [
[
“Sat Jun 13 15:00:37 2020 UTC”
]
]
}
}
PLAY RECAP **********************************************************************
192.168.211.10             : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Checking stdout for a string…

– hosts: voss
gather_facts: false
  tasks:
– name: show run pipe prompt
voss_command:
commands:
– enable
– show run | i promptregister: output- name: show output

        when: output.stdout is search(‘VOSS1’)

debug:

            msg: ‘{{ output.stdout.1 }}’
###
Output snippet includes…
TASK [show output] *************************************************************
ok: [192.168.211.10] => {
“msg”: “prompt \”VOSS1\””
}
###
Add VLANs…

– hosts: voss
gather_facts: false
vars:
vlan_numbers: [100, 200]
tasks:
– name: add vlans
voss_config:
commands:
– vlan create {{ item }} type port-mstprstp 0
with_items: “{{ vlan_numbers }}”
become: yes
     register: output
###
-bash-4.4$ ansible-playbook test3.yml
PLAY [voss] ********************************************************************
TASK [add vlans] ***************************************************************
changed: [192.168.211.10] => (item=100)
changed: [192.168.211.10] => (item=200)
PLAY RECAP *********************************************************************
192.168.211.10             : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
###
VOSS1:1#show vlan basic | i ‘100’
100   VLAN-100         byPort       0       none         N/A             N/A             0
VOSS1:1#show vlan basic | i ‘200’
200   VLAN-200         byPort       0       none         N/A             N/A             0
###
Looping over a dictionary…

– hosts: voss
gather_facts: falsevars:
vlans: {
“100”: {“description”: “floor1”, “ip”: “1.1.1.1”},
“200”: {“description”: “floor2”, “ip”: “1.1.2.1”}
}
  tasks:
– name: add vlans
voss_config:
commands:
– vlan create {{ item.key }} type port-mstprstp 0
with_dict: “{{ vlans }}”
become: yes- name: configure vlans
voss_config:
commands:
– ip address {{ item.value.ip }}/24
parents: interface vlan {{ item.key }}
with_dict: “{{ vlans }}”
become: yes- name: name vlans
voss_config:
commands:
– vlan name {{ item.key }} {{ item.value.description }}
with_dict: “{{ vlans }}”
become: yes

###
Create a results folder and write results to a file using each host in the filename.

$ cat playbook1.yml

– name: “Play 1: Capture sys-info”

hosts: routers

connection: network_cli

tasks:

– name: “Task 1: Show sys-info”

voss_command:

commands: show sys-info

register: result

– name: “Task 2: Print output”

debug:

msg: “{{ result }}”

– name: “Task 3: Create files folder”

file:

path: “outputs”

state: directory

run_once: true

– name: “Task 4: Write stdout to file”

copy:

content: “{{ result.stdout[0] }}\n”

dest: “outputs/{{ inventory_hostname }}.txt”

###
References:

Nornir Script

Get ARP table from VSP switches…

from nornir import InitNornir
from nornir.plugins.tasks.networking import netmiko_send_command
from nornir.plugins.functions.text import print_result

nr = InitNornir()

result = nr.run(
task=netmiko_send_command,
command_string=”show ip arp”
)

print_result(result)

Hosts.yaml


voss-1:
hostname: ‘192.168.1.10’
port: 22
username: ‘rwa’
password: ‘rwa’
platform: ‘extreme_vsp’

voss-2:
hostname: ‘192.168.1.11’
port: 22
username: ‘rwa’
password: ‘rwa’
platform: ‘extreme_vsp’

Netmiko Script

Device_Type: Extreme_VSP

Get list of IP interfaces…

from netmiko import ConnectHandler
voss1 = {‘device_type’: ‘extreme_vsp’, ‘host’: ‘192.168.1.10’, ‘username’: ‘rwa’, ‘password’: ‘rwa’}
net_connect = ConnectHandler(**voss1)
net_connect.find_prompt()
output = net_connect.send_command(‘show ip interface’)
print(output)

Making a configuration change, for example, disabling FTPD…

from netmiko import ConnectHandler
voss2 = {‘device_type’: ‘extreme_vsp’, ‘host’: ‘192.168.1.11’, ‘username’: ‘rwa’, ‘password’: ‘rwa’}
net_connect = ConnectHandler(**voss2)
net_connect.find_prompt()
net_connect.enable()
net_connect.send_config_set([‘no boot config flags ftpd’])

Device_Type: Extreme_ERS

from netmiko import ConnectHandler
ers1 = {‘device_type’: ‘extreme_ers’, ‘host’: ‘192.168.1.5’, ‘username’: ‘RW’, ‘password’: ‘securepasswd’}
net_connect = ConnectHandler(**ers1)
net_connect.find_prompt()
output = net_connect.send_command(‘show system’)
print(output)

 

 

 

Paramiko Change Config

Paramiko script which logs in to VOSS and sets the FTPD boot flag.

Devices.json

{
“voss-1”: {“ip”: “192.168.1.10”},
“voss-2”: {“ip”: “192.168.1.11”}
}

Commands.txt

enable
config t
boot config flags ftpd
exit
save config
exit

Verify changes made to running-config and configuration file on intflash.

show run | I ftpd

show grep ftpd config.cfg

VSP-8284XSQ-1:1#show run | i ftpd
boot config flags ftpd
VSP-8284XSQ-1:1#grep ftpd config.cfg
boot config flags ftpd
VSP-8284XSQ-1:1#

Script:

import paramiko, getpass, time, json

with open(‘devices.json’, ‘r’) as f:
devices = json.load(f)

with open(‘commands.txt’, ‘r’) as f:
commands = f.readlines()

username = input(‘Username: ‘)
password = getpass.getpass(‘Password: ‘)

max_buffer = 65535

def clear_buffer(connection):
if connection.recv_ready():
return connection.recv(max_buffer)

# Starts the loop for devices
for device in devices.keys():
outputFileName = device + ‘_output.txt’
connection = paramiko.SSHClient()
connection.set_missing_host_key_policy(paramiko.AutoAddPolicy())
connection.connect(devices[device][‘ip’], username=username, password=password, look_for_keys=False, allow_agent=False)
new_connection = connection.invoke_shell()
output = clear_buffer(new_connection)
time.sleep(2)
new_connection.send(“terminal more disable\n”)
output = clear_buffer(new_connection)
with open(outputFileName, ‘wb’) as f:
for command in commands:
new_connection.send(command)
time.sleep(2)
output = new_connection.recv(max_buffer)
print(output)
f.write(output)

new_connection.close()