OpenConfig Interfaces - Some Examples

              · · · · · · ·

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:

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:

  1. each model maintains its own list of interfaces, or
  2. 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:

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).