diff options
-rw-r--r-- | .github/codeql/c-cpp-config.yml | 2 | ||||
-rw-r--r-- | .github/workflows/codeql.yml | 1 | ||||
-rw-r--r-- | .gitmodules | 4 | ||||
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | Makefile.am | 127 | ||||
-rw-r--r-- | configure.ac | 40 | ||||
-rw-r--r-- | daemon/buildinfo.c | 10 | ||||
-rw-r--r-- | daemon/common.h | 5 | ||||
-rw-r--r-- | daemon/main.c | 11 | ||||
-rw-r--r-- | daemon/main.h | 3 | ||||
-rw-r--r-- | daemon/static_threads.c | 12 | ||||
m--------- | httpd/h2o | 0 | ||||
-rw-r--r-- | httpd/h2o_utils.c | 60 | ||||
-rw-r--r-- | httpd/h2o_utils.h | 38 | ||||
-rw-r--r-- | httpd/http_server.c | 339 | ||||
-rw-r--r-- | httpd/http_server.h | 10 | ||||
-rw-r--r-- | libnetdata/buffer/buffer.c | 8 | ||||
-rw-r--r-- | libnetdata/buffer/buffer.h | 8 | ||||
-rw-r--r-- | libnetdata/http/http_defs.h | 30 | ||||
-rw-r--r-- | libnetdata/libnetdata.h | 1 | ||||
-rw-r--r-- | web/server/web_client.h | 24 |
21 files changed, 706 insertions, 28 deletions
diff --git a/.github/codeql/c-cpp-config.yml b/.github/codeql/c-cpp-config.yml new file mode 100644 index 0000000000..cd7c240116 --- /dev/null +++ b/.github/codeql/c-cpp-config.yml @@ -0,0 +1,2 @@ +paths-ignore: + - httpd/h2o diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b2af615e41..174f650eac 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -84,6 +84,7 @@ jobs: uses: github/codeql-action/init@v2 with: languages: cpp + config-file: ./.github/codeql/c-cpp-config.yml - name: Prepare environment run: ./packaging/installer/install-required-packages.sh --dont-wait --non-interactive netdata - name: Build netdata diff --git a/.gitmodules b/.gitmodules index dd687fee89..d2730eb8a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,3 +9,7 @@ url = https://github.com/davisking/dlib.git shallow = true ignore = dirty +[submodule "httpd/h2o"] + path = httpd/h2o + url = https://github.com/h2o/h2o.git + ignore = untracked diff --git a/CMakeLists.txt b/CMakeLists.txt index 2447abb6cc..b220af9f1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,6 +513,7 @@ set(LIBNETDATA_FILES libnetdata/worker_utilization/worker_utilization.h libnetdata/parser/parser.h libnetdata/parser/parser.c + libnetdata/http/http_defs.h ) IF(ENABLE_PLUGIN_EBPF) diff --git a/Makefile.am b/Makefile.am index 7d9abd549c..7ade016cb7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -83,6 +83,7 @@ dist_noinst_DATA = \ packaging/protobuf.version \ packaging/version \ database/engine/journalfile_v2.ksy.in \ + httpd/h2o \ $(NULL) # until integrated within build @@ -197,6 +198,7 @@ LIBNETDATA_FILES = \ libnetdata/string/utf8.h \ libnetdata/worker_utilization/worker_utilization.c \ libnetdata/worker_utilization/worker_utilization.h \ + libnetdata/http/http_defs.h \ $(NULL) if ENABLE_PLUGIN_EBPF @@ -749,7 +751,8 @@ libmqttwebsockets_a_SOURCES = \ mqtt_websockets/c-rbuf/src/ringbuffer_internal.h \ mqtt_websockets/c_rhash/src/c_rhash.c \ mqtt_websockets/c_rhash/include/c_rhash.h \ - mqtt_websockets/c_rhash/src/c_rhash_internal.h + mqtt_websockets/c_rhash/src/c_rhash_internal.h \ + $(NULL) libmqttwebsockets_a_CFLAGS = $(CFLAGS) -DMQTT_WSS_CUSTOM_ALLOC -DRBUF_CUSTOM_MALLOC -DMQTT_WSS_CPUSTATS -I$(srcdir)/aclk/helpers -I$(srcdir)/mqtt_websockets/c_rhash/include @@ -939,6 +942,123 @@ DAEMON_FILES = \ daemon/unit_test.h \ $(NULL) +HTTPD_FILES = \ + httpd/http_server.c \ + httpd/http_server.h \ + httpd/h2o_utils.c \ + httpd/h2o_utils.h \ + $(NULL) + +libh2o_dir = httpd/h2o + +libh2o_a_SOURCES = \ + $(libh2o_dir)/deps/cloexec/cloexec.c \ + $(libh2o_dir)/deps/libgkc/gkc.c \ + $(libh2o_dir)/deps/libyrmcds/close.c \ + $(libh2o_dir)/deps/libyrmcds/connect.c \ + $(libh2o_dir)/deps/libyrmcds/recv.c \ + $(libh2o_dir)/deps/libyrmcds/send.c \ + $(libh2o_dir)/deps/libyrmcds/send_text.c \ + $(libh2o_dir)/deps/libyrmcds/socket.c \ + $(libh2o_dir)/deps/libyrmcds/strerror.c \ + $(libh2o_dir)/deps/libyrmcds/text_mode.c \ + $(libh2o_dir)/deps/picohttpparser/picohttpparser.c \ + $(libh2o_dir)/lib/common/cache.c \ + $(libh2o_dir)/lib/common/file.c \ + $(libh2o_dir)/lib/common/filecache.c \ + $(libh2o_dir)/lib/common/hostinfo.c \ + $(libh2o_dir)/lib/common/http1client.c \ + $(libh2o_dir)/lib/common/memcached.c \ + $(libh2o_dir)/lib/common/memory.c \ + $(libh2o_dir)/lib/common/multithread.c \ + $(libh2o_dir)/lib/common/serverutil.c \ + $(libh2o_dir)/lib/common/socket.c \ + $(libh2o_dir)/lib/common/socketpool.c \ + $(libh2o_dir)/lib/common/string.c \ + $(libh2o_dir)/lib/common/time.c \ + $(libh2o_dir)/lib/common/timeout.c \ + $(libh2o_dir)/lib/common/url.c \ + $(libh2o_dir)/lib/core/config.c \ + $(libh2o_dir)/lib/core/configurator.c \ + $(libh2o_dir)/lib/core/context.c \ + $(libh2o_dir)/lib/core/headers.c \ + $(libh2o_dir)/lib/core/logconf.c \ + $(libh2o_dir)/lib/core/proxy.c \ + $(libh2o_dir)/lib/core/request.c \ + $(libh2o_dir)/lib/core/token.c \ + $(libh2o_dir)/lib/core/util.c \ + $(libh2o_dir)/lib/handler/access_log.c \ + $(libh2o_dir)/lib/handler/chunked.c \ + $(libh2o_dir)/lib/handler/compress.c \ + $(libh2o_dir)/lib/handler/compress/gzip.c \ + $(libh2o_dir)/lib/handler/errordoc.c \ + $(libh2o_dir)/lib/handler/expires.c \ + $(libh2o_dir)/lib/handler/fastcgi.c \ + $(libh2o_dir)/lib/handler/file.c \ + $(libh2o_dir)/lib/handler/headers.c \ + $(libh2o_dir)/lib/handler/mimemap.c \ + $(libh2o_dir)/lib/handler/proxy.c \ + $(libh2o_dir)/lib/handler/redirect.c \ + $(libh2o_dir)/lib/handler/reproxy.c \ + $(libh2o_dir)/lib/handler/throttle_resp.c \ + $(libh2o_dir)/lib/handler/status.c \ + $(libh2o_dir)/lib/handler/headers_util.c \ + $(libh2o_dir)/lib/handler/status/events.c \ + $(libh2o_dir)/lib/handler/status/requests.c \ + $(libh2o_dir)/lib/handler/http2_debug_state.c \ + $(libh2o_dir)/lib/handler/status/durations.c \ + $(libh2o_dir)/lib/handler/configurator/access_log.c \ + $(libh2o_dir)/lib/handler/configurator/compress.c \ + $(libh2o_dir)/lib/handler/configurator/errordoc.c \ + $(libh2o_dir)/lib/handler/configurator/expires.c \ + $(libh2o_dir)/lib/handler/configurator/fastcgi.c \ + $(libh2o_dir)/lib/handler/configurator/file.c \ + $(libh2o_dir)/lib/handler/configurator/headers.c \ + $(libh2o_dir)/lib/handler/configurator/proxy.c \ + $(libh2o_dir)/lib/handler/configurator/redirect.c \ + $(libh2o_dir)/lib/handler/configurator/reproxy.c \ + $(libh2o_dir)/lib/handler/configurator/throttle_resp.c \ + $(libh2o_dir)/lib/handler/configurator/status.c \ + $(libh2o_dir)/lib/handler/configurator/http2_debug_state.c \ + $(libh2o_dir)/lib/handler/configurator/headers_util.c \ + $(libh2o_dir)/lib/http1.c \ + $(libh2o_dir)/lib/tunnel.c \ + $(libh2o_dir)/lib/http2/cache_digests.c \ + $(libh2o_dir)/lib/http2/casper.c \ + $(libh2o_dir)/lib/http2/connection.c \ + $(libh2o_dir)/lib/http2/frame.c \ + $(libh2o_dir)/lib/http2/hpack.c \ + $(libh2o_dir)/lib/http2/scheduler.c \ + $(libh2o_dir)/lib/http2/stream.c \ + $(libh2o_dir)/lib/http2/http2_debug_state.c \ + $(NULL) + +libh2o_a_INCLUDES = \ + -I$(srcdir)/$(libh2o_dir)/include \ + -I$(srcdir)/$(libh2o_dir)/deps/cloexec \ + -I$(srcdir)/$(libh2o_dir)/deps/brotli/enc \ + -I$(srcdir)/$(libh2o_dir)/deps/golombset \ + -I$(srcdir)/$(libh2o_dir)/deps/libgkc \ + -I$(srcdir)/$(libh2o_dir)/deps/libyrmcds \ + -I$(srcdir)/$(libh2o_dir)/deps/klib \ + -I$(srcdir)/$(libh2o_dir)/deps/neverbleed \ + -I$(srcdir)/$(libh2o_dir)/deps/picohttpparser \ + -I$(srcdir)/$(libh2o_dir)/deps/picotest \ + -I$(srcdir)/$(libh2o_dir)/deps/yaml/include \ + -I$(srcdir)/$(libh2o_dir)/deps/yoml \ + $(NULL) + +if ENABLE_HTTPD +noinst_LIBRARIES += libh2o.a + +# until h2o updates support for OpenSSL 3.0 we silence the warnings +libh2o_a_CFLAGS = $(CFLAGS) -Wno-deprecated-declarations -Wno-unused-parameter -Wno-sign-compare -Wno-missing-field-initializers -DH2O_USE_LIBUV=0 $(libh2o_a_INCLUDES) + +if LINUX + libh2o_a_CFLAGS += -D_GNU_SOURCE +endif +endif #ENABLE_HTTPD + NETDATA_FILES = \ collectors/all.h \ $(DAEMON_FILES) \ @@ -1007,6 +1127,11 @@ if ENABLE_ACLK NETDATA_COMMON_LIBS += libmqttwebsockets.a endif +if ENABLE_HTTPD + NETDATA_FILES += $(HTTPD_FILES) + NETDATA_COMMON_LIBS += libh2o.a +endif + if LINK_STATIC_JSONC NETDATA_COMMON_LIBS += $(abs_top_srcdir)/externaldeps/jsonc/libjson-c.a endif diff --git a/configure.ac b/configure.ac index 3c4a75ae35..49015b297f 100644 --- a/configure.ac +++ b/configure.ac @@ -213,6 +213,12 @@ AC_ARG_ENABLE( [aclk_ssl_debug="yes"], [aclk_ssl_debug="no"] ) +AC_ARG_ENABLE( + [httpd], + [AS_HELP_STRING([--disable-httpd], [Disable webserver (h2o based) @<:@default autodetect@:>@])], + , + [enable_httpd="detect"] +) # ----------------------------------------------------------------------------- # Enforce building with C99, bail early if we can't. @@ -800,6 +806,38 @@ AC_MSG_RESULT([${with_libcap}]) AM_CONDITIONAL([ENABLE_CAPABILITY], [test "${with_libcap}" = "yes"]) # ----------------------------------------------------------------------------- +# HTTPD and h2o related + +can_build_httpd="no" +if test "${enable_httpd}" != "no"; then + can_build_httpd="yes" + AC_MSG_CHECKING([can build HTTPD]) + if test -z "${UV_LIBS}"; then + can_build_httpd="no" + fi + if test -n "${SSL_LIBS}"; then + OPTIONAL_SSL_CFLAGS="${SSL_CFLAGS}" + OPTIONAL_SSL_LIBS="${SSL_LIBS}" + else + can_build_httpd="no" + fi + if test "${with_zlib}" != "yes"; then + can_build_httpd="no" + fi + AC_MSG_RESULT([${can_build_httpd}]) + + if test "${can_build_httpd}" = "no" -a "${enable_httpd}" = "yes"; then + AC_MSG_ERROR([HTTPD was requested but it cannot be built]) + fi + + if test "${can_build_httpd}" = "yes"; then + AC_DEFINE([ENABLE_HTTPD], [1], [HTTPD (h2o based web server)]) + HTTPD_CFLAGS="-I\$(abs_top_srcdir)/httpd/h2o/include -I\$(abs_top_srcdir)/httpd/h2o/deps/picotls/include -I\$(abs_top_srcdir)/httpd/h2o/deps/quicly/include -DH2O_USE_LIBUV=0" + fi +fi +AM_CONDITIONAL([ENABLE_HTTPD], [test "${can_build_httpd}" = "yes"]) + +# ----------------------------------------------------------------------------- # ACLK bundled_proto_avail="no" @@ -1689,7 +1727,7 @@ CFLAGS="${originalCFLAGS} ${OPTIONAL_LTO_CFLAGS} ${OPTIONAL_PROTOBUF_CFLAGS} ${O ${OPTIONAL_LIBCAP_CFLAGS} ${OPTIONAL_IPMIMONITORING_CFLAGS} ${OPTIONAL_CUPS_CFLAGS} ${OPTIONAL_XENSTAT_FLAGS} \ ${OPTIONAL_KINESIS_CFLAGS} ${OPTIONAL_PUBSUB_CFLAGS} ${OPTIONAL_PROMETHEUS_REMOTE_WRITE_CFLAGS} \ ${OPTIONAL_MONGOC_CFLAGS} ${LWS_CFLAGS} ${OPTIONAL_JSONC_STATIC_CFLAGS} ${OPTIONAL_YAML_STATIC_CFLAGS} ${OPTIONAL_BPF_CFLAGS} ${JUDY_CFLAGS} \ - ${OPTIONAL_ACLK_CFLAGS} ${OPTIONAL_ML_CFLAGS} ${OPTIONAL_OS_DEP_CFLAGS}" + ${OPTIONAL_ACLK_CFLAGS} ${OPTIONAL_ML_CFLAGS} ${OPTIONAL_OS_DEP_CFLAGS} ${HTTPD_CFLAGS}" CXXFLAGS="${CFLAGS} ${OPTIONAL_KINESIS_CXXFLAGS} ${CPP_STD_FLAG}" diff --git a/daemon/buildinfo.c b/daemon/buildinfo.c index ef813a9617..50f3b3a8e5 100644 --- a/daemon/buildinfo.c +++ b/daemon/buildinfo.c @@ -20,6 +20,12 @@ #endif #endif +#ifdef ENABLE_HTTPD +#define FEAT_HTTPD 1 +#else +#define FEAT_HTTPD 0 +#endif + #ifdef ENABLE_DBENGINE #define FEAT_DBENGINE 1 #else @@ -275,6 +281,7 @@ void print_build_info(void) { printf(" TLS Host Verification: %s\n", FEAT_YES_NO(FEAT_TLS_HOST_VERIFY)); printf(" Machine Learning: %s\n", FEAT_YES_NO(FEAT_ML)); printf(" Stream Compression: %s\n", FEAT_YES_NO(FEAT_STREAM_COMPRESSION)); + printf(" HTTPD (h2o): %s\n", FEAT_YES_NO(FEAT_HTTPD)); printf("Libraries:\n"); printf(" protobuf: %s%s\n", FEAT_YES_NO(FEAT_PROTOBUF), FEAT_PROTOBUF_BUNDLED); @@ -328,7 +335,8 @@ void print_build_info_json(void) { printf(" \"tls-host-verify\": %s,\n", FEAT_JSON_BOOL(FEAT_TLS_HOST_VERIFY)); printf(" \"machine-learning\": %s\n", FEAT_JSON_BOOL(FEAT_ML)); - printf(" \"stream-compression\": %s\n", FEAT_JSON_BOOL(FEAT_STREAM_COMPRESSION)); + printf(" \"stream-compression\": %s\n", FEAT_JSON_BOOL(FEAT_STREAM_COMPRESSION)); + printf(" \"httpd-h2o\": %s\n", FEAT_JSON_BOOL(FEAT_HTTPD)); printf(" },\n"); printf(" \"libs\": {\n"); diff --git a/daemon/common.h b/daemon/common.h index 66ffd4a749..aeaf01637a 100644 --- a/daemon/common.h +++ b/daemon/common.h @@ -41,6 +41,11 @@ // the netdata webserver(s) #include "web/server/web_server.h" +// the new h2o based netdata webserver +#ifdef ENABLE_HTTPD +#include "httpd/http_server.h" +#endif + // streaming metrics between netdata servers #include "streaming/rrdpush.h" diff --git a/daemon/main.c b/daemon/main.c index 606de128bd..19688c725b 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -170,6 +170,8 @@ static void service_to_buffer(BUFFER *wb, SERVICE_TYPE service) { buffer_strcat(wb, "ANALYTICS "); if(service & SERVICE_EXPORTERS) buffer_strcat(wb, "EXPORTERS "); + if(service & SERVICE_HTTPD) + buffer_strcat(wb, "HTTPD "); } static bool service_wait_exit(SERVICE_TYPE service, usec_t timeout_ut) { @@ -365,6 +367,7 @@ void netdata_cleanup_and_exit(int ret) { | SERVICE_EXPORTERS | SERVICE_HEALTH | SERVICE_WEB_SERVER + | SERVICE_HTTPD , 3 * USEC_PER_SEC); delta_shutdown_time("stop collectors and streaming threads"); @@ -1979,6 +1982,14 @@ int main(int argc, char **argv) { if(web_server_mode != WEB_SERVER_MODE_NONE) api_listen_sockets_setup(); + +#ifdef ENABLE_HTTPD + delta_startup_time("initialize httpd server"); + for (int i = 0; static_threads[i].name; i++) { + if (static_threads[i].start_routine == httpd_main) + static_threads[i].enabled = httpd_is_enabled(); + } +#endif } delta_startup_time("set resource limits"); diff --git a/daemon/main.h b/daemon/main.h index 3e32c5ad6d..232b7a98ac 100644 --- a/daemon/main.h +++ b/daemon/main.h @@ -41,7 +41,8 @@ typedef enum { SERVICE_CONTEXT = (1 << 10), SERVICE_ANALYTICS = (1 << 11), SERVICE_EXPORTERS = (1 << 12), - SERVICE_ACLKSYNC = (1 << 13) + SERVICE_ACLKSYNC = (1 << 13), + SERVICE_HTTPD = (1 << 14) } SERVICE_TYPE; typedef enum { diff --git a/daemon/static_threads.c b/daemon/static_threads.c index d93cfe9d03..fe83945cfe 100644 --- a/daemon/static_threads.c +++ b/daemon/static_threads.c @@ -142,6 +142,18 @@ const struct netdata_static_thread static_threads_common[] = { .start_routine = socket_listen_main_static_threaded }, +#ifdef ENABLE_HTTPD + { + .name = "httpd", + .config_section = NULL, + .config_name = NULL, + .enabled = 0, + .thread = NULL, + .init_routine = NULL, + .start_routine = httpd_main + }, +#endif + #ifdef ENABLE_ACLK { .name = "ACLK_MAIN", diff --git a/httpd/h2o b/httpd/h2o new file mode 160000 +Subproject 7359e98d78d018a35f5da7523feac69f64eddb4 diff --git a/httpd/h2o_utils.c b/httpd/h2o_utils.c new file mode 100644 index 0000000000..943216f596 --- /dev/null +++ b/httpd/h2o_utils.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "h2o_utils.h" + +#include "h2o/string_.h" + +#include "libnetdata/libnetdata.h" + +char *iovec_to_cstr(h2o_iovec_t *str) +{ + char *c_str = mallocz(str->len + 1); + memcpy(c_str, str->base, str->len); + c_str[str->len] = 0; + return c_str; +} + +#define KEY_VAL_BUFFER_GROWTH_STEP 5 +h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string) +{ + h2o_iovec_pair_vector_t *params_vec = h2o_mem_alloc_shared(pool, sizeof(h2o_iovec_pair_vector_t), NULL); + memset(params_vec, 0, sizeof(h2o_iovec_pair_vector_t)); + + h2o_iovec_pair_t param; + while ((param.name.base = (char*)h2o_next_token(¶ms_string, '&', ¶m.name.len, ¶m.value)) != NULL) { + if (params_vec->capacity == params_vec->size) + h2o_vector_reserve(pool, params_vec, params_vec->capacity + KEY_VAL_BUFFER_GROWTH_STEP); + + params_vec->entries[params_vec->size++] = param; + } + + return params_vec; +} + +h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len) +{ + for (size_t i = 0; i < params_vec->size; i++) { + h2o_iovec_pair_t *ret = ¶ms_vec->entries[i]; + if (h2o_memis(ret->name.base, ret->name.len, needle, needle_len)) + return ret; + } + return NULL; +} + +char *url_unescape(const char *url) +{ + char *result = mallocz(strlen(url) + 1); + + int i, j; + for (i = 0, j = 0; url[i] != 0; i++, j++) { + if (url[i] == '%' && isxdigit(url[i+1]) && isxdigit(url[i+2])) { + char hex[3] = { url[i+1], url[i+2], 0 }; + result[j] = strtol(hex, NULL, 16); + i += 2; + } else + result[j] = url[i]; + } + result[j] = 0; + + return result; +} diff --git a/httpd/h2o_utils.h b/httpd/h2o_utils.h new file mode 100644 index 0000000000..6760ed9a95 --- /dev/null +++ b/httpd/h2o_utils.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_H2O_UTILS_H +#define NETDATA_H2O_UTILS_H + +#include "h2o/memory.h" + +#define __HAS_URL_PARAMS(reqptr) ((reqptr)->query_at != SIZE_MAX && ((reqptr)->path.len - (reqptr)->query_at > 1)) +#define IF_HAS_URL_PARAMS(reqptr) if __HAS_URL_PARAMS(reqptr) +#define UNLESS_HAS_URL_PARAMS(reqptr) if (!__HAS_URL_PARAMS(reqptr)) +#define URL_PARAMS_IOVEC_INIT(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at + 1], \ + .len = (reqptr)->path.len - (reqptr)->query_at - 1 } +#define URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(reqptr) { .base = &(reqptr)->path.base[(reqptr)->query_at], \ + .len = (reqptr)->path.len - (reqptr)->query_at } + +#define PRINTF_H2O_IOVEC_FMT "%.*s" +#define PRINTF_H2O_IOVEC(iovec) ((int)(iovec)->len), ((iovec)->base) + +char *iovec_to_cstr(h2o_iovec_t *str); + +typedef struct h2o_iovec_pair { + h2o_iovec_t name; + h2o_iovec_t value; +} h2o_iovec_pair_t; + +typedef H2O_VECTOR(h2o_iovec_pair_t) h2o_iovec_pair_vector_t; + +// Takes the part of url behind ? (the url encoded parameters) +// and parse it to vector of name/value pairs without copying the actual strings +h2o_iovec_pair_vector_t *parse_URL_params(h2o_mem_pool_t *pool, h2o_iovec_t params_string); + +// Searches for parameter by name (provided in needle) +// returns pointer to it or NULL +h2o_iovec_pair_t *get_URL_param_by_name(h2o_iovec_pair_vector_t *params_vec, const void *needle, size_t needle_len); + +char *url_unescape(const char *url); + +#endif /* NETDATA_H2O_UTILS_H */ diff --git a/httpd/http_server.c b/httpd/http_server.c new file mode 100644 index 0000000000..24b168d92d --- /dev/null +++ b/httpd/http_server.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "daemon/common.h" +#include "http_server.h" +#include "h2o.h" + +#include "h2o_utils.h" + +static h2o_globalconf_t config; +static h2o_context_t ctx; +static h2o_accept_ctx_t accept_ctx; + +#define CONTENT_JSON_UTF8 H2O_STRLIT("application/json; charset=utf-8") +#define CONTENT_TEXT_UTF8 H2O_STRLIT("text/plain; charset=utf-8") +#define NBUF_INITIAL_SIZE_RESP (4096) +#define API_V1_PREFIX "/api/v1/" +#define HOST_SELECT_PREFIX "/host/" + +#define HTTPD_CONFIG_SECTION "httpd" +#define HTTPD_ENABLED_DEFAULT false + +static void on_accept(h2o_socket_t *listener, const char *err) +{ + h2o_socket_t *sock; + + if (err != NULL) { + return; + } + + if ((sock = h2o_evloop_socket_accept(listener)) == NULL) + return; + h2o_accept(&accept_ctx, sock); +} + +static int create_listener(const char *ip, int port) +{ + struct sockaddr_in addr; + int fd, reuseaddr_flag = 1; + h2o_socket_t *sock; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr(ip); + addr.sin_port = htons(port); + + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 || + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_flag, sizeof(reuseaddr_flag)) != 0 || + bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(fd, SOMAXCONN) != 0) { + return -1; + } + + sock = h2o_evloop_socket_create(ctx.loop, fd, H2O_SOCKET_FLAG_DONT_READ); + h2o_socket_read_start(sock, on_accept); + + return 0; +} + +static int ssl_init() +{ + if (!config_get_boolean(HTTPD_CONFIG_SECTION, "ssl", false)) + return 0; + + char default_fn[FILENAME_MAX + 1]; + + snprintfz(default_fn, FILENAME_MAX, "%s/ssl/key.pem", netdata_configured_user_config_dir); + const char *key_fn = config_get(HTTPD_CONFIG_SECTION, "ssl key", default_fn); + + snprintfz(default_fn, FILENAME_MAX, "%s/ssl/cert.pem", netdata_configured_user_config_dir); + const char *cert_fn = config_get(HTTPD_CONFIG_SECTION, "ssl certificate", default_fn); + +#if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_110 + accept_ctx.ssl_ctx = SSL_CTX_new(SSLv23_server_method()); +#else + accept_ctx.ssl_ctx = SSL_CTX_new(TLS_server_method()); +#endif + + SSL_CTX_set_options(accept_ctx.ssl_ctx, SSL_OP_NO_SSLv2); + + /* load certificate and private key */ + if (SSL_CTX_use_PrivateKey_file(accept_ctx.ssl_ctx, key_fn, SSL_FILETYPE_PEM) != 1) { + error("Could not load server key from \"%s\"", key_fn); + return -1; + } + if (SSL_CTX_use_certificate_file(accept_ctx.ssl_ctx, cert_fn, SSL_FILETYPE_PEM) != 1) { + error("Could not load certificate from \"%s\"", cert_fn); + return -1; + } + + h2o_ssl_register_alpn_protocols(accept_ctx.ssl_ctx, h2o_http2_alpn_protocols); + + info("SSL support enabled"); + + return 0; +} + +// I did not find a way to do wildcard paths to make common handler for urls like: +// /api/v1/info +// /host/child/api/v1/info +// /host/uuid/api/v1/info +// ideally we could do something like "/*/api/v1/info" subscription +// so we do it "manually" here with uberhandler +static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host) +{ + if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) + return -1; + + static h2o_generator_t generator = { NULL, NULL }; + + h2o_iovec_t norm_path = req->path_normalized; + + if (norm_path.len > strlen(HOST_SELECT_PREFIX) && !memcmp(norm_path.base, HOST_SELECT_PREFIX, strlen(HOST_SELECT_PREFIX))) { + h2o_iovec_t host_id; // host_id can be either and UUID or a hostname of the child + + norm_path.base += strlen(HOST_SELECT_PREFIX); + norm_path.len -= strlen(HOST_SELECT_PREFIX); + + host_id = norm_path; + + size_t end_loc = h2o_strstr(host_id.base, host_id.len, "/", 1); + if (end_loc != SIZE_MAX) { + host_id.len = end_loc; + norm_path.base += end_loc; + norm_path.len -= end_loc; + } + + char *c_host_id = iovec_to_cstr(&host_id); + *host = rrdhost_find_by_hostname(c_host_id); + if (!*host) + *host = rrdhost_find_by_guid(c_host_id); + if (!*host) { + req->res.status = HTTP_RESP_BAD_REQUEST; + req->res.reason = "Wrong host id"; + h2o_send_inline(req, H2O_STRLIT("Host id provided was not found!\n")); + freez(c_host_id); + return 0; + } + freez(c_host_id); + + // we have to rewrite URL here in case this is not an api call + // so that the subsequent file upload handler can send the correct + // files to the client + // if this is not an API call we will abort this handler later + // and let the internal serve file handler of h2o care for things + + if (end_loc == SIZE_MAX) { + req->path.len = 1; + req->path_normalized.len = 1; + } else { + size_t offset = norm_path.base - req->path_normalized.base; + req->path.len -= offset; + req->path.base += offset; + req->query_at -= offset; + req->path_normalized.len -= offset; + req->path_normalized.base += offset; + } + } + + // workaround for a dashboard bug which causes sometimes urls like + // "//api/v1/info" to be caled instead of "/api/v1/info" + if (norm_path.len > 2 && + norm_path.base[0] == '/' && + norm_path.base[1] == '/' ) { + norm_path.base++; + norm_path.len--; + } + + size_t api_loc = h2o_strstr(norm_path.base, norm_path.len, H2O_STRLIT(API_V1_PREFIX)); + if (api_loc == SIZE_MAX) + return 1; + + h2o_iovec_t api_command = norm_path; + api_command.base += api_loc + strlen(API_V1_PREFIX); + api_command.len -= api_loc + strlen(API_V1_PREFIX); + + if (!api_command.len) + return 1; + + // this (emulating struct web_client) is a hack and will be removed + // in future PRs but needs bigger changes in old http_api_v1 + // we need to make the web_client_api_request_v1 to be web server + // agnostic and remove the old webservers dependency creep into the + // individual response generators and thus remove the need to "emulate" + // the old webserver calling this function here and in ACLK + struct web_client w; + w.response.data = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + w.response.header = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + w.url_query_string_decoded = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + w.acl = WEB_CLIENT_ACL_DASHBOARD; + + char *path_c_str = iovec_to_cstr(&api_command); + char *path_unescaped = url_unescape(path_c_str); + freez(path_c_str); + + IF_HAS_URL_PARAMS(req) { + h2o_iovec_t query_params = URL_PARAMS_IOVEC_INIT_WITH_QUESTIONMARK(req); + char *query_c_str = iovec_to_cstr(&query_params); + char *query_unescaped = url_unescape(query_c_str); + freez(query_c_str); + buffer_strcat(w.url_query_string_decoded, query_unescaped); + freez(query_unescaped); + } + + web_client_api_request_v1(*host, &w, path_unescaped); + freez(path_unescaped); + + h2o_iovec_t body = buffer_to_h2o_iovec(w.response.data); + + // we move msg body to req->pool managed memory as it has to + // live until whole response has been encrypted and sent + // when req is finished memory will be freed with the pool + void *managed = h2o_mem_alloc_shared(&req->pool, body.len, NULL); + memcpy(managed, body.base, body.len); + body.base = managed; + + req->res.status = HTTP_RESP_OK; + req->res.reason = "OK"; + if (w.response.data->content_type == CT_APPLICATION_JSON) + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_JSON_UTF8); + else + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_TEXT_UTF8); + h2o_start_response(req, &generator); + h2o_send(req, &body, 1, H2O_SEND_STATE_FINAL); + + buffer_free(w.response.data); + buffer_free(w.response.header); + buffer_free(w.url_query_string_decoded); + + return 0; +} + +static int netdata_uberhandler(h2o_handler_t *self, h2o_req_t *req) +{ + UNUSED(self); + RRDHOST *host = localhost; + + int ret = _netdata_uberhandler(req, &host); + + char host_uuid_str[UUID_STR_LEN]; + uuid_unparse_lower(host->host_uuid, host_uuid_str); + + if (!ret) { + log_access("HTTPD OK method: " PRINTF_H2O_IOVEC_FMT + ", path: " PRINTF_H2O_IOVEC_FMT + ", as host: %s" + ", response: %d", + PRINTF_H2O_IOVEC(&req->method), + PRINTF_H2O_IOVEC(&req->input.path), + host == localhost ? "localhost" : host_uuid_str, + req->res.status); + } else { + log_access("HTTPD %d" + " method: " PRINTF_H2O_IOVEC_FMT + ", path: " PRINTF_H2O_IOVEC_FMT + ", forwarding to file handler as path: " PRINTF_H2O_IOVEC_FMT, + ret, + PRINTF_H2O_IOVEC(&req->method), + PRINTF_H2O_IOVEC(&req->input.path), + PRINTF_H2O_IOVEC(&req->path)); + } + + return ret; +} + +static int hdl_netdata_conf(h2o_handler_t *self, h2o_req_t *req) +{ + UNUSED(self); + if (!h2o_memis(req->method.base, req->method.len, H2O_STRLIT("GET"))) + return -1; + + BUFFER *buf = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL); + config_generate(buf, 0); + + void *managed = h2o_mem_alloc_shared(&req->pool, buf->len, NULL); + memcpy(managed, buf->buffer, buf->len); + + req->res.status = HTTP_RESP_OK; + req->res.reason = "OK"; + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_TYPE, NULL, CONTENT_TEXT_UTF8); + h2o_send_inline(req, managed, buf->len); + buffer_free(buf); + + return 0; +} + +#define POLL_INTERVAL 100 + +void *httpd_main(void *ptr) { + struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr; + + h2o_pathconf_t *pathconf; + h2o_hostconf_t *hostconf; + + netdata_thread_disable_cancelability(); + + const char *bind_addr = config_get(HTTPD_CONFIG_SECTION, "bind to", "127.0.0.1"); + int bind_port = config_get_number(HTTPD_CONFIG_SECTION, "port", 19998); + + h2o_config_init(&config); + hostconf = h2o_config_register_host(&config, h2o_iovec_init(H2O_STRLIT("default")), bind_port); + + pathconf = h2o_config_register_path(hostconf, "/netdata.conf", 0); + h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler)); + handler->on_req = hdl_netda |