From e0814bc034e9acb8b8f7b07a1c97681d4176f360 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 8 Mar 2024 09:03:01 +0100 Subject: [PATCH 1/3] x86: move byte order conversion helpers from def64.h to machine.h This is being done so that macros such as htobe32() and le64toh() can be called from buffer.h (which will be needed in the next commit). --- src/x86_64/def64.h | 58 -------------------------------------------- src/x86_64/machine.h | 58 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/x86_64/def64.h b/src/x86_64/def64.h index 249e7ac9e..35cfb0909 100644 --- a/src/x86_64/def64.h +++ b/src/x86_64/def64.h @@ -55,64 +55,6 @@ typedef u8 value_tag; __x;\ }) -/* These are defined as functions to avoid multiple evaluation of x. */ -static inline u16 -__bswap16(u16 _x) -{ - return (u16)(_x << 8 | _x >> 8); -} - -static inline u32 -__bswap32(u32 _x) -{ - return ((u32)__bswap16(_x & 0xffff) << 16) | __bswap16(_x >> 16); -} - -static inline u64 -__bswap64(u64 _x) -{ - return ((u64)__bswap32(_x & 0xffffffff) << 32) | __bswap32(_x >> 32); -} - -#ifndef htobe16 -#define htobe16(x) __bswap16(x) -#endif -#ifndef be16toh -#define be16toh(x) __bswap16(x) -#endif -#ifndef htole16 -#define htole16(x) (x) -#endif -#ifndef le16toh -#define le16toh(x) (x) -#endif - -#ifndef htobe32 -#define htobe32(x) __bswap32(x) -#endif -#ifndef be32toh -#define be32toh(x) __bswap32(x) -#endif -#ifndef htole32 -#define htole32(x) (x) -#endif -#ifndef le32toh -#define le32toh(x) (x) -#endif - -#ifndef htobe64 -#define htobe64(x) __bswap64(x) -#endif -#ifndef be64toh -#define be64toh(x) __bswap64(x) -#endif -#ifndef htole64 -#define htole64(x) (x) -#endif -#ifndef le64toh -#define le64toh(x) (x) -#endif - /* returns -1 if x == 0, caller must check */ static inline u64 msb(u64 x) { diff --git a/src/x86_64/machine.h b/src/x86_64/machine.h index 4295f3f05..4841e3c2e 100644 --- a/src/x86_64/machine.h +++ b/src/x86_64/machine.h @@ -87,6 +87,64 @@ static inline __attribute__((always_inline)) s64 s64_from_tagged_immediate(void return (sword)v >> 2; } +/* These are defined as functions to avoid multiple evaluation of x. */ +static inline u16 +__bswap16(u16 _x) +{ + return (u16)(_x << 8 | _x >> 8); +} + +static inline u32 +__bswap32(u32 _x) +{ + return ((u32)__bswap16(_x & 0xffff) << 16) | __bswap16(_x >> 16); +} + +static inline u64 +__bswap64(u64 _x) +{ + return ((u64)__bswap32(_x & 0xffffffff) << 32) | __bswap32(_x >> 32); +} + +#ifndef htobe16 +#define htobe16(x) __bswap16(x) +#endif +#ifndef be16toh +#define be16toh(x) __bswap16(x) +#endif +#ifndef htole16 +#define htole16(x) (x) +#endif +#ifndef le16toh +#define le16toh(x) (x) +#endif + +#ifndef htobe32 +#define htobe32(x) __bswap32(x) +#endif +#ifndef be32toh +#define be32toh(x) __bswap32(x) +#endif +#ifndef htole32 +#define htole32(x) (x) +#endif +#ifndef le32toh +#define le32toh(x) (x) +#endif + +#ifndef htobe64 +#define htobe64(x) __bswap64(x) +#endif +#ifndef be64toh +#define be64toh(x) __bswap64(x) +#endif +#ifndef htole64 +#define htole64(x) (x) +#endif +#ifndef le64toh +#define le64toh(x) (x) +#endif + static inline __attribute__((always_inline)) void compiler_barrier(void) { asm volatile("" ::: "memory"); From a3fbbcc6ad69fd4ed7a43728dbb2c43bf660b86f Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 8 Mar 2024 09:06:30 +0100 Subject: [PATCH 2/3] Runtime buffers: refactor byte-order read/write helpers With this change, the buffer.h header file contains all the helper functions to read and write 16-, 32- and 64-bit values in big endian or little endian format; these functions can be used regardless of the endianness of the target architecture. --- src/runtime/buffer.h | 88 ++++++++++++++--------------------------- test/unit/buffer_test.c | 14 +++++++ 2 files changed, 43 insertions(+), 59 deletions(-) diff --git a/src/runtime/buffer.h b/src/runtime/buffer.h index 78e88d163..08a4c0b62 100644 --- a/src/runtime/buffer.h +++ b/src/runtime/buffer.h @@ -239,51 +239,40 @@ boolean buffer_append(buffer b, const void *body, bytes length); -// little endian variants -#define WRITE_BE(bits) \ - static inline boolean buffer_write_be##bits(buffer b, u64 x) \ - { \ - u64 k = (x); \ - int len = bits>>3; \ - if (!buffer_extend((b), len)) \ - return false; \ - u8 *n = buffer_ref((b), (b)->end); \ - for (int i = len-1; i >= 0; i--) { \ - n[i] = k & 0xff; \ - k >>= 8; \ - } \ - b->end += len; \ - return true; \ +#define BUF_WRITE_ENDIAN(_bits, _endian) \ + static inline boolean buffer_write_##_endian##_bits(buffer b, u##_bits x) \ + { \ + int len = _bits >> 3; \ + if (!buffer_extend(b, len)) \ + return false; \ + u##_bits *p = b->contents + b->end; \ + *p = hto##_endian##_bits(x); \ + b->end += len; \ + return true; \ } -#define READ_BE(bits) \ - static inline u64 buffer_read_be##bits(buffer b) \ - { \ - u64 k = 0; \ - int len = bits>>3; \ - u8 *n = buffer_ref((b), 0); \ - for (int i = 0; i < len; i++) { \ - k = (k << 8) | (*n++); \ - } \ - (b)->start +=len; \ - return(k); \ +#define BUF_READ_ENDIAN(_bits, _endian) \ + static inline u##_bits buffer_read_##_endian##_bits(buffer b) \ + { \ + int len = _bits >> 3; \ + u##_bits *p = b->contents + b->start; \ + b->start += len; \ + return _endian##_bits##toh(*p); \ } -WRITE_BE(64) -WRITE_BE(32) -WRITE_BE(16) -READ_BE(64) -READ_BE(32) -READ_BE(16) +BUF_WRITE_ENDIAN(16, le) +BUF_WRITE_ENDIAN(32, le) +BUF_WRITE_ENDIAN(64, le) +BUF_READ_ENDIAN(16, le) +BUF_READ_ENDIAN(32, le) +BUF_READ_ENDIAN(64, le) -static inline boolean buffer_write_le64(buffer b, u64 v) -{ - if (!buffer_extend(b, sizeof(u64))) - return false; - *(u64 *)(b->contents + b->end) = v; - b->end += sizeof(u64); - return true; -} +BUF_WRITE_ENDIAN(16, be) +BUF_WRITE_ENDIAN(32, be) +BUF_WRITE_ENDIAN(64, be) +BUF_READ_ENDIAN(16, be) +BUF_READ_ENDIAN(32, be) +BUF_READ_ENDIAN(64, be) // end of buffer? static inline u64 buffer_read_byte(buffer b) @@ -452,25 +441,6 @@ static inline void push_u8(buffer b, u8 x) b->end++; } -static inline u32 buffer_read_le32(buffer b) -{ - buffer_assert(b->start + sizeof(u32) <= b->end); - // bounds - u32 x = *(u32 *)buffer_ref(b, 0); - b->start+=sizeof(u32); - return (x); -} - -static inline boolean buffer_write_le32(buffer b, u32 x) -{ - if (!buffer_extend(b, sizeof(u32))) - return false; - buffer_assert(b->end + sizeof(u32) <= b->length); - *(u32 *)buffer_ref(b, buffer_length(b)) = x; - b->end+=sizeof(u32); - return true; -} - static inline void push_varint(buffer b, u64 x) { int last = 0; diff --git a/test/unit/buffer_test.c b/test/unit/buffer_test.c index 77b4a06ee..e18d4438d 100644 --- a/test/unit/buffer_test.c +++ b/test/unit/buffer_test.c @@ -120,6 +120,20 @@ boolean byteorder_tests(heap h) test_assert(buffer_read_le32(b) == 0x44332211); // Read back 32-bits in BE and compare test_assert(buffer_read_be32(b) == 0xdeadbeef); + + buffer_write_le32(b, 0x11223344); + buffer_write_be32(b, 0xdeadbeef); + test_assert(buffer_read_le64(b) == 0xefbeadde11223344); + + buffer_write_le16(b, 0x1122); + buffer_write_be16(b, 0x3344); + buffer_write_le32(b, 0xdeadbeef); + test_assert(buffer_read_be64(b) == 0x22113344efbeadde); + + buffer_write_be32(b, 0x11223344); + test_assert(buffer_read_le16(b) == 0x2211); + test_assert(buffer_read_be16(b) == 0x3344); + failure = false; fail: deallocate_buffer(b); From 2a3ba30038b5a41314c5a24d9d5d9c64866fad9c Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 8 Mar 2024 09:12:02 +0100 Subject: [PATCH 3/3] Klibs: add DigitalOcean metrics agent This new klib implements an agent which, similarly to the DigitalOcean agent at https://github.com/digitalocean/do-agent, collects memory usage metrics (consisting of values for total, free, cached and available memory) and sends them to the DigitalOcean monitoring server. This klib is configured via a "digitalocean" tuple in the manifest options: sending of memory metrics is enabled when this tuple contains a "metrics" tuple. The default interval between two successive metrics data points is 120 seconds; the "metrics" configuration tuple may contain an "interval" option to specify an arbitrary interval value, expressed in seconds. Example snippet of Ops configuration: ``` "ManifestPassthrough": { "digitalocean": { "metrics": {"interval":"180" } } } ``` Note: when the DigitalOcean klib is configured to send memory metrics, CPU and disk I/O metrics are not displayed in the graphs of the DigitalOcean droplet control panel. Closes https://github.com/nanovms/ops/issues/1583 --- klib/Makefile | 5 + klib/crc.h | 6 + klib/crc32.c | 47 ++++ klib/digitalocean.c | 508 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 566 insertions(+) create mode 100644 klib/crc.h create mode 100644 klib/crc32.c create mode 100644 klib/digitalocean.c diff --git a/klib/Makefile b/klib/Makefile index 89bbd690f..a165aa3b9 100644 --- a/klib/Makefile +++ b/klib/Makefile @@ -6,6 +6,7 @@ MBEDTLS_DIR= $(VENDORDIR)/mbedtls PROGRAMS= \ cloud_init \ cloudwatch \ + digitalocean \ firewall \ gcp \ ntp \ @@ -26,6 +27,10 @@ SRCS-cloudwatch= \ $(CURDIR)/aws.c \ $(CURDIR)/cloudwatch.c \ +SRCS-digitalocean= \ + $(CURDIR)/crc32.c \ + $(CURDIR)/digitalocean.c \ + SRCS-firewall= \ $(CURDIR)/firewall.c \ diff --git a/klib/crc.h b/klib/crc.h new file mode 100644 index 000000000..46edeb653 --- /dev/null +++ b/klib/crc.h @@ -0,0 +1,6 @@ +#ifndef _CRC_H_ +#define _CRC_H_ + +u32 crc32c(const u8 *data, bytes len); + +#endif diff --git a/klib/crc32.c b/klib/crc32.c new file mode 100644 index 000000000..ca8ff0660 --- /dev/null +++ b/klib/crc32.c @@ -0,0 +1,47 @@ +#include + +#include "crc.h" + +static const u32 crc32c_table[256] = { + 0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb, + 0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24, + 0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384, + 0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b, + 0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35, + 0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa, + 0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a, + 0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595, + 0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957, + 0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198, + 0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38, + 0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7, + 0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789, + 0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46, + 0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6, + 0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829, + 0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93, + 0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c, + 0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc, + 0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033, + 0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d, + 0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982, + 0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622, + 0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed, + 0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f, + 0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0, + 0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540, + 0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f, + 0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1, + 0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e, + 0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e, + 0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351 +}; + + +u32 crc32c(const u8 *data, bytes len) +{ + u32 crc = ~0; + for (bytes i = 0; i < len; i++) + crc = crc32c_table[(crc & 0xff) ^ data[i]] ^ (crc >> 8); + return ~crc; +} diff --git a/klib/digitalocean.c b/klib/digitalocean.c new file mode 100644 index 000000000..0f1bdada5 --- /dev/null +++ b/klib/digitalocean.c @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include + +#define DO_SERVER_ADDR IPADDR4_INIT_BYTES(169, 254, 169, 254) + +static struct digitalocean { + heap h; + closure_struct(status_handler, setup_complete); + char server_host[IPADDR_STRLEN_MAX]; + int server_host_len; + buffer droplet_token; + struct { + timestamp interval; + buffer url; + struct timer timer; + closure_struct(timer_handler, th); + closure_struct(connection_handler, ch); + buffer_handler out; + closure_struct(input_buffer_handler, ibh); + buffer_handler resp_parser; + closure_struct(value_handler, vh); + boolean pending; + } metrics; + timestamp retry_backoff; +} digitalocean; + +/* Metrics are encoded with the Snappy framing format + * (https://github.com/google/snappy/blob/main/framing_format.txt). */ + +#define SNAPPY_CHUNK_HDR_LEN 4 +#define SNAPPY_CHUNK_CRC_LEN 4 + +enum snappy_chunk_type { + snappy_chunk_stream_id = 0xff, + snappy_chunk_compressed = 0x00, + snappy_chunk_uncompressed = 0x01, +}; + +static const u8 snappy_stream_id[] = { + snappy_chunk_stream_id, 0x06, 0x00, 0x00, 's', 'N', 'a', 'P', 'p', 'Y' +}; + +static boolean do_instance_md_available(void) +{ + ip_addr_t md_server = DO_SERVER_ADDR; + struct netif *n = ip_route(&ip_addr_any, &md_server); + if (n) { + netif_unref(n); + return true; + } + return false; +} + +closure_function(4, 1, boolean, do_instance_md_in, + buffer_handler, out, buffer_handler, parser, value_handler, vh, status_handler, complete, + buffer data) +{ + buffer_handler out = bound(out); + status_handler complete = bound(complete); + status s; + if (data) { + if (bound(parser) == INVALID_ADDRESS) { + value_handler vh = bound(vh); + bound(parser) = allocate_http_parser(digitalocean.h, vh); + if (bound(parser) == INVALID_ADDRESS) { + s = timm_oom; + deallocate_closure(vh); + goto error; + } + } + s = apply(bound(parser), data); + if (is_ok(s)) { + return false; + } else { + s = timm_up(s, "result", "failed to parse HTTP response"); + goto error; + } + } else { /* connection closed */ + buffer_handler parser = bound(parser); + if (parser != INVALID_ADDRESS) + apply(parser, 0); /* deallocates the parser */ + closure_finish(); + return true; + } + error: + apply(out, 0); + apply(complete, s); + return true; +} + +closure_function(4, 1, input_buffer_handler, do_instance_md_ch, + sstring, url, buffer, auth, value_handler, vh, status_handler, complete, + buffer_handler out) +{ + buffer auth = bound(auth); + value_handler vh = bound(vh); + status_handler complete = bound(complete); + status s; + if (!out) { + s = timm("result", "failed to connect to DigitalOcean instance metadata server"); + goto error; + } + tuple req = allocate_tuple(); + if (req == INVALID_ADDRESS) { + s = timm_oom; + goto error; + } + set(req, sym(url), alloca_wrap_sstring(bound(url))); + set(req, sym(Host), alloca_wrap_buffer(digitalocean.server_host, digitalocean.server_host_len)); + if (auth) + set(req, sym(Authorization), auth); + set(req, sym(Connection), alloca_wrap_cstring("close")); + s = http_request(digitalocean.h, out, HTTP_REQUEST_METHOD_GET, req, 0); + deallocate_value(req); + if (!is_ok(s)) { + s = timm_up(s, "result", "failed to send HTTP request"); + goto error; + } + if (auth) + deallocate_buffer(auth); + closure_finish(); + return closure(digitalocean.h, do_instance_md_in, out, INVALID_ADDRESS, vh, complete); + error: + if (auth) + deallocate_buffer(auth); + deallocate_closure(vh); + closure_finish(); + apply(complete, s); + return INVALID_ADDRESS; +} + +static void do_instance_md_get(sstring url, buffer auth, value_handler vh, status_handler complete) +{ + status s; + connection_handler ch = closure(digitalocean.h, do_instance_md_ch, url, auth, vh, complete); + if (ch == INVALID_ADDRESS) { + s = timm_oom; + goto error; + } + ip_addr_t md_server = DO_SERVER_ADDR; + s = direct_connect(digitalocean.h, &md_server, 80, ch); + if (!is_ok(s)) { + s = timm_up(s, "result", "failed to connect to DigitalOcean instance metadata server"); + deallocate_closure(ch); + goto error; + } + return; + error: + deallocate_closure(vh); + deallocate_buffer(auth); + apply(complete, s); +} + +static buffer do_instance_md_get_content(value server_resp) +{ + value start_line = get(server_resp, sym(start_line)); + buffer status_code = get(start_line, integer_key(1)); + if (status_code && !buffer_strcmp(status_code, "200")) + return get(server_resp, sym_this("content")); + else + return 0; +} + +closure_function(1, 1, void, do_droplet_id_vh, + status_handler, complete, + value v) +{ + buffer content = do_instance_md_get_content(v); + status s; + if (content) { + if (push_buffer(digitalocean.metrics.url, content)) + s = STATUS_OK; + else + s = timm("result", "failed to store droplet ID"); + } else { + s = timm("result", "unexpected droplet ID response %v", v); + } + apply(bound(complete), s); +} + +static void do_get_droplet_id(status_handler complete) +{ + value_handler vh = closure(digitalocean.h, do_droplet_id_vh, complete); + if (vh != INVALID_ADDRESS) + do_instance_md_get(ss("/metadata/v1/id"), 0, vh, complete); + else + apply(complete, timm_oom); +} + +closure_function(1, 1, void, do_droplet_token_vh, + status_handler, complete, + value v) +{ + buffer content = do_instance_md_get_content(v); + status s; + if (content) { + digitalocean.droplet_token = clone_buffer(digitalocean.h, content); + if (digitalocean.droplet_token != INVALID_ADDRESS) { + if ((buffer_length(digitalocean.droplet_token) > 3) && + (peek_char(digitalocean.droplet_token) == '"')) { + /* remove double quotes surrounding the token and trailing newline */ + digitalocean.droplet_token->start++; + digitalocean.droplet_token->end -= 2; + } + s = STATUS_OK; + } else { + s = timm_oom; + } + } else { + s = timm("result", "unexpected droplet token response %v", v); + } + apply(bound(complete), s); +} + +static void do_get_droplet_token(buffer auth, status_handler complete) +{ + value_handler vh = closure(digitalocean.h, do_droplet_token_vh, complete); + if (vh != INVALID_ADDRESS) { + do_instance_md_get(ss("/v1/appkey/droplet-auth-token"), auth, vh, complete); + } else { + deallocate_buffer(auth); + apply(complete, timm_oom); + } +} + +closure_function(1, 1, void, do_auth_token_vh, + status_handler, complete, + value v) +{ + buffer content = do_instance_md_get_content(v); + status_handler complete = bound(complete); + status s; + if (content) { + sstring auth_type = ss("DOMETADATA "); + bytes auth_len = auth_type.len + buffer_length(content); + buffer auth_token = allocate_buffer(digitalocean.h, auth_len); + if (auth_token != INVALID_ADDRESS) { + buffer_write_sstring(auth_token, auth_type); + push_buffer(auth_token, content); + do_get_droplet_token(auth_token, complete); + return; + } + s = timm_oom; + } else { + s = timm("result", "unexpected auth token response %v", v); + } + apply(complete, s); +} + +static void do_get_auth_token(status_handler complete) +{ + value_handler vh = closure(digitalocean.h, do_auth_token_vh, complete); + if (vh != INVALID_ADDRESS) + do_instance_md_get(ss("/metadata/v1/auth-token"), 0, vh, complete); + else + apply(complete, timm_oom); +} + +static void do_setup(void) +{ + status_handler complete = (status_handler)&digitalocean.setup_complete; + status s; + if (!do_instance_md_available()) { + s = timm("result", "DigitalOcean instance metadata not available"); + goto out; + } + merge m = allocate_merge(digitalocean.h, complete); + if (m == INVALID_ADDRESS) { + s = timm_oom; + goto out; + } + complete = apply_merge(m); + s = STATUS_OK; + do_get_droplet_id(apply_merge(m)); + do_get_auth_token(apply_merge(m)); + out: + apply(complete, s); +} + +closure_function(1, 2, void, do_setup_retry, + struct timer, t, + u64 expiry, u64 overruns) +{ + if (overruns != timer_disabled) + do_setup(); + closure_finish(); +} + +closure_func_basic(status_handler, void, do_setup_complete, + status s) +{ + if (is_ok(s)) { + digitalocean.retry_backoff = seconds(1); + if (digitalocean.metrics.interval && !timer_is_active(&digitalocean.metrics.timer)) + register_timer(kernel_timers, &digitalocean.metrics.timer, CLOCK_ID_MONOTONIC, 0, false, + digitalocean.metrics.interval, (timer_handler)&digitalocean.metrics.th); + } else { + /* Do not print error messages if the instance metadata server is unreachable just after + * instance startup (a few seconds might elapse before the network interface acquires a DHCP + * address). */ + if (digitalocean.retry_backoff > seconds(2)) + msg_err("setup failed: %v\n", s); + + timm_dealloc(s); + if (digitalocean.retry_backoff < seconds(3600)) + digitalocean.retry_backoff <<= 1; + struct timer t = {0}; + init_timer(&t); + timer_handler setup_retry = closure(digitalocean.h, do_setup_retry, t); + if (setup_retry != INVALID_ADDRESS) + register_timer(kernel_timers, &closure_member(do_setup_retry, setup_retry, t), + CLOCK_ID_MONOTONIC, digitalocean.retry_backoff, false, 0, setup_retry); + } +} + +closure_func_basic(timer_handler, void, do_metrics_timer_handler, + u64 expiry, u64 overruns) +{ + if ((overruns == timer_disabled) || digitalocean.metrics.pending) + return; + digitalocean.metrics.pending = true; + ip_addr_t metrics_server = DO_SERVER_ADDR; + connection_handler ch = (connection_handler)&digitalocean.metrics.ch; + status s = direct_connect(digitalocean.h, &metrics_server, 80, ch); + if (!is_ok(s)) + apply(ch, 0); +} + +static boolean do_metrics_init(buffer b) +{ + if (!buffer_extend(b, sizeof(snappy_stream_id) + SNAPPY_CHUNK_HDR_LEN + SNAPPY_CHUNK_CRC_LEN)) + return false; + buffer_write(b, snappy_stream_id, sizeof(snappy_stream_id)); + buffer_produce(b, SNAPPY_CHUNK_HDR_LEN + SNAPPY_CHUNK_CRC_LEN); + return true; +} + +static boolean do_metrics_add(buffer b, sstring type, u64 value) +{ + /* value is encoded with double-precision floating-point format (IEEE 754) */ + u64 val_msb = msb(value); + u64 exponent = (value == 0) ? 0 : (1023 + val_msb); /* 1023 exponent bias */ + u64 fraction = (value == 0) ? 0 : ((value - U64_FROM_BIT(val_msb)) << (52 - val_msb)); + u64 float64_val = (exponent << 52) | fraction; + + return (buffer_write_le16(b, type.len) && buffer_write_sstring(b, type) && + buffer_write_le64(b, 0) && buffer_write_le64(b, float64_val)); +} + +static void do_metrics_commit(buffer b) +{ + u8 *snappy_chunk_hdr = buffer_ref(b, sizeof(snappy_stream_id)); + bytes chunk_len = buffer_length(b) - sizeof(snappy_stream_id) - SNAPPY_CHUNK_HDR_LEN; + snappy_chunk_hdr[0] = snappy_chunk_uncompressed; + snappy_chunk_hdr[1] = chunk_len; + snappy_chunk_hdr[2] = chunk_len >> 8; + snappy_chunk_hdr[3] = chunk_len >> 16; + u32 checksum = crc32c(snappy_chunk_hdr + SNAPPY_CHUNK_HDR_LEN + SNAPPY_CHUNK_CRC_LEN, + chunk_len - SNAPPY_CHUNK_CRC_LEN); + u32 *snappy_chunk_crc = (u32 *)(snappy_chunk_hdr + SNAPPY_CHUNK_HDR_LEN); + *snappy_chunk_crc = ((checksum >> 15) | (checksum << 17)) + 0xa282ead8; +} + +static boolean do_metrics_post(void) +{ + tuple req = allocate_tuple(); + if (req == INVALID_ADDRESS) + return false; + set(req, sym(url), digitalocean.metrics.url); + set(req, sym(Host), alloca_wrap_buffer(digitalocean.server_host, digitalocean.server_host_len)); + set(req, sym(X-Auth-Key), digitalocean.droplet_token); + set(req, sym(Content-Type), alloca_wrap_cstring("application/timeseries-binary-0")); + set(req, sym(Connection), alloca_wrap_cstring("close")); + boolean success = false; + buffer body = allocate_buffer(digitalocean.h, 256); + if (body == INVALID_ADDRESS) + goto req_dealloc; + heap phys = (heap)heap_physical(get_kernel_heaps()); + u64 total = heap_total(phys); + u64 free = total - heap_allocated(phys); + u64 cached = pagecache_get_occupancy(); + u64 available = free + cached; + do_metrics_init(body); + + /* Insert dummy agent version and revision data, otherwise metrics are not shown in the graphs + * of the DigitalOcean droplet control panel. */ + if (do_metrics_add(body, ss("sonar_build_info\000revision\0003e2db3a\000version\0003.16.7"), + 1) && + do_metrics_add(body, ss("sonar_memory_total"), total) && + do_metrics_add(body, ss("sonar_memory_free"), free) && + do_metrics_add(body, ss("sonar_memory_cached"), cached) && + do_metrics_add(body, ss("sonar_memory_available"), available)) { + do_metrics_commit(body); + status s = http_request(digitalocean.h, digitalocean.metrics.out, HTTP_REQUEST_METHOD_POST, + req, body); + success = is_ok(s); + if (!success) { + msg_err("%v\n", s); + timm_dealloc(s); + } + } + + if (!success) + deallocate_buffer(body); + req_dealloc: + deallocate_value(req); + return success; +} + +closure_func_basic(connection_handler, input_buffer_handler, do_metrics_conn_handler, + buffer_handler out) +{ + input_buffer_handler ibh; + if (out) { + digitalocean.metrics.out = out; + if (do_metrics_post()) + ibh = (input_buffer_handler)&digitalocean.metrics.ibh; + else + ibh = 0; + } else { + ibh = 0; + } + if (!ibh) + digitalocean.metrics.pending = false; + return ibh; +} + +closure_func_basic(input_buffer_handler, boolean, do_metrics_in_handler, + buffer data) +{ + if (data) { + status s = apply(digitalocean.metrics.resp_parser, data); + if (is_ok(s)) { + if (!digitalocean.metrics.out) + return true; + } else { + msg_err("failed to parse response: %v\n", s); + timm_dealloc(s); + apply(digitalocean.metrics.out, 0); + return true; + } + } else { /* connection closed */ + digitalocean.metrics.pending = false; + } + return false; +} + +closure_func_basic(value_handler, void, do_metrics_value_handler, + value v) +{ + value resp = get(v, sym(start_line)); + buffer status_code = get(resp, integer_key(1)); + if (!status_code || peek_char(status_code) != '2') + msg_err("unexpected response %v\n", v); + apply(digitalocean.metrics.out, 0); + digitalocean.metrics.out = 0; /* signal to input buffer handler that connection is closed */ +} + +int init(status_handler complete) +{ + tuple root = get_root_tuple(); + if (!root) + return KLIB_INIT_FAILED; + tuple do_config = get_tuple(root, sym_this("digitalocean")); + if (!do_config) + return KLIB_INIT_OK; + digitalocean.h = heap_locked(get_kernel_heaps()); + boolean config_empty = true; + tuple metrics = get_tuple(do_config, sym_this("metrics")); + if (metrics) { + const u64 min_interval = 60; + u64 interval; + if (get_u64(metrics, sym_this("interval"), &interval)) { + if (interval < min_interval) { + rprintf("DigitalOcean: invalid metrics interval (minimum allowed value %ld " + "seconds)\n", min_interval); + return KLIB_INIT_FAILED; + } + } else { + interval = 120; + } + digitalocean.metrics.interval = seconds(interval); + init_timer(&digitalocean.metrics.timer); + digitalocean.metrics.url = allocate_buffer(digitalocean.h, 32); + assert(digitalocean.metrics.url != INVALID_ADDRESS); + buffer_write_cstring(digitalocean.metrics.url, "/v1/metrics/droplet_id/"); + init_closure_func(&digitalocean.metrics.th, timer_handler, do_metrics_timer_handler); + init_closure_func(&digitalocean.metrics.ch, connection_handler, do_metrics_conn_handler); + init_closure_func(&digitalocean.metrics.ibh, input_buffer_handler, do_metrics_in_handler); + value_handler vh = init_closure_func(&digitalocean.metrics.vh, value_handler, + do_metrics_value_handler); + digitalocean.metrics.resp_parser = allocate_http_parser(digitalocean.h, vh); + assert(digitalocean.metrics.resp_parser != INVALID_ADDRESS); + config_empty = false; + } + if (!config_empty) { + ip_addr_t metrics_server = DO_SERVER_ADDR; + digitalocean.server_host_len = ipaddr_ntoa_r(&metrics_server, digitalocean.server_host, + sizeof(digitalocean.server_host)); + digitalocean.retry_backoff = seconds(1); + init_closure_func(&digitalocean.setup_complete, status_handler, do_setup_complete); + do_setup(); + } + return KLIB_INIT_OK; +}