Provision a Cisco CSR1000V-Router as Virtual-Box-VM with sliptreamed Configuration, wait until it’s up and open an Putty-SSH-Connection

The existing Windows-Comman-Script has been enhanced so it now

  • waits until the Router-VM is up
  • got an IP-Address assigned using DHCP.

For Demonstration-Purposes an SSH-Connection will be established.

  • The public-key of my laptop-ssh-client is part of the slipstreamed Router-Configuration.

Deploy a Router-VM with hostname „CSY“.

C:\RH\work\entwicklung\csr1000v-provision-in-virtualbox>CSR1000v-Virtual-Box.cmd CSY
Virtual machine 'CSY' is created and registered.
UUID: 915a7495-0728-4fc5-9c4c-21b3106a07e5
Settings file: 'c:\RH\LAB\VM\CSY\CSY.vbox'
Creating ISO image at c:\RH\LAB\VM\CSY\CSY_config.iso, 1 Files, Size: 8,00 KB
100%
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Medium created. UUID: b79d708a-0a0a-4866-853b-3ac45c6a6127
Waiting for VM "CSY" to power on...
VM "CSY" has been successfully started.
Waiting for VM "CSY" to be initialized...
Router "CSY" up and running, using IP=192.168.56.102
Establishing SSH-Connection as "labuser"
Done

Establish an SSH-Connection to the discovered Router-IP:

Have a look to the startup-config used:

This is the „CMD“-Script used:

@echo off

: Ronald Heitmann

:set VM=CSR1000vX
:set /p VM="Enter CSR1000v Hostname:"
: Parameter given? Use it as VM-Name
set VM=%1
if not defined VM set /p VM="Enter CSR1000v Hostname:"

:echo %VM%

set PUTTYUSER=labuser
set PUTTYPK=C:\RH\work\putty\labuser.ppk


set BASE=c:\RH\LAB\VM

set ISO=C:\Downloads\csr1000v-universalk9.16.09.0x.iso

: Create VM
:VBoxManage createvm --name %VM% --ostype "Linux_64" --basefolder %BASE% --register
VBoxManage createvm --name %VM% --ostype "Linux26_64" --basefolder %BASE% --register

:now the Path in the Filesystem to ...LOC... exists
set LOC=%BASE%\%VM%

: Create ISO containing slipstreamed config-file
: use UNXTOOLS "sed" to customize the "hostname"-Command
:
set CFGISO=%LOC%\%VM%_config.iso
set CFGSRC=C:\RH\LAB\VM\iosxe_config.txt
set CFGTXT=%LOC%\iosxe_config.txt
cat %CFGSRC% | sed "s/HOSTNAME/%VM%/g" > %CFGTXT%
"%ProgramFiles(x86)%"\CDBurnerXP\cdbxpcmd.exe --burn-data -file:%CFGTXT% -iso:%CFGISO% -format:iso

: Customize VM
VBoxManage modifyvm %VM% --memory 4096
VBoxManage modifyvm %VM% --vram 16
VBoxManage modifyvm %VM% --pae on --paravirtprovider default --hwvirtex on --nestedpaging on

: Storage
VBoxManage createhd --filename %LOC%\%VM%.vdi --size 8192
VBoxManage storagectl %VM% --name "IDE-CTL" --add ide --portcount 2 --bootable on
VBoxManage storageattach %VM% --storagectl IDE-CTL --port 0 --device 0 --type hdd --medium %LOC%\%VM%.vdi
VBoxManage storageattach %VM% --storagectl IDE-CTL --port 1 --device 0 --type dvddrive --medium %ISO%
VBoxManage storageattach %VM% --storagectl IDE-CTL --port 1 --device 1 --type dvddrive --medium %CFGISO%
VBoxManage modifyvm %VM% --boot1=dvd --boot2=disk --boot3=none --boot4=none

: Serial-Interfaces via "Pipe"
VBoxManage modifyvm %VM% --uartmode1 server \\.\pipe\%VM%
VBoxManage modifyvm %VM% --uart1 0x3f8 4
VBoxManage modifyvm %VM% --uartmode2 server \\.\pipe\%VM%_diag
VBoxManage modifyvm %VM% --uart2 0x2f8 4

: Audio
VBoxManage modifyvm %VM% --audio none

: NICs
VBoxManage modifyvm %VM% --nic1 nat --nic2 hostonly --nic3 hostonly --nic4 hostonly
VBoxManage modifyvm %VM% --nictype1 virtio --nictype2 virtio --nictype3 virtio --nictype4 virtio
VBoxManage modifyvm %VM% --nicpromisc1 allow-all --nicpromisc2 allow-all --nicpromisc3 allow-all --nicpromisc4 allow-all
VBoxManage modifyvm %VM% --hostonlyadapter2 "VirtualBox Host-Only Ethernet Adapter"
VBoxManage modifyvm %VM% --hostonlyadapter3 "VirtualBox Host-Only Ethernet Adapter"
VBoxManage modifyvm %VM% --hostonlyadapter4 "VirtualBox Host-Only Ethernet Adapter"

: Set the VM-Logo for the VirtualBox-Inventory
VBoxManage modifyvm %VM% --iconfile C:\RH\LAB\72px-Cisco_logo.svg.png

: Boot the VM, it'l reboot once to apply the running-config
VBoxManage startvm %VM% --type headless

: Wait for the VM to be fully initialized
: - with DHCP-IP-Address at "Gig 2"
: - and store this IP-Address in Variable VMIP
echo Waiting for VM "%VM%" to be initialized...
for /f %%I in ('python WaitForCDPNeighbor.py -n %VM% -c -i "VirtualBox Host-Only Ethernet Adapter" 2^> nul') do @(set VMIP=%%I)

echo Router "%VM%" up and running, using IP=%VMIP%

: Connect to Router using Putty/SSH
echo Establishing SSH-Connection as "%PUTTYUSER%"
start putty -ssh -i %PUTTYPK% %PUTTYUSER%@%VMIP%

echo Done

WaitForCDPNeighbor.py enhanced: Wait for CDP Hostname with usable connected IP-Address.

The following version adds a „-c“ option: „Connected IP-Address“-Check.

  • the script exits, when the Router-VM is up and running and has an usable IP-Address

The Script still checks all CDP-Packets received at the specified Interface:

  • If the Sender has the correct „Hostname“ it reads the CDP-Management-IP-Address announced.
  • This IP-Address has to be within the IP-Range of any connected IP-Network at the specified Interface.

So it’ll be possible to establish a SSH-Session to the router-VM.

C:\> echo %VM%
CSX

C:\> python WaitForCDPNeighbor.py -n %VM% -c -i "VirtualBox Host-Only Ethernet Adapter"
192.168.56.101
#! /usr/bin/env python

# Ronald Heitmann
#
import argparse

from scapy.all import *
load_contrib("cdp")

from netaddr import IPNetwork, IPAddress

from netifaces import AF_INET, AF_INET6, AF_LINK
import netifaces

# returns a list of all IP-Adresses bound to the specified Interface "if_name"
def get_connected(if_name):
  if_id = ""
  networks = []

  for i in ifaces.data.keys():
    iface = ifaces.data[i]
    wname = iface.data['name']
    if wname == if_name:
      if_id = i
      addresses = netifaces.ifaddresses(i)
      if AF_INET in addresses:
        for addr in netifaces.ifaddresses(i)[AF_INET]:
          #print(addr)
          ipnetwork = IPNetwork(addr["addr"]+"/"+addr["netmask"]).cidr
          networks.append(ipnetwork)
  #print(networks)
  return networks

# checks, if the IP-Address "ip" is within the subnet-range of any network contained in the list "networks"

def is_IP_connected(ip, networks):
  found = False
  ipnetwork = IPNetwork(ip).cidr

  for n in networks:
    if (ipnetwork in n):
      found = True

  return found

  
def main():

    # Parse CLI-Arguments
    parser = argparse.ArgumentParser(description='Wait for a CDP-Neighbor.')
    parser.add_argument("-i", "--interface", help="monitored interface", default="VirtualBox Host-Only Ethernet Adapter")
    parser.add_argument("-n", "--hostname", help="Neighbor to wait for", required=True)
    parser.add_argument("-c", "--connected", help="wait, until CDP-Neighbor announces an directly-connected IP-Address", action='store_true')
    args = parser.parse_args()

    #looking for a specific hostname
    #
    hostname = ""
    wait_for_hostname = args.hostname

    #watching for CDP-Packets from this host on a specific interface
    #the router-IP should use a directly-connected IP-Address
    ip = "0.0.0.0"
    wait_for_connected = args.connected
    
    interface=args.interface
    networks = get_connected(interface)
    #print(networks) 

    #CDP
    capturefilter="ether dst 01:00:0c:cc:cc:cc"

    while not((hostname == wait_for_hostname) and (not(wait_for_connected) or is_IP_connected(ip,networks))):
      p=sniff(iface=interface, count=1, filter=capturefilter)
      pkt=p[0]
      #print("Packet received",pkt.show())

      #is this a CDP-Packet containing a hostname?
      if (CDPMsgDeviceID in pkt):
        #is this the CDP-Neighbor we're looking for?
        device=pkt["CDPMsgDeviceID"].val.decode()
        hostname=device.split(".")[0]
        #print("Hostname:",hostname)

        if (hostname == wait_for_hostname):
          #is this a CDP-Packet containing a management-IP-Address?
          if (CDPAddrRecordIPv4 in pkt):
            ip=pkt["CDPAddrRecordIPv4"].addr
            #print("IP-Address found:",ip)

    #return the IP-Address to the calling application or the CLI
    return ip

if __name__ == "__main__":
    print(main())

Python: Combine NetIfaces, Scapy and IPAddress to find local connected network

You got the task to generate a list of IP-Networks connected to LAN „VirtualBox Host-Only Network“. There might be more than one IP-Network.

Use Scapy to crawl through all Interfaces and get the human-readable interface name [only required for windows users]. Use NetIfaces to get a list of IP-Addresses connected to this interface. Use IPAddress to calculate the IP-Network(s) directly connected.

! multiple IPs per Interface supporte
!
from netifaces import AF_INET, AF_INET6, AF_LINK
import netifaces

from scapy.all import *

import ipaddress


if_name = "VirtualBox Host-Only Network"
if_id = ""
if_inet = []

for i in ifaces.data.keys():
  iface = ifaces.data[i]
  wname = iface.data['netid']
  if wname == if_name:
    if_id = i
    addresses = netifaces.ifaddresses(i)
    if AF_INET in addresses:
      for addr in netifaces.ifaddresses(i)[AF_INET]:
        print(addr)
        ipaddr = ipaddress.ip_interface(addr["addr"]+"/"+addr["netmask"])
        ipnetwork = ipaddr.network
        print(ipaddr,ipnetwork)
        if_inet.append(ipnetwork)

print("NAME: {0}\nIP: {1}\nID: {2}".format(if_name,if_inet,if_id))

In my case, only one subnet is directly connected:

  • 192.168.56.0/24
...
{'addr': '192.168.56.1', 'netmask': '255.255.255.0', 'broadcast': '192.168.56.255'}
192.168.56.1/24 192.168.56.0/24
>>> print("NAME: {0}\nIP: {1}\nID: {2}".format(if_name,if_inet,if_id))
NAME: VirtualBox Host-Only Network
IP: [IPv4Network('192.168.56.0/24')]
ID: {D30DEC05-D495-4DA1-81F1-42B07885B0EB}
>>>

Python netifaces and scapy: Getting meaningful Interface-names and full IP-Adresses-/Netmask-Information

Using Windows:

  • scapy reads:
    • meaningful interface-names
    • ip-address
    • but no netmask
  • netifaces reads:
    • full Ethernet/IP/IPv6-Information
    • but no meaningful interface-names

Mixing both, provides everything needed.

  1. Netifaces only
  2. from netifaces import AF_INET, AF_INET6, AF_LINK
    import netifaces
    
    for i in netifaces.interfaces():
       niif=netifaces.ifaddresses(i)
       print("i",i)
       for k,v in niif.items():
         print("Key",k)
         if k==AF_LINK:
           print("LINK:",v)
         if k==AF_INET:
           print("IPv4",v)
         if k==AF_INET6:
           print("IPv6",v)
       print()
    
    >>> for i in netifaces.interfaces():
    ...    niif=netifaces.ifaddresses(i)
    ...    print("i",i)
    ...    for k,v in niif.items():
    ...      print("Key",k)
    ...      if k==AF_LINK:
    ...        print("LINK:",v)
    ...      if k==AF_INET:
    ...        print("IPv4",v)
    ...      if k==AF_INET6:
    ...        print("IPv6",v)
    ...    print()
    ...
    i {07E9D8A4-E167-4FFF-B851-61A20C49AE6E}
    Key -1000
    LINK: [{'addr': '00:00:81:00:de:11'}]
    Key 23
    IPv6 [{'addr': 'fe80::8400:abb0:2a62:a173%4', 'netmask': 'ffff:ffff:ffff:ffff::/64', 'broadcast': 'fe80::ffff:ffff:ffff:ffff%4'}]
    
    i {D30DEC05-D495-4DA1-81F1-42B07885B0EB}
    Key -1000
    LINK: [{'addr': '0a:00:27:00:00:14'}]
    Key 23
    IPv6 [{'addr': 'fe80::1e0:4a4:8afc:90f7%20', 'netmask': 'ffff:ffff:ffff:ffff::/64', 'broadcast': 'fe80::ffff:ffff:ffff:ffff%20'}]
    Key 2
    IPv4 [{'addr': '192.168.56.1', 'netmask': '255.255.255.0', 'broadcast': '192.168.56.255'}]
    
    i {402C453F-4B74-4883-9257-BD31FAB7AB57}
    Key -1000
    LINK: [{'addr': '74:70:fd:bd:10:38'}]
    Key 23
    IPv6 [{'addr': 'fe80::2599:368f:e80d:94b6%8', 'netmask': 'ffff:ffff:ffff:ffff::/64', 'broadcast': 'fe80::ffff:ffff:ffff:ffff%8'}]
    
    i {2120C2CA-E7D4-45DF-8090-A8D7F48EFF42}
    Key -1000
    LINK: [{'addr': '76:70:fd:bd:10:37'}]
    Key 23
    IPv6 [{'addr': 'fe80::34d6:9683:4af5:afad%6', 'netmask': 'ffff:ffff:ffff:ffff::/64', 'broadcast': 'fe80::ffff:ffff:ffff:ffff%6'}]
    
    i {F931F123-70A4-4CFB-BD9C-C0A509080286}
    Key -1000
    LINK: [{'addr': '74:70:fd:bd:10:37'}]
    Key 23
    IPv6 [{'addr': 'fe80::1926:3c29:1b34:e1f6%22', 'netmask': 'ffff:ffff:ffff:ffff::/64', 'broadcast': 'fe80::ffff:ffff:ffff:ffff%22'}]
    Key 2
    IPv4 [{'addr': '192.168.1.163', 'netmask': '255.255.255.0', 'broadcast': '192.168.1.255'}]
    
    i {693A5869-6A31-11E8-85D1-806E6F6E6963}
    Key -1000
    LINK: [{'addr': ''}]
    Key 23
    IPv6 [{'addr': '::1', 'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128', 'broadcast': '::1'}]
    Key 2
    IPv4 [{'addr': '127.0.0.1', 'netmask': '255.0.0.0', 'broadcast': '127.255.255.255'}]
    
  3. Adding Scapy to get meaningful interface names
  4. from scapy.all import *
    
    for i in ifaces.data.keys():
      print(i)
      iface = ifaces.data[i]
      wname = iface.data['netid']
      addresses = netifaces.ifaddresses(i)
      af_inet = [{"addr": "0.0.0.0", "netmask": "0.0.0.0", "broadcast": "0.0.0.0"}]
      if AF_INET in addresses:
        af_inet = netifaces.ifaddresses(i)[AF_INET]
      print(wname,af_inet)
    
    >>> for i in ifaces.data.keys():
    ...   print(i)
    ...   iface = ifaces.data[i]
    ...   wname = iface.data['netid']
    ...   addresses = netifaces.ifaddresses(i)
    ...   af_inet = [{"addr": "0.0.0.0", "netmask": "0.0.0.0", "broadcast": "0.0.0.0"}]
    ...   if AF_INET in addresses:
    ...     af_inet = netifaces.ifaddresses(i)[AF_INET]
    ...   print(wname,af_inet)
    ...
    {F931F123-70A4-4CFB-BD9C-C0A509080286}
    Wi-Fi [{'addr': '192.168.1.163', 'netmask': '255.255.255.0', 'broadcast': '192.168.1.255'}]
    {D30DEC05-D495-4DA1-81F1-42B07885B0EB}
    VirtualBox Host-Only Network [{'addr': '192.168.56.1', 'netmask': '255.255.255.0', 'broadcast': '192.168.56.255'}]
    {07E9D8A4-E167-4FFF-B851-61A20C49AE6E}
    ETH_DELL [{'addr': '0.0.0.0', 'netmask': '0.0.0.0', 'broadcast': '0.0.0.0'}]
    
    

Python „netifaces“ installation: „Microsoft Visual C++ 14.0 is required“

I expected a nobrainer:

C:\RH>pip install netifaces
Collecting netifaces
  Downloading https://files.pythonhosted.org/packages/81/39/4e9a026265ba944ddf1fea176dbb29e0fe50c43717ba4fcf3646d099fe38/netifaces-0.10.7.tar.gz
Installing collected packages: netifaces
  Running setup.py install for netifaces ... error
    Complete output from command c:\users\rh\appdata\local\programs\python\python37\python.exe -u -c "import setuptools, tokenize;__file__='C:\\Users\\RH\\AppData\\Local\\Temp\\pip-install-wbfanly3\\netifaces\\setup.py';f=getattr(tokenize, 'open', open)(__file__);code=f.read().replace('\r\n', '\n');f.close();exec(compile(code, __file__, 'exec'))" install --record C:\Users\RONALD~1.HEI\AppData\Local\Temp\pip-record-m26yfbyt\install-record.txt --single-version-externally-managed --compile:
    running install
    running build
    running build_ext
    building 'netifaces' extension
    error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools

The suggested URL to download the missing software is wrong (HTTP 404).

  1. Go to the Microsoft-Repository Tools for Visual Studio 2017 or use the direct link to vs_buildtools.exe

    • …it’s about 1.2MB
  2. run „vs_buildtools.exe“
    • …it downloads ~ 70 MB
  3. Select „Workloads => Windows => [x] Visual C++ Build Tools“ => [Install]
    • …it downloads 1.12 GB
    • …and installs
  4. and … unlucky…

  5. reboot required

Now netifaces can get installed:

C:\RH>pip install netifaces
Collecting netifaces
  Using cached https://files.pythonhosted.org/packages/81/39/4e9a026265ba944ddf1fea176dbb29e0fe50c43717ba4fcf3646d099fe38/netifaces-0.10.7.tar.gz
Installing collected packages: netifaces
  Running setup.py install for netifaces ... done
Successfully installed netifaces-0.10.7

Python Scapy: Wait for CDP-Neighbor

After provisioning a new Cisco CSR1000V-Router this script

  • waits until the router is fully deployed
  • got it’s Management-IP-Address assigned via DHCP.

and returns this Management IP-Address.

  1. Prepare
  2. First, install „netaddr“-package.

    • needed to check, if the management-IP of the CDP-Neighbor is in the correct IP-Subnet
    C:\RH>pip install netaddr
    Collecting netaddr
      Downloading https://files.pythonhosted.org/packages/ba/97/ce14451a9fd7bdb5a397abf99b24a1a6bb7a1a440b019bebd2e9a0dbec74/netaddr-0.7.19-py2.py3-none-any.whl (1.6MB)
        100% |████████████████████████████████| 1.6MB 4.0MB/s
    Installing collected packages: netaddr
    Successfully installed netaddr-0.7.19
    
  3. the „wait-for-CDP“-Script
  4. Obviously far to much hard-coded stuff, just as an example 😉 waiting for a CDP-Neighbor to appear with the following properties:

    • hostname „CSR-A“
    • an IP-Address within the network „192.168.56.0 /24“
    • at the interface

    • VirtualBox Host-Only-Network
    #! /usr/bin/env python
    
    from scapy.all import *
    load_contrib("cdp")
    
    from netaddr import IPNetwork, IPAddress
    
    # run it for max. 99 Packets
    ip = "0.0.0.0"
    wait_for_hostname = "CSR-A"
    wait_for_host_in_network = "192.168.56.0/24"
    
    interface="VirtualBox Host-Only Ethernet Adapter"
    capturefilter="ether dst 01:00:0c:cc:cc:cc"
    
    while not(IPAddress(ip) in IPNetwork(wait_for_host_in_network)):
      p=sniff(iface=interface, count=1, filter=capturefilter)
      pkt=p[0]
      #print("Packet received",pkt.show())
    
      #is this a CDP-Packet containing a hostname?
      if (CDPMsgDeviceID in pkt):
        #is this the CDP-Neighbor we're looking for?
        device=pkt["CDPMsgDeviceID"].val.decode()
        hostname=device.split(".")[0]
        #print("Hostname:",hostname)
    
        if (hostname == wait_for_hostname):
          #is this a CDP-Packet containing a management-IP-Address?
          if (CDPAddrRecordIPv4 in pkt):
            ip=pkt["CDPAddrRecordIPv4"].addr
            #print("IP-Address found:",ip)
    
    #return the IP-Address to the calling application or the CLI
    print(ip)
    
  5. Provision a new CSR1000V Router named „CSR-A“
  6. C:\RH>CSR1000v-Virtual-Box.cmd
    Enter CSR1000v Hostname:CSR-A
    Virtual machine 'CSR-A' is created and registered.
    UUID: 8a9c969e-3895-4a7c-9cbc-5f5551bf1b7b
    Settings file: 'c:\RH\LAB\VM\CSR-A\CSR-A.vbox'
    Creating ISO image at c:\RH\LAB\VM\CSR-A\config.iso, 1 Files, Size: 8,00 KB
    100%
    0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
    Medium created. UUID: 55ea8df5-7e3c-4485-8e08-9302cb61a09d
    Waiting for VM "CSR-A" to power on...
    VM "CSR-A" has been successfully started.
    
  7. Run the „wait-for-CDP-Neighbor“-Script
  8. In a second CMD-Box, and… Be patient, of course.

    C:\RH>python wait-for-cdpneighbor.py
    192.168.56.101
    
  9. Or store the returned IP-Address in an Shell-Variable „ROUTER-IP“/li>
    C:\RH>for /f %I in ('python wait-for-cdpneighbor.py 2^> nul') do @(set ROUTER-IP=%I)
    
  10. and pass it to putty – using public-key authentication
  11. The Public-Key for user „labuser“ is part of my baseline-config used to provision the virtual-router.

    C:\RH>putty -ssh -i C:\RH\work\putty-rsa-key\labuser.ppk labuser@%ROUTER-IP%
    

    Works, this is a nice preparation for an automated virtual ansible-lab, too!

Python Scapy: CDP-Monitor

Watch and decode Cisco Discovery Protocol Packetes.

  1. CDP Monitor-Script
  2. Inspired by the Scapy ARP-Monitor i created an CDP-Monitor displaying hostname and an optional Management-IP-Address.

    #! /usr/bin/env python
    from scapy.all import *
    load_contrib("cdp")
    
    def cdp_monitor_callback(pkt):
      ip = "0.0.0.0"
      if (CDPMsgDeviceID in pkt):
        device=pkt["CDPMsgDeviceID"].val.decode()
        hostname=device.split(".")[0]
        if (CDPAddrRecordIPv4 in pkt):
          ip=pkt["CDPAddrRecordIPv4"].addr
        return "Device: {0} IP: {1}".format(hostname,ip)
    
    interface="VirtualBox Host-Only Ethernet Adapter"
    capturefilter="ether dst 01:00:0c:cc:cc:cc"
    
    # run it for max. 99 Packets
    p=sniff(prn=cdp_monitor_callback, iface=interface, count=99, filter=capturefilter, store=0)
    
  3. Real-Life Example
    • Run this script:
    • C:\RH>python
      Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
      Type "help", "copyright", "credits" or "license" for more information.
      >>> #! /usr/bin/env python
      ... from scapy.all import *
      >>> load_contrib("cdp")
      >>>
      >>> def cdp_monitor_callback(pkt):
      ...   ip = "0.0.0.0"
      ...   if (CDPMsgDeviceID in pkt):
      ...     device=pkt["CDPMsgDeviceID"].val.decode()
      ...     hostname=device.split(".")[0]
      ...     if (CDPAddrRecordIPv4 in pkt):
      ...       ip=pkt["CDPAddrRecordIPv4"].addr
      ...     return "Device: {0} IP: {1}".format(hostname,ip)
      ...
      >>> interface="VirtualBox Host-Only Ethernet Adapter"
      >>> capturefilter="ether dst 01:00:0c:cc:cc:cc"
      >>>
      >>> # run it for max. 99 Packets
      ... p=sniff(prn=cdp_monitor_callback, iface=interface, count=99, filter=capturefilter, store=0)
      
    • Open another CMD-Window to spin up a new CSR1000V-Router:
    • C:\RH\work\entwicklung\csr1000v-provision-in-virtualbox>CSR1000v-Virtual-Box.cmd
      Enter CSR1000v Hostname:CSR-CDPTEST-A
      Virtual machine 'CSR-CDPTEST-A' is created and registered.
      UUID: eaa931bd-9db4-4a78-b263-754d4bb300a0
      Settings file: 'c:\RH\LAB\VM\CSR-CDPTEST-A\CSR-CDPTEST-A.vbox'
      Creating ISO image at c:\RH\LAB\VM\CSR-CDPTEST-A\config.iso, 1 Files, Size: 8,00 KB
      100%
      0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
      Medium created. UUID: 3019216e-2e61-435a-95f2-3d26312c3ed9
      Waiting for VM "CSR-CDPTEST-A" to power on...
      VM "CSR-CDPTEST-A" has been successfully started.
      
    • Go back to the „python“-Screen and wait:
    • >>> # run it for max. 99 Packets
      ... p=sniff(prn=cdp_monitor_callback, iface=interface, count=99, filter=capturefilter, store=0)
      Device: CSR-CDPTEST-A IP: 0.0.0.0
      Device: CSR-CDPTEST-A IP: 0.0.0.0
      Device: CSR-CDPTEST-A IP: 0.0.0.0
      Device: CSR-CDPTEST-A IP: 0.0.0.0
      Device: CSR-CDPTEST-A IP: 0.0.0.0
      Device: CSR-CDPTEST-A IP: 10.0.2.15
      Device: CSR-CDPTEST-A IP: 10.0.2.15
      Device: CSR-CDPTEST-A IP: 10.0.2.15
      

      It works, the IOS-XE CSR1000V-Router is installed and it got it’s baseline-config applied:

      • Hostname
      • Interface set to „ip address dhcp“
      • CDP enabled

      takes some time, but these first CDP-Packets captured were not sent in 60s interval, so don’t be scared 😉

Python Scapy: Capturing „Cisco Discovery Protocol (CDP)“

It’s hard to believe, how easy it is to capture (and to craft) LAN-Data using Python Scapy.

I need to capture CDP-Packets and to extract information like the hostname of the sender and it’s IP-Address.

As an modification of the „ARP-Monitor“-example taken from the manual Using scapy in your tools.

  1. Scapy Installation
  2. nothing special: pip install

    C:\RH>pip install scapy
    Collecting scapy
      Downloading https://files.pythonhosted.org/packages/68/01/b9943984447e7ea6f8948e90c1729b78161c2bb3eef908430638ec3f7296/scapy-2.4.0.tar.gz (3.1MB)
        100% |████████████████████████████████| 3.1MB 420kB/s
      In the tar file C:\Users\RONALD~1.HEI\AppData\Local\Temp\pip-unpack-4jm9kde1\scapy-2.4.0.tar.gz the member scapy-2.4.0/README is invalid: unable to resolve link inside archive
    Installing collected packages: scapy
      Running setup.py install for scapy ... done
    Successfully installed scapy-2.4.0
    
  3. Python: Install Scapy
  4. C:\RH>python
    Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    
    >>> from scapy.all import *
    
  5. Example: List all Interfaces of my Laptop
  6. >>> ifaces
    INDEX  IFACE                                     IP            MAC
    20     VirtualBox Host-Only Ethernet Adapter     192.168.56.1  0A:00:27:00:00:14
    22     Intel(R) Dual Band Wireless-AC 8265       172.20.10.2   IntelCor:BD:10:37
    4      Intel(R) Ethernet Connection (4) I219-LM  47.11.17.10   BayNetwo:00:DE:11
    
    >>> for i in ifaces.data.keys():
    ...     iface = ifaces.data[i]
    ...     name = str(iface.name)
    ...     wname = iface.data['netid']
    ...     mac = str(iface.mac)
    ...     ip  = str(iface.ip)
    ...     print("DESCR:{0}, NAME:{1}, MAC:{2}, IP:{3}".format(wname,name,mac,ip))
    ...
    DESCR:Wi-Fi, NAME:Intel(R) Dual Band Wireless-AC 8265, MAC:74:70:FD:BD:10:37, IP:172.20.10.2
    DESCR:VirtualBox Host-Only Network, NAME:VirtualBox Host-Only Ethernet Adapter, MAC:0A:00:27:00:00:14, IP:192.168.56.1
    DESCR:ETH_DELL, NAME:Intel(R) Ethernet Connection (4) I219-LM, MAC:00:00:81:00:DE:11, IP:47.11.17.10
    
  7. Capture 10 Packets on a specific interface
  8. The "NAME" is needed as interface-handle
    >>> interface="VirtualBox Host-Only Ethernet Adapter"
    
    >>> p=sniff(iface=interface, count=10)
    >>> p.display()
    0000 Ether / IP / UDP 192.168.56.1:17500 > 255.255.255.255:17500 / Raw
    0001 Ether / IP / UDP 192.168.56.1:17500 > 255.255.255.255:17500 / Raw
    0002 Ether / IP / UDP 192.168.56.1:17500 > 192.168.56.255:17500 / Raw
    0003 Ether / IP / UDP 192.168.56.1:17500 > 255.255.255.255:17500 / Raw
    0004 Ether / IP / UDP 192.168.56.1:17500 > 255.255.255.255:17500 / Raw
    0005 Ether / IP / UDP 192.168.56.100:bootps > 255.255.255.255:bootpc / BOOTP / DHCP
    0006 Ether / IP / UDP 192.168.56.102:50472 > 255.255.255.255:10067 / Raw
    0007 Ether / IP / UDP 192.168.56.103:49580 > 255.255.255.255:10067 / Raw
    0008 Ether / IP / UDP 192.168.56.1:17500 > 255.255.255.255:17500 / Raw
    0009 Ether / IP / UDP 192.168.56.1:17500 > 255.255.255.255:17500 / Raw
    
  9. Capture CDP-Packets
  10. Capture CDP
    
    >>> p=sniff(iface=interface, count=2, filter="ether dst 01:00:0c:cc:cc:cc")
    >>> p
    <Sniffed: TCP:0 UDP:0 ICMP:0 Other:2>
    >>> p.display()
    0000 802.3 08:00:27:c9:d5:7e > 01:00:0c:cc:cc:cc / LLC / SNAP / Raw
    0001 802.3 08:00:27:c9:d5:7e > 01:00:0c:cc:cc:cc / LLC / SNAP / Raw
    
  11. Decode CDP (with inactive dissector)
  12. >>> p[0].display()
    ###[ 802.3 ]###
      dst       = 01:00:0c:cc:cc:cc
      src       = 08:00:27:c9:d5:7e
      len       = 386
    ###[ LLC ]###
         dsap      = 0xaa
         ssap      = 0xaa
         ctrl      = 3
    ###[ SNAP ]###
            OUI       = 0xc
            code      = 0x2000
    ###[ Raw ]###
               load      = '\x02\xb4\x91\x18\x00\x01\x00\x13CSR-A.lab.local\x00\x05\x01\x05Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.1, RELEASE SOFTWARE (fc2)\nTechnical Support: http://www.cisco.com/techsupport\nCopyright (c) 1986-2018 by Cisco Systems, Inc.\nCompiled Tue 17-Jul-18 16:57 by mcpre\x00\x06\x00\x12cisco CSR1000V\x00\x02\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa88e\x00\x03\x00\x14GigabitEthernet2\x00\x04\x00\x08\x00\x00\x00!\x00\x07\x00\t\n\x00\x02\x00\x18\x00\x0b\x00\x05\x01\x00\x16\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa88e'
    
  13. Activate the CDP-Parser
  14. >>> list_contrib()
    avs                 : AVS WLAN Monitor Header                  status=loads
    bgp                 : BGP v0.1                                 status=loads
    carp                : CARP                                     status=loads
    cdp                 : Cisco Discovery Protocol                 status=loads
    chdlc               : Cisco HDLC and SLARP                     status=loads
    coap                : Constrained Application Protocol (CoAP)  status=loads
    diameter            : Diameter                                 status=loads
    dtp                 : DTP                                      status=loads
    eigrp               : EIGRP                                    status=loads
    etherip             : EtherIP                                  status=loads
    gsm_um              : PPI                                      status=loads
    gtp                 : GTP                                      status=loads
    gtp_v2              : GTPv2                                    status=loads
    homeplugav          : HomePlugAV Layer                         status=loads
    http2               : HTTP/2 (RFC 7540, RFC 7541)              status=loads
    icmp_extensions     : ICMP Extensions                          status=loads
    igmp                : IGMP/IGMPv2                              status=loads
    igmpv3              : IGMPv3                                   status=loads
    ikev2               : IKEv2                                    status=loads
    isis                : ISIS                                     status=loads
    ldp                 : Label Distribution Protocol (LDP)        status=loads
    lldp                : LLDP                                     status=loads
    macsec              : -                                        status=?
    modbus              : ModBus Protocol                          status=loads
    mpls                : MPLS                                     status=loads
    mqtt                : -                                        status=?
    nsh                 : NSH Protocol                             status=loads
    openflow            : Openflow v1.0                            status=loads
    openflow3           : Openflow v1.3                            status=loads
    ospf                : OSPF                                     status=loads
    pnio                : ProfinetIO base layer                    status=loads
    pnio_rtc            : ProfinetIO Real-Time Cyclic (RTC)        status=loads
    ppi                 : PPI                                      status=loads
    ppi_cace            : PPI CACE                                 status=loads
    ppi_geotag          : PPI GEOLOCATION                          status=loads
    ripng               : RIPng                                    status=loads
    rsvp                : RSVP                                     status=loads
    sebek               : Sebek                                    status=loads
    send                : Secure Neighbor Discovery (SEND) (ICMPv6) status=loads
    skinny              : Skinny Call Control Protocol (SCCP)      status=loads
    spbm                : SBPM                                     status=loads
    tacacs              : TACACS+ Protocol                         status=loads
    tzsp                : TZSP                                     status=loads
    ubberlogger         : Ubberlogger dissectors                   status=loads
    vqp                 : VLAN Query Protocol                      status=loads
    vtp                 : VLAN Trunking Protocol (VTP)             status=loads
    wpa_eapol           : WPA EAPOL dissector                      status=loads
    
    >>> load_contrib("cdp")
    
  15. Capture again and display CDP-Information as cleartext
  16. >>> load_contrib("cdp")
    
    >>> p=sniff(iface=interface, count=1, filter=capturefilter)
    >>>
    >>>
    >>> p[0].show()
    ###[ 802.3 ]###
      dst       = 01:00:0c:cc:cc:cc
      src       = 08:00:27:c9:d5:7e
      len       = 386
    ###[ LLC ]###
         dsap      = 0xaa
         ssap      = 0xaa
         ctrl      = 3
    ###[ SNAP ]###
            OUI       = 0xc
            code      = 0x2000
    ###[ Cisco Discovery Protocol version 2 ]###
               vers      = 2
               ttl       = 180
               cksum     = 0x9118
               \msg       \
                |###[ Device ID ]###
                |  type      = Device ID
                |  len       = 19
                |  val       = 'CSR-A.lab.local'
                |###[ Software Version ]###
                |  type      = Software Version
                |  len       = 261
                |  val       = 'Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.1, RELEASE SOFTWARE (fc2)\nTechnical Support: http://www.cisco.com/techsupport\nCopyright (c) 1986-2018 by Cisco Systems, Inc.\nCompiled Tue 17-Jul-18 16:57 by mcpre'
                |###[ Platform ]###
                |  type      = Platform
                |  len       = 18
                |  val       = 'cisco CSR1000V'
                |###[ Addresses ]###
                |  type      = Addresses
                |  len       = 17
                |  naddr     = 1
                |  \addr      \
                |   |###[ CDP Address IPv4 ]###
                |   |  ptype     = NLPID
                |   |  plen      = 1
                |   |  proto     = '\xcc'
                |   |  addrlen   = 4
                |   |  addr      = 192.168.56.101
                |###[ Port ID ]###
                |  type      = Port ID
                |  len       = 20
                |  iface     = 'GigabitEthernet2'
                |###[ Capabilities ]###
                |  type      = Capabilities
                |  len       = 8
                |  cap       = Router+IGMPCapable
                |###[ IP Prefix ]###
                |  type      = IP Prefix
                |  len       = 9
                |  defaultgw = 10.0.2.0
                |###[ CDP Generic Message ]###
                |  type      = 0x1800
                |  len       = 2816
                |  val       = '\x05\x01\x00\x16\x00\x11\x00\x00\x00\x01\x01\x01\xcc\x00\x04\xc0\xa88e'
    
  17. Decode CDP-Hostname
  18. >>> device=p[0]["CDPMsgDeviceID"].val.decode()
    >>> device
    'CSR-A.lab.local'
    >>> hostname=device.split(".")[0]
    >>> hostname
    'CSR-A'
    
  19. Decode CDP-Management-IP-Address
  20. >>> ip=p[0]["CDPAddrRecordIPv4"]
    >>> ip.addr
    '192.168.56.101'
    

Windows Command Shell: Store the output of another command in a variable.

How often had i to look up this in the web, always wondering about myself still using the Windows-CMD-shell… Should move on to powershell…

store the output of another command in a variable

  • Example: the „date“-command
  • c:\RH\>date /T
    30.10.2018
    
  • Copy the date into the variable „TTT“
  • c:\RH\>for /f %D in ('date /T 2^> nul') do @(set TTT=%D)
    
    c:\RH\>echo %TTT%
    30.10.2018
    

Not elegant, not intuitive, not self-explanatory: This Shell Scripting language seems not to be made for daily usage 😉

Scripted provisioning of a Cisco CSR1000V as VirtualBox-Guest: Slipstream initial startup-config

To enable immediate remote-access to brandnew deployed virtual Cisco CSR1000V-Routers by scripts, ansible etc. it’s necessary to predeploy the startup-config at least with public-rsa-keys etc.

  1. Prepare
  2. Download and install the nice tool http://CDBurnerXP. This will install a companion cli-tool cdbxpcmd.exe along with the GUI-Version.

    It’ll be used to prepare an ISO-„CDROM“ containing the prepared „startup-config“-file.

  3. Prepare
  4. Prepare an IOS-XE Configuration file…

    !
    hostname HOSTNAME
    !
    ...
    !
    !
    platform console serial
    !
    crypto key generate rsa modulus 2048 label RSA_SSH
    !
    !
    username labuser privilege 15
    !
    ip ssh pubkey-chain
    !
    username labuser
      key-string    
      ...
    exit
    exit
    !
    exit
    !
    ...
    !
    ip ssh rsa keypair-name RSA_SSH
    !
    ip ssh server algorithm authentication publickey
    !
    ...
    !
    line vty 0 15
      login local
      transport input ssh
      access-class ACL_NMM in vrf-also
    !
    end
    
  5. Modify the existing script
  6. These commands create the ISO-File in the %LOC%-Path.

    set VM=CSR1000vX
    
    ...
    
    : Create ISO containing slipstreamed config-file
    : use UNXTOOLS "sed" to customize the "hostname"-Command
    :
    set CFGISO=%LOC%\config.iso
    set CFGSRC=C:\RH\LAB\VM\iosxe_config.txt
    set CFGTXT=%LOC%\iosxe_config.txt
    cat %CFGSRC% | sed "s/HOSTNAME/%VM%/g" > %CFGTXT%
    "%ProgramFiles(x86)%"\CDBurnerXP\cdbxpcmd.exe --burn-data -file:%CFGTXT% -iso:%CFGISO% -format:iso
    
  7. Mount this ISO to the VM
  8. The contained config-file will get automatically applied when the router boots the first time.

    VBoxManage storageattach %VM% --storagectl IDE-CTL --port 1 --device 1 --type dvddrive --medium %CFGISO%
    
  9. Run the VM
    • the config-file gets applied
  10. access the router
  11. This depends of the configuration-file, of course.