libnetconf  0.10.0-146_trunk
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages

Server Architecture

It is strongly advised to set SUID (or SGID) bit on every application that is built on libnetconf for a user (or group) created for this purpose, as several internal functions behaviour is based on this precondition. libnetconf uses a number of files which pose a security risk if they are accessible by untrustworthy users. This way it is possible not to restrict the use of an application but only the access to its files, so keep this in mind when creating any directories or files that are used.

Generally, there are three approaches of how to implement a NETCONF server using libnetconf. The order in which they are mentioned is ascending in their implementation complexity. The use of the first single-level architecture for production environment is discouraged.

Single-level Architecture

Single-level architecture

In this case, all the server functionality is implemented as a single process. It is started by SSH daemon as its Subsystem for each incoming NETCONF session connection. The main issue of this approach is a simultaneous access to shared resources. The device manager has to solve concurrent access to the controlled device from its multiple instances. libnetconf itself has to deal with simultaneous access to a shared configuration datastore.

Multi-level Architecture

Multi-level architecture

In the second case, there is only one device manager (NETCONF server) running as a system daemon. This solves the problem of concurrent device access from multiple processes. On the other hand, there is a need for inter-process communication between the device manager and the agents launched as SSH Subsystems. These agents hold NETCONF sessions and receive requests from the clients. libnetconf provides functions (nc_rpc_dump() and nc_rpc_build()) to (de-)serialise content of the NETCONF messages. This allows the NETCONF messages to be passed between an agent and a device manager that applies requests to the operated device and a configuration datastore.

Integrated Architecture

Integrated architecture

In this last case, there is only a single application running having the SSH transport integrated. This way there is no need for any communication to other processes, which can be substituted by threads. At the cost of significantly increasing the complexity of the server and likely requiring additional libraries, it gains full control over client connections including the transport layer and dependencies on any external applications are removed. This is especially convenient for TLS, which in effect makes this architecture the only viable option that fully supports it.

Server Workflow

Here is a description of using libnetconf functions in a NETCONF server. According to the used architecture, the workflow can be split between an agent and a server. For this purpose, functions nc_rpc_dump(), nc_rpc_build() and nc_session_dummy() can be very helpful.

  1. Set the verbosity (optional)
    The verbosity of the libnetconf can be set by nc_verbosity(). By default, libnetconf is completely silent.
    There is a default message printing function writing messages on stderr. On the server side, this is not very useful, since server usually runs as a daemon without stderr. In this case, something like syslog should be used. The application's specific message printing function can be set via nc_callback_print() function.
  2. Initiate libnetconf
    As the first step, libnetconf MUST be initiated using nc_init(). At this moment, the libnetconf subsystems, such as NETCONF Notifications or NETCONF Access Control, are initiated according to the specified parameter of the nc_init() function.
  3. Set With-defaults basic mode (optional)
    By default, libnetconf uses explicit basic mode of the with-defaults capability. The basic mode can be changed via ncdflt_set_basic_mode() function. libnetconf supports explicit, trim, report-all and report-all-tagged basic modes of the with-defaults capability.
  4. Initiate datastore
    Now, a NETCONF datastore(s) can be created. Each libnetconf's datastore is connected with a single configuration data model. This connection is defined by calling the ncds_new() function, which returns a datastore handler for further manipulation with an uninitialized datastore. Using this function, caller also specifies which datastore implementation type will be used. Optionally, some implementation-type-specific parameters can be set (e.g. ncds_file_set_path()). Finally, datastore must be initiated by ncds_init() that returns datastore's ID which is used in the subsequent calls. There is a set of special implicit datastores with ID NCDS_INTERNAL_ID that refer to the libnetconf's internal datastore(s).
    Optionally, each datastore can be extended by an augment data model that can be specified by ncds_add_model(). The same function can be used to specify models to resolve YANG's import statements. Alternatively, using ncds_add_models_path(), caller can specify a directory where such models can be found automatically. libnetconf searches for the needed models based on the modules names. Filename of the model is expected in a form module_name[@revision].yin.
    Caller can also switch on or off the YANG feauters in the specific module using ncds_feature_enable(), ncds_feature_disable(), ncds_features_enableall() and ncds_features_disableall() functions.
    Finally, ncds_consolidate() must be called to check all the internal structures and to solve all import, uses and augment statements.
  5. Initiate the controlled device
    This step is actually out of the libnetconf scope. From the NETCONF point of view, startup configuration data should be applied to the running datastore at this point. ncds_device_init() can be used to perform this task, but applying running configuration data to the controlled device must be done by a server specific (non-libnetconf) function.
  6. Accept incoming NETCONF connection.
    This is done by a single call of nc_session_accept() or nc_session_Accept_username() alternatively. Optionally, any specific capabilities supported by the server can be set as the function's parameter.
  7. Server loop
    Repeat these three steps:
    1. Process incoming requests.
      Use nc_session_recv_rpc() to get the next request from the client from the specified NETCONF session. In case of an error return code, the state of the session should be checked by nc_session_get_status() to learn if the session can be further used.
      According to the type of the request (nc_rpc_get_type()), perform an appropriate action:
      • NC_RPC_DATASTORE_READ or NC_RPC_DATASTORE_WRITE: use ncds_apply_rpc2all() to perform the requested operation on the datastore. If the request affects the running datastore (nc_rpc_get_target() returns NC_DATASTORE_RUNNING), apply configuration changes to the controlled device.
      • NC_RPC_SESSION: See the Netopeer example server source codes. There will be a common function added in the future to handle these requests.
    2. Reply to the client's request.
      The reply message is automatically generated by the ncds_apply_rpc2all() function. However, server can generate its own replies using nc_reply_ok(), nc_reply_data() (nc_reply_data_ns()) or nc_reply_error() functions. The reply is sent to the client using nc_session_send_reply() call.
    3. Free all unused objects.
      Do not forget to free received rpc messages (nc_rpc_free()) and any created replies (nc_reply_free()).
  8. Close the NETCONF session.
    Use functions nc_session_free() to close and free all the used sources and structures connected with the session. Server should close the session when a nc_session_* function fails and libnetconf set the status of the session as non-working (nc_session_get_status != NC_SESSION_STATUS_WORKING).
  9. Close the libnetconf instance
    Close internal libnetconf structures and subsystems by the nc_close() call.