(* (c) 2017, 2018 Hannes Mehnert, all rights reserved *) open Vmm_core open Vmm_commands open Rresult open Astring let oid = Asn.OID.(base 1 3 <| 6 <| 1 <| 4 <| 1 <| 49836 <| 42) open Rresult.R.Infix let guard p err = if p then Ok () else Error err let decode_strict codec cs = match Asn.decode codec cs with | Ok (a, cs) -> guard (Cstruct.len cs = 0) (`Msg "trailing bytes") >>= fun () -> Ok a | Error (`Parse msg) -> Error (`Msg msg) let projections_of asn = let c = Asn.codec Asn.der asn in (decode_strict c, Asn.encode c) let ipv4 = let f cs = Ipaddr.V4.of_octets_exn (Cstruct.to_string cs) and g ip = Cstruct.of_string (Ipaddr.V4.to_octets ip) in Asn.S.map f g Asn.S.octet_string let policy = let f (cpuids, vms, memory, block, bridges) = let bridges = String.Set.of_list bridges and cpuids = IS.of_list cpuids in Policy.{ vms ; cpuids ; memory ; block ; bridges } and g policy = (IS.elements policy.Policy.cpuids, policy.Policy.vms, policy.Policy.memory, policy.Policy.block, String.Set.elements policy.Policy.bridges) in Asn.S.map f g @@ Asn.S.(sequence5 (required ~label:"cpuids" Asn.S.(sequence_of int)) (required ~label:"vms" int) (required ~label:"memory" int) (optional ~label:"block" int) (required ~label:"bridges" Asn.S.(sequence_of utf8_string))) let console_cmd = let f = function | `C1 () -> `Console_add | `C2 `C1 ts -> `Console_subscribe (`Since ts) | `C2 `C2 c -> `Console_subscribe (`Count c) and g = function | `Console_add -> `C1 () | `Console_subscribe `Since ts -> `C2 (`C1 ts) | `Console_subscribe `Count c -> `C2 (`C2 c) in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 null) (explicit 1 (choice2 (explicit 0 utc_time) (explicit 1 int)))) (* TODO is this good? *) let int64 = let f cs = Cstruct.BE.get_uint64 cs 0 and g data = let buf = Cstruct.create 8 in Cstruct.BE.set_uint64 buf 0 data ; buf in Asn.S.map f g Asn.S.octet_string let timeval = Asn.S.(sequence2 (required ~label:"seconds" int64) (required ~label:"microseconds" int)) let ru = let open Stats in let f (utime, (stime, (maxrss, (ixrss, (idrss, (isrss, (minflt, (majflt, (nswap, (inblock, (outblock, (msgsnd, (msgrcv, (nsignals, (nvcsw, nivcsw))))))))))))))) = { utime ; stime ; maxrss ; ixrss ; idrss ; isrss ; minflt ; majflt ; nswap ; inblock ; outblock ; msgsnd ; msgrcv ; nsignals ; nvcsw ; nivcsw } and g ru = (ru.utime, (ru.stime, (ru.maxrss, (ru.ixrss, (ru.idrss, (ru.isrss, (ru.minflt, (ru.majflt, (ru.nswap, (ru.inblock, (ru.outblock, (ru.msgsnd, (ru.msgrcv, (ru.nsignals, (ru.nvcsw, ru.nivcsw))))))))))))))) in Asn.S.map f g @@ Asn.S.(sequence @@ (required ~label:"utime" timeval) @ (required ~label:"stime" timeval) @ (required ~label:"maxrss" int64) @ (required ~label:"ixrss" int64) @ (required ~label:"idrss" int64) @ (required ~label:"isrss" int64) @ (required ~label:"minflt" int64) @ (required ~label:"majflt" int64) @ (required ~label:"nswap" int64) @ (required ~label:"inblock" int64) @ (required ~label:"outblock" int64) @ (required ~label:"msgsnd" int64) @ (required ~label:"msgrcv" int64) @ (required ~label:"nsignals" int64) @ (required ~label:"nvcsw" int64) -@ (required ~label:"nivcsw" int64)) (* although this changed (+runtime + cow + start) from V3 to V4, since it's not persistent, no need to care about it *) let kinfo_mem = let open Stats in let f (vsize, (rss, (tsize, (dsize, (ssize, (runtime, (cow, start))))))) = { vsize ; rss ; tsize ; dsize ; ssize ; runtime ; cow ; start } and g t = (t.vsize, (t.rss, (t.tsize, (t.dsize, (t.ssize, (t.runtime, (t.cow, t.start))))))) in Asn.S.map f g @@ Asn.S.(sequence @@ (required ~label:"bsize" int64) @ (required ~label:"rss" int64) @ (required ~label:"tsize" int64) @ (required ~label:"dsize" int64) @ (required ~label:"ssize" int64) @ (required ~label:"runtime" int64) @ (required ~label:"cow" int) -@ (required ~label:"start" timeval)) (* TODO is this good? *) let int32 = let f i = Int32.of_int i and g i = Int32.to_int i in Asn.S.map f g Asn.S.int let ifdata = let open Stats in let f (bridge, (flags, (send_length, (max_send_length, (send_drops, (mtu, (baudrate, (input_packets, (input_errors, (output_packets, (output_errors, (collisions, (input_bytes, (output_bytes, (input_mcast, (output_mcast, (input_dropped, output_dropped))))))))))))))))) = { bridge ; flags; send_length; max_send_length; send_drops; mtu; baudrate; input_packets; input_errors; output_packets; output_errors; collisions; input_bytes; output_bytes; input_mcast; output_mcast; input_dropped; output_dropped } and g i = (i.bridge, (i.flags, (i.send_length, (i.max_send_length, (i.send_drops, (i.mtu, (i.baudrate, (i.input_packets, (i.input_errors, (i.output_packets, (i.output_errors, (i.collisions, (i.input_bytes, (i.output_bytes, (i.input_mcast, (i.output_mcast, (i.input_dropped, i.output_dropped))))))))))))))))) in Asn.S.map f g @@ Asn.S.(sequence @@ (required ~label:"bridge" utf8_string) @ (required ~label:"flags" int32) @ (required ~label:"send_length" int32) @ (required ~label:"max_send_length" int32) @ (required ~label:"send_drops" int32) @ (required ~label:"mtu" int32) @ (required ~label:"baudrate" int64) @ (required ~label:"input_packets" int64) @ (required ~label:"input_errors" int64) @ (required ~label:"output_packets" int64) @ (required ~label:"output_errors" int64) @ (required ~label:"collisions" int64) @ (required ~label:"input_bytes" int64) @ (required ~label:"output_bytes" int64) @ (required ~label:"input_mcast" int64) @ (required ~label:"output_mcast" int64) @ (required ~label:"input_dropped" int64) -@ (required ~label:"output_dropped" int64)) let stats_cmd = let f = function | `C1 (name, pid, taps) -> `Stats_add (name, pid, taps) | `C2 () -> `Stats_remove | `C3 () -> `Stats_subscribe and g = function | `Stats_add (name, pid, taps) -> `C1 (name, pid, taps) | `Stats_remove -> `C2 () | `Stats_subscribe -> `C3 () in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 (sequence3 (required ~label:"vmmdev" utf8_string) (required ~label:"pid" int) (required ~label:"network" (sequence_of (sequence2 (required ~label:"bridge" utf8_string) (required ~label:"tap" utf8_string)))))) (explicit 1 null) (explicit 2 null)) let of_name, to_name = Name.to_list, fun list -> match Name.of_list list with | Error (`Msg msg) -> Asn.S.error (`Parse msg) | Ok name -> name let log_event = (* this is stored on disk persistently -- be aware when changing the grammar below to only ever extend it! *) let f = function | `C1 `C1 () -> `Startup | `C1 `C2 (name, ip, port) -> `Login (to_name name, ip, port) | `C1 `C3 (name, ip, port) -> `Logout (to_name name, ip, port) | `C1 `C4 (name, pid, taps, block) -> let name = to_name name in let blocks = match block with | None -> [] | Some block -> [ block, Name.block_name name block ] and taps = List.map (fun tap -> tap, tap) taps in `Unikernel_start (name, pid, taps, blocks) | `C2 `C1 (name, pid, taps, blocks) -> let blocks = List.map (fun (name, dev) -> name, match Name.of_string dev with | Error `Msg msg -> Asn.S.error (`Parse msg) | Ok id -> id) blocks in `Unikernel_start (to_name name, pid, taps, blocks) | `C1 `C5 (name, pid, status) -> let status' = match status with | `C1 n -> `Exit n | `C2 n -> `Signal n | `C3 n -> `Stop n in `Unikernel_stop (to_name name, pid, status') | `C1 `C6 () -> `Hup | `C2 `C2 () -> assert false (* placeholder *) and g = function | `Startup -> `C1 (`C1 ()) | `Login (name, ip, port) -> `C1 (`C2 (of_name name, ip, port)) | `Logout (name, ip, port) -> `C1 (`C3 (of_name name, ip, port)) | `Unikernel_start (name, pid, taps, blocks) -> let blocks = List.map (fun (name, dev) -> name, Name.to_string dev) blocks in `C2 (`C1 (of_name name, pid, taps, blocks)) | `Unikernel_stop (name, pid, status) -> let status' = match status with | `Exit n -> `C1 n | `Signal n -> `C2 n | `Stop n -> `C3 n in `C1 (`C5 (of_name name, pid, status')) | `Hup -> `C1 (`C6 ()) in let endp = Asn.S.(sequence3 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"ip" ipv4) (required ~label:"port" int)) in Asn.S.map f g @@ Asn.S.(choice2 (choice6 (explicit 0 null) (explicit 1 endp) (explicit 2 endp) (* the old V3 unikernel start *) (explicit 3 (sequence4 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"pid" int) (required ~label:"taps" (sequence_of utf8_string)) (optional ~label:"block" utf8_string))) (explicit 4 (sequence3 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"pid" int) (required ~label:"status" (choice3 (explicit 0 int) (explicit 1 int) (explicit 2 int))))) (explicit 5 null)) (choice2 (* the new V4 unikernel start*) (explicit 6 (sequence4 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"pid" int) (required ~label:"taps" (sequence_of (sequence2 (required ~label:"bridge" utf8_string) (required ~label:"tap" utf8_string)))) (required ~label:"blocks" (sequence_of (sequence2 (required ~label:"name" utf8_string) (required ~label:"device" utf8_string)))))) (explicit 7 null (* placeholder *) ))) let log_cmd = let f = function | `C1 since -> `Log_subscribe (`Since since) | `C2 n -> `Log_subscribe (`Count n) and g = function | `Log_subscribe `Since since -> `C1 since | `Log_subscribe `Count n -> `C2 n in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 utc_time) (explicit 1 int)) let typ = let f = function | `C1 () -> `Solo5 | `C2 () -> assert false and g = function | `Solo5 -> `C1 () in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 null) (explicit 1 null)) let fail_behaviour = let f = function | `C1 () -> `Quit | `C2 xs -> let exit_codes = match xs with | [] -> None | xs -> Some (IS.of_list xs) in `Restart exit_codes and g = function | `Quit -> `C1 () | `Restart xs -> let exit_codes = match xs with | None -> [] | Some i -> IS.elements i in `C2 exit_codes in Asn.S.map f g @@ Asn.S.(choice2 (explicit 0 null) (explicit 1 (set_of int))) (* this is part of the state file! *) let v0_unikernel_config = let image = let f = function | `C1 x -> `Hvt_amd64, x | `C2 x -> `Hvt_arm64, x | `C3 x -> `Hvt_amd64_compressed, x and g = function | `Hvt_amd64, x -> `C1 x | `Hvt_arm64, x -> `C2 x | `Hvt_amd64_compressed, x -> `C3 x in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 octet_string) (explicit 1 octet_string) (explicit 2 octet_string)) in let open Unikernel in let f (cpuid, memory, block_device, network_interfaces, image, argv) = let bridges = match network_interfaces with None -> [] | Some xs -> List.map (fun n -> n, None) xs and block_devices = match block_device with None -> [] | Some b -> [ b ] in let typ = `Solo5 and compressed = match fst image with `Hvt_amd64_compressed -> true | _ -> false and image = snd image and fail_behaviour = `Quit (* TODO maybe set to restart by default :) *) in { typ ; compressed ; image ; fail_behaviour ; cpuid ; memory ; block_devices ; bridges ; argv } and g _vm = failwith "cannot encode v0 unikernel configs" in Asn.S.map f g @@ Asn.S.(sequence6 (required ~label:"cpu" int) (required ~label:"memory" int) (optional ~label:"block" utf8_string) (optional ~label:"network_interfaces" (sequence_of utf8_string)) (required ~label:"image" image) (optional ~label:"arguments" (sequence_of utf8_string))) (* this is part of the state file (and unikernel_create command) be aware if this (or a dependent grammar) is changed! *) let v1_unikernel_config = let open Unikernel in let f (typ, (compressed, (image, (fail_behaviour, (cpuid, (memory, (blocks, (bridges, argv)))))))) = let bridges = match bridges with None -> [] | Some xs -> List.map (fun b -> b, None) xs and block_devices = match blocks with None -> [] | Some xs -> xs in { typ ; compressed ; image ; fail_behaviour ; cpuid ; memory ; block_devices ; bridges ; argv } and g _vm = failwith "cannot encode v1 unikernel configs" in Asn.S.(map f g @@ sequence @@ (required ~label:"typ" typ) @ (required ~label:"compressed" bool) @ (required ~label:"image" octet_string) @ (required ~label:"fail behaviour" fail_behaviour) @ (required ~label:"cpuid" int) @ (required ~label:"memory" int) @ (optional ~label:"blocks" (explicit 0 (set_of utf8_string))) @ (optional ~label:"bridges" (explicit 1 (set_of utf8_string))) -@ (optional ~label:"arguments"(explicit 2 (sequence_of utf8_string)))) let unikernel_config = let open Unikernel in let f (typ, (compressed, (image, (fail_behaviour, (cpuid, (memory, (blocks, (bridges, argv)))))))) = let bridges = match bridges with None -> [] | Some xs -> xs and block_devices = match blocks with None -> [] | Some xs -> xs in { typ ; compressed ; image ; fail_behaviour ; cpuid ; memory ; block_devices ; bridges ; argv } and g vm = let bridges = match vm.bridges with [] -> None | xs -> Some xs and blocks = match vm.block_devices with [] -> None | xs -> Some xs in (vm.typ, (vm.compressed, (vm.image, (vm.fail_behaviour, (vm.cpuid, (vm.memory, (blocks, (bridges, vm.argv)))))))) in Asn.S.(map f g @@ sequence @@ (required ~label:"typ" typ) @ (required ~label:"compressed" bool) @ (required ~label:"image" octet_string) @ (required ~label:"fail behaviour" fail_behaviour) @ (required ~label:"cpuid" int) @ (required ~label:"memory" int) @ (optional ~label:"blocks" (explicit 0 (set_of utf8_string))) @ (optional ~label:"bridges" (explicit 1 (sequence_of (sequence2 (required ~label:"netif" utf8_string) (optional ~label:"bridge" utf8_string))))) -@ (optional ~label:"arguments"(explicit 2 (sequence_of utf8_string)))) let unikernel_cmd = let f = function | `C1 `C1 () -> `Unikernel_info | `C1 `C2 vm -> `Unikernel_create vm | `C1 `C3 vm -> `Unikernel_force_create vm | `C1 `C4 () -> `Unikernel_destroy | `C1 `C5 vm -> `Unikernel_create vm | `C1 `C6 vm -> `Unikernel_force_create vm | `C2 `C1 () -> `Unikernel_get | `C2 `C2 () -> assert false (* placeholder *) and g = function | `Unikernel_info -> `C1 (`C1 ()) | `Unikernel_create vm -> `C1 (`C5 vm) | `Unikernel_force_create vm -> `C1 (`C6 vm) | `Unikernel_destroy -> `C1 (`C4 ()) | `Unikernel_get -> `C2 (`C1 ()) in Asn.S.map f g @@ Asn.S.(choice2 (choice6 (explicit 0 null) (explicit 1 v1_unikernel_config) (explicit 2 v1_unikernel_config) (explicit 3 null) (explicit 4 unikernel_config) (explicit 5 unikernel_config)) (choice2 (explicit 6 null) (explicit 7 null (* placeholder *) ))) let policy_cmd = let f = function | `C1 () -> `Policy_info | `C2 policy -> `Policy_add policy | `C3 () -> `Policy_remove and g = function | `Policy_info -> `C1 () | `Policy_add policy -> `C2 policy | `Policy_remove -> `C3 () in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 null) (explicit 1 policy) (explicit 2 null)) let block_cmd = let f = function | `C1 () -> `Block_info | `C2 size -> `Block_add size | `C3 () -> `Block_remove and g = function | `Block_info -> `C1 () | `Block_add size -> `C2 size | `Block_remove -> `C3 () in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 null) (explicit 1 int) (explicit 2 null)) let version = let f data = match data with | 4 -> `AV4 | 3 -> `AV3 | x -> Asn.S.error (`Parse (Printf.sprintf "unknown version number 0x%X" x)) and g = function | `AV4 -> 4 | `AV3 -> 3 in Asn.S.map f g Asn.S.int let wire_command = let f = function | `C1 console -> `Console_cmd console | `C2 stats -> `Stats_cmd stats | `C3 log -> `Log_cmd log | `C4 vm -> `Unikernel_cmd vm | `C5 policy -> `Policy_cmd policy | `C6 block -> `Block_cmd block and g = function | `Console_cmd c -> `C1 c | `Stats_cmd c -> `C2 c | `Log_cmd c -> `C3 c | `Unikernel_cmd c -> `C4 c | `Policy_cmd c -> `C5 c | `Block_cmd c -> `C6 c in Asn.S.map f g @@ Asn.S.(choice6 (explicit 0 console_cmd) (explicit 1 stats_cmd) (explicit 2 log_cmd) (explicit 3 unikernel_cmd) (explicit 4 policy_cmd) (explicit 5 block_cmd)) let data = let f = function | `C1 (timestamp, data) -> `Console_data (timestamp, data) | `C2 (ru, ifs, vmm, mem) -> `Stats_data (ru, mem, vmm, ifs) | `C3 (timestamp, event) -> `Log_data (timestamp, event) and g = function | `Console_data (timestamp, data) -> `C1 (timestamp, data) | `Stats_data (ru, mem, ifs, vmm) -> `C2 (ru, vmm, ifs, mem) | `Log_data (timestamp, event) -> `C3 (timestamp, event) in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 (sequence2 (required ~label:"timestamp" utc_time) (required ~label:"data" utf8_string))) (explicit 1 (sequence4 (required ~label:"resource_usage" ru) (required ~label:"ifdata" (sequence_of ifdata)) (optional ~label:"vmm_stats" @@ explicit 0 (sequence_of (sequence2 (required ~label:"key" utf8_string) (required ~label:"value" int64)))) (optional ~label:"kinfo_mem" @@ implicit 1 kinfo_mem))) (explicit 2 (sequence2 (required ~label:"timestamp" utc_time) (required ~label:"event" log_event)))) let header = let f (version, sequence, name) = { version ; sequence ; name = to_name name } and g h = h.version, h.sequence, of_name h.name in Asn.S.map f g @@ Asn.S.(sequence3 (required ~label:"version" version) (required ~label:"sequence" int64) (required ~label:"name" (sequence_of utf8_string))) let success = let f = function | `C1 () -> `Empty | `C2 str -> `String str | `C3 policies -> `Policies (List.map (fun (name, p) -> to_name name, p) policies) | `C4 vms -> `Unikernels (List.map (fun (name, vm) -> to_name name, vm) vms) | `C5 blocks -> `Block_devices (List.map (fun (name, s, a) -> to_name name, s, a) blocks) and g = function | `Empty -> `C1 () | `String s -> `C2 s | `Policies ps -> `C3 (List.map (fun (name, p) -> of_name name, p) ps) | `Unikernels vms -> `C4 (List.map (fun (name, v) -> of_name name, v) vms) | `Block_devices blocks -> `C5 (List.map (fun (name, s, a) -> of_name name, s, a) blocks) in Asn.S.map f g @@ Asn.S.(choice5 (explicit 0 null) (explicit 1 utf8_string) (explicit 2 (sequence_of (sequence2 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"policy" policy)))) (explicit 3 (sequence_of (sequence2 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"config" unikernel_config)))) (explicit 4 (sequence_of (sequence3 (required ~label:"name" (sequence_of utf8_string)) (required ~label:"size" int) (required ~label:"active" bool))))) let payload = let f = function | `C1 cmd -> `Command cmd | `C2 s -> `Success s | `C3 str -> `Failure str | `C4 data -> `Data data and g = function | `Command cmd -> `C1 cmd | `Success s -> `C2 s | `Failure str -> `C3 str | `Data d -> `C4 d in Asn.S.map f g @@ Asn.S.(choice4 (explicit 0 wire_command) (explicit 1 success) (explicit 2 utf8_string) (explicit 3 data)) let wire = Asn.S.(sequence2 (required ~label:"header" header) (required ~label:"payload" payload)) let wire_of_cstruct, wire_to_cstruct = projections_of wire let log_entry = Asn.S.(sequence2 (required ~label:"timestamp" utc_time) (required ~label:"event" log_event)) let log_entry_of_cstruct, log_entry_to_cstruct = projections_of log_entry (* data is persisted to disk, we need to ensure to be able to decode (and encode) properly without conflicts! *) let log_disk = Asn.S.(sequence2 (required ~label:"version" version) (required ~label:"entry" log_entry)) let log_disk_of_cstruct, log_disk_to_cstruct = let c = Asn.codec Asn.der log_disk in (Asn.decode c, Asn.encode c) let log_to_disk entry = log_disk_to_cstruct (current, entry) let logs_of_disk buf = let rec next acc buf = match log_disk_of_cstruct buf with | Ok ((version, entry), cs) -> Logs.info (fun m -> m "read a log entry version %a" pp_version version) ; next (entry :: acc) cs | Error (`Parse msg) -> Logs.warn (fun m -> m "parse error %s while parsing log" msg) ; acc (* ignore *) in next [] buf let trie e = let f elts = List.fold_left (fun trie (key, value) -> match Name.of_string key with | Error (`Msg m) -> invalid_arg m | Ok name -> let trie, ret = Vmm_trie.insert name value trie in assert (ret = None); trie) Vmm_trie.empty elts and g trie = List.map (fun (k, v) -> Name.to_string k, v) (Vmm_trie.all trie) in Asn.S.map f g @@ Asn.S.(sequence_of (sequence2 (required ~label:"name" utf8_string) (required ~label:"value" e))) let version0_unikernels = trie v0_unikernel_config let version1_unikernels = trie v1_unikernel_config let version2_unikernels = trie unikernel_config let unikernels = (* the choice is the implicit version + migration... be aware when any dependent data layout changes .oO(/o\) *) let f = function | `C1 data -> data | `C2 data -> data | `C3 data -> data and g data = `C3 data in Asn.S.map f g @@ Asn.S.(choice3 (explicit 0 version1_unikernels) (explicit 1 version0_unikernels) (explicit 2 version2_unikernels)) let unikernels_of_cstruct, unikernels_to_cstruct = projections_of unikernels let cert_extension = Asn.S.(sequence2 (required ~label:"version" version) (required ~label:"command" wire_command)) let of_cert_extension, to_cert_extension = let a, b = projections_of cert_extension in a, (fun d -> b (current, d))