]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Flock: Hypervisor control socket CBOR push parser
authorMaria Matejka <mq@ucw.cz>
Mon, 9 Sep 2024 12:14:49 +0000 (14:14 +0200)
committerMaria Matejka <mq@ucw.cz>
Sun, 23 Feb 2025 18:07:35 +0000 (19:07 +0100)
It reads some data, the actions are not implemented yet.

flock/Makefile
flock/ctl.c [new file with mode: 0644]
flock/flock.h
flock/hypervisor.c

index debbd66cbeeea8767b9130b62d2d09e1ab25099e..c873e169e4412a125a15bc365b4b89cda27481a0 100644 (file)
@@ -1,4 +1,4 @@
-src := flock.c hypervisor.c
+src := ctl.c flock.c hypervisor.c
 obj := $(src-o-files)
 
 flock=$(exedir)/flock-sim
diff --git a/flock/ctl.c b/flock/ctl.c
new file mode 100644 (file)
index 0000000..d94bae7
--- /dev/null
@@ -0,0 +1,300 @@
+#include "lib/birdlib.h"
+#include "lib/string.h"
+
+#include "flock/flock.h"
+
+#include <stdlib.h>
+
+/*
+ * Hand-written parser for a very simple CBOR protocol:
+ *
+ * - on toplevel always mapping of one element
+ * - key of the element may be:
+ *   - 0 with NULL (7-22) = shutdown the hypervisor
+ *   - 1 with NULL = open a telnet listener
+ *   - 2 with NULL = close the telnet listener (if not used)
+ *   - 3 with array of strings = run the given command inside the hypervisor
+ */
+
+struct cbor_parser_context {
+  linpool *lp;
+
+  PACKED enum {
+    CPE_TYPE = 0,
+    CPE_READ_INT,
+    CPE_COMPLETE_INT,
+    CPE_READ_BYTE,
+  } partial_state, partial_next;
+
+  byte type;
+  u64 value;
+  u64 partial_countdown;
+
+  u64 bytes_consumed;
+
+  byte *target_buf;
+  uint target_len;
+
+  u64 major_state;
+
+  const char *error;
+
+#define LOCAL_STACK_MAX_DEPTH 3
+  u64 stack_countdown[LOCAL_STACK_MAX_DEPTH];
+  uint stack_pos;
+};
+
+#define CBOR_PARSER_ERROR(...) do {            \
+  ctx->error = lp_sprintf(ctx->lp, __VA_ARGS__);\
+  return -(ctx->bytes_consumed + pos + 1);     \
+} while (0)
+
+#define CBOR_PARSER_READ_INT(next)  do {               \
+  ctx->partial_state = CPE_READ_INT;                   \
+  ctx->partial_countdown = (1 << (ctx->value - 24));   \
+  ctx->value = 0;                                      \
+  ctx->partial_next = next;                            \
+} while (0)
+
+struct cbor_parser_context *
+hcs_parser_init(pool *p)
+{
+  linpool *lp = lp_new(p);
+  struct cbor_parser_context *ctx = lp_allocz(lp, sizeof *ctx);
+
+  ctx->lp = lp;
+
+  ctx->type = 0xff;
+  ctx->stack_countdown[0] = 1;
+
+  return ctx;
+}
+
+s64
+hcs_parse(struct cbor_parser_context *ctx, const byte *buf, s64 size)
+{
+  ASSERT_DIE(size > 0);
+
+  for (int pos = 0; pos < size; pos++)
+  {
+    const byte bp = buf[pos];
+    bool value_is_special = 0;
+    bool exit_stack = false;
+
+    switch (ctx->partial_state)
+    {
+      case CPE_TYPE:
+       /* Split the byte to type and value */
+       ctx->type = bp >> 5;
+       ctx->value = bp & 0x1f;
+
+       if (ctx->type == 7)
+       {
+         if (ctx->value < 20)
+           CBOR_PARSER_ERROR("Unknown simple value %u", ctx->value);
+         else if (ctx->value < 24)
+           ; /* false, true, null, undefined */
+         else if (ctx->value < 28)
+         {
+           /* Need more data */
+           CBOR_PARSER_READ_INT(CPE_COMPLETE_INT);
+           break;
+         }
+         else if (ctx->value == 31)
+           ; /* break-stop */
+         else
+           CBOR_PARSER_ERROR("Unknown simple value %u", ctx->value);
+       }
+       else
+       {
+         if (ctx->value < 24)
+           ; /* Immediate value, fall through */
+         else if (ctx->value < 28)
+         {
+           /* Need more data */
+           CBOR_PARSER_READ_INT(CPE_COMPLETE_INT);
+           break;
+         }
+         else if ((ctx->value == 31) && (ctx->type >= 2) && (ctx->type <= 5))
+           /* Indefinite length, fall through */
+           value_is_special = 1;
+         else
+           CBOR_PARSER_ERROR("Garbled additional value %u for type %u", ctx->value, ctx->type);
+       }
+       /* fall through */
+
+      case CPE_READ_INT:
+       if (ctx->partial_state == CPE_READ_INT)
+       {
+         /* Reading a network order integer */
+         ctx->value <<= 8;
+         ctx->value |= bp;
+         if (--ctx->partial_countdown)
+           break;
+       }
+       /* fall through */
+
+      case CPE_COMPLETE_INT:
+       /* TODO: exception for 7-31 end of long thing */
+
+       /* Check type acceptance */
+       switch (ctx->major_state)
+       {
+         case 0: /* toplevel */
+           if (ctx->type != 5)
+             CBOR_PARSER_ERROR("Expected mapping, got %u", ctx->type);
+
+           if (ctx->value != 1)
+             CBOR_PARSER_ERROR("Expected mapping of length 1, got %u", ctx->value);
+
+           ctx->major_state = 1;
+           break;
+
+         case 1: /* inside toplevel mapping */
+           if (ctx->type != 0)
+             CBOR_PARSER_ERROR("Expected integer, got %u", ctx->type);
+           
+           if (ctx->value >= 4)
+             CBOR_PARSER_ERROR("Command key too high, got %lu", ctx->value);
+
+           ctx->major_state = ctx->value + 2;
+           break;
+
+         case 2: /* shutdown command: expected null */
+           if ((ctx->type != 7) || (ctx->value != 22))
+             CBOR_PARSER_ERROR("Expected null, got %u-%u", ctx->type, ctx->value);
+
+           log(L_INFO "Requested shutdown");
+           ctx->major_state = 1;
+           break;
+
+         case 3: /* telnet listener open */
+           if ((ctx->type != 7) || (ctx->value != 22))
+             CBOR_PARSER_ERROR("Expected null, got %u-%u", ctx->type, ctx->value);
+
+           log(L_INFO "Requested telnet open");
+           ctx->major_state = 1;
+           break;
+
+         case 4: /* telnet listener close */
+           if ((ctx->type != 7) || (ctx->value != 22))
+             CBOR_PARSER_ERROR("Expected null, got %u-%u", ctx->type, ctx->value);
+
+           log(L_INFO "Requested telnet close");
+           ctx->major_state = 1;
+           break;
+
+         case 5: /* process spawner */
+           CBOR_PARSER_ERROR("NOT IMPLEMENTED YET");
+           break;
+
+         default:
+           bug("invalid parser state");
+       }
+
+       /* Some types are completely parsed, some not yet */
+       switch (ctx->type)
+       {
+         case 0:
+         case 1:
+         case 7:
+           exit_stack = !--ctx->stack_countdown[ctx->stack_pos];
+           ctx->partial_state = CPE_TYPE;
+           break;
+
+         case 2:
+         case 3:
+           ctx->partial_state = CPE_READ_BYTE;
+           ctx->partial_countdown = ctx->value;
+           ctx->target_buf = ctx->target_buf ?: lp_allocu(
+               ctx->lp, ctx->target_len = (ctx->target_len ?: ctx->value));
+           break;
+
+         case 4:
+         case 5:
+           if (++ctx->stack_pos >= LOCAL_STACK_MAX_DEPTH)
+             CBOR_PARSER_ERROR("Stack too deep");
+
+           /* set array/map size;
+            * once for arrays, twice for maps;
+            * ~0 for indefinite */
+           ctx->stack_countdown[ctx->stack_pos] = value_is_special ? ~0ULL :
+             (ctx->value * (ctx->type - 3));
+           ctx->partial_state = CPE_TYPE;
+           break;
+       }
+
+       break;
+
+      case CPE_READ_BYTE:
+       *ctx->target_buf = bp;
+       ctx->target_buf++;
+       if (--ctx->target_len)
+         break;
+
+       /* Read completely! */
+       switch (ctx->major_state)
+       {
+         case 5:
+           /* Actually not this one */
+           CBOR_PARSER_ERROR("NOT IMPLEMENTED YET");
+
+         default:
+           bug("Unexpected state to end a (byte)string in");
+         /* Code to run at the end of a (byte)string */
+       }
+
+       exit_stack = !--ctx->stack_countdown[ctx->stack_pos];
+    }
+
+    /* End of array or map */
+    while (exit_stack)
+    {
+      switch (ctx->major_state)
+      {
+       /* Code to run at the end of the mapping */
+       case 0: /* toplevel item ended */
+         ctx->major_state = ~0ULL;
+         return pos + 1;
+
+       case 1:
+         ctx->major_state = 0;
+         break;
+
+       case 5:
+         /* Finalize the command to exec in hypervisor */
+         CBOR_PARSER_ERROR("NOT IMPLEMENTED YET");
+         ctx->major_state = 1;
+         break;
+
+       default:
+         bug("Unexpected state to end a mapping in");
+      }
+
+      /* Check exit from the next item */
+      ASSERT_DIE(ctx->stack_pos);
+      exit_stack = !--ctx->stack_countdown[--ctx->stack_pos];
+    }
+  }
+
+  ctx->bytes_consumed += size;
+  return size;
+}
+
+bool
+hcs_complete(struct cbor_parser_context *ctx)
+{
+  return ctx->major_state == ~0ULL;
+}
+
+const char *
+hcs_error(struct cbor_parser_context *ctx)
+{
+  return ctx->error;
+}
+
+void
+hcs_parser_cleanup(struct cbor_parser_context *ctx)
+{
+  rfree(ctx->lp);
+}
index 988e4fa81ba22acef3fd7d275552f59194490f0d..4a7956fa5cd862b6d706afd5e625717e4db68ba1 100644 (file)
@@ -3,6 +3,7 @@
 #ifndef INCLUDE_FLOCK_H
 #define INCLUDE_FLOCK_H
 #include "lib/birdlib.h"
+#include "lib/resource.h"
 
 void hypervisor_exposed_fork(void);
 void hypervisor_control_socket(void);
@@ -15,4 +16,10 @@ struct flock_config {
 
 extern struct flock_config flock_config;
 
+struct cbor_parser_context *hcs_parser_init(pool *p);
+s64 hcs_parse(struct cbor_parser_context *ctx, const byte *buf, s64 size);
+void hcs_parser_cleanup(struct cbor_parser_context *ctx);
+const char *hcs_error(struct cbor_parser_context *ctx);
+bool hcs_complete(struct cbor_parser_context *ctx);
+
 #endif
index 0f62c52bec4ce12143f4cdac8c74d2d7f0e25e08..2b52396db7c821fabaebbef621814cfc6e68ae2e 100644 (file)
 
 pool *hypervisor_control_socket_pool;
 
+static int
+hcs_rx(sock *s, uint size)
+{
+  s64 sz = hcs_parse(s->data, s->rbuf, size);
+  if (sz < 0)
+  {
+    log(L_INFO "CLI parser error at position %ld: %s", -sz-1, hcs_error(s->data));
+    sk_close(s);
+    return 0; /* Must return 0 when closed */
+  }
+
+  if (!hcs_complete(s->data))
+  {
+    ASSERT_DIE(sz == size);
+    return 1;
+  }
+
+  log(L_INFO "Parsed command.");
+
+  /* TODO do something more */
+
+  hcs_parser_cleanup(s->data);
+  s->data = hcs_parser_init(s->pool);
+
+  if (sz == size)
+    return 1;
+
+  memmove(s->rbuf, s->rbuf + sz, size - sz);
+  return hcs_rx(s, size - sz);
+}
+
+static void
+hcs_err(sock *s, int err)
+{
+  log(L_INFO "CLI dropped: %s", strerror(err));
+  hcs_parser_cleanup(s->data);
+  sk_close(s);
+}
+
 static int
 hcs_connect(sock *s, uint size UNUSED)
 {
   log(L_INFO "CLI connected: %p", s);
-  sk_close(s);
+
+  s->rx_hook = hcs_rx;
+  s->err_hook = hcs_err;
+  s->data = hcs_parser_init(s->pool);
   return 1;
 }