Wireguard-Haskell: Getting Started

Why Starting this Project?

After finishing DobadoBots, I was looking for a Haskell project in which I could be confronted with some performance and parallelism problems.

Wireguard seemed to be the perfect project for that. At the time, no userspace implementation was available, the specification seemed to be simple enough for being implemented by a single person in a couple of months.

Furthermore, a work in progress Haskell implementation called Nara was already available. The only contributor to this project sadly abandoned it, undocumented, several months ago. This is a great opportunity for me to complete what he started.

Starting Point

The userspace implementation is not providing a CLI by itself. Instead, we are using the wg CLI utility to communicate with the VPN daemon through a Unix socket.

The overall architecture looks like this.

Nara architecture overview

We first need to test this RPC interface implementation. It was sadly broken. The RPC protocol changed and these changes were not reflected in the Haskell implementation.

We clearly need to first re-implement and test this interface.

Refactoring the RPC Interface

The new protocol specified here is text-based. Both the Wireguard device and peers are described using a several key/value couples.

We process the incoming data as a Conduit stream. Conduit comes with the conduit-attoparsec module, which let us parse the incoming stream using an Attoparsec monadic parser.

Attoparsec is based around the same concept as Parsec. The main difference being that while Parsec aims to parse user-generated inputs, Attoparsec targets machine to machine data formats. It is hence faster, at the cost of a weaker error reporting system.

You can find the implementation details of this parser in Nara’s git repository.

The four main takeaways from this implementation are:

  1. Parsing an IP address is not a trivial task. You need to be extra cautious, especially while parsing an IPv6 address.
  2. Do not hesitate to create a lot of sub-parsers. It makes the code both easily testable and more readable.
  3. You need to be extra cautious with your test cases. Test extensively the edge cases. It is dead easy to screw things up when using a Parsec-like library.
  4. The Alternative typeclass will hold your back while dealing with optional repetitive options. This typeclass is implemented by the Parser type: you can use it with everywhere.

Next steps

We need to re-implement the queue system. As it is, the packets are multiplexed in one TUN queue and one UDP queue. It creates a lot of latency in case of a handshake: we need the handshake to successfully complete before sending any data, regardless the peer we want to send the data to.

We first need to design a new queue system. When implementing it, we will need to test it extra carefully. It may be a good excuse to dive into property-based testing techniques.