The anatomy of NETCONF

In this article we will examine the basic characteristics of the NETCONF protocol as defined by the original RFC 6241. This protocol works as a standardized API for interacting directly with network devices such as switches and routers. NETCONF is a key element and appear multiple times on the Cisco Certified DevNet Expert lab blueprint.

Ready-to-use enterprise grade software like Cisco DNA Center or Cisco NSO does device interaction using NETCONF. In these cases all the inner workings are hidden from the user.

We can also build our own automation tools using client packages like ncclient or scrapli_netconf for Python to interact with NETCONF. This also hides some of complexity by providing an API to work through.

To understand the NETCONF protocol more in detail, I would like to show how it works under the hood and how we can interact with it directly from the command line, using nothing but a standard SSH client.

The NETCONF protocol operates on four distinct layers. In the most common cases, all four layers are present in every session.

Let's go through the necessary steps and layers to interact with an IOS-XE device through the NETCONF API. In the first example we will configure the device hostname of an IOS-XE device.

Before we can send content to the device, we need to find a supported data model to use. One method is simply to open an SSH connection on port 830 to the device. As soon as the connection is established, the device will send an <hello>-XML document with a list of all the capabilities. Most of these are YANG different modules that corespond to individual device features such as Interfaces, NAT, BGP etc.

(main) expert@expert-cws:~$ ssh -p 830 192.168.255.51

expert@192.168.255.51's password:

<?xml version="1.0" encoding="UTF-8"?>

<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">

  <capabilities>

    <capability>urn:ietf:params:netconf:base:1.0</capability>

    <capability>urn:ietf:params:netconf:base:1.1</capability>

    ...

    <capability>http://cisco.com/ns/yang/Cisco-IOS-XE-nat-oper?module=Cisco-IOS-XE-nat-oper&amp;revision=2019-05-01</capability>

    <capability>http://cisco.com/ns/yang/Cisco-IOS-XE-native?module=Cisco-IOS-XE-native&amp;revision=2021-03-01</capability>

    <capability>http://cisco.com/ns/yang/Cisco-IOS-XE-nbar?module=Cisco-IOS-XE-nbar&amp;revision=2020-09-28</capability>

    ...

  </capabilities>

  <session-id>99</session-id>

</hello>

]]>]]>
^CConnection to 192.168.255.51 closed.

(main) expert@expert-cws:~$

One of the listed capabilities is the Cisco-IOS-XE-native module. As we will be covering the YANG language and data modelling in another article, for now let's just consider the fact that the Cisco-IOS-XE-native module treats nested XML elements very similar to consecutive parameters on the command line. To most network engineer with a CLI background, it will probably be the most intuitive data model to use.

When we create the content of the payload we must indicate a namespace along with the root element. The namespace is the URL (or URI) listed as the capability, and we indicate it on the root element with the attribute: xmlns

To send the a hostname configuration to the router, the content (Layer 4) will then looks like this:

<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">

  <hostname>DevNetRouter</hostname>

</native>

The next layer (3) specifies the operation. Because we want to add or change configuration on the router, we must encapsulate the content from above in a <edit-config> element which also requires a <config> element to hold the actual content.

Content (layer 4) and operation (layer 3) combined looks like this:

<edit-config>
  <config>
    <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">

      <hostname>DevNetRouter</hostname>

    </native>
  </config>
</edit-config>

The next thing we need to do is wrap the entire operation as a layer 2 message. The message also includes a namespace that indicates wether we use NETCONF version 1.0 or 1.1. To keep things simple, we will use version 1.0. This version is the very first capability announces by the device when we connected, so let's just grab the URI from there: urn:ietf:params:netconf:base:1.0

The message to initiate a new <edit-config> operation consists of a single <rpc> element. RPC stands for Remote Procedure Call and is how we actually invoke the API and make it interact with the delivered message(s). Each RPC has a unique ID, which enables the NETCONF server (the device) to send a response back using the same ID. This makes it possible to send multiple RPCs and get individual responses for them that we can identify.

It is up to us as the NETCONF client to create an ID. Any sequence number or GUID would be suitable, so in this example we use 20220006.

Content (layer 4), operation (layer 3) and message (layer 2) combined looks like this:

<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="20220006">

 <edit-config>

   <target>

     <running/>

   </target>

   <config>

     <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">

       <hostname>DevNetRouter</hostname>

     </native>

   </config>

 </edit-config>

</rpc>

There is one final thing we need to do before we can ship the payload to the device. Remember how the device told us its capabilities as soon as we opened the SSH session on port 830? It expect us to do the same. Actually, it is required!

The router send us a large amount of capailities. Because we only intend to send a single message using the namespace urn:ietf:params:xml:ns:netconf:base:1.0 we just need to announce this capability before sending the payload.

<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">

  <capabilities>

    <capability>urn:ietf:params:netconf:base:1.0</capability>

  </capabilities>

</hello>

The separator that indicated the end of any message on NETCONF is the symbol sequence ]]>]]>
You can see that the IOS-XE device used that sequence at the end of its <hello> message. We should do the same for our <hello> and <rpc> message.

Now we are ready to combine the <hello> element, the separator sequence and the message (layer 2) including the operation (layer 3) and content (layer 4). All of the following content can be sent directly on the SSH connection on TCP port 830.

<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">

  <capabilities>

    <capability>urn:ietf:params:netconf:base:1.0</capability>

  </capabilities>

</hello>

]]>]]>

<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="20220006">

 <edit-config>

   <target>

     <running/>

   </target>

   <config>

     <native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">

       <hostname>DevNetRouter</hostname>

     </native>

   </config>

 </edit-config>

</rpc>

]]>]]>

If everything was done correctly, we should receive a <rpc-reply> message back containing the same message ID as an attribute:

<?xml version="1.0" encoding="UTF-8"?>

<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="20220006">

 <ok/>

</rpc-reply>]]>]]>

^CConnection to 192.168.255.51 closed.

(main) expert@expert-cws:~$

That's it! We just configured the hostname of a router through NETCONF using nothing but an open SSH connection and raw XML documents.

I wouldn't recommend this manual approach for you daily configuration changes though... But it sure is interesting to see the basics of the protocol, to get a better understanding of what is happening when we use ncclient, DNA Center or NSO with NETCONF based drivers.

Written by:
Andreas Baekdahl
Cisco Certified DevNet Expert #20220006
3xCCIE and CCDE