Compare commits

...

No commits in common. 'main' and 'pages' have entirely different histories.
main ... pages

  1. 1
      .config/blog_name
  2. 1
      .config/index_page
  3. 1
      .config/root
  4. 0
      .config/timestamp-listings
  5. 1
      .config/uuid
  6. 143
      About
  7. 129
      Posts/ARP
  8. 1
      Posts/About
  9. 207
      Posts/Albatross
  10. 120
      Posts/BadRecordMac
  11. 145
      Posts/BottomUp
  12. 471
      Posts/Conex
  13. 312
      Posts/DNS
  14. 148
      Posts/Deploy
  15. 349
      Posts/DnsServer
  16. 137
      Posts/EC
  17. 220
      Posts/Functoria
  18. 419
      Posts/Jackline
  19. 138
      Posts/Maintainers
  20. 180
      Posts/Monitoring
  21. 171
      Posts/NGI
  22. 201
      Posts/OCaml
  23. 98
      Posts/OpamMirror
  24. 229
      Posts/OperatingSystem
  25. 100
      Posts/Pinata
  26. 130
      Posts/ReproducibleOPAM
  27. 112
      Posts/Solo5
  28. 96
      Posts/Summer2019
  29. 248
      Posts/Syslog
  30. 259
      Posts/Traceroute
  31. 286
      Posts/VMM
  32. 126
      Posts/X50907
  33. 28
      Posts/index.html
  34. 405
      Posts/nqsbWebsite
  35. 1109
      atom
  36. 28
      index.html
  37. 3
      tags/UI
  38. 6
      tags/background
  39. 3
      tags/bitcoin
  40. 9
      tags/deployment
  41. 3
      tags/future
  42. 3
      tags/http
  43. 3
      tags/logging
  44. 24
      tags/mirageos
  45. 4
      tags/monitoring
  46. 3
      tags/myself
  47. 3
      tags/opam
  48. 3
      tags/operating system
  49. 6
      tags/overview
  50. 6
      tags/package signing
  51. 8
      tags/protocol
  52. 3
      tags/provisioning
  53. 12
      tags/security
  54. 6
      tags/tls

@ -1 +0,0 @@
full stack engineer

@ -1 +0,0 @@
https://hannes.robur.coop

@ -1 +0,0 @@
981361ca-e71d-4997-a52c-baeee78e4156

143
About

@ -1,113 +1,88 @@
---
title: About
author: hannes
tags: overview, myself, background
abstract: introduction (myself, this site)
---
## What is a "full stack engineer"?
Analysing the word literally, we should start with silicon and some electrons,
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>About</title><meta charset="UTF-8"/><link rel="stylesheet" href="/static/css/style.css"/><link rel="stylesheet" href="/static/css/highlight.css"/><script src="/static/js/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="alternate" href="/atom" title="About" type="application/atom+xml"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/></head><body><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><a class="navbar-brand" href="/Posts">full stack engineer</a></div><div class="collapse navbar-collapse collapse"><ul class="nav navbar-nav navbar-right"><li><a href="/About"><span>About</span></a></li><li><a href="/Posts"><span>Posts</span></a></li></ul></div></div></nav><main><div class="flex-container"><div class="post"><h2>About</h2><span class="author">Written by hannes</span><br/><div class="tags">Classified under: <a href="/tags/overview" class="tag">overview</a><a href="/tags/myself" class="tag">myself</a><a href="/tags/background" class="tag">background</a></div><span class="date">Published: 2016-04-01 (last updated: 2021-11-19)</span><article><h2>What is a &quot;full stack engineer&quot;?</h2>
<p>Analysing the word literally, we should start with silicon and some electrons,
maybe a soldering iron, and build everything all the way up to our favourite
communication system.
While I know how to solder, I don't plan to write about hardware in here. I'll
communication system.</p>
<p>While I know how to solder, I don't plan to write about hardware in here. I'll
assume that off-the-shelf hardware (arm/amd64) is available and trustworthy.
Read the [Intel x86 considered
harmful](http://blog.invisiblethings.org/papers/2015/x86_harmful.pdf) paper in
case you're interested in trustworthiness of hardware.
My current obsession is to enable people to take back control over their data:
Read the <a href="http://blog.invisiblethings.org/papers/2015/x86_harmful.pdf">Intel x86 considered
harmful</a> paper in
case you're interested in trustworthiness of hardware.</p>
<p>My current obsession is to enable people to take back control over their data:
simple to setup, secure, decentralised infrastructure. We're not there yet,
which also means I've plenty of projects :).
I will write about my projects, which cover topics on various software layers.
### Myself
I'm Hannes Mehnert, a [hacker](http://www.catb.org/jargon/html/H/hacker.html)
which also means I've plenty of projects :).</p>
<p>I will write about my projects, which cover topics on various software layers.</p>
<h3>Myself</h3>
<p>I'm Hannes Mehnert, a <a href="http://www.catb.org/jargon/html/H/hacker.html">hacker</a>
(in the original sense of the word), 3X years old. In my spare time, I'm not
only a hacker, but also a barista. I like to travel and repair my recumbent
bicycle.
Back in 199X, my family bought a PC. It came
bicycle.</p>
<p>Back in 199X, my family bought a PC. It came
with MS-DOS installed, I also remember Windows 3.1 (likely on a later computer).
This didn't really hook me into computers, but over the years I started with
friends to modify some computer games (e.g. modifying text of Civilization). I
first encountered programming in high school around 1995: Borland's Turbo Pascal
(which chased me for several years).
Fast forwarding a bit, I learned about the operating system Linux (starting with
(which chased me for several years).</p>
<p>Fast forwarding a bit, I learned about the operating system Linux (starting with
SUSE 6.4) and got hooked (by providing basic network services (NFS/YP/Samba)) to
UNIX. In 2000 I joined the [Chaos Computer Club](https://www.ccc.de).
UNIX. In 2000 I joined the <a href="https://www.ccc.de">Chaos Computer Club</a>.
Over the years I learned various things, from Linux kernel modifications,
Perl, PHP, basic network and security. I use [FreeBSD](https://www.FreeBSD.org) since 4.5, FreeBSD-CURRENT
on my laptop. I helped to [reverse engineer and analyse the security of a voting
computer](http://wijvertrouwenstemcomputersniet.nl) in the Netherlands, and some
[art installations](http://blinkenlights.net/) in Berlin and Paris. There were
Perl, PHP, basic network and security. I use <a href="https://www.FreeBSD.org">FreeBSD</a> since 4.5, FreeBSD-CURRENT
on my laptop. I helped to <a href="http://wijvertrouwenstemcomputersniet.nl">reverse engineer and analyse the security of a voting
computer</a> in the Netherlands, and some
<a href="http://blinkenlights.net/">art installations</a> in Berlin and Paris. There were
several annual Chaos Communication Congresses where I co-setup the network
(backbone, access layer, wireless, network services such as DHCP/DNS), struggling with
Cisco hardware from their demo pool, and also amongst others HP, Force10, Lucent, Juniper
equipment.
In the early 200X I started to program [Dylan](https://opendylan.org), a LISP
equipment.</p>
<p>In the early 200X I started to program <a href="https://opendylan.org">Dylan</a>, a LISP
dialect (dynamic, multiple inheritance, object-oriented), which even resulted in
a [TCP/IP
implementation](https://github.com/dylan-hackers/network-night-vision/)
including a wireshark-like GTK based user interface with a shell similar to IOS for configuring the stack.
I got excited about programming languages and type theory (thanks to
[types and programming languages](https://www.cis.upenn.edu/~bcpierce/tapl/), an
excellent book); a key event for me was the [international conference on functional programming (ICFP)](http://cs.au.dk/~danvy/icfp05/). I wondered how a
[gradually typed](http://homes.soic.indiana.edu/jsiek/what-is-gradual-typing/)
a <a href="https://github.com/dylan-hackers/network-night-vision/">TCP/IP
implementation</a>
including a wireshark-like GTK based user interface with a shell similar to IOS for configuring the stack.</p>
<p>I got excited about programming languages and type theory (thanks to
<a href="https://www.cis.upenn.edu/~bcpierce/tapl/">types and programming languages</a>, an
excellent book); a key event for me was the <a href="http://cs.au.dk/~danvy/icfp05/">international conference on functional programming (ICFP)</a>. I wondered how a
<a href="http://homes.soic.indiana.edu/jsiek/what-is-gradual-typing/">gradually typed</a>
Dylan would look like, leading to my master thesis. Gradual typing is the idea to evolve untyped programs into typed ones, and runtime type errors must be in the dynamic part. To me, this sounded like a great idea, to start with some random code, and add types later.
My result was not too convincing (too slow, unsound type system).
Another problem with Dylan is that the community is very small, without sufficient time and energy to maintain the
self-hosted compiler(s) and the graphical IDE.
During my studies I met [Peter Sestoft](http://www.itu.dk/people/sestoft/).
self-hosted compiler(s) and the graphical IDE.</p>
<p>During my studies I met <a href="http://www.itu.dk/people/sestoft/">Peter Sestoft</a>.
After half a year off in New Zealand (working on formalising some type systems),
I did a PhD in the ambitious research project "[Tools and methods for
scalable software verification](https://itu.dk/research/tomeso/)", where we mechanised proofs of the functional correctness
of imperative code (PIs: Peter and [Lars Birkedal](http://cs.au.dk/~birke/)).
I did a PhD in the ambitious research project &quot;<a href="https://itu.dk/research/tomeso/">Tools and methods for
scalable software verification</a>&quot;, where we mechanised proofs of the functional correctness
of imperative code (PIs: Peter and <a href="http://cs.au.dk/~birke/">Lars Birkedal</a>).
The idea was great, the project was fun, but we ended with 3000 lines of proof
script for a 100 line Java program. The Java program was taken off-the-shelf,
several times refactored, and most of its shared mutable state was removed. The
proof script was in [Coq](https://coq.inria.fr), using our higher-order separation logic.
I concluded two things: formal verification is hard and usually not applicable
for off-the-shelf software. *Since we have to rewrite the software anyways, why
not do it in a declarative way?*
Some artefacts from that time are still around: an [eclipse plugin for
Coq](https://coqoon.github.io/), I also started (with David) the [idris-mode for
emacs](https://github.com/idris-hackers/idris-mode). Idris is a dependently
proof script was in <a href="https://coq.inria.fr">Coq</a>, using our higher-order separation logic.</p>
<p>I concluded two things: formal verification is hard and usually not applicable
for off-the-shelf software. <em>Since we have to rewrite the software anyways, why
not do it in a declarative way?</em></p>
<p>Some artefacts from that time are still around: an <a href="https://coqoon.github.io/">eclipse plugin for
Coq</a>, I also started (with David) the <a href="https://github.com/idris-hackers/idris-mode">idris-mode for
emacs</a>. Idris is a dependently
typed programming language (you can express richer types), actively being
researched (I would not consider it production ready yet, needs more work on a
faster runtime, and libraries).
After I finished my PhD, I decided to slack off for some time to make decent
faster runtime, and libraries).</p>
<p>After I finished my PhD, I decided to slack off for some time to make decent
espresso. I ended up spending the winter (beginning of 2014) in Mirleft,
Morocco. A good friend of mine pointed me to [MirageOS](https://mirage.io), a
clean-slate operating system written in the high-level language [OCaml](https://ocaml.org). I got
Morocco. A good friend of mine pointed me to <a href="https://mirage.io">MirageOS</a>, a
clean-slate operating system written in the high-level language <a href="https://ocaml.org">OCaml</a>. I got
hooked pretty fast, after some experience with LISP machines I imagined a modern
OS written in a single functional programming language.
From summer 2014 until end of 2017 I worked as a postdoctoral researcher at University of Cambridge (in the [rigorous engineering of mainstream systems](https://www.cl.cam.ac.uk/~pes20/rems) project) with [Peter Sewell](https://www.cl.cam.ac.uk/~pes20/). I primarily worked on TLS, MirageOS, opam signing, and network semantics. In 2018 I relocated back to Berlin and am working on [robur](http://robur.io).
MirageOS had various bits and pieces into place, including infrastructure for
OS written in a single functional programming language.</p>
<p>From summer 2014 until end of 2017 I worked as a postdoctoral researcher at University of Cambridge (in the <a href="https://www.cl.cam.ac.uk/~pes20/rems">rigorous engineering of mainstream systems</a> project) with <a href="https://www.cl.cam.ac.uk/~pes20/">Peter Sewell</a>. I primarily worked on TLS, MirageOS, opam signing, and network semantics. In 2018 I relocated back to Berlin and am working on <a href="http://robur.io">robur</a>.</p>
<p>MirageOS had various bits and pieces into place, including infrastructure for
building and testing (and a neat self-hosted website). A big gap was security.
No access control, no secure sockets layer, nothing. This will be the topic of
another post.
OCaml is [academically](http://compcert.inria.fr/) and [commercially](https://blogs.janestreet.com/) used, compiles to native code (arm/amd64/likely more), is
fast enough ("Reassuring, because our blanket performance statement 'OCaml
another post.</p>
<p>OCaml is <a href="http://compcert.inria.fr/">academically</a> and <a href="https://blogs.janestreet.com/">commercially</a> used, compiles to native code (arm/amd64/likely more), is
fast enough (&quot;Reassuring, because our blanket performance statement 'OCaml
delivers at least 50% of the performance of a decent C compiler' is
not invalidated :-)" [Xavier Leroy](https://lwn.net/Articles/19378/)), and the [community](https://opam.ocaml.org/packages/) is sufficiently large.
### Me on the intertubes
You can find me on [twitter](https://twitter.com/h4nnes) and on
[GitHub](https://github.com/hannesm).
The data of this blog is [stored in a git repository](https://git.robur.io/hannes/hannes.robur.coop).
not invalidated :-)&quot; <a href="https://lwn.net/Articles/19378/">Xavier Leroy</a>), and the <a href="https://opam.ocaml.org/packages/">community</a> is sufficiently large.</p>
<h3>Me on the intertubes</h3>
<p>You can find me on <a href="https://twitter.com/h4nnes">twitter</a> and on
<a href="https://github.com/hannesm">GitHub</a>.</p>
<p>The data of this blog is <a href="https://git.robur.io/hannes/hannes.robur.coop">stored in a git repository</a>.</p>
</article></div></div></main></body></html>

@ -1,77 +1,44 @@
---
title: Re-engineering ARP
author: hannes
tags: mirageos, protocol
abstract: If you want it as you like, you've to do it yourself
---
## What is ARP?
ARP is the *A*ddress *R*esolution *P*rotocol, widely used in legacy IP networks (which support only IPv4). It is responsible to translate an IPv4 address to an Ethernet address. It is strictly more general, abstracting over protocol and hardware addresses. It is basically DNS (the domain name system) on a different layer.
ARP is link-local: ARP frames are not routed into other networks, all stay in the same broadcast domain. Thus there is no need for a hop limit (time-to-live). A reverse lookup mechanism (hardware address to protocol) is also available, named reverse ARP ;).
I will focus on ARP in this article, as used widely to translate IPv4 addresses into Ethernet addresses. There are two operations in ARP: request and response. A request is usually broadcasted to all hosts (by setting the destination to the broadcast Ethernet address, `ff:ff:ff:ff:ff:ff`), while a reply is send via unicast (to the host which requested that information).
The frame format is pretty straightforward: 2 bytes hardware address type, 2 bytes protocol type, 1 byte length for both types, 2 bytes operation, followed by source addresses (hardware and protocol), and target addresses. In total 28 bytes, considering 48 bit Ethernet addresses and 32 bit IPv4 addresses.
It was initially specified in [RFC 826](https://tools.ietf.org/html/rfc826), but reading through [RFC 1122](https://tools.ietf.org/html/rfc1122) (requirements for Internet Hosts - Communication layer), and maybe the newer [RFC 5227](https://tools.ietf.org/html/rfc5227) (IPv4 address conflict detection) does not hurt.
On UNIX systems, you can investigate your arp table, also called arp cache, using the `arp` command line utility.
### Protocol logic
Let us look what our ARP handler actually needs to do? Translating IPv4 addresses to Ethernet addresses, but where does it learn new information?
First of all, our ARP handler needs to know its own IPv4 address and its Ethernet address. It will even broadcast them on startup, so-called gratuitous ARP. The purpose of this is to inform all other hosts on the same network that we are here now. And if another host, let's name it barf, has the same IPv4 address, some sort of conflict resolution needs to happen (otherwise all hosts on the network are confused to whether to send us or barf packets).
Once initialisation is over, our ARP handler needs to wait for ARP requests from other hosts on the network, and if addresses to our IPv4 address, issue a reply. The other event which might happen is that a user wants to send an IPv4 packet to another host on the network. In this case, we either already have the Ethernet address in our cache, or we need to send an ARP request to the network and wait for a reply. Since packets might get lost, we actually need to retry sending ARP requests until a limit is reached. To keep the cache in a reasonable size, old entries should be dropped if unused. Also, the Ethernet address of hosts may change, due to hardware replacement or failover.
That's it. Pretty straightforward.
## Design
Back in 2008, together with Andreas Bogk, we just used a hash table and installed expiration and retransmission timers when needed. Certainly timers sometimes needed to be cancelled, and testing the code was cumbersome. It were only [250 lines of Dylan code](https://github.com/dylan-hackers/network-night-vision/blob/master/network/ip-stack/layers/network/arp/arp.dylan) plus some [wire format definition](https://github.com/dylan-hackers/network-night-vision/blob/master/protocols/ipv4.dylan).
Nowadays, after some years of doing formal verification and typed functional programming, I try to have effects, including mutable state, isolated and explicitly annotated. The code should not contain surprises, but straightforward to understand. The core protocol logic should not be convoluted with side effects, rather a small wrapper around it should. Once this is achieved, testing is straightforward. If the fashion of the asynchronous task library changes (likely with OCaml multicore), the core logic can be reused. It can also be repurposed to run as a test oracle. You can read more marketing of this style in our [Usenix security paper](https://usenix15.nqsb.io).
My proposed style and hash tables are not good friends, since hash tables in OCaml are imperative structures. Instead, a *Map* ([documentation](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Map.html)) is a functional data structure for associating keys with values. Its underlying data structure is a balanced binary tree.
Our ARP handler certainly has some *state*, at least its IPv4 address, its Ethernet address, and the map containing entries.
We have to deal with the various effects mentioned earlier:
- *Network* we provide a function taking a state and a packet, transforming to successor state, potentially output on the network, and potentially waking up tasks which are awaiting the mac address.
- *Timer* we need to rely on an external periodic event calling our function `tick`, which transforms a state to a successor state, a list of ARP requests to be send out (retransmission), and a list of tasks to be informed that a timeout occurred.
- *Query* a query for an IPv4 address using some state leads to a successor state, and either an immediate answer with the Ethernet address, or an ARP request to be sent and waiting for an answer, or just waiting for an answer in the case another task has already requested that IPv4 address. Since we don't want to convolute the protocol core with tasks, we'll let the effectful layer decide how to achieve that by abstracting over some alpha to store, and requiring a `merge : alpha option -> alpha` function.
### Excursion: security
ARP is a link-local protocol, thus attackers have to have access to the same link-layer: either a cable in the same switch or hub, or in the same wireless network (if you're into modern technology).
A very common attack vector for protocols is the so called person in the middle attack, where the attacker sits between you and the remote host. An attacker can achieve this using ARP spoofing: if they can convince your computer that the attacker is the gateway, your computer will send all packets to the attacker, who either forwards them to the remote host, or modifies them, or drops them.
ARP does not employ any security mechanism, it is more a question of receiving the first answer (depending on the implementation). A common countermeasure is to manually fill the cache with the gateway statically. This only needs updates if the gateway is replaced, or gets a new network card.
Denial of service attacks are also possible using ARP: if the implementation preserves all replies, the cache might expand immensely. This happens sometimes in switch hardware, which have a limited cache, and once it is full, they go into hub mode. This means all frames are broadcasted on all ports. This enables an attacker to passively sniff all traffic in the local network.
One denial of service attack vector is due to choosing a hash table as underlying store. Its hash function should be collision-resistant, one way, and its output should be fixed length. A good choice would be a cryptographic hash function (like SHA-256), but these are too expensive and thus rarely used for hash tables. [Denial of Service via Algorithmic Complexity Attacks](https://www.usenix.org/conference/12th-usenix-security-symposium/denial-service-algorithmic-complexity-attacks) and [Efficient Denial of Service Attacks on Web Application Platforms](https://events.ccc.de/congress/2011/Fahrplan/attachments/2007_28C3_Effective_DoS_on_web_application_platforms.pdf) are worth studying. If you expose your hash function to user input (and don't use a private seed), you might accidentally open your attack surface.
### Back to our design
To mitigate person in the middle attacks, we provide an API to add static entries, which are never overwritten by network input. While our own IPv4 addresses are advertised if a matching ARP request was received, other static entries are not advertised (neither are dynamic entries). We do only insert entries to our cache if we have an outstanding request or already an entry. To provide low latency, just before a dynamic entry would timeout, we send another request for this IPv4 address to the network.
### Implementation
I have the [source](https://github.com/hannesm/arp), its [documentation](https://hannesm.github.io/arp), a test suite and a [coverage report](https://hannesm.github.io/arp/coverage) online.
The implementation of the core logic still fits in less than 250 lines of code. Below 100 more lines are needed for decoding and encoding byte buffers. And another 140 lines to implement the Mirage ARP interface. Tests are available which cover the protocol logic and decoding/encoding to 100%.
The effectful layer is underspecified (especially regarding conflicts: what happens if there is an outstanding request for an IPv4 address and I add a static entry for this?). There is an implementation based on hash tables, which I used to benchmark a bit.
Correctness aside, the performance should be in the same ballpark. I am mainly interested in how much input can be processed, being it invalid input, random valid input, random requests, random replies, and a mix of all that above plus some valid requests which should be answered. I ran the tests in two modes, one with accelerated time (where a minute passed in a second) to increase the pressure on the cache (named fast), one in real time. The results are in the table below (bigger numbers are better). It shows that neither approach is slower by design (of course there is still room for improvement).
```
| Test | Hashtable | fast | Map | fast |
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Re-engineering ARP</title><meta charset="UTF-8"/><link rel="stylesheet" href="/static/css/style.css"/><link rel="stylesheet" href="/static/css/highlight.css"/><script src="/static/js/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="alternate" href="/atom" title="Re-engineering ARP" type="application/atom+xml"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/></head><body><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><a class="navbar-brand" href="/Posts">full stack engineer</a></div><div class="collapse navbar-collapse collapse"><ul class="nav navbar-nav navbar-right"><li><a href="/About"><span>About</span></a></li><li><a href="/Posts"><span>Posts</span></a></li></ul></div></div></nav><main><div class="flex-container"><div class="post"><h2>Re-engineering ARP</h2><span class="author">Written by hannes</span><br/><div class="tags">Classified under: <a href="/tags/mirageos" class="tag">mirageos</a><a href="/tags/protocol" class="tag">protocol</a></div><span class="date">Published: 2016-07-12 (last updated: 2021-11-19)</span><article><h2>What is ARP?</h2>
<p>ARP is the <em>A</em>ddress <em>R</em>esolution <em>P</em>rotocol, widely used in legacy IP networks (which support only IPv4). It is responsible to translate an IPv4 address to an Ethernet address. It is strictly more general, abstracting over protocol and hardware addresses. It is basically DNS (the domain name system) on a different layer.</p>
<p>ARP is link-local: ARP frames are not routed into other networks, all stay in the same broadcast domain. Thus there is no need for a hop limit (time-to-live). A reverse lookup mechanism (hardware address to protocol) is also available, named reverse ARP ;).</p>
<p>I will focus on ARP in this article, as used widely to translate IPv4 addresses into Ethernet addresses. There are two operations in ARP: request and response. A request is usually broadcasted to all hosts (by setting the destination to the broadcast Ethernet address, <code>ff:ff:ff:ff:ff:ff</code>), while a reply is send via unicast (to the host which requested that information).</p>
<p>The frame format is pretty straightforward: 2 bytes hardware address type, 2 bytes protocol type, 1 byte length for both types, 2 bytes operation, followed by source addresses (hardware and protocol), and target addresses. In total 28 bytes, considering 48 bit Ethernet addresses and 32 bit IPv4 addresses.</p>
<p>It was initially specified in <a href="https://tools.ietf.org/html/rfc826">RFC 826</a>, but reading through <a href="https://tools.ietf.org/html/rfc1122">RFC 1122</a> (requirements for Internet Hosts - Communication layer), and maybe the newer <a href="https://tools.ietf.org/html/rfc5227">RFC 5227</a> (IPv4 address conflict detection) does not hurt.</p>
<p>On UNIX systems, you can investigate your arp table, also called arp cache, using the <code>arp</code> command line utility.</p>
<h3>Protocol logic</h3>
<p>Let us look what our ARP handler actually needs to do? Translating IPv4 addresses to Ethernet addresses, but where does it learn new information?</p>
<p>First of all, our ARP handler needs to know its own IPv4 address and its Ethernet address. It will even broadcast them on startup, so-called gratuitous ARP. The purpose of this is to inform all other hosts on the same network that we are here now. And if another host, let's name it barf, has the same IPv4 address, some sort of conflict resolution needs to happen (otherwise all hosts on the network are confused to whether to send us or barf packets).</p>
<p>Once initialisation is over, our ARP handler needs to wait for ARP requests from other hosts on the network, and if addresses to our IPv4 address, issue a reply. The other event which might happen is that a user wants to send an IPv4 packet to another host on the network. In this case, we either already have the Ethernet address in our cache, or we need to send an ARP request to the network and wait for a reply. Since packets might get lost, we actually need to retry sending ARP requests until a limit is reached. To keep the cache in a reasonable size, old entries should be dropped if unused. Also, the Ethernet address of hosts may change, due to hardware replacement or failover.</p>
<p>That's it. Pretty straightforward.</p>
<h2>Design</h2>
<p>Back in 2008, together with Andreas Bogk, we just used a hash table and installed expiration and retransmission timers when needed. Certainly timers sometimes needed to be cancelled, and testing the code was cumbersome. It were only <a href="https://github.com/dylan-hackers/network-night-vision/blob/master/network/ip-stack/layers/network/arp/arp.dylan">250 lines of Dylan code</a> plus some <a href="https://github.com/dylan-hackers/network-night-vision/blob/master/protocols/ipv4.dylan">wire format definition</a>.</p>
<p>Nowadays, after some years of doing formal verification and typed functional programming, I try to have effects, including mutable state, isolated and explicitly annotated. The code should not contain surprises, but straightforward to understand. The core protocol logic should not be convoluted with side effects, rather a small wrapper around it should. Once this is achieved, testing is straightforward. If the fashion of the asynchronous task library changes (likely with OCaml multicore), the core logic can be reused. It can also be repurposed to run as a test oracle. You can read more marketing of this style in our <a href="https://usenix15.nqsb.io">Usenix security paper</a>.</p>
<p>My proposed style and hash tables are not good friends, since hash tables in OCaml are imperative structures. Instead, a <em>Map</em> (<a href="http://caml.inria.fr/pub/docs/manual-ocaml/libref/Map.html">documentation</a>) is a functional data structure for associating keys with values. Its underlying data structure is a balanced binary tree.</p>
<p>Our ARP handler certainly has some <em>state</em>, at least its IPv4 address, its Ethernet address, and the map containing entries.</p>
<p>We have to deal with the various effects mentioned earlier:</p>
<ul>
<li><em>Network</em> we provide a function taking a state and a packet, transforming to successor state, potentially output on the network, and potentially waking up tasks which are awaiting the mac address.
</li>
<li><em>Timer</em> we need to rely on an external periodic event calling our function <code>tick</code>, which transforms a state to a successor state, a list of ARP requests to be send out (retransmission), and a list of tasks to be informed that a timeout occurred.
</li>
<li><em>Query</em> a query for an IPv4 address using some state leads to a successor state, and either an immediate answer with the Ethernet address, or an ARP request to be sent and waiting for an answer, or just waiting for an answer in the case another task has already requested that IPv4 address. Since we don't want to convolute the protocol core with tasks, we'll let the effectful layer decide how to achieve that by abstracting over some alpha to store, and requiring a <code>merge : alpha option -&gt; alpha</code> function.
</li>
</ul>
<h3>Excursion: security</h3>
<p>ARP is a link-local protocol, thus attackers have to have access to the same link-layer: either a cable in the same switch or hub, or in the same wireless network (if you're into modern technology).</p>
<p>A very common attack vector for protocols is the so called person in the middle attack, where the attacker sits between you and the remote host. An attacker can achieve this using ARP spoofing: if they can convince your computer that the attacker is the gateway, your computer will send all packets to the attacker, who either forwards them to the remote host, or modifies them, or drops them.</p>
<p>ARP does not employ any security mechanism, it is more a question of receiving the first answer (depending on the implementation). A common countermeasure is to manually fill the cache with the gateway statically. This only needs updates if the gateway is replaced, or gets a new network card.</p>
<p>Denial of service attacks are also possible using ARP: if the implementation preserves all replies, the cache might expand immensely. This happens sometimes in switch hardware, which have a limited cache, and once it is full, they go into hub mode. This means all frames are broadcasted on all ports. This enables an attacker to passively sniff all traffic in the local network.</p>
<p>One denial of service attack vector is due to choosing a hash table as underlying store. Its hash function should be collision-resistant, one way, and its output should be fixed length. A good choice would be a cryptographic hash function (like SHA-256), but these are too expensive and thus rarely used for hash tables. <a href="https://www.usenix.org/conference/12th-usenix-security-symposium/denial-service-algorithmic-complexity-attacks">Denial of Service via Algorithmic Complexity Attacks</a> and <a href="https://events.ccc.de/congress/2011/Fahrplan/attachments/2007_28C3_Effective_DoS_on_web_application_platforms.pdf">Efficient Denial of Service Attacks on Web Application Platforms</a> are worth studying. If you expose your hash function to user input (and don't use a private seed), you might accidentally open your attack surface.</p>
<h3>Back to our design</h3>
<p>To mitigate person in the middle attacks, we provide an API to add static entries, which are never overwritten by network input. While our own IPv4 addresses are advertised if a matching ARP request was received, other static entries are not advertised (neither are dynamic entries). We do only insert entries to our cache if we have an outstanding request or already an entry. To provide low latency, just before a dynamic entry would timeout, we send another request for this IPv4 address to the network.</p>
<h3>Implementation</h3>
<p>I have the <a href="https://github.com/hannesm/arp">source</a>, its <a href="https://hannesm.github.io/arp">documentation</a>, a test suite and a <a href="https://hannesm.github.io/arp/coverage">coverage report</a> online.</p>
<p>The implementation of the core logic still fits in less than 250 lines of code. Below 100 more lines are needed for decoding and encoding byte buffers. And another 140 lines to implement the Mirage ARP interface. Tests are available which cover the protocol logic and decoding/encoding to 100%.</p>
<p>The effectful layer is underspecified (especially regarding conflicts: what happens if there is an outstanding request for an IPv4 address and I add a static entry for this?). There is an implementation based on hash tables, which I used to benchmark a bit.</p>
<p>Correctness aside, the performance should be in the same ballpark. I am mainly interested in how much input can be processed, being it invalid input, random valid input, random requests, random replies, and a mix of all that above plus some valid requests which should be answered. I ran the tests in two modes, one with accelerated time (where a minute passed in a second) to increase the pressure on the cache (named fast), one in real time. The results are in the table below (bigger numbers are better). It shows that neither approach is slower by design (of course there is still room for improvement).</p>
<pre><code>| Test | Hashtable | fast | Map | fast |
| ------------- | --------- | ------- | ------- | ------- |
| invalid | 2813076 | 2810684 | 2806899 | 2835905 |
| valid | 1126805 | 1320737 | 1770123 | 1785630 |
@ -79,11 +46,9 @@ Correctness aside, the performance should be in the same ballpark. I am mainly
| replies | 1293293 | 1313405 | 1432225 | 1449860 |
| mixed | 2158481 | 2191617 | 2196092 | 2213530 |
| queries | 42058 | 45258 | 44803 | 44379 |
```
I ran each benchmark 3 times on a single core (used `cpuset -l 3` to pin it to one specific core) and picked the best set of results. The measure is number of packets processed over 5 seconds, using the Mirage ARP API. The full source code is in the `bench` subdirectory. As always, take benchmarks with a grain of salt: everybody will always find the right parameters for their microbenchmarks.
There was even a bug in the MirageOS ARP code: [its definition of gratuitous ARP is wrong](https://github.com/mirage/mirage-tcpip/issues/225).
I'm interested in feedback, either via
[twitter](https://twitter.com/h4nnes) or via eMail.
</code></pre>
<p>I ran each benchmark 3 times on a single core (used <code>cpuset -l 3</code> to pin it to one specific core) and picked the best set of results. The measure is number of packets processed over 5 seconds, using the Mirage ARP API. The full source code is in the <code>bench</code> subdirectory. As always, take benchmarks with a grain of salt: everybody will always find the right parameters for their microbenchmarks.</p>
<p>There was even a bug in the MirageOS ARP code: <a href="https://github.com/mirage/mirage-tcpip/issues/225">its definition of gratuitous ARP is wrong</a>.</p>
<p>I'm interested in feedback, either via
<a href="https://twitter.com/h4nnes">twitter</a> or via eMail.</p>
</article></div></div></main></body></html>

@ -1 +0,0 @@
redirect: /About

@ -1,138 +1,81 @@
---
title: Deploying reproducible unikernels with albatross
author: hannes
tags: mirageos, deployment
abstract: fleet management for MirageOS unikernels using a mutually authenticated TLS handshake
---
## Deploying MirageOS unikernels
More than five years ago, I posted [how to deploy MirageOS unikernels](/Posts/VMM). My motivation to work on this topic is that I'm convinced of reduced complexity, improved security, and more sustainable resource footprint of MirageOS unikernels, and want to ease deployment thereof. More than one year ago, I described [how to deploy reproducible unikernels](/Posts/Deploy).
## Albatross
In recent months we worked hard on the underlying infrastructure: [albatross](https://github.com/roburio/albatross). Albatross is the orchestration system for MirageOS unikernels that use solo5 with [hvt or spt tender](https://github.com/Solo5/solo5/blob/master/docs/architecture.md). It deals with three tasks:
- unikernel creation (destroyal, restart)
- capturing console output
- collecting metrics in the host system about unikernels
An addition to the above is dealing with multiple tenants on the same machine: remote management of your unikernel fleet via TLS, and resource policies.
## History
The initial commit of albatross was in May 2017. Back then it replaced the shell scripts and manual `scp` of unikernel images to the server. Over time it evolved and adapted to new environments. Initially a solo5 unikernel would only know of a single network interface, these days there can be multiple distinguished by name. Initially there was no support for block devices. Only FreeBSD was supported in the early days. Nowadays we built daily packages for Debian, Ubuntu, FreeBSD, and have support for NixOS, and the client side is supported on macOS as well.
### ASN.1
The communication format between the albatross daemons and clients was changed multiple times. I'm glad that albatross uses ASN.1 as communication format, which makes extension with optional fields easy, and also allows "choice" (the sum type) to be not tagged (the binary is the same as no choice type), thus adding choice to an existing grammar, and preserving the old in the default (untagged) case is a decent solution.
So, if you care about backward and forward compatibility, as we do, since we may be in control of which albatross servers are deployed on our machine, but not what albatross versions the clients are using -- it may be wise to look into ASN.1. Recent efforts (json with schema, ...) may solve similar issues, but ASN.1 is as well very tiny in size.
## What resources does a unikernel need?
A unikernel is just an operating system for a single service, there can't be much it can need.
### Name
So, first of all a unikernel has a name, or a handle. This is useful for reporting statistics, but also to specify which console output you're interested in. The name is a string with printable ASCII characters (and dash '-' and dot '.'), with a length up to 64 characters - so yes, you can use an UUID if you like.
### Memory
Another resource is the amount of memory assigned to the unikernel. This is specified in megabyte (as solo5 does), with the range being 10 (below not even a hello world wants to start) to 1024.
### Arguments
Of course, you can pass via albatross boot parameters to the unikernel. Albatross doesn't impose any restrictions here, but the lower levels may.
### CPU
Due to multiple tenants, and side channel attacks, it looked right at the beginning like a good idea to restrict each unikernel to a specific CPU. This way, one tenant may use CPU 5, and another CPU 9 - and they'll not starve each other (best to make sure that these CPUs are in different packages). So, albatross takes a number as the CPU, and executes the solo5 tender within `taskset`/`cpuset`.
### Fail behaviour
In normal operations, exceptional behaviour may occur. I have to admit that I've seen MirageOS unikernels that suffer from not freeing all the memory they have allocated. To avoid having to get up at 4 AM just to start the unikernel that went out of memory, there's the possibility to restart the unikernel when it exited. You can even specify on which exit codes it should be restarted (the exit code is the only piece of information we have from the outside what caused the exit). This feature was implemented in October 2019, and has been very precious since then. :)
### Network
This becomes a bit more complex: a MirageOS unikernel can have network interfaces, and solo5 specifies a so-called manifest with a list of these (name and type, and type is so far always basic). Then, on the actual server there are bridges (virtual switches) configured. Now, these may have the same name, or may need to be mapped. And of course, the unikernel expects a tap interface that is connected to such a bridge, not the bridge itself. Thus, albatross creates tap devices, attaches these to the respective bridges, and takes care about cleaning them up on teardown. The albatross client verifies that for each network interface in the manifest, there is a command-line argument specified (`--net service:my_bridge` or just `--net service` if the bridge is named service). The tap interface name is not really of interest to the user, and will not be exposed.
### Block devices
On the host system, it's just a file, and passed to the unikernel. There's the need to be able to create one, dump it, and ensure that each file is only used by one unikernel. That's all that is there.
## Metrics
Everyone likes graphs, over time, showing how much traffic or CPU or memory or whatever has been used by your service. Some of these statistics are only available in the host system, and it is also crucial for development purposes to compare whether the bytes sent in the unikernel sum up to the same on the host system's tap interface.
The albatross-stats daemon collects metrics from three sources: network interfaces, getrusage (of a child process), VMM debug counters (to count VM exits etc.). Since the recent 1.5.3, albatross-stats now connects at startup to the albatross-daemon and then retrieves the information which unikernels are up and running, and starts periodically collecting data in memory.
Other clients, being it a dump on your console window, a write into an rrd file (good old MRTG times), or a push to influx, can use the stats data to correlate and better analyse what is happening on the grand scale of things. This helped a lot by running several unikernels with different opam package sets to figure out which opam packages leave their hands on memory over time.
As a side note, if you make the unikernel name also available in the unikernel, it can tag its own metrics with the same identifier, and you can correlate high-level events (such as amount of HTTP requests) with low-level things "allocated more memory" or "consumed a lot of CPU".
## Console
There's not much to say about the console, just that the albatross-console daemon is running with low privileges, and reading from a FIFO that the unikernel writes to. It never writes anything to disk, but keeps the last 1000 lines in memory, available from a client asking for it.
## The daemons
So, the main albatross-daemon runs with superuser privileges to create virtual machines, and opens a unix domain socket where the clients and other daemons are connecting to. The other daemons are executed with normal user privileges, and never write anything to disk.
The albatross-daemon keeps state about the running unikernels, and if it is restarted, the unikernels are started again. Maybe worth to mention that this lead sometimes to headaches (due to data being dumped to disk, and the old format should always be supported), but was also a huge relief to not have to care about creating all the unikernels just because albatross-daemon was killed.
## Remote management
There's one more daemon program, either albatross-tls-inetd (to be executed by inetd), or albatross-tls-endpoint. They accept clients via a remote TCP connection, and establish a mutual-authenticated TLS handshake. When done, they forward the command to the respective Unix domain socket, and send back the reply.
The daemon itself has a X.509 certificate to authenticate, but the client is requested to show its certificate chain as well. This by now requires TLS 1.3, so the client certificates are sent over the encrypted channel.
A step back, x X.509 certificate contains a public key and a signature from one level up. When the server knows about the root (or certificate authority (CA)) certificate, and following the chain can verify that the leaf certificate is valid. Additionally, a X.509 certificate is a ASN.1 structure with some fixed fields, but also contains extensions, a key-value store where the keys are object identifiers, and the values are key-dependent data. Also note that this key-value store is cryptographically signed.
Albatross uses the object identifier, assigned to Camelus Dromedarius (MirageOS - 1.3.6.1.4.1.49836.42) to encode the command to be executed. This means that once the TLS handshake is established, the command to be executed is already transferred.
In the leaf certificate, there may be the "create unikernel" command with the unikernel image, it's boot parameters, and other resources. Or a "read the console of my unikernel". In the intermediate certificates (from root to leaf), resource policies are encoded (this path may only have X unikernels running with a total of Y MB memory, and Z MB of block storage, using CPUs A and B, accessing bridges C and D). From the root downwards these policies may only decrease. When a unikernel should be created (or other commands are executed), the policies are verified to hold. If they do not, an error is reported.
## Fleet management
Of course it is very fine to create your locally compiled unikernel to your albatross server, go for it. But in terms of "what is actually running here?" and "does this unikernel need to be updated because some opam package had a security issues?", this is not optimal.
Since we provide [daily reproducible builds](https://builds.robur.coop) with the current HEAD of the main opam-repository, and these unikernels have no configuration embedded (but take everything as boot parameters), we just deploy them. They come with the information what opam packages contributed to the binary, which environment variables were set, and which system packages were installed with which versions.
The whole result of reproducible builds for us means: we have a hash of a unikernel image that we can lookup in our build infrastructure, and take a look whether there is a newer image for the same job. And if there is, we provide a diff between the packages contributed to the currently running unikernel and the new image. That is what the albatross-client update command is all about.
Of course, your mileage may vary and you want automated deployments where each git commit triggers recompilation and redeployment. The downside would be that sometimes only dependencies are updated and you've to cope with that.
At the moment, there is a client connecting directly to the unix domain sockets, `albatross-client-local`, and one connecting to the TLS endpoint, `albatross-client-bistro`. The latter applies compression to the unikernel image.
## Installation
For Debian and Ubuntu systems, we provide package repositories. Browse the dists folder for one matching your distribution, and add it to `/etc/apt/sources.list`:
```
$ wget -q -O /etc/apt/trusted.gpg.d/apt.robur.coop.gpg https://apt.robur.coop/gpg.pub
$ echo "deb https://apt.robur.coop ubuntu-20.04 main" >> /etc/apt/sources.list # replace ubuntu-20.04 with e.g. debian-11 on a debian buster machine
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Deploying reproducible unikernels with albatross</title><meta charset="UTF-8"/><link rel="stylesheet" href="/static/css/style.css"/><link rel="stylesheet" href="/static/css/highlight.css"/><script src="/static/js/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="alternate" href="/atom" title="Deploying reproducible unikernels with albatross" type="application/atom+xml"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/></head><body><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><a class="navbar-brand" href="/Posts">full stack engineer</a></div><div class="collapse navbar-collapse collapse"><ul class="nav navbar-nav navbar-right"><li><a href="/About"><span>About</span></a></li><li><a href="/Posts"><span>Posts</span></a></li></ul></div></div></nav><main><div class="flex-container"><div class="post"><h2>Deploying reproducible unikernels with albatross</h2><span class="author">Written by hannes</span><br/><div class="tags">Classified under: <a href="/tags/mirageos" class="tag">mirageos</a><a href="/tags/deployment" class="tag">deployment</a></div><span class="date">Published: 2022-11-17 (last updated: 2022-11-17)</span><article><h2>Deploying MirageOS unikernels</h2>
<p>More than five years ago, I posted <a href="/Posts/VMM">how to deploy MirageOS unikernels</a>. My motivation to work on this topic is that I'm convinced of reduced complexity, improved security, and more sustainable resource footprint of MirageOS unikernels, and want to ease deployment thereof. More than one year ago, I described <a href="/Posts/Deploy">how to deploy reproducible unikernels</a>.</p>
<h2>Albatross</h2>
<p>In recent months we worked hard on the underlying infrastructure: <a href="https://github.com/roburio/albatross">albatross</a>. Albatross is the orchestration system for MirageOS unikernels that use solo5 with <a href="https://github.com/Solo5/solo5/blob/master/docs/architecture.md">hvt or spt tender</a>. It deals with three tasks:</p>
<ul>
<li>unikernel creation (destroyal, restart)
</li>
<li>capturing console output
</li>
<li>collecting metrics in the host system about unikernels
</li>
</ul>
<p>An addition to the above is dealing with multiple tenants on the same machine: remote management of your unikernel fleet via TLS, and resource policies.</p>
<h2>History</h2>
<p>The initial commit of albatross was in May 2017. Back then it replaced the shell scripts and manual <code>scp</code> of unikernel images to the server. Over time it evolved and adapted to new environments. Initially a solo5 unikernel would only know of a single network interface, these days there can be multiple distinguished by name. Initially there was no support for block devices. Only FreeBSD was supported in the early days. Nowadays we built daily packages for Debian, Ubuntu, FreeBSD, and have support for NixOS, and the client side is supported on macOS as well.</p>
<h3>ASN.1</h3>
<p>The communication format between the albatross daemons and clients was changed multiple times. I'm glad that albatross uses ASN.1 as communication format, which makes extension with optional fields easy, and also allows &quot;choice&quot; (the sum type) to be not tagged (the binary is the same as no choice type), thus adding choice to an existing grammar, and preserving the old in the default (untagged) case is a decent solution.</p>
<p>So, if you care about backward and forward compatibility, as we do, since we may be in control of which albatross servers are deployed on our machine, but not what albatross versions the clients are using -- it may be wise to look into ASN.1. Recent efforts (json with schema, ...) may solve similar issues, but ASN.1 is as well very tiny in size.</p>
<h2>What resources does a unikernel need?</h2>
<p>A unikernel is just an operating system for a single service, there can't be much it can need.</p>
<h3>Name</h3>
<p>So, first of all a unikernel has a name, or a handle. This is useful for reporting statistics, but also to specify which console output you're interested in. The name is a string with printable ASCII characters (and dash '-' and dot '.'), with a length up to 64 characters - so yes, you can use an UUID if you like.</p>
<h3>Memory</h3>
<p>Another resource is the amount of memory assigned to the unikernel. This is specified in megabyte (as solo5 does), with the range being 10 (below not even a hello world wants to start) to 1024.</p>
<h3>Arguments</h3>
<p>Of course, you can pass via albatross boot parameters to the unikernel. Albatross doesn't impose any restrictions here, but the lower levels may.</p>
<h3>CPU</h3>
<p>Due to multiple tenants, and side channel attacks, it looked right at the beginning like a good idea to restrict each unikernel to a specific CPU. This way, one tenant may use CPU 5, and another CPU 9 - and they'll not starve each other (best to make sure that these CPUs are in different packages). So, albatross takes a number as the CPU, and executes the solo5 tender within <code>taskset</code>/<code>cpuset</code>.</p>
<h3>Fail behaviour</h3>
<p>In normal operations, exceptional behaviour may occur. I have to admit that I've seen MirageOS unikernels that suffer from not freeing all the memory they have allocated. To avoid having to get up at 4 AM just to start the unikernel that went out of memory, there's the possibility to restart the unikernel when it exited. You can even specify on which exit codes it should be restarted (the exit code is the only piece of information we have from the outside what caused the exit). This feature was implemented in October 2019, and has been very precious since then. :)</p>
<h3>Network</h3>
<p>This becomes a bit more complex: a MirageOS unikernel can have network interfaces, and solo5 specifies a so-called manifest with a list of these (name and type, and type is so far always basic). Then, on the actual server there are bridges (virtual switches) configured. Now, these may have the same name, or may need to be mapped. And of course, the unikernel expects a tap interface that is connected to such a bridge, not the bridge itself. Thus, albatross creates tap devices, attaches these to the respective bridges, and takes care about cleaning them up on teardown. The albatross client verifies that for each network interface in the manifest, there is a command-line argument specified (<code>--net service:my_bridge</code> or just <code>--net service</code> if the bridge is named service). The tap interface name is not really of interest to the user, and will not be exposed.</p>
<h3>Block devices</h3>
<p>On the host system, it's just a file, and passed to the unikernel. There's the need to be able to create one, dump it, and ensure that each file is only used by one unikernel. That's all that is there.</p>
<h2>Metrics</h2>
<p>Everyone likes graphs, over time, showing how much traffic or CPU or memory or whatever has been used by your service. Some of these statistics are only available in the host system, and it is also crucial for development purposes to compare whether the bytes sent in the unikernel sum up to the same on the host system's tap interface.</p>
<p>The albatross-stats daemon collects metrics from three sources: network interfaces, getrusage (of a child process), VMM debug counters (to count VM exits etc.). Since the recent 1.5.3, albatross-stats now connects at startup to the albatross-daemon and then retrieves the information which unikernels are up and running, and starts periodically collecting data in memory.</p>
<p>Other clients, being it a dump on your console window, a write into an rrd file (good old MRTG times), or a push to influx, can use the stats data to correlate and better analyse what is happening on the grand scale of things. This helped a lot by running several unikernels with different opam package sets to figure out which opam packages leave their hands on memory over time.</p>
<p>As a side note, if you make the unikernel name also available in the unikernel, it can tag its own metrics with the same identifier, and you can correlate high-level events (such as amount of HTTP requests) with low-level things &quot;allocated more memory&quot; or &quot;consumed a lot of CPU&quot;.</p>
<h2>Console</h2>
<p>There's not much to say about the console, just that the albatross-console daemon is running with low privileges, and reading from a FIFO that the unikernel writes to. It never writes anything to disk, but keeps the last 1000 lines in memory, available from a client asking for it.</p>
<h2>The daemons</h2>
<p>So, the main albatross-daemon runs with superuser privileges to create virtual machines, and opens a unix domain socket where the clients and other daemons are connecting to. The other daemons are executed with normal user privileges, and never write anything to disk.</p>
<p>The albatross-daemon keeps state about the running unikernels, and if it is restarted, the unikernels are started again. Maybe worth to mention that this lead sometimes to headaches (due to data being dumped to disk, and the old format should always be supported), but was also a huge relief to not have to care about creating all the unikernels just because albatross-daemon was killed.</p>
<h2>Remote management</h2>
<p>There's one more daemon program, either albatross-tls-inetd (to be executed by inetd), or albatross-tls-endpoint. They accept clients via a remote TCP connection, and establish a mutual-authenticated TLS handshake. When done, they forward the command to the respective Unix domain socket, and send back the reply.</p>
<p>The daemon itself has a X.509 certificate to authenticate, but the client is requested to show its certificate chain as well. This by now requires TLS 1.3, so the client certificates are sent over the encrypted channel.</p>
<p>A step back, x X.509 certificate contains a public key and a signature from one level up. When the server knows about the root (or certificate authority (CA)) certificate, and following the chain can verify that the leaf certificate is valid. Additionally, a X.509 certificate is a ASN.1 structure with some fixed fields, but also contains extensions, a key-value store where the keys are object identifiers, and the values are key-dependent data. Also note that this key-value store is cryptographically signed.</p>
<p>Albatross uses the object identifier, assigned to Camelus Dromedarius (MirageOS - 1.3.6.1.4.1.49836.42) to encode the command to be executed. This means that once the TLS handshake is established, the command to be executed is already transferred.</p>
<p>In the leaf certificate, there may be the &quot;create unikernel&quot; command with the unikernel image, it's boot parameters, and other resources. Or a &quot;read the console of my unikernel&quot;. In the intermediate certificates (from root to leaf), resource policies are encoded (this path may only have X unikernels running with a total of Y MB memory, and Z MB of block storage, using CPUs A and B, accessing bridges C and D). From the root downwards these policies may only decrease. When a unikernel should be created (or other commands are executed), the policies are verified to hold. If they do not, an error is reported.</p>
<h2>Fleet management</h2>
<p>Of course it is very fine to create your locally compiled unikernel to your albatross server, go for it. But in terms of &quot;what is actually running here?&quot; and &quot;does this unikernel need to be updated because some opam package had a security issues?&quot;, this is not optimal.</p>
<p>Since we provide <a href="https://builds.robur.coop">daily reproducible builds</a> with the current HEAD of the main opam-repository, and these unikernels have no configuration embedded (but take everything as boot parameters), we just deploy them. They come with the information what opam packages contributed to the binary, which environment variables were set, and which system packages were installed with which versions.</p>
<p>The whole result of reproducible builds for us means: we have a hash of a unikernel image that we can lookup in our build infrastructure, and take a look whether there is a newer image for the same job. And if there is, we provide a diff between the packages contributed to the currently running unikernel and the new image. That is what the albatross-client update command is all about.</p>
<p>Of course, your mileage may vary and you want automated deployments where each git commit triggers recompilation and redeployment. The downside would be that sometimes only dependencies are updated and you've to cope with that.</p>
<p>At the moment, there is a client connecting directly to the unix domain sockets, <code>albatross-client-local</code>, and one connecting to the TLS endpoint, <code>albatross-client-bistro</code>. The latter applies compression to the unikernel image.</p>
<h2>Installation</h2>
<p>For Debian and Ubuntu systems, we provide package repositories. Browse the dists folder for one matching your distribution, and add it to <code>/etc/apt/sources.list</code>:</p>
<pre><code>$ wget -q -O /etc/apt/trusted.gpg.d/apt.robur.coop.gpg https://apt.robur.coop/gpg.pub
$ echo &quot;deb https://apt.robur.coop ubuntu-20.04 main&quot; &gt;&gt; /etc/apt/sources.list # replace ubuntu-20.04 with e.g. debian-11 on a debian buster machine
$ apt update
$ apt install solo5 albatross
```
On FreeBSD:
```
$ fetch -o /usr/local/etc/pkg/robur.pub https://pkg.robur.coop/repo.pub # download RSA public key
</code></pre>
<p>On FreeBSD:</p>
<pre><code>$ fetch -o /usr/local/etc/pkg/robur.pub https://pkg.robur.coop/repo.pub # download RSA public key
$ echo 'robur: {
url: "https://pkg.robur.coop/${ABI}",
mirror_type: "srv",
signature_type: "pubkey",
pubkey: "/usr/local/etc/pkg/robur.pub",
url: &quot;https://pkg.robur.coop/${ABI}&quot;,
mirror_type: &quot;srv&quot;,
signature_type: &quot;pubkey&quot;,
pubkey: &quot;/usr/local/etc/pkg/robur.pub&quot;,
enabled: yes
}' > /usr/local/etc/pkg/repos/robur.conf # Check https://pkg.robur.coop which ABI are available
}' &gt; /usr/local/etc/pkg/repos/robur.conf # Check https://pkg.robur.coop which ABI are available
$ pkg update
$ pkg install solo5 albatross
```
For other distributions and systems we do not (yet?) provide binary packages. You can compile and install them using opam (`opam install solo5 albatross`). Get in touch if you're keen on adding some other distribution to our reproducible build infrastructure.
## Conclusion
After five years of development and operating albatross, feel free to get it and try it out. Or read the code, discuss issues and shortcomings with us - either at the issue tracker or via eMail.
Please reach out to us (at team AT robur DOT coop) if you have feedback and suggestions. We are a non-profit company, and rely on [donations](https://robur.coop/Donate) for doing our work - everyone can contribute.
</code></pre>
<p>For other distributions and systems we do not (yet?) provide binary packages. You can compile and install them using opam (<code>opam install solo5 albatross</code>). Get in touch if you're keen on adding some other distribution to our reproducible build infrastructure.</p>
<h2>Conclusion</h2>
<p>After five years of development and operating albatross, feel free to get it and try it out. Or read the code, discuss issues and shortcomings with us - either at the issue tracker or via eMail.</p>
<p>Please reach out to us (at team AT robur DOT coop) if you have feedback and suggestions. We are a non-profit company, and rely on <a href="https://robur.coop/Donate">donations</a> for doing our work - everyone can contribute.</p>
</article></div></div></main></body></html>

@ -1,36 +1,17 @@
---
title: Catch the bug, walking through the stack
author: hannes
tags: mirageos, security
abstract: 10BTC could've been yours
---
## BAD RECORD MAC
Roughly 2 weeks ago, [Engil](https://github.com/Engil) informed me that a TLS alert pops up in his browser sometimes when he reads this website. His browser reported that the [message authentication code](https://en.wikipedia.org/wiki/Message_authentication_code) was wrong. From [RFC 5246](https://tools.ietf.org/html/rfc5246): This message is always fatal and should never be observed in communication between proper implementations (except when messages were corrupted in the network).
I tried hard, but could not reproduce, but was very worried and was eager to find the root cause (some little fear remained that it was in our TLS stack). I setup this website with some TLS-level tracing (extending the code from our [TLS handshake server](https://tls.openmirage.org)). We tried to reproduce the issue with traces and packet captures (both on client and server side) in place from our computer labs office with no success. Later, Engil tried from his home and after 45MB of wire data, ran into this issue. Finally, evidence! Isolating the TCP flow with the alert resulted in just about 200KB of packet capture data (TLS ASCII trace around 650KB).
![encrypted alert](/static/img/encrypted-alert.png)
What is happening on the wire? After some data is successfully transferred, at some point the client sends an encrypted alert (see above). The TLS session used a RSA key exchange and I could decrypt the TLS stream with Wireshark, which revealed that the alert was indeed a bad record MAC. Wireshark's "follow SSL stream" showed all client requests, but not all server responses. The TLS level trace from the server showed properly encrypted data. I tried to spot the TCP payload which caused the bad record MAC, starting from the alert in the client capture (the offending TCP frame should be closely before the alert).
![client TCP frame](/static/img/tcp-frame-client.png)
There is plaintext data which looks like a HTTP request in the TCP frame sent by the server to the client? WTF? This should never happen! The same TCP frame on the server side looked even more strange: it had an invalid checksum.
![server TCP frame](/static/img/tcp-frame-server.png)
What do we have so far? We spotted some plaintext data in a TCP frame which is part of a TLS session. The TCP checksum is invalid.
This at least explains why we were not able to reproduce from our office: usually, TCP frames with invalid checksums are dropped by the receiving TCP stack, and the sender will retransmit TCP frames which have not been acknowledged by the recipient. However, this mechanism only works if the checksums haven't been changed by a well-meaning middleman to be correct! Our traces are from a client behind a router doing [network address translation](https://en.wikipedia.org/wiki/Network_address_translation), which has to recompute the [TCP checksum](https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation) because it modifies destination IP address and port. It seems like this specific router does not validate the TCP checksum before recomputing it, so it replaced the invalid TCP checksum with a valid one.
Next steps are: what did the TLS layer intended to send? Why is there a TCP frame with an invalid checksum emitted?
Looking into the TLS trace, the TCP payload in question should have started with the following data:
```
0000 0B C9 E5 F3 C5 32 43 6F 53 68 ED 42 F8 67 DA 8B .....2Co Sh.B.g..
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Catch the bug, walking through the stack</title><meta charset="UTF-8"/><link rel="stylesheet" href="/static/css/style.css"/><link rel="stylesheet" href="/static/css/highlight.css"/><script src="/static/js/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="alternate" href="/atom" title="Catch the bug, walking through the stack" type="application/atom+xml"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/></head><body><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><a class="navbar-brand" href="/Posts">full stack engineer</a></div><div class="collapse navbar-collapse collapse"><ul class="nav navbar-nav navbar-right"><li><a href="/About"><span>About</span></a></li><li><a href="/Posts"><span>Posts</span></a></li></ul></div></div></nav><main><div class="flex-container"><div class="post"><h2>Catch the bug, walking through the stack</h2><span class="author">Written by hannes</span><br/><div class="tags">Classified under: <a href="/tags/mirageos" class="tag">mirageos</a><a href="/tags/security" class="tag">security</a></div><span class="date">Published: 2016-05-03 (last updated: 2021-11-19)</span><article><h2>BAD RECORD MAC</h2>
<p>Roughly 2 weeks ago, <a href="https://github.com/Engil">Engil</a> informed me that a TLS alert pops up in his browser sometimes when he reads this website. His browser reported that the <a href="https://en.wikipedia.org/wiki/Message_authentication_code">message authentication code</a> was wrong. From <a href="https://tools.ietf.org/html/rfc5246">RFC 5246</a>: This message is always fatal and should never be observed in communication between proper implementations (except when messages were corrupted in the network).</p>
<p>I tried hard, but could not reproduce, but was very worried and was eager to find the root cause (some little fear remained that it was in our TLS stack). I setup this website with some TLS-level tracing (extending the code from our <a href="https://tls.openmirage.org">TLS handshake server</a>). We tried to reproduce the issue with traces and packet captures (both on client and server side) in place from our computer labs office with no success. Later, Engil tried from his home and after 45MB of wire data, ran into this issue. Finally, evidence! Isolating the TCP flow with the alert resulted in just about 200KB of packet capture data (TLS ASCII trace around 650KB).</p>
<p><img src="/static/img/encrypted-alert.png" alt="encrypted alert" /></p>
<p>What is happening on the wire? After some data is successfully transferred, at some point the client sends an encrypted alert (see above). The TLS session used a RSA key exchange and I could decrypt the TLS stream with Wireshark, which revealed that the alert was indeed a bad record MAC. Wireshark's &quot;follow SSL stream&quot; showed all client requests, but not all server responses. The TLS level trace from the server showed properly encrypted data. I tried to spot the TCP payload which caused the bad record MAC, starting from the alert in the client capture (the offending TCP frame should be closely before the alert).</p>
<p><img src="/static/img/tcp-frame-client.png" alt="client TCP frame" /></p>
<p>There is plaintext data which looks like a HTTP request in the TCP frame sent by the server to the client? WTF? This should never happen! The same TCP frame on the server side looked even more strange: it had an invalid checksum.</p>
<p><img src="/static/img/tcp-frame-server.png" alt="server TCP frame" /></p>
<p>What do we have so far? We spotted some plaintext data in a TCP frame which is part of a TLS session. The TCP checksum is invalid.</p>
<p>This at least explains why we were not able to reproduce from our office: usually, TCP frames with invalid checksums are dropped by the receiving TCP stack, and the sender will retransmit TCP frames which have not been acknowledged by the recipient. However, this mechanism only works if the checksums haven't been changed by a well-meaning middleman to be correct! Our traces are from a client behind a router doing <a href="https://en.wikipedia.org/wiki/Network_address_translation">network address translation</a>, which has to recompute the <a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Checksum_computation">TCP checksum</a> because it modifies destination IP address and port. It seems like this specific router does not validate the TCP checksum before recomputing it, so it replaced the invalid TCP checksum with a valid one.</p>
<p>Next steps are: what did the TLS layer intended to send? Why is there a TCP frame with an invalid checksum emitted?</p>
<p>Looking into the TLS trace, the TCP payload in question should have started with the following data:</p>
<pre><code>0000 0B C9 E5 F3 C5 32 43 6F 53 68 ED 42 F8 67 DA 8B .....2Co Sh.B.g..
0010 17 87 AB EA 3F EC 99 D4 F3 38 88 E6 E3 07 D5 6E ....?... .8.....n
0020 94 9A 81 AF DD 76 E2 7C 6F 2A C6 98 BA 70 1A AD .....v.| o*...p..
0030 95 5E 13 B0 F7 A3 8C 25 6B 3D 59 CE 30 EC 56 B8 .^.....% k=Y.0.V.
@ -40,40 +21,39 @@ Looking into the TLS trace, the TCP payload in question should have started with
0070 45 6D 7F A6 1D B7 0F 43 C4 D0 8C CF 52 77 9F 06 Em.....C ....Rw..
0080 59 31 E0 9D B2 B5 34 BD A4 4B 3F 02 2E 56 B9 A9 Y1....4. .K?..V..
0090 95 38 FD AD 4A D6 35 E4 66 86 6E 03 AF 2C C9 00 .8..J.5. f.n..,..
```
The ethernet, IP, and TCP headers are in total 54 bytes, thus we have to compare starting at 0x0036 in the screenshot above. The first 74 bytes (till 0x007F in the screenshot, 0x0049 in the text dump) are very much the same, but then they diverge (for another 700 bytes).
I manually computed the TCP checksum using the TCP/IP payload from the TLS trace, and it matches the one reported as invalid. Thus, a big relief: both the TLS and the TCP/IP stack have used the correct data. Our memory disclosure issue must be after the [TCP checksum is computed](https://github.com/mirage/mirage-tcpip/blob/1617953b4674c9c832786c1ab3236b91d00f5c25/tcp/wire.ml#L78). After this:
* the [IP frame header is filled](https://github.com/mirage/mirage-tcpip/blob/1617953b4674c9c832786c1ab3236b91d00f5c25/lib/ipv4.ml#L98)
* the [mac addresses are put](https://github.com/mirage/mirage-tcpip/blob/1617953b4674c9c832786c1ab3236b91d00f5c25/lib/ipv4.ml#L126) into the ethernet frame
* the frame is then passed to [mirage-net-xen for sending](https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L538) via the Xen hypervisor.
As mentioned [earlier](/Posts/OCaml) I'm still using mirage-net-xen release 1.4.1.
Communication with the Xen hypervisor is done via shared memory. The memory is allocated by mirage-net-xen, which then grants access to the hypervisor using [Xen grant tables](http://wiki.xen.org/wiki/Grant_Table). The TX protocol is implemented [here in mirage-net-xen](https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L84-L132), which includes allocation of a [ring buffer](https://github.com/mirage/shared-memory-ring/blob/2955bf502c79bc963a02d090481b0e8958cc0c49/lwt/lwt_ring.mli). The TX protocol also has implementations for writing requests and waiting for responses, both of which are identified using a 16bit integer. When a response has arrived from the hypervisor, the respective page is returned into the pool of [shared pages](https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L134-L213), to be reused by the next packet to be transmitted.
Instead of a whole page (4096 byte) per request/response, each page is [split into two blocks](https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L194-L198) (since the most common [MTU](https://en.wikipedia.org/wiki/Maximum_transmission_unit) for ethernet is 1500 bytes). The [identifier in use](https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L489-L490) is the grant reference, which might be unique per page, but not per block.
Thus, when two blocks are requested to be sent, the first [polled response](https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L398) will immediately [release](https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L182-L185) both into the list of free blocks. When another packet is sent, the block still waiting to be sent in the ringbuffer can be reused. This leads to corrupt data being sent.
The fix was already done [back in December](https://github.com/mirage/mirage-net-xen/commit/47de2edfad9c56110d98d0312c1a7e0b9dcc8fbf) to the master branch of mirage-net-xen, and has now been [backported to the 1.4 branch](https://github.com/mirage/mirage-net-xen/pull/40/commits/ec9b1046b75cba5ae3473b2d3b223c3d1284489d). In addition, a patch to [avoid collisions on the receiving side](https://github.com/mirage/mirage-net-xen/commit/0b1e53c0875062a50e2d5823b7da0d8e0a64dc37) has been applied to both branches (and released in versions 1.4.2 resp. 1.6.1).
What can we learn from this? Read the interface documentation (if there is any), and make sure unique identifiers are really unique. Think about the lifecycle of pieces of memory. Investigation of high level bugs pays off, you might find some subtle error on a different layer. There is no perfect security, and code only gets better if more people read and understand it.
The issue was in mirage-net-xen since its initial release, but only occured under load, and thanks to reliable protocols, was silently discarded (an invalid TCP checksum leads to a dropped frame and retransmission of its payload).
We have seen plain data in a TLS encrypted stream. The plain data was intended to be sent to the dom0 for logging access to the webserver. The [same code](https://github.com/mirleft/btc-pinata/blob/master/logger.ml) is used in our [Piñata](http://ownme.ipredator.se), thus it could have been yours (although I tried hard and couldn't get the Piñata to leak data).
Certainly, interfacing the outside world is complex. The [mirage-block-xen](https://github.com/mirage/mirage-block-xen) library uses a similar protocol to access block devices. From a brief look, that library seems to be safe (using 64bit identifiers).
I'm interested in feedback, either via
[twitter](https://twitter.com/h4nnes) or via eMail.
## Other updates in the MirageOS ecosystem
- Canopy uses a [map instead of a hashtable](https://github.com/Engil/Canopy/issues/30#issuecomment-215010365), [tags](https://hannes.nqsb.io/tags) now contains a list of tags ([PR here](https://github.com/Engil/Canopy/pull/39)), both thanks to voila! I also use the [new CSS](https://github.com/Engil/Canopy/pull/38) from Engil
- There is a [CVE for OCaml <=4.03](http://www.openwall.com/lists/oss-security/2016/04/29/1)
- [Mirage 2.9.0](https://github.com/mirage/mirage/pull/534) was released, which integrates support of the logs library (now already used in [mirage-net-xen](https://github.com/mirage/mirage-net-xen/pull/43) and [mirage-tcpip](https://github.com/mirage/mirage-tcpip/pull/199))
- This blog post has an accompanied [MirageOS security advisory](https://mirage.io/blog/MSA00)
- cfcs documented some [basic unikernels](https://github.com/cfcs/mirage-examples)
</code></pre>
<p>The ethernet, IP, and TCP headers are in total 54 bytes, thus we have to compare starting at 0x0036 in the screenshot above. The first 74 bytes (till 0x007F in the screenshot, 0x0049 in the text dump) are very much the same, but then they diverge (for another 700 bytes).</p>
<p>I manually computed the TCP checksum using the TCP/IP payload from the TLS trace, and it matches the one reported as invalid. Thus, a big relief: both the TLS and the TCP/IP stack have used the correct data. Our memory disclosure issue must be after the <a href="https://github.com/mirage/mirage-tcpip/blob/1617953b4674c9c832786c1ab3236b91d00f5c25/tcp/wire.ml#L78">TCP checksum is computed</a>. After this:</p>
<ul>
<li>the <a href="https://github.com/mirage/mirage-tcpip/blob/1617953b4674c9c832786c1ab3236b91d00f5c25/lib/ipv4.ml#L98">IP frame header is filled</a>
</li>
<li>the <a href="https://github.com/mirage/mirage-tcpip/blob/1617953b4674c9c832786c1ab3236b91d00f5c25/lib/ipv4.ml#L126">mac addresses are put</a> into the ethernet frame
</li>
<li>the frame is then passed to <a href="https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L538">mirage-net-xen for sending</a> via the Xen hypervisor.
</li>
</ul>
<p>As mentioned <a href="/Posts/OCaml">earlier</a> I'm still using mirage-net-xen release 1.4.1.</p>
<p>Communication with the Xen hypervisor is done via shared memory. The memory is allocated by mirage-net-xen, which then grants access to the hypervisor using <a href="http://wiki.xen.org/wiki/Grant_Table">Xen grant tables</a>. The TX protocol is implemented <a href="https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L84-L132">here in mirage-net-xen</a>, which includes allocation of a <a href="https://github.com/mirage/shared-memory-ring/blob/2955bf502c79bc963a02d090481b0e8958cc0c49/lwt/lwt_ring.mli">ring buffer</a>. The TX protocol also has implementations for writing requests and waiting for responses, both of which are identified using a 16bit integer. When a response has arrived from the hypervisor, the respective page is returned into the pool of <a href="https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L134-L213">shared pages</a>, to be reused by the next packet to be transmitted.</p>
<p>Instead of a whole page (4096 byte) per request/response, each page is <a href="https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L194-L198">split into two blocks</a> (since the most common <a href="https://en.wikipedia.org/wiki/Maximum_transmission_unit">MTU</a> for ethernet is 1500 bytes). The <a href="https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L489-L490">identifier in use</a> is the grant reference, which might be unique per page, but not per block.</p>
<p>Thus, when two blocks are requested to be sent, the first <a href="https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L398">polled response</a> will immediately <a href="https://github.com/mirage/mirage-net-xen/blob/541e86f53cb8cf426aabdd7f090779fc5ea9fe93/lib/netif.ml#L182-L185">release</a> both into the list of free blocks. When another packet is sent, the block still waiting to be sent in the ringbuffer can be reused. This leads to corrupt data being sent.</p>
<p>The fix was already done <a href="https://github.com/mirage/mirage-net-xen/commit/47de2edfad9c56110d98d0312c1a7e0b9dcc8fbf">back in December</a> to the master branch of mirage-net-xen, and has now been <a href="https://github.com/mirage/mirage-net-xen/pull/40/commits/ec9b1046b75cba5ae3473b2d3b223c3d1284489d">backported to the 1.4 branch</a>. In addition, a patch to <a href="https://github.com/mirage/mirage-net-xen/commit/0b1e53c0875062a50e2d5823b7da0d8e0a64dc37">avoid collisions on the receiving side</a> has been applied to both branches (and released in versions 1.4.2 resp. 1.6.1).</p>
<p>What can we learn from this? Read the interface documentation (if there is any), and make sure unique identifiers are really unique. Think about the lifecycle of pieces of memory. Investigation of high level bugs pays off, you might find some subtle error on a different layer. There is no perfect security, and code only gets better if more people read and understand it.</p>
<p>The issue was in mirage-net-xen since its initial release, but only occured under load, and thanks to reliable protocols, was silently discarded (an invalid TCP checksum leads to a dropped frame and retransmission of its payload).</p>
<p>We have seen plain data in a TLS encrypted stream. The plain data was intended to be sent to the dom0 for logging access to the webserver. The <a href="https://github.com/mirleft/btc-pinata/blob/master/logger.ml">same code</a> is used in our <a href="http://ownme.ipredator.se">Piñata</a>, thus it could have been yours (although I tried hard and couldn't get the Piñata to leak data).</p>
<p>Certainly, interfacing the outside world is complex. The <a href="https://github.com/mirage/mirage-block-xen">mirage-block-xen</a> library uses a similar protocol to access block devices. From a brief look, that library seems to be safe (using 64bit identifiers).</p>
<p>I'm interested in feedback, either via
<a href="https://twitter.com/h4nnes">twitter</a> or via eMail.</p>
<h2>Other updates in the MirageOS ecosystem</h2>
<ul>
<li>Canopy uses a <a href="https://github.com/Engil/Canopy/issues/30#issuecomment-215010365">map instead of a hashtable</a>, <a href="https://hannes.nqsb.io/tags">tags</a> now contains a list of tags (<a href="https://github.com/Engil/Canopy/pull/39">PR here</a>), both thanks to voila! I also use the <a href="https://github.com/Engil/Canopy/pull/38">new CSS</a> from Engil
</li>
<li>There is a <a href="http://www.openwall.com/lists/oss-security/2016/04/29/1">CVE for OCaml &lt;=4.03</a>
</li>
<li><a href="https://github.com/mirage/mirage/pull/534">Mirage 2.9.0</a> was released, which integrates support of the logs library (now already used in <a href="https://github.com/mirage/mirage-net-xen/pull/43">mirage-net-xen</a> and <a href="https://github.com/mirage/mirage-tcpip/pull/199">mirage-tcpip</a>)
</li>
<li>This blog post has an accompanied <a href="https://mirage.io/blog/MSA00">MirageOS security advisory</a>
</li>
<li>cfcs documented some <a href="https://github.com/cfcs/mirage-examples">basic unikernels</a>
</li>
</ul>
</article></div></div></main></body></html>

@ -1,108 +1,61 @@
---
title: Counting Bytes
author: hannes
tags: mirageos, background
abstract: looking into dependencies and their sizes
---
I was busy writing code, text, talks, and also spend a week without Internet, where I ground and brewed 15kg espresso.
## Size of a MirageOS unikernel
There have been lots of claims and myths around the concrete size of MirageOS unikernels. In this article I'll apply some measurements which overapproximate the binary sizes. The tools used for the visualisations are available online, and soon hopefully upstreamed into the mirage tool. This article uses mirage-2.9.0 (which might be outdated at the time of reading).
Let us start with a very minimal unikernel, consisting of a `unikernel.ml`:
```OCaml
module Main (C: V1_LWT.CONSOLE) = struct
let start c = C.log_s c "hello world"
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Counting Bytes</title><meta charset="UTF-8"/><link rel="stylesheet" href="/static/css/style.css"/><link rel="stylesheet" href="/static/css/highlight.css"/><script src="/static/js/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="alternate" href="/atom" title="Counting Bytes" type="application/atom+xml"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/></head><body><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><a class="navbar-brand" href="/Posts">full stack engineer</a></div><div class="collapse navbar-collapse collapse"><ul class="nav navbar-nav navbar-right"><li><a href="/About"><span>About</span></a></li><li><a href="/Posts"><span>Posts</span></a></li></ul></div></div></nav><main><div class="flex-container"><div class="post"><h2>Counting Bytes</h2><span class="author">Written by hannes</span><br/><div class="tags">Classified under: <a href="/tags/mirageos" class="tag">mirageos</a><a href="/tags/background" class="tag">background</a></div><span class="date">Published: 2016-06-11 (last updated: 2021-11-19)</span><article><p>I was busy writing code, text, talks, and also spend a week without Internet, where I ground and brewed 15kg espresso.</p>
<h2>Size of a MirageOS unikernel</h2>
<p>There have been lots of claims and myths around the concrete size of MirageOS unikernels. In this article I'll apply some measurements which overapproximate the binary sizes. The tools used for the visualisations are available online, and soon hopefully upstreamed into the mirage tool. This article uses mirage-2.9.0 (which might be outdated at the time of reading).</p>
<p>Let us start with a very minimal unikernel, consisting of a <code>unikernel.ml</code>:</p>
<pre><code class="language-OCaml">module Main (C: V1_LWT.CONSOLE) = struct
let start c = C.log_s c &quot;hello world&quot;
end
```
and the following `config.ml`:
```OCaml
open Mirage
</code></pre>
<p>and the following <code>config.ml</code>:</p>
<pre><code class="language-OCaml">open Mirage
let () =
register "console" [
foreign "Unikernel.Main" (console @-> job) $ default_console
register &quot;console&quot; [
foreign &quot;Unikernel.Main&quot; (console @-&gt; job) $ default_console
]
```
If we `mirage configure --unix` and `mirage build`, we end up (at least on a 64bit FreeBSD-11 system with OCaml 4.02.3) with a 2.8MB `main.native`, dynamically linked against `libthr`, `libm` and `libc` (`ldd` ftw), or a 4.5MB Xen virtual image (built on a 64bit Linux computer).
In the `_build` directory, we can find some object files and their byte sizes:
```bash
7144 key_gen.o
</code></pre>
<p>If we <code>mirage configure --unix</code> and <code>mirage build</code>, we end up (at least on a 64bit FreeBSD-11 system with OCaml 4.02.3) with a 2.8MB <code>main.native</code>, dynamically linked against <code>libthr</code>, <code>libm</code> and <code>libc</code> (<code>ldd</code> ftw), or a 4.5MB Xen virtual image (built on a 64bit Linux computer).</p>
<p>In the <code>_build</code> directory, we can find some object files and their byte sizes:</p>
<pre><code class="language-bash"> 7144 key_gen.o
14568 main.o
3552 unikernel.o
```
These do not sum up to 2.8MB ;)
We did not specify any dependencies ourselves, thus all bits have been injected automatically by the `mirage` tool. Let us dig a bit deeper what we actually used. `mirage configure` generates a `Makefile` which includes the dependent OCaml libraries, and the packages which are used:
```Makefile
LIBS = -pkgs functoria.runtime, mirage-clock-unix, mirage-console.unix, mirage-logs, mirage-types.lwt, mirage-unix, mirage.runtime
</code></pre>
<p>These do not sum up to 2.8MB ;)</p>
<p>We did not specify any dependencies ourselves, thus all bits have been injected automatically by the <code>mirage</code> tool. Let us dig a bit deeper what we actually used. <code>mirage configure</code> generates a <code>Makefile</code> which includes the dependent OCaml libraries, and the packages which are used:</p>
<pre><code class="language-Makefile">LIBS = -pkgs functoria.runtime, mirage-clock-unix, mirage-console.unix, mirage-logs, mirage-types.lwt, mirage-unix, mirage.runtime
PKGS = functoria lwt mirage-clock-unix mirage-console mirage-logs mirage-types mirage-types-lwt mirage-unix
```
I explained bits of our configuration DSL [Functoria](/Posts/Functoria) earlier. The [mirage-clock](https://github.com/mirage/mirage-clock) device is automatically injected by mirage, providing an implementation of the `CLOCK` device. We use a [mirage-console](https://github.com/mirage/mirage-console) device, where we print the `hello world`. Since `mirage-2.9.0` the logging library (and its reporter, [mirage-logs](https://github.com/mirage/mirage-logs)) is automatically injected as well, which actually uses the clock. Also, the [mirage type signatures](https://github.com/mirage/mirage/tree/master/types) are required. The [mirage-unix](https://github.com/mirage/mirage-platform/tree/master/unix) contains a `sleep`, a `main`, and provides the argument vector `argv` (all symbols in the `OS` module).
Looking into the archive files of those libraries, we end up with ~92KB (NB `mirage-types` only contains types, and thus no runtime data):
```bash
15268 functoria/functoria-runtime.a
</code></pre>
<p>I explained bits of our configuration DSL <a href="/Posts/Functoria">Functoria</a> earlier. The <a href="https://github.com/mirage/mirage-clock">mirage-clock</a> device is automatically injected by mirage, providing an implementation of the <code>CLOCK</code> device. We use a <a href="https://github.com/mirage/mirage-console">mirage-console</a> device, where we print the <code>hello world</code>. Since <code>mirage-2.9.0</code> the logging library (and its reporter, <a href="https://github.com/mirage/mirage-logs">mirage-logs</a>) is automatically injected as well, which actually uses the clock. Also, the <a href="https://github.com/mirage/mirage/tree/master/types">mirage type signatures</a> are required. The <a href="https://github.com/mirage/mirage-platform/tree/master/unix">mirage-unix</a> contains a <code>sleep</code>, a <code>main</code>, and provides the argument vector <code>argv</code> (all symbols in the <code>OS</code> module).</p>
<p>Looking into the archive files of those libraries, we end up with ~92KB (NB <code>mirage-types</code> only contains types, and thus no runtime data):</p>
<pre><code class="language-bash">15268 functoria/functoria-runtime.a
3194 mirage-clock-unix/mirage-clock.a
12514 mirage-console/mirage_console_unix.a
24532 mirage-logs/mirage_logs.a
14244 mirage-unix/OS.a
21964 mirage/mirage-runtime.a
```
This still does not sum up to 2.8MB since we're missing the transitive dependencies.
### Visualising recursive dependencies
Let's use a different approach: first recursively find all dependencies. We do this by using `ocamlfind` to read `META` files which contain a list of dependent libraries in their `requires` line. As input we use `LIBS` from the Makefile snippet above. The code (OCaml script) is [available here](https://gist.github.com/hannesm/bcbe54c5759ed5854f05c8f8eaee4c79). The colour scheme is red for pieces of the OCaml distribution, yellow for input packages, and orange for the dependencies.
[<img src="/static/img/mirage-console.svg" title="UNIX dependencies of hello world" width="700" />](/static/img/mirage-console.svg)
This is the UNIX version only, the Xen version looks similar (but worth mentioning).
[<img src="/static/img/mirage-console-xen.svg" title="Xen dependencies of hello world" width="700" />](/static/img/mirage-console-xen.svg)
You can spot at the right that `mirage-bootvar` uses `re`, which provoked me to [open a PR](https://github.com/mirage/mirage-bootvar-xen/pull/19), but Jon Ludlam [already had a nicer PR](https://github.com/mirage/mirage-bootvar-xen/pull/18) which is now merged (and a [new release is in preparation](https://github.com/mirage/mirage-bootvar-xen/pull/20)).
### Counting bytes
While a dependency graphs gives a big picture of what the composed libraries of a MirageOS unikernel, we also want to know how many bytes they contribute to the unikernel. The dependency graph only contains the OCaml-level dependencies, but MirageOS has in addition to that a `pkg-config` universe of the libraries written in C (such as mini-os, openlibm, ...).
We overapproximate the sizes here by assuming that a linker simply concatenates all required object files. This is not true, since the sum of all objects is empirically factor two of the actual size of the unikernel.
I developed a pie chart visualisation, but a friend of mine reminded me that such a chart is pretty useless for comparing slices for the human brain. I spent some more time to develop a treemap visualisation to satisfy the brain. The implemented algorithm is based on [squarified treemaps](http://www.win.tue.nl/~vanwijk/stm.pdf), but does not use implicit mutable state. In addition, the [provided script](https://gist.github.com/hannesm/c8a9b2e75bb4f98b5100a838ea125f3b) parses common linker flags (`-o -L -l`) and collects arguments to be linked in. It can be passed to `ocamlopt` as the C linker, more instructions at the end of `treemap.ml` (which should be cleaned up and integrated into the mirage tool, as mentioned earlier).
[<img src="/static/img/mirage-console-bytes.svg" title="byte sizes of hello-world (UNIX)" width="700" />](/static/img/mirage-console-bytes.svg)
[<img src="/static/img/mirage-console-xen-bytes-full.svg" title="byte sizes of hello-world (Xen)" width="700" />](/static/img/mirage-console-xen-bytes-full.svg)
As mentioned above, this is an overapproximation. The `libgcc.a` is only needed on Xen (see [this comment](https://github.com/mirage/mirage/commit/c17f2f60a6309322ba45cecb00a808f62f05cf82#commitcomment-17573123)), I have not yet tracked down why there is a `libasmrun.a` and a `libxenasmrun.a`.
### More complex examples
Besides the hello world, I used the same tools on our [BTC Piñata](http://ownme.ipredator.se).
[<img src="/static/img/pinata-deps.svg" title="Piñata dependencies" width="700" />](/static/img/pinata-deps.svg)
[<img src="/static/img/pinata-bytes.svg" title="Piñata byte sizes" width="700" />](/static/img/pinata-bytes.svg)
### Conclusion
OCaml does not yet do dead code elimination, but there [is a PR](https://github.com/ocaml/ocaml/pull/608) based on the flambda middle-end which does so. I haven't yet investigated numbers using that branch.
Those counting statistics could go into more detail (e.g. using `nm` to count the sizes of concrete symbols - which opens the possibility to see which symbols are present in the objects, but not in the final binary). Also, collecting the numbers for each module in a library would be great to have. In the end, it would be great to easily spot the source fragments which are responsible for a huge binary size (and getting rid of them).
I'm interested in feedback, either via
[twitter](https://twitter.com/h4nnes) or via eMail.
</code></pre>
<p>This still does not sum up to 2.8MB since we're missing the transitive dependencies.</p>
<h3>Visualising recursive dependencies</h3>
<p>Let's use a different approach: first recursively find all dependencies. We do this by using <code>ocamlfind</code> to read <code>META</code> files which contain a list of dependent libraries in their <code>requires</code> line. As input we use <code>LIBS</code> from the Makefile snippet above. The code (OCaml script) is <a href="https://gist.github.com/hannesm/bcbe54c5759ed5854f05c8f8eaee4c79">available here</a>. The colour scheme is red for pieces of the OCaml distribution, yellow for input packages, and orange for the dependencies.</p>
<p><a href="/static/img/mirage-console.svg"><img src="/static/img/mirage-console.svg" title="UNIX dependencies of hello world" width="700" /></a></p>
<p>This is the UNIX version only, the Xen version looks similar (but worth mentioning).</p>
<p><a href="/static/img/mirage-console-xen.svg"><img src="/static/img/mirage-console-xen.svg" title="Xen dependencies of hello world" width="700" /></a></p>
<p>You can spot at the right that <code>mirage-bootvar</code> uses <code>re</code>, which provoked me to <a href="https://github.com/mirage/mirage-bootvar-xen/pull/19">open a PR</a>, but Jon Ludlam <a href="https://github.com/mirage/mirage-bootvar-xen/pull/18">already had a nicer PR</a> which is now merged (and a <a href="https://github.com/mirage/mirage-bootvar-xen/pull/20">new release is in preparation</a>).</p>
<h3>Counting bytes</h3>
<p>While a dependency graphs gives a big picture of what the composed libraries of a MirageOS unikernel, we also want to know how many bytes they contribute to the unikernel. The dependency graph only contains the OCaml-level dependencies, but MirageOS has in addition to that a <code>pkg-config</code> universe of the libraries written in C (such as mini-os, openlibm, ...).</p>
<p>We overapproximate the sizes here by assuming that a linker simply concatenates all required object files. This is not true, since the sum of all objects is empirically factor two of the actual size of the unikernel.</p>
<p>I developed a pie chart visualisation, but a friend of mine reminded me that such a chart is pretty useless for comparing slices for the human brain. I spent some more time to develop a treemap visualisation to satisfy the brain. The implemented algorithm is based on <a href="http://www.win.tue.nl/~vanwijk/stm.pdf">squarified treemaps</a>, but does not use implicit mutable state. In addition, the <a href="https://gist.github.com/hannesm/c8a9b2e75bb4f98b5100a838ea125f3b">provided script</a> parses common linker flags (<code>-o -L -l</code>) and collects arguments to be linked in. It can be passed to <code>ocamlopt</code> as the C linker, more instructions at the end of <code>treemap.ml</code> (which should be cleaned up and integrated into the mirage tool, as mentioned earlier).</p>
<p><a href="/static/img/mirage-console-bytes.svg"><img src="/static/img/mirage-console-bytes.svg" title="byte sizes of hello-world (UNIX)" width="700" /></a></p>
<p><a href="/static/img/mirage-console-xen-bytes-full.svg"><img src="/static/img/mirage-console-xen-bytes-full.svg" title="byte sizes of hello-world (Xen)" width="700" /></a></p>
<p>As mentioned above, this is an overapproximation. The <code>libgcc.a</code> is only needed on Xen (see <a href="https://github.com/mirage/mirage/commit/c17f2f60a6309322ba45cecb00a808f62f05cf82#commitcomment-17573123">this comment</a>), I have not yet tracked down why there is a <code>libasmrun.a</code> and a <code>libxenasmrun.a</code>.</p>
<h3>More complex examples</h3>
<p>Besides the hello world, I used the same tools on our <a href="http://ownme.ipredator.se">BTC Piñata</a>.</p>
<p><a href="/static/img/pinata-deps.svg"><img src="/static/img/pinata-deps.svg" title="Piñata dependencies" width="700" /></a></p>
<p><a href="/static/img/pinata-bytes.svg"><img src="/static/img/pinata-bytes.svg" title="Piñata byte sizes" width="700" /></a></p>
<h3>Conclusion</h3>
<p>OCaml does not yet do dead code elimination, but there <a href="https://github.com/ocaml/ocaml/pull/608">is a PR</a> based on the flambda middle-end which does so. I haven't yet investigated numbers using that branch.</p>
<p>Those counting statistics could go into more detail (e.g. using <code>nm</code> to count the sizes of concrete symbols - which opens the possibility to see which symbols are present in the objects, but not in the final binary). Also, collecting the numbers for each module in a library would be great to have. In the end, it would be great to easily spot the source fragments which are responsible for a huge binary size (and getting rid of them).</p>
<p>I'm interested in feedback, either via
<a href="https://twitter.com/h4nnes">twitter</a> or via eMail.</p>
</article></div></div></main></body></html>

@ -1,34 +1,23 @@
---
title: Conex, establish trust in community repositories
author: hannes
tags: package signing, security, overview
abstract: Conex is a library to verify and attest package release integrity and authenticity through the use of cryptographic signatures.
---
Less than two years after the initial proposal, we're happy to present conex
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Conex, establish trust in community repositories</title><meta charset="UTF-8"/><link rel="stylesheet" href="/static/css/style.css"/><link rel="stylesheet" href="/static/css/highlight.css"/><script src="/static/js/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="alternate" href="/atom" title="Conex, establish trust in community repositories" type="application/atom+xml"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/></head><body><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><a class="navbar-brand" href="/Posts">full stack engineer</a></div><div class="collapse navbar-collapse collapse"><ul class="nav navbar-nav navbar-right"><li><a href="/About"><span>About</span></a></li><li><a href="/Posts"><span>Posts</span></a></li></ul></div></div></nav><main><div class="flex-container"><div class="post"><h2>Conex, establish trust in community repositories</h2><span class="author">Written by hannes</span><br/><div class="tags">Classified under: <a href="/tags/package signing" class="tag">package signing</a><a href="/tags/security" class="tag">security</a><a href="/tags/overview" class="tag">overview</a></div><span class="date">Published: 2017-02-16 (last updated: 2021-11-19)</span><article><p>Less than two years after the initial proposal, we're happy to present conex
0.9.2. Pleas note that this is still work in progress, to be deployed with opam
2.0 and the [opam repository](https://github.com/ocaml/opam-repository).
![screenshot](/static/img/conex.png)
[Conex](https://github.com/hannesm/conex) is a library to verify and attest release integrity and
authenticity of a community repository through the use of cryptographic signatures.
Packages are collected in a community repository to provide an index and
2.0 and the <a href="https://github.com/ocaml/opam-repository">opam repository</a>.</p>
<p><img src="/static/img/conex.png" alt="screenshot" /></p>
<p><a href="https://github.com/hannesm/conex">Conex</a> is a library to verify and attest release integrity and
authenticity of a community repository through the use of cryptographic signatures.</p>
<p>Packages are collected in a community repository to provide an index and
allowing cross-references. Authors submit their packages to the repository. which
is curated by a team of janitors. Information
about a package stored in a repository includes: license, author, releases,
their dependencies, build instructions, url, tarball checksum. When someone
publishes a new package, the janitors integrate it into the repository, if it
compiles and passes some validity checks. For example, its name must not be misleading,
nor may it be too general.
Janitors keep an eye on the repository and fix emergent failures. A new
nor may it be too general.</p>
<p>Janitors keep an eye on the repository and fix emergent failures. A new
compiler release, or a release of a package on which other packages depend, might break the compilation of
a package. Janitors usually fix these problems by adding a patch to the build script, or introducing
a version constraint in the repository.
*Conex* ensures that every release of each package has been approved by its author or a quorum of janitors.
a version constraint in the repository.</p>
<p><em>Conex</em> ensures that every release of each package has been approved by its author or a quorum of janitors.
A conex-aware client initially verifies the repository using janitor key fingerprints as anchor.
Afterwards, the on-disk repository is trusted, and every update is verified (as a patch) individually.
This incremental verification is accomplished by ensuring all resources
@ -37,240 +26,204 @@ sufficient approvals. Additionally, monotonicity is preserved by
embedding counters in each resource, and enforcing a counter
increment after modification.
This mechanism avoids rollback attacks, when an
attacker presents you an old version of the repository.
A timestamping service (NYI) will periodically approve a global view of the
attacker presents you an old version of the repository.</p>
<p>A timestamping service (NYI) will periodically approve a global view of the
verified repository, together with a timestamp. This is then used by the client
to prevent mix-and-match attacks, where an attacker mixes some old packages and
some new ones. Also, the client is able to detect freeze attacks, since at
least every day there should be a new signature done by the timestamping service.
The trust is rooted in digital signatures by package authors. The server which
least every day there should be a new signature done by the timestamping service.</p>
<p>The trust is rooted in digital signatures by package authors. The server which
hosts the repository does not need to be trusted. Neither does the host serving
release tarballs.
If a single janitor would be powerful enough to approve a key for any author,
release tarballs.</p>
<p>If a single janitor would be powerful enough to approve a key for any author,
compromising one janitor would be sufficient to enroll any new identities,
modify dependencies, build scripts, etc. In conex, a quorum of janitors (let's
say 3) have to approve such changes. This is different from current workflows,
where a single janitor with access to the repository can merge fixes.
Conex adds metadata, in form of resources, to the repository to ensure integrity and
authenticity. There are different kinds of resources:
- *Authors*, consisting of a unique identifier, public key(s), accounts.
- *Teams*, sharing the same namespace as authors, containing a set of members.
- *Authorisation*, one for each package, describing which identities are authorised for the package.
- *Package index*, for each package, listing all releases.
- *Release*, for each release, listing checksums of all data files.
Modifications to identities and authorisations need to be approved by a quorum
where a single janitor with access to the repository can merge fixes.</p>
<p>Conex adds metadata, in form of resources, to the repository to ensure integrity and
authenticity. There are different kinds of resources:</p>
<ul>
<li><em>Authors</em>, consisting of a unique identifier, public key(s), accounts.
</li>
<li><em>Teams</em>, sharing the same namespace as authors, containing a set of members.
</li>
<li><em>Authorisation</em>, one for each package, describing which identities are authorised for the package.
</li>
<li><em>Package index</em>, for each package, listing all releases.
</li>
<li><em>Release</em>, for each release, listing checksums of all data files.
</li>
</ul>
<p>Modifications to identities and authorisations need to be approved by a quorum
of janitors, package index and release files can be modified either by an authorised
id or by a quorum of janitors.
## Documentation
[API documentation](https://hannesm.github.io/conex/doc/) is
available online, also a [coverage
report](https://hannesm.github.io/conex/coverage/).
We presented an [abstract at OCaml
2016](https://github.com/hannesm/conex-paper/raw/master/paper.pdf) about an
earlier design.
Another article on an [earlier design (from
2015)](http://opam.ocaml.org/blog/Signing-the-opam-repository/) is also
available.
Conex is inspired by [the update
framework](https://theupdateframework.github.io/), especially on their [CCS 2010
paper](https://isis.poly.edu/~jcappos/papers/samuel_tuf_ccs_2010.pdf), and
adapted to the opam repository.
The [TUF
spec](https://github.com/theupdateframework/tuf/blob/develop/docs/tuf-spec.txt)
has a good overview of attacks and threat model, both of which are shared by conex.
## What's missing
- See [issue 7](https://github.com/hannesm/conex/issues/7) for a laundry list
- Timestamping service
- Key revocation and rollover
- Tool to approve a PR (for janitors)
- Camelus like opam-repository check bot
- Integration into release management systems
## Getting started
At the moment, our [opam repository](https://github.com/ocaml/opam-repository)
id or by a quorum of janitors.</p>
<h2>Documentation</h2>
<p><a href="https://hannesm.github.io/conex/doc/">API documentation</a> is
available online, also a <a href="https://hannesm.github.io/conex/coverage/">coverage
report</a>.</p>
<p>We presented an <a href="https://github.com/hannesm/conex-paper/raw/master/paper.pdf">abstract at OCaml
2016</a> about an
earlier design.</p>
<p>Another article on an <a href="http://opam.ocaml.org/blog/Signing-the-opam-repository/">earlier design (from
2015)</a> is also
available.</p>
<p>Conex is inspired by <a href="https://theupdateframework.github.io/">the update
framework</a>, especially on their <a href="https://isis.poly.edu/~jcappos/papers/samuel_tuf_ccs_2010.pdf">CCS 2010
paper</a>, and
adapted to the opam repository.</p>
<p>The <a href="https://github.com/theupdateframework/tuf/blob/develop/docs/tuf-spec.txt">TUF
spec</a>
has a good overview of attacks and threat model, both of which are shared by conex.</p>
<h2>What's missing</h2>
<ul>
<li>See <a href="https://github.com/hannesm/conex/issues/7">issue 7</a> for a laundry list
</li>
<li>Timestamping service
</li>
<li>Key revocation and rollover
</li>
<li>Tool to approve a PR (for janitors)
</li>
<li>Camelus like opam-repository check bot
</li>
<li>Integration into release management systems
</li>
</ul>
<h2>Getting started</h2>
<p>At the moment, our <a href="https://github.com/ocaml/opam-repository">opam repository</a>
does not include any metadata needed for signing. We're in a bootstrap phase:
we need you to generate a keypair, claim your packages, and approve your releases.
We cannot verify the main opam repository yet, but opam2 has support for a
[`repository validation command`](http://opam.ocaml.org/doc/2.0/Manual.html#configfield-repository-validation-command),
builtin, which should then call out to `conex_verify` (there is a `--nostrict`
flag for the impatient). There is also an [example repository](https://github.com/hannesm/testrepo) which uses the opam validation command.
To reduce the manual work, we analysed 7000 PRs of the opam repository within
the last 4.5 years (more details [here](/Posts/Maintainers).
we need you to generate a keypair, claim your packages, and approve your releases.</p>
<p>We cannot verify the main opam repository yet, but opam2 has support for a
<a href="http://opam.ocaml.org/doc/2.0/Manual.html#configfield-repository-validation-command"><code>repository validation command</code></a>,
builtin, which should then call out to <code>conex_verify</code> (there is a <code>--nostrict</code>
flag for the impatient). There is also an <a href="https://github.com/hannesm/testrepo">example repository</a> which uses the opam validation command.</p>
<p>To reduce the manual work, we analysed 7000 PRs of the opam repository within
the last 4.5 years (more details <a href="/Posts/Maintainers">here</a>.
This resulted in an educated guess who are the people
modifying each package, which we use as a basis whom to authorise for
which packages. Please check with `conex_author status` below whether your team
membership and authorised packages were inferred correctly.
Each individual author - you - need to generate their private key, submit
which packages. Please check with <code>conex_author status</code> below whether your team
membership and authorised packages were inferred correctly.</p>
<p>Each individual author - you - need to generate their private key, submit
their public key and starts approving releases (and old ones after careful
checking that the build script, patches, and tarball checksum are valid).
Each resource can be approved in multiple versions at the same time.
### Installation
TODO: remove clone once [PR 8494](https://github.com/ocaml/opam-repository/pull/8494) is merged.
```bash
$ git clone -b auth https://github.com/hannesm/opam-repository.git repo
Each resource can be approved in multiple versions at the same time.</p>
<h3>Installation</h3>
<p>TODO: remove clone once <a href="https://github.com/ocaml/opam-repository/pull/8494">PR 8494</a> is merged.</p>
<pre><code class="language-bash">$ git clone -b auth https://github.com/hannesm/opam-repository.git repo
$ opam install conex
$ cd repo
```
This will install conex, namely command line utilities, `conex_author` and
`conex_verify_nocrypto`/`conex_verify_openssl`. All files read and written by conex are in the usual
</code></pre>
<p>This will install conex, namely command line utilities, <code>conex_author</code> and
<code>conex_verify_nocrypto</code>/<code>conex_verify_openssl</code>. All files read and written by conex are in the usual
opam file format. This means can always manually modify them (but be careful,
modifications need to increment counters, add checksums, and be signed). Conex
does not deal with git, you have to manually `git add` files and open pull
requests.
### Author enrollment
For the opam repository, we will use GitHub ids as conex ids. Thus, your conex
id and your GitHub id should match up.
```bash
repo$ conex_author init --repo ~/repo --id hannesm
does not deal with git, you have to manually <code>git add</code> files and open pull
requests.</p>
<h3>Author enrollment</h3>
<p>For the opam repository, we will use GitHub ids as conex ids. Thus, your conex
id and your GitHub id should match up.</p>
<pre><code class="language-bash">repo$ conex_author init --repo ~/repo --id hannesm
Created keypair hannesm. Join teams, claim your packages, sign your approved resources and open a PR :)
```
This attempts to parse `~/repo/id/hannesm`, errors if it is a team or an author
</code></pre>
<p>This attempts to parse <code>~/repo/id/hannesm</code>, errors if it is a team or an author
with a publickey. Otherwise it generates a keypair, writes the private part as
`home.hannes.repo.hannesm.private` (the absolute path separated by dots,
followed by your id, and `private` - if you move your repository, rename your
private key) into `~/.conex/`, the checksums of the public part and your
accounts into `~/repo/id/hannesm`. See `conex_author help init` for more
options (esp. additional verbosity `-v` can be helpful).
```bash
repo$ git status -s
<code>home.hannes.repo.hannesm.private</code> (the absolute path separated by dots,
followed by your id, and <code>private</code> - if you move your repository, rename your
private key) into <code>~/.conex/</code>, the checksums of the public part and your
accounts into <code>~/repo/id/hannesm</code>. See <code>conex_author help init</code> for more
options (esp. additional verbosity <code>-v</code> can be helpful).</p>
<pre><code class="language-bash">repo$ git status -s
M id/hannesm
repo$ git diff //abbreviated output
- ["counter" 0x0]
+ ["counter" 0x1]
- [&quot;counter&quot; 0x0]
+ [&quot;counter&quot; 0x1]
- ["resources" []]
- [&quot;resources&quot; []]
+ [
+ "resources"
+ &quot;resources&quot;
+ [
+ [
+ ["typ" "key"]
+ ["name" "hannesm"]
+ ["index" 0x1]
+ ["digest" ["SHA256" "ht9ztjjDwWwD/id6LSVi7nKqVyCHQuQu9ORpr8Zo2aY="]]
+ [&quot;typ&quot; &quot;key&quot;]
+ [&quot;name&quot; &quot;hannesm&quot;]
+ [&quot;index&quot; 0x1]
+ [&quot;digest&quot; [&quot;SHA256&quot; &quot;ht9ztjjDwWwD/id6LSVi7nKqVyCHQuQu9ORpr8Zo2aY=&quot;]]
+ ]
+ [
+ ["typ" "account"]
+ ["name" "hannesm"]
+ ["index" 0x2]
+ ["digest" ["SHA256" "aCsktJ5M9PI6T+m1NIQtuIFYILFkqoHKwBxwvuzpuzg="]]
+ [&quot;typ&quot; &quot;account&quot;]
+ [&quot;name&quot; &quot;hannesm&quot;]
+ [&quot;index&quot; 0x2]
+ [&quot;digest&quot; [&quot;SHA256&quot; &quot;aCsktJ5M9PI6T+m1NIQtuIFYILFkqoHKwBxwvuzpuzg=&quot;]]
+ ]
+
+keys: [
+ [
+ [
+ "RSA"
+ """
+ &quot;RSA&quot;
+ &quot;&quot;&quot;
+-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyUhArwt4XcxLanARyH9S
...
+9KQdg6QnLsQh/j74QKLOZacCAwEAAQ==
+-----END PUBLIC KEY-----"""
+-----END PUBLIC KEY-----&quot;&quot;&quot;
+ 0x58A3419F
+ ]
+ [
+ 0x58A79A1D
+ "RSA-PSS-SHA256"
+ "HqqicsDx4hG9pFM5E7"
+ &quot;RSA-PSS-SHA256&quot;
+ &quot;HqqicsDx4hG9pFM5E7&quot;
+ ]
+ ]
+]
```
### Status
If you have a single identity and contribute to a single signed opam repository,
you don't need to specify `--id` or `--repo` from now on.
The `status` subcommand presents an author-specific view on the repository. It
</code></pre>
<h3>Status</h3>
<p>If you have a single identity and contribute to a single signed opam repository,
you don't need to specify <code>--id</code> or <code>--repo</code> from now on.</p>
<p>The <code>status</code> subcommand presents an author-specific view on the repository. It
lists the own public keys, team membership, queued resources, and authorised
packages.
The opam repository is in a transitionary state, we explicitly pass `--quorum
0`, which means that every checksum is valid (approved by a quorum of 0
janitors).
```bash
repo$ conex_author status --quorum 0 arp
packages.</p>
<p>The opam repository is in a transitionary state, we explicitly pass <code>--quorum 0</code>, which means that every checksum is valid (approved by a quorum of 0
janitors).</p>
<pre><code class="language-bash">repo$ conex_author status --quorum 0 arp
author hannesm #1 (created 0) verified 3 resources, 0 queued
4096 bit RSA key created 1487094175 approved, SHA256: ht9ztjjDwWwD/id6LSVi7nKqVyCHQuQu9ORpr8Zo2aY=
account GitHub hannesm approved
account email hannes@mehnert.org approved
package arp authorisation approved
conex_author: [ERROR] package index arp was not found in repository
```
This shows your key material and accounts, team membership and packages you are
</code></pre>
<p>This shows your key material and accounts, team membership and packages you are
authorised to modify (inferred as described
[here](/Posts/Maintainers).
The `--noteam` argument limits the package list to only these you are personally
authorised for. The `--id` argument presents you with a view of another author,
<a href="/Posts/Maintainers">here</a>.</p>
<p>The <code>--noteam</code> argument limits the package list to only these you are personally
authorised for. The <code>--id</code> argument presents you with a view of another author,
or from a team perspective. The positional argument is a prefix matching on
package names (leave empty for all).
### Resource approval
Each resource needs to be approved individually. Each author has a local queue
for to-be-signed resources, which is extended with `authorisation`, `init`,
`key`, `release`, and `team` (all have a `--dry-run` flag). The queue can be
dropped using `conex_author reset`. Below shown is `conex_author sign`, which
package names (leave empty for all).</p>
<h3>Resource approval</h3>
<p>Each resource needs to be approved individually. Each author has a local queue
for to-be-signed resources, which is extended with <code>authorisation</code>, <code>init</code>,
<code>key</code>, <code>release</code>, and <code>team</code> (all have a <code>--dry-run</code> flag). The queue can be
dropped using <code>conex_author reset</code>. Below shown is <code>conex_author sign</code>, which
let's you interactively approve queued resources and cryptopgraphically signs
your approved resources afterwards.
The output of `conex_author status` listed an authorisation for `conf-gsl`,
which I don't feel responsible for. Let's drop my privileges:
```bash
repo$ conex_author authorisation conf-gsl --remove -m hannesm
your approved resources afterwards.</p>
<p>The output of <code>conex_author status</code> listed an authorisation for <code>conf-gsl</code>,
which I don't feel responsible for. Let's drop my privileges:</p>
<pre><code class="language-bash">repo$ conex_author authorisation conf-gsl --remove -m hannesm
modified authorisation and added resource to your queue.
```
I checked my arp release careful (checksums of tarballs are correct, opam files
</code></pre>
<p>I checked my arp release careful (checksums of tarballs are correct, opam files
do not execute arbitrary shell code, etc.), and approve this package and its
single release:
```bash
repo$ conex_author release arp
single release:</p>
<pre><code class="language-bash">repo$ conex_author release arp
conex_author.native: [WARNING] package index arp was not found in repository
conex_author.native: [WARNING] release arp.0.1.1 was not found in repository
wrote release and added resources to your queue.
```
Once finished with joining and leaving teams (using the `team` subcommand),
claiming packages (using the `authorisation` subcommand), and approve releases
(using the `release` subcommand), you have to cryprographically sign your queued
resource modifications:
```bash
repo$ conex_author sign
</code></pre>
<p>Once finished with joining and leaving teams (using the <code>team</code> subcommand),
claiming packages (using the <code>authorisation</code> subcommand), and approve releases
(using the <code>release</code> subcommand), you have to cryprographically sign your queued
resource modifications:</p>
<pre><code class="language-bash">repo$ conex_author sign
release arp.0.1.1 #1 (created 1487269425)
[descr: SHA256: aCsNvcj3cBKO0GESWG4r3AzoUEnI0pHGSyEDYNPouoE=;
opam: SHA256: nqy6lD1UP+kXj3+oPXLt2VMUIENEuHMVlVaG2V4z3p0=;
@ -289,35 +242,26 @@ account GitHub hannesm approved
account email hannes@mehnert.org approved
package arp authorisation approved package index approved
release arp.0.1.1: approved
```
If you now modify anything in `packages/arp` (add subdirectories, modify opam,
etc.), this will not be automatically approved (see below for how to do this).
You manually need to `git add` some created files.
```bash
repo$ git status -s
</code></pre>
<p>If you now modify anything in <code>packages/arp</code> (add subdirectories, modify opam,
etc.), this will not be automatically approved (see below for how to do this).</p>
<p>You manually need to <code>git add</code> some created files.</p>
<pre><code class="language-bash">repo$ git status -s
M id/hannesm
M packages/conf-gsl/authorisation
?? packages/arp/arp.0.1.1/release
?? packages/arp/package
repo$ git add packages/arp/arp.0.1.1/release packages/arp/package
repo$ git commit -m "hannesm key enrollment and some fixes" id packages
```
Now push this to your fork, and open a PR on opam-repository!
### Editing a package
If you need to modify a released package, you modify the opam file (as before,
repo$ git commit -m &quot;hannesm key enrollment and some fixes&quot; id packages
</code></pre>
<p>Now push this to your fork, and open a PR on opam-repository!</p>
<h3>Editing a package</h3>
<p>If you need to modify a released package, you modify the opam file (as before,
e.g. introducing a conflict with a dependency), and then approve the
modifications. After your local modifications, `conex_author status` will
complain:
```bash
repo$ conex_author status arp --quorum 0
modifications. After your local modifications, <code>conex_author status</code> will
complain:</p>
<pre><code class="language-bash">repo$ conex_author status arp --quorum 0
package arp authorisation approved package index approved
release arp.0.1.1: checksums for arp.0.1.1 differ, missing on disk: empty, missing in checksums file: empty, checksums differ: [have opam: SHA256: QSGUU9HdPOrwoRs6XJka4cZpd8h+8NN1Auu5IMN8ew4= want opam: SHA256: nqy6lD1UP+kXj3+oPXLt2VMUIENEuHMVlVaG2V4z3p0=]
@ -331,62 +275,43 @@ opam: SHA256: QSGUU9HdPOrwoRs6XJka4cZpd8h+8NN1Auu5IMN8ew4=;
url: SHA256: FaUPievda6cEMjNkWdi0kGVK7t6EpWGfQ4q2NTSTcy0=]
approved (yes/No)? y
wrote hannesm to disk
```
The `release` subcommand recomputed the checksums, incremented the counter, and
added it to your queue. The `sign` command signed the approved resource.
```bash
repo$ git status -s
</code></pre>
<p>The <code>release</code> subcommand recomputed the checksums, incremented the counter, and
added it to your queue. The <code>sign</code> command signed the approved resource.</p>
<pre><code class="language-bash">repo$ git status -s
M id/hannesm
M packages/arp/arp.0.1.1/opam
M packages/arp/arp.0.1.1/package
repo$ git commit -m "fixed broken arp package" id packages
```
### Janitor tools
Janitors need to approve teams, keys, accounts, and authorisations.
To approve resources which are already in the repository on disk,
the `key` subcommand queues approval of keys and accounts of the provided author:
```bash
repo$ conex_author key avsm
repo$ git commit -m &quot;fixed broken arp package&quot; id packages
</code></pre>
<h3>Janitor tools</h3>
<p>Janitors need to approve teams, keys, accounts, and authorisations.</p>
<p>To approve resources which are already in the repository on disk,
the <code>key</code> subcommand queues approval of keys and accounts of the provided author:</p>
<pre><code class="language-bash">repo$ conex_author key avsm
added keys and accounts to your resource list.
```
The `authorisation` subcommand, and `team` subcommand behave similarly for
authorisations and teams.
Bulk operations are supported as well:
```bash
conex_author authorisation all
```
This will approve all authorisations of the repository which are not yet
approved by you. Similar for the `key` and `team` subcommands, which also
accept `all`.
Don't forget to `conex_author sign` afterwards (or `yes | conex_author sign`).
### Verification
The two command line utlities, `conex_verify_openssl` and
`conex_verify_nocrypto` contain the same logic and same command line arguments.
For bootstrapping purposes (`nocrypto` is an opam package with dependencies),
`conex_verify_openssl` relies on the openssl command line tool (version 1.0.0
and above) for digest computation and verification of the RSA-PSS signature.
The goal is to use the opam2 provided hooks, but before we have signatures we
cannot enable them.
See the [example repository](https://github.com/hannesm/testrepo) for initial
verification experiments, and opam2 integration.
I'm interested in feedback, please open an issue on the [conex
repository](https://github.com/hannesm/conex). This article itself is stored as
Markdown [in a different repository](https://git.robur.io/hannes/hannes.robur.coop).
</code></pre>
<p>The <code>authorisation</code> subcommand, and <code>team</code> subcommand behave similarly for
authorisations and teams.</p>
<p>Bulk operations are supported as well:</p>
<pre><code class="language-bash">conex_author authorisation all
</code></pre>
<p>This will approve all authorisations of the repository which are not yet
approved by you. Similar for the <code>key</code> and <code>team</code> subcommands, which also
accept <code>all</code>.</p>
<p>Don't forget to <code>conex_author sign</code> afterwards (or <code>yes | conex_author sign</code>).</p>
<h3>Verification</h3>
<p>The two command line utlities, <code>conex_verify_openssl</code> and
<code>conex_verify_nocrypto</code> contain the same logic and same command line arguments.</p>
<p>For bootstrapping purposes (<code>nocrypto</code> is an opam package with dependencies),
<code>conex_verify_openssl</code> relies on the openssl command line tool (version 1.0.0
and above) for digest computation and verification of the RSA-PSS signature.</p>
<p>The goal is to use the opam2 provided hooks, but before we have signatures we
cannot enable them.</p>
<p>See the <a href="https://github.com/hannesm/testrepo">example repository</a> for initial
verification experiments, and opam2 integration.</p>
<p>I'm interested in feedback, please open an issue on the <a href="https://github.com/hannesm/conex">conex
repository</a>. This article itself is stored as
Markdown <a href="https://git.robur.io/hannes/hannes.robur.coop">in a different repository</a>.</p>
</article></div></div></main></body></html>

@ -1,40 +1,31 @@
---
title: My 2018 contains robur and starts with re-engineering DNS
author: hannes
tags: mirageos, protocol
abstract: New year brings new possibilities and a new environment. I've been working on the most Widely deployed key-value store, the domain name system. Primary and secondary name services are available, including dynamic updates, notify, and tsig authentication.
---
## 2018
At the end of 2017, I resigned from my PostDoc position at University of
Cambridge (in the [rems](https://www.cl.cam.ac.uk/~pes20/rems/) project). Early
December 2017 I organised the [4th MirageOS hack
retreat](https://mirage.io/blog/2017-winter-hackathon-roundup), with which I'm
very satisfied. In March 2018 the [5th retreat](http://retreat.mirage.io) will
happen (please sign up!).
In 2018 I moved to Berlin and started to work for the (non-profit) [Center for
the cultivation of technology](https://techcultivation.org) with our
[robur.io](http://robur.io) project "At robur, we build performant bespoke
minimal operating systems for high-assurance services". robur is only possible
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>My 2018 contains robur and starts with re-engineering DNS</title><meta charset="UTF-8"/><link rel="stylesheet" href="/static/css/style.css"/><link rel="stylesheet" href="/static/css/highlight.css"/><script src="/static/js/highlight.pack.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="alternate" href="/atom" title="My 2018 contains robur and starts with re-engineering DNS" type="application/atom+xml"/><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/></head><body><nav class="navbar navbar-default navbar-fixed-top"><div class="container"><div class="navbar-header"><a class="navbar-brand" href="/Posts">full stack engineer</a></div><div class="collapse navbar-collapse collapse"><ul class="nav navbar-nav navbar-right"><li><a href="/About"><span>About</span></a></li><li><a href="/Posts"><span>Posts</span></a></li></ul></div></div></nav><main><div class="flex-container"><div class="post"><h2>My 2018 contains robur and starts with re-engineering DNS</h2><span class="author">Written by hannes</span><br/><div class="tags">Classified under: <a href="/tags/mirageos" class="tag">mirageos</a><a href="/tags/protocol" class="tag">protocol</a></div><span class="date">Published: 2018-01-11 (last updated: 2021-11-19)</span><article><h2>2018</h2>
<p>At the end of 2017, I resigned from my PostDoc position at University of
Cambridge (in the <a href="https://www.cl.cam.ac.uk/~pes20/rems/">rems</a> project). Early
December 2017 I organised the <a href="https://mirage.io/blog/2017-winter-hackathon-roundup">4th MirageOS hack
retreat</a>, with which I'm
very satisfied. In March 2018 the <a href="http://retreat.mirage.io">5th retreat</a> will
happen (please sign up!).</p>
<p>In 2018 I moved to Berlin and started to work for the (non-profit) <a href="https://techcultivation.org">Center for
the cultivation of technology</a> with our
<a href="http://robur.io">robur.io</a> project &quot;At robur, we build performant bespoke
minimal operating systems for high-assurance services&quot;. robur is only possible
by generous donations in autumn 2017, enthusiastic collaborateurs, supportive
friends, and a motivated community, thanks to all. We will receive funding from
the [prototypefund](https://prototypefund.de/project/robur-io/) to work on a
[CalDAV server](https://robur.io/Our%20Work/Projects#CalDAV-Server) implementation in OCaml
the <a href="https://prototypefund.de/project/robur-io/">prototypefund</a> to work on a
<a href="https://robur.io/Our%20Work/Projects#CalDAV-Server">CalDAV server</a> implementation in OCaml
targeting MirageOS. We're still looking for donations and further funding,
please get in touch. Apart from CalDAV, I want to start the year by finishing
several projects which I discovered on my hard drive. This includes DNS, [opam
signing](/Posts/Conex), TCP, ... . My personal goal for 2018 is to develop a