Introducing netlab Plugins
Remember the BGP anycast lab I described in December 2021? In that blog post I briefly mentioned a problem of extraneous IBGP sessions and promised to address it at a later date. Let’s see how we can fix that with a netlab plugin.
We always knew that it’s impossible to implement every nerd knob someone would like to have when building their labs, and extending the tool with Python plugins seemed like the only sane way to go. We added custom plugins to netlab in late 2021, but I didn’t want to write about them because we had to optimize the internal data structures first.
Back to the original challenge. In the BGP anycast lab I wanted to have BGP sessions set up like this:
However, netlab tries to do the right thing and creates IBGP full mesh within an AS1. The configured BGP sessions within my lab looked like this:
The IBGP sessions within AS 65101 were never established as the loopback addresses of A1…A3 were not advertised into BGP and thus the anycast routers had no way to reach each other (there are no direct links between them). The lab worked as expected, but I still didn’t like the results.
a1#show ip bgp summary | begin Neighbor
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
10.0.0.6 4 65101 0 0 1 0 0 never Idle
10.0.0.7 4 65101 0 0 1 0 0 never Idle
10.1.0.14 4 65000 30 25 7 0 0 00:20:05 4
I could see three solutions to that conundrum:
- Remain unhappy with the way the lab works and move on. Not an option.
- Implement a bgp.peering.ibgp (or something similar) nerd knob to enable/disable IBGP sessions. Doable, but we’d have to change all device configuration templates and forever keep track of which devices support this nerd knob. Sounds like a lot of technical debt to solve an edge case.
- Write a plugin that removes unneeded IBGP sessions once the lab topology transformation is complete.
The plugin turned out to be remarkably simple. I imported Python Box2 and netlab API modules3.
from box import Box
from netsim import api
Next, I added anycast attribute to the list of allowed BGP node attributes in the plugin initialization code. I could do that in the topology file, but I wanted to end with a self-contained module (more about that in a week or so).
def init(topo: Box) -> None:
topo.defaults.bgp.attributes.node.anycast = { 'type': 'ipv4', 'use': 'prefix' }
- Python Box module makes deeply nested dictionaries a pleasure to work with – imagine having to do the same with traditional hodgepodge of square brackets and quotes.
- BGP anycast attribute is explained in the Building a BGP Anycast Lab blog post.
Finally, I wanted to modify the lists of BGP neighbors once the topology transformation has been complete. post_transform seemed the perfect hook to use, and all I had to do was to:
- Find the nodes with bgp.anycast attribute
- Set bgp.advertise_loopback to False (so I can further simplify the topology file)
- Keep only those BGP neighbors where the BGP session type is not
ibgp
def post_transform(topo: Box) -> None:
for name,node in topo.nodes.items():
if 'anycast' in node.get('bgp',{}):
node.bgp.advertise_loopback = False
node.bgp.neighbors = [ n for n in node.bgp.neighbors if n.type != 'ibgp' ]
Let’s unpack that code:
- All plugin hooks are called with a single parameter – current lab topology. When the init hook is called, you’ll be working with the original topology definition, at the post_transform stage the data model has been extensively modified.
- Lab devices are described in the nodes dictionary with the lab topology.
- Each node is a dictionary that contains numerous parameters, including configuration module settings (another dictionary).
- Python Box module is a wonderful tool when you need to traverse deep hierarchies, but it does have a few side effects. With the default netlab settings, Python Box automatically creates empty dictionaries when needed. That’s awesome in 90% of the cases, but sometimes I don’t want to get extra dictionaries, so I have to be a bit careful – instead of
if node.bgp.anycast:
(which would work even if the node had no BGP parameters), I decided to do a check that would never auto-create empty data structures. - If the node.bgp.anycast attribute is set, it’s safe to work with BGP parameters, so we can assume that we can set advertise_loopback and that the node has a list of BGP neighbors in node.bgp.neighbors (even if that list happens to be empty).
- Every BGP session described in node.bgp.neighbors includes session type in type attribute, and the value of that parameter could be
ibgp
orebgp
. The list comprehension I used selects all list elements that are not IBGP sessions.
Want to do something similar? It’s not too hard once understand the netlab data structures. Here’s how you can get there:
- Build a lab topology
- Run
netlab create -o yaml
to get the final data model. You can also limit the output to individual components of the data model. - Explore the data structures and figure out what needs to be modified.
You can also open a discussion in netlab GitHub repository or ask a question in netlab channel in networktocode Slack team and we’ll do our best to help you.
Revision History
- 2023-03-02
- Updated the plugin code to work with netlab release 1.5.