Skip to content

Commit

Permalink
Rename to MirageVPN
Browse files Browse the repository at this point in the history
  • Loading branch information
reynir committed Mar 17, 2023
1 parent e45b9db commit e7dea54
Show file tree
Hide file tree
Showing 28 changed files with 172 additions and 172 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ services:
sudo: false
env:
global:
- PACKAGE="openvpn"
- PACKAGE="miragevpn"
- TESTS=false
- DISTRO=alpine
matrix:
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
## OpenVPN library purely in OCaml
## OpenVPN-compatible library purely in OCaml

OpenVPN creates secure point-to-point or site-to-site connections in routed or bridged configurations and remote access facilities.
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 goal of this project is to provide:
- A pure library implementing the protocol logic, and the OpenVPN config file format to enable interoperabilty and a smooth transition for existing deployments.
- A [MirageOS](https://mirage.io) unikernel that acts as an OpenVPN client.
- A [MirageOS](https://mirage.io) unikernel that acts as an OpenVPN-compatible client.

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 [nqsb-tls](https://nqsb.io)). An initial draft of the network setup is depicted in the diagram below:
Expand All @@ -14,13 +14,13 @@ Our goal is not to implement the complete protocol, but rather a small useful su

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:

- [spec.md](https://git.robur.io/?p=openvpn-spec.git;a=blob_plain;f=spec.md;hb=HEAD)
- [spec.md](https://git.robur.io/robur/openvpn-spec/src/branch/master/spec.md)

Our OpenVPN configuration parser can be tested with an OpenVPN configuration file:

- `./_build/default/app/openvpn_config_parser.exe my.openvpn.conf`

# Unix client `openvpn_client_lwt`
# Unix client `miragevpn_client_lwt`

Included in this repository is a unix program that will connect to an
OpenVPN server, open a `tun` interface, and tunnel packets between
Expand All @@ -46,9 +46,9 @@ There are two ways to open `tun` interfaces:
dune build

# Bestowing the binary with CAP_NET_ADMIN if using dynamic tun allocation:
sudo setcap cap_net_admin=ep ./_build/default/app/openvpn_client_lwt.exe
sudo setcap cap_net_admin=ep ./_build/default/app/miragevpn_client_lwt.exe

./_build/default/app/openvpn_client_lwt.exe -v MY-CONFIG-FILE.CONF
./_build/default/app/miragevpn_client_lwt.exe -v MY-CONFIG-FILE.CONF
```

# OpenVPN-compatible config parser
Expand Down
14 changes: 7 additions & 7 deletions app/dune
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
(executable
(name openvpn_client_lwt)
(public_name openvpn-client-lwt)
(package openvpn)
(modules openvpn_client_lwt)
(libraries openvpn lwt lwt.unix cmdliner fmt.tty logs.fmt logs.cli fmt.cli
(name miragevpn_client_lwt)
(public_name miragevpn-client-lwt)
(package miragevpn)
(modules miragevpn_client_lwt)
(libraries miragevpn lwt lwt.unix cmdliner fmt.tty logs logs.fmt logs.cli fmt.cli
ipaddr ipaddr.unix ptime.clock.os dns-client-lwt
mirage-crypto-rng-lwt mtime.clock.os tuntap cstruct-lwt))

(executable
(name openvpn_config_parser)
(public_name openvpn-config-parser)
(package openvpn)
(package miragevpn)
(modules openvpn_config_parser)
(libraries openvpn logs.fmt fmt.tty rresult))
(libraries miragevpn logs logs.fmt fmt.tty rresult))
36 changes: 18 additions & 18 deletions app/openvpn_client_lwt.ml → app/miragevpn_client_lwt.ml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
open Lwt.Infix

let open_tun config { Openvpn.cidr ; gateway }
: (Openvpn.Config.t * Lwt_unix.file_descr, [> `Msg of string]) Lwt_result.t =
let open_tun config { Miragevpn.cidr ; gateway }
: (Miragevpn.Config.t * Lwt_unix.file_descr, [> `Msg of string]) Lwt_result.t =
(* This returns a Config with updated MTU, and a file descriptor for
the TUN interface *)
let open Lwt_result.Infix in
begin match Openvpn.Config.find Dev config with
begin match Miragevpn.Config.find Dev config with
| None | Some (`Tun, None) -> Ok None
| Some (`Tun, Some name) -> Ok (Some name)
| Some (`Tap, name) ->
Expand All @@ -21,9 +21,9 @@ let open_tun config { Openvpn.cidr ; gateway }
Tuntap.set_up_and_running dev;
Logs.debug (fun m -> m "set TUN interface up and running");
(* TODO set the mtu of the device *)
let config = match Openvpn.Config.find Tun_mtu config with
let config = match Miragevpn.Config.find Tun_mtu config with
| Some _mtu -> (*Tuntap.set_mtu dev mtu TODO ; *) config
| None -> Openvpn.Config.add Tun_mtu (Tuntap.get_mtu dev) config in
| None -> Miragevpn.Config.add Tun_mtu (Tuntap.get_mtu dev) config in
begin
(* TODO factor the uname -s out into a separate library *)
let local = Ipaddr.V4.to_string (Ipaddr.V4.Prefix.address cidr)
Expand Down Expand Up @@ -236,12 +236,12 @@ let connect_udp ip port =
Unix.ADDR_INET (unix_ip, port), fd

type conn = {
mutable o_client : Openvpn.t ;
mutable o_client : Miragevpn.t ;
mutable peer : ([ `Udp of Lwt_unix.sockaddr * Lwt_unix.file_descr | `Tcp of Lwt_unix.file_descr ]) option ;
mutable est_switch : Lwt_switch.t ;
data_mvar : Cstruct.t list Lwt_mvar.t ;
est_mvar : (Openvpn.ip_config * int) Lwt_mvar.t ;
event_mvar : Openvpn.event Lwt_mvar.t ;
est_mvar : (Miragevpn.ip_config * int) Lwt_mvar.t ;
event_mvar : Miragevpn.event Lwt_mvar.t ;
}

let safe_close fd =
Expand Down Expand Up @@ -293,21 +293,21 @@ let handle_action conn = function
| `Exit -> Lwt.fail_with "exit called"
| `Payload data -> Lwt_mvar.put conn.data_mvar data
| `Established (ip, mtu) ->
Logs.app (fun m -> m "established %a" Openvpn.pp_ip_config ip);
Logs.app (fun m -> m "established %a" Miragevpn.pp_ip_config ip);
Lwt_mvar.put conn.est_mvar (ip, mtu)

let rec event conn =
Logs.info (fun m -> m "processing event");
Lwt_mvar.take conn.event_mvar >>= fun ev ->
Logs.info (fun m -> m "now for real processing event %a" Openvpn.pp_event ev);
match Openvpn.handle conn.o_client ev with
Logs.info (fun m -> m "now for real processing event %a" Miragevpn.pp_event ev);
match Miragevpn.handle conn.o_client ev with
| Error e ->
Logs.err (fun m -> m "openvpn handle failed %a" Openvpn.pp_error e);
Logs.err (fun m -> m "miragevpn handle failed %a" Miragevpn.pp_error e);
Lwt.return_unit
| Ok (t', outs, action) ->
conn.o_client <- t';
Logs.info (fun m -> m "handling action %a"
Fmt.(option ~none:(any "none") Openvpn.pp_action) action);
Fmt.(option ~none:(any "none") Miragevpn.pp_action) action);
(match outs with
| [] -> ()
| _ ->
Expand Down Expand Up @@ -341,7 +341,7 @@ let send_recv conn config ip_config _mtu =
let buf = Cstruct.create 1500 in
Lwt_cstruct.read tun_fd buf |> Lwt_result.ok
>|= Cstruct.sub buf 0 >>= fun buf ->
match Openvpn.outgoing conn.o_client buf with
match Miragevpn.outgoing conn.o_client buf with
| Error `Not_ready -> Lwt.fail_with "tunnel not ready, dropping data"
| Ok (s', out) ->
conn.o_client <- s';
Expand All @@ -355,7 +355,7 @@ let send_recv conn config ip_config _mtu =
Lwt.pick [ process_incoming () ; process_outgoing tun_fd ]

let establish_tunnel config =
match Openvpn.client config ts now Mirage_crypto_rng.generate with
match Miragevpn.client config ts now Mirage_crypto_rng.generate with
| Error `Msg msg ->
Logs.err (fun m -> m "client construction failed %s" msg);
Lwt.fail_with msg
Expand All @@ -377,7 +377,7 @@ let establish_tunnel config =
Logs.info (fun m -> m "waiting for established");
Lwt_mvar.take est_mvar >>= fun (ip_config, mtu) ->
Logs.info (fun m -> m "now established %a (mtu %d)"
Openvpn.pp_ip_config ip_config mtu);
Miragevpn.pp_ip_config ip_config mtu);
send_recv conn config ip_config mtu

let string_of_file ~dir filename =
Expand All @@ -399,7 +399,7 @@ let parse_config filename =
let dir, filename = Filename.(dirname filename, basename filename) in
let string_of_file = string_of_file ~dir in
match string_of_file filename with
| Ok str -> Openvpn.Config.parse_client ~string_of_file str
| Ok str -> Miragevpn.Config.parse_client ~string_of_file str
| Error _ as e -> e

let jump _ filename =
Expand Down Expand Up @@ -429,7 +429,7 @@ let config =

let cmd =
let term = Term.(term_result (const jump $ setup_log $ config))
and info = Cmd.info "openvpn_client" ~version:"%%VERSION_NUM%%"
and info = Cmd.info "miragevpn_client" ~version:"%%VERSION_NUM%%"
in
Cmd.v info term

Expand Down
2 changes: 1 addition & 1 deletion app/openvpn_config_parser.ml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
open Rresult
open Openvpn.Config
open Miragevpn.Config

let read_config_file fn =
let string_of_file ~dir filename =
Expand Down
2 changes: 1 addition & 1 deletion dune-project
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
(lang dune 1.0)
(name openvpn)
(name miragevpn)
2 changes: 1 addition & 1 deletion lzo/dune
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(library
(name lzo)
(modules lzo)
(public_name openvpn.lzo)
(public_name miragevpn.lzo)
(libraries fmt logs rresult)
)

Expand Down
8 changes: 4 additions & 4 deletions mirage-client/config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ open Mirage
let data_key = Key.(value @@ kv_ro ~group:"data" ())
let data = generic_kv_ro ~key:data_key "configuration"

let openvpn_handler =
let miragevpn_handler =
let packages =
let pin = "git+https://github.com/roburio/openvpn.git" in
let pin = "git+https://github.com/roburio/miragevpn.git" in
[
package "logs" ;
package ~pin ~sublibs:["mirage"] "openvpn";
package ~pin ~sublibs:["mirage"] "miragevpn";
package "mirage-kv";
]
in
Expand All @@ -17,4 +17,4 @@ let openvpn_handler =
"Unikernel.Main" (random @-> mclock @-> pclock @-> time @-> stackv4v6 @-> kv_ro @-> job)

let () =
register "ovpn-client" [openvpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ generic_stackv4v6 default_network $ data ]
register "ovpn-client" [miragevpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ generic_stackv4v6 default_network $ data ]
4 changes: 2 additions & 2 deletions mirage-client/unikernel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ open Lwt.Infix

module Main (R : Mirage_random.S) (M : Mirage_clock.MCLOCK) (P : Mirage_clock.PCLOCK) (T : Mirage_time.S) (S : Tcpip.Stack.V4V6) (FS: Mirage_kv.RO) = struct

module O = Openvpn_mirage.Make_stack(R)(M)(P)(T)(S)
module O = Miragevpn_mirage.Make_stack(R)(M)(P)(T)(S)
module I = Icmpv4.Make(O)

let read_config data =
FS.get data (Mirage_kv.Key.v "openvpn.config") >|= function
| Error e -> Rresult.R.error_to_msg ~pp_error:FS.pp_error (Error e)
| Ok data ->
let string_of_file _ = Error (`Msg "not supported") in
Openvpn.Config.parse_client ~string_of_file data
Miragevpn.Config.parse_client ~string_of_file data

let cb icmp ~proto ~src ~dst buf =
match proto with
Expand Down
8 changes: 4 additions & 4 deletions mirage-nat/config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ let private_ethernet = etif private_netif
let private_arp = arp private_ethernet
let private_ipv4 = create_ipv4 ~group:"private" private_ethernet private_arp

let openvpn_handler =
let miragevpn_handler =
let packages =
let pin = "git+https://github.com/roburio/openvpn.git" in
let pin = "git+https://github.com/roburio/miragevpn.git" in
[
package "logs" ;
package ~pin ~sublibs:["mirage"] "openvpn";
package ~pin ~sublibs:["mirage"] "miragevpn";
package "mirage-kv";
package ~min:"2.1.0" "mirage-nat";
package ~min:"3.8.0" "mirage-runtime";
Expand All @@ -25,4 +25,4 @@ let openvpn_handler =
(random @-> mclock @-> pclock @-> time @-> stackv4v6 @-> network @-> ethernet @-> arpv4 @-> ipv4 @-> kv_ro @-> job)

let () =
register "ovpn-nat" [openvpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ generic_stackv4v6 default_network $ private_netif $ private_ethernet $ private_arp $ private_ipv4 $ data ]
register "ovpn-nat" [miragevpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ generic_stackv4v6 default_network $ private_netif $ private_ethernet $ private_arp $ private_ipv4 $ data ]
4 changes: 2 additions & 2 deletions mirage-nat/unikernel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ module Main (R : Mirage_random.S) (M : Mirage_clock.MCLOCK) (P : Mirage_clock.PC
(N : Mirage_net.S) (E : Ethernet.S) (A : Arp.S) (_ : Tcpip.Ip.S with type ipaddr = Ipaddr.V4.t)
(FS: Mirage_kv.RO) = struct

module O = Openvpn_mirage.Make(R)(M)(P)(T)(S)
module O = Miragevpn_mirage.Make(R)(M)(P)(T)(S)

let read_config data =
FS.get data (Mirage_kv.Key.v "openvpn.config") >|= function
| Error e -> Rresult.R.error_to_msg ~pp_error:FS.pp_error (Error e)
| Ok data ->
let string_of_file _ = Error (`Msg "not supported") in
Openvpn.Config.parse_client ~string_of_file data
Miragevpn.Config.parse_client ~string_of_file data

let log = Logs.Src.create "nat" ~doc:"NAT device"
module Log = (val Logs.src_log log : Logs.LOG)
Expand Down
10 changes: 5 additions & 5 deletions mirage-router/config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ let private_ethernet = etif private_netif
let private_arp = arp private_ethernet
let private_ipv4 = create_ipv4 ~group:"private" private_ethernet private_arp

let openvpn_handler =
let miragevpn_handler =
let packages =
let pin = "git+https://github.com/roburio/openvpn.git" in
let pin = "git+https://github.com/roburio/miragevpn.git" in
[
package "logs" ;
package ~pin ~sublibs:["mirage"] "openvpn";
package ~pin ~sublibs:["mirage"] "miragevpn";
package "mirage-kv";
package ~min:"3.8.0" "mirage-runtime";
]
Expand Down Expand Up @@ -39,7 +39,7 @@ let management_stack =

let name =
let doc = Key.Arg.info ~doc:"Name of the unikernel" [ "name" ] in
Key.(v (create "name" Arg.(opt string "openvpn.robur.coop" doc)))
Key.(v (create "name" Arg.(opt string "miragevpn.robur.coop" doc)))

let monitoring =
let monitor =
Expand Down Expand Up @@ -95,5 +95,5 @@ let () =
register "ovpn-router" [
optional_syslog default_console default_posix_clock management_stack ;
optional_monitoring default_time default_posix_clock management_stack ;
openvpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ stack $ private_netif $ private_ethernet $ private_arp $ private_ipv4 $ block
miragevpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ stack $ private_netif $ private_ethernet $ private_arp $ private_ipv4 $ block
]
4 changes: 2 additions & 2 deletions mirage-router/unikernel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ open Lwt.Infix
module Main (R : Mirage_random.S) (M : Mirage_clock.MCLOCK) (P : Mirage_clock.PCLOCK) (T : Mirage_time.S) (S : Tcpip.Stack.V4V6)
(N : Mirage_net.S) (E : Ethernet.S) (A : Arp.S) (I : Tcpip.Ip.S with type ipaddr = Ipaddr.V4.t) (B: Mirage_block.S) = struct

module O = Openvpn_mirage.Make(R)(M)(P)(T)(S)
module O = Miragevpn_mirage.Make(R)(M)(P)(T)(S)

let strip_0_suffix cfg =
let rec find0 idx =
Expand Down Expand Up @@ -66,7 +66,7 @@ module Main (R : Mirage_random.S) (M : Mirage_clock.MCLOCK) (P : Mirage_clock.PC
read_data block >>= fun data ->
let config = strip_0_suffix (Cstruct.concat data) in
let string_of_file _ = Error (`Msg "not supported") in
Lwt.return (Openvpn.Config.parse_client ~string_of_file (Cstruct.to_string config))
Lwt.return (Miragevpn.Config.parse_client ~string_of_file (Cstruct.to_string config))

let local_network ip =
let cidr = Key_gen.private_ipv4 () in
Expand Down
8 changes: 4 additions & 4 deletions mirage-server/config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ open Mirage
let data_key = Key.(value @@ kv_ro ~group:"data" ())
let data = generic_kv_ro ~key:data_key "configuration"

let openvpn_handler =
let miragevpn_handler =
let packages =
let pin = "git+https://github.com/roburio/openvpn.git" in
let pin = "git+https://github.com/roburio/miragevpn.git" in
[
package "logs" ;
package ~pin ~sublibs:["mirage"] "openvpn";
package ~pin ~sublibs:["mirage"] "miragevpn";
package "dns";
package "dns-client";
package "mirage-kv";
Expand All @@ -19,4 +19,4 @@ let openvpn_handler =
"Unikernel.Main" (random @-> mclock @-> pclock @-> time @-> stackv4v6 @-> kv_ro @-> job)

let () =
register "ovpn-server" [openvpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ generic_stackv4v6 default_network $ data ]
register "ovpn-server" [miragevpn_handler $ default_random $ default_monotonic_clock $ default_posix_clock $ default_time $ generic_stackv4v6 default_network $ data ]
4 changes: 2 additions & 2 deletions mirage-server/unikernel.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ open Lwt.Infix

module Main (R : Mirage_random.S) (M : Mirage_clock.MCLOCK) (P : Mirage_clock.PCLOCK) (T : Mirage_time.S) (S : Tcpip.Stack.V4V6) (FS: Mirage_kv.RO) = struct

module O = Openvpn_mirage.Server(R)(M)(P)(T)(S)
module O = Miragevpn_mirage.Server(R)(M)(P)(T)(S)

let read_config data =
FS.get data (Mirage_kv.Key.v "openvpn.config") >|= function
| Error e -> Rresult.R.error_to_msg ~pp_error:FS.pp_error (Error e)
| Ok data ->
let string_of_file _ = Error (`Msg "no string_of_file support") in
Openvpn.Config.parse ~string_of_file data
Miragevpn.Config.parse ~string_of_file data

let start _ _ _ _ s data =
read_config data >>= function
Expand Down
4 changes: 2 additions & 2 deletions mirage/design-data-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ On the internal network, the OpenVPN gateway forwards received packets via the t

More detailed, a packet with Ethernet destination address equals 00-f0-0d-f0-0d-00 is handled by the internal stack. In the IPv4 layer (`Static_ipv4.input`), the destination IP address is checked: if it is 10.38.2.1, the respective local TCP/UDP/ICMP layers are used for handling the packet (which may lead to sending out replies).

If the IP address is somewhere else (and not broadcast or multicast or in the 10.38.2.0/24 network), this IP packet is transmitted via the OpenVPN tunnel (`Openvpn_mirage.send_data`), which involves: potential fragmentation, encryption, and transferring via the external stack: from 192.168.1.5 to 1.2.3.4 on the IP layer, to the ethernet address of 192.168.1.1 (the default gateway).
If the IP address is somewhere else (and not broadcast or multicast or in the 10.38.2.0/24 network), this IP packet is transmitted via the OpenVPN tunnel (`Miragevpn_mirage.send_data`), which involves: potential fragmentation, encryption, and transferring via the external stack: from 192.168.1.5 to 1.2.3.4 on the IP layer, to the ethernet address of 192.168.1.1 (the default gateway).

## Receiving a packet via OpenVPN

If a OpenVPN packet is received on the flow, `Openvpn_mirage.handle` is called, which decrypts the packet, and if it contains a data payload, this is passed to the internal stack. Depending on whether the destination IP address is 10.38.2.1 (in which case, the packet is processed by the internal stack) or some other IP in the 10.38.2.0/24 network - which leads to transmitting that packet to the respective IP address via the internal stack (using `Static_ipv4.write`, which uses its ARP cache to find the destination Ethernet address, and 00-f0-0d-f0-0d-00 as source Ethernet address).
If a OpenVPN packet is received on the flow, `Miragevpn_mirage.handle` is called, which decrypts the packet, and if it contains a data payload, this is passed to the internal stack. Depending on whether the destination IP address is 10.38.2.1 (in which case, the packet is processed by the internal stack) or some other IP in the 10.38.2.0/24 network - which leads to transmitting that packet to the respective IP address via the internal stack (using `Static_ipv4.write`, which uses its ARP cache to find the destination Ethernet address, and 00-f0-0d-f0-0d-00 as source Ethernet address).
6 changes: 3 additions & 3 deletions mirage/dune
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(library
(name openvpn_mirage)
(public_name openvpn.mirage)
(libraries openvpn logs ipaddr cstruct
(name miragevpn_mirage)
(public_name miragevpn.mirage)
(libraries miragevpn logs ipaddr cstruct
rresult tcpip tcpip.icmpv4 tcpip.tcp tcpip.udp tcpip.ipv4
mirage-random mirage-clock dns-client dns-client-mirage)
(wrapped false))
Loading

0 comments on commit e7dea54

Please sign in to comment.