Introduction
In this tutorial, we will introduce many YANG concepts by developing a
module to configure and monitor a DHCP server. For this task, we will
base our data model on the configuration of the ISC DHCP reference
implementation. For the sake of a simpler example, we will model a
subset of the complete configuration only.
We will model a few global parameters, subnets and shared-networks,
and for monitoring purposes a list of active leases.
An example dhcp.conf file of what we want to model:
# Sample configuration file for ISC dhcpd
default-lease-time 600;
max-lease-time 7200;
subnet 10.254.239.0 netmask 255.255.255.224 {
range dynamic-bootp 10.254.239.10 10.254.239.20;
option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
max-lease-time 1200;
}
shared-network 224-29 {
subnet 10.17.224.0 netmask 255.255.255.0 {
range 10.17.224.10 10.17.224.250;
option routers rtr-224.example.org;
}
subnet 10.0.29.0 netmask 255.255.255.0 {
range 10.0.29.10 10.0.29.230;
option routers rtr-29.example.org;
}
}
The module header
A YANG module starts with a couple of mandatory statements. First, we
have to give a unique name to our module so that others can refer to
it. Second, we have to define an XML namespace URI for the module.
There is a one-to-one mapping between the module name and the
namespace name. The module name is used to identify the module in
other YANG modules, and the namespace URI is used to identify the
module in XML.
module dhcp {
namespace "http://yang-central.org/ns/example/dhcp";
prefix dhcp;
We also declare a short prefix for the module. It is
used as a hint to other module developers when they import our
module. To see an example of this, we use the import statement to
import the standard YANG modules:
import ietf-yang-types { prefix yang; }
import ietf-inet-types { prefix inet; }
Note the prefixes above. In order to refer to the yang-module from
now on, we use the prefix, e.g. the statement:
type yang:date-and-time;
refers to the
date-and-time
type defined in the yang-types module.
We use the prefix defined in the module itself, e.g. in the yang-types
module, the prefix is defined as
yang
. You can use which prefix you
want in your import, as long as it is unique within the module, but
by using the prefix from the module, your module will be easier to
read for others.
Next, we also have to add two more administrative statements:
organization
"yang-central.org";
description
"Partial data model for DHCP, based on the config of
the ISC DHCP reference implementation.";
Data definitions
Now we can start defining the data. First, at the top-level we add a
structural
container 'dhcp'. This container is strictly speaking not
necessary, but it is considered good modeling practise to group all
data definitions in a module under a single container. This makes
NETCONF filtering simpler to use because you can get all data from one
module with a single filter element.
container dhcp {
description
"configuration and operational parameters for a DHCP server.";
Next we have two global parameters, max-lease-time and
default-lease-time. We use the
leaf statement to define them:
leaf max-lease-time {
type uint32;
units seconds;
default 7200;
}
leaf default-lease-time {
type uint32;
units seconds;
default 600;
}
Now we need to model subnets and shared-networks. We note that a
shared-network also can contain subnets. Thus it makes sense to
define the subnets as a resuable structure. We do this with the
grouping statement. Since there can be zero or more subnets, we
define them as a
list in YANG:
grouping subnet-list {
description "A reusable list of subnets";
list subnet {
Each subnet is identified by its ip and mask. In the YANG standard
module inet-types, there is a type ip-prefix which can be used instead
of using a separate ip and mask. We define the ip-prefix as a leaf,
and declare that this leaf is the list's
key:
key net;
leaf net {
type inet:ip-prefix;
}
A subnet has an optional range definition. If a range is given, it
means that the clients on the subnet get addresses dynamically. If a
range specification is not given, the clients have to be configured
statically. A range has a dynamic-bootp flag, and a low and high
address. We model the optional range structure as a container with a
presence statement:
container range {
presence "enables dynamic address assignment";
leaf dynamic-bootp {
type empty;
description
"Allows BOOTP clients to get addresses in this range";
}
leaf low {
type inet:ip-address;
mandatory true;
}
leaf high {
type inet:ip-address;
mandatory true;
}
}
The presence statement means that if the
range
container is present
in the configuration datastore, then dynamic address assignment is
enabled.
The
dynamic-bootp
leaf is of type empty. This means that the
existance of leaf carries all meaning; there is no additional value to
the leaf.
Both the leaf
low
and
high
are marked as
mandatory. This means
that if the optinal
range
container exists in the data store, then
both
low
and
high
must exist in order for validation to succeed.
Each subnet can also specify some DHCP protocol related options, such
as which routers the clients should use, and the domain-name of the
subnet. We collect all these options under a container
dhcp-options
:
container dhcp-options {
description "Options in the DHCP protocol";
leaf-list router {
type inet:host;
ordered-by user;
reference "RFC 2132, sec. 3.8";
}
leaf domain-name {
type inet:domain-name;
reference "RFC 2132, sec. 3.17";
}
}
Note that the container
dhcp-options
does not have a presence
statement. This means that the container is there only to provide
structure to the data model. The container itself doesn't carry any
meaning. We could as well have defined all options,
router
and
domain-name
, directly under the
subnet
node.
The list of routers that is sent to the clients are sent in order of
preference. I.e. a client that receives a list of routers from a DHCP
server, should try with the first one, then the second one and so on.
This concept is supported in YANG, by declaring that a list or
leaf-list is
ordered-by user
. For such a list, the order of the
list entries is semantically important. For other lists, such as the
subnet
list we are defining, the order has no semantic meaning, and
thus this list is not ordered-by user.
Also note that we model the list of routers as a
leaf-list. A
leaf-list is like an array of a simple type, which is what is needed
here.
Finally, each subnet may define it's own
max-lease-time
, which
overrides the global one. Thus, we add a leaf to the subnet:
leaf max-lease-time {
type uint32;
units seconds;
default 7200;
}
Now we have a reusable grouping subnet-list which can be used in the
dhcp
container and in the
shared-network
list:
container dhcp {
description
"configuration and operational parameters for a DHCP server.";
leaf max-lease-time { ... }
leaf default-lease-time { ... }
uses subnet-list;
container shared-networks {
list shared-network {
key name;
leaf name { type string; }
uses subnet-list;
}
}
}
Adding integrity constraints
Even in this simple data model, there are several semantic integrity
constraints that must hold in order for a configuration to be valid.
For example, the default-lease-time must never be larger than the
max-lease-time.
YANG supports adding formal integrity constraints with the
must
statement. As an example, we can add a must constraint to the
default-lease-time:
leaf default-lease-time {
type uint32;
units seconds;
must 'current() <= ../max-lease-time' {
error-message
"The default-lease-time must be less than max-lease-time";
}
default 600;
}
Note how we also give an error-message to the constraint. If a
NETCONF validation fails due to this constraint, this error-message
string will be used by the server in the <rpc-error>.
Operational parameters
At any given time, a DHCP server has a set of active leases. This is
not configuration data, but we can define this in YANG as operational
data. We do this by adding a container
status
under the
dhcp
container, and mark it as being non-configuration:
container status {
config false;
When a container is marked as non-config, all its children inherits
this property, so we don't have to specify this on all leafs.
The active leases is a list with the leased ip address as key, the
start and stop time, and the client's MAC address and type of
hardware:
list leases {
key address;
leaf address { type inet:ip-address; }
leaf starts { type yang:date-and-time;}
leaf ends { type yang:date-and-time; }
container hardware {
leaf type {
type enumeration {
enum "ethernet";
enum "token-ring";
enum "fddi";
}
}
leaf address { type yang:phys-address; }
}
}
Complete module
module dhcp {
namespace "http://yang-central.org/ns/example/dhcp";
prefix dhcp;
import ietf-yang-types { prefix yang; }
import ietf-inet-types { prefix inet; }
organization
"yang-central.org";
description
"Partial data model for DHCP, based on the config of
the ISC DHCP reference implementation.";
container dhcp {
description
"configuration and operational parameters for a DHCP server.";
leaf max-lease-time {
type uint32;
units seconds;
default 7200;
}
leaf default-lease-time {
type uint32;
units seconds;
must 'current() <= ../max-lease-time' {
error-message
"The default-lease-time must be less than max-lease-time";
}
default 600;
}
uses subnet-list;
container shared-networks {
list shared-network {
key name;
leaf name {
type string;
}
uses subnet-list;
}
}
container status {
config false;
list leases {
key address;
leaf address {
type inet:ip-address;
}
leaf starts {
type yang:date-and-time;
}
leaf ends {
type yang:date-and-time;
}
container hardware {
leaf type {
type enumeration {
enum "ethernet";
enum "token-ring";
enum "fddi";
}
}
leaf address {
type yang:phys-address;
}
}
}
}
}
grouping subnet-list {
description "A reusable list of subnets";
list subnet {
key net;
leaf net {
type inet:ip-prefix;
}
container range {
presence "enables dynamic address assignment";
leaf dynamic-bootp {
type empty;
description
"Allows BOOTP clients to get addresses in this range";
}
leaf low {
type inet:ip-address;
mandatory true;
}
leaf high {
type inet:ip-address;
mandatory true;
}
}
container dhcp-options {
description "Options in the DHCP protocol";
leaf-list router {
type inet:host;
ordered-by user;
reference "RFC 2132, sec. 3.8";
}
leaf domain-name {
type inet:domain-name;
reference "RFC 2132, sec. 3.17";
}
}
leaf max-lease-time {
type uint32;
units seconds;
default 7200;
}
}
}
}
XML snippet
The following is an XML snippet which contains the same data as the
dhcp.conf file above, matching the YANG module:
<dhcp xmlns="http://yang-central.org/ns/example/dhcp">
<max-lease-time>7200</max-lease-time>
<default-lease-time>600</default-lease-time>
<subnet>
<net>10.254.239.0/27</net>
<range>
<dynamic-bootp/>
<low>10.254.239.10</low>
<high>10.254.239.20</high>
</range>
<dhcp-options>
<router>rtr-239-0-1.example.org</router>
<router>rtr-239-0-2.example.org</router>
</dhcp-options>
<max-lease-time>1200</max-lease-time>
</subnet>
<shared-networks>
<shared-network>
<name>224-29</name>
<subnet>
<net>10.17.224.0/24</net>
<range>
<low>10.17.224.10</low>
<high>10.17.224.250</high>
</range>
<dhcp-options>
<router>rtr-224.example.org</router>
</dhcp-options>
</subnet>
<subnet>
<net>10.0.29.0/24</net>
<range>
<low>10.0.29.10</low>
<high>10.0.29.230</high>
</range>
<dhcp-options>
<router>rtr-29.example.org</router>
</dhcp-options>
</subnet>
</shared-network>
</shared-networks>
</dhcp>
--
MartinBjoerklund - 12 Nov 2007