The reincarnation of OpenVPN’s C++ library
At Mysterium Network we are working on the world’s 1st decentralized VPN. Our project is built on Golang (Go). Go is a statically compiled language, which offers a rich standard library. Go is syntactically similar to C but comes out as the winner when it comes to memory safety, garbage collection, structural typing, and CSP style concurrency.
There are many libraries written in C or C++. When you wish to use these libraries within Golang, there are two approaches:
Rewrite the library in Golang
Several projects have gone down this road. Wireguard® has done this, check out some of their libraries.
Reuse the code in a way that Golang can call it.
There are other tools that can help with calling java or objective C code into Golang, but everything goes through an intermediary. At a fundamental level, there is interoperability between C and Golang.
In this article, we will be talking about integrating C++ OpenVPN 3 library into a Golang Mysterium Node.
As mentioned above, we are using OpenVPN under the hood. This was our first protocol and it was used as an external binary (executable file).
This basically means that a Mysterium Node and OpenVPN are two different processes which communicate using OpenVPN config and IPC (local sockets to be exact).
Now, this has some limitations — for example, software distribution becomes complicated as you also need to distribute OpenVPN binary with each Mysterium Node — two steps, never great for UX.
It was workable for a proof of concept or very early versions, but as we moved to mobile platforms, this approach became very complicated or even not feasible — especially when considering iOS.
To solve this challenge, we decided to find a way to integrate OpenVPN into our Golang project directly. Also, we decided that this package could be useful for others, that’s how this library was born.
Openvpn3 to the rescue.
Openvpn3 is the official library maintained by OpenVPN team and is being used in almost all platforms as client or connector to OpenVPN server. Also, it’s written in C++ which came with some obstacles we needed to solve.
Golang and C++ don’t get along
Our first obstacle was that C++ code cannot be directly called by Golang (Cgo to be exact).
We needed to make small changes to the OpenVPN library itself to export OpenVPN Client as C callable code. This can be found here, and it’s basically a go compatible entry point to the OpenVPN library.
Then there is how Golang treats C code itself (cgo).
The problem was that Golang and it’s package management systems expect that all libraries are source files (i.e. there is no or very limited binary package management). And OpenVPN3 library build process was very over complicated and not easily expressed in a Go way.
So our decision was to compile that library in advance for all platforms we currently support or produce binaries for (arm family (android ios), amd64 family (Windows, Linux, some simulators). As we use Linux for our automatic build system, we had to set up all compilers and SDKs in one place — but that’s for another blog post. Sign up to our newsletter to hear more about what we’re building.
Our heavily patched docker image is heavily borrowed from Karalabe. The result was a single header file (very simple) and a bunch of static libraries for each platform/OS we needed.
We also had to ensure that these binaries were Go gettable (the go way to fetch a library from GitHub).
We simply committed those libraries to Go repo along with all supporting Go code (which is available at mysterium.network/go-openvpn/openvpn3). Not the best way to distribute the software, but our target was a go gettable library.
Now the easy part 😏 — to call Openvpn3 functions from Go.
It’s quite easy doable. The following examples are simple calls of C functions exported by OpenVPN library (our C wrapper):
And here come problems:
- First of all, strict rules as to what can and cannot be passed to C code and vice versa, for example — you cannot pass go function reference to C code.
- The openvpn3 client also depends heavily on callback functions. One way to approach this was to use only static functions for callbacks. However, this would have limited the flexibility and usability of the library.
A hybrid solution was to define customisable callback functions in Go and register them in a map with function ids. Static functions in the OpenVPN3 client would then dispatch respective callbacks to registered functions with corresponding ids.
Here is how it works (let’s take state event callback function as an example):
User defines normal go structures with methods, which satisfies interfaces expected by callback registry:
Structure is passed to callback registry which is essentially global id -> callback map:
What happens next, callbacks registry inserts user provided structure with methods, and creates a C structure, ready to be passed to C code, but instead of passing go function reference to C code, it passes id which is simply key to callback map and an exported go function (with special comment).
When C code wants to inform user of state changes, it calls static go function and one of the parameters is id. That id is then passed to callback registry to find and call apprioprate user defined callback.
It compiled. At least the Go part — that means that C code is reachable, and all headers are ok.
Most of the dragons started rearing their heads when it came to linking the Go packages with OpenVPN static libraries.
The biggest issue was that — the library was built with C++ compiler, but golang cgo used C compiler by default. As a result, all weird and ugly errors began to raise at the linking stage. So if you see similar errors as in example — you are not alone:
After hours of stack overflow exploration, a simple workaround was to put a empty .cpp file inside the package which uses “C” imports. That way cgo was tricked into using the c++ linker which already had c++ library by default.
There are several other issues we faced but that again is for another blog post. Stay tuned.
When using new technologies like Golang you have to sometimes go off-chain to find solutions that will help you use existing libraries so that you don’t have to start everything from scratch. However, as most solutions in IT, it’s not a silver bullet.
- Precompiled libraries on their own poses security risk — potential library users cannot be sure what is exactly compiled in, as there is no code to review
- Each OS and architecture combination has to have a separate version of the same library
- iOS framework problem — iOS framework lib (provided by gomobile tool) is a static library itself. So any other dependencies are linked but not combined into the framework — need to do it as a separate step
- It’s simply not a go way — golang usually expects all source needed for the package, to be in one place.
Connect with our project
Please be sure to follow and subscribe to the following:
Website — https://mysterium.network
Twitter — https://twitter.com/MysteriumNet
Telegram — https://t.me/Mysterium_Network
Facebook — https://www.facebook.com/MysteriumNet
Steemit — https://steemit.com/@mysteriumnetwork
Bitcointalk — https://bitcointalk.org/index.php?topic=1895626.0
Please join the Telegram groups most relevant to you and engage with our team. We want to hear from you.
English — https://t.me/Mysterium_Network
Rules & FAQ — https://t.me/MysteriumRulesAndFAQ
Announcements — https://t.me/MysteriumOfficialAnnouncements
Node Testing — https://t.me/mysterium_network_nodes
MysteriumVPN Testing — https://t.me/joinchat/I5-aG0z_3SA6PLgQBCOXlA
中文 / Chinese — https://t.me/MysteriumChineseChat
русский / Russian https://t.me/mystRU
Español / Spanish — https://t.me/mysterium_network_espanol
And finally, if you’d like to see more of these types of updates give us some claps and let us know.
*WireGuard” and the “WireGuard” logo are registered trademarks of Jason A. Donenfeld.