OpenConfig Interfaces - Some Examples
imported Code · Tech · Work · ISP · IETF · python · OpenConfig · YANG
I’ve talked a little on this site before about what we’re trying to achieve with OpenConfig. However, one of the observations that it’s easy to make is that YANG models alone don’t really achieve anything in terms of making the network more programmable. To make the network more programmable, we need to have tooling that helps us create instances of those modules, manipulate them, and then serialise the into a format that can be used to transmit data that conforms to the model to a device.
It might not be immediately clear what I mean here so, let me explain a little more. A YANG model is a definition of the schema for a set of data. It tells a system the rules that lets it validate whether particular data is valid or not. If we have a particular ‘leaf’ value that is specified to have a type
of string, and ‘pattern’ of a.*
, then all this tells the system is that the value must be a string, and it must start with the letter “a”. To do anything useful with that schema, we need to be able to create instances of the data - that is to say documents that contain actual values that are compliant when validated against the schema. We can’t express these documents in YANG, so we need an encoding for that data. NETCONF uses XML, but more recently there are approaches that are using JSON, and YAML.
We could create these documents by hand, but this would be akin to writing out CLI config, so we want to programatically create them. This raises one of the fundamentals around YANG:
- The reader of a YANG module is a machine - which takes the schema it describes - preferably including the human-readable
description
parts - and converts it into something that a program can manipulate.
This is what pyangbind - and the goyang project that the awesome folks over at Google published recently do. They allow you to programmatically interact with, or validate, data against a YANG model.
OpenConfig has recently pushed quite a few more models. Including one that covers interfaces. I wanted to take a little time to show how this module works, and how current configurations map into this model. I’m going to use some Cisco, Alcatel-Lucent and Juniper configuration snippets to show this off. These are taken from real networks, but they are not copy-and-pastes. I’ll focus on what the openconfig-interfaces
output looks like in JSON, and then mention a little about how the instance documents are generated.
Cisco IOS XR: A core-facing IP/MPLS port.
If we consider a core-facing port that has the following IOS XR configuration:
interface TenGigE0/4/2/0
description type=eth:cid=1042:remote=P2#Te0/0/0
mtu 9188
ipv4 address 192.0.2.100 255.255.255.254
service-policy input CORE__IN
service-policy output CORE_OUT
carrier-delay up 2000 down 0
The first thing that we should note is not all this configuration is actually pure interface configuration. The service-policy
statements are really QoS configuration that happens to be instantiated on the interface. This raises an important point about how configuration data is structured. For this kind of configuration, either:
- each model maintains its own list of interfaces, or
- each model ‘augments’ (adds configuration options to) the existing interfaces model,
In the openconfig-interfaces
approach, currently, for those elements that relate to behaviour directly of the interface (and not behaviour of a protocol when it establishes an adjacency over the interface), option 2 is taken. Thus, the base interfaces module only has physical characteristics defined, whereas IP addressing is defined in an extension openconfig-if-ip
module.
When build this configuration according to the OpenConfig interfaces modules, a JSON serialised instance of the data would look like this:
{
"interfaces": {
"interface": {
"TenGigE0/4/2/0": {
"hold-time": {
"config": {
"up": 2000
}
},
"config": {
"description": "type=eth:cid=1042:remote=P2#Te0/0/0",
"name": "TenGigE0/4/2/0",
"mtu": 9188
},
"name": "TenGigE0/4/2/0",
"subinterfaces": {
"subinterface": {
"4": {
"index": "4",
"config": {
"index": 4,
"description": "autogen=default-ipv4-subint"
},
"ipv4": {
"address": {
"192.0.2.100": {
"ip": "192.0.2.100",
"config": {
"ip": "192.0.2.100",
"prefix-length": 31
}
}
}
}
}
}
}
}
}
}
}
One could comment that it is somewhat longer than the XR configuration, but partially that is because of the JSON encoding adding curly-braces. The core layout of the config is familiar. There is a list of interfaces, which subsequently has other configuration options within it, which can be set as per the original configuration. The QoS configuration, which isn’t yet modelled within the OpenConfig model set, is omitted.
Juniper JUNOS: An access-facing port with a IPv4 and IPv6 subinterface specified.
On a Juniper device, we might have a port that faces an a customer, where we use 802.1q encapsulation. In this case, we might configure two subinterfaces (unit
constructs), such that one carries IPv4 and the other carries IPv6 traffic. In this case, the JUNOS config might look like:
ge-0/1/10 {
description “CustomerA”;
vlan-tagging;
mtu 4484;
hold-time up 4000 down 0;
gigether-options {
no-auto-negotiation;
}
unit 3044 {
description "CustomerA-IPv4";
vlan-id 3044;
family inet {
mtu 4000;
address 192.0.2.0/31;
}
}
unit 3046 {
description "CustomerA-IPv4";
vlan-id 3046;
family inet6 {
mtu 4000;
address 2001:DB8::1/112;
}
}
}
In this case, this config maps to the following instance of openconfig-interfaces
:
{
"interfaces": {
"interface": {
"ge-0/1/10": {
"hold-time": {
"config": {
"up": 4000
}
},
"config": {
"description": "CustomerA",
"name": "ge-0/1/10",
"mtu": 4484
},
"name": "ge-0/1/10",
"subinterfaces": {
"subinterface": {
"3044": {
"index": "3044",
"vlan": {
"config": {
"vlan-id": 3044
}
},
"config": {
"index": 3044,
"description": "CustomerA-IPv4"
},
"ipv4": {
"config": {
"mtu": 4000
},
"address": {
"192.0.2.0": {
"ip": "192.0.2.0",
"config": {
"ip": "192.0.2.0",
"prefix-length": 31
}
}
}
}
},
"3046": {
"index": "3046",
"vlan": {
"config": {
"vlan-id": 3046
}
},
"config": {
"index": 3046,
"description": "CustomerA-IPv6"
},
"ipv6": {
"config": {
"mtu": 4000
},
"address": {
"2001:db8::1": {
"ip": "2001:db8::1",
"config": {
"ip": "2001:db8::1",
"prefix-length": 64
}
}
}
}
}
}
}
}
}
}
}
A couple of things should be mentioned here:
- In
openconfig-interfaces
the default is that ‘auto-negotiation’ is disabled, so we don’t need to specify this (the leaf is an empty leaf, so auto-negotiation is only turned on if we have this leaf present in our schema). - Based on the approach that OpenConfig is taken for op-state, we have
config
containers where we store all read-write configuration. Whilst it might look like there’s some duplication of configuration options, the elements that are directly in the list objects (and act as their key) are actuallyleafref
values, so they simply reflect the value of the corresponding leaf within theconfig
container. - Since the hold-time for the interface going down is zero in the input (and really, it’s just there because JUNOS makes us specify it), then the tooling can specify that it does not want to introduce this value into the OpenConfig model instance - and hence it is omitted from this JSON document.
- Because the OpenConfig models are mapped to a vendor’s internal schema, where there is no real operational advantage of including a leaf, it can be omitted. For example, consider
vlan-tagging
in the above JUNOS configuration. The fact that there are two sub-interfaces that have 802.1q configuration on them means that we should expect that this interface is 802.1q tagged, and hence in the JUNOS mapping for this module, it should be possible to determine from this that the JUNOS config database’svlan-tagging
flag should be set to ‘true’.
Cisco IOS: A switch with a trunk port carrying multiple VLANs, and SVI interfaces.
OpenConfig interfaces isn’t just applicable to routed ports, it’s also applicable to switched interfaces that can carry multiple VLANs (trunks), or single VLANs (access) ports. It also covers cases where the same device has a Layer 3 interface, within the VLAN that it is switching (a Cisco SVI). If we consider the following IOS configuration:
interface GigabitEthernet3/20
description core-switch#Gig2/47
switchport
switchport trunk encapsulation dot1q
switchport trunk allowed vlan 2,69,243,282-292,388-397,559
switch port mode trunk
!
interface Vlan2
ip address 10.0.4.3 255.255.255.0
standby 2 ip 10.0.4.1
standby 2 priority 210
standby 2 preempt
!
interface Vlan388
ip address 10.0.5.3 255.255.255.240
standby 15 ip 10.100.5.19
standby 15 priority 210
standby 15 preempt
The configuration maps to the following openconfig-interfaces
instance:
{
"interfaces": {
"interface": {
"VLAN388": {
"config": {
"name": "VLAN388"
},
"name": "VLAN388",
"routed-vlan": {
"config": {
"vlan": 388
},
"ipv4": {
"address": {
"10.0.5.3": {
"ip": "10.0.5.3",
"vrrp": {
"vrrp-group": {
"15": {
"config": {
"priority": 210,
"virtual-address": [
"10.0.5.19"
],
"virtual-router-id": 15
},
"virtual-router-id": "15"
}
}
},
"config": {
"ip": "10.0.5.3",
"prefix-length": 28
}
}
}
}
}
},
"VLAN2": {
"config": {
"name": "VLAN2"
},
"name": "VLAN2",
"routed-vlan": {
"config": {
"vlan": 2
},
"ipv4": {
"address": {
"10.0.4.3": {
"ip": "10.0.4.3",
"vrrp": {
"vrrp-group": {
"2": {
"config": {
"priority": 210,
"virtual-address": [
"10.0.4.1"
],
"virtual-router-id": 2
},
"virtual-router-id": "2"
}
}
},
"config": {
"ip": "10.0.4.3",
"prefix-length": 24
}
}
}
}
}
},
"GigabitEthernet3/20": {
"ethernet": {
"vlan": {
"config": {
"trunk-vlans": [
2,
69,
243,
"282..292",
"388..397",
559
],
"interface-mode": "TRUNK"
}
}
},
"config": {
"description": "core-switch#Gig2/47",
"name": "GigabitEthernet3/20"
},
"name": "GigabitEthernet3/20"
}
}
}
}
Since the OpenConfig model is structured such that an interface with an IP address must always have a subinterface (as can be seen in the Cisco XR example above), then our config instance generates a ‘default’ subinterface that carries the IPv4 address that is specified, along with the associated VRRP groups.
This example also shows how the OpenConfig model handles VLAN trunks. A Layer 2 interface can have a certain interface-mode
set, which reflects whether it will carry 802.1q tagged traffic (or indeed double-tagged QinQ traffic) - and subsequently can have a list of VLANs specified. This list supports range values - which are marked as-per common YANG syntax as lower..upper
. It is debatable whether such ranges are really needed if one is configuring the device through a programatic means (does it matter if we have all VLANs in a range specified separately?), but these are supported for cases where this may be advantageous - for example, if VLANs 10-3000 are required. Where ranges are used, it is the application that generates the document’s job to determine how to split them up if it is later required.
Routed VLANs are also treated as a special interface type, with the routed_vlan
container only being configurable when the type of the interface is set to the IANA identity value of l3ipvlan
, which indicates a Layer 3 interfaces within a VLAN. This type may be set by the user based on a certain naming of interface, or implied by the device.
Alcatel-Lucent SROS: A mixed-mode port carrying a routed subinterface and a L2 VLAN.
Alcatel-Lucent’s configuration is perhaps the least natural to map to the OpenConfig interface’s model structure. This is essentially because of the way that SROS structures itself around services (it is ‘service-centric’) for interface configuration in general. There has been much debate as to whether such a ‘service centric’ approach (referred to a ‘VRF-centric’ in the IETF), or a ‘protocol-centric’, or even ‘interface-centric’ view of the world should be taken for YANG modelling. OpenConfig is in general trying to adopt an approach where configuration that relates directly to how an interface works (including IP on that interface) is specified in the interface structure. Protocols that might add other functions onto an interface maintain their own list of the interfaces to add other new, non-IP protocols.
ALU’s use in L2 and L3VPN networks makes it quite common to have mixed-mode configuration on an interface. For example, a routed subinterface terminated into a VPRN (L3VPN) service, with a L2 PWE service that sits alongside it (using the same port).
If we consider the following configuration:
port 2/2/2
description "cust=CustA:v=X~2"
ethernet
mode access
encap-type qinq
mtu 9112
hold-time up 2 down 2
no autonegotiate
exit
no shutdown
exit
epipe 3599 customer 1 create
sap 2/2/2:15.* create
description "epipe-svc=3599"
exit
...
exit
vprn 3791 customer 2 create
interface "custA" create
description "t=infra:l3mgmt"
address "192.0.2.1/30"
sap 2/2/2:1000.100 create
description "vprn-svc=3791:
exit
exit
exit
In this case, to map the Alcatel-Lucent configuration, we need to mix the L2 functions of the OpenConfig model with the L3 ones. That is to say, we create a “TRUNK” port that supports the Layer 2 switched VLAN - as per SAP 2/2/2:15.*
; and a subsequent OpenConfig subinterface
that supports the 2/2/2:1000.100
SAP:
{
"interfaces": {
"interface": {
"2/2/2": {
"ethernet": {
"vlan": {
"config": {
"trunk-vlans": [
"15.*"
],
"interface-mode": "TRUNK"
}
}
},
"hold-time": {
"config": {
"down": 2000,
"up": 2000
}
},
"config": {
"description": "cust=CustA:v=X~2",
"name": "2/2/2",
"mtu": 9112
},
"name": "2/2/2",
"subinterfaces": {
"subinterface": {
"3791": {
"index": "3791",
"vlan": {
"config": {
"vlan-id": "1000.100"
}
},
"config": {
"index": 3791,
"description": "custA"
},
"ipv4": {
"address": {
"192.0.2.1": {
"ip": "192.0.2.1",
"config": {
"ip": "192.0.2.1",
"prefix-length": 30
}
}
}
}
}
}
}
}
}
}
}
In this case, the trunk-vlans
configuration lists the VLANs that are to be switched, and the subinterface
construct contains a vlan-id
statement indicating that the frames received with this tag should be routed to the subinterface, or egress frames should be tagged with this value. This hybrid case, allows the same approach to having mixed services as the above ALU SROS config.
Some conclusions…
It’s certainly possible to map a bunch of different types of interface config to the OpenConfig interfaces model, and cover a number of use cases for interfaces whilst doing this. However, it’s of course still a pain if one needs to create this by hand. This is where the tooling comes in. The examples above were taken by mapping a JSON-based input to the model, through some relatively simple (c.80 lines) Python. Whilst in this case, it was a simple example input that has close to 1:1 mapping of data, this input could be some specifications as to which type of interface ‘service’ should be created, such that it is possible to request such an instance of that service and have it automatically mapped to the relevant interface configuration. The intent of pyangbind, and similar tools, it to give a means by which input instances of a model can be loaded into a hiearchy that can be manipulated, and such transformations to OpenConfig instances performed. I’ll add some examples of how this can be done when I get the time to blog again!
However, coming back to the key point of this post, I think we’re making good progress with the OpenConfig models, and we are, of course, still iterating on them. I’m particularly keen to hear from more operators on their use cases, to ensure that OpenConfig is usable to as many folks as possible. Questions, comments, queries or other correspondance to the usual address (rjs@rob.sh).