YANG In-Depth

In the previous article we took a quick look at YANG. YANG is a data modeling language which is used by NETCONF to standardize the configuration of network devices. YANG defines how data should “look” by defining the structure of the data and constraints for each data entry.

YANG is quite difficult to read when you are new to it. There are terms and rules unique to YANG itself, so there is a bit of a learning curve. YANG is most similar to XML, especially in terms of its heirarchical nature. In fact YANG actually maps directly to XML.

First we will look at a generic, made-up example of a YANG model to examine the basic nodes types and how YANG enforces constraints on data type. Then we’ll go more in depth with node types and data types and look at real-world examples from IETF models.

If this is your first time looking at YANG, I would highly recommend going through the links at the bottom of this article, and going back-and-forth between reading and watching content. YANG starts to make sense the more you look at it from different perspectives.

Example (Car Inventory)

The following is a YANG model called car-inventory which is used to keep an inventory of used cars for sale on a car lot.

module car-inventory {
  yang-version 1.1;
  namespace 'http://example.com/ns/car-inventory';
  prefix ci;

  container cars {
    list car {
      key inventory-num;

      leaf inventory-num {
        type uint32;
        mandatory true;
      }
      leaf make {
        type string;
        mandatory true;
      }
      leaf model {
        type string;
        mandatory true;
      }
      leaf engine-size {
        type decimal64 {
          fraction-digits 1;
        }
      }
      leaf fuel-type {
        type string;
        mandatory false;
      }
      leaf price {
        type decimal64 {
          fraction-digits 2;
        }
        units sim-dollar;
      }
    }
  }
}

We can use this simple example to explain common Yang statements.

  • module names the YANG model. The module name should be the same as the filename. For example, the file name here should be car-inventory.yang

  • namespace is required for each module. It should be unique for every module. Sometimes you see this referencing a URL but the URL doesn’t have to be reachable via HTTP. It is essentially just a string. Often an organization uses their own domain for the namespace because this gaurantees uniqueness.

  • prefix is the short-hand for the full namespace

  • container groups like items together and defines the heirarchy

  • list defines nodes that are alike. Each car we define will be a node of this list.

  • key defines how to index a node in the list. So if we want to lookup a particular car in this list, we need to pass the inventory-num.

  • Each leaf is basically an attribute of a car. A leaf must be a single value. A leaf can be required or optional. Each leaf has a type, which can be used for validation. If price is not a decimal that has two decimal places, it will be rejected for not conforming to the YANG model.

We can use pyang to print this model in a more readable tree format:

pyang -f tree car-inventory.yang 
module: car-inventory
  +--rw cars
     +--rw car* [inventory-num]
        +--rw inventory-num    uint32
        +--rw make             string
        +--rw model            string
        +--rw engine-size?     decimal64
        +--rw fuel-type?       string
        +--rw price?           decimal64

Notice above that the brackets around inventory-num mean that is it the key for the list cars.

Let’s make this model a little more specific and create better constraints. First we can limit what integers are used for the inventory number. Let’s start at 100 and go no higher than 10,000.

      leaf inventory-num {
        type uint32 {
	 range 100..10000;
        }
        mandatory true;
      }

Let’s also limit the “make” leaf to a set of known car manufacturers, instead of just allowing any string. To do this we create our own type defintion, and enumerate (list) each allowed value.

typedef manufacturer-type {
    type enumeration {
      enum ford;
      enum chevorlet;
      enum dodge;
      enum toyota;
      enum honda;
      enum subaru;
    }
}

...
      leaf make {
        type manufacturer-type;
        mandatory true;
      }

We can also limit the fuel-type to gasoline or diesel.

typedef fuel-type {
    type enumeration {
      enum gasoline;
      enum diesel;
    }
}

...
//To avoid confusion we change the leaf name from "fuel-type" to just "fuel"

      leaf fuel{
        type fuel-type;
        mandatory false;
      }

Putting it all togther we now have this:

module car-inventory {
  yang-version 1.1;
  namespace 'http://example.com/ns/car-inventory';
  prefix ci;
  typedef manufacturer-type {
    type enumeration {
      enum ford;
      enum chevorlet;
      enum dodge;
      enum toyota;
      enum honda;
      enum subaru;
    }
  }
  typedef fuel-type {
    type enumeration {
      enum gasoline;
      enum diesel;
    }
  }
  container cars {
    list car {
      key inventory-num;

      leaf inventory-num {
        type uint32 {
		   range 100..10000;
        }
        mandatory true;
      }
      leaf make {
        type manufacturer-type;
        mandatory true;
      }
      leaf model {
        type string;
        mandatory true;
      }
      leaf engine-size {
        type decimal64 {
          fraction-digits 1;
        }
      }
      leaf fuel {
        type fuel-type;
        mandatory false;
      }
      leaf price {
        type decimal64 {
          fraction-digits 2;
        }
        units sim-dollar;
      }
    }
  }
}

Our YANG module in tree format:

pyang -f tree car-inventory.yang 
module: car-inventory
  +--rw cars
     +--rw car* [inventory-num]
        +--rw inventory-num    uint32
        +--rw make             manufacturer-type
        +--rw model            string
        +--rw engine-size?     decimal64
        +--rw fuel?            fuel-type
        +--rw price?           decimal64

Notice that in the tree output, you don’t see our additional type defintions, but you do see the types have changed next to the associated leafs.

This example hopefully gives you a sense of how YANG models are used and the power of the data type constraints. This allows a YANG model to assert the format of data. The constraints ensure that data conforms correctly to the given model.

YANG Node Types

Let’s now more broadly look at YANG types. We’ll use real world YANG examples as we go through each definition.

Leaf node

A leaf node contains a single value and it must have a specific type.

container sessions {
	description
	  "BFD IP single-hop sessions.";
	list session {
	  key "interface dest-addr";
	  description
		"List of IP single-hop sessions.";
	  leaf interface {
		type if:interface-ref;
		description
		  "Interface on which the BFD session is running.";
	  }
	  leaf dest-addr {
		type inet:ip-address;
		description
		  "IP address of the peer.";
	  }
	  leaf source-addr {
		type inet:ip-address;
		description
		  "Local IP address.";
	  }

This is from ietf-bfd-ip-sh@2021-10-21.yang. We see three leafs here - interface, dest-addr, and source-addr. Each of these can only have a single value. Notice that each has a specific type associated with it. These three leafs are “attirbutes” of a BFD session.

List node

A list contains a sequence of entries. In the example above, session is a list and it contains our three leaf values. A list must have a key entry. This is how each node of the list is indexed. You can have multiple keys but this gets a little more advanced. The idea behind a list node is that you can have multiple entries of the same type. Here we can have multiple BFD sessions, each having an interface, dest-addr, and source-addr.

Container Node

A container contains a grouping of related nodes. It is an organizational YANG type. A container doesn’t itself have any data associated with it, it simply provides organization to the YANG tree. You can think of a container kind of like a file system directory which only has other directories and files within it. It is a parent node in the tree. In the example above, everything is under the container sessions.

You can also nest containers inside each other like this:

container igmp {
      if-feature "feature-igmp";
      description
        "IGMP configuration and operational state data.";
      container global {
        description
          "Global attributes.";
        uses global-config-attributes;
        uses global-state-attributes;
      }
      container interfaces {
        description
          "Containing a list of interfaces.";
        uses interfaces-config-attributes-igmp {
          if-feature "interface-global-config";
          refine "query-interval" {
            default "125";
          }
          refine "query-max-response-time" {
            default "10";
          }
          refine "robustness-variable" {
            default "2";
          }
          refine "version" {
            default "2";
          }
        }

Leaf-list node

A leaf-list node is a seqeunce of leaf nodes. From what I can tell, you always see a single type value under a leaf-list. It means that you can have a list of leafs of this one type.

An example will help. This comes from ietf-igmp-mld@2019-11-01.yang

grouping interface-state-attributes-igmp {
    description
      "Per-interface state attributes for IGMP.";
    uses interface-state-attributes;
    leaf querier {
      type inet:ipv4-address;
      config false;
      mandatory true;
      description
        "The querier address in the subnet.";
    }
    leaf-list joined-group {
      if-feature "intf-join-group";
      type rt-types:ipv4-multicast-group-address;
      config false;
      description
        "The routers that joined this multicast group.";
    }
    list group {
      key "group-address";
      config false;
      description
        "Multicast group membership information
         that joined on the interface.";
      leaf group-address {
        type rt-types:ipv4-multicast-group-address;
        description
          "Multicast group address.";
      }
      uses interface-state-group-attributes;
      leaf last-reporter {
        type inet:ipv4-address;
        description
          "The IPv4 address of the last host that has sent the
           report to join the multicast group.";
      }
      list source {
        key "source-address";
        description
          "List of multicast source information
           of the multicast group.";
        leaf source-address {
          type inet:ipv4-address;
          description
            "Multicast source address in group record.";
        }
        uses interface-state-source-attributes;
        leaf last-reporter {
          type inet:ipv4-address;
          description
            "The IPv4 address of the last host that has sent the
             report to join the multicast source and group.";
        }
        list host {
          if-feature "intf-explicit-tracking";
          key "host-address";
          description
            "List of hosts with the membership for the specific
             multicast source-group.";
          leaf host-address {
            type inet:ipv4-address;
            description
              "The IPv4 address of the host.";
          }
          uses interface-state-host-attributes;
        }
        // list host
      }
      // list source
    }
    // list group
  }
  // interface-state-attributes-igmp

This output is a bit lengthy so let’s focus on the major groupings. I have edited down the YANG model:

grouping interface-state-attributes-igmp {
    leaf querier {
    }
    leaf-list joined-group {
      if-feature "intf-join-group";
      type rt-types:ipv4-multicast-group-address;
      config false;
      description
        "The routers that joined this multicast group.";
    }
    list group {
      key "group-address";
      leaf group-address {
      }
      leaf last-reporter {
      }
      list source {
        key "source-address";
        leaf source-address {
        }
        leaf last-reporter {
        }
        list host {
          key "host-address";
          description
            "List of hosts with the membership for the specific
             multicast source-group.";
          leaf host-address {
          }
        }
        // list host
      }
      // list source
    }
    // list group
  }
  // interface-state-attributes-igmp

This grouping defines IGMP attributes on a per-interface basis. There will be an IGMP querier on the subnet (leaf querier). Each group (list group) will have a group address, last-reporter, source, and list of hosts that signaled membership for the group. Additionally the router itself can join groups on this interface. (Think about how a Cisco router joins 224.0.1.40 by default).

The joined-group is a leaf-list because there can be multiple groups that a router joins on an interface. Each group is the same “type” of value - a multicast group address.

When a list has only one leaf, it can be a leaf-list. When a list has multiple leafs, it is just a list.

Grouping

I believe this isn’t exactly considered a node, but it is important that we examine this YANG statement. A grouping groups together common types that might be reused in a data model. It is almost like a shorthand - instead of defining values every time you use them, over and over again, you can reference the group each time you need that data.

Let’s look at ietf-vrrp@2018-03-13.yang. A grouping called vrrp-common-attributes defines attributes that are used for both IPv4 and IPv6. Then the groupings for vrrp-ipv4-attributes and vrrp-ipv6-attributes can “inherit” the definitions in the common attributes with the uses statement.

grouping vrrp-common-attributes {
    description
      "Group of VRRP attributes common to versions 2 and 3.";

    leaf vrid {
      type uint8 {
        range "1..255";
      }
      description
        "Virtual Router ID (i.e., VRID).";
    }

    leaf version {
      type identityref {
        base vrrp:vrrp-version;
      }
      mandatory true;
      description
        "Version 2 or 3 of VRRP.";
    }

    leaf log-state-change {
      type boolean;
      default "false";
      description
        "Generates VRRP state change messages each time the
         VRRP instance changes state (from 'up' to 'down'
         or 'down' to 'up').";
    }

    container preempt {
      description
        "Enables a higher-priority VRRP backup router to preempt a
         lower-priority VRRP master.";
      leaf enabled {
        type boolean;
        default "true";
        description
          "'true' if preemption is enabled.";
      }
      leaf hold-time {
        type uint16;
        units seconds;
        default 0;
        description
          "Hold time, in seconds, for which a higher-priority VRRP
           backup router must wait before preempting a lower-priority
           VRRP master.";
      }
    }

    leaf priority {
      type uint8 {
        range "1..254";
      }
      default 100;
      description
        "Configures the VRRP election priority for the backup
         virtual router.";
    }

    leaf accept-mode {
      when "derived-from-or-self(current()/../version, 'vrrp-v3')" {
        description
          "Applicable only to version 3.";
      }
      type boolean;
      default "false";
      description
        "Controls whether a virtual router in master state will
         accept packets addressed to the address owner's IPvX address
         as its own if it is not the IPvX address owner.  The default
         is 'false'.  Deployments that rely on, for example, pinging
         the address owner's IPvX address may wish to configure
         accept-mode to 'true'.
         Note: IPv6 Neighbor Solicitations and Neighbor
         Advertisements MUST NOT be dropped when accept-mode
         is 'false'.";
    }
  } // vrrp-common-attributes

  grouping vrrp-ipv4-attributes {
    description
      "Group of VRRP attributes for IPv4.";

    uses vrrp-common-attributes;
  <--- snip --->
  grouping vrrp-ipv6-attributes {
    description
      "Group of VRRP attributes for IPv6.";

    uses vrrp-common-attributes;
  <--- snip --->

YANG Data Types

There are three main data types: built-in (base), common, and custom (derived).

Built-in (Base) types

Yang comes with built-in data types you can use without needing to reference another model. These data types are available for use by default.

Some examples are:

  • int8/16/32/64

    • 8/16/32/64 bit integer

  • uint8/16/32/64

    • unsigned integer

  • decimal64

  • string

  • enumeration

  • boolean

You can assign a leaf any of these types:

 leaf accept-mode {
      when "derived-from-or-self(current()/../version, 'vrrp-v3')" {
        description
          "Applicable only to version 3.";
      }
      type boolean;
  • Here leaf accept-mode is type boolean. Boolean is a built-in data type.

Common data types

These data types are defined by the IETF in RFC 6991. You must reference the ietf-yang-types YANG model when using these data types.

These include:

  • date-and-time

  • timestamp

  • mac-address

  • ipv4-address

  • ipv4-prefix

  • as-number

You can assign a leaf one of these common data types:

module ietf-interfaces {
  import ietf-yang-types {
    prefix yang;
  }
  container interfaces {
    description
      "Interface parameters.";

      leaf last-change {
        type yang:date-and-time;
        config false;
        description
          "The time the interface entered its current operational
           state.  If the current state was entered prior to the
           last re-initialization of the local network management
           subsystem, then this node is not present.";
        reference
          "RFC 2863: The Interfaces Group MIB - ifLastChange";
      }
  • From ietf-interfaces@2018-02-20.yang

It is common to import this YANG module with the prefix yang. Notice that when using these data types you preface the type with yang:. We will see how the import statement works in more detail later in this article.

Derived (Custom) data types

You can also define your own data types, as we did in the very first example in this chapter. When you define a data type you also typically associate a constraint with it. A custom/derived data type uses a typedef statement and uses an existing data type with some type of restriction.

In this example, we create an ACL number type. An ACL in IOS can only be particular numbers (1-199 and 1300-2699).

typedef acl-number-type {
  type int32 {
    range "1..199 | 1300..2699";
  }
}
  • This custom type must be an integer in the range 1 through 199 or 1300 through 2699.

You can also use a pattern statement to specify what characters are allowed on a string. For example, this is the mac-address typedef statement from ietf-yang-types, which is a common data type.

typedef mac-address {
    type string {
      pattern '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}';
    }

As you can see, common data types are really just dervied data types that are defined in the well-known ietf-yang-types module.

XPath

XPath is a query language for XML. Personally, it reminds me of indexing python dictionaries but with an added “find” feature.

Let’s say we have the following python code:

import xmltodict

xml_string = """
<interfaces>
 <interface name="Gi1">
  <ip-address>1.1.1.1</ip-address>
  <netmask>255.255.255.0</netmask>
 </interface>
 <interface name="Gi2">
  <ip-address>2.2.2.2</ip-address>
  <netmask>255.255.255.0</netmask>
 </interface>  
</interfaces>
"""

my_dict = xmltodict.parse(xml_string)

print(my_dict['interfaces']['interface'][0]['ip-address'])
print(my_dict['interfaces']['interface'][1]['ip-address'])

This prints the two IP address values:

$ python3 test.py 
1.1.1.1
2.2.2.2

Compare this to using XPath to print out the same values:

from lxml import etree

xml_string = """
<interfaces>
 <interface name="Gi1">
  <ip-address>1.1.1.1</ip-address>
  <netmask>255.255.255.0</netmask>
 </interface>
 <interface name="Gi2">
  <ip-address>2.2.2.2</ip-address>
  <netmask>255.255.255.0</netmask>
 </interface>  
</interfaces>
"""

tree = etree.fromstring(xml_string)

interface1 = tree.xpath('/interfaces/interface[1]/ip-address')
print(interface1[0].text)

interface2 = tree.xpath('/interfaces/interface[2]/ip-address')
print(interface2[0].text)

Notice that XPath uses an index that starts at 1, and python lists use a zero-based index. However, besides this, the XPath query and the python indexing is very similar.

XPath being a query language is much more powerful than just indexing a given tree. Above we have an absolute XPath, because it starts at the root of the document, denoted by the first / character. If we start the XPath query with two slashes (//) we use a relative XPath. In a relative XPath we can use wildcard characters.

interface1 = tree.xpath('//*/interface[1]/ip-address')
print(interface1[0].text)

interface2 = tree.xpath('//*/interface[2]/ip-address')
print(interface2[0].text)

We can even omit the first asterik altogether, and tell XPath to find the occurance of “interface” wherever it is in the document.

interface1 = tree.xpath('//interface[1]/ip-address')
print(interface1[0].text)

interface2 = tree.xpath('//interface[2]/ip-address')
print(interface2[0].text)

You can also find the particular interface based on the name attribute instead of the index number:

interface1 = tree.xpath('//interface[@name="Gi1"]/ip-address')
print(interface1[0].text)

interface2 = tree.xpath('//interface[@name="Gi2"]/ip-address')
print(interface2[0].text)

RW vs. RO Data

YANG is used for both configuration data (making changes to a device) and operational data (obtaining stats and settings on a device). Operational data is often used in model-driven telemetry which we will see in a future article. In model-driven telemetry, a network device periodically pushes data to a central server, such as interface statistics over time. The pushed data follows a YANG model, and the central server can subscribe to only parts of the YANG model by specifying an XPath filter.

In a YANG model you will frequently see a mix of configurable data and operational data. By default a leaf can be configured. If you see config false, this means everything under that statement is only operational data.

For example, look at this grouping from ietf-isis@2022-10-19.yang

grouping local-rib {
    description
      "Local RIB: RIB for routes computed by the local IS-IS
       routing instance.";
    container local-rib {
      config false;
      description
        "Local RIB.";
      list route {
        key "prefix";
        description
          "Routes.";
        leaf prefix {
          type inet:ip-prefix;
          description
            "Destination prefix.";
        }
        container next-hops {
          description
            "Next hops for the route.";
          list next-hop {
            key "next-hop";
            description
              "List of next hops for the route.";
            leaf outgoing-interface {
              type if:interface-ref;
              description
                "Name of the outgoing interface.";
            }
            leaf next-hop {
              type inet:ip-address;
              description
                "Next-hop address.";
            }
          }
        }
        leaf metric {
          type uint32;
          description
            "Metric for this route.";
        }
        leaf level {
          type level-number;
          description
            "Level number for this route.";
        }
        leaf route-tag {
          type uint32;
          description
            "Route tag for this route.";
        }
      }
    }

Everything under the local-rib container is only operational data. In pyang you see this as ro instead of rw. When the config statement is missing, it is implicitly config true.

Here is that same section in tree format:

pyang -f tree ietf-isis@2022-10-19.yang 
module: ietf-isis

  augment /rt:routing/rt:ribs/rt:rib/rt:routes/rt:route:
    +--ro metric?       uint32
    +--ro tag*          uint64
    +--ro route-type?   enumeration
  augment /if:interfaces/if:interface:
    +--rw clns-mtu?   uint16 {osi-interface}?
  augment /rt:routing/rt:control-plane-protocols/rt:control-plane-protocol:
    +--rw isis
	   <---snip--->
       +--ro local-rib
       |  +--ro route* [prefix]
       |     +--ro prefix       inet:ip-prefix
       |     +--ro next-hops
       |     |  +--ro next-hop* [next-hop]
       |     |     +--ro outgoing-interface?   if:interface-ref
       |     |     +--ro next-hop              inet:ip-address
       |     +--ro metric?      uint32
       |     +--ro level?       level-number
       |     +--ro route-tag?   uint32

Import and Include statements

Import

Import is used to refer to definitions in another YANG module. It pulls in references, but not the body of the file. Common typedefs and groupings and be referred to by importing a YANG module.

As an example, look at the import statements for ietf-isis@2022-10-19.yang

module ietf-isis {
  yang-version 1.1;
  namespace "urn:ietf:params:xml:ns:yang:ietf-isis";
  prefix isis;

  import ietf-routing {
    prefix rt;
    reference
      "RFC 8349: A YANG Data Model for Routing Management
       (NMDA Version)";
  }
  import ietf-inet-types {
    prefix inet;
    reference
      "RFC 6991: Common YANG Data Types";
  }
  import ietf-yang-types {
    prefix yang;
    reference
      "RFC 6991: Common YANG Data Types";
  }
  import ietf-interfaces {
    prefix if;
    reference
      "RFC 8343: A YANG Data Model for Interface Management";
  }
  import ietf-key-chain {
    prefix key-chain;
    reference
      "RFC 8177: YANG Data Model for Key Chains";
  }
  import ietf-routing-types {
    prefix rt-types;
    reference
      "RFC 8294: Common YANG Data Types for the Routing Area";
  }
  import iana-routing-types {
    prefix iana-rt-types;
    reference
      "RFC 8294: Common YANG Data Types for the Routing Area";
  }
  import ietf-bfd-types {
    prefix bfd-types;
    reference
      "RFC 9314: YANG Data Model for Bidirectional Forwarding
       Detection (BFD)";
  }

All of the import statement above reference another YANG module. Only the typedefs and groupings are able to be referenced - the entirety of the file is not included. You use an import statement when you want to use common typedefs and groupings from another module.

Include

An include statement pulls submodules into a main module. It completely pulls in that entire YANG file. You would use this when you split a big module into separate, smaller modules.

As an example, look at ietf-ipv6-unicast-routing@2016-11-04.yang. It pulls in the ietf-ipv6-router-advertisements submodule.

module ietf-ipv6-unicast-routing {

  yang-version "1.1";

  namespace "urn:ietf:params:xml:ns:yang:ietf-ipv6-unicast-routing";

  prefix "v6ur";

  import ietf-routing {
    prefix "rt";
  }

  import ietf-inet-types {
    prefix "inet";
  }

  include ietf-ipv6-router-advertisements {
    revision-date 2016-11-04;
  }

Looking at this submodule (below), we see that it has a different header. The first line is submodule rather than module, and it belongs-to the parent ipv6-uinicast-routing module. The belongs-to statement takes the place of a namespace declaration in a regular module.

submodule ietf-ipv6-router-advertisements {

  yang-version "1.1";

  belongs-to ietf-ipv6-unicast-routing {
    prefix "v6ur";
  }

  import ietf-inet-types {
    prefix "inet";
  }

  import ietf-interfaces {
    prefix "if";
  }

  import ietf-ip {
    prefix "ip";
  }

Must statement

A must statement is another tool to constrain data. It is used when a leaf has some type of relational constraint to another leaf. An example will explain how this works.

This is from ietf-isis@2022-10-19.yang. The YANG author needs some way to make sure than an ISIS interface priority is only applied to an interface if it is in broadcast mode. The interface priority leaf is relationally dependent on the interface-type leaf. If an interface is p2p, interface priority is unnecessary because an interface priority is only used to elect the DIS.

grouping priority-cfg-with-default {
    leaf value {
      type uint8 {
        range "0 .. 127";
      }
      default "64";
      description
        "Priority of the interface for DIS election.";
    }
    description
      "Interface DIS election priority grouping.";
  }
  
  grouping interface-config {
    description
      "Interface configuration grouping.";
    uses admin-control;
	
	<---snip--->
	
    leaf interface-type {
      type interface-type;
      default "broadcast";
      description
        "Type of adjacency to be established for the interface.
         This dictates the type of Hello messages that are used.";
    }
	
	<---snip--->
	
    container priority {
      must '../interface-type = "broadcast"' {
        error-message "Priority only applies to broadcast "
                    + "interfaces.";
        description
          "Checks for a broadcast interface.";
      }
	  uses priority-cfg-with-default;

The grouping priority-cfg-with-default at the top is used for the priority container, with the uses statement on the last line. The must statement uses an XPath filter which goes up one level (denoted by .. just like in linux), and into the interface-type, which must equal broadcast. The interface-type and priority are both under the grouping of interface-config.

Augment statement

An augment statement adds definitions to an existing model. You use this when you want to use a separate module via an import statement but also add some node types to it.

This is an example from ietf-isis@2020-10-19.yang. The ietf-interfaces module is imported. The author wishes to add a leaf called clns-mtu to the module.

import ietf-interfaces {
    prefix if;
    reference
      "RFC 8343: A YANG Data Model for Interface Management";
  }
augment "/if:interfaces/if:interface" {
    leaf clns-mtu {
      if-feature "osi-interface";
      type uint16;
      description
        "Connectionless-mode Network Service (CLNS) MTU of the
         interface.";
    }
    description
      "ISO-specific interface parameters.";
  }

YANG Model Sources

There are two types of YANG models: Industry standard, and native models. Industry standard YANG models are defined by the IETF and the OpenConfig working group. These are meant to be vendor and platform independent. Items such as interfaces, open routing protocols, BFD, etc. should be vendor neutral and be able to apply to any networking equipment. You might ask why the need for both the IETF and OpenConfig to develop industry standard models. Historically IETF has taken longer to produce models, so the OpenConfig working group was created to accelerate the pace of producing industry standard models.

Native YANG models are defined by vendors to support features that are not industry standard. For example, EIGRP and HSRP are Cisco proprietary technologies that the IETF would not write YANG models for. Therefore Cisco must write the YANG models themselves. There are two types of native models: common and platform-specific. Common models can apply to any Cisco device, while platform-specific models only apply to that specific device platform.

On a Cisco device you will see all of these types of models supported: IETF, OpenConfig, native common, and native platform-specifc models.

Yin

The XML representation of a YANG model is called Yin. YANG maps to XML 1-to-1 without loss of any features or defintions. A tool such as pyang can convert a YANG model to Yin, representing the YANG data model in XML format.

As an example, here is the car-inventory.yang file in XML:

$ pyang -f yin car-inventory.yang
<?xml version="1.0" encoding="UTF-8"?>
<module name="car-inventory"
        xmlns="urn:ietf:params:xml:ns:yang:yin:1"
        xmlns:ci="http://example.com/ns/car-inventory">
  <yang-version value="1.1"/>
  <namespace uri="http://example.com/ns/car-inventory"/>
  <prefix value="ci"/>
  <typedef name="manufacturer-type">
    <type name="enumeration">
      <enum name="ford"/>
      <enum name="chevorlet"/>
      <enum name="dodge"/>
      <enum name="toyota"/>
      <enum name="honda"/>
      <enum name="subaru"/>
    </type>
  </typedef>
  <typedef name="fuel-type">
    <type name="enumeration">
      <enum name="gasoline"/>
      <enum name="diesel"/>
    </type>
  </typedef>
  <container name="cars">
    <list name="car">
      <key value="inventory-num"/>
      <leaf name="inventory-num">
        <type name="uint32">
          <range value="100..10000"/>
        </type>
        <mandatory value="true"/>
      </leaf>
      <leaf name="make">
        <type name="manufacturer-type"/>
        <mandatory value="true"/>
      </leaf>
      <leaf name="model">
        <type name="string"/>
        <mandatory value="true"/>
      </leaf>
      <leaf name="engine-size">
        <type name="decimal64">
          <fraction-digits value="1"/>
        </type>
      </leaf>
      <leaf name="fuel">
        <type name="fuel-type"/>
        <mandatory value="false"/>
      </leaf>
      <leaf name="price">
        <type name="decimal64">
          <fraction-digits value="2"/>
        </type>
        <units name="sim-dollar"/>
      </leaf>
    </list>
  </container>
</module>

Further Reading/Watching

https://www.juniper.net/documentation/us/en/software/junos/netconf/topics/concept/netconf-yang-modules-overview.html

https://en.wikipedia.org/wiki/YANG

https://developer.cisco.com/docs/nso/guides/#!nso-5-7-development-guide-the-yang-data- modeling-language/the-yang-data-modeling-language

https://www.youtube.com/watch?v=zy9QA-uU0u4&ab_channel=UltraConfig

https://www.youtube.com/watch?v=b7IumSFInTs&ab_channel=AristaNetworks

  • This video is OK, you could skip it

https://www.youtube.com/watch?v=AdIcYrz3AjU&ab_channel=Tail-fSystems

  • This is the most thorough resource for learning YANG but it is quite dense. I recommend understand some of the basics before watching this, otherwise you will become lost pretty quickly.

https://www.youtube.com/watch?v=23iu0RWZ0UE

  • Part 2 of the video above.

https://www.youtube.com/watch?v=U-MZJ6rbqi4&ab_channel=AutomationStepbyStep

https://www.cbtnuggets.com/blog/technology/networking/native-yang-models-ietf-vs-openconfig-vs-cisco

Last updated