Connection Through Proxies

There are several ways to connect to a device via a ‘proxied’ connection, i.e. connecting to a device through another system. Unicon supports CLI proxy and SSH tunnel features. CLI proxy allows a device to connect via another (Unicon supported) device, SSH tunnel uses the SSH client to create TCP tunnels to connect to another device via a SSH connection.

CLI Proxy

The CLI proxy works by connecting via one or more proxy devices and executing a command to start the connection to the next device. The command can be specified explicitly as part of the proxy definition or it can be determined based on the connection details (i.e. protocol, ip and port or command).

Multiple intermediate devices are supported, you can specify as many proxy hosts and commands as needed to connect to the target device. Proxy devices must be defined as a device in the topology file including the relevant connection details and credentials. Device connection details are used for the first proxy device only, connection details of intermediate devices are ignored, you need to explicitly specify a command to connect to an intermediate device.

When the CLI proxy feature is when used as part of pyATS the proxy needs to be specified in the topology YAML file.

Note

If the proxy device has more than one connection defined, you must specify the ‘via’ settings under the connection defaults of the proxy device.

CLI proxy with pyATS topology integration

Example topology file with a proxy configuration for the cli connection. Please note that credentials have been left out of this example.

devices:
  jumphost:
    os: linux
    type: linux
    connections:
      cli:
        protocol: ssh
        ip: 127.0.0.1
        port: 2222
  Router:
    os: ios
    type: router
    connections:
      defaults:
        class: unicon.Unicon
      cli:
        protocol: telnet
        ip: 127.0.0.1
        port: 64001
        proxy: jumphost

Connection log (abbreviated) for above example:

%UNICON-INFO: ssh 127.0.0.1 -p 2222
Last login: Wed Jan 24 08:02:24 2018 from 10.0.2.2
admin@host:~$
%UNICON-INFO: +++ initializing handle +++
stty cols 200
admin@host:~$ stty rows 200
admin@host:~$
%UNICON-INFO: +++ connection to spawn_command: ssh 127.0.0.1 -p 2222, id: 4394786888 +++
telnet 127.0.0.1 64001
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

Router#
%UNICON-INFO: +++ initializing handle +++

In the above log, you can see the command telnet 127.0.0.1 64001 is executed to connect to the target device. This command is derived automatically from the connection details of the target device.

Note

There is no support for hierarchical proxy configurations. If you need to pass multiple devices to get to the target device, you need to specify a list of proxy devices for that device. If a proxy device has a proxy specified for its connection, it is ignored.

CLI Proxy topology schema

devices:

  <name>:
    connections:

      # proxy device only, command is derived from connection details
      <name>:
        proxy: <name> # proxy device name

      # proxy with specific command
      <name>:
        proxy:
          device: <name> # proxy device name
          command: <cmd> # command to connect to target device

      # proxy with lists of commands
      <name>:
        proxy:
          - device: <name> # proxy device name
            command: [ <cmd>, <cmd> ] # list of commands,
                                      # the last command connects
                                      # to the next proxy device
          - device: <name> # list of commands using different syntax
            command:
              - <cmd>
              - <cmd>

      # multiple proxy devices, last device without specific command
      # derives the command from the connection details
      <name>:
        proxy:
          - device: <name> # proxy device name
            command:
              - <cmd> # command to connect to next proxy device
          - device: <name>

      # multiple proxy devices with a list of commands for one of the hosts
      <name>:
        proxy:
          - device: <name> # proxy device name
            command:
              - <cmd>
              - <cmd>
          - device: <name> # proxy device name
            command: <cmd>

CLI proxy with Unicon standalone Connections

The CLI Proxy feature can also be used when using Unicon in standalone mode. Proxy connections can be specified via the proxy_connections argument of the Connection class.

The proxy_connections argument expects a list of Connection objects with the start parameter containing the command to be executed to connect to the next device. If multiple commands should be executed, a list of lists should be passed, e.g. start=[['cmd1','cmd2','cmd3']]

Below example shows a single proxy connection used to reach the IOS router R01.

proxy_conn = Connection(hostname='lnx2',
               start=['ssh -p 2222 localhost'],
               os='linux',
               credentials={'default': {'username': 'admin', 'password': 'cisco'}})

c = Connection(hostname='R01',
               start=['telnet 10.3.3.1'],
               os='ios',
               credentials={'default': {'username': 'admin', 'password': 'cisco'}},
               proxy_connections=[proxy_conn])
c.connect()

CLI Proxy examples

Connecting to ConfD/NSO CLI via a linux server

devices:
  lnx:
    os: linux
    type: linux
    credentials:
        default:
            username: cisco
            password: cisco
    connections:
      defaults:
        class: unicon.Unicon
      cli:
        protocol: ssh
        ip: 127.0.0.1
        port: 2222
  nso:
    os: confd
    type: nso
    credentials:
        default:
            username: admin
            password: admin
    connections:
      defaults:
        class: unicon.Unicon
      cli:
        command: ncs_cli -u admin -C
        proxy: lnx
from pyats.topology import loader
tb = loader.load('nso.yaml')

# Connect to target device, proxy connection is done automatically
n = tb.devices.nso
n.connect(via='cli')

Connecting to a VNF console via Cloud Services Platform (CSP)

# Example with IOS VNF on CSP

devices:
    Router:
        type: router
        os: ios
        credentials:
            default:
                username: cisco
                password: cisco
        connections:
            defaults:
                class: unicon.Unicon
            cli:
                command: telnet 7005
                proxy: csp
    csp:
        type: nfvi
        os: confd
        platform: csp
        credentials:
            default:
                username: admin
                password: admin
        connections:
            defaults:
                class: unicon.Unicon
            cli:
                protocol: ssh
                ip: 172.27.132.75
from pyats.topology import loader
tb = loader.load('csp.yaml')

# Connect to target device, proxy connection is done automatically
r = tb.devices.Router
r.connect(via='cli')

Connecting via multiple proxy devices

Topology file with target device Sw03 and three intermediate devices, lnx, R01 and R02.

testbed:
    credentials:
        default:
            username: cisco
            password: cisco
devices:
    lnx:
      type: linux
      os: linux
      connections:
        defaults:
          class: unicon.Unicon
        cli:
          protocol: ssh
          ip: 127.0.0.1
          port: 2222

    R01:
      os: ios
      type: router
      connections:
        defaults:
          class: unicon.Unicon
        cli:
          protocol: telnet
          ip: 127.0.0.1
          port: 64001

    R02:
      os: ios
      type: router
      connections:
        defaults:
          class: unicon.Unicon
        cli:
          protocol: telnet
          ip: 127.0.0.1
          port: 64002

    Sw03:
      os: ios
      type: switch
      connections:
        defaults:
          class: unicon.Unicon
        cli:
          protocol: telnet
          ip: 10.2.3.3
          proxy:
            - device: lnx
              command: telnet 10.3.3.1  # Command specifies how to connect to R01
            - device: R01
              command: telnet 2.2.2.2   # Command specifies how to connect to R02
            - device: R02  # no command, use the connection details of Sw03

Example script and abbreviated connection log.

>>>
>>> from pyats.topology import loader
>>> tb = loader.load('cliproxy.yaml')
>>> sw = tb.devices['Sw03']
>>> sw.connect())

2018-02-13T12:20:53: %UNICON-INFO: +++ initializing context +++

...

2018-02-13T12:20:53: %UNICON-INFO: connection via proxy lnx

2018-02-13T12:20:53: %UNICON-INFO: connection to lnx

Linux$
2018-02-13T12:20:53: %UNICON-INFO: +++ initializing handle +++

2018-02-13T12:20:53: %UNICON-INFO: connection via proxy R01

2018-02-13T12:20:53: %UNICON-INFO: connection to R01
telnet 10.3.3.1
Trying 10.3.3.1...
Connected to 10.3.3.1.
Escape character is '^]'.


User Access Verification

Password:
R01>
2018-02-13T12:20:53: %UNICON-INFO: +++ initializing handle +++
enable
Password:
R01#
2018-02-13T12:20:53: %UNICON-INFO: connection via proxy R02

2018-02-13T12:20:53: %UNICON-INFO: connection to R02
telnet 2.2.2.2
Trying 2.2.2.2...
Connected to 2.2.2.2.
Escape character is '^]'.


User Access Verification

Password:
R02>
2018-02-13T12:20:53: %UNICON-INFO: +++ initializing handle +++
enable
Password:
R02#
2018-02-13T12:20:53: %UNICON-INFO: connection to Sw03
telnet 10.2.3.3
Trying 10.2.3.3 ... Open

User Access Verification

Password:
Sw03>

CLI proxy with standalone Unicon Connections

Below example code and abbreviated execution log shows how to instantiate the Connection objects to create a proxied connection.

>>> from unicon import Connection
>>>
>>> proxy_conn = Connection(hostname='lnx2',
...                start=['ssh lnx2'],
...                os='linux',
...                credentials={'default': {'username': 'admin', 'password': 'cisco'}})

>>>
>>> c = Connection(hostname='R01',
...                start=['telnet 10.3.3.1'],
...                os='ios',
...                credentials={'default': {'username': 'admin', 'password': 'cisco'}})
...                proxy_connections=[proxy_conn])

>>> c.connect()

2018-02-13T12:56:30: %UNICON-INFO: connection via proxy lnx2

2018-02-13T12:56:30: %UNICON-INFO: connection to lnx2

Linux$
2018-02-13T12:56:31: %UNICON-INFO: +++ initializing handle +++

2018-02-13T12:56:31: %UNICON-INFO: connection to R01
telnet 10.3.3.1
Trying 10.3.3.1...
Connected to 10.3.3.1.
Escape character is '^]'.


User Access Verification

Password:
R01>
2018-02-13T12:56:31: %UNICON-INFO: +++ initializing handle +++

CLI proxy with Dual RP device

Below example code shows how to use CLI proxy for dual rp device.

# Example with IOSXE Ha device - testbed.yaml

devices:
  Router:
    alias: uut
    os: iosxe
    credentials:
      default:
        password: cisco
        username: cisco
      enable:
        password: cisco
    connections:
      defaults:
        class: unicon.Unicon
      a:
        protocol: telnet
        ip: 1.1.1.1
        port: 2001
        proxy: jump_host
      b:
        protocol: telnet
        ip: 172.27.114.25
        port: 2002
        proxy: jump_host

  jump_host:
    alias: jh
    connections:
      cli:
        ip: 2.2.2.2
        port: 22
        protocol: ssh
    credentials:
      default:
        password: pyats
        username: virl
    os: linux
    type: linux
>>> # pyats shell --testbed-file testbed.yaml
>>> from genie.testbed import load
>>> testbed = load('testbed.yaml')
-------------------------------------------------------------------------------
>>> d = testbed.devices['uut']
>>> d.connect()

2020-08-14 14:08:15,959: %UNICON-INFO: +++ Router logfile /tmp/Router-cli-20200814T140815956.log +++

2020-08-14 14:08:15,960: %UNICON-INFO: +++ Unicon plugin iosxe +++

2020-08-14 14:08:15,995: %UNICON-INFO: +++ Router logfile /tmp/Router-cli-20200814T140815956.log +++

2020-08-14 14:08:15,996: %UNICON-INFO: +++ Unicon plugin iosxe +++

2020-08-14 14:08:16,033: %UNICON-INFO: +++ Router logfile /tmp/Router-cli-20200814T140815956.log +++

2020-08-14 14:08:16,036: %UNICON-INFO: +++ Unicon plugin iosxe +++

2020-08-14 14:08:16,039: %UNICON-INFO: connection via proxy jump_host

2020-08-14 14:08:16,053: %UNICON-INFO: +++ connection to spawn: ssh -l virl 2.2.2.2 -p 22, id: 139774725172192 +++

2020-08-14 14:08:16,054: %UNICON-INFO: connection to jump_host
virl@2.2.2.2's password:
Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-139-generic x86_64)

Last login: Fri Aug 14 18:06:18 2020 from 10.0.10.1
virl@cisco.com:~$

2020-08-14 14:08:19,351: %UNICON-INFO: +++ initializing handle +++


2020-08-14 14:08:19,351: %UNICON-INFO: connection via proxy jump_host

2020-08-14 14:08:19,362: %UNICON-INFO: +++ connection to spawn: ssh -l virl 2.2.2.2 -p 22, id: 139774725151152 +++

2020-08-14 14:08:19,363: %UNICON-INFO: connection to jump_host
virl@2.2.2.2's password:
Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-139-generic x86_64)

Last login: Fri Aug 14 18:08:19 2020 from 10.0.10.1
virl@cisco.com:~$

2020-08-14 14:08:22,638: %UNICON-INFO: +++ initializing handle +++

2020-08-14 14:08:22,640: %UNICON-INFO: +++ connection to spawn: ssh -l virl 2.2.2.2 -p 22, id: 139774725172192 +++

2020-08-14 14:08:22,641: %UNICON-INFO: +++ connection to spawn: ssh -l virl 2.2.2.2 -p 22, id: 139774725151152 +++
telnet 1.1.1.1 2001
Trying 1.1.1.1...
Connected to 1.1.1.1.
Escape character is '^]'.

Router-stby#
Router-stby#

telnet 1.1.1.1 2002
Trying 1.1.1.1...
Connected to 1.1.1.1.
Escape character is '^]'.

Router#
Router#
Router-stby#
>>>

SSH Tunnel

The SSH tunnel feature uses the escape sequence feature of the ssh command line client to create TCP tunnel connections via a (linux) server. This server acts as a ‘jumphost’ or proxy device to connect to devices that are reachable only through this server and not directly.

Connections via the SSH tunnel feature make a TCP connection to the device via the SSH connection.

The current implementation supports connections from the SSH client host (i.e. where the pyATS script runs) to devices behind the (linux) server in the lab.

You can find more information on the escape sequence of the OpenSSH client here: SSH escape characters.

To configure a connection to use the SSH tunnel feature, configure sshtunnel key under the connection and add the host key with the device name or server name as the value.

The SSH tunnel host can be a testbed server or can be another device from the testbed.

SSH tunnel with pyATS topology integration

Example topology file with a sshtunnel configuration for the a connection of device R2.

testbed:
  servers:
    js:
      address: 127.0.0.1
      credentials:
          ssh:
              username: cisco
              password: cisco
      custom:
        port: 2222
        ssh_options: -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null

devices:
    R2:
      os: ios
      type: router
      credentials:
        default:
          username: cisco
          password: cisco
      connections:
        defaults:
          class: unicon.Unicon
        a:
          protocol: ssh
          ip: 10.0.0.1
          port: 22
          sshtunnel:
              host: js

Example script and abbreviated connection log.

>>>
>>> from pyats.topology import loader
>>> tb = loader.load('sshtunnel.yaml')
>>> r2 = tb.devices['R2']
>>> r2.connect())
2018-03-29T18:19:26: %UNICON-INFO: Connecting proxy host js

2018-03-29T18:19:26: %UNICON-INFO: connection to js

2018-03-29T18:19:26: %UNICON-INFO: +++ connection to spawn_command: ssh -l cisco -p 2222 127.0.0.1 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null, id: 4440916152 +++

2018-03-29T18:19:26: %UNICON-INFO: ssh -l cisco -p 2222 127.0.0.1 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
Warning: Permanently added '[127.0.0.1]:2222' (RSA) to the list of known hosts.
Password:

Linux$
2018-03-29T18:19:26: %UNICON-INFO: +++ initializing handle +++
stty cols 200
Linux$ stty rows 200
Linux$
2018-03-29T18:19:26: %UNICON-INFO: Attaching  all Subcommands
2018-03-29T18:19:26: %UNICON-INFO: Adding tunnel 127.0.0.1:20001 for 10.0.0.1:22
2018-03-29T18:19:26: %UNICON-INFO: Device 'R2' connection 'a' via new SSH tunnel 127.0.0.1:20001

2018-03-29T18:19:26: %UNICON-INFO: connection to R2

2018-03-29T18:19:26: %UNICON-INFO: +++ connection to spawn_command: ssh -l cisco 127.0.0.1 -p 20001 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null, id: 4442821240 +++

2018-03-29T18:19:26: %UNICON-INFO: ssh -l cisco 127.0.0.1 -p 20001 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null
Warning: Permanently added '[127.0.0.1]:20001' (RSA) to the list of known hosts.
Password:
Username: cisco
Password: cisco
R2>
2018-03-29T18:19:26: %UNICON-INFO: +++ initializing handle +++
enable
Password: cisco
R2#
2018-03-29T18:19:26: %UNICON-INFO: +++ execute  +++
term length 0
R2#
2018-03-29T18:19:26: %UNICON-INFO: +++ execute  +++
term width 0
R2#

SSH tunnel with IPv6 target device

Below example topology file shows a router device that is reachable via IPv6 via the IPv4 jump host.

Unicon will create a SSH connection to the jump host and create the IPv4 tunnel that connects to the IPv6 target device from the jump host.

devices:
    js:
      os: linux
      type: server
      credentials:
        default:
          username: cisco
          password: cisco
      connections:
        defaults:
          class: unicon.Unicon
        ssh:
          protocol: ssh
          ip: 10.0.0.1
          port: 22
          ssh_options: -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null

    R1:
      os: ios
      type: router
      credentials:
        default:
          username: cisco
          password: cisco
      connections:
        defaults:
          class: unicon.Unicon
        vty:
          protocol: ssh
          ip: 2001:abcd::1
          sshtunnel:
              host: js

SSH Tunnel topology schema

devices:

  <name>:
    connections:

      <name>:
        sshtunnel:
          # tunnel device name is required
          host: <device name>
          # optional settings
          tunnel_ip: <ip> # default: 127.0.0.1
          tunnel_port: <port> # default: automatic from port 20000 and up

SSH Tunnel with standalone Unicon Connections

Below example code shows how to instantiate the Connection objects to create a tunneled connection.

from unicon import Connection

proxy = Connection(hostname='linux',
               start=['ssh jumphost'],
               os='linux',
               credentials={'default': {'username': 'cisco', 'password': 'cisco'}})
proxy.connect()

from unicon.sshutil import sshtunnel

tunnel_port = sshtunnel.add_tunnel(
            proxy_conn=proxy,
            target_address='1.1.1.1',
            target_port=23
            )

c = Connection(hostname='R1',
               start=['telnet 127.0.0.1 {}'.format(tunnel_port)],
               os='ios',
               credentials={'default': {'username': 'cisco', 'password': 'cisco'}})
c.connect()

Limitations

  • UDP tunnels are currently not supported.

Section author: Dave Wapstra <dwapstra@cisco.com>