Connecting over SSH and running a command over NETCONF

When HW/virtualized/containerized network element offers NETCONF interface to manage, it is extremely beneficial to use it for repetitive tasks (upgrades, sanity checking, route table checking etc).

NETCONF can be used over different transports as below:

netconf-ssh     830/tcp    NETCONF-over-SSH             # [RFC4742]
netconf-beep    831/tcp    NETCONF-over-BEEP            # [RFC4744]
netconfsoaphttp 832/tcp    NETCONF-for-SOAP-over-HTTPS  # [RFC4743]
netconfsoapbeep 833/tcp    NETCONF-for-SOAP-over-BEEP   # [RFC4743]
netconf-tls     6513/tcp   NETCONF-over-TLS             # [RFC5539]

In case SSH is used then it must be made sure that SSH subsystem is enabled in SSH config on a device.

If a YANG model is available then TailF offers a client and Java class generator under the link.

Alternatively, as in the example below, Python can be used to manually connect over SSH and based on YANG model (if used) instruct the device to perform a specific task.

There is a library in Python for that purpose called ncclient NETCONF client

Firstly we need to do the proper import in our client script after installing the library:

from ncclient import manager
from ncclient.xml_ import *
from ncclient.operations.rpc import RPC

Let’s define method used for connecting to the device:

def connect_to_the_device(host, port, user, password):
   return manager.connect(host=host,
   port=port,
   username=user,
   password=password,
   hostkey_verify=False)

Create an object class that inherits from RPC class of ncclient library and define a method that will be compose an XML NETCONF message based on YANG model:

class Action(RPC):
   def perform_yang_action1(self, arg1, arg2, arg3):
         action1 = etree.Element("action", {"xmlns": "http://tail-f.com/ns/netconf/actions/1.0" }
         data = etree.SubElement(action, 'data')
         perform_action1 = etree.SubElement(data, 'action1', {"xmlns": "http://stackblog.local/ns/yang/action1"})
         etree.SubElement(perform_action1, 'arg1').text = arg1
         etree.SubElement(perform_action1, 'arg2').text = arg2
         etree.SubElement(perform_action1, 'arg3').text = arg3
   return self._request(action1)

Connect to the device and perform requested action (probably not the safest way to use clear text password to connect):

connectionManager = connect_to_the_device("1.2.3.4", "22", "netconf", "netconf")
 
#create action and invoke it
action1 = Action(connectionManager._session, connectionManager._device_handler, timeout=2)
action1.perform_yang_action1(arg1 = "arg1", arg2 = "arg2", arg3 = "arg3")
time.sleep(1)
 
#Print the result of the invoked action
if action1.reply != None:
   action1.print_reply()
 
#close the session to the device
connectionManager.close_session()

The request in NETCONF formatted XML would look as follows:

<nca:action xmlns:nca="http://tail-f.com/ns/netconf/actions/1.0">
   <nca:data>
      <action1 xmlns:"http://stackblog.local/ns/yang/actions/action1">
         <arg1>arg1</arg1>
         <arg2>arg2</arg2>
         <arg3>arg3</arg3>
      </action1>
    </nca:data>
</nca:action>
</nc:rpc>

And a corresponding YANG model:

module actions {
  yang-version "1";
  namespace: "http://stackblog.local/ns/yang/actions";
  prefix "actions";
 
  import tailf {
     prefix "yang/common";
  }
 
  rpc action1 {
    input {
       leaf arg1 {
          type string;
       }
       leaf arg2 {
          type string;
       }
       leaf arg3 {
          type string;
       }
    }
    output {
       leaf status {
          type string;
       }
    }
  }
}

Lastly in XML:

<?xml version="1.0" encoding="UTF-8"?>
<module name="actions"
        xmlns="urn:ietf:params:xml:ns:yang:yin:1"
        xmlns:actions="http://stackblog.local/ns/yang/actions"
        xmlns:tailf="http://tail-f.com/yang/common">
 
<namespace uri="http://stackblog.local/ns/yang/actions"/>
   <tailf:action name="action1">
      <input>
         <leaf name="arg1">
            <type name="string"/>
         </leaf>
         <leaf name="arg2">
            <type name="string"/>
         </leaf>
         <leaf name="arg3">
            <type name="string"/>
         </leaf>
      </input>
      <output>
	 <leaf name="status">
           <type name="string">
	 </leaf>
      </output>
   </tailf:action>
</module>