Cisco has recently introduced NETCONF/YANG support across the enterprise network portfolio. This capability is available in the 16.3 XE code for routers and switches. NETCONF/YANG allows programmatic access to network devices using structured data.
I have had a number of questions from customers looking to get started with NETCONF/YANG, which I will address here.
To keep it practical, we will end up using NETCONF/YANG to programmatically change the vlan membership for a switch interface.
Many of you will know that Cisco has had IOS support for NETCONF since 2004. The 2004 version did not really have a data model and was just using NETCONF for transport. You still needed to send/receive CLI chunks as text, which was similar to just SSH-ing into the device and running commands directly. (NOTE: technically there were a few small differences, but I am not going into them here J)
The 2016 version of NETCONF uses YANG to structure the data that is sent and received, which makes is much simpler to work with programatically. In the past, you would need to "screen scrape" CLI with regular expressions to extract specific pieces of information.
To enable the NETCONF, a single command is required. These examples are using a 3850 switch running 16.3.2 code.
3850-remote#conf t
Enter configuration commands, one per line. End with CNTL/Z.
3850-remote(config)#netconf-yang
NETCONF uses SSH as a transport, so one of the simplest ways to see how it works is to SSH to the device. I can simply use the ssh command and connect to port 830 (the default NETCONF port).
$ ssh -p 830 sdn@10.10.6.2
sdn@10.10.6.2's password:
Enter a password and there will be a large slab of XML in response. This has been <SNIPPED/> for brevity
<?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>urn:ietf:params:netconf:capability:writable-running:1.0</capability>
<capability>urn:ietf:params:netconf:capability:xpath:1.0</capability>
<capability>urn:ietf:params:netconf:capability:validate:1.0</capability>
<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>
<capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>
<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>
<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>
<capability>http://tail-f.com/ns/netconf/actions/1.0</capability>
<capability>http://tail-f.com/ns/netconf/extensions</capability>
<capability>urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=report-all</capability>
<capability>urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults?revision=2011-06-01&module=ietf-netconf-with-defaults</capability>
<capability>http://cisco.com/ns/yang/ned/ios?module=ned&revision=2016-09-19&deviations=ned-switching-devs</capability>
16</capability>
<SNIPPED/>
<capability>urn:ietf:params:xml:ns:yang:smiv2:VPN-TC-STD-MIB?module=VPN-TC-STD-MIB&revision=2005-11-15</capability>
</capabilities>
<session-id>19150</session-id></hello>]]>]]>
The key point here is that this is a <hello> message from the NETCONF device, containing a list of <capabilities>. The capabilities contain all of the YANG models that the device supports.
NOTE: the delimiter string ]]>]]> at the end of the response signifies the end of the message. You would need to use this string to indicate the end of any message you send back.
To continue the session, you would need to respond to the hello with a list of capabilities that you support. The simplest response is below. Remember the delimiter string (]]>]]>).
<?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>
</capabilities>
</hello>]]>]]>
You will not see a response, which is expected. You can now send NETCONF commands. The following example will return the running configuration.
<?xml version="1.0"?>
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"
message-id="101">
<get-config>
<source>
<running/>
</source>
</get-config>
</rpc>]]>]]>
You will get an XML response that contains the full configuration.
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101"><data><native xmlns="http://cisco.com/ns/yang/ned/ios"><device-model-version><major>2</major><minor>1</minor><bug-fix>0</bug-fix></device-model-version><version>16.3</version><boot><system><switch><all><flash>packages.conf</flash></all></switch></system></boot><service><password-recovery>false</password-recovery><timestamps><debug><datetime><msec/></datetime></debug><log><datetime><msec/></datetime></log></timestamps><compress-config/></service><hostname>3850-remote</hostname>
<SNIPPED/>
You can pass this through an XML formatter to get a structured view:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
<data>
<native xmlns="http://cisco.com/ns/yang/ned/ios">
<device-model-version>
<major>2
</major>
<minor>1
</minor>
<bug-fix>0
</bug-fix>
</device-model-version>
<version>16.3
</version>
<boot>
<system>
<switch>
<all>
<flash>packages.conf
</flash>
</all>
</switch>
</system>
</boot>
<service>
<password-recovery>false
</password-recovery>
<timestamps>
<debug>
<datetime>
<msec/>
</datetime>
</debug>
<log>
<datetime>
<msec/>
</datetime>
</log>
</timestamps>
<compress-config/>
</service>
<hostname>3850-remote
</hostname>
<SNIPPED/>
There are a number of tools that simplify interacting with NETCONF. One of my favourites (as it still exposes the underlying semantics of the protocol, which I think is important when learning) is netconf-console. You can download it from https://github.com/OpenNetworkingFoundation/configuration/tree/master/netconf-console
The file was renamed to netconf-console.py. In the examples below, an explicit host, username, password and the netconf port were specified. You can edit the netconf-console.py file to change the default username, password and port to make testing easier.
The operation that is being run here is just "hello", so will return a list of capabilities from the device. This will be identical to the first step in the earlier SSH example.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830
--hello
Similar to the earlier example, the get-config command can be used, and this time the XML response will be pretty-printed.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830 --get-config
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<native xmlns="http://cisco.com/ns/yang/ned/ios">
<device-model-version>
<major>2</major>
<minor>1</minor>
<bug-fix>0</bug-fix>
</device-model-version>
<version>16.3</version>
<boot>
<system>
<switch>
<all>
<flash>packages.conf</flash>
</all>
</switch>
</system>
</boot>
<SNIPPED/>
The earlier NETCONF responses were XML. This is known as structured data. The reason this is important is it is very easy for software to parse and understand. XML tags are used to mark-up or describe the data they contain. For example <major>2</major> denotes an attribute "major" with a value of 2. NOTE <tag> is the start and </tag> is the end.
XML is also hierarchical, <major>2</major> is contained in the <device-model-version> tag. We know that the device-model-version/major is 2. Other attributes of device-model-version include minor and bug-fix.
Software engineers like hierarchical trees. These are easy to search, sort and scale. XML tags define a tree-like structure, with a root, branches and leaves. In the example above <major>2</major> is a leaf as it only contains data, not other XML constructs. YANG can be mapped easily into XML. We will cover YANG in a future blog.
As seen in the earlier example, a full device configuration may be quite large. In my case it was nearly 3000 lines. Some of this is due to the overhead of XML, but it is still a lot of data. Here is where trees/XML can help due to filtering.
A filter can be used to specify a subset of the configuration. For example, if you are only interested in the configuration of the interfaces on the device. This is achieved with the -x "interfaces" option. "-x" means XPATH for those familiar with XML.
This time only the interface specific configuration is returned (386 lines, vs 3000 lines for full configuration for my example).
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830 --get-config -x "interfaces"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>GigabitEthernet1/0/1</name>
<description>uplink to router</description>
<type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type>
<enabled>true</enabled>
<diffserv-target-entry xmlns="urn:ietf:params:xml:ns:yang:ietf-diffserv-target">
<direction>outbound</direction>
<policy-name>EasyQos-Egress</policy-name>
</diffserv-target-entry>
<ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<address>
<ip>10.10.6.2</ip>
<netmask>255.255.255.0</netmask>
</address>
</ipv4>
<ipv6 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"/>
</interface>
<interface>
<name>GigabitEthernet1/0/10</name>
<description>802.1x host</description>
<SNIPPED/>
Filters can be used to drill down as deep as you like. In this example, only the configuration of interface GigabitEthernet1/0/1 is returned. The following filter interfaces/interface[name='GigabitEthernet1/0/1'] achieves this.
"name" is a key, and has a value of "GigabitEthernet1/0/1" [name='GigabitEthernet1/0/1']. NOTE: the name is case sensitive.
For the first time I do not need to truncate the output.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830 --get-config -x "interfaces/interface[name='GigabitEthernet1/0/1']"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>GigabitEthernet1/0/1</name>
<description>uplink to router</description>
<type xmlns:ianaift="urn:ietf:params:xml:ns:yang:iana-if-type">ianaift:ethernetCsmacd</type>
<enabled>true</enabled>
<diffserv-target-entry xmlns="urn:ietf:params:xml:ns:yang:ietf-diffserv-target">
<direction>outbound</direction>
<policy-name>EasyQos-Egress</policy-name>
</diffserv-target-entry>
<ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<address>
<ip>10.10.6.2</ip>
<netmask>255.255.255.0</netmask>
</address>
</ipv4>
<ipv6 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip"/>
</interface>
</interfaces>
</data>
</rpc-reply>>
This example takes this one step further by just returning the description attribute for a specific interface. NOTE: the "name" attribute is also returned, as it is the key (details on why this is the case will be explained in a future blog).
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830 --get-config -x "interfaces/interface[name='GigabitEthernet1/0/1']/description"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>GigabitEthernet1/0/1</name>
<description>uplink to router</description>
</interface>
</interfaces>
</data>
</rpc-reply>
Another example of the power of filters is restricting to a set of leaves. In this example the search is restricted to get the description for all interfaces. Only interfaces with descriptions are returned.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830 --get-config -x "interfaces/interface/description"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>GigabitEthernet1/0/1</name>
<description>uplink to router</description>
</interface>
<interface>
<name>GigabitEthernet1/0/10</name>
<description>802.1x host</description>
</interface>
<interface>
<name>GigabitEthernet1/0/4</name>
<description>IP Phone test</description>
</interface>
</interfaces>
</data>
</rpc-reply>
This is a great example of why structured data (models) is important. If I was to try to do this from CLI, I could use the following, but i still need to filter/regexp the relevant information:
3850-remote#show run | inc Gigabit|desc
description Topology control
description DHCP snooping, show forward and rest of traffic
description Learning cache ovfl, Crypto Control, Exception, EGR Exception, NFL SAMPLED DATA, Gold Pkt, RPF Failed
description Wireless priority 1
description Wireless priority 2
description Wireless priority 3,4 and 5
description Routing control
description Protocol snooping
interface GigabitEthernet0/0
interface GigabitEthernet1/0/1
description uplink to router
interface GigabitEthernet1/0/2
interface GigabitEthernet1/0/3
interface GigabitEthernet1/0/4
description IP Phone test
interface GigabitEthernet1/0/5
interface GigabitEthernet1/0/6
interface GigabitEthernet1/0/7
interface GigabitEthernet1/0/8
interface GigabitEthernet1/0/9
interface GigabitEthernet1/0/10
description 802.1x host
<SNIPPED/>
Notice there is no configuration for vlan membership in the example above. We are using the standard ietf-interfaces model, and this does not (currently) include vlan membership.
More advanced features are modelled in the "native" model. The native model interface structure is slightly different to the standard model. The interface name is very different. Instead of the name being "GigabitEthernet0/0" there is a container for "GigabitEthernet" and the name is simply "0/0".
To get all interfaces, the filter is just "native/interface" instead of "interfaces/interface" with the standard model.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p sdn123 --port 830 --get-config -x "native/interface"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<native xmlns="http://cisco.com/ns/yang/ned/ios">
<interface>
<GigabitEthernet>
<name>0/0</name>
<negotiation>
<auto>true</auto>
</negotiation>
<vrf>
<forwarding>Mgmt-vrf</forwarding>
</vrf>
<ip>
<no-address>
<address>false</address>
</no-address>
</ip>
</GigabitEthernet>
<GigabitEthernet>
<SNIPPED/>
To get the configuration of a specific interface 1/0/10:
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p sdn123 --port 830 --get-config -x "native/interface/GigabitEthernet[name='1/0/10']"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<native xmlns="http://cisco.com/ns/yang/ned/ios">
<interface>
<GigabitEthernet>
<name>1/0/10</name>
<switchport>
<access>
<vlan>
<vlan>30</vlan>
</vlan>
</access>
<mode>
<access/>
</mode>
</switchport>
<authentication>
<host-mode>multi-auth</host-mode>
</authentication>
<spanning-tree>
<portfast/>
</spanning-tree>
<description>802.1x host</description>
<service-policy>
<output>EasyQos-Egress</output>
</service-policy>
</GigabitEthernet>
</interface>
</native>
</data>
</rpc-reply>
Looking at interface 1/0/9, there is currently no vlan defined.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p sdn123 --port 830 --get-config -x "native/interface/GigabitEthernet[name='1/0/9']"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<native xmlns="http://cisco.com/ns/yang/ned/ios">
<interface>
<GigabitEthernet>
<name>1/0/9</name>
<switchport>
<mode>
<access/>
</mode>
</switchport>
<service-policy>
<output>EasyQos-Egress</output>
</service-policy>
</GigabitEthernet>
</interface>
</native>
</data>
</rpc-reply> </data>
</rpc-reply>
I create a file /tmp/edit with the changes I want to make to interface 1/0/9. I want to change the access vlan to vlan 30.
$ cat /tmp/edit
<native xmlns="http://cisco.com/ns/yang/ned/ios">
<interface>
<GigabitEthernet>
<name>1/0/9</name>
<switchport>
<access>
<vlan>
<vlan>30</vlan>
</vlan>
</access>
</switchport>
</GigabitEthernet>
</interface>
</native>
This change can be applied by using the edit-config RPC. It returns successfully.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830 --edit-config=/tmp/edit
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<ok/>
</rpc-reply>
I can now verify the switchport/access/vlan configuration for interface 1/0/9. It has been changed to vlan 30.
$ ./netconf-console.py --host=10.10.6.2 -u sdn -p password --port 830 --get-config -x "native/interface/GigabitEthernet[name='1/0/9']/switchport/access/vlan"
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<data>
<native xmlns="http://cisco.com/ns/yang/ned/ios">
<interface>
<GigabitEthernet>
<name>1/0/9</name>
<switchport>
<access>
<vlan>
<vlan>30</vlan>
</vlan>
</access>
</switchport>
</GigabitEthernet>
</interface>
</native>
</data>
</rpc-reply>
This blog post has shown some basic ways to interact with NETCONF/YANG in Cisco IOS-XE 16.3.2. The following concepts have been explored:
In the meantime, if you would like to learn more about this, you could come hang out with us in The Cisco Devnet DNA Community. We’ll have a continuous stream of blogs like this and you can ask questions and we’ll get you answers.
Future blogs will contain more details of NETCONF/YANG, operational vs configuration data and other tools/python libraries you can use to interface via NETCONF. Check out Getting Started with NETCONF/YANG – Part 2
Thanks for reading,
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: