From: Maria Matejka Date: Mon, 9 Sep 2024 12:14:49 +0000 (+0200) Subject: Flock: Hypervisor control socket CBOR push parser X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9babba1c0f718a60e10c82208319280433e7eb94;p=thirdparty%2Fbird.git Flock: Hypervisor control socket CBOR push parser It reads some data, the actions are not implemented yet. --- diff --git a/flock/Makefile b/flock/Makefile index debbd66cb..c873e169e 100644 --- a/flock/Makefile +++ b/flock/Makefile @@ -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 index 000000000..d94bae755 --- /dev/null +++ b/flock/ctl.c @@ -0,0 +1,300 @@ +#include "lib/birdlib.h" +#include "lib/string.h" + +#include "flock/flock.h" + +#include + +/* + * 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); +} diff --git a/flock/flock.h b/flock/flock.h index 988e4fa81..4a7956fa5 100644 --- a/flock/flock.h +++ b/flock/flock.h @@ -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 diff --git a/flock/hypervisor.c b/flock/hypervisor.c index 0f62c52be..2b52396db 100644 --- a/flock/hypervisor.c +++ b/flock/hypervisor.c @@ -14,11 +14,53 @@ 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; }