So, we've decided to use NETCONF to configure our devices. In this repository we'll explore the YANG messages we need to communicate with our device, perform RAW NETCONF operations on a Cisco 8000v router and establish a workflow.
Then we'll explore the two clients that are available and options for structuring code to utilize NETCONF.
IOS XE Interfaces
IOS XE Static Routes
IOS XE Access Lists
Snippets and blocks
# First we need to configure our cisco router with an IP address
# username, login local and netconf-yang
conf t
hostname c8000v
!
aaa new-model
aaa session-id common
!
aaa authentication login default local
aaa authorization exec default local
!
username cisco privilege 15 secret 0 cisco
!
netconf-yang
!netconf-yang feature candidate-datastore
!
interface GigabitEthernet1
ip address 192.168.255.72 255.255.255.0
no shutdown
exit
!
line vty 0 4
transport input ssh
end
wr
Now we can connect to our router with SSH
$ ssh cisco@8000v
Password:
8000v#
Likewise we can connect to the netconf subsystem with ssh
$ ssh cisco@8000v -p 830 -s netconf
cisco@8000v'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>urn:ietf:params:netconf:capability:writable-running:1.0</capability>
<capability>urn:ietf:params:netconf:capability:rollback-on-error: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:xpath:1.0</capability>
<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>
<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>
<capability>urn:ietf:params:netconf:capability:with-defaults:1.0?basic-mode=explicit&also-supported=report-all-tagged,report-all</capability>
<capability>urn:ietf:params:netconf:capability:yang-library:1.0?revision=2016-06-21&module-set-id=62fce412ef7ae70741dbc9b96d64dda6</capability>
.
.
.
<capability>
urn:ietf:params:netconf:capability:notification:1.1
</capability>
</capabilities>
<session-id>26</session-id></hello>]]>]]>
NOTE: The end of the hello message ']]>]]>' this character sequence comes at the end of each message to indicate the message is over.
Now we're ready to interact with our device with NETCONF. The connection needs to start with a hello message (just like the one we received from the device) where we indicate our capabilities.
<?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>]]>]]>
<!-- This message indicates netconf 1.1 capabilities-->
<?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.1</capability>
</capabilities>
</hello>]]>]]>
Once we've been polite and said hello, we can start asking the device to do things for us. Each message we send needs to have a message ID which increments sequencially and wrapped in a RPC message:
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='{message_id}'></rpc>
We'll use this to wrap our get config request
<get-config><source><{source}/></source></get-config>
Our request to get the running config will therefor look like this:
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='102'>
<get-config>
<source>
<running/>
</source>
</get-config>
</rpc>
$ ssh cisco@8000v -p 830 -s netconf
cisco@8000v's password:
ROUTER SENDS:
<?xml version="1.0" encoding="UTF-8"?>
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<capabilities>
...
</capabilities>
</hello>]]>]]>
WE SEND:
<?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>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='102'>
<get-config>
<source>
<running/>
</source>
</get-config>
</rpc>]]>]]>
ROUTER SENDS:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="102">
<data>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
... Router configuration comes here
</native>
</data>
</rpc-reply>]]>]]>
Ok well, that was neat... but that's a lot of configuration. Can we filter it somehow? Sure can! We even have two types of filters we can use:
<filter type='subtree'></filter>
<filter type='xpath' select='{xpath}'></filter>
Let's start by getting something simple like the software version. This is located right at the top of the native configuration. That makes for the following request. We can send the hello and our first message in one go: WE SEND:
<?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>]]>]]>
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
<get-config>
<filter type="xpath" select="/native/version"></filter>
<source>
<running/>
</source>
</get-config>
</rpc>]]>]]>
DEVICE SENDS:
<?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/Cisco-IOS-XE-native">
<version>17.5</version>
</native>
</data>
</rpc-reply>]]>]]>
WE SEND:
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="102">
<get-config>
<filter type="xpath" select="/native/ip"></filter>
<source>
<running/>
</source>
</get-config>
</rpc>]]>]]>
DEVICE SENDS:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="104">
<data>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<ip>
<forward-protocol>
<protocol>nd</protocol>
</forward-protocol>
<ftp><passive/></ftp>
<multicast>
<route-limit xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-multicast">2147483647</route-limit>
</multicast>
<http xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-http">
<server>false</server>
<secure-server>true</secure-server>
</http>
<nbar xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-nbar">
<classification>
<dns>
<classify-by-domain/>
</dns>
</classification>
</nbar>
</ip>
</native>
</data>
</rpc-reply>]]>]]>
OK Great! We can communicate with our device over NETCONF and retreive the configuration blocks we want. Now we're ready to configure our router.
We're now going modify our device, lets start by changing the hostname, then we'll try something a bit more involved. First we'll have to look at the capabilities the router has, as enabling candidate datastore will remove writable-running. Looking at the initial response from our router:
<?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>
We can see writable-running:1.0 there in the list, this means we can target the running config directly over NETCONF. This line goes away once we enable netconf-yang feature candidadate-datastore and instead we get this:
<?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:confirmed-commit:1.1</capability>
<capability>urn:ietf:params:netconf:capability:confirmed-commit:1.0</capability>
<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>
This means we have to hit the candidate datastore and commit our changes if we want to configure our device. We can handle for both options in our python code later on. For now lets start by changing our running configuration directly.
Let's start by writing down all the messages we're going to be sending. Our target is the 'running' configuration. Each RFC message after the hello needs to be wrapped and sequenced
<?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>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='101'>
<lock>
<target>
<running/>
</target>
</lock>
</rpc>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='102'>
<edit-config>
<target>
<running/>
</target>
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<hostname>router</hostname>
</native>
</config>
</edit-config>
</rpc>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='103'>
<unlock>
<target>
<running/>
</target>
</unlock>
</rpc>]]>]]>
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="104">
<get-config>
<filter type="xpath" select="/native/hostname"></filter>
<source>
<running/>
</source>
</get-config>
</rpc>]]>]]>
Whoah.. neat! We just changed our running config. Each time we send a message we should get the following reply if things are going well:
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="104">
<ok/>
</rpc-reply>]]>]]>
And for our final message we should get our new config data back:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="104">
<data>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<hostname>router</hostname>
</native>
</data>
</rpc-reply>]]>]]>
Let's finish up by validating the configuration:
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="106">
<validate>
<source>
<running/>
</source>
</validate>
</rpc>]]>]]>
Now we just need to save our configuration to startup
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="107">
<save-config xmlns="http://cisco.com/yang/cisco-ia"/>
</rpc>]]>]]>
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="107">
<result xmlns='http://cisco.com/yang/cisco-ia'>
Save running-config successful
</result>
</rpc-reply>]]>]]>
we should now be graceful and close our session before we leave
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="110">
<close-session/>
</rpc>]]>]]>
In order to get commits we need to enable the candidate datastore on the device:
netconf-yang feature candidate-datastore
The flow then looks like this:
<?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>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='101'>
<lock>
<target>
<candidate/>
</target>
</lock>
</rpc>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='102'>
<edit-config>
<target>
<candidate/>
</target>
<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<hostname>8000v</hostname>
</native>
</config>
</edit-config>
</rpc>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='103'>
<validate>
<source>
<candidate/>
</source>
</validate>
</rpc>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='104'>
<commit/>
</rpc>]]>]]>
<rpc xmlns='urn:ietf:params:xml:ns:netconf:base:1.0' message-id='105'>
<unlock>
<target>
<candidate/>
</target>
</unlock>
</rpc>]]>]]>
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="106">
<get-config>
<filter type="xpath" select="/native/hostname"></filter>
<source>
<candidate/>
</source>
</get-config>
</rpc>]]>]]>
Whoah.. neat! We just changed our config and commited it. Each time we send a message we should get the following reply if things are going well:
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="105">
<ok/>
</rpc-reply>]]>]]>
And for our final message we should get our new config data back:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="106">
<data>
<native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
<hostname>8000v</hostname>
</native>
</data>
</rpc-reply>]]>]]>
Now we just need to save our configuration to startup
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="109">
<save-config xmlns="http://cisco.com/yang/cisco-ia"/>
</rpc>]]>]]>
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="109">
<result xmlns='http://cisco.com/yang/cisco-ia'>
Save running-config successful
</result>
</rpc-reply>]]>]]>
we should now be graceful and close our session before we leave
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="110">
<close-session/>
</rpc>]]>]]>
Great, now let's get a bit of Python power