Testing MirageVPN against OpenVPN™
+As our last milestone for the EU NGI Assure funded MirageVPN project (for now) we have been working on testing MirageVPN, our OpenVPN™-compatible VPN implementation against the upstream OpenVPN™. +During the development we have conducted many manual tests. +However, this scales poorly and it is easy to forget testing certain cases. +Therefore, we designed and implemented interoperability testing, driving the C implementation on the one side, and our OCaml implementation on the other side. The input for such a test is a configuration file that both implementations can use. +Thus we test establishment of the tunnel as well as the tunnel itself.
+While conducting the tests, our instrumented binaries expose code coverage information. We use that to guide ourselves which other configurations are worth testing. Our goal is to achieve a high code coverage rate while using a small amount of different configurations. These interoperability tests are running fast enough, so they are executed on each commit by CI.
+A nice property of this test setup is that it runs with an unmodified OpenVPN binary. +This means we can use an off-the-shelf OpenVPN binary from the package repository and does not entail further maintenance of an OpenVPN fork. +Testing against a future version of OpenVPN becomes trivial. +We do not just test a single part of our implementation but achieve an end-to-end test. +The same configuration files are used for both our implementation and the C implementation, and each configuration is used twice, once our implementation acts as the client, once as the server.
+We added a flag to our client and our recently finished server applications, --test
, which make them to exit once a tunnel is established and an ICMP echo request from the client has been replied to by the server.
+Our client and server can be run without a tun device which otherwise would require elevated privileges.
+Unfortunately, OpenVPN requires privileges to at least configure a tun device.
+Our MirageVPN implementation does IP packet parsing in userspace.
+We test our protocol implementation, not the entire unikernel - but the unikernel code is a tiny layer on top of the purely functional protocol implementation.
We explored unit testing the packet decoding and decryption with our implementation and the C implementation. +Specifically, we encountered a packet whose message authentication code (MAC) was deemed invalid by the C implementation. +It helped us discover the MAC computation was correct but the packet encoding was truncated - both implementations agreed that the MAC was bad. +The test was very tedious to write and would not easily scale to cover a large portion of the code. +If of interest, take a look into our modifications to OpenVPN and modifications to MirageVPN.
+The end-to-end testing is in addition to our unit tests and fuzz testing; and to our benchmarking binary.
+Our results are that with 4 configurations we achieve above 75% code coverage in MirageVPN. +While investigating the code coverage results, we found various pieces of code that were never executed, and we were able to remove them. +Code that does not exist is bug-free :D +With these tests in place future maintenance is less daunting as they will help us guard us from breaking the code.
+At the moment we do not exercise the error paths very well in the code.
+This is much less straightforward to test in this manner, and is important future work.
+We plan to develop a client and server that injects faults at various stages of the protocol to test these error paths.
+OpenVPN built with debugging enabled also comes with a --gremlin
mode that injects faults, and would be interesting to investigate.