NETCONF

POCO NETCONF User Guide

Introduction

The NETCONF configuration protocol provides mechanisms to install, manipulate, and delete the configuration of network devices. It uses an Extensible Markup Language (XML) based data encoding for the configuration data as well as the protocol messages.

This implementation conforms to the NETCONF protocol, as specified in RFC 4741. Further information about NETCONF can be found at http://www.ops.ietf.org/netconf.

POCO NETCONF

Applied Informatics' implementation of NETCONF is based on POCO Remoting. The key class of the implementation is the Poco::Netconf::ConfigurationService class. Its design follows closely the NETCONF standard, i.e. people familiar with the standard will immediately feel at home. But before checking out the ConfigurationService, lets have a short look on hwo to use Filters.

Filters

Filtering is an important concept in NETCONF. One can either filter via an XML subtree or via an XPath statement. Note that POCO NETCONF currently only supports XML based filtering! An uninitialized filter, i.e. created with the default constructor, will filter nothing and return the full document. The interface is given as:

class Netconf_API Filter
    /// A Filter filters a document either by xpath or by a XML tree. 
    /// A uninitialized filter will return everything.
    /// If a filter is initialized with an empty Document (which is valid),
    /// it will return nothing. If it is not initialized at all, it will 
    /// return everything.
{
public:
    enum Type
    {
        F_GETALL  = 0,
        F_XPATH   = 1,
        F_SUBTREE = 2
    };

    Filter();
        /// Creates a default Filter, which doesn't filter at all.

    ~Filter();
        /// Destroys the Filter.

    const std::string& getXPath() const;
        /// Returns an xpath expression, can be empty.

    Poco::AutoPtr < XML::Document > getSubTree() const;
        /// Returns the tree, which can be 0.

    void setXPath(const std::string& xPathExpress);
        /// Creates a xpath filter, removes a previously set XML::Document filter.

    void setSubTree(Poco::AutoPtr < XML::Document > filter= Poco::AutoPtr < XML::Document >());
        /// Creates a subtree filter, removes a previously set xpath filter.
        /// If the document is null, the filter will filter everything, i.e. returning nothing.

    void setToGetAll();
        /// Disables filtering.

    Type type() const;
        /// Returns the type of the filter.

private:
    Type        _filteringType;
    std::string _xPathFilter;
    Poco::AutoPtr < XML::Document > _subTreeFilter;
};

A simple example for using filters is given below. Assume you have the following configuration data

<value hi="attrValue" xmlns="http://appinf.com/dummyUri">
    <i dummyAttr="attrValue2">100</i
    <i dummyAttr="attrValue3">101</i>"
    <i>
        <x>103</x>
    </i>
    <m>
        <k/>
    </m>
</value>

and you create the following XML document for use as a filter

<value xmlns="http://appinf.com/dummyUri">
    <i/>
</value>

which tells the ConfigurationService to return all subchildren that are part of the given namespace and are named i:

<value xmlns="http://appinf.com/dummyUri">
    <i dummyAttr="attrValue2">100</i
    <i dummyAttr="attrValue3">101</i>"
    <i>
        <x>103</x>
    </i>
</value>

Basically what one does is to provide a pattern that the configuration nodes must match exactly. If something is not specified (e.g. attribute values, subchild nodes), this acts as a wildcard.

Please check the NETCONF standard document for more complex examples.

ConfigurationService

The configuration service offers the following methods:

namespace Poco {
namespace Transports {
namespace Netconf {


class Filter;


class Netconf_API ConfigurationService
    /// Implementation of the protocol part of NETCONF. Note that the generated remote 
    /// classes for the ConfigurationService (proxy, skeleton, interface) were manually
    /// (method names according to NETCONF schema have a minus sign in them which C++ 
    /// doesn't like, also default parameters must be at the end of the method signature 
    /// and attributes are not explicitely supported by the transport layer, same goes for 
    /// enums)
{
public:
    ConfigurationService(Persistency* pPersistency);
        /// Creates the ConfigurationService with the given persistency object.

    ~ConfigurationService();
        /// Destroys the ConfigurationService.

    Poco::AutoPtr<XML::Document> get(const Filter& filter = Filter());
        /// Retrieves configuration data from the default datastore ("running")

    Poco::AutoPtr<XML::Document> getConfig(
    	const std::string& source, 
    	const Filter& filter = Filter()) const;
        /// Implements the get-config operation. The source defines the configuration 
        /// datastore which is queried, the filter specifies the filtering criteria on 
        /// the complete datastore. If for filtering the defaultparameter 
        /// is used, filtering will be switched off.

    void editConfig(
        const std::string& target, 
        Poco::AutoPtr<XML::Document> config, 
        Utility::DefaultOperation defaultOperation = Utility::DOP_MERGE, 
        Utility::TestOption testOption = Utility::TO_TESTTHENSET, 
        Utility::ErrorOption errorOption = Utility::EO_STOP);
        /// Implements the edit-config operation. Note that the default behavior 
        /// is to try an update/replace first, then an add.
        /// This implies that simply adding data to an vector is not possible, 
        /// one must send the whole vector and use replace as default operation!

    void copyConfig(const std::string& target, const std::string& source);
        /// Implements the copy-config operation.

    void deleteConfig(const std::string& target);
        /// Implements the delete-config operation.

    void lock(const std::string& target);
        /// Implements the lock operation.

    void unlock(const std::string& target);
        /// Implements the unlock operation.

    void closeSession();
        /// Implements the close-session operation.

    void killSession(Poco::UInt32 sessionId);
        /// Implements the kill-session operation.
};

Constructor

A ConfigurationService is always created with a Persistency object. A persistency object is responsible for loading/saving configurations. POCO NETCONF offers per default file based persistency. Extend from Persistency with your own implementation, if you require different functionality.

Getting (filtered) Data

The get and getConfig methods are used to retrieve configuration data from a single datastore. Whereas the first method defaults to the datastore named running, the second method allows you to specify the datastore name. Note that the filter parameters are per default set to filter nothing, i.e. return the whole configuration data unaltered.

Editing Configuration Data

editConfig is one of the more complex methods defined by the NETCONF standard. One can actually delete, merge, or replace existing configuration entries. The action one intends to perform is signalled by the defaultOperation attribute:

enum DefaultOperation
{
    DOP_MERGE = 0,
    DOP_REPLACE,
    DOP_NONE
};

Note that delete is not a default operation. If one wants to delete a certain entry, one must add the NETCONF attribute operation explicitely to the part of the filter tree that should be deleted. Assume you have the following configuration data:

<value hi="attrValue" xmlns="http://appinf.com/dummyUri">
    <i>
        <x>103</x>
        <y>104</y>
    </i>
    <m>
        <k/>
    </m>
</value>

To delete element value.i.x one would create the following subtree filter:

<value xmlns="http://appinf.com/dummyUri">
    <i>
        <x xc:operation="delete" xmlns:xc="urn:ietf:params:xml:ns:netconf:base:1.0"/x>
    </i>
</value>

Note that per default data is merged and non-vectors are assumed. The second assumption is especially important, if we have configuration data like:

<value hi="attrValue" xmlns="http://appinf.com/dummyUri">
    <i>100</i>
</value>

Now assume we want to insert another i value. The following cases can occur depending on the underlying schema:

These are the main reasons why the merge operation replaces existing entries, something which is always correct for non-vector data but can have interesting effects on vector configuration data.

The following configuration and filter is given:

<value hi="attrValue" xmlns="http://appinf.com/dummyUri">
    <i>100</i>
    <i>101</i>
</value>

<value xmlns="http://appinf.com/dummyUri">
    <i>102</i>
</value>

This results in a configuration of:

<value hi="attrValue" xmlns="http://appinf.com/dummyUri">
    <i>102</i>
    <i>102</i>
</value>

Also nobody prevents you from defining a filter like that:

<value xmlns="http://appinf.com/dummyUri">
    <i>102</i>
    <i>103</i>
</value>

Which will result in 102 being written to all subchildren named i, followed by 103 written to all subchildren, yielding a result of:

<value hi="attrValue" xmlns="http://appinf.com/dummyUri">
    <i>103</i>
    <i>103</i>
</value>

Thus, if you are dealing with vector types, either add some kind of index attribute to the vector elements and always specify the index attribute in the filters, or set the default operation to replace and when updating, always send the complete vector in the filter.

The other methods are pretty self explainatory. copyConfig and deleteConfig do what you would expect them to do, copying/deleting complete configurations. If you are doing complex manipulations of configuration data (i.e. more than one call to ConfigurationService), you should lock the affected data stores (don't forget to unlock!), it is also considered nice, though not mandatory, to call closeSession once one is done with the ConfigurationService object.

killSession can be used to kill sessions of others, i.e. releasing their locks, though not your own session (don't ask why: that's the behavior that the NETCONF standard asks for).

Server Code

The server code follows closely the sample code for the Remoting samples, see Netconf/samples for the full source-code. The configuration service is created with file based persistency, i.e. during startup it parses the local data directory for all files of type xml and loads them:

using Poco::Transports::Netconf::ConfigurationService;
using Poco::Transports::Netconf::ConfigurationServiceServerHelper;

Poco::Transports::Netconf::TransportFactory::registerFactory();

Poco::Remoting::ORB::instance().registerListener(new Poco::Transports::Netconf::Listener(9999));

Poco::File storageDir("data/");
ConfigurationService* pCS = new ConfigurationService(
	new Poco::Transports::Netconf::FilePersistency(
		new Poco::Transports::Netconf::Transport(), 
		storageDir, 
		"xml"));
ConfigurationServiceServerHelper::registerObject(
	pCS, 
	"class1", 
	9999, 
	Poco::Transports::Netconf::Transport::ID, 
	false);

// wait for CTRL-C or kill
waitForTerminationRequest();
// Stop the ORB
Poco::Remoting::ORB::instance().shutdown();

This code differs not much from the other remoting samples but due to the NETCONF standard it has one restriction: One can only register one single RemoteObject at one listener. Further attempts will result in an exception being thrown.

Client Code

The client code is also very similar to the other Remoting samples. In this example we create a new running configuration and then retrieve the complete configuration from the server:

Poco::Transports::Netconf::TransportFactory::registerFactory();

std::string localHostName = Poco::Net::DNS::thisHost().name(); // assume that the server runs on the same machine
if (argc == 2)
    localHostName = argv[1];

using Poco::Transports::Netconf::IConfigurationService;
using Poco::Transports::Netconf::ConfigurationServiceClientHelper;
Poco::AutoPtr<IConfigurationService> ptrCS = ConfigurationServiceClientHelper::findObject(
	localHostName, 
	"class1", 
	9999, 
	Poco::Transports::Netconf::Transport::ID, 
	false);
const std::string input(
	"<ns1:value hi=\"attrValue\" xmlns:ns1=\"http://appinf.com/dummyUri\">"
    "<ns1:i dummyAttr=\"attrValue2\">100</ns1:i>"
    "<ns1:i dummyAttr=\"attrValue3\">101</ns1:i>"
    "<ns1:i>102</ns1:i>"
    "<ns1:i dummyAttr=\"attrValue2\">103</ns1:i>"
    "<ns1:l/>"
    "<ns1:m>"
    "<ns1:l/>"
    "</ns1:m>"
/ns1:value>");

Poco::AutoPtr<Poco::XML::Document> ptrDoc = createDoc(input);

ptrCS->editConfig(
	Poco::Transports::Netconf::ConfigurationService::RUNNING, 
	ptrDoc, 
	Poco::Transports::Netconf::Utility::DOP_REPLACE);

Poco::AutoPtr<Poco::XML::Document> ptrResult = ptrCS->get();

poco_check_ptr (ptrResult.get());
std::string result = createString(ptrResult);
poco_assert (result == input);
std::cout << result;

Note that createDoc and createString are small helper classes for which the implementation is not shown here. The full source code is in Netconf/samples.