I love seeing BGP pushed to the edges of its intended use-case, and I’d been wanting to learn Go for a while. I’d also been frustrated that I couldn’t ingest BGP routes on my Windows desktop - it’s only included in the server OS versions. So I decided that my holiday project would be extending GoBGP to get my network’s BGP routes into my Windows 10 desktop’s routing table.
I’m also a huge fan of “Why not?” projects. This one falls firmly in that camp!
I think this is also the first instance of BGP running on a Windows desktop SKU? I hadn’t been able to find any other way before this (otherwise I’d have used it) but it could just be The Algorithm thinking I’m looking specifically for Windows Server BGP.
This was a fun project. I mistakenly assumed GoBGP worked more or less like Free Range Routing with Zebra, that it already modified the route table of the host it’s running on. This was incorrect - it was a pure route server only - so I got to add a bit more code than I otherwise would have.
Adding Windows support
Go has great cross-platform support, except when you use some unabstracted details.
GoBGP uses some unabstracted details.
The Windows build was failing with some incompatible syscall concepts. I searched the GoBGP issue tracker and found that joshuaclausen had already found the same issues and proposed a fix (thanks Josh), but it was never merged because there was some undesired MD5 code in the commits.
From here I started looking at the failing files and worked out the project’s
cross-platform support pattern: it’s basically
<filename>_<platform>.go with different
build constraints to only use each file when building for
the selected platform. I used Josh’s PR as a base, made the minimum required changes,
made sure existing builds were unaffected and
added it to
At this point GoBGP would run on Windows and act like a full route server, but not do anything with them yet. Noice.
Adding host-route-table modification
I had routes in a structured Go format, now I needed to modify the Windows routing table. Ideally I wanted to avoid adding extra dependencies but this isn’t in the standard library at time of writing. I searched around and out of the options I found I selected the Windows routing package of the wireguard-go project, because if you can’t trust Jason Donenfeld who can you trust. I whipped up a quick simple test to confirm it worked and moved onto planning the code structure.
I reviewed the existing GoBGP code and found that the existing Zebra integration (GoBGP can’t modify the Linux routing table directly but can interface with Zebra/FRR to do it) was a good reference for it. This is where the project got bigger and I got to learn a little more than I was expecting.
It turns out that GoBGP generates its configuration from a YANG model - a data modelling language for the enterprise networking domain. I hadn’t seen this done before but it makes good sense - the underlying BGP configuration across network devices is approximately standards-based and that’s what most of the configuration of a BGP route server will involve. Way less reinventing the wheel. I read through the overview in the spec, extended the GoBGP model and rendered it to Go code.
Sidenote: YANG is neat. It has re-use, imports with smooth modifications, comments (hi json). Section 4 of the RFC is a great, readable overview. Also - this statement:
YANG resists the tendency to solve all possible problems, limiting the problem space to allow expression of data models for network management protocols
Tracing out the Zebra integration (called zapi from here on) showed the following:
/tools/pyang_plugins/gobgp.yangdefines the user-facing configuration options for Zebra in YANG format
- pyyang converts this into Go structures in
- If Zebra is enabled,
NewZebraClient()to create a new Zebra client and add it to the
pkg/server/zclient.gosets up the connection to Zebra and kicks off
loop()as a goroutine
pkg/server/zclient.gowatches for events from
BgpServerand the Zebra daemon and triggers a matching effect on the opposite side
I mirrored the zapi integration to get the loop up, then used the wireguard-go wrapper around the native Windows route table API to inject learned routes. Seeing BGP-learned routes in a Windows desktop OS’s routing table for the first time felt good. It felt cursed. It felt cursedgood.
After that I added an extra feature that the zapi code doesn’t have - when GoBGP shuts down cleanly the routes are removed for cleanliness. The stop code was fun to add - I got to use some of the newerish higher-level Go concurrency additions. The loop is stopped via a cancelFunc passed via context, and the loop stopping is awaited via a WaitGroup. From what I understand this can be done with basic channels, but the newer methods scale better?
You can see the full changes here.
Using it yourself
This change hasn’t been merged to upstream yet but you can grab it from a branch on my fork (words are fun). This assumes you’re on Windows 10:
# install go from https://go.dev/doc/install # then restart your powershell instance to ensure PATH is updated cd ~\Documents git clone https://github.com/GSGBen/gobgp.git cd gobgp git checkout 1-inject-received-routes-into-the-windows-routing-table go build .\cmd\gobgp\ go build .\cmd\gobgpd\
Create a config file in the same directory called
gobgp.toml, changing the bits to
match your external BGP peer:
# the BGP details of the desktop you're running GoBGP on [global.config] as = 65001 router-id = "10.1.1.10" [[neighbors]] [neighbors.config] neighbor-address = "10.1.1.20" peer-as = 65002 [experimental] [experimental.modify-host-fib] [experimental.modify-host-fib.config] enabled = true
Run a new powershell instance as administrator (required to modify the routing table) then start gobgp:
cd ~\Documents\gobgp\ .\gobgpd.exe -f gobgp.toml
If your BGP settings were correct you should see some routes come through in a bit. And
route print should now show them!
Limitations and extending
This currently only adds routes to the route table - I didn’t have the scope to redistribute routes because I’d want filtering in place first.
It also only injects IPv4 routes. I don’t have a full IPv6 environment any more to test
with so I scoped it down. Theoretically this should just be another address family case
and a few functions you could add in
The full dev documentation can be found here.
VideogamesHey, do you like videogames? If so please check out my game Grab n' Throw on Steam, and add it to your wishlist. One gamemode is like golf but on a 256 km^2 landscape, with huge throw power, powerups, and a moving hole. Another is like dodgeball crossed with soccer except instead of balls you throw stacks of your own teammates. And there's plenty more!
See full gameplay on Steam!