A Quick Guide to Quick Changes in MirageOS
MirageOS is a collection of libraries and a system for assembling them into unikernels. What happens if you want to make changes to those libraries and test them with a new unikernel?
Say, for example, I have a static website (like this blog) that I build in MirageOS. I want to make some changes to the TCP implementation against which the blog is built. In order to do that, I need to do all the following:
- figure out which module to change
- figure out which package provides that module
- get the source for that package and instruct the package manager to use it instead of the release
- make changes
- reinstall the package with your changes
- rebuild the unikernel completely
- see whether changes had the desired effect
Here’s a quick primer on how.
What am I changing?
If I want to change the TCP implementation (say, to try to fix this bug), I’m going to need to make an alteration to the code that’s provided to my unikernel fulfilling the TCP module type signature. I want to run my site as a unikernel on a Xen hypervisor, so I’ll build it with mirage configure --xen
, which decides which packages are most appropriate for providing the right functionality in the Xen context. Since I’m building with --xen
, the only option available for networking is the direct
stack (vs the socket
stack available when building with --unix
).
What package provides that?
There are a few ways I can discover which package is providing the TCP implementation. I can look directly into the mirage front-end tool and find the relevant section for the TCP module, which currently looks like this:
let tcp_direct_conf () = object
inherit base_configurable
method ty =
(ip: 'a ip typ) @-> time @-> clock @-> random @-> (tcp: 'a tcp typ)
method name = "tcp"
method module_name = "Tcp.Flow.Make"
method packages = Key.pure [ "tcpip" ]
method libraries = Key.pure [ "tcpip.tcp" ]
method connect _ modname = function
| [ip; _time; _clock; _random] -> Printf.sprintf "%s.connect %s" modname ip
| _ -> failwith "The tcp connect should receive exactly four arguments."
end
From method packages = Key.pure ["tcpip"]
, we can infer that the package providing this is tcpip
. Now that we know what package we need to look up, we can get some more information, including where to clone it from and where to report issues. Here’s an abbreviated look:
$ opam info tcpip
package: tcpip
version: 2.6.1
repository: default
upstream-url: https://github.com/mirage/mirage-tcpip/archive/v2.6.1.tar.gz
upstream-kind: http
upstream-checksum: 128a4250716424d32c6e84f35502925a
homepage: https://github.com/mirage/mirage-tcpip
bug-reports: https://github.com/mirage/mirage-tcpip/issues
dev-repo: https://github.com/mirage/mirage-tcpip.git
So now we know how to clone this package:
$ git clone https://github.com/mirage/mirage-tcpip
and we can make a new branch and commit some changes to it.
Pin that package in opam
Once we have a path or a git branch where we’ll make changes, we can instruct opam
to refer to that version of the package rather than the released one we were working with before. I generally use path-pinning for ease of hacking without making a lot of intermediate git commits, but others might prefer to point opam
at a branch.
$ opam pin add tcpip ~/mirage-tcpip
The initial pin will cause opam
to reinstall the tcpip
package. Now if we rebuild our unikernel, we’ll use the pinned version:
$ cd my_rad_unikernel
$ make clean
$ mirage configure --xen
$ make
We can verify that the unikernel has the behavior we desire to change after the pin (just in case someone’s already fixed the problem in the primary branch of the repository, but hasn’t yet made a release).
Make your changes
We’ll elide the details here, but it probably looks something like this:
$ cd mirage-tcpip
$ git checkout -b tcp_opt_parse
$ vi tcp/options.ml
$ git commit -m "got all the problems away" tcp/options.ml
Reinstall the package via opam
Since we made a change, we need to reinstall the package so that when we rebuild the unikernel, the changes will be reflected in the referenced installed package. We can do this with opam reinstall
:
$ opam reinstall tcpip
If we’ve broken the build with our changes, the package may fail to reinstall. That’s OK; we can fix it and just try opam reinstall tcpip
again, and confirm that we want to install this package.
Rebuild the unikernel completely
We’ll redo the same steps we did when we initially pinned the package: clean the old state, reconfigure the unikernel, and rebuild it.
$ cd my_rad_unikernel
$ make clean
$ mirage configure --xen
$ make
See whether the changes worked
If we have a simple test script for our unikernel (or our unikernel is a test script, like this ARP-testing unikernel), we can automatically confirm whether our changes fixed the problem. (We may even be able to see whether our changes broke anything else!)
Commit upstream!
Finally, we might want to share our rad changes with others! MirageOS has documentation on making contributions and a list of ideas for contributions if you’d like some inspiration.