Compare commits
6 Commits
4aa5745d06
...
main
Author | SHA1 | Date | |
---|---|---|---|
fdb77838b8 | |||
6d3f4ac206 | |||
baa3e78045 | |||
0972cf4aa1 | |||
4f81d377a0 | |||
153048eda4 |
@ -89,7 +89,7 @@ lcp lcp-sync off
|
|||||||
```
|
```
|
||||||
|
|
||||||
The prep work for the rest of the interface syncer starts with this
|
The prep work for the rest of the interface syncer starts with this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/2d00de080bd26d80ce69441b1043de37e0326e0a)], and
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/2d00de080bd26d80ce69441b1043de37e0326e0a)], and
|
||||||
for the rest of this blog post, the behavior will be in the 'on' position.
|
for the rest of this blog post, the behavior will be in the 'on' position.
|
||||||
|
|
||||||
### Change interface: state
|
### Change interface: state
|
||||||
@ -120,7 +120,7 @@ the state it was. I did notice that you can't bring up a sub-interface if its pa
|
|||||||
is down, which I found counterintuitive, but that's neither here nor there.
|
is down, which I found counterintuitive, but that's neither here nor there.
|
||||||
|
|
||||||
All of this is to say that we have to be careful when copying state forward, because as
|
All of this is to say that we have to be careful when copying state forward, because as
|
||||||
this [[commit](https://github.com/pimvanpelt/lcpng/commit/7c15c84f6c4739860a85c599779c199cb9efef03)]
|
this [[commit](https://git.ipng.ch/ipng/lcpng/commit/7c15c84f6c4739860a85c599779c199cb9efef03)]
|
||||||
shows, issuing `set int state ... up` on an interface, won't touch its sub-interfaces in VPP, but
|
shows, issuing `set int state ... up` on an interface, won't touch its sub-interfaces in VPP, but
|
||||||
the subsequent netlink message to bring the _LIP_ for that interface up, **will** update the
|
the subsequent netlink message to bring the _LIP_ for that interface up, **will** update the
|
||||||
children, thus desynchronising Linux and VPP: Linux will have interface **and all its
|
children, thus desynchronising Linux and VPP: Linux will have interface **and all its
|
||||||
@ -128,7 +128,7 @@ sub-interfaces** up unconditionally; VPP will have the interface up and its sub-
|
|||||||
whatever state they were before.
|
whatever state they were before.
|
||||||
|
|
||||||
To address this, a second
|
To address this, a second
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/a3dc56c01461bdffcac8193ead654ae79225220f)] was
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/a3dc56c01461bdffcac8193ead654ae79225220f)] was
|
||||||
needed. I'm not too sure I want to keep this behavior, but for now, it results in an intuitive
|
needed. I'm not too sure I want to keep this behavior, but for now, it results in an intuitive
|
||||||
end-state, which is that all interfaces states are exactly the same between Linux and VPP.
|
end-state, which is that all interfaces states are exactly the same between Linux and VPP.
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ DBGvpp# set int state TenGigabitEthernet3/0/0 up
|
|||||||
### Change interface: MTU
|
### Change interface: MTU
|
||||||
|
|
||||||
Finally, a straight forward
|
Finally, a straight forward
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/39bfa1615fd1cafe5df6d8fc9d34528e8d3906e2)], or
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/39bfa1615fd1cafe5df6d8fc9d34528e8d3906e2)], or
|
||||||
so I thought. When the MTU changes in VPP (with `set interface mtu packet N <int>`), there is
|
so I thought. When the MTU changes in VPP (with `set interface mtu packet N <int>`), there is
|
||||||
callback that can be registered which copies this into the _LIP_. I did notice a specific corner
|
callback that can be registered which copies this into the _LIP_. I did notice a specific corner
|
||||||
case: In VPP, a sub-interface can have a larger MTU than its parent. In Linux, this cannot happen,
|
case: In VPP, a sub-interface can have a larger MTU than its parent. In Linux, this cannot happen,
|
||||||
@ -179,7 +179,7 @@ higher than that, perhaps logging an error explaining why. This means two things
|
|||||||
1. Any change in VPP of a parent MTU should ensure all children are clamped to at most that.
|
1. Any change in VPP of a parent MTU should ensure all children are clamped to at most that.
|
||||||
|
|
||||||
I addressed the issue in this
|
I addressed the issue in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/79a395b3c9f0dae9a23e6fbf10c5f284b1facb85)].
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/79a395b3c9f0dae9a23e6fbf10c5f284b1facb85)].
|
||||||
|
|
||||||
### Change interface: IP Addresses
|
### Change interface: IP Addresses
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ VPP into the companion Linux devices:
|
|||||||
_LIP_ with `lcp_itf_set_interface_addr()`.
|
_LIP_ with `lcp_itf_set_interface_addr()`.
|
||||||
|
|
||||||
This means with this
|
This means with this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/f7e1bb951d648a63dfa27d04ded0b6261b9e39fe)], at
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/f7e1bb951d648a63dfa27d04ded0b6261b9e39fe)], at
|
||||||
any time a new _LIP_ is created, the IPv4 and IPv6 address on the VPP interface are fully copied
|
any time a new _LIP_ is created, the IPv4 and IPv6 address on the VPP interface are fully copied
|
||||||
over by the third change, while at runtime, new addresses can be set/removed as well by the first
|
over by the third change, while at runtime, new addresses can be set/removed as well by the first
|
||||||
and second change.
|
and second change.
|
||||||
|
@ -100,7 +100,7 @@ linux-cp {
|
|||||||
|
|
||||||
Based on this config, I set the startup default in `lcp_set_lcp_auto_subint()`, but I realize that
|
Based on this config, I set the startup default in `lcp_set_lcp_auto_subint()`, but I realize that
|
||||||
an administrator may want to turn it on/off at runtime, too, so I add a CLI getter/setter that
|
an administrator may want to turn it on/off at runtime, too, so I add a CLI getter/setter that
|
||||||
interacts with the flag in this [[commit](https://github.com/pimvanpelt/lcpng/commit/d23aab2d95aabcf24efb9f7aecaf15b513633ab7)]:
|
interacts with the flag in this [[commit](https://git.ipng.ch/ipng/lcpng/commit/d23aab2d95aabcf24efb9f7aecaf15b513633ab7)]:
|
||||||
|
|
||||||
```
|
```
|
||||||
DBGvpp# show lcp
|
DBGvpp# show lcp
|
||||||
@ -116,11 +116,11 @@ lcp lcp-sync off
|
|||||||
```
|
```
|
||||||
|
|
||||||
The prep work for the rest of the interface syncer starts with this
|
The prep work for the rest of the interface syncer starts with this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/2d00de080bd26d80ce69441b1043de37e0326e0a)], and
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/2d00de080bd26d80ce69441b1043de37e0326e0a)], and
|
||||||
for the rest of this blog post, the behavior will be in the 'on' position.
|
for the rest of this blog post, the behavior will be in the 'on' position.
|
||||||
|
|
||||||
The code for the configuration toggle is in this
|
The code for the configuration toggle is in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/934446dcd97f51c82ddf133ad45b61b3aae14b2d)].
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/934446dcd97f51c82ddf133ad45b61b3aae14b2d)].
|
||||||
|
|
||||||
### Auto create/delete sub-interfaces
|
### Auto create/delete sub-interfaces
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ I noticed that interface deletion had a bug (one that I fell victim to as well:
|
|||||||
remove the netlink device in the correct network namespace), which I fixed.
|
remove the netlink device in the correct network namespace), which I fixed.
|
||||||
|
|
||||||
The code for the auto create/delete and the bugfix is in this
|
The code for the auto create/delete and the bugfix is in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/934446dcd97f51c82ddf133ad45b61b3aae14b2d)].
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/934446dcd97f51c82ddf133ad45b61b3aae14b2d)].
|
||||||
|
|
||||||
### Further Work
|
### Further Work
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ For now, `lcp_nl_dispatch()` just throws the message away after logging it with
|
|||||||
a function that will come in very useful as I start to explore all the different Netlink message types.
|
a function that will come in very useful as I start to explore all the different Netlink message types.
|
||||||
|
|
||||||
The code that forms the basis of our Netlink Listener lives in [[this
|
The code that forms the basis of our Netlink Listener lives in [[this
|
||||||
commit](https://github.com/pimvanpelt/lcpng/commit/c4e3043ea143d703915239b2390c55f7b6a9b0b1)] and
|
commit](https://git.ipng.ch/ipng/lcpng/commit/c4e3043ea143d703915239b2390c55f7b6a9b0b1)] and
|
||||||
specifically, here I want to call out I was not the primary author, I worked off of Matt and Neale's
|
specifically, here I want to call out I was not the primary author, I worked off of Matt and Neale's
|
||||||
awesome work in this pending [Gerrit](https://gerrit.fd.io/r/c/vpp/+/31122).
|
awesome work in this pending [Gerrit](https://gerrit.fd.io/r/c/vpp/+/31122).
|
||||||
|
|
||||||
@ -182,7 +182,7 @@ Linux interface VPP is not aware of. But, if I can find the _LIP_, I can convert
|
|||||||
add or remove the ip4/ip6 neighbor adjacency.
|
add or remove the ip4/ip6 neighbor adjacency.
|
||||||
|
|
||||||
The code for this first Netlink message handler lives in this
|
The code for this first Netlink message handler lives in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/30bab1d3f9ab06670fbef2c7c6a658e7b77f7738)]. An
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/30bab1d3f9ab06670fbef2c7c6a658e7b77f7738)]. An
|
||||||
ironic insight is that after writing the code, I don't think any of it will be necessary, because
|
ironic insight is that after writing the code, I don't think any of it will be necessary, because
|
||||||
the interface plugin will already copy ARP and IPv6 ND packets back and forth and itself update its
|
the interface plugin will already copy ARP and IPv6 ND packets back and forth and itself update its
|
||||||
neighbor adjacency tables; but I'm leaving the code in for now.
|
neighbor adjacency tables; but I'm leaving the code in for now.
|
||||||
@ -197,7 +197,7 @@ it or remove it, and if there are no link-local addresses left, disable IPv6 on
|
|||||||
There's also a few multicast routes to add (notably 224.0.0.0/24 and ff00::/8, all-local-subnet).
|
There's also a few multicast routes to add (notably 224.0.0.0/24 and ff00::/8, all-local-subnet).
|
||||||
|
|
||||||
The code for IP address handling is in this
|
The code for IP address handling is in this
|
||||||
[[commit]](https://github.com/pimvanpelt/lcpng/commit/87742b4f541d389e745f0297d134e34f17b5b485), but
|
[[commit]](https://git.ipng.ch/ipng/lcpng/commit/87742b4f541d389e745f0297d134e34f17b5b485), but
|
||||||
when I took it out for a spin, I noticed something curious, looking at the log lines that are
|
when I took it out for a spin, I noticed something curious, looking at the log lines that are
|
||||||
generated for the following sequence:
|
generated for the following sequence:
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ interface and directly connected route addition/deletion is slightly different i
|
|||||||
So, I decide to take a little shortcut -- if an addition returns "already there", or a deletion returns
|
So, I decide to take a little shortcut -- if an addition returns "already there", or a deletion returns
|
||||||
"no such entry", I'll just consider it a successful addition and deletion respectively, saving my eyes
|
"no such entry", I'll just consider it a successful addition and deletion respectively, saving my eyes
|
||||||
from being screamed at by this red error message. I changed that in this
|
from being screamed at by this red error message. I changed that in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/d63fbd8a9a612d038aa385e79a57198785d409ca)],
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/d63fbd8a9a612d038aa385e79a57198785d409ca)],
|
||||||
turning this situation in a friendly green notice instead.
|
turning this situation in a friendly green notice instead.
|
||||||
|
|
||||||
### Netlink: Link (existing)
|
### Netlink: Link (existing)
|
||||||
@ -267,7 +267,7 @@ To avoid this loop, I temporarily turn off `lcp-sync` just before handling a bat
|
|||||||
turn it back to its original state when I'm done with that.
|
turn it back to its original state when I'm done with that.
|
||||||
|
|
||||||
The code for all/del of existing links is in this
|
The code for all/del of existing links is in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/e604dd34784e029b41a47baa3179296d15b0632e)].
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/e604dd34784e029b41a47baa3179296d15b0632e)].
|
||||||
|
|
||||||
### Netlink: Link (new)
|
### Netlink: Link (new)
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ doesn't have a _LIP_ for, but specifically describes a VLAN interface? Well, th
|
|||||||
is trying to create a new sub-interface. And supporting that operation would be super cool, so let's go!
|
is trying to create a new sub-interface. And supporting that operation would be super cool, so let's go!
|
||||||
|
|
||||||
Using the earlier placeholder hint in `lcp_nl_link_add()` (see the previous
|
Using the earlier placeholder hint in `lcp_nl_link_add()` (see the previous
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/e604dd34784e029b41a47baa3179296d15b0632e)]),
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/e604dd34784e029b41a47baa3179296d15b0632e)]),
|
||||||
I know that I've gotten a NEWLINK request but the Linux ifindex doesn't have a _LIP_. This could be
|
I know that I've gotten a NEWLINK request but the Linux ifindex doesn't have a _LIP_. This could be
|
||||||
because the interface is entirely foreign to VPP, for example somebody created a dummy interface or
|
because the interface is entirely foreign to VPP, for example somebody created a dummy interface or
|
||||||
a VLAN sub-interface on one:
|
a VLAN sub-interface on one:
|
||||||
@ -331,7 +331,7 @@ a boring `<phy>.<subid>` name.
|
|||||||
|
|
||||||
Alright, without further ado, the code for the main innovation here, the implementation of
|
Alright, without further ado, the code for the main innovation here, the implementation of
|
||||||
`lcp_nl_link_add_vlan()`, is in this
|
`lcp_nl_link_add_vlan()`, is in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/45f408865688eb7ea0cdbf23aa6f8a973be49d1a)].
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/45f408865688eb7ea0cdbf23aa6f8a973be49d1a)].
|
||||||
|
|
||||||
## Results
|
## Results
|
||||||
|
|
||||||
|
@ -118,7 +118,7 @@ or Virtual Routing/Forwarding domains). So first, I need to add these:
|
|||||||
|
|
||||||
All of this code was heavily inspired by the pending [[Gerrit](https://gerrit.fd.io/r/c/vpp/+/31122)]
|
All of this code was heavily inspired by the pending [[Gerrit](https://gerrit.fd.io/r/c/vpp/+/31122)]
|
||||||
but a few finishing touches were added, and wrapped up in this
|
but a few finishing touches were added, and wrapped up in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/7a76498277edc43beaa680e91e3a0c1787319106)].
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/7a76498277edc43beaa680e91e3a0c1787319106)].
|
||||||
|
|
||||||
### Deletion
|
### Deletion
|
||||||
|
|
||||||
@ -459,7 +459,7 @@ it as 'unreachable' rather than deleting it. These are *additions* which have a
|
|||||||
but with an interface index of 1 (which, in Netlink, is 'lo'). This makes VPP intermittently crash, so I
|
but with an interface index of 1 (which, in Netlink, is 'lo'). This makes VPP intermittently crash, so I
|
||||||
currently commented this out, while I gain better understanding. Result: blackhole/unreachable/prohibit
|
currently commented this out, while I gain better understanding. Result: blackhole/unreachable/prohibit
|
||||||
specials can not be set using the plugin. Beware!
|
specials can not be set using the plugin. Beware!
|
||||||
(disabled in this [[commit](https://github.com/pimvanpelt/lcpng/commit/7c864ed099821f62c5be8cbe9ed3f4dd34000a42)]).
|
(disabled in this [[commit](https://git.ipng.ch/ipng/lcpng/commit/7c864ed099821f62c5be8cbe9ed3f4dd34000a42)]).
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ stat['/if/rx-miss'][:, 1].sum() - returns the sum of packet counters for
|
|||||||
```
|
```
|
||||||
|
|
||||||
Alright, so let's grab that file and refactor it into a small library for me to use, I do
|
Alright, so let's grab that file and refactor it into a small library for me to use, I do
|
||||||
this in [[this commit](https://github.com/pimvanpelt/vpp-snmp-agent/commit/51eee915bf0f6267911da596b41a4475feaf212e)].
|
this in [[this commit](https://git.ipng.ch/ipng/vpp-snmp-agent/commit/51eee915bf0f6267911da596b41a4475feaf212e)].
|
||||||
|
|
||||||
### VPP's API
|
### VPP's API
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ idx=19 name=tap4 mac=02:fe:17:06:fc:af mtu=9000 flags=3
|
|||||||
|
|
||||||
So I added a little abstration with some error handling and one main function
|
So I added a little abstration with some error handling and one main function
|
||||||
to return interfaces as a Python dictionary of those `sw_interface_details`
|
to return interfaces as a Python dictionary of those `sw_interface_details`
|
||||||
tuples in [[this commit](https://github.com/pimvanpelt/vpp-snmp-agent/commit/51eee915bf0f6267911da596b41a4475feaf212e)].
|
tuples in [[this commit](https://git.ipng.ch/ipng/vpp-snmp-agent/commit/51eee915bf0f6267911da596b41a4475feaf212e)].
|
||||||
|
|
||||||
### AgentX
|
### AgentX
|
||||||
|
|
||||||
@ -207,9 +207,9 @@ once asked with `GetPDU` or `GetNextPDU` requests, by issuing a corresponding `R
|
|||||||
to the SNMP server -- it takes care of all the rest!
|
to the SNMP server -- it takes care of all the rest!
|
||||||
|
|
||||||
The resulting code is in [[this
|
The resulting code is in [[this
|
||||||
commit](https://github.com/pimvanpelt/vpp-snmp-agent/commit/8c9c1e2b4aa1d40a981f17581f92bba133dd2c29)]
|
commit](https://git.ipng.ch/ipng/vpp-snmp-agent/commit/8c9c1e2b4aa1d40a981f17581f92bba133dd2c29)]
|
||||||
but you can also check out the whole thing on
|
but you can also check out the whole thing on
|
||||||
[[Github](https://github.com/pimvanpelt/vpp-snmp-agent)].
|
[[Github](https://git.ipng.ch/ipng/vpp-snmp-agent)].
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
|
@ -480,7 +480,7 @@ is to say, those packets which were destined to any IP address configured on the
|
|||||||
plane. Any traffic going _through_ VPP will never be seen by Linux! So, I'll have to be
|
plane. Any traffic going _through_ VPP will never be seen by Linux! So, I'll have to be
|
||||||
clever and count this traffic by polling VPP instead. This was the topic of my previous
|
clever and count this traffic by polling VPP instead. This was the topic of my previous
|
||||||
[VPP Part 6]({{< ref "2021-09-10-vpp-6" >}}) about the SNMP Agent. All of that code
|
[VPP Part 6]({{< ref "2021-09-10-vpp-6" >}}) about the SNMP Agent. All of that code
|
||||||
was released to [Github](https://github.com/pimvanpelt/vpp-snmp-agent), notably there's
|
was released to [Github](https://git.ipng.ch/ipng/vpp-snmp-agent), notably there's
|
||||||
a hint there for an `snmpd-dataplane.service` and a `vpp-snmp-agent.service`, including
|
a hint there for an `snmpd-dataplane.service` and a `vpp-snmp-agent.service`, including
|
||||||
the compiled binary that reads from VPP and feeds this to SNMP.
|
the compiled binary that reads from VPP and feeds this to SNMP.
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ plugins:
|
|||||||
or route, or the system receiving ARP or IPv6 neighbor request/reply from neighbors), and applying
|
or route, or the system receiving ARP or IPv6 neighbor request/reply from neighbors), and applying
|
||||||
these events to the VPP dataplane.
|
these events to the VPP dataplane.
|
||||||
|
|
||||||
I've published the code on [Github](https://github.com/pimvanpelt/lcpng/) and I am targeting a release
|
I've published the code on [Github](https://git.ipng.ch/ipng/lcpng/) and I am targeting a release
|
||||||
in upstream VPP, hoping to make the upcoming 22.02 release in February 2022. I have a lot of ground to
|
in upstream VPP, hoping to make the upcoming 22.02 release in February 2022. I have a lot of ground to
|
||||||
cover, but I will note that the plugin has been running in production in [AS8298]({{< ref "2021-02-27-network" >}})
|
cover, but I will note that the plugin has been running in production in [AS8298]({{< ref "2021-02-27-network" >}})
|
||||||
since Sep'21 and no crashes related to LinuxCP have been observed.
|
since Sep'21 and no crashes related to LinuxCP have been observed.
|
||||||
@ -195,7 +195,7 @@ So grab a cup of tea, while we let Rhino stretch its legs, ehh, CPUs ...
|
|||||||
pim@rhino:~$ mkdir -p ~/src
|
pim@rhino:~$ mkdir -p ~/src
|
||||||
pim@rhino:~$ cd ~/src
|
pim@rhino:~$ cd ~/src
|
||||||
pim@rhino:~/src$ sudo apt install libmnl-dev
|
pim@rhino:~/src$ sudo apt install libmnl-dev
|
||||||
pim@rhino:~/src$ git clone https://github.com/pimvanpelt/lcpng.git
|
pim@rhino:~/src$ git clone https://git.ipng.ch/ipng/lcpng.git
|
||||||
pim@rhino:~/src$ git clone https://gerrit.fd.io/r/vpp
|
pim@rhino:~/src$ git clone https://gerrit.fd.io/r/vpp
|
||||||
pim@rhino:~/src$ ln -s ~/src/lcpng ~/src/vpp/src/plugins/lcpng
|
pim@rhino:~/src$ ln -s ~/src/lcpng ~/src/vpp/src/plugins/lcpng
|
||||||
pim@rhino:~/src$ cd ~/src/vpp
|
pim@rhino:~/src$ cd ~/src/vpp
|
||||||
|
@ -33,7 +33,7 @@ In this first post, let's take a look at tablestakes: writing a YAML specificati
|
|||||||
configuration elements of VPP, and then ensures that the YAML file is both syntactically as well as
|
configuration elements of VPP, and then ensures that the YAML file is both syntactically as well as
|
||||||
semantically correct.
|
semantically correct.
|
||||||
|
|
||||||
**Note**: Code is on [my Github](https://github.com/pimvanpelt/vppcfg), but it's not quite ready for
|
**Note**: Code is on [my Github](https://git.ipng.ch/ipng/vppcfg), but it's not quite ready for
|
||||||
prime-time yet. Take a look, and engage with us on GitHub (pull requests preferred over issues themselves)
|
prime-time yet. Take a look, and engage with us on GitHub (pull requests preferred over issues themselves)
|
||||||
or reach out by [contacting us](/s/contact/).
|
or reach out by [contacting us](/s/contact/).
|
||||||
|
|
||||||
@ -348,7 +348,7 @@ to mess up my (or your!) VPP router by feeding it garbage, so the lions' share o
|
|||||||
has been to assert the YAML file is both syntactically and semantically valid.
|
has been to assert the YAML file is both syntactically and semantically valid.
|
||||||
|
|
||||||
|
|
||||||
In the mean time, you can take a look at my code on [GitHub](https://github.com/pimvanpelt/vppcfg), but to
|
In the mean time, you can take a look at my code on [GitHub](https://git.ipng.ch/ipng/vppcfg), but to
|
||||||
whet your appetite, here's a hefty configuration that demonstrates all implemented types:
|
whet your appetite, here's a hefty configuration that demonstrates all implemented types:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -32,7 +32,7 @@ the configuration to the dataplane. Welcome to `vppcfg`!
|
|||||||
In this second post of the series, I want to talk a little bit about how planning a path from a running
|
In this second post of the series, I want to talk a little bit about how planning a path from a running
|
||||||
configuration to a desired new configuration might look like.
|
configuration to a desired new configuration might look like.
|
||||||
|
|
||||||
**Note**: Code is on [my Github](https://github.com/pimvanpelt/vppcfg), but it's not quite ready for
|
**Note**: Code is on [my Github](https://git.ipng.ch/ipng/vppcfg), but it's not quite ready for
|
||||||
prime-time yet. Take a look, and engage with us on GitHub (pull requests preferred over issues themselves)
|
prime-time yet. Take a look, and engage with us on GitHub (pull requests preferred over issues themselves)
|
||||||
or reach out by [contacting us](/s/contact/).
|
or reach out by [contacting us](/s/contact/).
|
||||||
|
|
||||||
|
@ -171,12 +171,12 @@ GigabitEthernet1/0/0 1 up GigabitEthernet1/0/0
|
|||||||
|
|
||||||
After this exploratory exercise, I have learned enough about the hardware to be able to take the
|
After this exploratory exercise, I have learned enough about the hardware to be able to take the
|
||||||
Fitlet2 out for a spin. To configure the VPP instance, I turn to
|
Fitlet2 out for a spin. To configure the VPP instance, I turn to
|
||||||
[[vppcfg](https://github.com/pimvanpelt/vppcfg)], which can take a YAML configuration file
|
[[vppcfg](https://git.ipng.ch/ipng/vppcfg)], which can take a YAML configuration file
|
||||||
describing the desired VPP configuration, and apply it safely to the running dataplane using the VPP
|
describing the desired VPP configuration, and apply it safely to the running dataplane using the VPP
|
||||||
API. I've written a few more posts on how it does that, notably on its [[syntax]({{< ref "2022-03-27-vppcfg-1" >}})]
|
API. I've written a few more posts on how it does that, notably on its [[syntax]({{< ref "2022-03-27-vppcfg-1" >}})]
|
||||||
and its [[planner]({{< ref "2022-04-02-vppcfg-2" >}})]. A complete
|
and its [[planner]({{< ref "2022-04-02-vppcfg-2" >}})]. A complete
|
||||||
configuration guide on vppcfg can be found
|
configuration guide on vppcfg can be found
|
||||||
[[here](https://github.com/pimvanpelt/vppcfg/blob/main/docs/config-guide.md)].
|
[[here](https://git.ipng.ch/ipng/vppcfg/blob/main/docs/config-guide.md)].
|
||||||
|
|
||||||
```
|
```
|
||||||
pim@fitlet:~$ sudo dpkg -i {lib,}vpp*23.06*deb
|
pim@fitlet:~$ sudo dpkg -i {lib,}vpp*23.06*deb
|
||||||
|
@ -185,7 +185,7 @@ forgetful chipmunk-sized brain!), so here, I'll only recap what's already writte
|
|||||||
|
|
||||||
**1. BUILD:** For the first step, the build is straight forward, and yields a VPP instance based on
|
**1. BUILD:** For the first step, the build is straight forward, and yields a VPP instance based on
|
||||||
`vpp-ext-deps_23.06-1` at version `23.06-rc0~71-g182d2b466`, which contains my
|
`vpp-ext-deps_23.06-1` at version `23.06-rc0~71-g182d2b466`, which contains my
|
||||||
[[LCPng](https://github.com/pimvanpelt/lcpng.git)] plugin. I then copy the packages to the router.
|
[[LCPng](https://git.ipng.ch/ipng/lcpng.git)] plugin. I then copy the packages to the router.
|
||||||
The router has an E-2286G CPU @ 4.00GHz with 6 cores and 6 hyperthreads. There's a really handy tool
|
The router has an E-2286G CPU @ 4.00GHz with 6 cores and 6 hyperthreads. There's a really handy tool
|
||||||
called `likwid-topology` that can show how the L1, L2 and L3 cache lines up with respect to CPU
|
called `likwid-topology` that can show how the L1, L2 and L3 cache lines up with respect to CPU
|
||||||
cores. Here I learn that CPU (0+6) and (1+7) share L1 and L2 cache -- so I can conclude that 0-5 are
|
cores. Here I learn that CPU (0+6) and (1+7) share L1 and L2 cache -- so I can conclude that 0-5 are
|
||||||
@ -351,7 +351,7 @@ in `vppcfg`:
|
|||||||
* When I create the initial `--novpp` config, there's a bug in `vppcfg` where I incorrectly
|
* When I create the initial `--novpp` config, there's a bug in `vppcfg` where I incorrectly
|
||||||
reference a dataplane object which I haven't initialized (because with `--novpp` the tool
|
reference a dataplane object which I haven't initialized (because with `--novpp` the tool
|
||||||
will not contact the dataplane at all. That one was easy to fix, which I did in [[this
|
will not contact the dataplane at all. That one was easy to fix, which I did in [[this
|
||||||
commit](https://github.com/pimvanpelt/vppcfg/commit/0a0413927a0be6ed3a292a8c336deab8b86f5eee)]).
|
commit](https://git.ipng.ch/ipng/vppcfg/commit/0a0413927a0be6ed3a292a8c336deab8b86f5eee)]).
|
||||||
|
|
||||||
After that small detour, I can now proceed to configure the dataplane by offering the resulting
|
After that small detour, I can now proceed to configure the dataplane by offering the resulting
|
||||||
VPP commands, like so:
|
VPP commands, like so:
|
||||||
@ -573,7 +573,7 @@ see is that which is destined to the controlplane (eg, to one of the IPv4 or IPv
|
|||||||
multicast/broadcast groups that they are participating in), so things like tcpdump or SNMP won't
|
multicast/broadcast groups that they are participating in), so things like tcpdump or SNMP won't
|
||||||
really work.
|
really work.
|
||||||
|
|
||||||
However, due to my [[vpp-snmp-agent](https://github.com/pimvanpelt/vpp-snmp-agent.git)], which is
|
However, due to my [[vpp-snmp-agent](https://git.ipng.ch/ipng/vpp-snmp-agent.git)], which is
|
||||||
feeding as an AgentX behind an snmpd that in turn is running in the `dataplane` namespace, SNMP scrapes
|
feeding as an AgentX behind an snmpd that in turn is running in the `dataplane` namespace, SNMP scrapes
|
||||||
work as they did before, albeit with a few different interface names.
|
work as they did before, albeit with a few different interface names.
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ performance and versatility. For those of us who have used Cisco IOS/XR devices,
|
|||||||
_ASR_ (aggregation service router), VPP will look and feel quite familiar as many of the approaches
|
_ASR_ (aggregation service router), VPP will look and feel quite familiar as many of the approaches
|
||||||
are shared between the two.
|
are shared between the two.
|
||||||
|
|
||||||
I've been working on the Linux Control Plane [[ref](https://github.com/pimvanpelt/lcpng)], which you
|
I've been working on the Linux Control Plane [[ref](https://git.ipng.ch/ipng/lcpng)], which you
|
||||||
can read all about in my series on VPP back in 2021:
|
can read all about in my series on VPP back in 2021:
|
||||||
|
|
||||||
[{: style="width:300px; float: right; margin-left: 1em;"}](https://video.ipng.ch/w/erc9sAofrSZ22qjPwmv6H4)
|
[{: style="width:300px; float: right; margin-left: 1em;"}](https://video.ipng.ch/w/erc9sAofrSZ22qjPwmv6H4)
|
||||||
@ -70,7 +70,7 @@ answered by a Response PDU.
|
|||||||
|
|
||||||
Using parts of a Python Agentx library written by GitHub user hosthvo
|
Using parts of a Python Agentx library written by GitHub user hosthvo
|
||||||
[[ref](https://github.com/hosthvo/pyagentx)], I tried my hands at writing one of these AgentX's.
|
[[ref](https://github.com/hosthvo/pyagentx)], I tried my hands at writing one of these AgentX's.
|
||||||
The resulting source code is on [[GitHub](https://github.com/pimvanpelt/vpp-snmp-agent)]. That's the
|
The resulting source code is on [[GitHub](https://git.ipng.ch/ipng/vpp-snmp-agent)]. That's the
|
||||||
one that's running in production ever since I started running VPP routers at IPng Networks AS8298.
|
one that's running in production ever since I started running VPP routers at IPng Networks AS8298.
|
||||||
After the _AgentX_ exposes the dataplane interfaces and their statistics into _SNMP_, an open source
|
After the _AgentX_ exposes the dataplane interfaces and their statistics into _SNMP_, an open source
|
||||||
monitoring tool such as LibreNMS [[ref](https://librenms.org/)] can discover the routers and draw
|
monitoring tool such as LibreNMS [[ref](https://librenms.org/)] can discover the routers and draw
|
||||||
@ -126,7 +126,7 @@ for any interface created in the dataplane.
|
|||||||
|
|
||||||
I wish I were good at Go, but I never really took to the language. I'm pretty good at Python, but
|
I wish I were good at Go, but I never really took to the language. I'm pretty good at Python, but
|
||||||
sorting through the stats segment isn't super quick as I've already noticed in the Python3 based
|
sorting through the stats segment isn't super quick as I've already noticed in the Python3 based
|
||||||
[[VPP SNMP Agent](https://github.com/pimvanpelt/vpp-snmp-agent)]. I'm probably the world's least
|
[[VPP SNMP Agent](https://git.ipng.ch/ipng/vpp-snmp-agent)]. I'm probably the world's least
|
||||||
terrible C programmer, so maybe I can take a look at the VPP Stats Client and make sense of it. Luckily,
|
terrible C programmer, so maybe I can take a look at the VPP Stats Client and make sense of it. Luckily,
|
||||||
there's an example already in `src/vpp/app/vpp_get_stats.c` and it reveals the following pattern:
|
there's an example already in `src/vpp/app/vpp_get_stats.c` and it reveals the following pattern:
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ same time keep an IPng Site Local network with IPv4 and IPv6 that is separate fr
|
|||||||
based on hardware/silicon based forwarding at line rate and high availability. You can read all
|
based on hardware/silicon based forwarding at line rate and high availability. You can read all
|
||||||
about my Centec MPLS shenanigans in [[this article]({{< ref "2023-03-11-mpls-core" >}})].
|
about my Centec MPLS shenanigans in [[this article]({{< ref "2023-03-11-mpls-core" >}})].
|
||||||
|
|
||||||
Ever since the release of the Linux Control Plane [[ref](https://github.com/pimvanpelt/lcpng)]
|
Ever since the release of the Linux Control Plane [[ref](https://git.ipng.ch/ipng/lcpng)]
|
||||||
plugin in VPP, folks have asked "What about MPLS?" -- I have never really felt the need to go this
|
plugin in VPP, folks have asked "What about MPLS?" -- I have never really felt the need to go this
|
||||||
rabbit hole, because I figured that in this day and age, higher level IP protocols that do tunneling
|
rabbit hole, because I figured that in this day and age, higher level IP protocols that do tunneling
|
||||||
are just as performant, and a little bit less of an 'art' to get right. For example, the Centec
|
are just as performant, and a little bit less of an 'art' to get right. For example, the Centec
|
||||||
|
@ -459,6 +459,6 @@ and VPP, and the overall implementation before attempting to use in production.
|
|||||||
we got at least some of this right, but testing and runtime experience will tell.
|
we got at least some of this right, but testing and runtime experience will tell.
|
||||||
|
|
||||||
I will be silently porting the change into my own copy of the Linux Controlplane called lcpng on
|
I will be silently porting the change into my own copy of the Linux Controlplane called lcpng on
|
||||||
[[GitHub](https://github.com/pimvanpelt/lcpng.git)]. If you'd like to test this - reach out to the VPP
|
[[GitHub](https://git.ipng.ch/ipng/lcpng.git)]. If you'd like to test this - reach out to the VPP
|
||||||
Developer [[mailinglist](mailto:vpp-dev@lists.fd.io)] any time!
|
Developer [[mailinglist](mailto:vpp-dev@lists.fd.io)] any time!
|
||||||
|
|
||||||
|
@ -385,5 +385,5 @@ and VPP, and the overall implementation before attempting to use in production.
|
|||||||
we got at least some of this right, but testing and runtime experience will tell.
|
we got at least some of this right, but testing and runtime experience will tell.
|
||||||
|
|
||||||
I will be silently porting the change into my own copy of the Linux Controlplane called lcpng on
|
I will be silently porting the change into my own copy of the Linux Controlplane called lcpng on
|
||||||
[[GitHub](https://github.com/pimvanpelt/lcpng.git)]. If you'd like to test this - reach out to the VPP
|
[[GitHub](https://git.ipng.ch/ipng/lcpng.git)]. If you'd like to test this - reach out to the VPP
|
||||||
Developer [[mailinglist](mailto:vpp-dev@lists.fd.io)] any time!
|
Developer [[mailinglist](mailto:vpp-dev@lists.fd.io)] any time!
|
||||||
|
@ -304,7 +304,7 @@ Gateway, just to show a few of the more advanced features of VPP. For me, this t
|
|||||||
line of thinking: classifiers. This extract/match/act pattern can be used in policers, ACLs and
|
line of thinking: classifiers. This extract/match/act pattern can be used in policers, ACLs and
|
||||||
arbitrary traffic redirection through VPP's directed graph (eg. selecting a next node for
|
arbitrary traffic redirection through VPP's directed graph (eg. selecting a next node for
|
||||||
processing). I'm going to deep-dive into this classifier behavior in an upcoming article, and see
|
processing). I'm going to deep-dive into this classifier behavior in an upcoming article, and see
|
||||||
how I might add this to [[vppcfg](https://github.com/pimvanpelt/vppcfg.git)], because I think it
|
how I might add this to [[vppcfg](https://git.ipng.ch/ipng/vppcfg.git)], because I think it
|
||||||
would be super powerful to abstract away the rather complex underlying API into something a little
|
would be super powerful to abstract away the rather complex underlying API into something a little
|
||||||
bit more ... user friendly. Stay tuned! :)
|
bit more ... user friendly. Stay tuned! :)
|
||||||
|
|
||||||
|
@ -359,7 +359,7 @@ does not have an IPv4 address. Except -- I'm bending the rules a little bit by d
|
|||||||
There's an internal function `ip4_sw_interface_enable_disable()` which is called to enable IPv4
|
There's an internal function `ip4_sw_interface_enable_disable()` which is called to enable IPv4
|
||||||
processing on an interface once the first IPv4 address is added. So my first fix is to force this to
|
processing on an interface once the first IPv4 address is added. So my first fix is to force this to
|
||||||
be enabled for any interface that is exposed via Linux Control Plane, notably in `lcp_itf_pair_create()`
|
be enabled for any interface that is exposed via Linux Control Plane, notably in `lcp_itf_pair_create()`
|
||||||
[[here](https://github.com/pimvanpelt/lcpng/blob/main/lcpng_interface.c#L777)].
|
[[here](https://git.ipng.ch/ipng/lcpng/blob/main/lcpng_interface.c#L777)].
|
||||||
|
|
||||||
This approach is partially effective:
|
This approach is partially effective:
|
||||||
|
|
||||||
@ -500,7 +500,7 @@ which is unnumbered. Because I don't know for sure if everybody would find this
|
|||||||
I make sure to guard the behavior behind a backwards compatible configuration option.
|
I make sure to guard the behavior behind a backwards compatible configuration option.
|
||||||
|
|
||||||
If you're curious, please take a look at the change in my [[GitHub
|
If you're curious, please take a look at the change in my [[GitHub
|
||||||
repo](https://github.com/pimvanpelt/lcpng/commit/a960d64a87849d312b32d9432ffb722672c14878)], in
|
repo](https://git.ipng.ch/ipng/lcpng/commit/a960d64a87849d312b32d9432ffb722672c14878)], in
|
||||||
which I:
|
which I:
|
||||||
1. add a new configuration option, `lcp-sync-unnumbered`, which defaults to `on`. That would be
|
1. add a new configuration option, `lcp-sync-unnumbered`, which defaults to `on`. That would be
|
||||||
what the plugin would do in the normal case: copy forward these borrowed IP addresses to Linux.
|
what the plugin would do in the normal case: copy forward these borrowed IP addresses to Linux.
|
||||||
|
@ -147,7 +147,7 @@ With all of that, I am ready to demonstrate two working solutions now. I first c
|
|||||||
Ondrej's [[commit](https://gitlab.nic.cz/labs/bird/-/commit/280daed57d061eb1ebc89013637c683fe23465e8)].
|
Ondrej's [[commit](https://gitlab.nic.cz/labs/bird/-/commit/280daed57d061eb1ebc89013637c683fe23465e8)].
|
||||||
Then, I compile VPP with my pending [[gerrit](https://gerrit.fd.io/r/c/vpp/+/40482)]. Finally,
|
Then, I compile VPP with my pending [[gerrit](https://gerrit.fd.io/r/c/vpp/+/40482)]. Finally,
|
||||||
to demonstrate how `update_loopback_addr()` might work, I compile `lcpng` with my previous
|
to demonstrate how `update_loopback_addr()` might work, I compile `lcpng` with my previous
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/a960d64a87849d312b32d9432ffb722672c14878)],
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/a960d64a87849d312b32d9432ffb722672c14878)],
|
||||||
which allows me to inhibit copying forward addresses from VPP to Linux, when using _unnumbered_
|
which allows me to inhibit copying forward addresses from VPP to Linux, when using _unnumbered_
|
||||||
interfaces.
|
interfaces.
|
||||||
|
|
||||||
|
@ -250,10 +250,10 @@ remove the IPv4 and IPv6 addresses from the <span style='color:red;font-weight:b
|
|||||||
routers in Brüttisellen. They are directly connected, and if anything goes wrong, I can walk
|
routers in Brüttisellen. They are directly connected, and if anything goes wrong, I can walk
|
||||||
over and rescue them. Sounds like a safe way to start!
|
over and rescue them. Sounds like a safe way to start!
|
||||||
|
|
||||||
I quickly add the ability for [[vppcfg](https://github.com/pimvanpelt/vppcfg)] to configure
|
I quickly add the ability for [[vppcfg](https://git.ipng.ch/ipng/vppcfg)] to configure
|
||||||
_unnumbered_ interfaces. In VPP, these are interfaces that don't have an IPv4 or IPv6 address of
|
_unnumbered_ interfaces. In VPP, these are interfaces that don't have an IPv4 or IPv6 address of
|
||||||
their own, but they borrow one from another interface. If you're curious, you can take a look at the
|
their own, but they borrow one from another interface. If you're curious, you can take a look at the
|
||||||
[[User Guide](https://github.com/pimvanpelt/vppcfg/blob/main/docs/config-guide.md#interfaces)] on
|
[[User Guide](https://git.ipng.ch/ipng/vppcfg/blob/main/docs/config-guide.md#interfaces)] on
|
||||||
GitHub.
|
GitHub.
|
||||||
|
|
||||||
Looking at their `vppcfg` files, the change is actually very easy, taking as an example the
|
Looking at their `vppcfg` files, the change is actually very easy, taking as an example the
|
||||||
@ -291,7 +291,7 @@ interface.
|
|||||||
|
|
||||||
In the article, you'll see that discussed as _Solution 2_, and it includes a bit of rationale why I
|
In the article, you'll see that discussed as _Solution 2_, and it includes a bit of rationale why I
|
||||||
find this better. I implemented it in this
|
find this better. I implemented it in this
|
||||||
[[commit](https://github.com/pimvanpelt/lcpng/commit/a960d64a87849d312b32d9432ffb722672c14878)], in
|
[[commit](https://git.ipng.ch/ipng/lcpng/commit/a960d64a87849d312b32d9432ffb722672c14878)], in
|
||||||
case you're curious, and the commandline keyword is `lcp lcp-sync-unnumbered off` (the default is
|
case you're curious, and the commandline keyword is `lcp lcp-sync-unnumbered off` (the default is
|
||||||
_on_).
|
_on_).
|
||||||
|
|
||||||
|
@ -230,7 +230,7 @@ does not have any form of configuration persistence and that's deliberate. VPP's
|
|||||||
programmable dataplane, and explicitly has left the programming and configuration as an exercise for
|
programmable dataplane, and explicitly has left the programming and configuration as an exercise for
|
||||||
integrators. I have written a Python project that takes a YAML file as input and uses it to
|
integrators. I have written a Python project that takes a YAML file as input and uses it to
|
||||||
configure (and reconfigure, on the fly) the dataplane automatically, called
|
configure (and reconfigure, on the fly) the dataplane automatically, called
|
||||||
[[VPPcfg](https://github.com/pimvanpelt/vppcfg.git)]. Previously, I wrote some implementation thoughts
|
[[VPPcfg](https://git.ipng.ch/ipng/vppcfg.git)]. Previously, I wrote some implementation thoughts
|
||||||
on its [[datamodel]({{< ref 2022-03-27-vppcfg-1 >}})] and its [[operations]({{< ref 2022-04-02-vppcfg-2
|
on its [[datamodel]({{< ref 2022-03-27-vppcfg-1 >}})] and its [[operations]({{< ref 2022-04-02-vppcfg-2
|
||||||
>}})] so I won't repeat that here. Instead, I will just show the configuration:
|
>}})] so I won't repeat that here. Instead, I will just show the configuration:
|
||||||
|
|
||||||
|
@ -430,7 +430,7 @@ Boom. I could not be more pleased.
|
|||||||
This was a nice exercise for me! I'm going this direction becaue the
|
This was a nice exercise for me! I'm going this direction becaue the
|
||||||
[[Containerlab](https://containerlab.dev)] framework will start containers with given NOS images,
|
[[Containerlab](https://containerlab.dev)] framework will start containers with given NOS images,
|
||||||
not too dissimilar from the one I just made, and then attaches `veth` pairs between the containers.
|
not too dissimilar from the one I just made, and then attaches `veth` pairs between the containers.
|
||||||
I started dabbling with a [[pull-request](https://github.com/srl-labs/containerlab/pull/2569)], but
|
I started dabbling with a [[pull-request](https://github.com/srl-labs/containerlab/pull/2571)], but
|
||||||
I got stuck with a part of the Containerlab code that pre-deploys config files into the containers.
|
I got stuck with a part of the Containerlab code that pre-deploys config files into the containers.
|
||||||
You see, I will need to generate two files:
|
You see, I will need to generate two files:
|
||||||
|
|
||||||
@ -448,7 +448,7 @@ will connect a few VPP containers together with an SR Linux node in a lab. Stand
|
|||||||
|
|
||||||
Once we have that, there's still quite some work for me to do. Notably:
|
Once we have that, there's still quite some work for me to do. Notably:
|
||||||
* Configuration persistence. `clab` allows you to save the running config. For that, I'll need to
|
* Configuration persistence. `clab` allows you to save the running config. For that, I'll need to
|
||||||
introduce [[vppcfg](https://github.com/pimvanpelt/vppcfg.git)] and a means to invoke it when
|
introduce [[vppcfg](https://git.ipng.ch/ipng/vppcfg)] and a means to invoke it when
|
||||||
the lab operator wants to save their config, and then reconfigure VPP when the container
|
the lab operator wants to save their config, and then reconfigure VPP when the container
|
||||||
restarts.
|
restarts.
|
||||||
* I'll need to have a few files from `clab` shared with the host, notably the `startup.conf` and
|
* I'll need to have a few files from `clab` shared with the host, notably the `startup.conf` and
|
||||||
|
373
content/articles/2025-05-04-containerlab-2.md
Normal file
373
content/articles/2025-05-04-containerlab-2.md
Normal file
@ -0,0 +1,373 @@
|
|||||||
|
---
|
||||||
|
date: "2025-05-04T15:07:23Z"
|
||||||
|
title: 'VPP in Containerlab - Part 2'
|
||||||
|
params:
|
||||||
|
asciinema: true
|
||||||
|
---
|
||||||
|
|
||||||
|
{{< image float="right" src="/assets/containerlab/containerlab.svg" alt="Containerlab Logo" width="12em" >}}
|
||||||
|
|
||||||
|
# Introduction
|
||||||
|
|
||||||
|
From time to time the subject of containerized VPP instances comes up. At IPng, I run the routers in
|
||||||
|
AS8298 on bare metal (Supermicro and Dell hardware), as it allows me to maximize performance.
|
||||||
|
However, VPP is quite friendly in virtualization. Notably, it runs really well on virtual machines
|
||||||
|
like Qemu/KVM or VMWare. I can pass through PCI devices directly to the host, and use CPU pinning to
|
||||||
|
allow the guest virtual machine access to the underlying physical hardware. In such a mode, VPP
|
||||||
|
performance almost the same as on bare metal. But did you know that VPP can also run in Docker?
|
||||||
|
|
||||||
|
The other day I joined the [[ZANOG'25](https://nog.net.za/event1/zanog25/)] in Durban, South Africa.
|
||||||
|
One of the presenters was Nardus le Roux of Nokia, and he showed off a project called
|
||||||
|
[[Containerlab](https://containerlab.dev/)], which provides a CLI for orchestrating and managing
|
||||||
|
container-based networking labs. It starts the containers, builds virtual wiring between them to
|
||||||
|
create lab topologies of users' choice and manages the lab lifecycle.
|
||||||
|
|
||||||
|
Quite regularly I am asked 'when will you add VPP to Containerlab?', but at ZANOG I made a promise
|
||||||
|
to actually add it. In my previous [[article]({{< ref 2025-05-03-containerlab-1.md >}})], I took
|
||||||
|
a good look at VPP as a dockerized container. In this article, I'll explore how to make such a
|
||||||
|
container run in Containerlab!
|
||||||
|
|
||||||
|
## Completing the Docker container
|
||||||
|
|
||||||
|
Just having VPP running by itself in a container is not super useful (although it _is_ cool!). I
|
||||||
|
decide first to add a few bits and bobs that will come in handy in the `Dockerfile`:
|
||||||
|
|
||||||
|
```
|
||||||
|
FROM debian:bookworm
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ARG VPP_INSTALL_SKIP_SYSCTL=true
|
||||||
|
ARG REPO=release
|
||||||
|
EXPOSE 22/tcp
|
||||||
|
RUN apt-get update && apt-get -y install curl procps tcpdump iproute2 iptables \
|
||||||
|
iputils-ping net-tools git python3 python3-pip vim-tiny openssh-server bird2 \
|
||||||
|
mtr-tiny traceroute && apt-get clean
|
||||||
|
|
||||||
|
# Install VPP
|
||||||
|
RUN mkdir -p /var/log/vpp /root/.ssh/
|
||||||
|
RUN curl -s https://packagecloud.io/install/repositories/fdio/${REPO}/script.deb.sh | bash
|
||||||
|
RUN apt-get update && apt-get -y install vpp vpp-plugin-core && apt-get clean
|
||||||
|
|
||||||
|
# Build vppcfg
|
||||||
|
RUN pip install --break-system-packages build netaddr yamale argparse pyyaml ipaddress
|
||||||
|
RUN git clone https://git.ipng.ch/ipng/vppcfg.git && cd vppcfg && python3 -m build && \
|
||||||
|
pip install --break-system-packages dist/vppcfg-*-py3-none-any.whl
|
||||||
|
|
||||||
|
# Config files
|
||||||
|
COPY files/etc/vpp/* /etc/vpp/
|
||||||
|
COPY files/etc/bird/* /etc/bird/
|
||||||
|
COPY files/init-container.sh /sbin/
|
||||||
|
RUN chmod 755 /sbin/init-container.sh
|
||||||
|
CMD ["/sbin/init-container.sh"]
|
||||||
|
```
|
||||||
|
|
||||||
|
A few notable additions:
|
||||||
|
* ***vppcfg*** is a handy utility I wrote and discussed in a previous [[article]({{< ref
|
||||||
|
2022-04-02-vppcfg-2 >}})]. Its purpose is to take YAML file that describes the configuration of
|
||||||
|
the dataplane (like which interfaces, sub-interfaces, MTU, IP addresses and so on), and then
|
||||||
|
apply this safely to a running dataplane. You can check it out in my
|
||||||
|
[[vppcfg](https://git.ipng.ch/ipng/vppcfg)] git repository.
|
||||||
|
* ***openssh-server*** will come in handy to log in to the container, in addition to the already
|
||||||
|
available `docker exec`.
|
||||||
|
* ***bird2*** which will be my controlplane of choice. At a future date, I might also add FRR,
|
||||||
|
which may be a good alterantive for some. VPP works well with both. You can check out Bird on
|
||||||
|
the nic.cz [[website](https://bird.network.cz/?get_doc&f=bird.html&v=20)].
|
||||||
|
|
||||||
|
I'll add a couple of default config files for Bird and VPP, and replace the CMD with a generic
|
||||||
|
`/sbin/init-container.sh` in which I can do any late binding stuff before launching VPP.
|
||||||
|
|
||||||
|
### Initializing the Container
|
||||||
|
|
||||||
|
#### VPP Containerlab: NetNS
|
||||||
|
|
||||||
|
VPP's Linux Control Plane plugin wants to run in its own network namespace. So the first order of
|
||||||
|
business of `/sbin/init-container.sh` is to create it:
|
||||||
|
|
||||||
|
```
|
||||||
|
NETNS=${NETNS:="dataplane"}
|
||||||
|
|
||||||
|
echo "Creating dataplane namespace"
|
||||||
|
/usr/bin/mkdir -p /etc/netns/$NETNS
|
||||||
|
/usr/bin/touch /etc/netns/$NETNS/resolv.conf
|
||||||
|
/usr/sbin/ip netns add $NETNS
|
||||||
|
```
|
||||||
|
|
||||||
|
#### VPP Containerlab: SSH
|
||||||
|
|
||||||
|
Then, I'll set the root password (which is `vpp` by the way), and start aan SSH daemon which allows
|
||||||
|
for password-less logins:
|
||||||
|
|
||||||
|
```
|
||||||
|
echo "Starting SSH, with credentials root:vpp"
|
||||||
|
sed -i -e 's,^#PermitRootLogin prohibit-password,PermitRootLogin yes,' /etc/ssh/sshd_config
|
||||||
|
sed -i -e 's,^root:.*,root:$y$j9T$kG8pyZEVmwLXEtXekQCRK.$9iJxq/bEx5buni1hrC8VmvkDHRy7ZMsw9wYvwrzexID:20211::::::,' /etc/shadow
|
||||||
|
/etc/init.d/ssh start
|
||||||
|
```
|
||||||
|
|
||||||
|
#### VPP Containerlab: Bird2
|
||||||
|
|
||||||
|
I can already predict that Bird2 won't be the only option for a controlplane, even though I'm a huge
|
||||||
|
fan of it. Therefore, I'll make it configurable to leave the door open for other controlplane
|
||||||
|
implementations in the future:
|
||||||
|
|
||||||
|
```
|
||||||
|
BIRD_ENABLED=${BIRD_ENABLED:="true"}
|
||||||
|
|
||||||
|
if [ "$BIRD_ENABLED" == "true" ]; then
|
||||||
|
echo "Starting Bird in $NETNS"
|
||||||
|
mkdir -p /run/bird /var/log/bird
|
||||||
|
chown bird:bird /var/log/bird
|
||||||
|
ROUTERID=$(ip -br a show eth0 | awk '{ print $3 }' | cut -f1 -d/)
|
||||||
|
sed -i -e "s,.*router id .*,router id $ROUTERID; # Set by container-init.sh," /etc/bird/bird.conf
|
||||||
|
/usr/bin/nsenter --net=/var/run/netns/$NETNS /usr/sbin/bird -u bird -g bird
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
I am reminded that Bird won't start if it cannot determine its _router id_. When I start it in the
|
||||||
|
`dataplane` namespace, it will immediately exit, because there will be no IP addresses configured
|
||||||
|
yet. But luckily, it logs its complaint and it's easily addressed. I decide to take the management
|
||||||
|
IPv4 address from `eth0` and write that into the `bird.conf` file, which otherwise does some basic
|
||||||
|
initialization that I described in a previous [[article]({{< ref 2021-09-02-vpp-5 >}})], so I'll
|
||||||
|
skip that here. However, I do include an empty file called `/etc/bird/bird-local.conf` for users to
|
||||||
|
further configure Bird2.
|
||||||
|
|
||||||
|
#### VPP Containerlab: Binding veth pairs
|
||||||
|
|
||||||
|
When Containerlab starts the VPP container, it'll offer it a set of `veth` ports that connect this
|
||||||
|
container to other nodes in the lab. This is done by the `links` list in the topology file
|
||||||
|
[[ref](https://containerlab.dev/manual/network/)]. It's my goal to take all of the interfaces
|
||||||
|
that are of type `veth`, and generate a little snippet to grab them and bind them into VPP while
|
||||||
|
setting their MTU to 9216 to allow for jumbo frames:
|
||||||
|
|
||||||
|
```
|
||||||
|
CLAB_VPP_FILE=${CLAB_VPP_FILE:=/etc/vpp/clab.vpp}
|
||||||
|
|
||||||
|
echo "Generating $CLAB_VPP_FILE"
|
||||||
|
: > $CLAB_VPP_FILE
|
||||||
|
MTU=9216
|
||||||
|
for IFNAME in $(ip -br link show type veth | cut -f1 -d@ | grep -v '^eth0$' | sort); do
|
||||||
|
MAC=$(ip -br link show dev $IFNAME | awk '{ print $3 }')
|
||||||
|
echo " * $IFNAME hw-addr $MAC mtu $MTU"
|
||||||
|
ip link set $IFNAME up mtu $MTU
|
||||||
|
cat << EOF >> $CLAB_VPP_FILE
|
||||||
|
create host-interface name $IFNAME hw-addr $MAC
|
||||||
|
set interface name host-$IFNAME $IFNAME
|
||||||
|
set interface mtu $MTU $IFNAME
|
||||||
|
set interface state $IFNAME up
|
||||||
|
|
||||||
|
EOF
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
{{< image width="5em" float="left" src="/assets/shared/warning.png" alt="Warning" >}}
|
||||||
|
|
||||||
|
One thing I realized is that VPP will assign a random MAC address on its copy of the `veth` port,
|
||||||
|
which is not great. I'll explicitly configure it with the same MAC address as the `veth` interface
|
||||||
|
itself, otherwise I'd have to put the interface into promiscuous mode.
|
||||||
|
|
||||||
|
#### VPP Containerlab: VPPcfg
|
||||||
|
|
||||||
|
I'm almost ready, but I have one more detail. The user will be able to offer a
|
||||||
|
[[vppcfg](https://git.ipng.ch/ipng/vppcfg)] YAML file to configure the interfaces and so on. If such
|
||||||
|
a file exists, I'll apply it to the dataplane upon startup:
|
||||||
|
|
||||||
|
```
|
||||||
|
VPPCFG_VPP_FILE=${VPPCFG_VPP_FILE:=/etc/vpp/vppcfg.vpp}
|
||||||
|
|
||||||
|
echo "Generating $VPPCFG_VPP_FILE"
|
||||||
|
: > $VPPCFG_VPP_FILE
|
||||||
|
if [ -r /etc/vpp/vppcfg.yaml ]; then
|
||||||
|
vppcfg plan --novpp -c /etc/vpp/vppcfg.yaml -o $VPPCFG_VPP_FILE
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the VPP process starts, it'll execute `/etc/vpp/bootstrap.vpp`, which in turn executes these
|
||||||
|
newly generated `/etc/vpp/clab.vpp` to grab the `veth` interfaces, and then `/etc/vpp/vppcfg.vpp` to
|
||||||
|
further configure the dataplane. Easy peasy!
|
||||||
|
|
||||||
|
### Adding VPP to Containerlab
|
||||||
|
|
||||||
|
Roman points out a previous integration for the 6WIND VSR in
|
||||||
|
[[PR#2540](https://github.com/srl-labs/containerlab/pull/2540)]. This serves as a useful guide to
|
||||||
|
get me started. I fork the repo, create a branch so that Roman can also add a few commits, and
|
||||||
|
together we start hacking in [[PR#2571](https://github.com/srl-labs/containerlab/pull/2571)].
|
||||||
|
|
||||||
|
First, I add the documentation skeleton in `docs/manual/kinds/fdio_vpp.md`, which links in from a
|
||||||
|
few other places, and will be where the end-user facing documentation will live. That's about half
|
||||||
|
the contributed LOC, right there!
|
||||||
|
|
||||||
|
Next, I'll create a Go module in `nodes/fdio_vpp/fdio_vpp.go` which doesn't do much other than
|
||||||
|
creating the `struct`, and its required `Register` and `Init` functions. The `Init` function ensures
|
||||||
|
the right capabilities are set in Docker, and the right devices are bound for the container.
|
||||||
|
|
||||||
|
I notice that Containerlab rewrites the Dockerfile `CMD` string and prepends an `if-wait.sh` script
|
||||||
|
to it. This is because when Containerlab starts the container, it'll still be busy adding these
|
||||||
|
`link` interfaces to it, and if a container starts too quickly, it may not see all the interfaces.
|
||||||
|
So, containerlab informs the container using an environment variable called `CLAB_INTFS`, so this
|
||||||
|
script simply sleeps for a while until that exact amount of interfaces are present. Ok, cool beans.
|
||||||
|
|
||||||
|
Roman helps me a bit with Go templating. You see, I think it'll be slick to have the CLI prompt for
|
||||||
|
the VPP containers to reflect their hostname, because normally, VPP will assign `vpp# `. I add the
|
||||||
|
template in `nodes/fdio_vpp/vpp_startup_config.go.tpl` and it only has one variable expansion: `unix
|
||||||
|
{ cli-prompt {{ .ShortName }}# }`. But I totally think it's worth it, because when running many VPP
|
||||||
|
containers in the lab, it could otherwise get confusing.
|
||||||
|
|
||||||
|
Roman also shows me a trick in the function `PostDeploy()`, which will write the user's SSH pubkeys
|
||||||
|
to `/root/.ssh/authorized_keys`. This allows users to log in without having to use password
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
Collectively, we decide to punt on the `SaveConfig` function until we're a bit further along. I have
|
||||||
|
an idea how this would work, basically along the lines of calling `vppcfg dump` and bind-mounting
|
||||||
|
that file into the lab directory somewhere. This way, upon restarting, the YAML file can be re-read
|
||||||
|
and the dataplane initialized. But it'll be for another day.
|
||||||
|
|
||||||
|
After the main module is finished, all I have to do is add it to `clab/register.go` and that's just
|
||||||
|
about it. In about 170 lines of code, 50 lines of Go template, and 170 lines of Markdown, this
|
||||||
|
contribution is about ready to ship!
|
||||||
|
|
||||||
|
### Containerlab: Demo
|
||||||
|
|
||||||
|
After I finish writing the documentation, I decide to include a demo with a quickstart to help folks
|
||||||
|
along. A simple lab showing two VPP instances and two Alpine Linux clients can be found on
|
||||||
|
[[git.ipng.ch/ipng/vpp-containerlab](https://git.ipng.ch/ipng/vpp-containerlab)]. Simply check out the
|
||||||
|
repo and start the lab, like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git clone https://git.ipng.ch/ipng/vpp-containerlab.git
|
||||||
|
$ cd vpp-containerlab
|
||||||
|
$ containerlab deploy --topo vpp.clab.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Containerlab: configs
|
||||||
|
|
||||||
|
The file `vpp.clab.yml` contains an example topology existing of two VPP instances connected each to
|
||||||
|
one Alpine linux container, in the following topology:
|
||||||
|
|
||||||
|
{{< image src="/assets/containerlab/learn-vpp.png" alt="Containerlab Topo" width="100%" >}}
|
||||||
|
|
||||||
|
Two relevant files for each VPP router are included in this
|
||||||
|
[[repository](https://git.ipng.ch/ipng/vpp-containerlab)]:
|
||||||
|
1. `config/vpp*/vppcfg.yaml` configures the dataplane interfaces, including a loopback address.
|
||||||
|
1. `config/vpp*/bird-local.conf` configures the controlplane to enable BFD and OSPF.
|
||||||
|
|
||||||
|
To illustrate these files, let me take a closer look at node `vpp1`. It's VPP dataplane
|
||||||
|
configuration looks like this:
|
||||||
|
```
|
||||||
|
pim@summer:~/src/vpp-containerlab$ cat config/vpp1/vppcfg.yaml
|
||||||
|
interfaces:
|
||||||
|
eth1:
|
||||||
|
description: 'To client1'
|
||||||
|
mtu: 1500
|
||||||
|
lcp: eth1
|
||||||
|
addresses: [ 10.82.98.65/28, 2001:db8:8298:101::1/64 ]
|
||||||
|
eth2:
|
||||||
|
description: 'To vpp2'
|
||||||
|
mtu: 9216
|
||||||
|
lcp: eth2
|
||||||
|
addresses: [ 10.82.98.16/31, 2001:db8:8298:1::1/64 ]
|
||||||
|
loopbacks:
|
||||||
|
loop0:
|
||||||
|
description: 'vpp1'
|
||||||
|
lcp: loop0
|
||||||
|
addresses: [ 10.82.98.0/32, 2001:db8:8298::/128 ]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, I enable BFD, OSPF and OSPFv3 on `eth2` and `loop0` on both of the VPP routers:
|
||||||
|
```
|
||||||
|
pim@summer:~/src/vpp-containerlab$ cat config/vpp1/bird-local.conf
|
||||||
|
protocol bfd bfd1 {
|
||||||
|
interface "eth2" { interval 100 ms; multiplier 30; };
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol ospf v2 ospf4 {
|
||||||
|
ipv4 { import all; export all; };
|
||||||
|
area 0 {
|
||||||
|
interface "loop0" { stub yes; };
|
||||||
|
interface "eth2" { type pointopoint; cost 10; bfd on; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol ospf v3 ospf6 {
|
||||||
|
ipv6 { import all; export all; };
|
||||||
|
area 0 {
|
||||||
|
interface "loop0" { stub yes; };
|
||||||
|
interface "eth2" { type pointopoint; cost 10; bfd on; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Containerlab: playtime!
|
||||||
|
|
||||||
|
Once the lab comes up, I can SSH to the VPP containers (`vpp1` and `vpp2`) which have my SSH pubkeys
|
||||||
|
installed thanks to Roman's work. Barring that, I could still log in as user `root` using
|
||||||
|
password `vpp`. VPP runs its own network namespace called `dataplane`, which is very similar to SR
|
||||||
|
Linux default `network-instance`. I can join that namespace to take a closer look:
|
||||||
|
|
||||||
|
```
|
||||||
|
pim@summer:~/src/vpp-containerlab$ ssh root@vpp1
|
||||||
|
root@vpp1:~# nsenter --net=/var/run/netns/dataplane
|
||||||
|
root@vpp1:~# ip -br a
|
||||||
|
lo DOWN
|
||||||
|
loop0 UP 10.82.98.0/32 2001:db8:8298::/128 fe80::dcad:ff:fe00:0/64
|
||||||
|
eth1 UNKNOWN 10.82.98.65/28 2001:db8:8298:101::1/64 fe80::a8c1:abff:fe77:acb9/64
|
||||||
|
eth2 UNKNOWN 10.82.98.16/31 2001:db8:8298:1::1/64 fe80::a8c1:abff:fef0:7125/64
|
||||||
|
|
||||||
|
root@vpp1:~# ping 10.82.98.1
|
||||||
|
PING 10.82.98.1 (10.82.98.1) 56(84) bytes of data.
|
||||||
|
64 bytes from 10.82.98.1: icmp_seq=1 ttl=64 time=9.53 ms
|
||||||
|
64 bytes from 10.82.98.1: icmp_seq=2 ttl=64 time=15.9 ms
|
||||||
|
^C
|
||||||
|
--- 10.82.98.1 ping statistics ---
|
||||||
|
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
|
||||||
|
rtt min/avg/max/mdev = 9.530/12.735/15.941/3.205 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
From `vpp1`, I can tell that Bird2's OSPF adjacency has formed, because I can ping the `loop0`
|
||||||
|
address of `vpp2` router on 10.82.98.1. Nice! The two client nodes are running a minimalistic Alpine
|
||||||
|
Linux container, which doesn't ship with SSH by default. But of course I can still enter the
|
||||||
|
containers using `docker exec`, like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
pim@summer:~/src/vpp-containerlab$ docker exec -it client1 sh
|
||||||
|
/ # ip addr show dev eth1
|
||||||
|
531235: eth1@if531234: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 9500 qdisc noqueue state UP
|
||||||
|
link/ether 00:c1:ab:00:00:01 brd ff:ff:ff:ff:ff:ff
|
||||||
|
inet 10.82.98.66/28 scope global eth1
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 2001:db8:8298:101::2/64 scope global
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::2c1:abff:fe00:1/64 scope link
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
/ # traceroute 10.82.98.82
|
||||||
|
traceroute to 10.82.98.82 (10.82.98.82), 30 hops max, 46 byte packets
|
||||||
|
1 10.82.98.65 (10.82.98.65) 5.906 ms 7.086 ms 7.868 ms
|
||||||
|
2 10.82.98.17 (10.82.98.17) 24.007 ms 23.349 ms 15.933 ms
|
||||||
|
3 10.82.98.82 (10.82.98.82) 39.978 ms 31.127 ms 31.854 ms
|
||||||
|
|
||||||
|
/ # traceroute 2001:db8:8298:102::2
|
||||||
|
traceroute to 2001:db8:8298:102::2 (2001:db8:8298:102::2), 30 hops max, 72 byte packets
|
||||||
|
1 2001:db8:8298:101::1 (2001:db8:8298:101::1) 0.701 ms 7.144 ms 7.900 ms
|
||||||
|
2 2001:db8:8298:1::2 (2001:db8:8298:1::2) 23.909 ms 22.943 ms 23.893 ms
|
||||||
|
3 2001:db8:8298:102::2 (2001:db8:8298:102::2) 31.964 ms 30.814 ms 32.000 ms
|
||||||
|
```
|
||||||
|
|
||||||
|
From the vantage point of `client1`, the first hop represents the `vpp1` node, which forwards to
|
||||||
|
`vpp2`, which finally forwards to `client2`, which shows that both VPP routers are passing traffic.
|
||||||
|
Dope!
|
||||||
|
|
||||||
|
## Results
|
||||||
|
|
||||||
|
After all of this deep-diving, all that's left is for me to demonstrate the Containerlab by means of
|
||||||
|
this little screencast [[asciinema](/assets/containerlab/vpp-containerlab.cast)]. I hope you enjoy
|
||||||
|
it as much as I enjoyed creating it:
|
||||||
|
|
||||||
|
{{< asciinema src="/assets/containerlab/vpp-containerlab.cast" >}}
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
|
||||||
|
I wanted to give a shout-out Roman Dodin for his help getting the Containerlab parts squared away
|
||||||
|
when I got a little bit stuck. He took the time to explain the internals and idiom of Containerlab
|
||||||
|
project, which really saved me a tonne of time. He also pair-programmed the
|
||||||
|
[[PR#2471](https://github.com/srl-labs/containerlab/pull/2571)] with me over the span of two
|
||||||
|
evenings.
|
||||||
|
|
||||||
|
Collaborative open source rocks!
|
BIN
static/assets/containerlab/learn-vpp.png
(Stored with Git LFS)
Normal file
BIN
static/assets/containerlab/learn-vpp.png
(Stored with Git LFS)
Normal file
Binary file not shown.
1270
static/assets/containerlab/vpp-containerlab.cast
Normal file
1270
static/assets/containerlab/vpp-containerlab.cast
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user