package mirage

  1. Overview
  2. Docs
The MirageOS library operating system

Install

Dune Dependency

Authors

Maintainers

Sources

mirage-4.9.0.tbz
sha256=0c07d59eb52dc3d1506eb4121c4953104a12df79d08a0f0923c9b71e7474a026
sha512=666bf9ee20c9f9de058441f252f4f40ceec6a9ffd00e5cd3b7bfa9532fd65000aeb8a83f9e55586be98d0a86ea72f2dda94e924608135e3d63441359505de58a

doc/mirage/Mirage/index.html

Module MirageSource

Release v4.9.0

What is MirageOS?

MirageOS is a library operating system that can build standalone unikernels on various platforms. More precisely, the architecture can be divided into:

  • operating system libraries that implement kernel and protocol functionality, ranging from low-level network card drivers to a full reimplementation of the TLS protocol, through to a reimplementation of the Git protocol to store versioned data.
  • A set of typed signatures to make sure these libraries are consistent and can interoperate. As all the library are almost all pure OCaml code, we have defined a set of OCaml module types that encode these conventions in a statically enforcable way. We make no compatibility guarantees at the C level, but compile those on a best-effort basis.
  • Finally, MirageOS is also a metaprogramming compiler that generates OCaml code. It takes as input: the OCaml source code of a program and all of its dependencies, the full description of the deployment target, including configuration values (like the HTTP port to listen on, or the private key or the service being deployed). The `mirage`CLI tool uses all of these to generate a executable unikernel: a specialised binary artefact containing only the code what is needed to run on the given deployment platform and no more.

It is possible to write high-level MirageOS applications, such as HTTPS, email or CalDAV servers which can be deployed on very heterogenous and embedded platforms by changing only a few compilation parameters. The supported platforms range from minimal virtual machines running on cloud providers, or processes running inside Docker containers configured with a tight security profile. In general, these platform do not have a full POSIX environment; MirageOS does not try to emulate POSIX and focuses on providing a small, well-defined, typed interface with the system components. The nearest equivalent to the MirageOS approach is the WASI (wasi.dev) set of interfaces for WebAssembly.

Is everything really written in OCaml?

While most of the code is written in OCaml, a typed, high-level language with many good safety properties, there are pieces of MirageOS which are still written in C. These bits can be separated in three categories:

  • The OCaml runtime is written in C. It needs to be ported to the platform that MirageOS is trying to target, which do not support POSIX. Hence, the first component to port to a new platform is the OCaml runtime.
  • The low-level device drivers (network, console, clock, etc) also need some C bits.
  • The base usual C bindings; some libraries are widely used and (unfortunately) very hard (but not impossible) to replace them completely without taking a big performance hit or having to trust code without much real-world usages. This is the case for low-level bit handling for crypto code (even if we try to make sure allocation is alway handled by the OCaml runtime) as well as arbitrary precision numeric computation (e.g. gmp). Ideally we could image rewriting all of these libraries in OCaml if we had an infinite amount of time in our hands.

MirageOS as a cross-compilator

The MirageOS compiler is basically a cross-compiler, where the host and target toolchain are identical, but with different flags for the C bindings: for instance, it is necessary to pass -freestanding to all C bindings to not use POSIX headers. The MirageOS compiler also uses a custom linker: eg. not only it needs a custom OCaml's runtime libasmrun.a, but it also needs to run a different linker to generate specialised executable images.

Historically, the OCaml ecosystem always had partial support for cross-compilation: for instance, the ocaml-cross way of doing it is to duplicate all existing opam pacakges by adding a -windows suffix to their names and dependencies; this allows normal packages and windows packages can be co-installed in the same opam switch.

MirageOS 3.x

MirageOS 3.x solves this by duplicating only the packages defining C bindings. It relies on every MirageOS backend registering a set of CFLAGS with pkg-config. Then every bindings uses pkg-config to configure their CFLAGS and ocamlfind to register link-time predicates, e.g. additional link time options like the name of the C archives. Finally, the final link step is done by querying ocamlfind (using the custom registered predicates) to link the list of dependencies' objects files with the result of OCam compiler's --output-obj option.

MirageOS 4.x

MirageOS 4 solves this by relying on dune's built-in support for cross-compilation. This is done by gathering all the sources of the dependencies locally with opam-monorepo, and by creating a `dune-workspace` file describing the C flags to use in each cross-compilation "context". Once this is set-up, only one dune build can cross-compile the unikernel target with all its local sources.

MirageOS eDSL

The rest of the document describes Functoria, the embedded domain-specific language to be used in config.ml files, to described how the typed libraries have to be assembled.

Combinators

Sourcetype 'a typ = 'a Functoria.Type.t

The type for values representing module types.

Sourceval typ : 'a -> 'a typ

type t is a value representing the module type t.

Sourceval (@->) : 'a typ -> 'b typ -> ('a -> 'b) typ

Construct a functor type from a type and an existing functor type. This corresponds to prepending a parameter to the list of functor parameters. For example:

  kv_ro @-> ip @-> kv_ro

This describes a functor type that accepts two arguments -- a kv_ro and an ip device -- and returns a kv_ro.

Sourcetype 'a impl = 'a Functoria.Impl.t

The type for values representing module implementations.

Sourceval ($) : ('a -> 'b) impl -> 'a impl -> 'b impl

m $ a applies the functor m to the module a.

Sourcetype abstract_impl = Functoria.Impl.abstract

Same as impl but with hidden type.

Sourceval dep : 'a impl -> abstract_impl

dep t is the (build-time) dependency towards t.

Keys

Sourcetype 'a key = 'a Functoria.Key.key

The type for configure-time command-line arguments.

Sourcetype 'a runtime_arg = 'a Functoria.Runtime_arg.arg

The type for runtime command-line arguments.

Sourceval runtime_arg : pos:(string * int * int * int) -> ?packages:Functoria.Package.t list -> string -> Functoria.Runtime_arg.t

runtime_arg ~pos ?packages v is the runtime argument pointing to the value v. pos is expected to be __POS__. packages specifies in which opam package the value v is defined.

Sourcetype abstract_key = Functoria.Key.t

The type for abstract keys.

The type for keys' parsing context. See Key.context.

Sourcetype 'a value = 'a Functoria.Key.value

The type for values parsed from the command-line. See Key.value.

Sourceval key : 'a key -> Functoria.Key.t

key k is an untyped representation of k.

Sourceval if_impl : bool value -> 'a impl -> 'a impl -> 'a impl

if_impl v impl1 impl2 is impl1 if v is resolved to true and impl2 otherwise.

Sourceval match_impl : 'b value -> default:'a impl -> ('b * 'a impl) list -> 'a impl

match_impl v cases ~default chooses the implementation amongst cases by matching the v's value. default is chosen if no value matches.

Package dependencies

For specifying opam package dependencies, the type package is used. It consists of the opam package name, the ocamlfind names, and optional lower and upper bounds. The version constraints are merged with other modules.

The type for opam packages.

Installation scope of a package.

Sourceval package : ?scope:scope -> ?build:bool -> ?sublibs:string list -> ?libs:string list -> ?min:string -> ?max:string -> ?pin:string -> ?pin_version:string -> string -> package

package ~scope ~build ~sublibs ~libs ~min ~max ~pin opam is a package. Build indicates a build-time dependency only, defaults to false. The library name is by default the same as opam, you can specify ~sublibs to add additional sublibraries (e.g. ~sublibs:["mirage"] "foo" will result in the library names ["foo"; "foo.mirage"]. In case the library name is disjoint (or empty), use ~libs. Specifying both ~libs and ~sublibs leads to an invalid argument. Version constraints are given as min (inclusive) and max (exclusive). If pin is provided, a pin-depends is generated, pin_version is "dev" by default. ~scope specifies the installation location of the package.

Application Builder

Values of type impl are tied to concrete module implementation with the device and main construct. Module implementations of type job can then be registered into an application builder. The builder is in charge if parsing the command-line arguments and of generating code for the final application. See Functoria.Lib for details.

The type for build information.

Sourceval main : ?pos:(string * int * int * int) -> ?packages:package list -> ?packages_v:package list value -> ?runtime_args:Functoria.Runtime_arg.t list -> ?deps:abstract_impl list -> string -> 'a typ -> 'a impl

main name typ is the functor name, having the module type typ. The connect code will call <name>.start.

  • If packages or packages_v is set, then the given packages are installed before compiling the current application.

Devices

Sourcetype 'a code = 'a Functoria.Device.code
Sourceval code : pos:(string * int * int * int) -> ('a, Format.formatter, unit, 'b code) format4 -> 'a
Sourceval of_device : 'a device -> 'a impl

of_device t is the implementation device t.

Sourceval impl : ?packages:package list -> ?packages_v:package list Functoria.Key.value -> ?install:(Functoria.Info.t -> Functoria.Install.t) -> ?install_v:(Functoria.Info.t -> Functoria.Install.t Functoria.Key.value) -> ?keys:Functoria.Key.t list -> ?runtime_args:Functoria.Runtime_arg.t list -> ?extra_deps:abstract_impl list -> ?connect:(info -> string -> string list -> 'a code) -> ?dune:(info -> Functoria.Dune.stanza list) -> ?configure:(info -> unit Functoria.Action.t) -> ?files:(info -> Fpath.t list) -> string -> 'a typ -> 'a impl

impl ~packages ~packages_v ~install ~install_v ~keys ~runtime_args ~extra_deps ~connect ~dune ~configure ~files module_name module_type is an implementation of the device constructed by the arguments. packages and packages_v are the dependencies (where packages_v is inside Key.value). install and install_v are the install instructions (used in the generated opam file), keys are the configuration-time keys, runtime_args the arguments at runtime, extra_deps are a list of extra dependencies (other implementations), connect is the code emitted for initializing the device, dune are dune stanzas added to the build rule, configure are commands executed at the configuration phase, files are files to be added to the list of generated files, module_name is the name of the device module, and module_type is the type of the module.

Jobs

Sourcetype job
Sourcemodule Key : module type of struct include Devices.Key end

Configuration keys.

Sourcemodule Runtime_arg : module type of struct include Devices.Runtime_arg end

Configuration keys.

General mirage devices

Sourcetype qubesdb
Sourceval qubesdb : qubesdb typ

For the Qubes target, the Qubes database from which to look up dynamic runtime configuration information.

Sourceval default_qubesdb : qubesdb impl

A default qubes database, guessed from the usual valid configurations.

Sleep

Sourcetype sleep

Abstract type for sleep.

Sourceval sleep : sleep typ

Implementations of the Mirage_sleep signature.

Sourceval default_sleep : sleep impl

The default sleep implementation.

Sourceval no_sleep : sleep impl

Disables the sleep implementation.

Posix time

Sourcetype ptime

Abstract type for POSIX time.

Sourceval ptime : ptime typ

Implementations of the Mirage_ptime signature.

Sourceval default_ptime : ptime impl

The default mirage-ptime implementation.

Sourceval no_ptime : ptime impl

Disables the mirage-ptime implementation.

Sourceval mock_ptime : ptime impl

A ptime mock implementation where you can manually set the clock via Mirage_ptime_set.

Monotonic time

Sourcetype mtime

Abstract type for monotonic time

Sourceval mtime : mtime typ

Implementations of the Mirage_mtime signature.

Sourceval default_mtime : mtime impl

The default mirage-mtime implementation.

Sourceval no_mtime : mtime impl

Disables the mirage-mtime implementation.

Sourceval mock_mtime : mtime impl

A mtime mock implementation where you can manually set the clock via Mirage_mtime_set.

Log reporters

Sourcetype reporter

The type for log reporters.

Sourceval reporter : reporter typ

Implementation of the log reporter type.

Sourceval default_reporter : ?level:Logs.level option -> unit -> reporter impl

default_reporter ?level () is the log reporter that prints log messages to the console, with a timestamp as prefix. level is the default log threshold. It is Some Logs.Info if not specified.

Sourceval no_reporter : reporter impl

no_reporter disable log reporting.

Random

Sourcetype random

Abstract type for random sources.

Sourceval random : random typ

Implementations of the Mirage_crypto_rng_mirage2 signature.

Sourceval default_random : random impl

Default PRNG device to be used in unikernels. It uses getrandom/getentropy on Unix, and a Fortuna PRNG on other targets.

Sourceval no_random : random impl

Disables the random device.

Block devices

Sourcetype block

Abstract type for raw block device configurations.

Sourceval block : block typ

Implementations of the Mirage_block.S signature.

Sourceval block_of_file : string -> block impl

Use the given file as a raw block device.

Sourceval block_of_xenstore_id : string -> block impl

Use the given XenStore ID (ex: /dev/xvdi1 or 51760) as a raw block device.

Sourceval ramdisk : string -> block impl

Use a ramdisk with the given name.

Sourceval generic_block : ?group:string -> ?key:[ `XenstoreId | `BlockFile | `Ramdisk ] value -> string -> block impl

Static key/value stores

Sourcetype kv_ro

Abstract type for read-only key/value store.

Sourceval kv_ro : kv_ro typ

Implementations of the Mirage_kv.RO signature.

Sourceval crunch : string -> kv_ro impl

Crunch a directory. The contents of the directory is transformed into OCaml code, which is then compiled as part of the unikernel.

Sourceval tar_kv_ro : block impl -> kv_ro impl

tar_kv_ro block is a read-only tar archive.

Sourceval direct_kv_ro : string -> kv_ro impl

Direct access to the underlying filesystem as a key/value store for Unix. For other backends, this is equivalent to crunch.

Sourceval fat_ro : block impl -> kv_ro impl

Use a FAT formatted block device.

Sourceval generic_kv_ro : ?group:string -> ?key:[ `Crunch | `Direct ] value -> string -> kv_ro impl

Generic key/value that will choose dynamically between direct_kv_ro and crunch. To use a filesystem implementation, try kv_ro_of_fs.

If no key is provided, a new Key.kv_ro is created with the group argument.

Sourceval docteur : ?mode:[ `Fast | `Light ] -> ?name:string key -> ?output:string key -> ?analyze:bool runtime_arg -> ?branch:string -> ?extra_deps:string list -> string -> kv_ro impl

docteur ?mode ?name ?output ?analyze remote is a read-only, key-value store device. Data is stored on that device using the Git PACK file format, version 2. This format has very good compression factors for many similar files of relatively small size. For instance, 14Gb of HTML files can be compressed into a disk image of 240Mb.

Unlike crunch, docteur produces an external image which means that less memory is used to keep and get files. The image can be produced from many sources:

  • A local Git repository (like file://path/to/the/git/repository/)
  • A simple directory (like file://path/to/a/simple/directory/)
  • A remote Git repository (via SSH, HTTP(S) or TCP/IP as what git clone expects)

If you use a Git repository, you can choose a specific branch with the ?branch argument (like refs/heads/main). Otherwise, this argument is ignored.

If you use a simple directory, it can be a relative from your unikernel project (relativize://directory) or an absolute path (file://home/user/directory).

If a required file is produced by a dune rule, you must notice it via the extra_deps argument.

For a Solo5 target, users must attach the image as a block device:

  $ solo5-hvt --block:<name>=<path-to-the-image> -- unikernel.{hvt,...}

The user is able to specify the name of the block device (default to "docteur"). The user can also specify the output of docteur.make, the tool which generate the image (default to "disk.img").

For the Unix target, the program open the image at the beginning of the process. An integrity check of the image can be done via the analyze value (defaults to true).

It's possible to use the file-system into 2 modes:

  • `Light: any access requires that we reconstruct the path to the requested file. That means that we will need to extract a few additional objects before the extraction of the requested one. `Light does not cache anything in memory but it can be slower if the requested file is deep in the directory structure.
  • `Fast: reconstructs and cache the layout of the directory structure when the unikernel starts: it might increase boot-time and bigger memory requirements. However, `Fast allows the device to decode only the requested object so it is faster than the `Light mode.
Sourcetype kv_rw

Abstract type for read-write key/value store.

Sourceval kv_rw : kv_rw typ

Implementations of the Mirage_kv.RW signature.

Sourceval direct_kv_rw : string -> kv_rw impl

Direct access to the underlying filesystem as a key/value store. Only available on Unix backends.

Sourceval kv_rw_mem : unit -> kv_rw impl

An in-memory key-value store using mirage-kv-mem.

Sourceval chamelon : program_block_size:int runtime_arg -> block impl -> kv_rw impl

chamelon ~program_block_size returns a kv_rw filesystem which is an implementation of littlefs in OCaml. The chamelon device expects a block-device.

unikernel.ml:

  open Cmdliner

  let program_block_size =
    Arg.(value & opt int 16 & info [ "program-block-size" ])

config.ml:

  let db =
    let program_block_size =
      Runtime_arg.create ~pos:__POS__ "Unikernel.program_block_size"
    in
    let block = block_of_file "db" in
    chamelon ~program_block_size block
  in

For Solo5 targets, you finally can launch the unikernel with:

  $ solo5-hvt --block:db=db.img unikernel.hvt

The block-device must be well-formed and formatted by the chamelon tool:

  $ dd if=/dev/zero of=db.img bs=1M count=1
  $ chamelon format db.img 512
Sourceval tar_kv_rw : block impl -> kv_rw impl

tar_kv_rw block is a read/write tar archive. Note that the filesystem is append-only. That is, files can generally not be removed, set_partial only works on what is allocated, and there are restrictions on rename.

Sourceval ccm_block : ?nonce_len:int -> string option runtime_arg -> block impl -> block impl

ccm_block key block returns a new block which is a AES-CCM encrypted disk.

Note also that the available size of an encrypted block is always divided by 2 of its real size: a 512M block will only be able to contain 256M data if it is encrypted.

You can either use a fresh block device as encrypted storage. This does not need any preparation, just using ccm_block with the desired key. If you have an existing disk image that you want to encrypt, you can use the ccmblock tool given by the mirage-block-ccm opam package.

  $ ccmblock enc -i db.img -k 0x10786d3a9c920d0b3ec80dfaaac557a7 -o edb.img

Accept the key as a runtime argument, in unikernel.ml:

  open Cmdliner

  let aes_ccm_key =
    let doc = "The key of the block device (hex formatted)" in
    Arg.(required & opt (some string) None & info ~doc [ "aes-ccm-key" ])

Then, into you config.ml, you just need to compose your block device with ccm_block:

  let encrypted_block =
    let aes_ccm_key =
      Runtime_arg.create ~pos:__POS__ "Unikernel.aes_ccm_key"
    in
    let block = block_of_file "edb"
    ccm_block aes_ccm_key block
  in

Finally, with Solo5, you can launch your unikernel with that:

  $ solo5-hvt --block:edb=edb.img \
    --arg="--aes-ccm-key=0x10786d3a9c920d0b3ec80dfaaac557a7" \
    unikernel.hvt

You can finally compose a file-system such as chamelon with this block device (and you have a encrypted file-system!):

  let fs = chamelon ~program_block_size encrypted_block

Network interfaces

Sourcetype network

Abstract type for network configurations.

Sourceval network : network typ

Implementations of the Mirage_net.S signature.

Sourceval default_network : network impl

default_network is a dynamic network implementation which attempts to do something reasonable based on the target.

Sourceval netif : ?group:string -> string -> network impl

A custom network interface. Exposes a Runtime_arg.interface key.

Ethernet configuration

Sourcetype ethernet
Sourceval ethernet : ethernet typ

Implementations of the Ethernet.S signature.

etif net is the ethernet layer on net.

  • deprecated Deprecated. Use [ethif] instead.

ethif net is the ethernet layer on net.

ARP configuration

Sourcetype arpv4
Sourceval arpv4 : arpv4 typ

Implementation of the Arp.S signature.

ARP implementation provided by the arp library

IP configuration

Implementations of the Tcpip.Ip.S signature.

Sourcetype v4
Sourcetype v6
Sourcetype v4v6
Sourcetype 'a ip

Abstract type for IP configurations.

Sourcetype ipv4 = v4 ip
Sourcetype ipv6 = v6 ip
Sourcetype ipv4v6 = v4v6 ip
Sourceval ipv4 : ipv4 typ

The Tcpip.Ip.S module signature with ipaddr = Ipaddr.V4.

Sourceval ipv6 : ipv6 typ

The Tcpip.Ip.S module signature with ipaddr = Ipaddr.V6.

Sourceval ipv4v6 : ipv4v6 typ

The Tcpip.Ip.S module signature with ipaddr = Ipaddr.t.

Sourcetype ipv4_config = {
  1. network : Ipaddr.V4.Prefix.t;
  2. gateway : Ipaddr.V4.t option;
}

Types for manual IPv4 configuration.

Sourcetype ipv6_config = {
  1. network : Ipaddr.V6.Prefix.t;
  2. gateway : Ipaddr.V6.t option;
}

Types for manual IPv6 configuration.

Sourceval ipv4_of_dhcp : network impl -> ethernet impl -> arpv4 impl -> ipv4 impl

Configure the interface via DHCP

Sourceval create_ipv4 : ?group:string -> ?config:ipv4_config -> ethernet impl -> arpv4 impl -> ipv4 impl

Use an IPv4 address Exposes the keys Runtime_arg.V4.network and Runtime_arg.V4.gateway. If provided, the values of these keys will override those supplied in the ipv4 configuration record, if that has been provided.

Sourceval ipv4_qubes : qubesdb impl -> ethernet impl -> arpv4 impl -> ipv4 impl

Use a given initialized QubesDB to look up and configure the appropriate * IPv4 interface.

Sourceval create_ipv6 : ?group:string -> ?config:ipv6_config -> network impl -> ethernet impl -> ipv6 impl

Use an IPv6 address. Exposes the keys Runtime_arg.V6.network, Runtime_arg.V6.gateway.

Sourceval create_ipv4v6 : ?group:string -> ipv4 impl -> ipv6 impl -> ipv4v6 impl

UDP configuration

Sourcetype 'a udp
Sourcetype udpv4v6 = v4v6 udp
Sourceval udp : 'a udp typ

Implementation of the Tcpip.Udp.S signature.

Sourceval udpv4v6 : udpv4v6 typ
Sourceval direct_udp : 'a ip impl -> 'a udp impl
Sourceval socket_udpv4v6 : ?group:string -> Ipaddr.V4.t option -> Ipaddr.V6.t option -> udpv4v6 impl

TCP configuration

Sourcetype 'a tcp
Sourcetype tcpv4v6 = v4v6 tcp
Sourceval tcp : 'a tcp typ

Implementation of the Tcpip.Tcp.S signature.

Sourceval tcpv4v6 : tcpv4v6 typ
Sourceval direct_tcp : 'a ip impl -> 'a tcp impl
Sourceval socket_tcpv4v6 : ?group:string -> Ipaddr.V4.t option -> Ipaddr.V6.t option -> tcpv4v6 impl

Network stack configuration

Dual IPv4 and IPv6

Sourcetype stackv4v6
Sourceval stackv4v6 : stackv4v6 typ

Implementation of the Tcpip.Stack.V4V6 signature.

Sourceval direct_stackv4v6 : ?group:string -> ?tcp:tcpv4v6 impl -> network impl -> ethernet impl -> arpv4 impl -> ipv4 impl -> ipv6 impl -> stackv4v6 impl

Direct network stack with given ip.

Sourceval socket_stackv4v6 : ?group:string -> unit -> stackv4v6 impl

Network stack with sockets.

Sourceval static_ipv4v6_stack : ?group:string -> ?ipv6_config:ipv6_config -> ?ipv4_config:ipv4_config -> ?arp:(ethernet impl -> arpv4 impl) -> ?tcp:tcpv4v6 impl -> network impl -> stackv4v6 impl

Build a stackv4v6 by checking the Runtime_arg.V6.network, and Runtime_arg.V6.gateway keys for IPv4 and IPv6 configuration information, filling in unspecified information from ?config, then building a stack on top of that.

Sourceval generic_stackv4v6 : ?group:string -> ?ipv6_config:ipv6_config -> ?ipv4_config:ipv4_config -> ?dhcp_key:bool value -> ?net_key:[ `OCaml | `Host ] option value -> ?tcp:tcpv4v6 impl -> network impl -> stackv4v6 impl

Generic stack using a net keys: Key.net.

If a key is not provided, it uses Key.net (with the group argument) to create it.

Sourceval tcpv4v6_of_stackv4v6 : stackv4v6 impl -> tcpv4v6 impl

tcpv4v6 stackv4v6 is an helper to extract the TCP/IP stack regardless the UDP/IP stack expected by some devices such as protocols.

Resolver configuration

Sourcetype resolver
Sourceval resolver : resolver typ
Sourceval resolver_dns : ?ns:string list -> stackv4v6 impl -> resolver impl
Sourceval resolver_unix_system : resolver impl

Happy-eyeballs

Happy-eyeballs is an implementation of RFC 8305 which specifies how to connect to a remote host using either IP protocol version 4 or IP protocol version 6 from a stackv4v6 network implementation.

The given device is able to resolve a remote host via a dns_client device and both must share the same stackv4v6 implementation.

Sourcetype happy_eyeballs
Sourceval happy_eyeballs : happy_eyeballs typ
Sourceval generic_happy_eyeballs : ?group:string -> ?aaaa_timeout:int64 -> ?connect_delay:int64 -> ?connect_timeout:int64 -> ?resolve_timeout:int64 -> ?resolve_retries:int -> ?timer_interval:int64 -> stackv4v6 impl -> happy_eyeballs impl

generic_happy_eyeballs stackv4v6 creates a new happy-eyeballs value which is able to connect to a remote host and allocate finally a connected flow from the given network implementation stackv4v6. However, if you want to resolve (DNS resolution) & connect to a remote host, you must complete your unikernel with a generic_dns_client which upgrade the happy-eyeballs stack with a DNS resolution stack.

This device has several optional arguments of keys for timeouts specified in nanoseconds.

DNS client

A DNS client is a module which implements:

  • getaddrinfo to request a query_type-dependent response to a nameserver regarding a domain-name such as the MX record.
  • gethostbyname to request the A regarding a domain-name
  • gethostbyname6 to request the AAAA record regarding a domain-name
Sourcetype dns_client
Sourceval dns_client : dns_client typ
Sourceval generic_dns_client : ?group:string -> ?timeout:int64 -> ?nameservers:string list -> ?cache_size:int -> stackv4v6 impl -> happy_eyeballs impl -> dns_client impl

generic_dns_client stackv4v6 happy_eyeballs creates a new DNS value which is able to resolve domain-name from nameservers. It requires a network and happy-eyeballs stack to communicate with these nameservers.

The nameservers argument is a list of strings. The format of them is:

  • udp:ipaddr(:port)? if you want to communicate with a DNS resolver via UDP
  • tcp:ipaddr(:port)? if you want to communicate with a DNS resolver via TCP/IP
  • tls:ipaddr(:port)?(!<authenticator>) if you to communicate with a DNS resolver via TLS. You are able to introduce an <authenticator> (please, follow the documentation about X509.Authenticator.of_string to get an explanation of its format). Otherwise, by default, we use trust anchors from NSS' certdata.txt.

Syslog configuration

Syslog exfiltrates log messages (generated by libraries using the logs library) via a network connection. The log level of the log sources is controlled via the Mirage_runtime.logs key. The functionality is provided by the logs-syslog package.

Sourcetype syslog

The type for syslog

Sourceval syslog : syslog typ

Implementation of the syslog type.

Sourceval syslog_udp : ?group:string -> stackv4v6 impl -> syslog impl

Emit log messages via UDP.

Sourceval syslog_tcp : ?group:string -> stackv4v6 impl -> syslog impl

Emit log messages via TCP.

Sourceval syslog_tls : ?group:string -> stackv4v6 impl -> kv_ro impl -> syslog impl

Emit log messages via TLS, using the credentials (private key, certificate, trust anchor) provided in the KV_RO.

Sourceval monitoring : ?group:string -> stackv4v6 impl -> job impl

Monitoring

Monitor metrics to a remote Influx host, also allow adjustments to log sources and levels. The provided stack should not be publicly reachable.

Conduit configuration

Sourcetype conduit
Sourceval conduit : conduit typ
Sourceval conduit_direct : ?tls:bool -> stackv4v6 impl -> conduit impl

Mimic devices

For some implementations which requires to communicate with an external resources (such as a webserver or a git server), we must hide the underlying implementations that depend on the target (such as the network stack) and are necessary for these implementations.

The aim of mimic is to offer first of all the ability to initiate a TCP/IP connection independently of the chosen target (see mimic_happy_eyeballs).

The resulting device can then be composed with other protocols like TLS, Git or HTTP and it is through this resulting device that other devices can initiate an internet connection to a peer (like a webserver or a Git server).

Sourcetype mimic
Sourceval mimic : mimic typ
Sourceval mimic_happy_eyeballs : stackv4v6 impl -> happy_eyeballs impl -> dns_client impl -> mimic impl

mimic_happy_eyeballs stackv4v6 happy_eyeballs dns_client creates a device which initiate a global happy-eyeballs loop. By this way, an underlying instance works to initiate a TCP/IP connection from an IP address or a domain-name.

For the domain-name resolution, we ask the happy-eyeballs instance to resolve the given domain-name via its DNS client.

The resulting device can be used and re-used to for any clients which need to initiate a connection (like alpn_client or git_tcp).

HTTP configuration

Sourcetype http
Sourceval http : http typ
Sourceval cohttp_server : conduit impl -> http impl

cohttp_server starts a Cohttp server.

Sourceval httpaf_server : conduit impl -> http impl

httpaf_server starts a http/af server.

Sourcetype http_client
Sourceval http_client : http_client typ
Sourceval cohttp_client : resolver impl -> conduit impl -> http_client impl

cohttp_server starts a Cohttp server.

Sourcetype http_server
Sourceval http_server : http_server typ
Sourceval paf_server : port:int runtime_arg -> tcpv4v6 impl -> http_server impl

paf_server ~port tcpv4v6 creates an instance which will start to listen on the given port. With this instance and the produced module HTTP_server, the user can initiate:

  • a simple HTTP server
  • a simple HTTPS server (with a TLS configuration)
  • a simple ALPN (http/1.1 & h2) server with TLS

This is a simple example of how to launch an HTTP server: unikernel.ml

  open Cmdliner

  let port =
    let doc = "Port of the HTTP service." in
    Arg.(value & opt int 8080 & info [ "p"; "port" ])

  module Make (HTTP_server : Paf_mirage.S with type ipaddr = Ipaddr.t) =
  struct
    let error_handler (_ipaddr, _port) ?request:_ _error _send = ()

    let request_handler :
        HTTP_server.TCP.flow -> Ipaddr.t * int -> Httpaf.Reqd.t -> unit =
     fun _socket (_ipaddr, _port) reqd ->
      let contents = "Hello World!\n" in
      let headers =
        Httpaf.Headers.of_list
          [
            ("content-length", string_of_int (String.length contents));
            ("content-type", "text/plain");
            ("connection", "close");
          ]
      in
      let response = Httpaf.Response.create ~headers `OK in
      Httpaf.Reqd.respond_with_string reqd response contents

    let start http_server port =
      let service =
        HTTP_service.http_service ~error_handler request_handler
      in
      let (`Initialized thread) = HTTP_server.serve service http_server in
      thread
  end

config.ml

  open Mirage

  let port = Runtime_arg.create ~pos:__POS__ "Unikernel.port"
  let main = main "Unikernel.Make" (http_server @-> job)
  let stackv4v6 = generic_stackv4v6 default_network
  let http_server = paf_server ~port (tcpv4v6_of_stackv4v6 stackv4v6)

  let () =
    register "main"
      ~runtime_args:[ Runtime_arg.v port ]
      [ main $ http_server ]
Sourcetype alpn_client

Abstract type for ALPN HTTP clients

Sourceval alpn_client : alpn_client typ
Sourceval paf_client : tcpv4v6 impl -> mimic impl -> alpn_client impl

paf_client tcpv4v6 dns creates an ALPN device which can do HTTP (http/1.1 & h2) requests as a HTTP client. The device allocated represents values required to initiate a connection to HTTP webservers. The user can, then, use the module Http_mirage_client.request to communicate with HTTP webservers. This is an example of how to use the ALPN devices:

unikernel.ml

  module Make (HTTP_client : Http_mirage_client.S) = struct
    let start http =
      Http_mirage_client.request http "https://google.com"
        (fun _response buf str -> Buffer.add_string buf str ; Lwt.return buf)
        (Buffer.create 0x100) >>= function
      | Ok (response, buf) ->
        let body = Buffer.contents buf in
        ...
      | Error _ -> ...
  end

config.ml

  open Mirage

  let main = main "Unikernel.Make" (alpn_client @-> job)
  let stackv4v6 = generic_stackv4v6 default_network
  let he = generic_happy_eyeballs stack
  let dns = generic_dns_client stack he

  let alpn_client =
    let mimic = mimic_happy_eyeballs stackv4v6 he dns in
    paf_client (tcpv4v6_of_stackv4v6 stackv4v6) mimic

  let () = register "main" [ main $ alpn_client ]

Argv configuration

Sourceval argv : argv typ
Sourceval default_argv : argv impl

default_argv is a dynamic argv implementation which attempts to do something reasonable based on the target.

Sourceval no_argv : argv impl

no_argv Disable command line parsing and set argv to |""|.

Git client configuration

Users can connect to a remote Git repository in many ways:

  • TCP/IP
  • HTTP
  • HTTP + TLS
  • SSH

The devices defined below define these in composable ways. The git_client impl returned from them can be passed to Git or Irmin in order to be able to fetch and push from/into a Git repository.

The user is able to restrict or enlarge protocol possibilities needed for its application. For instance, the user is able to restrict only the SSH connection to communicate with a Git repository or the user can handle TCP/IP and SSH as possible protocols to communicate with a peer.

For instance, a device which is able to communicate via TCP/IP and SSH can be implemented like:

  let he = generic_happy_eyeballs stack
  let dns = generic_dns_client stack he

  let git_client =
    let mimic = mimic_happy_eyeballs stackv4v6 he dns in
    let ssh =
      git_ssh ~key ~password (tcpv4v6_of_stackv4v6 stackv4v6) mimic
    in
    let tcp = git_tcp (tcpv4v6_of_stackv4v6 stackv4v6) mimic in
    merge_git_clients ssh tcp
Sourcetype git_client

The type for devices that implement the Git protocol.

Sourceval git_client : git_client typ
Sourceval merge_git_clients : git_client impl -> git_client impl -> git_client impl

merge_git_clients a b is a device that can connect to remote Git repositories using either the device a or the device b.

git_tcp tcpv4v6 dns is a device able to connect to a remote Git repository using TCP/IP.

Sourceval git_ssh : ?group:string -> ?authenticator:string -> ?key:string -> ?password:string -> tcpv4v6 impl -> mimic impl -> git_client impl

git_ssh ?group ?authenticator ?key ?password tcpv4v6 dns is a device able to connect to a remote Git repository using an SSH connection with the given private key or password. The identity of the remote Git repository can be verified using authenticator.

The format of the private key is: <type>:<seed or b64 encoded>. <type> can be rsa or ed25519 and, if the type is RSA, we expect the seed of the private key. Otherwise (if the type is Ed25519), we expect the b64-encoded private key.

The format of the authenticator is SHA256:<b64-encoded-public-key>, the output of:

  $ ssh-keygen -lf <(ssh-keyscan -t rsa|ed25519 remote 2>/dev/null)
Sourceval git_http : ?group:string -> ?authenticator:string -> ?headers:(string * string) list -> tcpv4v6 impl -> mimic impl -> git_client impl

git_http ?group ?authenticator ?headers tcpv4v6 dns is a device able to connect to a remote Git repository via an HTTP(S) connection, using the provided HTTP headers. The identity of the remote Git repository can be verified using authenticator.

The format of it is:

  • none no authentication
  • key(:<hash>)?:<b64-encoded fingerprint> to authenticate via the key fingerprint
  • cert(:<hash>)?:<b64-encoded fingerprint> to authenticate via the cert fingerprint
  • trust-anchor(:<der-encoded cert>)+ to authenticate via a list of certificates
  • By default, we use X.509 trust anchors extracted from Mozilla's NSS

Other devices

Sourceval job : job typ

job is the combinator for representing main tasks.

Sourceval noop : job impl

noop is a job that does nothing, has no dependency and returns ()

Sourceval runtime_args : argv impl -> job impl

runtime_args argv is a job that loads argv.

Application registering

Sourceval register : ?argv:argv impl -> ?reporter:reporter impl -> ?sleep:sleep impl -> ?ptime:ptime impl -> ?mtime:mtime impl -> ?random:random impl -> ?src:[ `Auto | `None | `Some of string ] -> string -> job impl list -> unit

register ~argv ~reporter ~src name jobs registers the application named by name which will executes the given jobs.

  • parameter argv

    Configure command-line argument parsing. The default parser is default_argv. To disable command-line parsing, use no_argv.

  • parameter ptime

    Configure the POSIX clock. The default is default_ptime. To disable the POSIX clock, use no_ptime.

  • parameter mtime

    Configure the monotonic clock. The default is default_mtime. To disable the monotonic clock, use no_mtime.

  • parameter random

    Configure the random number generator. The default is default_random. To disable the random device, use no_random.

  • parameter src

    The source to use in the generated opam file. If `None no source is output. If `Some mysource the string mysource is used as the source. If `Auto (default) the is guessed from the VCS information.

Sourcemodule Type = Functoria.Type
Sourcemodule Impl = Functoria.Impl
Sourcemodule Info = Functoria.Info
Sourcemodule Dune = Functoria.Dune
Sourcemodule Action = Functoria.Action
Sourcemodule Context = Functoria.Context
Sourceval connect_err : string -> int -> 'a
Sourcemodule Project : sig ... end
Sourcemodule Tool : sig ... end
OCaml

Innovation. Community. Security.