An opinionated implementation of the OpenVPN protocol
OCaml
80
780 commits
updated Sep 14, 2026
MirageVPN creates secure point-to-point or site-to-site connections in routed or bridged configurations and remote access facilities. It uses TLS to establish a (mutually) authenticated connection, over which material to derive the symmetric keys for packet encryption is exchanged. The protocol is compatible with OpenVPN™.
The goal of this project is to provide:
We provide a handbook to guide you to install MirageVPN.
Our goal is not to implement the complete protocol, but rather a small useful subset with modern crypto and the latest key exchange methods, without deprecated or redundant features (embodying the philosophy of OCaml-tls). An initial draft of the network setup is depicted in the diagram below:
Since OpenVPN™ is not detailed in a protocol specificaton specified, apart from comments in the header files, we have written a specification document in Markdown, still work in progress.
Our configuration parser can be tested with an OpenVPN™ configuration file:
./_build/default/app/openvpn_config_parser.exe my.openvpn.confmiragevpn_client_lwtIncluded in this repository is a unix program that will connect to an
OpenVPN™ server, open a tun interface, and tunnel packets between
the two.
There are two ways to open tun interfaces:
dev tun).
In order to dynamically allocate a tun interface, the process will need
privileges to do so. Either by running the client as root or with
the CAP_NET_ADMIN privilege.
You would then add dev tun to your configuration file.dev tunX)
This is the recommend configuration.
To allocate such an interface for tun5 you can use this command:
sudo ip tuntap add mode tun user MYUSERNAME name tun5
You would then add dev tun5 to your configuration file.dune build
# Bestowing the binary with CAP_NET_ADMIN if using dynamic tun allocation:
sudo setcap cap_net_admin=ep ./_build/default/app/miragevpn_client_lwt.exe
./_build/default/app/miragevpn_client_lwt.exe -v MY-CONFIG-FILE.CONF
Our goal has been to implement a usable subset (as found in various real-world configuration files available to us during the implementation phase).
As far as possible we have strived to derive a representation that does not
permit ambiguity or conflicting options to be present in the parsed config.
Consult the type 'a k declaration in openvpn_config.mli for more
information.
This does not mean that conflicting options cannot be accepted from an on-disk
configuration file, but rather that such conflicts are explicitly handled in
the parser code (specifically in the resolve_conflict function).
A notable difference from OpenVPN™ configuration parser is that we treat relative
paths in a configuration file to be relative to the configuration file
location, and not relative to the current working directory. OpenVPN™ supports
a --cd argument, which we do not.
You can check compatibility with your configuration file by executing
dune build
./_build/default/app/openvpn_config_parser.exe MY-CONFIG-FILE.CONF
The "verify-x509-name name" in OpenVPN™ checks by default only the commonName of the subject in the X.509 certificate. MirageVPN validates the provided host against the set of hostnames in the certificate, namely the union of the commonName and the DNS entries in the SubjectAlternativeName extension.
When using a PKCS#12 file the certificates in it are not used to authenticate
the remote. OpenVPN™ will use the certificates if (and only if) no "ca" option
is specified. If it is desired to use the certificates from the PKCS#12 file
to authenticate the remote the certificates can be added with the "ca" option
by extracting the certificates with e.g. openssl pkcs12.
For tls clients (as opposed to static key clients) we only support data channel
AEAD ciphers. This means the --auth option is ignored for data channel
authentication for tls clients. For --tls-auth it is still used to choose the
hmac used for the control channel while for --tls-crypt and --tls-crypt-v2
the hmac is hardcoded (as per OpenVPN™).
If a "port" is specified, "lport" and "rport" may not be specified.
If a "remote" is specified with an IP address and no protocol (and thus no address family), and a default "proto" with an IP address family is specified, we ignore that default IP address family:
proto udpv6
remote 127.0.0.1
We consider this as "remote 127.0.0.1 udp4".
This project was funded in 2019 for six months by the German federal ministry for education and research via the Prototypefund - the amount was 47500 EUR.
In 2023, we received funding from European Union in the Next Generation Internet project (NGI assure, via NLnet. The scope was updating to the current protocol version (tls-crypt-v2 etc.), a QubesOS client, a server implementation, and more documentation. The amount was 57000 EUR. Learn more at the NLnet project page.
OCaml
99.3%
An opinionated implementation of the OpenVPN protocol
OCaml
80
780 commits
updated Sep 14, 2026
MirageVPN creates secure point-to-point or site-to-site connections in routed or bridged configurations and remote access facilities. It uses TLS to establish a (mutually) authenticated connection, over which material to derive the symmetric keys for packet encryption is exchanged. The protocol is compatible with OpenVPN™.
The goal of this project is to provide:
We provide a handbook to guide you to install MirageVPN.
Our goal is not to implement the complete protocol, but rather a small useful subset with modern crypto and the latest key exchange methods, without deprecated or redundant features (embodying the philosophy of OCaml-tls). An initial draft of the network setup is depicted in the diagram below:
Since OpenVPN™ is not detailed in a protocol specificaton specified, apart from comments in the header files, we have written a specification document in Markdown, still work in progress.
Our configuration parser can be tested with an OpenVPN™ configuration file:
./_build/default/app/openvpn_config_parser.exe my.openvpn.confmiragevpn_client_lwtIncluded in this repository is a unix program that will connect to an
OpenVPN™ server, open a tun interface, and tunnel packets between
the two.
There are two ways to open tun interfaces:
dev tun).
In order to dynamically allocate a tun interface, the process will need
privileges to do so. Either by running the client as root or with
the CAP_NET_ADMIN privilege.
You would then add dev tun to your configuration file.dev tunX)
This is the recommend configuration.
To allocate such an interface for tun5 you can use this command:
sudo ip tuntap add mode tun user MYUSERNAME name tun5
You would then add dev tun5 to your configuration file.dune build
# Bestowing the binary with CAP_NET_ADMIN if using dynamic tun allocation:
sudo setcap cap_net_admin=ep ./_build/default/app/miragevpn_client_lwt.exe
./_build/default/app/miragevpn_client_lwt.exe -v MY-CONFIG-FILE.CONF
Our goal has been to implement a usable subset (as found in various real-world configuration files available to us during the implementation phase).
As far as possible we have strived to derive a representation that does not
permit ambiguity or conflicting options to be present in the parsed config.
Consult the type 'a k declaration in openvpn_config.mli for more
information.
This does not mean that conflicting options cannot be accepted from an on-disk
configuration file, but rather that such conflicts are explicitly handled in
the parser code (specifically in the resolve_conflict function).
A notable difference from OpenVPN™ configuration parser is that we treat relative
paths in a configuration file to be relative to the configuration file
location, and not relative to the current working directory. OpenVPN™ supports
a --cd argument, which we do not.
You can check compatibility with your configuration file by executing
dune build
./_build/default/app/openvpn_config_parser.exe MY-CONFIG-FILE.CONF
The "verify-x509-name name" in OpenVPN™ checks by default only the commonName of the subject in the X.509 certificate. MirageVPN validates the provided host against the set of hostnames in the certificate, namely the union of the commonName and the DNS entries in the SubjectAlternativeName extension.
When using a PKCS#12 file the certificates in it are not used to authenticate
the remote. OpenVPN™ will use the certificates if (and only if) no "ca" option
is specified. If it is desired to use the certificates from the PKCS#12 file
to authenticate the remote the certificates can be added with the "ca" option
by extracting the certificates with e.g. openssl pkcs12.
For tls clients (as opposed to static key clients) we only support data channel
AEAD ciphers. This means the --auth option is ignored for data channel
authentication for tls clients. For --tls-auth it is still used to choose the
hmac used for the control channel while for --tls-crypt and --tls-crypt-v2
the hmac is hardcoded (as per OpenVPN™).
If a "port" is specified, "lport" and "rport" may not be specified.
If a "remote" is specified with an IP address and no protocol (and thus no address family), and a default "proto" with an IP address family is specified, we ignore that default IP address family:
proto udpv6
remote 127.0.0.1
We consider this as "remote 127.0.0.1 udp4".
This project was funded in 2019 for six months by the German federal ministry for education and research via the Prototypefund - the amount was 47500 EUR.
In 2023, we received funding from European Union in the Next Generation Internet project (NGI assure, via NLnet. The scope was updating to the current protocol version (tls-crypt-v2 etc.), a QubesOS client, a server implementation, and more documentation. The amount was 57000 EUR. Learn more at the NLnet project page.
OCaml
99.3%