- Start
- What's New
- Getting Started
- User Guide
- About
- Basic Operations
- Network Element Drivers and Adding Devices
- Managing Network Services
- NSO CLI
- The NSO Device Manager
- SSH Key Management
- Alarm Manager
- Plug-and-play Scripting
- Compliance reporting
- NSO Packages
- Life-cycle Operations - Manipulating Existing Services and Devices
- Web User Interface
- Network Simulator
- Administration Guide
- Northbound APIs
- Development Guide
- Preface
- Development Environment and Resources
- The Configuration Database and YANG
- Basic Automation with Python
- Developing a Simple Service
- Applications in NSO
- Implementing Services
- Templates
- Services Deep Dive
- The NSO Java VM
- The NSO Python VM
- Embedded Erlang applications
- The YANG Data Modeling Language
- Using CDB
- Java API Overview
- Python API Overview
- NSO Packages
- Package Development
- Service Development Using Java
- NED Development
- NED Upgrades and Migration
- Service Handling of Ambiguous Device Models
- Scaling and Performance Optimization
- NSO Concurrency Model
- Developing Alarm Applications
- SNMP Notification Receiver
- The web server
- Kicker
- Scheduler
- Progress Trace
- Nano Services for Staged Provisioning
- Encryption Keys
- External Logging
- NSO Developer Studio
- Web UI
- Layered Service Architecture
- Manual Pages
- NSO Documentation Home
- NSO SDK API Reference
- NSO Change Log Explorer
- NSO NED Change Log Explorer
- NSO NED Capabilities Explorer
- NSO on DevNet
- Get Support
OUTDATED
OUTDATED
This documentation corresponds to an older version of the product, is no longer updated, and may contain outdated information.
Please access the latest versions from https://cisco-tailf.gitbook.io/nso-docs and update your bookmarks. OK
This section will explain the different locks that exist in NSO and how they interact. It is important to understand the architecture of NSO with its management backplane, and the transaction state machine as described in Package Development in Development Guide to be able to understand how the different locks fit into the picture.
The NSO management backplane keeps a lock on the datastore: running. This lock is usually referred to as the global lock and it provides a mechanism to grant exclusive access to the datastore.
The global is the only lock that can explicitly be
taken through a northbound agent, for example by the NETCONF
<lock>
operation, or by calling
Maapi.lock()
.
A global lock can be taken for the whole datastore, or it can be a partial lock (for a subset of the data model). Partial locks are exposed through NETCONF and MAAPI and are only supported for operations toward the running datastore.
An agent can request a global lock to ensure that it has
exclusive write-access. When a global lock is
held by an agent it is not possible for anyone else to write to
the datastore the lock guards - this is enforced by the
transaction engine. A global lock on running is granted to
an agent if there are no other holders of it (including partial
locks), and if all data providers approve the lock request. Each
data provider (CDB and/or external data providers) will have its
lock()
callback invoked to get a chance to refuse or
accept the lock. The output of ncs --status includes
locking status. For each user session locks (if any) per
datastore is listed.
A northbound agent starts a user session towards NSO's management backplane. Each user session can then start multiple transactions. A transaction is either read/write or read-only.
The transaction engine has its internal locks towards the running datastore. These transaction locks exists to serialize configuration updates towards the datastore and are separate from the global locks.
As a northbound agent wants to update the running datastore with a new configuration it will implicitly grab and release the transactional lock. The transaction engine takes care of managing the locks, as it moves through the transaction state machine and there is no API that exposes the transactional locks to the northbound agents.
When the transaction engine wants to take a lock for a
transaction (for example when entering the validate state) it
first checks that no other transaction has the lock. Then it
checks that no user session has a global lock on that
datastore. Finally each data provider is invoked by its
transLock()
callback.
In contrast to the implicit transactional locks, some northbound agents expose explicit access to the global locks. This is done a bit differently by each agent.
The management API exposes the global locks by providing
Maapi.lock()
and Maapi.unlock()
methods (and the corresponding Maapi.lockPartial()
Maapi.unlockPartial()
for partial locking). Once a user
session is established (or attached to) these functions can be
called.
In the CLI the global locks are taken when entering different configure modes as follows:
- config exclusive
-
The running datastore global lock will be taken.
- config terminal
-
Does not grab any locks
The global lock is then kept by the CLI until the configure mode is exited.
The Web UI behaves in the same way as the CLI (it presents three edit tabs called "Edit private", "Edit exclusive", and which corresponds to the CLI modes described above).
The NETCONF agent translates the <lock>
operation into
a request for the global lock for the requested
datastore. Partial locks are also exposed through the
partial-lock rpc.
Implementing the lock()
and unlock()
callbacks
is not required of an external data provider. NSO will never
try to initiate the transLock()
state transition (see
the transaction state diagram in
Package Development
in Development Guide)
towards a data provider while a global lock is taken - so the
reason for a data provider to implement the locking callbacks is
if someone else can write (or lock for example to take a backup)
to the data providers database.
CDB ignores the lock()
and unlock()
callbacks
(since the data-provider interface is the only write interface
towards it).
CDB has its own internal locks on the database. The running datastore has a single write and multiple read locks. It is not possible to grab the write-lock on a datastore while there are active read-locks on it. The locks in CDB exists to make sure that a reader always gets a consistent view of the data (in particular it becomes very confusing if another user is able to delete configuration nodes in between calls to getNext() on YANG list entries).
During a transaction transLock()
takes a CDB
read-lock towards the transactions datastore and writeStart()
tries to release the read-lock and grab the write-lock
instead.
A CDB external reader client implicitly takes a CDB read-lock
between
Cdb.startSession()
and Cdb.endSession()
This means that while
an CDB client is reading, a transaction can not pass through
writeStart()
(and conversely a CDB reader can not start
while a transaction is in between writeStart()
and
commit()
or abort()
).
The Operational store in CDB does not have any locks. NSO's transaction engine can only read from it, and the CDB client writes are atomic per write operation.
When a session tries to modify a data store that is locked in some way, it will fail. For example, the CLI might print:
admin@ncs(config)# commit
Aborted: the configuration database is locked
Since some of the locks are short lived (such as a CDB read lock), NSO is by default configured to retry the failing operation for a short period of time. If the data store still is locked after this time, the operation fails.
To configure this, set
/ncs-config/commit-retry-timeout
in
ncs.conf
.
CDB implements write-ahead logging to provide durability in the datastores, appending a new log for each CDB transaction to the target datastore (A.cdb for configuration, O.cdb for operational, and S.cdb for snapshot datastore). Depending on the size and number of transactions towards the system, these files will grow in size leading to increased disk utilization, longer boot times, and longer initial data synchronization time when setting up a high-availability cluster. Compaction is a mechanism used to reduce the size of the write-ahead logs to a minimum. It works by replacing an existing write-ahead log, which is composed by a number of consecutive transactions logs created in run-time, with a single transaction log representing the full current state of the datastore. From this perspective, it can be seen that a compaction acts similar to a write transaction towards a datastore. To ensure data integrity, write transactions towards the datastore are not permitted during the time compaction takes place.
By default, compaction is handled automatically by the CDB. After each transaction, CDB evaluates whether compaction is required for the affected datastore.
This is done by examining the number of added
nodes as well as the file size changes since the last performed
compaction. The thresholds used can be modified in the
ncs.conf
file by configuring the
/ncs-config/compaction/file-size-relative
,
/ncs-config/compaction/file-size-absolute
, and
/ncs-config/compaction/num-node-relative
settings.
It is also possible to automatically trigger compaction after a set
number of transactions by setting the
/ncs-config/compaction/num-transaction
property.
Compaction may require a significant amount of time, during which write transactions cannot be performed. In certain use-cases, it may be preferable to disable automatic compaction by CDB and instead trigger compaction manually according to the specific needs. If doing so, it is highly recommended to have another automated system in place.
CDB CAPI provides a set of functions which may be used to create an
external mechanism for compaction. See
cdb_initiate_journal_compaction()
,
cdb_initiate_journal_dbfile_compaction()
, and
cdb_get_compaction_info()
in confd_lib_cdb(3) in Manual Pages
.
Automation of compaction can be done by using a scheduling mechanism such as CRON, or by using the NCS scheduler. See Scheduler in Development Guide. for more information
By default, CDB may perform compaction during its boot process. This may be disabled if required, by starting NSO with the flag --disable-compaction-on-start.
In the configuration datastore, compaction is by default delayed by 5
seconds when the threshold is reached in order to prevent any upcoming
write transaction from being blocked. If the system is idle
during these 5 seconds, meaning that there is no new transaction, the
compaction will initiate. Otherwise, compaction is delayed by another
5 seconds. The delay time can be configured in
ncs.conf
by setting the
/ncs-config/compaction/delayed-compaction-timeout
property.
Client libraries connect to NSO using TCP. We tell NSO
which address to use for these connections through the
/ncs-config/ncs-ipc-address/ip
(default value
127.0.0.1) and /ncs-config/ncs-ipc-address/port
(default value 4569) elements in ncs.conf
.
It is possible to change these
values, but it requires a number of steps to also configure the
clients. Also there are security implications, see
the section called “Security issues” below.
Some clients read the environment
variables NCS_IPC_ADDR
and NCS_IPC_PORT
to
determine if something other than the default is to be used,
others might need to be recompiled. This is a list of
clients which communicate with NSO, and what needs to be
done when ncs-ipc-address
is changed.
Client | Changes required |
Remote commands via the ncs command | Remote commands, such as ncs --reload, check the environment variables NCS_IPC_ADDR and NCS_IPC_PORT. |
CDB and MAAPI clients |
The address supplied to Cdb.connect() and
Maapi.connect() must be changed.
|
Data provider API clients |
The address supplied to Dp constructor socket
must be changed.
|
ncs_cli |
The Command Line Interface (CLI) client,
ncs_cli, checks the environment
variables |
Notification API clients |
The new address must be supplied to
the socket for the Nofif constructor.
|
To run more than one instance of NSO on the same host
(which can be useful in development scenarios) each instance
needs its own IPC port. For each instance set
/ncs-config/ncs-ipc-address/port
in
ncs.conf
to something different.
There are two more instances of ports that will have to be
modified, NETCONF and CLI over SSH. The netconf (SSH and
TCP) ports that NSO listens to by default are 2022 and
2023 respectively. Modify
/ncs-config/netconf/transport/ssh
and
/ncs-config/netconf/transport/tcp
, either by
disabling them or changing the ports they listen to. The CLI
over SSH by default listens to 2024; modify
/ncs-config/cli/ssh
either by disabling or
changing the default port.
By default, the clients connecting to the IPC port are
considered trusted, i.e. there is no authentication required,
and we rely on the use of 127.0.0.1 for
/ncs-config/ncs-ipc-address/ip
to prevent remote
access. In case this is not sufficient, it is possible to
restrict the access to the IPC port by configuring an access
check.
The access check is enabled by setting the
ncs.conf
element
/ncs-config/ncs-ipc-access-check/enabled
to "true",
and specifying a filename for
/ncs-config/ncs-ipc-access-check/filename
. The file
should contain a shared secret, i.e. a random character string.
Clients connecting to the IPC port will then be required to
prove that they have knowledge of the secret through a challenge
handshake, before they are allowed access to the NSO functions
provided via the IPC port.
Note
Obviously the access permissions on this file must be restricted via OS file permissions, such that it can only be read by the NSO daemon and client processes that are allowed to connect to the IPC port. E.g. if both the daemon and the clients run as root, the file can be owned by root and have only "read by owner" permission (i.e. mode 0400). Another possibility is to have a group that only the daemon and the clients belong to, set the group ID of the file to that group, and have only "read by group" permission (i.e. mode 040).
To provide the secret to the client libraries, and inform them
that they need to use the access check handshake, we have to set
the environment variable NCS_IPC_ACCESS_FILE
to the
full pathname of the file containing the secret. This is
sufficient for all the clients mentioned above, i.e. there is no
need to change application code to support or enable this check.
Note
The access check must be either enabled or disabled for both
the daemon and the clients. E.g. if
/ncs-config/ncs-ipc-access-check/enabled
in
ncs.conf
is not set
to "true", but clients are started with the environment
variable NCS_IPC_ACCESS_FILE
pointing to a file
with a secret, the client connections will fail.
The service manager executes in a Java VM outside of NSO.
The NcsMux initializes a number of sockets to NSO at startup.
These are Maapi sockets and data provider sockets.
NSO can choose to close any of these sockets whenever NSO requests
the service manager to perform a task, and that task is not
finished within the stipulated timeout.
If that happens, the service manager must be restarted.
The timeout(s) are controlled by a several ncs.conf parameters
found under /ncs-config/japi
.
NSO requires some privileges to perform certain tasks. The following tasks may, depending on the target system, require root privileges.
-
Binding to privileged ports. The
ncs.conf
configuration file specifies which port numbers NSO should bind(2) to. If any of these port numbers are lower than 1024, NSO usually requires root privileges unless the target operating system allows NSO to bind to these ports as a non-root user. -
If PAM is to be used for authentication, the program installed as
$NCS_DIR/lib/ncs/priv/pam/epam
acts as a PAM client. Depending on the local PAM configuration, this program may require root privileges. If PAM is configured to read the localpasswd
file, the program must either run as root, or be setuid root. If the local PAM configuration instructs NSO to run for example pam_radius_auth, root privileges are possibly not required depending on the local PAM installation. -
If the CLI is used and we want to create CLI commands that run executables, we may want to modify the permissions of the
$NCS_DIR/lib/ncs/lib/core/confd/priv/cmdptywrapper
program.To be able to run an executable as root or a specific user, we need to make cmdptywrapper setuid root, i.e.:
-
# chown root cmdptywrapper
-
# chmod u+s cmdptywrapper
Failing that, all programs will be executed as the user running the ncs daemon. Consequently, if that user is root we do not have to perform the chmod operations above.
The same applies for executables run via actions, but then we may want to modify the permissions of the
$NCS_DIR/lib/ncs/lib/core/confd/priv/cmdwrapper
program instead:-
# chown root cmdwrapper
-
# chmod u+s cmdwrapper
-
NSO can be instructed to terminate NETCONF over clear text TCP. This is useful for debugging since the NETCONF traffic can then be easily captured and analyzed. It is also useful if we want to provide some local proprietary transport mechanism which is not SSH. Clear text TCP termination is not authenticated, the clear text client simply tells NSO which user the session should run as. The idea is that authentication is already done by some external entity, such as an SSH server. If clear text TCP is enabled, it is very important that NSO binds to localhost (127.0.0.1) for these connections.
Client libraries connect to NSO. For example the CDB API
is TCP based and a CDB client connects to NSO. We instruct
NSO which address to use for these connections through the
ncs.conf parameters
/ncs-config/ncs-ipc-address/ip
(default address 127.0.0.1) and
/ncs-config/ncs-ipc-address/port
(default port 4565).
NSO multiplexes different kinds of connections on the same socket (IP and port combination). The following programs connect on the socket:
-
Remote commands, such as e.g. ncs --reload
-
CDB clients.
-
External database API clients.
-
MAAPI, The Management Agent API clients.
-
The ncs_cli program
By default, all of the above are considered trusted. MAAPI clients and ncs_cli should supposedly authenticate the user before connecting to NSO whereas CDB clients and external database API clients are considered trusted and do not have to authenticate.
Thus, since the ncs-ipc-address socket allows full unauthenticated access to the system, it is important to ensure that the socket is not accessible from untrusted networks. However it is also possible to restrict access to this socket by means of an access check, see the section called “Restricting access to the IPC port”.
A common misfeature found on UN*X operating systems is the restriction that only root can bind to ports below 1024. Many a dollar has been wasted on workarounds and often the results are security holes.
Both FreeBSD and Solaris have elegant configuration options to turn this feature off. On FreeBSD:
# sysctl net.inet.ip.portrange.reservedhigh=0
The above is best added to your /etc/sysctl.conf
Similarly on Solaris we can just configure this. Assuming we want to run NSO under a non-root user "ncs". On Solaris we can do that easily by granting the specific right to bind privileged ports below 1024 (and only that) to the "ncs" user using:
# /usr/sbin/usermod -K defaultpriv=basic,net_privaddr ncs
And check the we get what we want through:
# grep ncs /etc/user_attr
ncs::::type=normal;defaultpriv=basic,net_privaddr
Linux doesn't have anything like the above. There are a couple
of options on Linux. The best is to use an auxiliary program
like authbind http://packages.debian.org/stable/authbind
or privbind http://sourceforge.net/projects/privbind/
These programs are run by root. To start ncs under e.g privbind we can do:
# privbind -u ncs /opt/ncs/current/bin/ncs -c /etc/ncs.conf
The above command starts NSO as user ncs and binds to ports below 1024
NSO supports access to all northbound interfaces via IPv6, and
in the most simple case, i.e. IPv6-only access, this is just a
matter of configuring an IPv6 address (typically the wildcard
address "::") instead of IPv4 for the respective agents and
transports in ncs.conf
, e.g.
/ncs-config/cli/ssh/ip
for SSH connections to the CLI, or
/ncs-config/netconf-north-bound/transport/ssh/ip
for SSH
to the NETCONF agent. The SNMP agent configuration is configured via
one of the other northbound interfaces rather than via
ncs.conf
, see
The NSO SNMP Agent
in Northbound APIs
.
For example via the CLI, we
would set 'snmp agent ip' to the desired address. All these
addresses default to the IPv4 wildcard address "0.0.0.0".
In most IPv6 deployments, it will however be necessary to
support IPv6 and IPv4 access simultaneously. This requires that both
IPv4 and IPv6 addresses are configured, typically "0.0.0.0" plus
"::". To support this, there is in addition to the ip
and
port
leafs also a list extra-listen
for each
agent and transport, where additional IP address and port pairs can
be configured. Thus to configure the CLI to accept SSH connections
to port 2024 on any local IPv6 address, in addition to the default
(port 2024 on any local IPv4 address), we can add an
<extra-listen>
section under
/ncs-config/cli/ssh
in
ncs.conf
:
<cli> <enabled>true</enabled> <!-- Use the built-in SSH server --> <ssh> <enabled>true</enabled> <ip>0.0.0.0</ip> <port>2024</port> <extra-listen> <ip>::</ip> <port>2024</port> </extra-listen> </ssh> ... </cli>
To configure the SNMP agent to accept requests to port 161 on any local IPv6 address, we could similarly use the CLI and give the command:
admin@ncs(config)# snmp agent extra-listen :: 161
The extra-listen
list can take any number of
address/port pairs, thus this method can also be used when we want
to accept connections/requests on several specified (IPv4 and/or
IPv6) addresses instead of the wildcard address, or we want to use
multiple ports.