diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:57:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 02:57:58 +0000 |
commit | be1c7e50e1e8809ea56f2c9d472eccd8ffd73a97 (patch) | |
tree | 9754ff1ca740f6346cf8483ec915d4054bc5da2d /fluent-bit/lib/monkey/mk_server | |
parent | Initial commit. (diff) | |
download | netdata-be1c7e50e1e8809ea56f2c9d472eccd8ffd73a97.tar.xz netdata-be1c7e50e1e8809ea56f2c9d472eccd8ffd73a97.zip |
Adding upstream version 1.44.3.upstream/1.44.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'fluent-bit/lib/monkey/mk_server')
23 files changed, 11302 insertions, 0 deletions
diff --git a/fluent-bit/lib/monkey/mk_server/CMakeLists.txt b/fluent-bit/lib/monkey/mk_server/CMakeLists.txt new file mode 100644 index 00000000..457525e6 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/CMakeLists.txt @@ -0,0 +1,57 @@ +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +set(src + monkey.c + mk_lib.c + mk_fifo.c + mk_mimetype.c + mk_vhost.c + mk_header.c + mk_config.c + mk_user.c + mk_utils.c + mk_stream.c + mk_scheduler.c + mk_http.c + mk_http_parser.c + mk_http_thread.c + mk_socket.c + mk_net.c + mk_clock.c + mk_cache.c + mk_server.c + mk_kernel.c + mk_plugin.c + ) + +if(MK_HTTP2) + set(src + ${src} + "mk_http2.c" + ) +endif() + +# Always build a static library, thats our core :) +add_library(monkey-core-static STATIC ${src}) +set_target_properties(monkey-core-static PROPERTIES OUTPUT_NAME monkey) +target_link_libraries(monkey-core-static mk_core ${CMAKE_THREAD_LIBS_INIT} ${STATIC_PLUGINS_LIBS} ${CMAKE_DL_LIBS} rbtree co) + +message(STATUS "LINKING ${STATIC_PLUGINS_LIBS}") + +if(NOT DEFINED MK_HAVE_REGEX) + target_link_libraries(monkey-core-static regex) +endif() + +# Linux Kqueue emulation +if(MK_HAVE_LINUX_KQUEUE) + target_link_libraries(monkey-core-static kqueue) +endif() + +# FreeBSD backtrace +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + target_link_libraries(monkey-core-static execinfo) +endif() + +if (CMAKE_SYSTEM_NAME MATCHES "SunOS") + target_link_libraries(monkey-core-static socket nsl) +endif() diff --git a/fluent-bit/lib/monkey/mk_server/mk_cache.c b/fluent-bit/lib/monkey/mk_server/mk_cache.c new file mode 100644 index 00000000..c678afa8 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_cache.c @@ -0,0 +1,81 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include <monkey/mk_core.h> +#include <monkey/mk_cache.h> +#include <monkey/mk_cache_tls.h> +#include <monkey/mk_config.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_tls.h> + +/* This function is called when a thread is created */ +void mk_cache_worker_init() +{ + char *cache_error; + mk_ptr_t *p_tmp; + + /* Cache header request -> last modified */ + p_tmp = mk_mem_alloc_z(sizeof(mk_ptr_t)); + p_tmp->data = mk_mem_alloc_z(32); + p_tmp->len = -1; + MK_TLS_SET(mk_tls_cache_header_lm, p_tmp); + + /* Cache header request -> content length */ + p_tmp = mk_mem_alloc_z(sizeof(mk_ptr_t)); + p_tmp->data = mk_mem_alloc_z(MK_UTILS_INT2MKP_BUFFER_LEN); + p_tmp->len = -1; + MK_TLS_SET(mk_tls_cache_header_cl, p_tmp); + + /* Cache gmtime buffer */ + MK_TLS_SET(mk_tls_cache_gmtime, mk_mem_alloc(sizeof(struct tm))); + + /* Cache the most used text representations of utime2gmt */ + MK_TLS_SET(mk_tls_cache_gmtext, + mk_mem_alloc_z(sizeof(struct mk_gmt_cache) * MK_GMT_CACHES)); + + /* Cache buffer for strerror_r(2) */ + cache_error = mk_mem_alloc(MK_UTILS_ERROR_SIZE); + pthread_setspecific(mk_utils_error_key, (void *) cache_error); +} + +void mk_cache_worker_exit() +{ + char *cache_error; + + /* Cache header request -> last modified */ + mk_ptr_free(MK_TLS_GET(mk_tls_cache_header_lm)); + mk_mem_free(MK_TLS_GET(mk_tls_cache_header_lm)); + + /* Cache header request -> content length */ + mk_ptr_free(MK_TLS_GET(mk_tls_cache_header_cl)); + mk_mem_free(MK_TLS_GET(mk_tls_cache_header_cl)); + + /* Cache gmtime buffer */ + mk_mem_free(MK_TLS_GET(mk_tls_cache_gmtime)); + + /* Cache the most used text representations of utime2gmt */ + mk_mem_free(MK_TLS_GET(mk_tls_cache_gmtext)); + + /* Cache buffer for strerror_r(2) */ + cache_error = pthread_getspecific(mk_utils_error_key); + mk_mem_free(cache_error); +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_clock.c b/fluent-bit/lib/monkey/mk_server/mk_clock.c new file mode 100644 index 00000000..3a45512f --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_clock.c @@ -0,0 +1,171 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + +#include <mk_core/mk_pthread.h> +#include <mk_core/mk_unistd.h> + +#include <monkey/mk_core.h> +#include <monkey/mk_config.h> +#include <monkey/mk_clock.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_tls.h> + +#ifdef _WIN32 +static struct tm* localtime_r(const time_t* timep, struct tm* result) +{ + localtime_s(result, timep); + + return result; +} + +static struct tm* gmtime_r(const time_t* timep, struct tm* result) +{ + gmtime_s(result, timep); + + return result; +} +#endif + + +/* + * The mk_ptr_ts have two buffers for avoid in half-way access from + * another thread while a buffer is being modified. The function below returns + * one of two buffers to work with. + */ +static inline char *_next_buffer(mk_ptr_t *pointer, char **buffers) +{ + if (pointer->data == buffers[0]) { + return buffers[1]; + } + else { + return buffers[0]; + } +} + +static void mk_clock_log_set_time(time_t utime, struct mk_server *server) +{ + char *time_string; + struct tm result; + + time_string = _next_buffer(&server->clock_context->log_current_time, server->clock_context->log_time_buffers); + server->clock_context->log_current_utime = utime; + + strftime(time_string, LOG_TIME_BUFFER_SIZE, "[%d/%b/%G %T %z]", + localtime_r(&utime, &result)); + + server->clock_context->log_current_time.data = time_string; +} + +static void mk_clock_headers_preset(time_t utime, struct mk_server *server) +{ + int len1; + int len2; + struct tm *gmt_tm; + struct tm result; + char *buffer; + + buffer = _next_buffer(&server->clock_context->headers_preset, + server->clock_context->header_time_buffers); + + gmt_tm = gmtime_r(&utime, &result); + + len1 = snprintf(buffer, + HEADER_TIME_BUFFER_SIZE, + "%s", + server->server_signature_header); + + len2 = strftime(buffer + len1, + HEADER_PRESET_SIZE - len1, + MK_CLOCK_GMT_DATEFORMAT, + gmt_tm); + + server->clock_context->headers_preset.data = buffer; + server->clock_context->headers_preset.len = len1 + len2; +} + +void *mk_clock_worker_init(void *data) +{ + time_t cur_time; + struct mk_server *server = data; + + mk_utils_worker_rename("monkey: clock"); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); + + server->clock_context->mk_clock_tid = pthread_self(); + + while (1) { + cur_time = time(NULL); + + if(cur_time != ((time_t)-1)) { + mk_clock_log_set_time(cur_time, server); + mk_clock_headers_preset(cur_time, server); + } + sleep(1); + } + + return NULL; +} + +void mk_clock_exit(struct mk_server *server) +{ + pthread_cancel(server->clock_context->mk_clock_tid); + pthread_join(server->clock_context->mk_clock_tid, NULL); + + mk_mem_free(server->clock_context->header_time_buffers[0]); + mk_mem_free(server->clock_context->header_time_buffers[1]); + mk_mem_free(server->clock_context->log_time_buffers[0]); + mk_mem_free(server->clock_context->log_time_buffers[1]); + + mk_mem_free(server->clock_context); +} + +/* This function must be called before any threads are created */ +void mk_clock_sequential_init(struct mk_server *server) +{ + server->clock_context = mk_mem_alloc_z(sizeof(struct mk_clock_context)); + + if (server->clock_context == NULL) { + return; + } + + /* Time when monkey was started */ + server->clock_context->monkey_init_time = time(NULL); + + server->clock_context->log_current_time.len = LOG_TIME_BUFFER_SIZE - 2; + server->clock_context->headers_preset.len = HEADER_PRESET_SIZE - 1; + + server->clock_context->header_time_buffers[0] = mk_mem_alloc_z(HEADER_PRESET_SIZE); + server->clock_context->header_time_buffers[1] = mk_mem_alloc_z(HEADER_PRESET_SIZE); + + server->clock_context->log_time_buffers[0] = mk_mem_alloc_z(LOG_TIME_BUFFER_SIZE); + server->clock_context->log_time_buffers[1] = mk_mem_alloc_z(LOG_TIME_BUFFER_SIZE); + + /* Set the time once */ + time_t cur_time = time(NULL); + + if (cur_time != ((time_t)-1)) { + mk_clock_log_set_time(cur_time, server); + mk_clock_headers_preset(cur_time, server); + } +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_config.c b/fluent-bit/lib/monkey/mk_server/mk_config.c new file mode 100644 index 00000000..f8dc9c00 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_config.c @@ -0,0 +1,636 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/monkey.h> +#include <monkey/mk_kernel.h> +#include <monkey/mk_config.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_mimetype.h> +#include <monkey/mk_info.h> +#include <monkey/mk_core.h> +#include <monkey/mk_server.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_mimetype.h> +#include <monkey/mk_info.h> + +#include <ctype.h> +#include <limits.h> +#include <mk_core/mk_dirent.h> +#include <sys/stat.h> + +struct mk_server_config *mk_config; + +static int mk_config_key_have(struct mk_list *list, const char *value) +{ + struct mk_list *head; + struct mk_string_line *entry; + + mk_list_foreach(head, list) { + entry = mk_list_entry(head, struct mk_string_line, _head); + if (strcasecmp(entry->val, value) == 0) { + return MK_TRUE; + } + } + return MK_FALSE; +} + +void mk_config_listeners_free(struct mk_server *server) +{ + struct mk_list *tmp; + struct mk_list *head; + struct mk_config_listener *l; + + mk_list_foreach_safe(head, tmp, &server->listeners) { + l = mk_list_entry(head, struct mk_config_listener, _head); + mk_list_del(&l->_head); + mk_mem_free(l->address); + mk_mem_free(l->port); + mk_mem_free(l); + } +} + +void mk_config_free_all(struct mk_server *server) +{ + mk_vhost_free_all(server); + mk_mimetype_free_all(server); + + if (server->config) { + mk_rconf_free(server->config); + } + + if (server->path_conf_root) { + mk_mem_free(server->path_conf_root); + } + + if (server->path_conf_pidfile) { + mk_mem_free(server->path_conf_pidfile); + } + + if (server->conf_user_pub) { + mk_mem_free(server->conf_user_pub); + } + + /* free config->index_files */ + if (server->index_files) { + mk_string_split_free(server->index_files); + } + + if (server->user) { + mk_mem_free(server->user); + } + + if (server->transport_layer) { + mk_mem_free(server->transport_layer); + } + + mk_config_listeners_free(server); + + mk_ptr_free(&server->server_software); + mk_mem_free(server); +} + +/* Print a specific error */ +static void mk_config_print_error_msg(char *variable, char *path) +{ + mk_err("[config] %s at %s has an invalid value", + variable, path); + mk_mem_free(path); + exit(EXIT_FAILURE); +} + +/* + * Check if at least one of the Listen interfaces are being used by another + * process. + */ +int mk_config_listen_check_busy(struct mk_server *server) +{ + int fd; + struct mk_list *head; + struct mk_plugin *p; + struct mk_config_listener *listen; + + p = mk_plugin_cap(MK_CAP_SOCK_PLAIN, server); + if (!p) { + mk_warn("Listen check: consider build monkey with basic socket handling!"); + return MK_FALSE; + } + + mk_list_foreach(head, &server->listeners) { + listen = mk_list_entry(head, struct mk_config_listener, _head); + + fd = mk_socket_connect(listen->address, atol(listen->port), MK_FALSE); + if (fd != -1) { + close(fd); + return MK_TRUE; + } + } + + return MK_FALSE; +} + +int mk_config_listen_parse(char *value, struct mk_server *server) +{ + int ret = -1; + int flags = 0; + long port_num; + char *address = NULL; + char *port = NULL; + char *divider; + struct mk_list *list = NULL; + struct mk_string_line *listener; + + list = mk_string_split_line(value); + if (!list) { + goto error; + } + + if (mk_list_is_empty(list) == 0) { + goto error; + } + + /* Parse the listener interface */ + listener = mk_list_entry_first(list, struct mk_string_line, _head); + if (listener->val[0] == '[') { + /* IPv6 address */ + divider = strchr(listener->val, ']'); + if (divider == NULL) { + mk_err("[config] Expected closing ']' in IPv6 address."); + goto error; + } + if (divider[1] != ':' || divider[2] == '\0') { + mk_err("[config] Expected ':port' after IPv6 address."); + goto error; + } + + address = mk_string_copy_substr(listener->val + 1, 0, + divider - listener->val - 1); + port = mk_string_dup(divider + 2); + } + else if (strchr(listener->val, ':') != NULL) { + /* IPv4 address */ + divider = strrchr(listener->val, ':'); + if (divider == NULL || divider[1] == '\0') { + mk_err("[config] Expected ':port' after IPv4 address."); + goto error; + } + + address = mk_string_copy_substr(listener->val, 0, + divider - listener->val); + port = mk_string_dup(divider + 1); + } + else { + /* Port only */ + address = NULL; + port = mk_string_dup(listener->val); + } + + errno = 0; + port_num = strtol(port, NULL, 10); + if (errno != 0 || port_num == LONG_MAX || port_num == LONG_MIN) { + mk_warn("Using defaults, could not understand \"Listen %s\"", + listener->val); + port = NULL; + } + + /* Check extra properties of the listener */ + flags = MK_CAP_HTTP; + if (mk_config_key_have(list, "!http")) { + flags |= ~MK_CAP_HTTP; + } + +#ifdef MK_HAVE_HTTP2 + if (mk_config_key_have(list, "h2")) { + flags |= (MK_CAP_HTTP2 | MK_CAP_SOCK_TLS); + } + + if (mk_config_key_have(list, "h2c")) { + flags |= MK_CAP_HTTP2; + } +#endif + + if (mk_config_key_have(list, "tls")) { + flags |= MK_CAP_SOCK_TLS; + } + + /* register the new listener */ + mk_config_listener_add(address, port, flags, server); + mk_string_split_free(list); + list = NULL; + ret = 0; + +error: + if (address) { + mk_mem_free(address); + } + if (port) { + mk_mem_free(port); + } + if (list) { + mk_string_split_free(list); + } + + return ret; +} + +static int mk_config_listen_read(struct mk_rconf_section *section, + struct mk_server *server) +{ + int ret; + struct mk_list *cur; + struct mk_rconf_entry *entry; + + mk_list_foreach(cur, §ion->entries) { + entry = mk_list_entry(cur, struct mk_rconf_entry, _head); + if (strcasecmp(entry->key, "Listen")) { + continue; + } + + ret = mk_config_listen_parse(entry->val, server); + if (ret != 0) { + return -1; + } + } + + return 0; +} + +/* Read configuration files */ +static int mk_config_read_files(char *path_conf, char *file_conf, + struct mk_server *server) +{ + unsigned long len; + char *tmp = NULL; + struct stat checkdir; + struct mk_rconf *cnf; + struct mk_rconf_section *section; + + if (!path_conf) { + return -1; + } + + if (!file_conf) { + file_conf = "monkey.conf"; + } + + server->path_conf_root = mk_string_dup(path_conf); + + if (stat(server->path_conf_root, &checkdir) == -1) { + mk_err("ERROR: Cannot find/open '%s'", server->path_conf_root); + return -1; + } + + mk_string_build(&tmp, &len, "%s/%s", path_conf, file_conf); + cnf = mk_rconf_open(tmp); + if (!cnf) { + mk_mem_free(tmp); + mk_err("Cannot read '%s'", server->conf_main); + return -1; + } + section = mk_rconf_section_get(cnf, "SERVER"); + if (!section) { + mk_err("ERROR: No 'SERVER' section defined"); + return -1; + } + + /* Map source configuration */ + server->config = cnf; + + /* Listen */ + if (!server->port_override) { + /* Process each Listen entry */ + if (mk_config_listen_read(section, server)) { + mk_err("[config] Failed to read listen sections."); + } + if (mk_list_is_empty(&server->listeners) == 0) { + mk_warn("[config] No valid Listen entries found, set default"); + mk_config_listener_add(NULL, NULL, MK_CAP_HTTP, server); + } + } + else { + mk_config_listener_add(NULL, server->port_override, + MK_CAP_HTTP, server); + } + + /* Number of thread workers */ + if (server->workers == -1) { + server->workers = (size_t) mk_rconf_section_get_key(section, + "Workers", + MK_RCONF_NUM); + } + + if (server->workers < 1) { + server->workers = mk_utils_get_system_core_count(); + + if (server->workers < 1) { + mk_config_print_error_msg("Workers", tmp); + } + } + + /* Timeout */ + server->timeout = (size_t) mk_rconf_section_get_key(section, + "Timeout", MK_RCONF_NUM); + if (server->timeout < 1) { + mk_config_print_error_msg("Timeout", tmp); + } + + /* KeepAlive */ + server->keep_alive = (size_t) mk_rconf_section_get_key(section, + "KeepAlive", + MK_RCONF_BOOL); + if (server->keep_alive == MK_ERROR) { + mk_config_print_error_msg("KeepAlive", tmp); + } + + /* MaxKeepAliveRequest */ + server->max_keep_alive_request = (size_t) + mk_rconf_section_get_key(section, + "MaxKeepAliveRequest", + MK_RCONF_NUM); + + if (server->max_keep_alive_request == 0) { + mk_config_print_error_msg("MaxKeepAliveRequest", tmp); + } + + /* KeepAliveTimeout */ + server->keep_alive_timeout = (size_t) mk_rconf_section_get_key(section, + "KeepAliveTimeout", + MK_RCONF_NUM); + if (server->keep_alive_timeout == 0) { + mk_config_print_error_msg("KeepAliveTimeout", tmp); + } + + /* Pid File */ + if (!server->path_conf_pidfile) { + server->path_conf_pidfile = mk_rconf_section_get_key(section, + "PidFile", + MK_RCONF_STR); + } + + /* Home user's directory /~ */ + server->conf_user_pub = mk_rconf_section_get_key(section, + "UserDir", + MK_RCONF_STR); + + /* Index files */ + server->index_files = mk_rconf_section_get_key(section, + "Indexfile", MK_RCONF_LIST); + + /* HideVersion Variable */ + server->hideversion = (size_t) mk_rconf_section_get_key(section, + "HideVersion", + MK_RCONF_BOOL); + if (server->hideversion == MK_ERROR) { + mk_config_print_error_msg("HideVersion", tmp); + } + + /* User Variable */ + server->user = mk_rconf_section_get_key(section, "User", MK_RCONF_STR); + + /* Resume */ + server->resume = (size_t) mk_rconf_section_get_key(section, + "Resume", MK_RCONF_BOOL); + if (server->resume == MK_ERROR) { + mk_config_print_error_msg("Resume", tmp); + } + + /* Max Request Size */ + server->max_request_size = (size_t) mk_rconf_section_get_key(section, + "MaxRequestSize", + MK_RCONF_NUM); + if (server->max_request_size <= 0) { + mk_config_print_error_msg("MaxRequestSize", tmp); + } + else { + server->max_request_size *= 1024; + } + + /* Symbolic Links */ + server->symlink = (size_t) mk_rconf_section_get_key(section, + "SymLink", MK_RCONF_BOOL); + if (server->symlink == MK_ERROR) { + mk_config_print_error_msg("SymLink", tmp); + } + + /* Transport Layer plugin */ + if (!server->transport_layer) { + server->transport_layer = mk_rconf_section_get_key(section, + "TransportLayer", + MK_RCONF_STR); + } + + /* Default Mimetype */ + mk_mem_free(tmp); + tmp = mk_rconf_section_get_key(section, "DefaultMimeType", MK_RCONF_STR); + if (tmp) { + mk_string_build(&server->mimetype_default_str, &len, "%s\r\n", tmp); + } + + /* File Descriptor Table (FDT) */ + server->fdt = (size_t) mk_rconf_section_get_key(section, + "FDT", + MK_RCONF_BOOL); + + /* FIXME: Overcapacity not ready */ + server->fd_limit = (size_t) mk_rconf_section_get_key(section, + "FDLimit", + MK_RCONF_NUM); + /* Get each worker clients capacity based on FDs system limits */ + server->server_capacity = mk_server_capacity(server); + + + if (!server->one_shot) { + mk_vhost_init(path_conf, server); + } + else { + mk_vhost_set_single(server->one_shot, server); + } + + mk_mem_free(tmp); + return 0; +} + +void mk_config_signature(struct mk_server *server) +{ + unsigned long len; + + /* Server Signature */ + if (server->hideversion == MK_FALSE) { + snprintf(server->server_signature, + sizeof(server->server_signature) - 1, + "Monkey/%s", MK_VERSION_STR); + } + else { + snprintf(server->server_signature, + sizeof(server->server_signature) - 1, + "Monkey"); + } + len = snprintf(server->server_signature_header, + sizeof(server->server_signature_header) - 1, + "Server: %s\r\n", server->server_signature); + server->server_signature_header_len = len; +} + +/* read main configuration from monkey.conf */ +void mk_config_start_configure(struct mk_server *server) +{ + int ret; + unsigned long len; + + ret = mk_config_read_files(server->path_conf_root, + server->conf_main, server); + if (ret != 0) { + return; + } + + /* Load mimes */ + mk_mimetype_read_config(server); + + mk_ptr_reset(&server->server_software); + + /* Basic server information */ + if (server->hideversion == MK_FALSE) { + mk_string_build(&server->server_software.data, + &len, "Monkey/%s (%s)", MK_VERSION_STR, MK_BUILD_OS); + server->server_software.len = len; + } + else { + mk_string_build(&server->server_software.data, &len, "Monkey Server"); + server->server_software.len = len; + } +} + +/* Register a new listener into the main configuration */ +struct mk_config_listener *mk_config_listener_add(char *address, + char *port, int flags, + struct mk_server *server) +{ + struct mk_list *head; + struct mk_config_listener *check; + struct mk_config_listener *listen = NULL; + + listen = mk_mem_alloc(sizeof(struct mk_config_listener)); + if (!listen) { + mk_err("[listen_add] malloc() failed"); + return NULL; + } + + if (!address) { + listen->address = mk_string_dup(MK_DEFAULT_LISTEN_ADDR); + } + else { + listen->address = mk_string_dup(address); + } + + /* Set the port */ + if (!port) { + mk_err("[listen_add] TCP port not defined"); + exit(EXIT_FAILURE); + } + + listen->port = mk_string_dup(port); + listen->flags = flags; + + /* Before to add a new listener, lets make sure it's not a duplicated */ + mk_list_foreach(head, &server->listeners) { + check = mk_list_entry(head, struct mk_config_listener, _head); + if (strcmp(listen->address, check->address) == 0 && + strcmp(listen->port, check->port) == 0) { + mk_warn("Listener: duplicated %s:%s, skip.", + listen->address, listen->port); + + /* free resources */ + mk_mem_free(listen->address); + mk_mem_free(listen->port); + mk_mem_free(listen); + return NULL; + } + } + + mk_list_add(&listen->_head, &server->listeners); + return listen; +} + +void mk_config_set_init_values(struct mk_server *server) +{ + /* Init values */ + server->is_seteuid = MK_FALSE; + server->timeout = 15; + server->hideversion = MK_FALSE; + server->keep_alive = MK_TRUE; + server->keep_alive_timeout = 15; + server->max_keep_alive_request = 50; + server->resume = MK_TRUE; + server->standard_port = 80; + server->symlink = MK_FALSE; + server->nhosts = 0; + mk_list_init(&server->hosts); + server->user = NULL; + server->open_flags = O_RDONLY; /* The only place this is effectively used (other than the sanity check) + * is mk_http.c where it's used to test for file existence and the fd is apparently leaked */ + server->index_files = NULL; + server->conf_user_pub = NULL; + server->workers = 1; + + /* TCP REUSEPORT: available on Linux >= 3.9 */ + if (server->scheduler_mode == -1) { + if (server->kernel_features & MK_KERNEL_SO_REUSEPORT) { + server->scheduler_mode = MK_SCHEDULER_REUSEPORT; + } + else { + server->scheduler_mode = MK_SCHEDULER_FAIR_BALANCING; + } + } + + /* Max request buffer size allowed + * right now, every chunk size is 4KB (4096 bytes), + * so we are setting a maximum request size to 32 KB */ + server->max_request_size = MK_REQUEST_CHUNK * 8; + + /* Internals */ + server->safe_event_write = MK_FALSE; + + /* Init plugin list */ + mk_list_init(&server->plugins); + + /* Init listeners */ + mk_list_init(&server->listeners); +} + +void mk_config_sanity_check(struct mk_server *server) +{ + /* Check O_NOATIME for current user, flag will just be used + * if running user is allowed to. + */ + int fd; + int flags; + + if (!server->path_conf_root) { + return; + } + + flags = server->open_flags; + flags |= O_NOATIME; + fd = open(server->path_conf_root, flags); + + if (fd > -1) { + server->open_flags = flags; + close(fd); + } +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_fifo.c b/fluent-bit/lib/monkey/mk_server/mk_fifo.c new file mode 100644 index 00000000..fd148db7 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_fifo.c @@ -0,0 +1,463 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/mk_fifo.h> +#include <monkey/mk_scheduler.h> + +#ifdef _WIN32 +#include <event.h> +#endif + +static struct mk_fifo_worker *mk_fifo_worker_create(struct mk_fifo *ctx, + void *data) +{ + int id; + int ret; + struct mk_fifo_worker *fw; + + /* Get an ID */ + id = mk_list_size(&ctx->workers); + + fw = mk_mem_alloc_z(sizeof(struct mk_fifo_worker)); + if (!fw) { + perror("malloc"); + return NULL; + } + MK_EVENT_NEW(&fw->event); + + fw->worker_id = id; + fw->data = data; + fw->fifo = ctx; + + fw->buf_data = mk_mem_alloc(MK_FIFO_BUF_SIZE); + if (!fw->buf_data) { + perror("malloc"); + mk_mem_free(fw); + return NULL; + } + fw->buf_len = 0; + fw->buf_size = MK_FIFO_BUF_SIZE; + +#ifdef _WIN32 + ret = evutil_socketpair(AF_INET, SOCK_STREAM, 0, fw->channel); + if (ret == -1) { + perror("socketpair"); + mk_mem_free(fw); + return NULL; + } +#else + ret = pipe(fw->channel); + if (ret == -1) { + perror("pipe"); + mk_mem_free(fw); + return NULL; + } +#endif + + mk_list_add(&fw->_head, &ctx->workers); + return fw; +} + +/* + * Function used as a callback triggered by mk_worker_callback() or + * through a mk_sched_worker_cb_add(). It purpose is to prepare the + * channels on the final worker thread so it can consume pushed + * messages. + */ +void mk_fifo_worker_setup(void *data) +{ + struct mk_fifo_worker *mw = NULL; + struct mk_fifo *ctx = data; + + pthread_mutex_lock(&ctx->mutex_init); + + mw = mk_fifo_worker_create(ctx, data); + if (!mw) { + mk_err("[msg] error configuring msg-worker context "); + pthread_mutex_unlock(&ctx->mutex_init); + return; + } + + /* Make the current worker context available */ + pthread_setspecific(*ctx->key, mw); + pthread_mutex_unlock(&ctx->mutex_init); +} + +struct mk_fifo *mk_fifo_create(pthread_key_t *key, void *data) +{ + struct mk_fifo *ctx; + + ctx = mk_mem_alloc(sizeof(struct mk_fifo)); + if (!ctx) { + perror("malloc"); + return NULL; + } + ctx->data = data; + + /* Lists */ + mk_list_init(&ctx->queues); + mk_list_init(&ctx->workers); + + + /* Pthread specifics */ + + /* We need to isolate this because there is a key that's shared between monkey + * instances by design. + */ + if (key != NULL) { + ctx->key = key; + pthread_key_create(ctx->key, NULL); + } + + pthread_mutex_init(&ctx->mutex_init, NULL); + + return ctx; +} + +int mk_fifo_queue_create(struct mk_fifo *ctx, char *name, + void (*cb)(struct mk_fifo_queue *, void *, + size_t, void *), + void *data) + +{ + int id = -1; + int len; + struct mk_list *head; + struct mk_fifo_queue *q; + + /* Get ID for the new queue */ + if (mk_list_is_empty(&ctx->queues) == 0) { + id = 0; + } + else { + q = mk_list_entry_last(&ctx->queues, struct mk_fifo_queue, _head); + id = q->id + 1; + } + + /* queue name might need to be truncated if is too long */ + len = strlen(name); + if (len > (int) sizeof(q->name) - 1) { + len = sizeof(q->name) - 1; + } + + /* Validate that name is not a duplicated */ + mk_list_foreach(head, &ctx->queues) { + q = mk_list_entry(head, struct mk_fifo_queue, _head); + if (strlen(q->name) != (unsigned int) len) { + continue; + } + + if (strncmp(q->name, name, len) == 0) { + return -1; + } + } + + /* Allocate and register queue */ + q = mk_mem_alloc(sizeof(struct mk_fifo_queue)); + if (!q) { + perror("malloc"); + return -1; + } + q->id = id; + q->cb_message = cb; + q->data = data; + + strncpy(q->name, name, len); + q->name[len] = '\0'; + mk_list_add(&q->_head, &ctx->queues); + + return id; +} + +struct mk_fifo_queue *mk_fifo_queue_get(struct mk_fifo *ctx, int id) +{ + struct mk_list *head; + struct mk_fifo_queue *q = NULL; + + mk_list_foreach(head, &ctx->queues) { + q = mk_list_entry(head, struct mk_fifo_queue, _head); + if (q->id == id) { + return q; + } + } + + return NULL; +} + +int mk_fifo_queue_destroy(struct mk_fifo *ctx, struct mk_fifo_queue *q) +{ + (void) ctx; + + mk_list_del(&q->_head); + mk_mem_free(q); + return 0; +} + +int mk_fifo_queue_id_destroy(struct mk_fifo *ctx, int id) +{ + struct mk_fifo_queue *q; + + q = mk_fifo_queue_get(ctx, id); + if (!q) { + return -1; + } + + mk_fifo_queue_destroy(ctx, q); + return 0; +} + +static int mk_fifo_queue_destroy_all(struct mk_fifo *ctx) +{ + int c = 0; + struct mk_list *tmp; + struct mk_list *head; + struct mk_fifo_queue *q; + + mk_list_foreach_safe(head, tmp, &ctx->queues) { + q = mk_list_entry(head, struct mk_fifo_queue, _head); + mk_fifo_queue_destroy(ctx, q); + c++; + } + + return c; +} + +static int mk_fifo_worker_destroy_all(struct mk_fifo *ctx) +{ + int c = 0; + struct mk_list *tmp; + struct mk_list *head; + struct mk_fifo_worker *fw; + + mk_list_foreach_safe(head, tmp, &ctx->workers) { + fw = mk_list_entry(head, struct mk_fifo_worker, _head); + +#ifdef _WIN32 + evutil_closesocket(fw->channel[0]); + evutil_closesocket(fw->channel[1]); +#else + close(fw->channel[0]); + close(fw->channel[1]); +#endif + mk_list_del(&fw->_head); + mk_mem_free(fw->buf_data); + mk_mem_free(fw); + c++; + } + + return c; +} + +static int msg_write(int fd, void *buf, size_t count) +{ + ssize_t bytes; + size_t total = 0; + + do { +#ifdef _WIN32 + bytes = send(fd, (uint8_t *)buf + total, count - total, 0); +#else + bytes = write(fd, (uint8_t *)buf + total, count - total); +#endif + if (bytes == -1) { + if (errno == EAGAIN) { + /* + * This could happen, since this function goal is not to + * return until all data have been read, just sleep a little + * bit (0.05 seconds) + */ + +#ifdef _WIN32 + Sleep(5); +#else + usleep(50000); +#endif + continue; + } + } + else if (bytes == 0) { + /* Broken pipe ? */ + perror("write"); + return -1; + } + total += bytes; + + } while (total < count); + + return total; +} + +/* + * Push a message into a queue: this function runs from the parent thread + * so it needs to write the message to every thread pipe channel. + */ +int mk_fifo_send(struct mk_fifo *ctx, int id, void *data, size_t size) +{ + int ret; + struct mk_list *head; + struct mk_fifo_msg msg; + struct mk_fifo_queue *q; + struct mk_fifo_worker *fw; + + /* Validate queue ID */ + q = mk_fifo_queue_get(ctx, id); + if (!q) { + return -1; + } + + pthread_mutex_lock(&ctx->mutex_init); + + mk_list_foreach(head, &ctx->workers) { + fw = mk_list_entry(head, struct mk_fifo_worker, _head); + + msg.length = size; + msg.flags = 0; + msg.queue_id = (uint16_t) id; + + ret = msg_write(fw->channel[1], &msg, sizeof(struct mk_fifo_msg)); + if (ret == -1) { + pthread_mutex_unlock(&ctx->mutex_init); + perror("write"); + fprintf(stderr, "[msg] error writing message header\n"); + return -1; + } + + ret = msg_write(fw->channel[1], data, size); + if (ret == -1) { + pthread_mutex_unlock(&ctx->mutex_init); + perror("write"); + fprintf(stderr, "[msg] error writing message body\n"); + return -1; + } + } + + pthread_mutex_unlock(&ctx->mutex_init); + + return 0; +} + +static inline void consume_bytes(char *buf, int bytes, int length) +{ + memmove(buf, buf + bytes, length - bytes); +} + +static inline int fifo_drop_msg(struct mk_fifo_worker *fw) +{ + size_t drop_bytes; + struct mk_fifo_msg *msg; + + msg = (struct mk_fifo_msg *) fw->buf_data; + drop_bytes = (sizeof(struct mk_fifo_msg) + msg->length); + consume_bytes(fw->buf_data, drop_bytes, fw->buf_len); + fw->buf_len -= drop_bytes; + + return 0; +} + +static inline int fifo_is_msg_ready(struct mk_fifo_worker *fw) +{ + struct mk_fifo_msg *msg; + + msg = (struct mk_fifo_msg *) fw->buf_data; + if (fw->buf_len >= (msg->length + sizeof(struct mk_fifo_msg))) { + return MK_TRUE; + } + + return MK_FALSE; +} + +int mk_fifo_worker_read(void *event) +{ + int available; + char *tmp; + size_t size; + ssize_t bytes; + struct mk_fifo_msg *fm; + struct mk_fifo_worker *fw; + struct mk_fifo_queue *fq; + + fw = (struct mk_fifo_worker *) event; + + /* Check available space */ + available = fw->buf_size - fw->buf_len; + if (available <= 1) { + size = fw->buf_size + (MK_FIFO_BUF_SIZE / 2); + tmp = mk_mem_realloc(fw->buf_data, size); + if (!tmp) { + perror("realloc"); + return -1; + } + fw->buf_data = tmp; + fw->buf_size = size; + available = fw->buf_size - fw->buf_len; + } + + /* Read data from pipe */ +#ifdef _WIN32 + bytes = recv(fw->channel[0], fw->buf_data + fw->buf_len, available, 0); +#else + bytes = read(fw->channel[0], fw->buf_data + fw->buf_len, available); +#endif + + if (bytes == 0) { + return -1; + } + else if (bytes == -1){ + perror("read"); + return -1; + } + + fw->buf_len += bytes; + + /* Find messages and trigger callbacks */ + while (fw->buf_len > 0) { + if (fifo_is_msg_ready(fw) == MK_TRUE) { + /* we got a complete message */ + fm = (struct mk_fifo_msg *) fw->buf_data; + fq = mk_fifo_queue_get(fw->fifo, fm->queue_id); + if (!fq) { + /* Invalid queue */ + fprintf(stderr, "[fifo worker read] invalid queue id %i\n", + fm->queue_id); + fifo_drop_msg(fw); + continue; + } + + /* Trigger callback if any */ + if (fq->cb_message) { + fq->cb_message(fq, fm->data, fm->length, fq->data); + } + fifo_drop_msg(fw); + } + else { + /* msg not ready */ + break; + } + } + + return 0; +} + +int mk_fifo_destroy(struct mk_fifo *ctx) +{ + mk_fifo_queue_destroy_all(ctx); + mk_fifo_worker_destroy_all(ctx); + mk_mem_free(ctx); + return 0; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_header.c b/fluent-bit/lib/monkey/mk_server/mk_header.c new file mode 100644 index 00000000..cd8f77bd --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_header.c @@ -0,0 +1,451 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/monkey.h> +#include <monkey/mk_server.h> +#include <monkey/mk_header.h> +#include <monkey/mk_core.h> +#include <monkey/mk_http_status.h> +#include <monkey/mk_config.h> +#include <monkey/mk_socket.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_clock.h> +#include <monkey/mk_cache.h> +#include <monkey/mk_http.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_tls.h> + +#define MK_HEADER_SHORT_DATE "Date: " +#define MK_HEADER_SHORT_LOCATION "Location: " +#define MK_HEADER_SHORT_CT "Content-Type: " +#define MK_HEADER_ACCEPT_RANGES "Accept-Ranges: bytes" MK_CRLF +#define MK_HEADER_ALLOWED_METHODS "Allow: " +#define MK_HEADER_CONN_KA "Connection: Keep-Alive" MK_CRLF +#define MK_HEADER_CONN_CLOSE "Connection: Close" MK_CRLF +#define MK_HEADER_CONN_UPGRADE "Connection: Upgrade" MK_CRLF +#define MK_HEADER_CONTENT_LENGTH "Content-Length: " +#define MK_HEADER_CONTENT_ENCODING "Content-Encoding: " +#define MK_HEADER_TE_CHUNKED "Transfer-Encoding: chunked" MK_CRLF +#define MK_HEADER_LAST_MODIFIED "Last-Modified: " +#define MK_HEADER_UPGRADE_H2C "Upgrade: h2c" MK_CRLF + +const mk_ptr_t mk_header_short_date = mk_ptr_init(MK_HEADER_SHORT_DATE); +const mk_ptr_t mk_header_short_location = mk_ptr_init(MK_HEADER_SHORT_LOCATION); +const mk_ptr_t mk_header_short_ct = mk_ptr_init(MK_HEADER_SHORT_CT); +const mk_ptr_t mk_header_allow = mk_ptr_init(MK_HEADER_ALLOWED_METHODS); + +const mk_ptr_t mk_header_conn_ka = mk_ptr_init(MK_HEADER_CONN_KA); +const mk_ptr_t mk_header_conn_close = mk_ptr_init(MK_HEADER_CONN_CLOSE); +const mk_ptr_t mk_header_conn_upgrade = mk_ptr_init(MK_HEADER_CONN_UPGRADE); +const mk_ptr_t mk_header_content_length = mk_ptr_init(MK_HEADER_CONTENT_LENGTH); +const mk_ptr_t mk_header_content_encoding = mk_ptr_init(MK_HEADER_CONTENT_ENCODING); +const mk_ptr_t mk_header_accept_ranges = mk_ptr_init(MK_HEADER_ACCEPT_RANGES); +const mk_ptr_t mk_header_te_chunked = mk_ptr_init(MK_HEADER_TE_CHUNKED); +const mk_ptr_t mk_header_last_modified = mk_ptr_init(MK_HEADER_LAST_MODIFIED); +const mk_ptr_t mk_header_upgrade_h2c = mk_ptr_init(MK_HEADER_UPGRADE_H2C); + +#define status_entry(num, str) {num, sizeof(str) - 1, str} + +static const struct header_status_response status_response[] = { + + /* + * The most used first: + * + * - HTTP/1.1 200 OK + * - HTTP/1.1 404 Not Found + */ + status_entry(MK_HTTP_OK, MK_RH_HTTP_OK), + status_entry(MK_CLIENT_NOT_FOUND, MK_RH_CLIENT_NOT_FOUND), + + /* Informational */ + status_entry(MK_INFO_CONTINUE, MK_RH_INFO_CONTINUE), + status_entry(MK_INFO_SWITCH_PROTOCOL, MK_RH_INFO_SWITCH_PROTOCOL), + + /* Successful */ + status_entry(MK_HTTP_CREATED, MK_RH_HTTP_CREATED), + status_entry(MK_HTTP_ACCEPTED, MK_RH_HTTP_ACCEPTED), + status_entry(MK_HTTP_NON_AUTH_INFO, MK_RH_HTTP_NON_AUTH_INFO), + status_entry(MK_HTTP_NOCONTENT, MK_RH_HTTP_NOCONTENT), + status_entry(MK_HTTP_RESET, MK_RH_HTTP_RESET), + status_entry(MK_HTTP_PARTIAL, MK_RH_HTTP_PARTIAL), + + /* Redirections */ + status_entry(MK_REDIR_MULTIPLE, MK_RH_REDIR_MULTIPLE), + status_entry(MK_REDIR_MOVED, MK_RH_REDIR_MOVED), + status_entry(MK_REDIR_MOVED_T, MK_RH_REDIR_MOVED_T), + status_entry(MK_REDIR_SEE_OTHER, MK_RH_REDIR_SEE_OTHER), + status_entry(MK_NOT_MODIFIED, MK_RH_NOT_MODIFIED), + status_entry(MK_REDIR_USE_PROXY, MK_RH_REDIR_USE_PROXY), + + /* Client side errors */ + status_entry(MK_CLIENT_BAD_REQUEST, MK_RH_CLIENT_BAD_REQUEST), + status_entry(MK_CLIENT_UNAUTH, MK_RH_CLIENT_UNAUTH), + status_entry(MK_CLIENT_PAYMENT_REQ, MK_RH_CLIENT_PAYMENT_REQ), + status_entry(MK_CLIENT_FORBIDDEN, MK_RH_CLIENT_FORBIDDEN), + status_entry(MK_CLIENT_METHOD_NOT_ALLOWED, MK_RH_CLIENT_METHOD_NOT_ALLOWED), + status_entry(MK_CLIENT_NOT_ACCEPTABLE, MK_RH_CLIENT_NOT_ACCEPTABLE), + status_entry(MK_CLIENT_PROXY_AUTH, MK_RH_CLIENT_PROXY_AUTH), + status_entry(MK_CLIENT_REQUEST_TIMEOUT, MK_RH_CLIENT_REQUEST_TIMEOUT), + status_entry(MK_CLIENT_CONFLICT, MK_RH_CLIENT_CONFLICT), + status_entry(MK_CLIENT_GONE, MK_RH_CLIENT_GONE), + status_entry(MK_CLIENT_LENGTH_REQUIRED, MK_RH_CLIENT_LENGTH_REQUIRED), + status_entry(MK_CLIENT_PRECOND_FAILED, MK_RH_CLIENT_PRECOND_FAILED), + status_entry(MK_CLIENT_REQUEST_ENTITY_TOO_LARGE, + MK_RH_CLIENT_REQUEST_ENTITY_TOO_LARGE), + status_entry(MK_CLIENT_REQUEST_URI_TOO_LONG, + MK_RH_CLIENT_REQUEST_URI_TOO_LONG), + status_entry(MK_CLIENT_UNSUPPORTED_MEDIA, MK_RH_CLIENT_UNSUPPORTED_MEDIA), + status_entry(MK_CLIENT_REQUESTED_RANGE_NOT_SATISF, + MK_RH_CLIENT_REQUESTED_RANGE_NOT_SATISF), + + /* Server side errors */ + status_entry(MK_SERVER_INTERNAL_ERROR, MK_RH_SERVER_INTERNAL_ERROR), + status_entry(MK_SERVER_NOT_IMPLEMENTED, MK_RH_SERVER_NOT_IMPLEMENTED), + status_entry(MK_SERVER_BAD_GATEWAY, MK_RH_SERVER_BAD_GATEWAY), + status_entry(MK_SERVER_SERVICE_UNAV, MK_RH_SERVER_SERVICE_UNAV), + status_entry(MK_SERVER_GATEWAY_TIMEOUT, MK_RH_SERVER_GATEWAY_TIMEOUT), + status_entry(MK_SERVER_HTTP_VERSION_UNSUP, MK_RH_SERVER_HTTP_VERSION_UNSUP) +}; + +static const int status_response_len = + (sizeof(status_response)/(sizeof(status_response[0]))); + +static void mk_header_cb_finished(struct mk_stream_input *in) +{ + struct mk_iov *iov = in->buffer; + + mk_iov_free_marked(iov); + +#if defined(__APPLE__) + /* + * Disable TCP_CORK right away, according to: + * + * --- + * commit 81e8b869d70f9da93ddfbfb17ec7f12ce3c28fc6 + * Author: Sonny Karlsson <ksonny@lotrax.org> + * Date: Sat Oct 18 12:11:49 2014 +0200 + * + * http: Remove cork before first call to sendfile(). + * + * This removes a large delay on Mac OS X when headers and file content + * does not fill a single frame. + * Deactivating TCP_NOPUSH does not cause pending frames to be sent until + * the next write operation. + * --- + */ + + mk_server_cork_flag(in->stream->channel->fd, TCP_CORK_OFF); +#endif +} + +static void cb_stream_iov_extended_free(struct mk_stream_input *in) +{ + struct mk_iov *iov; + + iov = in->buffer; + mk_iov_free(iov); +} + +/* Send response headers */ +int mk_header_prepare(struct mk_http_session *cs, struct mk_http_request *sr, + struct mk_server *server) +{ + int i = 0; + unsigned long len = 0; + char *buffer = 0; + mk_ptr_t response; + struct response_headers *sh; + struct mk_iov *iov; + + sh = &sr->headers; + iov = &sh->headers_iov; + + /* HTTP Status Code */ + if (sh->status == MK_CUSTOM_STATUS) { + response.data = sh->custom_status.data; + response.len = sh->custom_status.len; + } + else { + for (i = 0; i < status_response_len; i++) { + if (status_response[i].status == sh->status) { + response.data = status_response[i].response; + response.len = status_response[i].length; + break; + } + } + } + + /* Invalid status set */ + mk_bug(i == status_response_len); + + mk_iov_add(iov, response.data, response.len, MK_FALSE); + + /* + * Preset headers (mk_clock.c): + * + * - Server + * - Date + */ + mk_iov_add(iov, + server->clock_context->headers_preset.data, + server->clock_context->headers_preset.len, + MK_FALSE); + + /* Last-Modified */ + if (sh->last_modified > 0) { + mk_ptr_t *lm = MK_TLS_GET(mk_tls_cache_header_lm); + lm->len = mk_utils_utime2gmt(&lm->data, sh->last_modified); + + mk_iov_add(iov, + mk_header_last_modified.data, + mk_header_last_modified.len, + MK_FALSE); + mk_iov_add(iov, + lm->data, + lm->len, + MK_FALSE); + } + + /* Connection */ + if (sh->connection == 0) { + if (cs->close_now == MK_FALSE) { + if (sr->connection.len > 0) { + if (sr->protocol != MK_HTTP_PROTOCOL_11) { + mk_iov_add(iov, + mk_header_conn_ka.data, + mk_header_conn_ka.len, + MK_FALSE); + } + } + } + else { + mk_iov_add(iov, + mk_header_conn_close.data, + mk_header_conn_close.len, + MK_FALSE); + } + } + else if (sh->connection == MK_HEADER_CONN_UPGRADED) { + mk_iov_add(iov, + mk_header_conn_upgrade.data, + mk_header_conn_upgrade.len, + MK_FALSE); + } + + /* Location */ + if (sh->location != NULL) { + mk_iov_add(iov, + mk_header_short_location.data, + mk_header_short_location.len, + MK_FALSE); + + mk_iov_add(iov, + sh->location, + strlen(sh->location), + MK_TRUE); + } + + /* allowed methods */ + if (sh->allow_methods.len > 0) { + mk_iov_add(iov, + mk_header_allow.data, + mk_header_allow.len, + MK_FALSE); + mk_iov_add(iov, + sh->allow_methods.data, + sh->allow_methods.len, + MK_FALSE); + } + + /* Content type */ + if (sh->content_type.len > 0) { + mk_iov_add(iov, + sh->content_type.data, + sh->content_type.len, + MK_FALSE); + } + + /* + * Transfer Encoding: the transfer encoding header is just sent when + * the response has some content defined by the HTTP status response + */ + switch (sh->transfer_encoding) { + case MK_HEADER_TE_TYPE_CHUNKED: + mk_iov_add(iov, + mk_header_te_chunked.data, + mk_header_te_chunked.len, + MK_FALSE); + break; + } + + /* E-Tag */ + if (sh->etag_len > 0) { + mk_iov_add(iov, sh->etag_buf, sh->etag_len, MK_FALSE); + } + + /* Content-Encoding */ + if (sh->content_encoding.len > 0) { + mk_iov_add(iov, mk_header_content_encoding.data, + mk_header_content_encoding.len, + MK_FALSE); + mk_iov_add(iov, sh->content_encoding.data, + sh->content_encoding.len, + MK_FALSE); + } + + /* Content-Length */ + if (sh->content_length >= 0 && sh->transfer_encoding != 0) { + /* Map content length to MK_POINTER */ + mk_ptr_t *cl = MK_TLS_GET(mk_tls_cache_header_cl); + mk_string_itop(sh->content_length, cl); + + /* Set headers */ + mk_iov_add(iov, + mk_header_content_length.data, + mk_header_content_length.len, + MK_FALSE); + mk_iov_add(iov, + cl->data, + cl->len, + MK_FALSE); + } + + if ((sh->content_length != 0 && (sh->ranges[0] >= 0 || sh->ranges[1] >= 0)) && + server->resume == MK_TRUE) { + buffer = 0; + + /* yyy- */ + if (sh->ranges[0] >= 0 && sh->ranges[1] == -1) { + mk_string_build(&buffer, + &len, + "%s bytes %d-%ld/%ld\r\n", + RH_CONTENT_RANGE, + sh->ranges[0], + (sh->real_length - 1), sh->real_length); + mk_iov_add(iov, buffer, len, MK_TRUE); + } + + /* yyy-xxx */ + if (sh->ranges[0] >= 0 && sh->ranges[1] >= 0) { + mk_string_build(&buffer, + &len, + "%s bytes %d-%d/%ld\r\n", + RH_CONTENT_RANGE, + sh->ranges[0], sh->ranges[1], sh->real_length); + + mk_iov_add(iov, buffer, len, MK_TRUE); + } + + /* -xxx */ + if (sh->ranges[0] == -1 && sh->ranges[1] > 0) { + mk_string_build(&buffer, + &len, + "%s bytes %ld-%ld/%ld\r\n", + RH_CONTENT_RANGE, + (sh->real_length - sh->ranges[1]), + (sh->real_length - 1), sh->real_length); + mk_iov_add(iov, buffer, len, MK_TRUE); + } + } + + if (sh->upgrade == MK_HEADER_UPGRADED_H2C) { + mk_iov_add(iov, mk_header_upgrade_h2c.data, mk_header_upgrade_h2c.len, + MK_FALSE); + } + + + if (sh->cgi == SH_NOCGI || sh->breakline == MK_HEADER_BREAKLINE) { + if (!sr->headers._extra_rows) { + mk_iov_add(iov, mk_iov_crlf.data, mk_iov_crlf.len, + MK_FALSE); + } + else { + mk_iov_add(sr->headers._extra_rows, mk_iov_crlf.data, + mk_iov_crlf.len, MK_FALSE); + } + } + + /* + * Configure the Stream to dispatch the headers + */ + + /* Set the IOV input stream */ + sr->in_headers.buffer = iov; + sr->in_headers.bytes_total = iov->total_len; + sr->in_headers.cb_finished = mk_header_cb_finished; + + if (sr->headers._extra_rows) { + /* Our main sr->stream contains the main headers (header_iov) + * and 'may' have already some linked data. If we have some + * extra headers rows we need to link this IOV right after + * the main header_iov. + */ + struct mk_stream_input *in = &sr->in_headers_extra; + in->type = MK_STREAM_IOV; + in->dynamic = MK_FALSE; + in->cb_consumed = NULL; + in->cb_finished = cb_stream_iov_extended_free; + in->stream = &sr->stream; + in->buffer = sr->headers._extra_rows; + in->bytes_total = sr->headers._extra_rows->total_len; + + mk_list_add_after(&sr->in_headers_extra._head, + &sr->in_headers._head, + &sr->stream.inputs); + } + + sh->sent = MK_TRUE; + + return 0; +} + +void mk_header_set_http_status(struct mk_http_request *sr, int status) +{ + mk_bug(!sr); + sr->headers.status = status; + + MK_TRACE("Set HTTP status = %i", status); +} + +void mk_header_response_reset(struct response_headers *header) +{ + struct mk_iov *iov; + + header->status = -1; + header->sent = MK_FALSE; + header->ranges[0] = -1; + header->ranges[1] = -1; + header->content_length = -1; + header->connection = 0; + header->transfer_encoding = -1; + header->last_modified = -1; + header->upgrade = -1; + header->cgi = SH_NOCGI; + mk_ptr_reset(&header->content_type); + mk_ptr_reset(&header->content_encoding); + header->location = NULL; + header->_extra_rows = NULL; + header->allow_methods.len = 0; + + /* Initialize headers IOV */ + iov = &header->headers_iov; + iov->io = (struct iovec *) &header->__iov_io; + iov->buf_to_free = (void *) &header->__iov_buf; + mk_iov_init(&header->headers_iov, MK_HEADER_IOV, 0); +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_http.c b/fluent-bit/lib/monkey/mk_server/mk_http.c new file mode 100644 index 00000000..1e2d219d --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_http.c @@ -0,0 +1,1638 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <sys/stat.h> +#include <fcntl.h> +//#include <regex.h> +#include <re.h> + +#include <monkey/monkey.h> +#include <monkey/mk_user.h> +#include <monkey/mk_core.h> +#include <monkey/mk_http.h> +#include <monkey/mk_http_status.h> +#include <monkey/mk_http_thread.h> +#include <monkey/mk_clock.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_config.h> +#include <monkey/mk_socket.h> +#include <monkey/mk_mimetype.h> +#include <monkey/mk_header.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_server.h> +#include <monkey/mk_plugin_stage.h> + +const mk_ptr_t mk_http_method_get_p = mk_ptr_init(MK_METHOD_GET_STR); +const mk_ptr_t mk_http_method_post_p = mk_ptr_init(MK_METHOD_POST_STR); +const mk_ptr_t mk_http_method_head_p = mk_ptr_init(MK_METHOD_HEAD_STR); +const mk_ptr_t mk_http_method_put_p = mk_ptr_init(MK_METHOD_PUT_STR); +const mk_ptr_t mk_http_method_delete_p = mk_ptr_init(MK_METHOD_DELETE_STR); +const mk_ptr_t mk_http_method_options_p = mk_ptr_init(MK_METHOD_OPTIONS_STR); +const mk_ptr_t mk_http_method_null_p = { NULL, 0 }; + +const mk_ptr_t mk_http_protocol_09_p = mk_ptr_init(MK_HTTP_PROTOCOL_09_STR); +const mk_ptr_t mk_http_protocol_10_p = mk_ptr_init(MK_HTTP_PROTOCOL_10_STR); +const mk_ptr_t mk_http_protocol_11_p = mk_ptr_init(MK_HTTP_PROTOCOL_11_STR); +const mk_ptr_t mk_http_protocol_null_p = { NULL, 0 }; + +/* Create a memory allocation in order to handle the request data */ +void mk_http_request_init(struct mk_http_session *session, + struct mk_http_request *request, + struct mk_server *server) +{ + struct mk_list *host_list = &server->hosts; + + request->port = 0; + request->status = MK_TRUE; + request->uri.data = NULL; + request->method = MK_METHOD_UNKNOWN; + request->protocol = MK_HTTP_PROTOCOL_UNKNOWN; + request->connection.len = -1; + request->file_fd = -1; + request->file_info.size = -1; + request->vhost_fdt_id = 0; + request->vhost_fdt_hash = 0; + request->vhost_fdt_enabled = MK_FALSE; + request->host.data = NULL; + request->stage30_blocked = MK_FALSE; + request->session = session; + request->host_conf = mk_list_entry_first(host_list, struct mk_vhost, _head); + request->uri_processed.data = NULL; + request->real_path.data = NULL; + request->handler_data = NULL; + + request->in_file.fd = -1; + + /* Response Headers */ + mk_header_response_reset(&request->headers); + + /* Reset callbacks for headers stream */ + mk_stream_set(&request->stream, + session->channel, + NULL, + NULL, NULL, NULL); +} + +static inline int mk_http_point_header(mk_ptr_t *h, + struct mk_http_parser *parser, int key) +{ + struct mk_http_header *header; + + header = &parser->headers[key]; + if (header->type == key) { + h->data = header->val.data; + h->len = header->val.len; + return 0; + } + else { + h->data = NULL; + h->len = -1; + } + + return -1; +} + +static int mk_http_request_prepare(struct mk_http_session *cs, + struct mk_http_request *sr, + struct mk_server *server) +{ + int ret; + int status = 0; + char *temp; + struct mk_list *hosts = &server->hosts; + struct mk_list *alias; + struct mk_http_header *header; + + /* + * Process URI, if it contains ASCII encoded strings like '%20', + * it will return a new memory buffer with the decoded string, otherwise + * it returns NULL + */ + temp = mk_utils_url_decode(sr->uri); + + if (temp) { + sr->uri_processed.data = temp; + sr->uri_processed.len = strlen(temp); + } + else { + sr->uri_processed.data = sr->uri.data; + sr->uri_processed.len = sr->uri.len; + } + + /* Always assign the default vhost' */ + sr->host_conf = mk_list_entry_first(hosts, struct mk_vhost, _head); + sr->user_home = MK_FALSE; + + /* Valid request URI? */ + if (sr->uri_processed.data[0] != '/') { + mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr, server); + return MK_EXIT_OK; + } + + /* Check if we have a Host header: Hostname ; port */ + mk_http_point_header(&sr->host, &cs->parser, MK_HEADER_HOST); + + /* Header: Connection */ + mk_http_point_header(&sr->connection, &cs->parser, MK_HEADER_CONNECTION); + + /* Header: Range */ + mk_http_point_header(&sr->range, &cs->parser, MK_HEADER_RANGE); + + /* Header: If-Modified-Since */ + mk_http_point_header(&sr->if_modified_since, + &cs->parser, + MK_HEADER_IF_MODIFIED_SINCE); + + /* HTTP/1.1 needs Host header */ + if (!sr->host.data && sr->protocol == MK_HTTP_PROTOCOL_11) { + mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr, server); + return MK_EXIT_OK; + } + + /* Should we close the session after this request ? */ + mk_http_keepalive_check(cs, sr, server); + + /* Content Length */ + header = &cs->parser.headers[MK_HEADER_CONTENT_LENGTH]; + if (header->type == MK_HEADER_CONTENT_LENGTH) { + sr->_content_length.data = header->val.data; + sr->_content_length.len = header->val.len; + } + else { + sr->_content_length.data = NULL; + } + + /* Assign the first node alias */ + alias = &sr->host_conf->server_names; + sr->host_alias = mk_list_entry_first(alias, + struct mk_vhost_alias, _head); + + if (sr->host.data) { + /* Set the given port */ + if (cs->parser.header_host_port > 0) { + sr->port = cs->parser.header_host_port; + } + + /* Match the virtual host */ + mk_vhost_get(sr->host, &sr->host_conf, &sr->host_alias, server); + + /* Check if this virtual host have some redirection */ + if (sr->host_conf->header_redirect.data) { + mk_header_set_http_status(sr, MK_REDIR_MOVED); + sr->headers.location = mk_string_dup(sr->host_conf->header_redirect.data); + sr->headers.content_length = 0; + sr->headers.location = NULL; + mk_header_prepare(cs, sr, server); + return 0; + } + } + + /* Is requesting an user home directory ? */ + if (server->conf_user_pub && + sr->uri_processed.len > 2 && + sr->uri_processed.data[1] == MK_USER_HOME) { + + if (mk_user_init(cs, sr, server) != 0) { + mk_http_error(MK_CLIENT_NOT_FOUND, cs, sr, server); + return MK_EXIT_ABORT; + } + } + + /* Plugins Stage 20 */ + ret = mk_plugin_stage_run_20(cs, sr, server); + if (ret == MK_PLUGIN_RET_CLOSE_CONX) { + MK_TRACE("STAGE 20 requested close conexion"); + return MK_EXIT_ABORT; + } + + /* Normal HTTP process */ + status = mk_http_init(cs, sr, server); + + MK_TRACE("[FD %i] HTTP Init returning %i", cs->socket, status); + return status; +} + +/* + * This function allow the core to invoke the closing connection process + * when some connection was not proceesed due to a premature close or similar + * exception, it also take care of invoke the STAGE_40 and STAGE_50 plugins events + */ +static void mk_request_premature_close(int http_status, struct mk_http_session *cs, + struct mk_server *server) +{ + struct mk_http_request *sr; + struct mk_list *sr_list = &cs->request_list; + struct mk_list *host_list = &server->hosts; + + /* + * If the connection is too premature, we need to allocate a temporal session_request + * to do not break the plugins stages + */ + if (mk_list_is_empty(sr_list) == 0) { + sr = &cs->sr_fixed; + memset(sr, 0, sizeof(struct mk_http_request)); + mk_http_request_init(cs, sr, server); + mk_list_add(&sr->_head, &cs->request_list); + } + else { + sr = mk_list_entry_first(sr_list, struct mk_http_request, _head); + } + + /* Raise error */ + if (http_status > 0) { + if (!sr->host_conf) { + sr->host_conf = mk_list_entry_first(host_list, + struct mk_vhost, _head); + } + mk_http_error(http_status, cs, sr, server); + + /* STAGE_40, request has ended */ + mk_plugin_stage_run_40(cs, sr, server); + } + + /* STAGE_50, connection closed and remove the http_session */ + mk_plugin_stage_run_50(cs->socket, server); + mk_http_session_remove(cs, server); +} + +int mk_http_handler_read(struct mk_sched_conn *conn, struct mk_http_session *cs, + struct mk_server *server) +{ + int bytes; + int max_read; + int available = 0; + int new_size; + int total_bytes = 0; + char *tmp = 0; + +#ifdef MK_HAVE_TRACE + int socket = conn->event.fd; +#endif + + MK_TRACE("MAX REQUEST SIZE: %i", server->max_request_size); + + try_pending: + + available = cs->body_size - cs->body_length; + if (available <= 0) { + /* Reallocate buffer size if pending data does not have space */ + new_size = cs->body_size + conn->net->buffer_size; + if (new_size > server->max_request_size) { + MK_TRACE("Requested size is > mk_config->max_request_size"); + mk_request_premature_close(MK_CLIENT_REQUEST_ENTITY_TOO_LARGE, cs, + server); + return -1; + } + + /* + * Check if the body field still points to the initial body_fixed, if so, + * allow the new space required in body, otherwise perform a realloc over + * body. + */ + if (cs->body == cs->body_fixed) { + cs->body = mk_mem_alloc(new_size + 1); + cs->body_size = new_size; + memcpy(cs->body, cs->body_fixed, cs->body_length); + MK_TRACE("[FD %i] New size: %i, length: %i", + socket, new_size, cs->body_length); + } + else { + MK_TRACE("[FD %i] Realloc from %i to %i", + socket, cs->body_size, new_size); + tmp = mk_mem_realloc(cs->body, new_size + 1); + if (tmp) { + cs->body = tmp; + cs->body_size = new_size; + } + else { + mk_request_premature_close(MK_SERVER_INTERNAL_ERROR, cs, + server); + return -1; + } + } + } + + /* Read content */ + max_read = (cs->body_size - cs->body_length); + bytes = mk_sched_conn_read(conn, cs->body + cs->body_length, max_read); + MK_TRACE("[FD %i] read %i", socket, bytes); + + if (bytes == 0) { + MK_TRACE("[FD %i] broken pipe?", socket); + errno = 0; + return -1; + } + else if (bytes == -1) { + return -1; + } + + if (bytes > max_read) { + MK_TRACE("[FD %i] Buffer still have data: %i", + socket, bytes - max_read); + cs->body_length += max_read; + cs->body[cs->body_length] = '\0'; + total_bytes += max_read; + + goto try_pending; + } + else { + cs->body_length += bytes; + cs->body[cs->body_length] = '\0'; + + total_bytes += bytes; + } + + MK_TRACE("[FD %i] Retry total bytes: %i", socket, total_bytes); + return total_bytes; +} + +/* Build error page */ +static int mk_http_error_page(char *title, mk_ptr_t *message, char *signature, + char **out_buf, unsigned long *out_size) +{ + char *temp; + + *out_buf = NULL; + + if (message) { + temp = mk_ptr_to_buf(*message); + } + else { + temp = mk_string_dup(""); + } + + mk_string_build(out_buf, out_size, + MK_REQUEST_DEFAULT_PAGE, title, temp, signature); + mk_mem_free(temp); + return 0; +} + +static int mk_http_range_set(struct mk_http_request *sr, size_t file_size, + struct mk_server *server) +{ + struct response_headers *sh = &sr->headers; + struct mk_stream_input *in; + + in = &sr->in_file; + in->bytes_total = file_size; + in->bytes_offset = 0; + + if (server->resume == MK_TRUE && sr->range.data) { + /* yyy- */ + if (sh->ranges[0] >= 0 && sh->ranges[1] == -1) { + in->bytes_offset = sh->ranges[0]; + in->bytes_total = file_size - in->bytes_offset; + } + + /* yyy-xxx */ + if (sh->ranges[0] >= 0 && sh->ranges[1] >= 0) { + in->bytes_offset = sh->ranges[0]; + in->bytes_total = labs(sh->ranges[1] - sh->ranges[0]) + 1; + } + + /* -xxx */ + if (sh->ranges[0] == -1 && sh->ranges[1] > 0) { + in->bytes_total = sh->ranges[1]; + in->bytes_offset = file_size - sh->ranges[1]; + } + + if ((size_t) in->bytes_offset >= file_size || + in->bytes_total > file_size) { + return -1; + } + + lseek(in->fd, in->bytes_offset, SEEK_SET); + } + return 0; +} + +static int mk_http_range_parse(struct mk_http_request *sr) +{ + int eq_pos, sep_pos, len; + char *buffer = 0; + struct response_headers *sh; + + if (!sr->range.data) + return -1; + + if ((eq_pos = mk_string_char_search(sr->range.data, '=', sr->range.len)) < 0) + return -1; + + if (strncasecmp(sr->range.data, "Bytes", eq_pos) != 0) + return -1; + + if ((sep_pos = mk_string_char_search(sr->range.data, '-', sr->range.len)) < 0) + return -1; + + len = sr->range.len; + sh = &sr->headers; + + /* =-xxx */ + if (eq_pos + 1 == sep_pos) { + sh->ranges[0] = -1; + sh->ranges[1] = (unsigned long) atol(sr->range.data + sep_pos + 1); + + if (sh->ranges[1] <= 0) { + return -1; + } + + sh->content_length = sh->ranges[1]; + return 0; + } + + /* =yyy-xxx */ + if ((eq_pos + 1 != sep_pos) && (len > sep_pos + 1)) { + buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, sep_pos); + sh->ranges[0] = (unsigned long) atol(buffer); + mk_mem_free(buffer); + + buffer = mk_string_copy_substr(sr->range.data, sep_pos + 1, len); + sh->ranges[1] = (unsigned long) atol(buffer); + mk_mem_free(buffer); + + if (sh->ranges[1] < 0 || (sh->ranges[0] > sh->ranges[1])) { + return -1; + } + + sh->content_length = abs(sh->ranges[1] - sh->ranges[0]) + 1; + return 0; + } + /* =yyy- */ + if ((eq_pos + 1 != sep_pos) && (len == sep_pos + 1)) { + buffer = mk_string_copy_substr(sr->range.data, eq_pos + 1, len); + sr->headers.ranges[0] = (unsigned long) atol(buffer); + mk_mem_free(buffer); + + sh->content_length = (sh->content_length - sh->ranges[0]); + return 0; + } + + return -1; +} + +static int mk_http_directory_redirect_check(struct mk_http_session *cs, + struct mk_http_request *sr, + struct mk_server *server) +{ + int port_redirect = 0; + char *host; + char *location = 0; + char *real_location = 0; + char *protocol = "http"; + unsigned long len; + + /* + * We have to check if there is a slash at the end of + * this string. If it doesn't exist, we send a redirection header. + */ + if (sr->uri_processed.data[sr->uri_processed.len - 1] == '/') { + return 0; + } + + host = mk_ptr_to_buf(sr->host); + + /* + * Add ending slash to the location string + */ + location = mk_mem_alloc(sr->uri_processed.len + 2); + memcpy(location, sr->uri_processed.data, sr->uri_processed.len); + location[sr->uri_processed.len] = '/'; + location[sr->uri_processed.len + 1] = '\0'; + + /* FIXME: should we done something similar for SSL = 443 */ + if (sr->host.data && sr->port > 0) { + if (sr->port != server->standard_port) { + port_redirect = sr->port; + } + } + + if (MK_SCHED_CONN_PROP(cs->conn) & MK_CAP_SOCK_TLS) { + protocol = "https"; + } + + if (port_redirect > 0) { + mk_string_build(&real_location, &len, "%s://%s:%i%s\r\n", + protocol, host, port_redirect, location); + } + else { + mk_string_build(&real_location, &len, "%s://%s%s\r\n", + protocol, host, location); + } + + MK_TRACE("Redirecting to '%s'", real_location); + mk_mem_free(host); + + mk_header_set_http_status(sr, MK_REDIR_MOVED); + sr->headers.content_length = 0; + + mk_ptr_reset(&sr->headers.content_type); + sr->headers.location = real_location; + sr->headers.cgi = SH_NOCGI; + sr->headers.pconnections_left = + (server->max_keep_alive_request - cs->counter_connections); + + mk_header_prepare(cs, sr, server); + + /* we do not free() real_location as it's freed by iov */ + mk_mem_free(location); + sr->headers.location = NULL; + return -1; +} + +/* Look for some index.xxx in pathfile */ +static inline char *mk_http_index_lookup(mk_ptr_t *path_base, + char *buf, size_t buf_size, + size_t *out, size_t *bytes, + struct mk_server *server) +{ + off_t off = 0; + size_t len; + struct mk_string_line *entry; + struct mk_list *head; + + if (!server->index_files) { + return NULL; + } + + off = path_base->len; + memcpy(buf, path_base->data, off); + + mk_list_foreach(head, server->index_files) { + entry = mk_list_entry(head, struct mk_string_line, _head); + + len = off + entry->len + 1; + if (len >= buf_size) { + continue; + } + + memcpy(buf + off, entry->val, entry->len); + buf[off + entry->len] = '\0'; + + if (access(buf, F_OK) == 0) { + MK_TRACE("Index lookup OK '%s'", buf); + *out = off + entry->len; + *bytes = path_base->len - 1; + return buf; + } + } + + return NULL; +} + +/* Turn CORK_OFF once headers are sent */ +#if defined (__linux__) +static inline void mk_http_cb_file_on_consume(struct mk_stream_input *in, + long bytes) +{ + int ret; + (void) bytes; + + /* + * This callback is invoked just once as we want to turn off + * the TCP Cork. We do this just overriding the callback for + * the file stream. + */ + ret = mk_server_cork_flag(in->stream->channel->fd, TCP_CORK_OFF); + if (ret == -1) { + mk_warn("Could not set TCP_CORK/TCP_NOPUSH off"); + } + MK_TRACE("[FD %i] Disable TCP_CORK/TCP_NOPUSH", + in->stream->channel->fd); + in->cb_consumed = NULL; +} +#endif + +int mk_http_init(struct mk_http_session *cs, struct mk_http_request *sr, + struct mk_server *server) +{ + int ret; + int ret_file; + struct mk_mimetype *mime; + struct mk_list *head; + struct mk_list *handlers; + struct mk_plugin *plugin; + struct mk_vhost_handler *h_handler; + struct mk_http_thread *mth = NULL; + size_t index_length; + size_t index_bytes; + char *index_path = NULL; + + MK_TRACE("[FD %i] HTTP Protocol Init, session %p", cs->socket, sr); + + /* Request to root path of the virtualhost in question */ + if (sr->uri_processed.len == 1 && sr->uri_processed.data[0] == '/') { + sr->real_path.data = sr->host_conf->documentroot.data; + sr->real_path.len = sr->host_conf->documentroot.len; + } + + /* Compose real path */ + if (sr->user_home == MK_FALSE) { + int len; + + len = sr->host_conf->documentroot.len + sr->uri_processed.len; + if (len < MK_PATH_BASE) { + memcpy(sr->real_path_static, + sr->host_conf->documentroot.data, + sr->host_conf->documentroot.len); + memcpy(sr->real_path_static + sr->host_conf->documentroot.len, + sr->uri_processed.data, + sr->uri_processed.len); + sr->real_path_static[len] = '\0'; + sr->real_path.data = sr->real_path_static; + sr->real_path.len = len; + } + else { + ret = mk_buffer_cat(&sr->real_path, + sr->host_conf->documentroot.data, + sr->host_conf->documentroot.len, + sr->uri_processed.data, + sr->uri_processed.len); + + if (ret < 0) { + MK_TRACE("Error composing real path"); + return MK_EXIT_ERROR; + } + } + } + + /* Check if this is related to a protocol upgrade */ +#ifdef MK_HAVE_HTTP2 + if (cs->parser.header_connection & MK_HTTP_PARSER_CONN_UPGRADE) { + /* HTTP/2.0 upgrade ? */ + if (cs->parser.header_connection & MK_HTTP_PARSER_CONN_HTTP2_SE) { + MK_TRACE("Connection Upgrade request: HTTP/2.0"); + /* + * This is a HTTP/2.0 upgrade, we need to validate that we + * have at least the 'Upgrade' and 'HTTP2-Settings' headers. + */ + struct mk_http_header *p; + p = &cs->parser.headers[MK_HEADER_HTTP2_SETTINGS]; + if (cs->parser.header_upgrade == MK_HTTP_PARSER_UPGRADE_H2C && + p->key.data) { + /* + * Switch protocols and invoke the callback upgrade to prepare + * the new protocol internals. + */ + mk_sched_switch_protocol(cs->conn, MK_CAP_HTTP2); + return cs->conn->protocol->cb_upgrade(cs, sr, server); + } + else { + MK_TRACE("Invalid client upgrade request, skip it"); + } + } + } +#endif + + /* Check backward directory request */ + if (memmem(sr->uri_processed.data, sr->uri_processed.len, + MK_HTTP_DIRECTORY_BACKWARD, + sizeof(MK_HTTP_DIRECTORY_BACKWARD) - 1)) { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + + if (sr->_content_length.data && + (sr->method != MK_METHOD_POST && + sr->method != MK_METHOD_PUT)) { + sr->_content_length.data = NULL; + sr->_content_length.len = 0; + } + + ret_file = mk_file_get_info(sr->real_path.data, &sr->file_info, MK_FILE_READ); + + /* Manually set the headers input streams */ + sr->in_headers.type = MK_STREAM_IOV; + sr->in_headers.dynamic = MK_FALSE; + sr->in_headers.cb_consumed = NULL; + sr->in_headers.cb_finished = NULL; + sr->in_headers.stream = &sr->stream; + mk_list_add(&sr->in_headers._head, &sr->stream.inputs); + + /* Plugin Stage 30: look for handlers for this request */ + if (sr->stage30_blocked == MK_FALSE) { + sr->uri_processed.data[sr->uri_processed.len] = '\0'; + handlers = &sr->host_conf->handlers; + mk_list_foreach(head, handlers) { + h_handler = mk_list_entry(head, struct mk_vhost_handler, _head); + + if (re_matchp(h_handler->match, + sr->uri_processed.data, NULL) == -1) { + continue; + } + + if (h_handler->cb) { + /* Create coroutine/thread context */ + sr->headers.content_length = 0; + mth = mk_http_thread_create(MK_HTTP_THREAD_LIB, + h_handler, + cs, sr, + 0, NULL); + if (!mth) { + return -1; + } + + mk_http_thread_start(mth); + return MK_EXIT_OK; + } + else { + if (!h_handler->handler) { + return mk_http_error(MK_SERVER_INTERNAL_ERROR, cs, sr, + server); + } + plugin = h_handler->handler; + sr->stage30_handler = h_handler->handler; + ret = plugin->stage->stage30(plugin, cs, sr, + h_handler->n_params, + &h_handler->params); + mk_header_prepare(cs, sr, server); + } + + MK_TRACE("[FD %i] STAGE_30 returned %i", cs->socket, ret); + switch (ret) { + case MK_PLUGIN_RET_CONTINUE: + /* FIXME: PLUGINS DISABLED + if ((plugin->flags & MK_PLUGIN_THREAD) && + plugin->stage->stage30_thread) { + mth = mk_http_thread_new(MK_HTTP_THREAD_PLUGIN, + plugin, cs, sr, + h_handler->n_params, + &h_handler->params); + printf("[http thread] %p\n", mth); + mk_http_thread_resume(mth->parent); + } + */ + return MK_PLUGIN_RET_CONTINUE; + case MK_PLUGIN_RET_CLOSE_CONX: + if (sr->headers.status > 0) { + return mk_http_error(sr->headers.status, cs, sr, server); + } + else { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + case MK_PLUGIN_RET_END: + return MK_EXIT_OK; + } + } + } + + /* If there is no handler and the resource don't exists, raise a 404 */ + if (ret_file == -1) { + return mk_http_error(MK_CLIENT_NOT_FOUND, cs, sr, server); + } + + /* is it a valid directory ? */ + if (sr->file_info.is_directory == MK_TRUE) { + /* Send redirect header if end slash is not found */ + if (mk_http_directory_redirect_check(cs, sr, server) == -1) { + MK_TRACE("Directory Redirect"); + + /* Redirect has been sent */ + return -1; + } + + /* looking for an index file */ + char tmppath[MK_MAX_PATH]; + index_path = mk_http_index_lookup(&sr->real_path, + tmppath, MK_MAX_PATH, + &index_length, &index_bytes, + server); + if (index_path) { + if (sr->real_path.data != sr->real_path_static) { + mk_ptr_free(&sr->real_path); + sr->real_path.data = mk_string_dup(index_path); + } + /* If it's static and it still fits */ + else if (index_length < MK_PATH_BASE) { + memcpy(sr->real_path_static, index_path, index_length); + sr->real_path_static[index_length] = '\0'; + } + /* It was static, but didn't fit */ + else { + sr->real_path.data = mk_string_dup(index_path); + } + sr->real_path.len = index_length; + + ret = mk_file_get_info(sr->real_path.data, + &sr->file_info, MK_FILE_READ); + if (ret != 0) { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + + } + } + +#ifndef _WIN32 + /* Check symbolic link file */ + if (sr->file_info.is_link == MK_TRUE) { + if (server->symlink == MK_FALSE) { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + else { + int n; + char linked_file[MK_MAX_PATH]; + n = readlink(sr->real_path.data, linked_file, MK_MAX_PATH); + if (n < 0) { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + } + } +#endif + + /* Plugin Stage 30: look for handlers for this request */ + if (sr->stage30_blocked == MK_FALSE) { + char *uri; + + if (!index_path) { + sr->uri_processed.data[sr->uri_processed.len] = '\0'; + uri = sr->uri_processed.data; + } + else { + uri = sr->real_path.data + index_bytes; + } + + handlers = &sr->host_conf->handlers; + mk_list_foreach(head, handlers) { + h_handler = mk_list_entry(head, struct mk_vhost_handler, _head); + if (re_matchp(h_handler->match, uri, NULL) == -1) { + continue; + } + + plugin = h_handler->handler; + sr->stage30_handler = h_handler->handler; + ret = plugin->stage->stage30(plugin, cs, sr, + h_handler->n_params, + &h_handler->params); + + MK_TRACE("[FD %i] STAGE_30 returned %i", cs->socket, ret); + switch (ret) { + case MK_PLUGIN_RET_CONTINUE: + return MK_PLUGIN_RET_CONTINUE; + case MK_PLUGIN_RET_CLOSE_CONX: + if (sr->headers.status > 0) { + return mk_http_error(sr->headers.status, cs, sr, server); + } + else { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + case MK_PLUGIN_RET_END: + return MK_EXIT_OK; + } + } + } + + /* + * Monkey listens for PUT and DELETE methods in addition to GET, POST and + * HEAD, but it does not care about them, so if any plugin did not worked + * on it, Monkey will return error 501 (501 Not Implemented). + */ + if (sr->method == MK_METHOD_PUT || sr->method == MK_METHOD_DELETE) { + return mk_http_error(MK_CLIENT_METHOD_NOT_ALLOWED, cs, sr, server); + } + else if (sr->method == MK_METHOD_UNKNOWN) { + return mk_http_error(MK_SERVER_NOT_IMPLEMENTED, cs, sr, server); + } + + /* counter connections */ + sr->headers.pconnections_left = (int) + (server->max_keep_alive_request - cs->counter_connections); + + /* Set default value */ + mk_header_set_http_status(sr, MK_HTTP_OK); + sr->headers.location = NULL; + sr->headers.content_length = 0; + + /* + * For OPTIONS method, we let the plugin handle it and + * return without any content. + */ + if (sr->method == MK_METHOD_OPTIONS) { + /* FIXME: OPTIONS NOT WORKING */ + //sr->headers.allow_methods.data = MK_METHOD_AVAILABLE; + //sr->headers.allow_methods.len = strlen(MK_METHOD_AVAILABLE); + + mk_ptr_reset(&sr->headers.content_type); + mk_header_prepare(cs, sr, server); + return MK_EXIT_OK; + } + else { + mk_ptr_reset(&sr->headers.allow_methods); + } + + /* read permissions and check file */ + if (sr->file_info.read_access == MK_FALSE) { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + + /* Matching MimeType */ + mime = mk_mimetype_find(server, &sr->real_path); + if (!mime) { + mime = server->mimetype_default; + } + + if (sr->file_info.is_directory == MK_TRUE) { + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + + /* get file size */ + if (sr->file_info.size == 0) { + return mk_http_error(MK_CLIENT_NOT_FOUND, cs, sr, server); + } + + /* Configure some headers */ + sr->headers.last_modified = sr->file_info.last_modification; + sr->headers.etag_len = snprintf(sr->headers.etag_buf, + MK_HEADER_ETAG_SIZE, + "ETag: \"%x-%zx\"\r\n", + (unsigned int) sr->file_info.last_modification, + sr->file_info.size); + + if (sr->if_modified_since.data && sr->method == MK_METHOD_GET) { + time_t date_client; /* Date sent by client */ + time_t date_file_server; /* Date server file */ + + date_client = mk_utils_gmt2utime(sr->if_modified_since.data); + date_file_server = sr->file_info.last_modification; + + if (date_file_server <= date_client && + date_client > 0) { + mk_header_set_http_status(sr, MK_NOT_MODIFIED); + mk_header_prepare(cs, sr, server); + return MK_EXIT_OK; + } + } + + /* Object size for log and response headers */ + sr->headers.content_length = sr->file_info.size; + sr->headers.real_length = sr->file_info.size; + + /* Open file */ + if (mk_likely(sr->file_info.size > 0)) { + sr->file_fd = mk_vhost_open(sr, server); + if (sr->file_fd == -1) { + MK_TRACE("open() failed"); + return mk_http_error(MK_CLIENT_FORBIDDEN, cs, sr, server); + } + sr->in_file.fd = sr->file_fd; + sr->in_file.bytes_offset = 0; + sr->in_file.bytes_total = sr->file_info.size; + sr->in_file.stream = &sr->stream; + } + + /* Process methods */ + if (sr->method == MK_METHOD_GET || sr->method == MK_METHOD_HEAD) { + if (mime) { + sr->headers.content_type = mime->header_type; + } + + /* HTTP Ranges */ + if (sr->range.data != NULL && server->resume == MK_TRUE) { + if (mk_http_range_parse(sr) < 0) { + sr->headers.ranges[0] = -1; + sr->headers.ranges[1] = -1; + return mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr, server); + } + if (sr->headers.ranges[0] >= 0 || sr->headers.ranges[1] >= 0) { + mk_header_set_http_status(sr, MK_HTTP_PARTIAL); + } + + /* Calc bytes to send & offset */ + if (mk_http_range_set(sr, sr->file_info.size, server) != 0) { + sr->headers.content_length = -1; + sr->headers.ranges[0] = -1; + sr->headers.ranges[1] = -1; + return mk_http_error(MK_CLIENT_REQUESTED_RANGE_NOT_SATISF, + cs, sr, server); + } + } + } + else { + /* without content-type */ + mk_ptr_reset(&sr->headers.content_type); + } + + /* Send headers */ + mk_header_prepare(cs, sr, server); + if (mk_unlikely(sr->headers.content_length == 0)) { + return 0; + } + /* Send file content */ + if (sr->method == MK_METHOD_GET || sr->method == MK_METHOD_POST) { + /* Note: bytes and offsets are set after the Range check */ + sr->in_file.type = MK_STREAM_FILE; + mk_stream_append(&sr->in_file, &sr->stream); + } + + /* + * Enable TCP Cork for the remote socket. It will be disabled + * later by the file stream on the channel after send the first + * file bytes. + */ +#if defined(__linux__) + sr->in_file.cb_consumed = mk_http_cb_file_on_consume; +#endif + + /* + * Enable CORK/NO_PUSH + * ------------------- + * If it was compiled for Linux, it will turn the Cork off after + * send the first round of bytes from the target static file. + * + * For OSX, it sets TCP_NOPUSH off after send all HTTP headers. Refer + * to mk_header.c for more details. + */ + //mk_server_cork_flag(cs->socket, TCP_CORK_ON); + + /* Start sending data to the channel */ + return MK_EXIT_OK; +} + +/* + * Check if a connection can stay open using + * the keepalive headers vars and Monkey configuration as criteria + */ +int mk_http_keepalive_check(struct mk_http_session *cs, + struct mk_http_request *sr, + struct mk_server *server) +{ + if (server->keep_alive == MK_FALSE) { + return -1; + } + + /* Default Keepalive is off */ + if (sr->protocol == MK_HTTP_PROTOCOL_10) { + cs->close_now = MK_TRUE; + } + else if (sr->protocol == MK_HTTP_PROTOCOL_11) { + cs->close_now = MK_FALSE; + } + + if (sr->connection.data) { + if (cs->parser.header_connection == MK_HTTP_PARSER_CONN_KA && + sr->protocol == MK_HTTP_PROTOCOL_11) { + cs->close_now = MK_FALSE; + } + else if (cs->parser.header_connection == MK_HTTP_PARSER_CONN_CLOSE) { + cs->close_now = MK_TRUE; + } + } + + /* Client has reached keep-alive connections limit */ + if (cs->counter_connections >= server->max_keep_alive_request) { + cs->close_now = MK_TRUE; + return -1; + } + + return 0; +} + +static inline void mk_http_request_ka_next(struct mk_http_session *cs) +{ + cs->body_length = 0; + cs->counter_connections++; + + /* Update data for scheduler */ + cs->init_time = cs->server->clock_context->log_current_utime; + cs->status = MK_REQUEST_STATUS_INCOMPLETE; + + /* Initialize parser */ + mk_http_parser_init(&cs->parser); +} + +int mk_http_request_end(struct mk_http_session *cs, struct mk_server *server) +{ + int ret; + int status; + int len; + struct mk_http_request *sr = NULL; + + if (server->max_keep_alive_request <= cs->counter_connections) { + cs->close_now = MK_TRUE; + goto shutdown; + } + + /* Check if we have some enqueued pipeline requests */ + ret = mk_http_parser_more(&cs->parser, cs->body_length); + if (ret == MK_TRUE) { + /* Our pipeline request limit is the same that our keepalive limit */ + cs->counter_connections++; + len = (cs->body_length - cs->parser.i) -1; + memmove(cs->body, + cs->body + cs->parser.i + 1, + len); + cs->body_length = len; + + /* Prepare for next one */ + sr = mk_list_entry_first(&cs->request_list, struct mk_http_request, _head); + mk_http_request_free(sr, server); + mk_http_request_init(cs, sr, server); + mk_http_parser_init(&cs->parser); + status = mk_http_parser(sr, &cs->parser, cs->body, cs->body_length, + server); + if (status == MK_HTTP_PARSER_OK) { + ret = mk_http_request_prepare(cs, sr, server); + if (ret == MK_EXIT_ABORT) { + return -1; + } + + /* + * Return 1 means, we still have more data to send in a different + * scheduler round. + */ + return 1; + } + else if (status == MK_HTTP_PARSER_PENDING) { + return 0; + } + else if (status == MK_HTTP_PARSER_ERROR) { + cs->close_now = MK_TRUE; + } + } + + shutdown: + /* + * We need to ask to http_keepalive if this + * connection can continue working or we must + * close it. + */ + if (cs->close_now == MK_TRUE) { + MK_TRACE("[FD %i] No KeepAlive mode, remove", cs->conn->event.fd); + mk_http_session_remove(cs, server); + return -1; + } + else { + mk_http_request_free_list(cs, server); + mk_http_request_ka_next(cs); + mk_sched_conn_timeout_add(cs->conn, mk_sched_get_thread_conf()); + return 0; + } + + return -1; +} + +void cb_stream_page_finished(struct mk_stream_input *in) +{ + mk_ptr_t *page = in->buffer; + + mk_ptr_free(page); + mk_mem_free(page); +} + +/* Enqueue an error response. This function always returns MK_EXIT_OK */ +int mk_http_error(int http_status, struct mk_http_session *cs, + struct mk_http_request *sr, + struct mk_server *server) +{ + int ret, fd; + size_t count; + mk_ptr_t message; + mk_ptr_t page; + struct mk_vhost_error_page *entry; + struct mk_list *head; + struct file_info finfo; + struct mk_iov *iov; + + /* This function requires monkey to be properly initialized which is not the case + * when it's just used to parse http requests in fluent-bit so we want it to ignore + * that case and let fluent-bit handle it. + */ + if (server->workers == 0) { + return MK_EXIT_OK; + } + + mk_header_set_http_status(sr, http_status); + mk_ptr_reset(&page); + + /* + * We are nice sending error pages for clients who at least respect + * the especification + */ + if (http_status != MK_CLIENT_LENGTH_REQUIRED && + http_status != MK_CLIENT_BAD_REQUEST && + http_status != MK_CLIENT_REQUEST_ENTITY_TOO_LARGE) { + + /* Lookup a customized error page */ + mk_list_foreach(head, &sr->host_conf->error_pages) { + entry = mk_list_entry(head, struct mk_vhost_error_page, _head); + if (entry->status != http_status) { + continue; + } + + /* validate error file */ + ret = mk_file_get_info(entry->real_path, &finfo, MK_FILE_READ); + if (ret == -1) { + break; + } + + /* open file */ + fd = open(entry->real_path, server->open_flags); + if (fd == -1) { + break; + } + /* This fd seems to be leaked, we need to verify this logic */ + + /* Outgoing headers */ + sr->headers.content_length = finfo.size; + sr->headers.real_length = finfo.size; + mk_header_prepare(cs, sr, server); + + /* Stream setup */ + mk_stream_in_file(&sr->stream, &sr->in_file, sr->file_fd, + finfo.size, 0, NULL, NULL); + return MK_EXIT_OK; + } + } + + mk_ptr_reset(&message); + + switch (http_status) { + case MK_CLIENT_FORBIDDEN: + mk_http_error_page("Forbidden", + &sr->uri, + server->server_signature, + &page.data, &page.len); + break; + case MK_CLIENT_NOT_FOUND: + mk_string_build(&message.data, &message.len, + "The requested URL was not found on this server."); + mk_http_error_page("Not Found", + &message, + server->server_signature, + &page.data, &page.len); + mk_ptr_free(&message); + break; + case MK_CLIENT_REQUEST_ENTITY_TOO_LARGE: + mk_string_build(&message.data, &message.len, + "The request entity is too large."); + mk_http_error_page("Entity too large", + &message, + server->server_signature, + &page.data, &page.len); + mk_ptr_free(&message); + break; + case MK_CLIENT_METHOD_NOT_ALLOWED: + mk_http_error_page("Method Not Allowed", + &sr->uri, + server->server_signature, + &page.data, &page.len); + break; + case MK_SERVER_NOT_IMPLEMENTED: + mk_http_error_page("Method Not Implemented", + &sr->uri, + server->server_signature, + &page.data, &page.len); + break; + case MK_SERVER_INTERNAL_ERROR: + mk_http_error_page("Internal Server Error", + &sr->uri, + server->server_signature, + &page.data, &page.len); + break; + } + + if (page.len > 0 && sr->method != MK_METHOD_HEAD && sr->method != MK_METHOD_UNKNOWN) { + sr->headers.content_length = page.len; + } + else { + sr->headers.content_length = 0; + } + + sr->headers.location = NULL; + sr->headers.cgi = SH_NOCGI; + sr->headers.pconnections_left = 0; + sr->headers.last_modified = -1; + + if (!page.data) { + mk_ptr_reset(&sr->headers.content_type); + } + else { + mk_ptr_set(&sr->headers.content_type, "Content-Type: text/html\r\n"); + } + + mk_header_prepare(cs, sr, server); + if (page.data) { + if (sr->method != MK_METHOD_HEAD) { + if (sr->headers._extra_rows) { + iov = sr->headers._extra_rows; + sr->in_headers_extra.bytes_total += page.len; + } + else { + iov = &sr->headers.headers_iov; + sr->in_headers.bytes_total += page.len; + } + mk_iov_add(iov, page.data, page.len, MK_TRUE); + } + else { + mk_mem_free(page.data); + } + } + + mk_channel_write(cs->channel, &count); + mk_http_request_end(cs, server); + + return MK_EXIT_OK; +} + +/* + * From thread mk_sched_worker "list", remove the http_session + * struct information + */ +void mk_http_session_remove(struct mk_http_session *cs, + struct mk_server *server) +{ + struct mk_list *tmp; + struct mk_list *head; + struct mk_plugin *handler; + struct mk_http_request *sr; + + MK_TRACE("[FD %i] HTTP Session remove", cs->socket); + if (cs->_sched_init == MK_FALSE) { + return; + } + + /* On session remove, make sure to cleanup any handler */ + mk_list_foreach_safe(head, tmp, &cs->request_list) { + sr = mk_list_entry(head, struct mk_http_request, _head); + if (sr->stage30_handler) { + MK_TRACE("Hangup stage30 handler"); + handler = sr->stage30_handler; + if (mk_unlikely(!handler->stage->stage30_hangup)) { + mk_warn("Plugin %s, do not implement stage30_hangup", handler->name); + continue; + } + handler->stage->stage30_hangup(handler, cs, sr); + } + } + + if (cs->body != cs->body_fixed) { + mk_mem_free(cs->body); + } + mk_http_request_free_list(cs, server); + mk_list_del(&cs->request_list); + + cs->_sched_init = MK_FALSE; +} + +/* FIXME: nobody is using this */ +struct mk_http_session *mk_http_session_lookup(int socket) +{ + (void) socket; + return NULL; +} + + +/* Initialize a HTTP session (just created) */ +int mk_http_session_init(struct mk_http_session *cs, struct mk_sched_conn *conn, + struct mk_server *server) +{ + /* Alloc memory for node */ + cs->_sched_init = MK_TRUE; + cs->pipelined = MK_FALSE; + cs->counter_connections = 0; + cs->close_now = MK_FALSE; + cs->socket = conn->event.fd; + cs->status = MK_REQUEST_STATUS_INCOMPLETE; + cs->server = server; + + /* Map the channel, just for protocol-handler internal stuff */ + cs->channel = &conn->channel; + + /* Map the connection instance, required to handle exceptions */ + cs->conn = conn; + + /* creation time in unix time */ + cs->init_time = conn->arrive_time; + + /* alloc space for body content */ + if (conn->net->buffer_size > MK_REQUEST_CHUNK) { + cs->body = mk_mem_alloc(conn->net->buffer_size); + cs->body_size = conn->net->buffer_size; + } + else { + /* Buffer size based in Chunk bytes */ + cs->body = cs->body_fixed; + cs->body_size = MK_REQUEST_CHUNK; + } + + /* Current data length */ + cs->body_length = 0; + + /* Init session request list */ + mk_list_init(&cs->request_list); + + /* Initialize the parser */ + mk_http_parser_init(&cs->parser); + + return 0; +} + + +void mk_http_request_free(struct mk_http_request *sr, struct mk_server *server) +{ + /* Let the vhost interface to handle the session close */ + mk_vhost_close(sr, server); + + if (sr->headers.location) { + mk_mem_free(sr->headers.location); + } + + if (sr->uri_processed.data != sr->uri.data) { + mk_ptr_free(&sr->uri_processed); + } + + if (sr->real_path.data != sr->real_path_static) { + mk_ptr_free(&sr->real_path); + } + + if (sr->stream.channel) { + mk_stream_release(&sr->stream); + } +} + +void mk_http_request_free_list(struct mk_http_session *cs, + struct mk_server *server) +{ + struct mk_list *head, *tmp; + struct mk_http_request *request; + + /* sr = last node */ + MK_TRACE("[FD %i] Free struct client_session", cs->socket); + mk_list_foreach_safe(head, tmp, &cs->request_list) { + request = mk_list_entry(head, struct mk_http_request, _head); + mk_list_del(&request->_head); + + mk_http_request_free(request, server); + if (request != &cs->sr_fixed) { + mk_mem_free(request); + } + } +} + +/* + * Lookup a known header or a non-known header. For unknown headers + * set the 'key' value wth a lowercase string + */ +struct mk_http_header *mk_http_header_get(int name, struct mk_http_request *req, + const char *key, unsigned int len) +{ + int i; + struct mk_http_parser *parser = &req->session->parser; + struct mk_http_header *header; + + /* Known header */ + if (name >= 0 && name < MK_HEADER_SIZEOF) { + return &parser->headers[name]; + } + + /* Check if want to retrieve a custom header */ + if (name == MK_HEADER_OTHER) { + /* Iterate over the extra headers identified by the parser */ + for (i = 0; i < parser->headers_extra_count; i++) { + header = &parser->headers_extra[i]; + if (header->key.len != len) { + continue; + } + + if (strncmp(header->key.data, key, len) == 0) { + return header; + } + } + return NULL; + } + + return NULL; +} + +/* + * Main callbacks for the Scheduler + */ +int mk_http_sched_read(struct mk_sched_conn *conn, + struct mk_sched_worker *worker, + struct mk_server *server) +{ + int ret; + int status; + size_t count; + (void) worker; + struct mk_http_session *cs; + struct mk_http_request *sr; + +#ifdef MK_HAVE_TRACE + int socket = conn->event.fd; +#endif + + cs = mk_http_session_get(conn); + if (cs->_sched_init == MK_FALSE) { + /* Create session for the client */ + MK_TRACE("[FD %i] Create HTTP session", socket); + ret = mk_http_session_init(cs, conn, server); + if (ret == -1) { + return -1; + } + } + + /* Invoke the read handler, on this case we only support HTTP (for now :) */ + ret = mk_http_handler_read(conn, cs, server); + if (ret > 0) { + if (mk_list_is_empty(&cs->request_list) == 0) { + /* Add the first entry */ + sr = &cs->sr_fixed; + mk_list_add(&sr->_head, &cs->request_list); + mk_http_request_init(cs, sr, server); + } + else { + sr = mk_list_entry_first(&cs->request_list, struct mk_http_request, _head); + } + status = mk_http_parser(sr, &cs->parser, cs->body, + cs->body_length, server); + if (status == MK_HTTP_PARSER_OK) { + MK_TRACE("[FD %i] HTTP_PARSER_OK", socket); + if (mk_http_status_completed(cs, conn) == -1) { + mk_http_session_remove(cs, server); + return -1; + } + mk_sched_conn_timeout_del(conn); + ret = mk_http_request_prepare(cs, sr, server); + } + else if (status == MK_HTTP_PARSER_ERROR) { + /* The HTTP parser may enqueued some response error */ + if (mk_channel_is_empty(cs->channel) != 0) { + mk_channel_write(cs->channel, &count); + } + mk_http_session_remove(cs, server); + MK_TRACE("[FD %i] HTTP_PARSER_ERROR", socket); + return -1; + } + else { + MK_TRACE("[FD %i] HTTP_PARSER_PENDING", socket); + } + } + + return ret; +} + +/* The scheduler got a connection close event from the remote client */ +int mk_http_sched_close(struct mk_sched_conn *conn, + struct mk_sched_worker *sched, + int type, struct mk_server *server) +{ + struct mk_http_session *session; + (void) sched; + +#ifdef MK_HAVE_TRACE + MK_TRACE("[FD %i] HTTP sched close (type=%i)", conn->event.fd, type); +#else + (void) type; +#endif + + /* Release resources of the requests and session */ + session = mk_http_session_get(conn); + mk_http_session_remove(session, server); + return 0; +} + +int mk_http_sched_done(struct mk_sched_conn *conn, + struct mk_sched_worker *worker, + struct mk_server *server) +{ + (void) worker; + struct mk_http_session *session; + struct mk_http_request *sr; + + session = mk_http_session_get(conn); + sr = mk_list_entry_first(&session->request_list, + struct mk_http_request, _head); + mk_plugin_stage_run_40(session, sr, server); + + return mk_http_request_end(session, server); +} + +struct mk_sched_handler mk_http_handler = { + .name = "http", + .cb_read = mk_http_sched_read, + .cb_close = mk_http_sched_close, + .cb_done = mk_http_sched_done, + .sched_extra_size = sizeof(struct mk_http_session), + .capabilities = MK_CAP_HTTP +}; diff --git a/fluent-bit/lib/monkey/mk_server/mk_http2.c b/fluent-bit/lib/monkey/mk_server/mk_http2.c new file mode 100644 index 00000000..7a4f1e82 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_http2.c @@ -0,0 +1,384 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include <inttypes.h> + +#include <monkey/mk_http2.h> +#include <monkey/mk_http2_settings.h> +#include <monkey/mk_header.h> +#include <monkey/mk_scheduler.h> + +/* HTTP/2 Connection Preface */ +#define MK_HTTP2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" +static mk_ptr_t http2_preface = { + .data = MK_HTTP2_PREFACE, + .len = sizeof(MK_HTTP2_PREFACE) - 1 +}; + +static inline void buffer_consume(struct mk_http2_session *h2s, int bytes) +{ + memmove(h2s->buffer, + h2s->buffer + bytes, + h2s->buffer_length - bytes); + + MK_TRACE("[h2] consume buffer length from %i to %i", + h2s->buffer_length, h2s->buffer_length - bytes); + h2s->buffer_length -= bytes; +} + +static struct mk_http2_session *mk_http2_session_create() +{ + struct mk_http2_session *h2s; + + h2s = mk_mem_alloc(sizeof(struct mk_http2_session)); + if (!h2s) { + return NULL; + } + h2s->buffer = NULL; + h2s->buffer_length = 0; + h2s->buffer_size = sizeof(h2s->buffer_fixed); + h2s->buffer = h2s->buffer_fixed; + h2s->settings = MK_HTTP2_SETTINGS_DEFAULT; + + return h2s; +} + +/* FIXME +static int mk_http2_session_destroy(struct mk_http2_session *h2s) +{ + if (h2s->buffer != h2s->buffer_fixed) { + mk_mem_free(h2s->buffer); + } + mk_mem_free(h2s); + return 0; +} + +static int mk_http2_frame_header(char *buf, uint32_t length, uint8_t type, + uint32_t flags, void *data) +{ + struct mk_http2_frame *f = (struct mk_http2_frame *) buf; + + f->len_type = (length << 8 | type); + f->flags = flags; + f->payload = data; + + return sizeof(struct mk_http2_frame); +} + +*/ + +/* Handle an upgraded session */ +static int mk_http2_upgrade(void *cs, void *sr, struct mk_server *server) +{ + struct mk_http_session *s = cs; + struct mk_http_request *r = sr; + struct mk_http2_session *h2s; + + mk_header_set_http_status(r, MK_INFO_SWITCH_PROTOCOL); + r->headers.connection = MK_HEADER_CONN_UPGRADED; + r->headers.upgrade = MK_HEADER_UPGRADED_H2C; + mk_header_prepare(s, r, server); + + h2s = mk_http2_session_create(); + if (!h2s) { + return -1; + } + + h2s->status = MK_HTTP2_UPGRADED; + s->conn->data = h2s; + + return MK_HTTP_OK; +} + +/* FIXME Decode a frame header, no more... no less +static inline void mk_http2_frame_decode_header(uint8_t *buf, + struct mk_http2_frame *frame) +{ + struct mk_http2_session *h2s; + (void) h2s; + + frame->len_type = mk_http2_bitdec_32u(buf); + frame->flags = buf[4]; + frame->stream_id = mk_http2_bitdec_stream_id(buf + 5); + frame->payload = buf + 9; + +#ifdef MK_HAVE_TRACE + MK_TRACE("Frame Header"); + printf(" length=%i, type=%i, stream_id=%i\n", + mk_http2_frame_len(frame), + mk_http2_frame_type(frame), + frame->stream_id); +#endif +} +*/ + +static inline int mk_http2_handle_settings(struct mk_sched_conn *conn, + struct mk_http2_frame *frame) +{ + int i; + int frame_len; + int settings; + int setting_size = 6; /* 16 bits identifier + 32 bits value = 6 bytes */ + uint16_t setting_id; + uint32_t setting_value; + uint8_t *p; + struct mk_http2_session *h2s; + + h2s = conn->data; + frame_len = mk_http2_frame_len(frame); + if (frame->flags == MK_HTTP2_SETTINGS_ACK) { + /* + * Nothing to do, the peer just received our SETTINGS and it's + * sending an acknowledge. + * + * note: validate that frame length is zero. + */ + if (frame_len > 0) { + /* + * This must he handled as a connection error, we must reply + * with a FRAME_SIZE_ERROR. ref: + * + * https://httpwg.github.io/specs/rfc7540.html#SETTINGS + */ + + /* FIXME: send a GOAWAY error frame */ + MK_TRACE("FRAME SIZE ERR: %i\n", frame_len); + return -1; + + } + return 0; + } + + /* + * Iterate our SETTINGS payload, it may contain many entries in the + * following format: + * + * +-------------------------------+ + * | Identifier (16) | + * +-------------------------------+-------------------------------+ + * | Value (32) | + * +---------------------------------------------------------------+ + * + * 48 bits = 6 bytes + */ + settings = (frame_len / setting_size); + for (i = 0; i < settings; i++ ) { + /* Seek payload per SETTINGS entry */ + p = frame->payload + (setting_size * i); + + setting_id = p[0] << 8 | p[1]; + setting_value = p[2] << 24 | p[3] << 16 | p[4] << 8 | p[5]; + MK_H2_TRACE(conn, "[Setting] ID=%" PRIu16 " VAL=%" PRIu32, + setting_id, setting_value); + + switch (setting_id) { + case MK_HTTP2_SETTINGS_HEADER_TABLE_SIZE: + /* unhandled */ + break; + case MK_HTTP2_SETTINGS_ENABLE_PUSH: + if (setting_value != 0 && setting_value != 1) { + /* FIXME: PROTOCOL_ERROR */ + MK_H2_TRACE(conn, "Invalid SETTINGS_ENABLE_PUSH"); + return -1; + } + h2s->settings.enable_push = setting_value; + break; + case MK_HTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: + if (setting_value < 64) { + h2s->settings.max_concurrent_streams = setting_value; + } + else { + h2s->settings.max_concurrent_streams = 64; + } + MK_H2_TRACE(conn, "SETTINGS MAX_CONCURRENT_STREAMS=%i", + setting_value); + break; + case MK_HTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + if (setting_value < 65535 || setting_value > 2147483647) { + /* FIXME: send FLOW_CONTROL_ERROR */ + MK_H2_TRACE(conn, "Invalid INITIAL_WINDOW_SIZE"); + return -1; + } + h2s->settings.initial_window_size = setting_value; + break; + case MK_HTTP2_SETTINGS_MAX_FRAME_SIZE: + if (setting_value < 16384 || setting_value > 2147483647) { + /* FIXME: send PROTOCOL_ERROR */ + return -1; + } + h2s->settings.max_frame_size = setting_value; + break; + case MK_HTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: + /* Unhandled */ + break; + default: + /* + * 5.5 Extending HTTP/2: ...Implementations MUST ignore unknown + * or unsupported values in all extensible protocol elements... + */ + break; + } + } + + /* FIXME // No errors, send the ACK + mk_http2_send_raw(conn, MK_HTTP2_SETTINGS_ACK_FRAME, + sizeof(MK_HTTP2_SETTINGS_ACK_FRAME) - 1); + */ + return 0; +} + + +static inline int mk_http2_frame_run(struct mk_sched_conn *conn, + struct mk_sched_worker *worker) +{ + int ret; + struct mk_http2_frame frame; + struct mk_http2_session *h2s; + (void) worker; + + h2s = conn->data; + + /* Decode the frame header */ + //FIXME mk_http2_frame_decode_header(h2s->buffer, &frame); + + /* Do some validations */ + if (h2s->buffer_length < (MK_HTTP2_HEADER_SIZE + (frame.len_type >> 8))) { + /* FIXME: need more data */ + return 0; + } + + /* Do some work based on the frame type */ + if (mk_http2_frame_type(&frame) == MK_HTTP2_SETTINGS) { + ret = mk_http2_handle_settings(conn, &frame); + /* FIXME: send our MK_HTTP2_SETTINGS_ACK_FRAME */ + return ret; + } + + return 0; +} + +static int mk_http2_sched_read(struct mk_sched_conn *conn, + struct mk_sched_worker *worker, + struct mk_server *server) +{ + int bytes; + int new_size; + int available; + char *tmp; + struct mk_http2_session *h2s; + (void) worker; + (void) server; + + h2s = conn->data; + available = h2s->buffer_size - h2s->buffer_length; + if (available == 0) { + new_size = h2s->buffer_size + MK_HTTP2_CHUNK; + if (h2s->buffer == h2s->buffer_fixed) { + h2s->buffer = mk_mem_alloc(new_size); + if (!h2s->buffer) { + /* FIXME: send internal server error ? */ + return -1; + } + memcpy(h2s->buffer, h2s->buffer_fixed, h2s->buffer_length); + MK_TRACE("[FD %i] Buffer new size: %i, length: %i", + conn->event.fd, new_size, h2s->buffer_length); + } + else { + MK_TRACE("[FD %i] Buffer realloc from %i to %i", + conn->event.fd, h2s->buffer_size, new_size); + tmp = mk_mem_realloc(h2s->buffer, new_size); + if (tmp) { + h2s->buffer = tmp; + h2s->buffer_size = new_size; + } + else { + /* FIXME: send internal server error ? */ + return -1; + } + + } + } + + /* Read the incoming data */ + bytes = mk_sched_conn_read(conn, + h2s->buffer, + h2s->buffer_size - h2s->buffer_length); + if (bytes == 0) { + errno = 0; + return -1; + } + else if (bytes == -1) { + return -1; + } + + h2s->buffer_length += bytes; + + /* Upgraded connections from HTTP/1.x requires the preface */ + if (h2s->status == MK_HTTP2_UPGRADED) { + if (h2s->buffer_length >= http2_preface.len) { + if (memcmp(h2s->buffer, + http2_preface.data, http2_preface.len) != 0) { + MK_H2_TRACE(conn, "Invalid HTTP/2 preface"); + return 0; + } + + MK_H2_TRACE(conn, "HTTP/2 preface OK"); + + buffer_consume(h2s, http2_preface.len); + h2s->status = MK_HTTP2_OK; + + /* Send out our default settings + mk_stream_set(&h2s->stream_settings, + MK_STREAM_RAW, + &conn->channel, + MK_HTTP2_SETTINGS_DEFAULT_FRAME, + sizeof(MK_HTTP2_SETTINGS_DEFAULT_FRAME) - 1, + NULL, + NULL, NULL, NULL); + */ + } + else { + /* We need more data */ + return 0; + } + } + + /* Check that we have a minimum header size */ + if (h2s->buffer_length < MK_HTTP2_HEADER_SIZE) { + MK_TRACE("HEADER FRAME incomplete %i/%i bytes", + h2s->buffer_length, MK_HTTP2_HEADER_SIZE); + return 0; + } + + /* We have at least one frame */ + return mk_http2_frame_run(conn, worker); +} + + +struct mk_sched_handler mk_http2_handler = { + .name = "http2", + .cb_read = mk_http2_sched_read, + .cb_close = NULL, + .cb_done = NULL, + .cb_upgrade = mk_http2_upgrade, + .sched_extra_size = sizeof(struct mk_http2_session), + .capabilities = MK_CAP_HTTP2 +}; diff --git a/fluent-bit/lib/monkey/mk_server/mk_http_parser.c b/fluent-bit/lib/monkey/mk_server/mk_http_parser.c new file mode 100644 index 00000000..4e7aa316 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_http_parser.c @@ -0,0 +1,744 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdint.h> +#include <limits.h> + +#include <monkey/mk_http.h> +#include <monkey/mk_http_parser.h> +#include <monkey/mk_http_status.h> + +#define mark_end() \ + p->end = p->i; \ + p->chars = -1; + +#define start_next() \ + p->start = p->i + 1; \ + continue + +#define field_len() (p->end - p->start) +#define header_scope_eq(p, x) p->header_min = p->header_max = x + +struct row_entry { + int len; + const char name[32]; +}; + +struct row_entry mk_methods_table[] = { + { 3, "GET" }, + { 4, "POST" }, + { 4, "HEAD" }, + { 3, "PUT" }, + { 6, "DELETE" }, + { 7, "OPTIONS" } +}; + +struct row_entry mk_headers_table[] = { + { 6, "accept" }, + { 14, "accept-charset" }, + { 15, "accept-encoding" }, + { 15, "accept-language" }, + { 13, "authorization" }, + { 13, "cache-control" }, + { 6, "cookie" }, + { 10, "connection" }, + { 14, "content-length" }, + { 13, "content-range" }, + { 12, "content-type" }, + { 4, "host" }, + { 14, "http2-settings" }, + { 17, "if-modified-since" }, + { 13, "last-modified" }, + { 19, "last-modified-since" }, + { 5, "range" }, + { 7, "referer" }, + { 7, "upgrade" }, + { 10, "user-agent" } +}; + +static inline void reverse_char_lookup(char *buf, char c, int len, struct mk_http_parser *p) +{ + int x = 0; + int y = 0; + + x = p->i; + do { + if (buf[x - y] == c) { + p->i = x - y; + return; + } + y++; + } while (y < len); +} + +static inline void char_lookup(char *buf, char c, int len, struct mk_http_parser *p) +{ + int x = 0; + + x = p->i; + do { + if (buf[x] == c) { + p->i = x; + return; + } + x++; + } while (x < len); +} + +static inline int str_searchr(char *buf, char c, int len) +{ + int i; + + for (i = len - 1; i >= 0; i--) { + if (buf[i] == c) { + return i; + } + } + + return -1; +} + +static inline int method_lookup(struct mk_http_request *req, + struct mk_http_parser *p, char *buffer) +{ + int i = 0; + int len; + + /* Method lenght */ + len = field_len(); + + /* Point the buffer */ + req->method = MK_METHOD_UNKNOWN; + req->method_p.data = buffer + p->start; + req->method_p.len = len; + + if (p->method >= 0) { + if (strncmp(buffer + p->start + 1, + mk_methods_table[p->method].name + 1, + len - 1) == 0) { + req->method = p->method; + return req->method; + } + } + + for (i = 0; i < MK_METHOD_SIZEOF; i++) { + if (len != mk_methods_table[i].len) { + continue; + } + + if (strncmp(buffer + p->start, mk_methods_table[i].name, len) == 0) { + req->method = i; + return i; + } + } + return MK_METHOD_UNKNOWN; +} + +static inline void request_set(mk_ptr_t *ptr, struct mk_http_parser *p, char *buffer) +{ + ptr->data = buffer + p->start; + ptr->len = field_len(); +} + +/* + * expected: a known & expected value in lowercase + * value : the expected string value in the header + * len : the value string length. + * + * If it matches it return zero. Otherwise -1. + */ +static inline int header_cmp(const char *expected, char *value, int len) +{ + int i = 0; + + if (len >= 8) { + if (expected[0] != tolower(value[0])) return -1; + if (expected[1] != tolower(value[1])) return -1; + if (expected[2] != tolower(value[2])) return -1; + if (expected[3] != tolower(value[3])) return -1; + if (expected[4] != tolower(value[4])) return -1; + if (expected[5] != tolower(value[5])) return -1; + if (expected[6] != tolower(value[6])) return -1; + if (expected[7] != tolower(value[7])) return -1; + i = 8; + } + + for (; i < len; i++) { + if (expected[i] != tolower(value[i])) { + return -1; + } + } + + return 0; +} + +static inline int header_lookup(struct mk_http_parser *p, char *buffer) +{ + int i; + int len; + int pos; + long val; + char *endptr; + char *tmp; + + struct mk_http_header *header; + struct mk_http_header *header_extra; + struct row_entry *h; + + len = (p->header_sep - p->header_key); + for (i = p->header_min; i <= p->header_max && i >= 0; i++) { + h = &mk_headers_table[i]; + /* Check string length first */ + if (h->len != len) { + continue; + } + + if (header_cmp(h->name + 1, buffer + p->header_key + 1, len - 1) == 0) { + /* We got a header match, register the header index */ + header = &p->headers[i]; + header->type = i; + header->key.data = buffer + p->header_key; + header->key.len = len; + header->val.data = buffer + p->header_val; + header->val.len = p->end - p->header_val; + p->header_count++; + mk_list_add(&header->_head, &p->header_list); + + if (i == MK_HEADER_HOST) { + /* Handle a possible port number in the Host header */ + int sep = str_searchr(header->val.data, ':', header->val.len); + if (sep > 0) { + int plen; + short int port_size = 6; + char port[6]; /* Can't use port_size to declare a stack allocated array in vc++ */ + + plen = header->val.len - sep - 1; + if (plen <= 0 || plen >= port_size) { + return -MK_CLIENT_BAD_REQUEST; + } + memcpy(&port, header->val.data + sep + 1, plen); + port[plen] = '\0'; + + errno = 0; + val = strtol(port, &endptr, 10); + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) + || (errno != 0 && val == 0)) { + return -MK_CLIENT_BAD_REQUEST; + } + + if (endptr == port || *endptr != '\0') { + return -MK_CLIENT_BAD_REQUEST; + } + + p->header_host_port = val; + + /* Re-set the Host header value without port */ + header->val.len = sep; + } + } + else if (i == MK_HEADER_CONTENT_LENGTH) { + errno = 0; + val = strtol(header->val.data, &endptr, 10); + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) + || (errno != 0 && val == 0)) { + return -MK_CLIENT_REQUEST_ENTITY_TOO_LARGE; + } + if (endptr == header->val.data) { + return -1; + } + if (val < 0) { + return -1; + } + + p->header_content_length = val; + } + else if (i == MK_HEADER_CONNECTION) { + /* Check Connection: Keep-Alive */ + if (header->val.len == sizeof(MK_CONN_KEEP_ALIVE) - 1) { + if (header_cmp(MK_CONN_KEEP_ALIVE, + header->val.data, + header->val.len ) == 0) { + p->header_connection = MK_HTTP_PARSER_CONN_KA; + } + } + /* Check Connection: Close */ + else if (header->val.len == sizeof(MK_CONN_CLOSE) -1) { + if (header_cmp(MK_CONN_CLOSE, + header->val.data, header->val.len) == 0) { + p->header_connection = MK_HTTP_PARSER_CONN_CLOSE; + } + } + else { + p->header_connection = MK_HTTP_PARSER_CONN_UNKNOWN; + + /* Try to find some known values */ + + /* Connection: upgrade */ + pos = mk_string_search_n(header->val.data, + "Upgrade", + MK_STR_INSENSITIVE, + header->val.len); + if (pos >= 0) { + p->header_connection = MK_HTTP_PARSER_CONN_UPGRADE; + } + + /* Connection: HTTP2-Settings */ + pos = mk_string_search_n(header->val.data, + "HTTP2-Settings", + MK_STR_INSENSITIVE, + header->val.len); + if (pos >= 0) { + p->header_connection |= MK_HTTP_PARSER_CONN_HTTP2_SE; + } + } + } + else if (i == MK_HEADER_UPGRADE) { + if (header_cmp(MK_UPGRADE_H2C, + header->val.data, header->val.len) == 0) { + p->header_upgrade = MK_HTTP_PARSER_UPGRADE_H2C; + } + } + + return 0; + } + } + + /* + * The header_lookup did not match any known header, so we register this + * entry into the headers_extra array. + */ + if (p->headers_extra_count < MK_HEADER_EXTRA_SIZE) { + header_extra = &p->headers_extra[p->headers_extra_count]; + header_extra->key.data = tmp = (buffer + p->header_key); + header_extra->key.len = len; + + /* Transform the header key string to lowercase */ + for (i = 0; i < len; i++) { + tmp[i] = tolower(tmp[i]); + } + + header_extra->val.data = buffer + p->header_val; + header_extra->val.len = p->end - p->header_val; + p->headers_extra_count++; + p->header_count++; + mk_list_add(&header_extra->_head, &p->header_list); + return 0; + } + + /* + * Header is unknown and we cannot store it on our extra headers + * list as it's already full. Request is too large. + */ + return -MK_CLIENT_REQUEST_ENTITY_TOO_LARGE; +} + +/* + * This function is invoked everytime the parser evaluate the request is + * OK. Here we perform some extra validations mostly based on some logic + * and protocol requirements according to the data received. + */ +static inline int mk_http_parser_ok(struct mk_http_request *req, + struct mk_http_parser *p, + struct mk_server *server) +{ + /* Validate HTTP Version */ + if (req->protocol == MK_HTTP_PROTOCOL_UNKNOWN) { + mk_http_error(MK_SERVER_HTTP_VERSION_UNSUP, req->session, req, server); + return MK_HTTP_PARSER_ERROR; + } + + /* POST checks */ + if (req->method == MK_METHOD_POST || req->method == MK_METHOD_PUT) { + /* validate Content-Length exists */ + if (p->headers[MK_HEADER_CONTENT_LENGTH].type == 0) { + mk_http_error(MK_CLIENT_LENGTH_REQUIRED, req->session, req, server); + return MK_HTTP_PARSER_ERROR; + } + } + + return MK_HTTP_PARSER_OK; +} + +/* + * Parse the protocol and point relevant fields, don't take logic decisions + * based on this, just parse to locate things. + */ +int mk_http_parser(struct mk_http_request *req, struct mk_http_parser *p, + char *buffer, int buf_len, struct mk_server *server) +{ + int s; + int tmp; + int ret; + int len; + + /* lazy test + printf("p->i=%i buf_len=%i\n", + p->i, buf_len); + + for (s = p->i; s < buf_len; s++) { + if (buffer[s] == '\r') { + printf("CR"); + } + else if (buffer[s] == '\n') { + printf("LF"); + } + else { + printf("%c", buffer[s]); + } + } + printf("\n"); + */ + + len = buf_len; + for (; p->i < len; p->i++, p->chars++) { + /* FIRST LINE LEVEL: Method, URI & Protocol */ + if (p->level == REQ_LEVEL_FIRST) { + switch (p->status) { + case MK_ST_REQ_METHOD: /* HTTP Method */ + if (p->chars == -1) { + switch (buffer[p->i]) { + case 'G': + p->method = MK_METHOD_GET; + break; + case 'P': + p->method = MK_METHOD_POST; + break; + case 'H': + p->method = MK_METHOD_HEAD; + break; + case 'D': + p->method = MK_METHOD_DELETE; + break; + case 'O': + p->method = MK_METHOD_OPTIONS; + break; + } + continue; + } + + if (buffer[p->i] == ' ') { + mark_end(); + p->status = MK_ST_REQ_URI; + if (p->end < 2) { + return MK_HTTP_PARSER_ERROR; + } + method_lookup(req, p, buffer); + start_next(); + } + else { + if ((p->i - p->start) > 10) { + return MK_HTTP_PARSER_ERROR; + } + } + break; + case MK_ST_REQ_URI: /* URI */ + if (buffer[p->i] == ' ') { + mark_end(); + p->status = MK_ST_REQ_PROT_VERSION; + if (field_len() < 1) { + return MK_HTTP_PARSER_ERROR; + } + request_set(&req->uri, p, buffer); + start_next(); + } + else if (buffer[p->i] == '?') { + mark_end(); + request_set(&req->uri, p, buffer); + p->status = MK_ST_REQ_QUERY_STRING; + start_next(); + } + else if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { + mk_http_error(MK_CLIENT_BAD_REQUEST, req->session, + req, server); + return MK_HTTP_PARSER_ERROR; + } + break; + case MK_ST_REQ_QUERY_STRING: /* Query string */ + char_lookup(buffer, '\n', len, p); + + if (buffer[p->i] == '\n') { + reverse_char_lookup(buffer, ' ', p->i, p); + } + + if (buffer[p->i] == ' ') { + mark_end(); + request_set(&req->query_string, p, buffer); + p->status = MK_ST_REQ_PROT_VERSION; + start_next(); + } + else if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { + mk_http_error(MK_CLIENT_BAD_REQUEST, req->session, + req, server); + return MK_HTTP_PARSER_ERROR; + } + break; + case MK_ST_REQ_PROT_VERSION: /* Protocol Version */ + /* + * Most of the time we already have the string version in our + * buffer, for that case try to match the version and avoid + * loop rounds. + */ + if (p->start + 6 >= p->i) { + continue; + } + + tmp = p->start; + if (buffer[tmp] == 'H' && + buffer[tmp + 1] == 'T' && + buffer[tmp + 2] == 'T' && + buffer[tmp + 3] == 'P' && + buffer[tmp + 4] == '/' && + buffer[tmp + 5] == '1' && + buffer[tmp + 6] == '.') { + + request_set(&req->protocol_p, p, buffer); + req->protocol_p.len = 8; + mk_http_set_minor_version(buffer[tmp + 7]); + } + else { + mk_http_error(MK_SERVER_HTTP_VERSION_UNSUP, + req->session, req, server); + return MK_HTTP_PARSER_ERROR; + } + p->status = MK_ST_FIRST_CONTINUE; + break; + case MK_ST_FIRST_CONTINUE: + if (buffer[p->i] == '\r') { + p->status = MK_ST_FIRST_FINALIZING; + } + else { + return MK_HTTP_PARSER_ERROR; + } + break; + case MK_ST_FIRST_FINALIZING: /* New Line */ + if (buffer[p->i] == '\n') { + p->level = REQ_LEVEL_CONTINUE; + start_next(); + } + else { + return MK_HTTP_PARSER_ERROR; + } + break; + case MK_ST_BLOCK_END: + if (buffer[p->i] == '\n') { + return mk_http_parser_ok(req, p, server); + } + else { + return MK_HTTP_PARSER_ERROR; + } + break; + }; + } + else if (p->level == REQ_LEVEL_CONTINUE) { + if (buffer[p->i] == '\r') { + p->level = REQ_LEVEL_FIRST; + p->status = MK_ST_BLOCK_END; + } + else { + p->level = REQ_LEVEL_HEADERS; + p->status = MK_ST_HEADER_KEY; + p->chars = 0; + } + } + /* HEADERS: all headers stuff */ + if (p->level == REQ_LEVEL_HEADERS) { + /* Expect a Header key */ + if (p->status == MK_ST_HEADER_KEY) { + if (buffer[p->i] == '\r') { + if (p->chars == 0) { + p->level = REQ_LEVEL_END; + start_next(); + } + else { + return MK_HTTP_PARSER_ERROR; + } + } + + if (p->chars == 0) { + /* + * We reach the start of a Header row, lets catch the most + * probable header. + * + * The goal of this 'first row character lookup', is to define a + * small range set of probable headers comparison once we catch + * a header end. + */ + s = tolower(buffer[p->i]); + switch (s) { + case 'a': + p->header_min = MK_HEADER_ACCEPT; + p->header_max = MK_HEADER_AUTHORIZATION; + break; + case 'c': + p->header_min = MK_HEADER_CACHE_CONTROL; + p->header_max = MK_HEADER_CONTENT_TYPE; + break; + case 'h': + p->header_min = MK_HEADER_HOST; + p->header_max = MK_HEADER_HTTP2_SETTINGS; + break; + case 'i': + header_scope_eq(p, MK_HEADER_IF_MODIFIED_SINCE); + break; + case 'l': + p->header_min = MK_HEADER_LAST_MODIFIED; + p->header_max = MK_HEADER_LAST_MODIFIED_SINCE; + break; + case 'r': + p->header_min = MK_HEADER_RANGE; + p->header_max = MK_HEADER_REFERER; + break; + case 'u': + p->header_min = MK_HEADER_UPGRADE; + p->header_max = MK_HEADER_USER_AGENT; + break; + default: + p->header_key = -1; + p->header_sep = -1; + p->header_min = -1; + p->header_max = -1; + }; + p->header_key = p->i; + continue; + } + + /* Found key/value separator */ + char_lookup(buffer, ':', len, p); + if (buffer[p->i] == ':') { + /* Set the key/value middle point */ + p->header_sep = p->i; + + /* validate length */ + mark_end(); + if (field_len() < 1) { + return MK_HTTP_PARSER_ERROR; + } + + /* Wait for a value */ + p->status = MK_ST_HEADER_VALUE; + start_next(); + } + } + /* Parsing the header value */ + else if (p->status == MK_ST_HEADER_VALUE) { + /* Trim left, set starts only when found something != ' ' */ + if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { + return MK_HTTP_PARSER_ERROR; + } + else if (buffer[p->i] != ' ') { + p->status = MK_ST_HEADER_VAL_STARTS; + p->start = p->header_val = p->i; + } + continue; + } + /* New header row starts */ + else if (p->status == MK_ST_HEADER_VAL_STARTS) { + /* Maybe there is no more headers and we reach the end ? */ + if (buffer[p->i] == '\r') { + mark_end(); + if (field_len() <= 0) { + return MK_HTTP_PARSER_ERROR; + } + + /* + * A header row has ended, lets lookup the header and populate + * our headers table index. + */ + ret = header_lookup(p, buffer); + if (ret != 0) { + if (ret < -1) { + mk_http_error(-ret, req->session, req, server); + } + return MK_HTTP_PARSER_ERROR; + } + + /* Try to catch next LF */ + if (p->i + 1 < len) { + if (buffer[p->i + 1] == '\n') { + p->i++; + p->status = MK_ST_HEADER_KEY; + p->chars = -1; + start_next(); + } + } + + p->status = MK_ST_HEADER_END; + start_next(); + } + else if (buffer[p->i] == '\n' && buffer[p->i - 1] != '\r') { + return MK_HTTP_PARSER_ERROR; + } + } + else if (p->status == MK_ST_HEADER_END) { + if (buffer[p->i] == '\n') { + p->status = MK_ST_HEADER_KEY; + p->chars = -1; + start_next(); + } + else { + return MK_HTTP_PARSER_ERROR; + } + } + } + else if (p->level == REQ_LEVEL_END) { + if (buffer[p->i] == '\n') { + if (p->header_content_length > 0) { + p->level = REQ_LEVEL_BODY; + p->chars = -1; + start_next(); + } + else { + return mk_http_parser_ok(req, p, server); + } + } + else { + return MK_HTTP_PARSER_ERROR; + } + } + else if (p->level == REQ_LEVEL_BODY) { + /* + * Reaching this level can means two things: + * + * - A Pipeline Request + * - A Body content (POST/PUT methods) + */ + if (p->header_content_length > 0) { + + p->body_received = len - p->start; + if ((len - p->start) < p->header_content_length) { + return MK_HTTP_PARSER_PENDING; + } + + /* Cut off */ + p->i += p->body_received; + req->data.len = p->body_received; + req->data.data = (buffer + p->start); + } + return mk_http_parser_ok(req, p, server); + } + } + + return MK_HTTP_PARSER_PENDING; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_http_thread.c b/fluent-bit/lib/monkey/mk_server/mk_http_thread.c new file mode 100644 index 00000000..d3c606f3 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_http_thread.c @@ -0,0 +1,290 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2016 Monkey Software LLC <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/mk_info.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_thread.h> +#include <monkey/mk_net.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_http_thread.h> + +#include <stdlib.h> + +/* + * libco do not support parameters in the entrypoint function due to the + * complexity of implementation in terms of architecture and compiler, but + * it provide a workaround using a global structure as a middle entry-point + * that achieve the same stuff. + */ +struct mk_http_libco_params { + int type; + struct mk_vhost_handler *handler; + struct mk_http_session *session; + struct mk_http_request *request; + int n_params; + struct mk_list *params; + struct mk_thread *th; +}; + +pthread_once_t mk_http_thread_initialize_tls_once_flag = PTHREAD_ONCE_INIT; + +MK_TLS_DEFINE(struct mk_http_libco_params, mk_http_thread_libco_params); +MK_TLS_DEFINE(struct mk_thread, mk_thread); + +/* This function could return NULL if the process runs out of memory, in that + * case failure is imminent. + */ +static inline struct mk_http_libco_params *thread_get_libco_params() +{ + struct mk_http_libco_params *libco_params; + + libco_params = MK_TLS_GET(mk_http_thread_libco_params); + + if (libco_params == NULL) { + libco_params = mk_mem_alloc_z(sizeof(struct mk_http_libco_params)); + + if (libco_params == NULL) { + mk_err("libco thread params could not be allocated."); + } + + MK_TLS_SET(mk_http_thread_libco_params, libco_params); + } + + return libco_params; +} + +static void mk_http_thread_initialize_tls_once() +{ + MK_TLS_INIT(mk_http_thread_libco_params); + MK_TLS_INIT(mk_thread); +} + +void mk_http_thread_initialize_tls() +{ + pthread_once(&mk_http_thread_initialize_tls_once_flag, + mk_http_thread_initialize_tls_once); +} + +static inline void thread_cb_init_vars() +{ + struct mk_http_libco_params *libco_params; + struct mk_vhost_handler *handler; + struct mk_http_session *session; + struct mk_http_request *request; + int close; + int type; + struct mk_http_thread *mth; + struct mk_thread *th; + + libco_params = thread_get_libco_params(); + + type = libco_params->type; + handler = libco_params->handler; + session = libco_params->session; + request = libco_params->request; + th = libco_params->th; + + /* + * Until this point the th->callee already set the variables, so we + * wait until the core wanted to resume so we really trigger the + * output callback. + */ + co_switch(th->caller); + + if (type == MK_HTTP_THREAD_LIB) { + /* Invoke the handler callback */ + handler->cb(request, handler->data); + + /* + * Once the callback finished, we need to sanitize the connection + * so other further requests can be processed. + */ + int ret; + struct mk_sched_worker *sched; + struct mk_channel *channel; + + channel = request->session->channel; + sched = mk_sched_get_thread_conf(); + + MK_EVENT_NEW(channel->event); + ret = mk_event_add(sched->loop, + channel->fd, + MK_EVENT_CONNECTION, + MK_EVENT_READ, channel->event); + if (ret == -1) { + //return -1; + } + + /* Save temporal session */ + mth = request->thread; + + /* + * Finalize request internally, if ret == -1 means we should + * ask to shutdown the connection. + */ + ret = mk_http_request_end(session, session->server); + if (ret == -1) { + close = MK_TRUE; + } + else { + close = MK_FALSE; + } + mk_http_thread_purge(mth, close); + + /* Return control to caller */ + mk_thread_yield(th); + } + else if (type == MK_HTTP_THREAD_PLUGIN) { + /* FIXME: call plugin handler callback with params */ + } +} + +static inline void thread_params_set(struct mk_thread *th, + int type, + struct mk_vhost_handler *handler, + struct mk_http_session *session, + struct mk_http_request *request, + int n_params, + struct mk_list *params) +{ + struct mk_http_libco_params *libco_params; + + libco_params = thread_get_libco_params(); + + /* Callback parameters in order */ + libco_params->type = type; + libco_params->handler = handler; + libco_params->session = session; + libco_params->request = request; + libco_params->n_params = n_params; + libco_params->params = params; + libco_params->th = th; + + co_switch(th->callee); +} + +struct mk_http_thread *mk_http_thread_create(int type, + struct mk_vhost_handler *handler, + struct mk_http_session *session, + struct mk_http_request *request, + int n_params, + struct mk_list *params) +{ + size_t stack_size; + struct mk_thread *th = NULL; + struct mk_http_thread *mth; + struct mk_sched_worker *sched; + + sched = mk_sched_get_thread_conf(); + if (!sched) { + return NULL; + } + + th = mk_thread_new(sizeof(struct mk_http_thread), NULL); + if (!th) { + return NULL; + } + + mth = (struct mk_http_thread *) MK_THREAD_DATA(th); + if (!mth) { + return NULL; + } + + mth->session = session; + mth->request = request; + mth->parent = th; + mth->close = MK_FALSE; + request->thread = mth; + mk_list_add(&mth->_head, &sched->threads); + + th->caller = co_active(); + th->callee = co_create(MK_THREAD_STACK_SIZE, + thread_cb_init_vars, &stack_size); + +#ifdef MK_HAVE_VALGRIND + th->valgrind_stack_id = VALGRIND_STACK_REGISTER(th->callee, + ((char *)th->callee) + stack_size); +#endif + + /* Workaround for makecontext() */ + thread_params_set(th, type, handler, session, request, n_params, params); + + return mth; +} + +/* + * Move a http thread context from sched->thread to sched->threads_purge list. + * On this way the scheduler will release or reasign the resource later. + */ +int mk_http_thread_purge(struct mk_http_thread *mth, int close) +{ + struct mk_sched_worker *sched; + + sched = mk_sched_get_thread_conf(); + if (!sched) { + return -1; + } + + mth->close = close; + mk_list_del(&mth->_head); + mk_list_add(&mth->_head, &sched->threads_purge); + + return 0; +} + +int mk_http_thread_destroy(struct mk_http_thread *mth) +{ + struct mk_thread *th; + + /* Unlink from scheduler thread list */ + mk_list_del(&mth->_head); + + /* release original memory context */ + th = mth->parent; + mth->session->channel->event->type = MK_EVENT_CONNECTION; + mk_thread_destroy(th); + + return 0; +} + +int mk_http_thread_event(struct mk_event *event) +{ + struct mk_sched_conn *conn = (struct mk_sched_conn *) event; + + /* + struct mk_thread *th; + struct mk_http_thread *mth; + + th = conn->channel.thread; + mth = (struct mk_http_thread *) MK_THREAD_DATA(th); + */ + + mk_thread_resume(conn->channel.thread); + return 0; +} + +/* + * Start the co-routine: invoke coroutine callback and start processing + * data flush requests. + */ +int mk_http_thread_start(struct mk_http_thread *mth) +{ + mk_http_thread_resume(mth); + return 0; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_kernel.c b/fluent-bit/lib/monkey/mk_server/mk_kernel.c new file mode 100644 index 00000000..cc69e96c --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_kernel.c @@ -0,0 +1,165 @@ +/*-*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/monkey.h> +#include <monkey/mk_core.h> +#include <monkey/mk_kernel.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_server.h> +#include <monkey/mk_scheduler.h> + +#include <ctype.h> + +#ifndef _WIN32 +#include <sys/utsname.h> + +int mk_kernel_version() +{ + int a, b, c; + int len; + int pos; + char *p, *t; + char *tmp; + struct utsname uts; + + if (uname(&uts) == -1) { + mk_libc_error("uname"); + } + len = strlen(uts.release); + + /* Fixme: this don't support Linux Kernel 10.x.x :P */ + a = (*uts.release - '0'); + + /* Second number */ + p = (uts.release) + 2; + pos = mk_string_char_search(p, '.', len - 2); + if (pos <= 0) { + /* Some Debian systems uses a different notation, e.g: 3.14-2-amd64 */ + pos = mk_string_char_search(p, '-', len - 2); + if (pos <= 0) { + return -1; + } + } + + tmp = mk_string_copy_substr(p, 0, pos); + if (!tmp) { + return -1; + } + b = atoi(tmp); + mk_mem_free(tmp); + + /* Last number (it needs filtering) */ + t = p = p + pos + 1; + do { + t++; + } while (isdigit(*t)); + + tmp = mk_string_copy_substr(p, 0, t - p); + if (!tmp) { + return -1; + } + c = atoi(tmp); + mk_mem_free(tmp); + + MK_TRACE("Kernel detected: %i.%i.%i", a, b, c); + return MK_KERNEL_VERSION(a, b, c); +} + +/* Detect specific Linux Kernel features that we may use */ +int mk_kernel_features(int version) +{ + int flags = 0; + + /* + * TCP Auto Corking (disabled by #175) + * ----------------------------------- + * I found that running some benchmarks on Linux 3.16 with + * tcp_autocorking enabled, it lead to lower performance, looks like + * a manual cork fits better for our needs. + * + * I think there is something wrong that we need to clarify, by now + * I've logged the following issue: + * + * https://github.com/monkey/monkey/issues/175 + * + if (mk_kernel_runver >= MK_KERNEL_VERSION(3, 14, 0) && + mk_socket_tcp_autocorking() == MK_TRUE) { + flags |= MK_KERNEL_TCP_AUTOCORKING; + } + */ + + /* SO_REUSEPORT */ + if (version >= MK_KERNEL_VERSION(3, 9, 0)) { + flags |= MK_KERNEL_SO_REUSEPORT; + } + + /* TCP_FASTOPEN */ + if (version >= MK_KERNEL_VERSION(3, 7, 0)) { + flags |= MK_KERNEL_TCP_FASTOPEN; + } + + return flags; +} + +int mk_kernel_features_print(char *buffer, size_t size, + struct mk_server *server) +{ + int offset = 0; + int features = 0; + + if (server->kernel_features & MK_KERNEL_TCP_FASTOPEN) { + offset += snprintf(buffer, size - offset, "%s", "TCP_FASTOPEN "); + features++; + } + + if (server->kernel_features & MK_KERNEL_SO_REUSEPORT) { + if (server->scheduler_mode == MK_SCHEDULER_FAIR_BALANCING) { + offset += snprintf(buffer + offset, size - offset, + "%s!%s", ANSI_BOLD ANSI_RED, ANSI_RESET); + } + offset += snprintf(buffer + offset, size - offset, "%s", "SO_REUSEPORT "); + features++; + } + + if (server->kernel_features & MK_KERNEL_TCP_AUTOCORKING) { + snprintf(buffer + offset, size - offset, "%s", "TCP_AUTOCORKING "); + features++; + } + + return features; +} +#else +/* We still need to determine if this can be safely ignored or what do we need to do here */ + +int mk_kernel_version() +{ + return 1; +} + +int mk_kernel_features(int version) +{ + return 0; +} + +int mk_kernel_features_print(char* buffer, size_t size, + struct mk_server* server) +{ + return 0; +} +#endif diff --git a/fluent-bit/lib/monkey/mk_server/mk_lib.c b/fluent-bit/lib/monkey/mk_server/mk_lib.c new file mode 100644 index 00000000..c2940086 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_lib.c @@ -0,0 +1,796 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <signal.h> + +#include <mk_core/mk_pthread.h> + +#include <monkey/mk_lib.h> +#include <monkey/monkey.h> +#include <monkey/mk_stream.h> +#include <monkey/mk_thread.h> +#include <monkey/mk_scheduler.h> +#include <monkey/mk_fifo.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_tls.h> + +#define config_eq(a, b) strcasecmp(a, b) + +static inline int bool_val(char *v) +{ + if (strcasecmp(v, "On") == 0 || strcasecmp(v, "Yes") == 0) { + return MK_TRUE; + } + else if (strcasecmp(v, "Off") == 0 || strcasecmp(v, "No") == 0) { + return MK_FALSE; + } + + return -1; +} + +mk_ctx_t *mk_create() +{ + mk_ctx_t *ctx; + + ctx = mk_mem_alloc_z(sizeof(mk_ctx_t)); + if (!ctx) { + return NULL; + } + + /* Create Monkey server instance */ + ctx->server = mk_server_create(); + + /* + * FIFO + * ==== + * Before to prepare the background service, we create a MK_FIFO interface + * for further communication between the caller (user) and HTTP end-point + * callbacks. + */ + ctx->fifo = mk_fifo_create(NULL, ctx->server); + ctx->fifo->key = &mk_server_fifo_key; + + /* + * FIFO: Set workers callback associated to the Monkey scheduler to prepare them + * before to enter the event loop + */ + mk_sched_worker_cb_add(ctx->server, mk_fifo_worker_setup, ctx->fifo); + return ctx; +} + +int mk_destroy(mk_ctx_t *ctx) +{ + mk_fifo_destroy(ctx->fifo); + mk_mem_free(ctx); + + return 0; +} + +static inline int mk_lib_yield(mk_request_t *req) +{ + int ret; + struct mk_thread *th; + struct mk_channel *channel; + struct mk_sched_worker *sched; + + sched = mk_sched_get_thread_conf(); + if (!sched) { + return -1; + } + + th = MK_TLS_GET(mk_thread); + channel = req->session->channel; + + channel->thread = th; + + ret = mk_event_add(sched->loop, + channel->fd, + MK_EVENT_THREAD, + MK_EVENT_WRITE, channel->event); + if (ret == -1) { + return -1; + } + + /* Just wait */ + mk_thread_yield(th); + + if (channel->event->status & MK_EVENT_REGISTERED) { + /* We got a notification, remove the event registered + ret = mk_event_add(sched->loop, + channel->fd, + MK_EVENT_CONNECTION, + MK_EVENT_READ, channel->event); + mk_thread_yield(th); + */ + + ret = mk_event_del(sched->loop, channel->event); + } + + return 0; +} + +static void mk_lib_worker(void *data) +{ + int fd; + int bytes; + uint64_t val; + struct mk_server *server; + struct mk_event *event; + + mk_ctx_t *ctx = data; + server = ctx->server; + + /* Start the service */ + mk_server_setup(server); + mk_server_loop(server); + + /* + * Give a second to the parent context to avoid consume an event + * we should not read at the moment (SIGNAL_START). + */ + sleep(1); + + /* Wait for events */ + mk_event_wait(server->lib_evl); + mk_event_foreach(event, server->lib_evl) { + fd = event->fd; + +#ifdef _WIN32 + bytes = recv(fd, &val, sizeof(uint64_t), MSG_WAITALL); +#else + bytes = read(fd, &val, sizeof(uint64_t)); +#endif + + if (bytes <= 0) { + return; + } + + if (val == MK_SERVER_SIGNAL_STOP) { + break; + } + } + + mk_event_loop_destroy(server->lib_evl); + mk_exit_all(server); + pthread_kill(pthread_self(), 0); + + return; +} + +int mk_start(mk_ctx_t *ctx) +{ + int fd; + int bytes; + int ret; + uint64_t val; + pthread_t tid; + struct mk_event *event; + struct mk_server *server; + + server = ctx->server; + + ret = mk_utils_worker_spawn(mk_lib_worker, ctx, &tid); + if (ret == -1) { + return -1; + } + ctx->worker_tid = tid; + + /* Wait for the started signal so we can return to the caller */ + mk_event_wait(server->lib_evl_start); + mk_event_foreach(event, server->lib_evl_start) { + fd = event->fd; + + /* When using libevent _mk_event_channel_create creates a unix socket + * instead of a pipe and windows doesn't us calling read / write on a + * socket instead of recv / send + */ +#ifdef _WIN32 + bytes = recv(fd, &val, sizeof(uint64_t), MSG_WAITALL); +#else + bytes = read(fd, &val, sizeof(uint64_t)); +#endif + + if (bytes <= 0) { + ret = -1; + break; + } + + if (val == MK_SERVER_SIGNAL_START) { + ret = 0; + break; + } + else { + ret = -1; + break; + } + } + + mk_event_loop_destroy(server->lib_evl_start); + if (ret == -1) { + mk_stop(ctx); + } + + return ret; +} + +int mk_stop(mk_ctx_t *ctx) +{ + int n; + uint64_t val; + int8_t scheduler_mode ; + struct mk_server *server = ctx->server; + + /* Keep track of the scheduler mode on stack, since the + * the worker may free the server before we need the info. + */ + scheduler_mode = server->scheduler_mode; + + val = MK_SERVER_SIGNAL_STOP; + + /* Send a stop signal to the main lib channel to abort. + * + * MK_SCHEDULER_FAIR_BALANCING: this signal will be + * consumed by mk_server_loop_balancer. + * + * MK_SCHEDULER_REUSEPORT: this signal will be consumed + * by mk_lib_worker. + */ +#ifdef _WIN32 + n = send(server->lib_ch_manager[1], &val, sizeof(val), 0); +#else + n = write(server->lib_ch_manager[1], &val, sizeof(val)); +#endif + if (n <= 0) { + perror("write"); + return -1; + } + + /* In MK_SCHEDULER_FAIR_BALANCING mode, we need one more + * stop signal to abort mk_lib_worker. + */ + if (scheduler_mode == MK_SCHEDULER_FAIR_BALANCING) { + /* Give mk_server_loop_balancer time to clean up. */ + sleep(1); + + /* Send a signal for mk_lib_worker to abort */ +#ifdef _WIN32 + n = send(server->lib_ch_manager[1], &val, sizeof(val), 0); +#else + n = write(server->lib_ch_manager[1], &val, sizeof(val)); +#endif + if (n <= 0) { + perror("write"); + return -1; + } + } + + /* Wait for the child thread to exit */ + pthread_join(ctx->worker_tid, NULL); + return 0; +} + +/* + * Instruct Monkey core to invoke a callback function inside each worker + * started by the scheduler. + */ +int mk_worker_callback(mk_ctx_t *ctx, + void (*cb_func) (void *), + void *data) +{ + return mk_sched_worker_cb_add(ctx->server, cb_func, data); +} + +int mk_config_set_property(struct mk_server *server, char *k, char *v) +{ + int b; + int ret; + int num; + unsigned long len; + + if (config_eq(k, "Listen") == 0) { + ret = mk_config_listen_parse(v, server); + if (ret != 0) { + return -1; + } + } + else if (config_eq(k, "Workers") == 0) { + num = atoi(v); + if (num <= 0) { + server->workers = mk_utils_get_system_core_count(); + } + else { + server->workers = num; + } + } + else if (config_eq(k, "Timeout") == 0) { + num = atoi(v); + if (num <= 0) { + return -1; + } + server->timeout = num; + } + else if (config_eq(k, "KeepAlive") == 0) { + b = bool_val(v); + if (b == -1) { + return -1; + } + server->keep_alive = b; + } + else if (config_eq(k, "MaxKeepAliveRequest") == 0) { + num = atoi(v); + if (num <= 0) { + return -1; + } + server->max_keep_alive_request = num; + } + else if (config_eq(k, "KeepAliveTimeout") == 0) { + num = atoi(v); + if (num <= 0) { + return -1; + } + server->keep_alive_timeout = num; + } + else if (config_eq(k, "UserDir") == 0) { + server->conf_user_pub = mk_string_dup(v); + } + else if (config_eq(k, "IndexFile") == 0) { + server->index_files = mk_string_split_line(v); + if (!server->index_files) { + return -1; + } + } + else if (config_eq(k, "HideVersion") == 0) { + b = bool_val(v); + if (b == -1) { + return -1; + } + server->hideversion = b; + } + else if (config_eq(k, "Resume") == 0) { + b = bool_val(v); + if (b == -1) { + return -1; + } + server->resume = b; + } + else if (config_eq(k, "MaxRequestSize") == 0) { + num = atoi(v); + if (num <= 0) { + return -1; + } + server->max_request_size = num; + } + else if (config_eq(k, "SymLink") == 0) { + b = bool_val(v); + if (b == -1) { + return -1; + } + server->symlink = b; + } + else if (config_eq(k, "DefaultMimeType") == 0) { + mk_string_build(&server->mimetype_default_str, &len, "%s\r\n", v); + } + else if (config_eq(k, "FDT") == 0) { + b = bool_val(v); + if (b == -1) { + return -1; + } + server->fdt = b; + } + + return 0; +} + +int mk_config_set(mk_ctx_t *ctx, ...) +{ + int ret; + char *key; + char *value; + va_list va; + + va_start(va, ctx); + + while ((key = va_arg(va, char *))) { + value = va_arg(va, char *); + if (!value) { + /* Wrong parameter */ + va_end(va); + return -1; + } + + ret = mk_config_set_property(ctx->server, key, value); + if (ret != 0) { + va_end(va); + return -1; + } + } + + va_end(va); + return 0; +} + +/* Given a vhost id, return the vhost context */ +struct mk_vhost *mk_vhost_lookup(mk_ctx_t *ctx, int id) +{ + struct mk_vhost *host; + struct mk_list *head; + + mk_list_foreach(head, &ctx->server->hosts) { + host = mk_list_entry(head, struct mk_vhost, _head); + if (host->id == id) { + return host; + } + } + + return NULL; +} + +int mk_vhost_create(mk_ctx_t *ctx, char *name) +{ + struct mk_vhost *h; + struct mk_vhost_alias *halias; + + /* Virtual host */ + h = mk_mem_alloc_z(sizeof(struct mk_vhost)); + if (!h) { + return -1; + } + + /* Assign a virtual host id, we just set based on list size */ + h->id = mk_list_size(&ctx->server->hosts); + mk_list_init(&h->error_pages); + mk_list_init(&h->server_names); + mk_list_init(&h->handlers); + + /* Host alias */ + halias = mk_mem_alloc_z(sizeof(struct mk_vhost_alias)); + if (!halias) { + mk_mem_free(h); + return -1; + } + + /* Host name */ + if (!name) { + halias->name = mk_string_dup("127.0.0.1"); + } + else { + halias->name = mk_string_dup(name); + } + mk_list_add(&halias->_head, &h->server_names); + mk_list_add(&h->_head, &ctx->server->hosts); + + /* Return the host id, that number is enough for further operations */ + return h->id; +} + +static int mk_vhost_set_property(struct mk_vhost *vh, char *k, char *v) +{ + struct mk_vhost_alias *ha; + + if (config_eq(k, "Name") == 0) { + ha = mk_mem_alloc(sizeof(struct mk_vhost_alias)); + if (!ha) { + return -1; + } + ha->name = mk_string_dup(v); + ha->len = strlen(v); + mk_list_add(&ha->_head, &vh->server_names); + } + else if (config_eq(k, "DocumentRoot") == 0) { + vh->documentroot.data = mk_string_dup(v); + vh->documentroot.len = strlen(v); + } + + return 0; +} + +int mk_vhost_set(mk_ctx_t *ctx, int vid, ...) +{ + int ret; + char *key; + char *value; + va_list va; + struct mk_vhost *vh; + + /* Lookup the virtual host */ + vh = mk_vhost_lookup(ctx, vid); + if (!vh) { + return -1; + } + + va_start(va, vid); + + while ((key = va_arg(va, char *))) { + value = va_arg(va, char *); + if (!value) { + /* Wrong parameter */ + return -1; + } + + ret = mk_vhost_set_property(vh, key, value); + if (ret != 0) { + va_end(va); + return -1; + } + } + + va_end(va); + return 0; +} + +int mk_vhost_handler(mk_ctx_t *ctx, int vid, char *regex, + void (*cb)(mk_request_t *, void *), void *data) +{ + struct mk_vhost *vh; + struct mk_vhost_handler *handler; + void (*_cb) (struct mk_http_request *, void *); + + /* Lookup the virtual host */ + vh = mk_vhost_lookup(ctx, vid); + if (!vh) { + return -1; + } + + _cb = cb; + handler = mk_vhost_handler_match(regex, _cb, data); + if (!handler) { + return -1; + } + mk_list_add(&handler->_head, &vh->handlers); + + return 0; +} + +/* Flush streams data associated to a request in question */ +int mk_http_flush(mk_request_t *req) +{ + int ret; + size_t out_bytes = 0; + + ret = mk_channel_stream_write(&req->stream, &out_bytes); + return ret; +} + +int mk_http_status(mk_request_t *req, int status) +{ + req->headers.status = status; + return 0; +} + +/* Append a response header */ +int mk_http_header(mk_request_t *req, + char *key, int key_len, + char *val, int val_len) +{ + int pos; + int len; + char *buf; + struct response_headers *h; + + h = &req->headers; + if (!h->_extra_rows) { + h->_extra_rows = mk_iov_create(MK_PLUGIN_HEADER_EXTRA_ROWS * 2, 0); + if (!h->_extra_rows) { + return -1; + } + } + + len = key_len + val_len + 4; + buf = mk_mem_alloc(len); + if (!buf) { + /* we don't free extra_rows as it's released later */ + return -1; + } + + /* Compose the buffer */ + memcpy(buf, key, key_len); + pos = key_len; + buf[pos++] = ':'; + buf[pos++] = ' '; + memcpy(buf + pos, val, val_len); + pos += val_len; + buf[pos++] = '\r'; + buf[pos++] = '\n'; + + /* Add the new buffer */ + mk_iov_add(h->_extra_rows, buf, pos, MK_TRUE); + + return 0; +} + +static inline int chunk_header(long num, char *out) +{ + int i = 1; + int j, c; + int remainder; + int quotient; + char tmp[32]; + char hex[] = "0123456789ABCDEF"; + + if (num == 0) { + out[0] = '0'; + out[1] = '\r'; + out[2] = '\n'; + out[3] = '\r'; + out[4] = '\n'; + out[5] = '\0'; + return 5; + } + + quotient = num; + while (quotient != 0) { + remainder = quotient % 16; + tmp[i++] = hex[remainder]; + quotient = quotient / 16; + } + + c = 0; + for (j = i -1 ; j > 0; j--, c++) { + out[c] = tmp[j]; + } + + out[c++] = '\r'; + out[c++] = '\n'; + out[c] = '\0'; + + return c; +} + +static void free_chunk_header(struct mk_stream_input *input) +{ + mk_mem_free(input->buffer); + input->buffer = NULL; +} + + +/* Check if response headers were processed, otherwise prepare them */ +static int headers_setup(mk_request_t *req) +{ + /* + * Let's keep it simple for now: if the headers have not been sent, do it + * now and then send the body content just queued. + */ + if (req->headers.sent == MK_FALSE) { + /* Force chunked-transfer encoding */ + if (req->protocol == MK_HTTP_PROTOCOL_11) { + req->headers.transfer_encoding = MK_HEADER_TE_TYPE_CHUNKED; + } + else { + req->headers.content_length = -1; + } + mk_header_prepare(req->session, req, req->session->server); + } + return 0; +} + +/* Enqueue some data for the body response */ +int mk_http_send(mk_request_t *req, char *buf, size_t len, + void (*cb_finish)(mk_request_t *)) +{ + int chunk_len; + int ret; + char *tmp; + char chunk_pre[32]; + (void) cb_finish; + + if (req->session->channel->status != MK_CHANNEL_OK) { + return -1; + } + + if (req->headers.status == -1) { + /* Cannot append data if the status have not been set */ + mk_err("HTTP: set the response status first"); + return -1; + } + + /* Chunk encoding prefix */ + if (req->protocol == MK_HTTP_PROTOCOL_11) { + chunk_len = chunk_header(len, chunk_pre); + tmp = mk_string_dup(chunk_pre); + if (!tmp) { + return -1; + } + ret = mk_stream_in_raw(&req->stream, NULL, + tmp, chunk_len, NULL, free_chunk_header); + if (ret != 0) { + return -1; + } + } + + /* Append raw data */ + if (len > 0) { + ret = mk_stream_in_raw(&req->stream, NULL, + buf, len, NULL, NULL); + if (ret == 0) { + /* Update count of bytes */ + req->stream_size += len; + } + } + + if (req->protocol == MK_HTTP_PROTOCOL_11 && len > 0) { + ret = mk_stream_in_raw(&req->stream, NULL, + "\r\n", 2, NULL, NULL); + } + + /* Validate if the response headers are ready */ + headers_setup(req); + + /* Flush channel data */ + ret = mk_http_flush(req); + + /* + * Flush have been done, before to return our original caller, we want to yield + * and give some execution time to the event loop to avoid possible blocking + * since the caller might be using this mk_http_send() in a loop. + */ + mk_lib_yield(req); + return ret; +} + +int mk_http_done(mk_request_t *req) +{ + if (req->session->channel->status != MK_CHANNEL_OK) { + return -1; + } + + /* Validate if the response headers are ready */ + headers_setup(req); + + if (req->headers.transfer_encoding == MK_HEADER_TE_TYPE_CHUNKED) { + /* Append end-of-chunk bytes */ + mk_http_send(req, NULL, 0, NULL); + } + else { + mk_http_send(req, NULL, 0, NULL); + } + + if (req->session->close_now == MK_TRUE) { + mk_lib_yield(req); + } + + return 0; +} + +/* Create a messaging queue end-point */ +int mk_mq_create(mk_ctx_t *ctx, char *name, void (*cb), void *data) +{ + int id; + + id = mk_fifo_queue_create(ctx->fifo, name, cb, data); + return id; +} + +/* Write a message to a specific queue ID */ +int mk_mq_send(mk_ctx_t *ctx, int qid, void *data, size_t size) +{ + return mk_fifo_send(ctx->fifo, qid, data, size); +} + +int mk_main() +{ + while (1) { + sleep(60); + } + + return 0; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_mimetype.c b/fluent-bit/lib/monkey/mk_server/mk_mimetype.c new file mode 100644 index 00000000..b86b4ef1 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_mimetype.c @@ -0,0 +1,227 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <string.h> +#include <ctype.h> + +#include <mk_core/mk_unistd.h> + +#include <monkey/monkey.h> +#include <monkey/mk_mimetype.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_config.h> +#include <monkey/mk_core.h> +#include <monkey/mk_http.h> + +struct mk_mimetype *mimetype_default; + +static int rbtree_compare(const void *lhs, const void *rhs) +{ + return strcmp((const char *)lhs, (const char *)rhs); +} + +/* Match mime type for requested resource */ +struct mk_mimetype *mk_mimetype_lookup(struct mk_server *server, char *name) +{ + int cmp; + struct rb_tree_node *node = server->mimetype_rb_head.root; + + while (node) { + struct mk_mimetype *entry = container_of(node, struct mk_mimetype, _rb_head); + cmp = strcmp(name, entry->name); + if (cmp < 0) + node = node->left; + else if (cmp > 0) + node = node->right; + else { + return entry; + } + } + return NULL; +} + +int mk_mimetype_add(struct mk_server *server, char *name, const char *type) +{ + int len = strlen(type) + 3; + char *p; + struct mk_mimetype *new_mime; + + /* make sure we register the extension in lower case */ + p = name; + for ( ; *p; ++p) *p = tolower(*p); + + new_mime = mk_mem_alloc_z(sizeof(struct mk_mimetype)); + if (!new_mime) { + return -1; + } + new_mime->name = mk_string_dup(name); + if (!new_mime->name) { + mk_mem_free(new_mime); + return -1; + } + new_mime->type.data = mk_mem_alloc(len); + if (!new_mime->type.data) { + mk_mem_free(new_mime->name); + mk_mem_free(new_mime); + return -1; + } + new_mime->type.len = len - 1; + new_mime->header_type.data = mk_mem_alloc(len + 32); + if (!new_mime->header_type.data) { + mk_mem_free(new_mime->name); + mk_mem_free(new_mime->type.data); + mk_mem_free(new_mime); + return -1; + } + new_mime->header_type.len = snprintf(new_mime->header_type.data, + len + 32, + "Content-Type: %s\r\n", + type); + strcpy(new_mime->type.data, type); + strcat(new_mime->type.data, MK_CRLF); + new_mime->type.data[len-1] = '\0'; + + /* Insert the node into the RBT */ + rb_tree_insert(&server->mimetype_rb_head, + new_mime->name, &new_mime->_rb_head); + + /* Add to linked list head */ + mk_list_add(&new_mime->_head, &server->mimetype_list); + + return 0; +} + +int mk_mimetype_init(struct mk_server *server) +{ + char *name; + int ret; + + /* Initialize the heads */ + mk_list_init(&server->mimetype_list); + rb_tree_new(&server->mimetype_rb_head, rbtree_compare); + + name = mk_string_dup(MIMETYPE_DEFAULT_NAME); + if (server->mimetype_default_str) { + ret = mk_mimetype_add(server, name, server->mimetype_default_str); + } + else { + ret = mk_mimetype_add(server, name, MIMETYPE_DEFAULT_TYPE); + } + if (ret < 0) { + mk_mem_free(name); + return -1; + } + server->mimetype_default = mk_list_entry_first(&server->mimetype_list, + struct mk_mimetype, + _head); + mk_mem_free(name); + return 0; +} + +/* Load the two mime arrays into memory */ +int mk_mimetype_read_config(struct mk_server *server) +{ + char path[MK_MAX_PATH]; + struct mk_rconf *cnf; + struct mk_rconf_section *section; + struct mk_rconf_entry *entry; + struct mk_list *head; + struct file_info f_info; + int ret; + + if (!server->conf_mimetype) { + return -1; + } + + /* Read mime types configuration file */ + snprintf(path, MK_MAX_PATH, "%s/%s", + server->path_conf_root, + server->conf_mimetype); + + ret = mk_file_get_info(path, &f_info, MK_FILE_EXISTS); + if (ret == -1 || f_info.is_file == MK_FALSE) { + snprintf(path, MK_MAX_PATH, "%s", server->conf_mimetype); + } + cnf = mk_rconf_open(path); + if (!cnf) { + mk_warn("[mime] skipping mimetype configuration file"); + return -1; + } + + /* Get MimeTypes tag */ + section = mk_rconf_section_get(cnf, "MIMETYPES"); + if (!section) { + mk_err("[mime] Invalid mime type file"); + return -1; + } + + mk_list_foreach(head, §ion->entries) { + entry = mk_list_entry(head, struct mk_rconf_entry, _head); + if (!entry->key || !entry->val) { + continue; + } + + if (mk_mimetype_add(server, entry->key, entry->val) != 0) { + mk_err("[mime] Error loading Mime Types"); + return -1; + } + } + + mk_rconf_free(cnf); + + return 0; +} + +struct mk_mimetype *mk_mimetype_find(struct mk_server *server, mk_ptr_t *filename) +{ + int j, len; + + j = len = filename->len; + + /* looking for extension */ + while (j >= 0 && filename->data[j] != '.') { + j--; + } + + if (j <= 0) { + return NULL; + } + + return mk_mimetype_lookup(server, filename->data + j + 1); +} + +void mk_mimetype_free_all(struct mk_server *server) +{ + struct mk_list *head; + struct mk_list *tmp; + struct mk_mimetype *mime; + + mk_list_foreach_safe(head, tmp, &server->mimetype_list) { + mime = mk_list_entry(head, struct mk_mimetype, _head); + mk_ptr_free(&mime->type); + mk_mem_free(mime->name); + mk_mem_free(mime->header_type.data); + mk_mem_free(mime); + } +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_net.c b/fluent-bit/lib/monkey/mk_server/mk_net.c new file mode 100644 index 00000000..de183de4 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_net.c @@ -0,0 +1,284 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2016 Monkey Software LLC <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/mk_core.h> +#include <monkey/mk_net.h> +#include <monkey/mk_scheduler.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_thread.h> +#include <monkey/mk_tls.h> + +#ifdef _WIN32 +#include <winsock2.h> +#include <afunix.h> +#else +#include <sys/socket.h> +#include <netinet/tcp.h> +#endif + +/* Initialize the network stack*/ +int mk_net_init() +{ +#ifdef _WIN32 + int result; + WSADATA wsa_data; + static int initialized = 0; + + if(0 != initialized) { + return 0; + } + + result = WSAStartup(MAKEWORD(2, 2), &wsa_data); + + if(0 != result) { + if(WSAEINPROGRESS == result) + { + Sleep(100); /* Let the other thread finish initializing the stack */ + + return 0; + } + + return -1; + } + + initialized = 1; +#endif + + return 0; +} + +/* Connect to a TCP socket server */ +static int mk_net_fd_connect(int fd, char *host, unsigned long port) +{ + int ret; + struct addrinfo hints; + struct addrinfo *res; + char _port[6]; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + snprintf(_port, sizeof(_port), "%lu", port); + ret = getaddrinfo(host, _port, &hints, &res); + if (ret != 0) { + return -1; + } + + ret = connect(fd, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + + return ret; +} + +struct mk_net_connection *mk_net_conn_create(char *addr, int port) +{ + int fd; + int ret; + int error = 0; + socklen_t len = sizeof(error); + struct mk_sched_worker *sched; + struct mk_net_connection *conn; + + /* Allocate connection context */ + conn = mk_mem_alloc(sizeof(struct mk_net_connection)); + if (!conn) { + return NULL; + } + + /* Create socket */ + fd = mk_socket_create(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + mk_mem_free(conn); + return NULL; + } + + /* Make socket async */ + mk_socket_set_nonblocking(fd); + conn->fd = fd; + + ret = mk_net_fd_connect(conn->fd, addr, port); + if (ret == -1) { + if (errno != EINPROGRESS) { + close(fd); + mk_mem_free(conn); + return NULL; + } + + MK_EVENT_NEW(&conn->event); + + sched = mk_sched_get_thread_conf(); + // FIXME: not including the thread + //conn->thread = mk_thread_get(); + ret = mk_event_add(sched->loop, conn->fd, MK_EVENT_THREAD, + MK_EVENT_WRITE, &conn->event); + if (ret == -1) { + close(fd); + mk_mem_free(conn); + return NULL; + } + + /* + * Return the control to the parent caller, we need to wait for + * the event loop to get back to us. + */ + mk_thread_yield(conn->thread); + + /* We got a notification, remove the event registered */ + ret = mk_event_del(sched->loop, &conn->event); + + /* Check the connection status */ + if (conn->event.mask & MK_EVENT_WRITE) { + ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len); + if (ret == -1) { + close(fd); + mk_mem_free(conn); + return NULL; + } + + if (error != 0) { + /* Connection is broken, not much to do here */ + fprintf(stderr, "Async connection failed %s:%i\n", + conn->host, conn->port); + close(fd); + mk_mem_free(conn); + return NULL; + } + MK_EVENT_NEW(&conn->event); + return conn; + } + else { + close(fd); + mk_mem_free(conn); + return NULL; + } + } + + return NULL; +} + +int mk_net_conn_write(struct mk_channel *channel, + void *data, size_t len) +{ + int ret = 0; + int error; + ssize_t bytes; + size_t total = 0; + size_t send; + socklen_t slen = sizeof(error); + struct mk_thread *th = MK_TLS_GET(mk_thread); + struct mk_sched_worker *sched; + + sched = mk_sched_get_thread_conf(); + if (!sched) { + return -1; + } + + retry: + error = 0; + + if (len - total > 524288) { + send = 524288; + } + else { + send = (len - total); + } + + send = len - total; + bytes = channel->io->write(channel->io->plugin, channel->fd, (uint8_t *)data + total, send); + if (bytes == -1) { + if (errno == EAGAIN) { + MK_EVENT_NEW(channel->event); + channel->thread = th; + ret = mk_event_add(sched->loop, + channel->fd, + MK_EVENT_THREAD, + MK_EVENT_WRITE, channel->event); + if (ret == -1) { + /* + * If we failed here there no much that we can do, just + * let the caller we failed + */ + return -1; + } + + /* + * Return the control to the parent caller, we need to wait for + * the event loop to get back to us. + */ + mk_thread_yield(th); + + /* We got a notification, remove the event registered */ + ret = mk_event_del(sched->loop, channel->event); + if (ret == -1) { + return -1; + } + + /* Check the connection status */ + if (channel->event->mask & MK_EVENT_WRITE) { + ret = getsockopt(channel->fd, SOL_SOCKET, SO_ERROR, &error, &slen); + if (ret == -1) { + fprintf(stderr, "[io] could not validate socket status"); + return -1; + } + + if (error != 0) { + return -1; + } + + MK_EVENT_NEW(channel->event); + goto retry; + } + else { + return -1; + } + + } + else { + return -1; + } + } + + /* Update counters */ + total += bytes; + if (total < len) { + channel->thread = th; + ret = mk_event_add(sched->loop, + channel->fd, + MK_EVENT_THREAD, + MK_EVENT_WRITE, channel->event); + if (ret == -1) { + /* + * If we failed here there no much that we can do, just + * let the caller we failed + */ + return -1; + } + + mk_thread_yield(th); + goto retry; + } + + if (channel->event->status & MK_EVENT_REGISTERED) { + /* We got a notification, remove the event registered */ + ret = mk_event_del(sched->loop, channel->event); + } + + return total; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_plugin.c b/fluent-bit/lib/monkey/mk_server/mk_plugin.c new file mode 100644 index 00000000..50e2886b --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_plugin.c @@ -0,0 +1,804 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/monkey.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_http.h> +#include <monkey/mk_clock.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_mimetype.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_static_plugins.h> +#include <monkey/mk_plugin_stage.h> +#include <monkey/mk_core.h> +#include <monkey/mk_net.h> + +#ifndef _WIN32 +#include <dlfcn.h> +#include <err.h> +#endif + +enum { + bufsize = 256 +}; + +static struct plugin_stagemap *plg_stagemap; +struct plugin_network_io *plg_netiomap; + +struct mk_plugin *mk_plugin_lookup(char *shortname, struct mk_server *server) +{ + struct mk_list *head; + struct mk_plugin *p = NULL; + + mk_list_foreach(head, &server->plugins) { + p = mk_list_entry(head, struct mk_plugin, _head); + if (strcmp(p->shortname, shortname) == 0){ + return p; + } + } + + return NULL; +} + +void *mk_plugin_load_dynamic(const char *path) +{ + void *handle; + +#ifdef _WIN32 + handle = (void *) LoadLibraryA(path); +#else + handle = dlopen(path, RTLD_LAZY); + + if (!handle) { + mk_warn("dlopen() %s", dlerror()); + } +#endif + + return handle; +} + +void *mk_plugin_load_symbol(void *handler, const char *symbol) +{ + void *s; + +#ifdef _WIN32 + s = GetProcAddress((HMODULE)handler, symbol); +#else + dlerror(); + s = dlsym(handler, symbol); + if (dlerror() != NULL) { + return NULL; + } +#endif + + return s; +} + +/* Initialize a plugin, trigger the init_plugin callback */ +static int mk_plugin_init(struct plugin_api *api, + struct mk_plugin *plugin, + struct mk_server *server) +{ + int ret; + unsigned long len; + char path[1024]; + char *conf_dir = NULL; + struct file_info f_info; + + MK_TRACE("Load Plugin: '%s'", plugin->shortname); + + snprintf(path, 1024, "%s/%s", + server->path_conf_root, server->conf_plugins); + ret = mk_file_get_info(path, &f_info, MK_FILE_READ); + if (ret == -1 || f_info.is_directory == MK_FALSE) { + snprintf(path, 1024, "%s", server->conf_plugins); + } + + /* Build plugin configuration path */ + mk_string_build(&conf_dir, + &len, + "%s/%s/", + path, plugin->shortname); + + /* Init plugin */ + plugin->api = api; + plugin->server_ctx = server; + + if (plugin->network != NULL) { + plugin->network->plugin = plugin; + } + + ret = plugin->init_plugin(plugin, conf_dir); + mk_mem_free(conf_dir); + + return ret; +} + + +/* + * Load a plugin into Monkey core, 'type' defines if it's a MK_PLUGIN_STATIC or + * a MK_PLUGIN_DYNAMIC. 'shortname' is mandatory and 'path' is only used when + * MK_PLUGIN_DYNAMIC is set and represents the absolute path of the shared + * library. + */ +struct mk_plugin *mk_plugin_load(int type, const char *shortname, + void *data, struct mk_server *server) +{ + char *path; + char symbol[64]; + void *handler; + struct mk_list *head; + struct mk_plugin *tmp; + struct mk_plugin *plugin = NULL; + struct mk_plugin_stage *stage; + + /* Set main struct name to reference */ + if (type == MK_PLUGIN_DYNAMIC) { + path = (char *) data; + handler = mk_plugin_load_dynamic(path); + if (!handler) { + return NULL; + } + + snprintf(symbol, sizeof(symbol) - 1, "mk_plugin_%s", shortname); + plugin = mk_plugin_load_symbol(handler, symbol); + if (!plugin) { + mk_warn("Plugin '%s' is not registering properly", path); +#ifdef _WIN32 + FreeLibrary((HMODULE)handler); +#else + dlclose(handler); +#endif + return NULL; + } + + /* Make sure this is not loaded twice (ref #218) */ + mk_list_foreach(head, &server->plugins) { + tmp = mk_list_entry(head, struct mk_plugin, _head); + if (tmp->load_type == MK_PLUGIN_STATIC && + strcmp(tmp->name, plugin->name) == 0){ + mk_warn("Plugin '%s' have been built-in.", + tmp->shortname); +#ifdef _WIN32 + FreeLibrary((HMODULE)handler); +#else + dlclose(handler); +#endif + return NULL; + } + } + + plugin->load_type = MK_PLUGIN_DYNAMIC; + plugin->handler = handler; + plugin->path = mk_string_dup(path); + } + else if (type == MK_PLUGIN_STATIC) { + plugin = (struct mk_plugin *) data; + plugin->load_type = MK_PLUGIN_STATIC; + } + + if (!plugin) { + return NULL; + } + + /* Validate all callbacks are set */ + if (!plugin->shortname || !plugin->name || !plugin->version || + !plugin->init_plugin || !plugin->exit_plugin) { + mk_warn("Plugin '%s' is not registering all fields properly", + shortname); + return NULL; + } + + if (plugin->hooks & MK_PLUGIN_NETWORK_LAYER) { + mk_bug(!plugin->network); + } + + mk_list_init(&plugin->stage_list); + if (plugin->hooks & MK_PLUGIN_STAGE) { + struct mk_plugin_stage *st; + + stage = plugin->stage; + if (stage->stage10) { + st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); + st->stage10 = stage->stage10; + st->plugin = plugin; + mk_list_add(&st->_head, &server->stage10_handler); + mk_list_add(&st->_parent_head, &plugin->stage_list); + } + if (stage->stage20) { + st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); + st->stage20 = stage->stage20; + st->plugin = plugin; + mk_list_add(&st->_head, &server->stage20_handler); + mk_list_add(&st->_parent_head, &plugin->stage_list); + } + if (stage->stage30) { + st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); + st->stage30 = stage->stage30; + st->plugin = plugin; + mk_list_add(&st->_head, &server->stage30_handler); + mk_list_add(&st->_parent_head, &plugin->stage_list); + } + if (stage->stage40) { + st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); + st->stage40 = stage->stage40; + st->plugin = plugin; + mk_list_add(&st->_head, &server->stage40_handler); + mk_list_add(&st->_parent_head, &plugin->stage_list); + } + if (stage->stage50) { + st = mk_mem_alloc(sizeof(struct mk_plugin_stage)); + st->stage50 = stage->stage50; + st->plugin = plugin; + mk_list_add(&st->_head, &server->stage50_handler); + mk_list_add(&st->_parent_head, &plugin->stage_list); + } + } + + if (type == MK_PLUGIN_DYNAMIC) { + /* Add Plugin to the end of the list */ + mk_list_add(&plugin->_head, &server->plugins); + } + + return plugin; +} + +void mk_plugin_unregister(struct mk_plugin *p) +{ + mk_mem_free(p->path); + mk_list_del(&p->_head); + if (p->load_type == MK_PLUGIN_DYNAMIC) { +#ifdef _WIN32 + FreeLibrary((HMODULE)p->handler); +#else + dlclose(p->handler); +#endif + } + +} + +void mk_plugin_api_init(struct mk_server *server) +{ + struct plugin_api *api; + + /* Create an instance of the API */ + api = mk_mem_alloc_z(sizeof(struct plugin_api)); + +#ifndef _WIN32 + __builtin_prefetch(api); +#endif + + /* Setup and connections list */ + /* FIXME: api->config = server; */ + + /* API plugins funcions */ + + /* Error helper */ + api->_error = mk_print; + + /* HTTP callbacks */ + api->http_request_end = mk_plugin_http_request_end; + api->http_request_error = mk_plugin_http_error; + + /* Memory callbacks */ + api->pointer_set = mk_ptr_set; + api->pointer_print = mk_ptr_print; + api->pointer_to_buf = mk_ptr_to_buf; + api->plugin_load_symbol = mk_plugin_load_symbol; + api->mem_alloc = mk_mem_alloc; + api->mem_alloc_z = mk_mem_alloc_z; + api->mem_realloc = mk_mem_realloc; + api->mem_free = mk_mem_free; + + /* String Callbacks */ + api->str_build = mk_string_build; + api->str_dup = mk_string_dup; + api->str_search = mk_string_search; + api->str_search_n = mk_string_search_n; + api->str_char_search = mk_string_char_search; + api->str_copy_substr = mk_string_copy_substr; + api->str_itop = mk_string_itop; + api->str_split_line = mk_string_split_line; + api->str_split_free = mk_string_split_free; + + /* File Callbacks */ + api->file_to_buffer = mk_file_to_buffer; + api->file_get_info = mk_file_get_info; + + /* HTTP Callbacks */ + api->header_prepare = mk_plugin_header_prepare; + api->header_add = mk_plugin_header_add; + api->header_get = mk_http_header_get; + api->header_set_http_status = mk_header_set_http_status; + + /* Channels / Streams */ + api->channel_new = mk_channel_new; + api->channel_flush = mk_channel_flush; + api->channel_write = mk_channel_write; + api->channel_append_stream = mk_channel_append_stream; + + /* IOV callbacks */ + api->iov_create = mk_iov_create; + api->iov_realloc = mk_iov_realloc; + api->iov_free = mk_iov_free; + api->iov_free_marked = mk_iov_free_marked; + api->iov_add = mk_iov_add; + api->iov_set_entry = mk_iov_set_entry; + api->iov_send = mk_iov_send; + api->iov_print = mk_iov_print; + + /* events mechanism */ + api->ev_loop_create = mk_event_loop_create; + api->ev_add = mk_event_add; + api->ev_del = mk_event_del; + api->ev_timeout_create = mk_event_timeout_create; + api->ev_channel_create = mk_event_channel_create; + api->ev_wait = mk_event_wait; + api->ev_backend = mk_event_backend; + + /* Mimetype */ + api->mimetype_lookup = mk_mimetype_lookup; + + /* Socket callbacks */ + api->socket_cork_flag = mk_socket_set_cork_flag; + api->socket_connect = mk_socket_connect; + api->socket_open = mk_socket_open; + api->socket_reset = mk_socket_reset; + api->socket_set_tcp_fastopen = mk_socket_set_tcp_fastopen; + api->socket_set_tcp_reuseport = mk_socket_set_tcp_reuseport; + api->socket_set_tcp_nodelay = mk_socket_set_tcp_nodelay; + api->socket_set_nonblocking = mk_socket_set_nonblocking; + api->socket_create = mk_socket_create; + api->socket_ip_str = mk_socket_ip_str; + + /* Async network */ + api->net_conn_create = mk_net_conn_create; + + /* Config Callbacks */ + api->config_create = mk_rconf_create; + api->config_open = mk_rconf_open; + api->config_free = mk_rconf_free; + api->config_section_get = mk_rconf_section_get; + api->config_section_get_key = mk_rconf_section_get_key; + + /* Scheduler and Event callbacks */ + api->sched_loop = mk_sched_loop; + api->sched_get_connection = mk_sched_get_connection; + api->sched_event_free = mk_sched_event_free; + api->sched_remove_client = mk_plugin_sched_remove_client; + api->sched_worker_info = mk_plugin_sched_get_thread_conf; + + /* Worker functions */ + api->worker_spawn = mk_utils_worker_spawn; + api->worker_rename = mk_utils_worker_rename; + + /* Time functions */ + api->time_unix = mk_plugin_time_now_unix; + api->time_to_gmt = mk_utils_utime2gmt; + api->time_human = mk_plugin_time_now_human; + + api->stacktrace = (void *) mk_utils_stacktrace; + api->kernel_version = mk_kernel_version; + api->kernel_features_print = mk_kernel_features_print; + api->plugins = &server->plugins; + + /* handler */ + api->handler_param_get = mk_handler_param_get; + + server->api = api; +} + +void mk_plugin_load_static(struct mk_server *server) +{ + /* Load static plugins */ + mk_list_init(&server->plugins); + mk_static_plugins(&server->plugins); +} + +void mk_plugin_load_all(struct mk_server *server) +{ + int ret; + char *tmp; + char *path; + char shortname[64]; + struct mk_plugin *p; + struct mk_rconf *cnf; + struct mk_rconf_section *section; + struct mk_rconf_entry *entry; + struct mk_list *head; + struct mk_list *htmp; + struct file_info f_info; + + mk_plugin_load_static(server); + mk_list_foreach_safe(head, htmp, &server->plugins) { + p = mk_list_entry(head, struct mk_plugin, _head); + + /* Load the static plugin */ + p = mk_plugin_load(MK_PLUGIN_STATIC, + p->shortname, + (void *) p, + server); + if (!p) { + continue; + } + ret = mk_plugin_init(server->api, p, server); + if (ret == -1) { + /* Free plugin, do not register, error initializing */ + mk_warn("Plugin initialization failed: %s", p->shortname); + mk_plugin_unregister(p); + continue; + } + else if (ret == -2) { + /* Do not register, just skip it */ + mk_plugin_unregister(p); + continue; + } + } + + /* In case there are not dynamic plugins */ + if (!server->conf_plugin_load) { + return; + } + + /* Read configuration file */ + path = mk_mem_alloc_z(1024); + snprintf(path, 1024, "%s/%s", server->path_conf_root, + server->conf_plugin_load); + ret = mk_file_get_info(path, &f_info, MK_FILE_READ); + if (ret == -1 || f_info.is_file == MK_FALSE) { + snprintf(path, 1024, "%s", server->conf_plugin_load); + } + + cnf = mk_rconf_open(path); + if (!cnf) { + mk_warn("No dynamic plugins loaded."); + mk_mem_free(path); + return; + } + + /* Read section 'PLUGINS' */ + section = mk_rconf_section_get(cnf, "PLUGINS"); + if (!section) { + exit(EXIT_FAILURE); + } + + /* Read key entries */ + mk_list_foreach_safe(head, htmp, §ion->entries) { + entry = mk_list_entry(head, struct mk_rconf_entry, _head); + if (strcasecmp(entry->key, "Load") == 0) { + + /* Get plugin 'shortname' */ + tmp = memrchr(entry->val, '-', strlen(entry->val)); + ++tmp; + memset(shortname, '\0', sizeof(shortname) - 1); + strncpy(shortname, tmp, strlen(tmp) - 3); + + /* Load the dynamic plugin */ + p = mk_plugin_load(MK_PLUGIN_DYNAMIC, + shortname, + entry->val, + server); + if (!p) { + mk_warn("Invalid plugin '%s'", entry->val); + continue; + } + + ret = mk_plugin_init(server->api, p, server); + if (ret < 0) { + /* Free plugin, do not register */ + MK_TRACE("Unregister plugin '%s'", p->shortname); + mk_plugin_unregister(p); + continue; + } + } + } + + /* Look for plugins thread key data */ + mk_plugin_preworker_calls(server); + mk_vhost_map_handlers(server); + mk_mem_free(path); + mk_rconf_free(cnf); +} + +static void mk_plugin_exit_stages(struct mk_plugin *p) +{ + struct mk_list *tmp; + struct mk_list *head; + struct mk_plugin_stage *st; + + mk_list_foreach_safe(head, tmp, &p->stage_list) { + st = mk_list_entry(head, struct mk_plugin_stage, _parent_head); + + /* remove from direct config->stageN head list */ + mk_list_del(&st->_head); + + /* remove from plugin->stage_lists */ + mk_list_del(&st->_parent_head); + mk_mem_free(st); + } +} + +/* Invoke all plugins 'exit' hook and free resources by the plugin interface */ +void mk_plugin_exit_all(struct mk_server *server) +{ + struct mk_plugin *plugin; + struct mk_list *head, *tmp; + + /* Plugins */ + mk_list_foreach(head, &server->plugins) { + plugin = mk_list_entry(head, struct mk_plugin, _head); + plugin->exit_plugin(plugin); + } + + /* Plugin interface it self */ + mk_list_foreach_safe(head, tmp, &server->plugins) { + plugin = mk_list_entry(head, struct mk_plugin, _head); + mk_list_del(&plugin->_head); + mk_plugin_exit_stages(plugin); + + if (plugin->load_type == MK_PLUGIN_DYNAMIC) { + mk_mem_free(plugin->path); +#ifdef _WIN32 + FreeLibrary((HMODULE)plugin->handler); +#else + dlclose(plugin ->handler); +#endif + } + else if (plugin->load_type == MK_PLUGIN_STATIC) { + if (plugin->network != NULL) { + mk_mem_free(plugin->network); + } + + mk_mem_free(plugin); + } + } + + mk_mem_free(server->api); + mk_mem_free(plg_stagemap); +} + +/* + * When a worker is exiting, it invokes this function to release any plugin + * associated data. + */ +void mk_plugin_exit_worker() +{ +} + +/* This function is called by every created worker + * for plugins which need to set some data under a thread + * context + */ +void mk_plugin_core_process(struct mk_server *server) +{ + struct mk_plugin *node; + struct mk_list *head; + + mk_list_foreach(head, &server->plugins) { + node = mk_list_entry(head, struct mk_plugin, _head); + + /* Init plugin */ + if (node->master_init) { + node->master_init(server); + } + } +} + +/* This function is called by every created worker + * for plugins which need to set some data under a thread + * context + */ +void mk_plugin_core_thread(struct mk_server *server) +{ + + struct mk_plugin *node; + struct mk_list *head; + + mk_list_foreach(head, &server->plugins) { + node = mk_list_entry(head, struct mk_plugin, _head); + + /* Init plugin thread context */ + if (node->worker_init) { + node->worker_init(server); + } + } +} + +/* This function is called by Monkey *outside* of the + * thread context for plugins, so here's the right + * place to set pthread keys or similar + */ +void mk_plugin_preworker_calls(struct mk_server *server) +{ + int ret; + struct mk_plugin *node; + struct mk_list *head; + + mk_list_foreach(head, &server->plugins) { + node = mk_list_entry(head, struct mk_plugin, _head); + + /* Init pthread keys */ + if (node->thread_key) { + MK_TRACE("[%s] Set thread key", node->shortname); + + ret = pthread_key_create(node->thread_key, NULL); + if (ret != 0) { + mk_err("Plugin Error: could not create key for %s", + node->shortname); + } + } + } +} + +int mk_plugin_http_error(int http_status, struct mk_http_session *cs, + struct mk_http_request *sr, + struct mk_plugin *plugin) +{ + return mk_http_error(http_status, cs, sr, plugin->server_ctx); +} + + +int mk_plugin_http_request_end(struct mk_plugin *plugin, + struct mk_http_session *cs, int close) +{ + int ret; + int con; + struct mk_http_request *sr; + struct mk_server *server = plugin->server_ctx; + + MK_TRACE("[FD %i] PLUGIN HTTP REQUEST END", cs->socket); + + cs->status = MK_REQUEST_STATUS_INCOMPLETE; + if (mk_list_is_empty(&cs->request_list) == 0) { + MK_TRACE("[FD %i] Tried to end non-existing request.", cs->socket); + return -1; + } + + sr = mk_list_entry_last(&cs->request_list, struct mk_http_request, _head); + mk_plugin_stage_run_40(cs, sr, server); + + if (close == MK_TRUE) { + cs->close_now = MK_TRUE; + } + + /* Let's check if we should ask to finalize the connection or not */ + ret = mk_http_request_end(cs, server); + MK_TRACE("[FD %i] HTTP session end = %i", cs->socket, ret); + if (ret < 0) { + con = mk_sched_event_close(cs->conn, mk_sched_get_thread_conf(), + MK_EP_SOCKET_DONE, server); + if (con != 0) { + return con; + } + else { + return -1; + } + } + + return ret; +} + +/* Plugin epoll event handlers + * --------------------------- + * this functions are called by connection.c functions as mk_conn_read(), + * mk_conn_write(),mk_conn_error(), mk_conn_close() and mk_conn_timeout(). + * + * Return Values: + * ------------- + * MK_PLUGIN_RET_EVENT_NOT_ME: There's no plugin hook associated + */ + +void mk_plugin_event_bad_return(const char *hook, int ret) +{ + mk_err("[%s] Not allowed return value %i", hook, ret); +} + +int mk_plugin_time_now_unix(struct mk_server *server) +{ + return server->clock_context->log_current_utime; +} + +mk_ptr_t *mk_plugin_time_now_human(struct mk_server *server) +{ + return &server->clock_context->log_current_time; +} + +int mk_plugin_sched_remove_client(int socket, struct mk_server *server) +{ + struct mk_sched_conn *conn; + struct mk_sched_worker *sched; + + MK_TRACE("[FD %i] remove client", socket); + + sched = mk_sched_get_thread_conf(); + conn = mk_sched_get_connection(sched, socket); + if (!conn) { + return -1; + } + + return mk_sched_remove_client(conn, sched, server); +} + +int mk_plugin_header_prepare(struct mk_plugin *plugin, + struct mk_http_session *cs, + struct mk_http_request *sr) +{ + return mk_header_prepare(cs, sr, plugin->server_ctx); +} + + +int mk_plugin_header_add(struct mk_http_request *sr, char *row, int len) +{ + mk_bug(!sr); + + if (!sr->headers._extra_rows) { + /* + * We allocate space for a fixed number of IOV entries: + * + * MK_PLUGIN_HEADER_EXTRA_ROWS = X + * + * we use (MK_PLUGIN_HEADER_EXTRA_ROWS * 2) thinking in an ending CRLF + */ + sr->headers._extra_rows = mk_iov_create(MK_PLUGIN_HEADER_EXTRA_ROWS * 2, 0); + mk_bug(!sr->headers._extra_rows); + } + + mk_iov_add(sr->headers._extra_rows, row, len, + MK_FALSE); + mk_iov_add(sr->headers._extra_rows, + mk_iov_crlf.data, mk_iov_crlf.len, + MK_FALSE); + return 0; +} + +struct mk_sched_worker *mk_plugin_sched_get_thread_conf() +{ + return MK_TLS_GET(mk_tls_sched_worker_node); +} + +struct mk_plugin *mk_plugin_cap(char cap, struct mk_server *server) +{ + struct mk_list *head; + struct mk_plugin *plugin; + + mk_list_foreach(head, &server->plugins) { + plugin = mk_list_entry(head, struct mk_plugin, _head); + if (plugin->capabilities & cap) { + return plugin; + } + } + + return NULL; +} + +struct mk_vhost_handler_param *mk_handler_param_get(int id, + struct mk_list *params) +{ + int i = 0; + struct mk_list *head; + + mk_list_foreach(head, params) { + if (i == id) { + return mk_list_entry(head, struct mk_vhost_handler_param, _head); + } + i++; + } + + return NULL; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_scheduler.c b/fluent-bit/lib/monkey/mk_server/mk_scheduler.c new file mode 100644 index 00000000..a680d3cd --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_scheduler.c @@ -0,0 +1,866 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/monkey.h> +#include <monkey/mk_info.h> +#include <monkey/mk_core.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_scheduler.h> +#include <monkey/mk_scheduler_tls.h> +#include <monkey/mk_server.h> +#include <monkey/mk_thread.h> +#include <monkey/mk_cache.h> +#include <monkey/mk_config.h> +#include <monkey/mk_clock.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_linuxtrace.h> +#include <monkey/mk_server.h> +#include <monkey/mk_plugin_stage.h> +#include <monkey/mk_http_thread.h> + +#include <signal.h> + +#ifndef _WIN32 +#include <sys/syscall.h> +#endif + +extern struct mk_sched_handler mk_http_handler; +extern struct mk_sched_handler mk_http2_handler; + +pthread_mutex_t mutex_worker_init = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t mutex_worker_exit = PTHREAD_MUTEX_INITIALIZER; + +/* + * Returns the worker id which should take a new incomming connection, + * it returns the worker id with less active connections. Just used + * if config->scheduler_mode is MK_SCHEDULER_FAIR_BALANCING. + */ +static inline int _next_target(struct mk_server *server) +{ + int i; + int target = 0; + unsigned long long tmp = 0, cur = 0; + struct mk_sched_ctx *ctx = server->sched_ctx; + struct mk_sched_worker *worker; + + cur = (ctx->workers[0].accepted_connections - + ctx->workers[0].closed_connections); + if (cur == 0) { + return 0; + } + + /* Finds the lowest load worker */ + for (i = 1; i < server->workers; i++) { + worker = &ctx->workers[i]; + tmp = worker->accepted_connections - worker->closed_connections; + if (tmp < cur) { + target = i; + cur = tmp; + + if (cur == 0) + break; + } + } + + /* + * If sched_ctx->workers[target] worker is full then the whole server too, + * because it has the lowest load. + */ + if (mk_unlikely(server->server_capacity > 0 && + server->server_capacity <= cur)) { + MK_TRACE("Too many clients: %i", server->server_capacity); + + /* Instruct to close the connection anyways, we lie, it will die */ + return -1; + } + + return target; +} + +struct mk_sched_worker *mk_sched_next_target(struct mk_server *server) +{ + int t; + struct mk_sched_ctx *ctx = server->sched_ctx; + + t = _next_target(server); + if (mk_likely(t != -1)) { + return &ctx->workers[t]; + } + + return NULL; +} + +/* + * This function is invoked when the core triggers a MK_SCHED_SIGNAL_FREE_ALL + * event through the signal channels, it means the server will stop working + * so this is the last call to release all memory resources in use. Of course + * this takes place in a thread context. + */ +void mk_sched_worker_free(struct mk_server *server) +{ + int i; + pthread_t tid; + struct mk_sched_ctx *ctx = server->sched_ctx; + struct mk_sched_worker *worker = NULL; + + pthread_mutex_lock(&mutex_worker_exit); + + /* + * Fix Me: needs to implement API to make plugins release + * their resources first at WORKER LEVEL + */ + + /* External */ + mk_plugin_exit_worker(); + mk_vhost_fdt_worker_exit(server); + mk_cache_worker_exit(); + + /* Scheduler stuff */ + tid = pthread_self(); + for (i = 0; i < server->workers; i++) { + worker = &ctx->workers[i]; + if (worker->tid == tid) { + break; + } + worker = NULL; + } + + mk_bug(!worker); + + /* FIXME!: there is nothing done here with the worker context */ + + /* Free master array (av queue & busy queue) */ + mk_mem_free(MK_TLS_GET(mk_tls_sched_cs)); + mk_mem_free(MK_TLS_GET(mk_tls_sched_cs_incomplete)); + mk_mem_free(MK_TLS_GET(mk_tls_sched_worker_notif)); + pthread_mutex_unlock(&mutex_worker_exit); +} + +struct mk_sched_handler *mk_sched_handler_cap(char cap) +{ + if (cap == MK_CAP_HTTP) { + return &mk_http_handler; + } + +#ifdef MK_HAVE_HTTP2 + else if (cap == MK_CAP_HTTP2) { + return &mk_http2_handler; + } +#endif + + return NULL; +} + +/* + * Register a new client connection into the scheduler, this call takes place + * inside the worker/thread context. + */ +struct mk_sched_conn *mk_sched_add_connection(int remote_fd, + struct mk_server_listen *listener, + struct mk_sched_worker *sched, + struct mk_server *server) +{ + int ret; + int size; + struct mk_sched_handler *handler; + struct mk_sched_conn *conn; + struct mk_event *event; + + /* Before to continue, we need to run plugin stage 10 */ + ret = mk_plugin_stage_run_10(remote_fd, server); + + /* Close connection, otherwise continue */ + if (ret == MK_PLUGIN_RET_CLOSE_CONX) { + listener->network->network->close(listener->network, remote_fd); + MK_LT_SCHED(remote_fd, "PLUGIN_CLOSE"); + return NULL; + } + + handler = listener->protocol; + if (handler->sched_extra_size > 0) { + void *data; + size = (sizeof(struct mk_sched_conn) + handler->sched_extra_size); + data = mk_mem_alloc_z(size); + conn = (struct mk_sched_conn *) data; + } + else { + conn = mk_mem_alloc_z(sizeof(struct mk_sched_conn)); + } + + if (!conn) { + mk_err("[server] Could not register client"); + return NULL; + } + + event = &conn->event; + event->fd = remote_fd; + event->type = MK_EVENT_CONNECTION; + event->mask = MK_EVENT_EMPTY; + event->status = MK_EVENT_NONE; + conn->arrive_time = server->clock_context->log_current_utime; + conn->protocol = handler; + conn->net = listener->network->network; + conn->is_timeout_on = MK_FALSE; + conn->server_listen = listener; + + /* Stream channel */ + conn->channel.type = MK_CHANNEL_SOCKET; /* channel type */ + conn->channel.fd = remote_fd; /* socket conn */ + conn->channel.io = conn->net; /* network layer */ + conn->channel.event = event; /* parent event ref */ + mk_list_init(&conn->channel.streams); + + /* + * Register the connections into the timeout_queue: + * + * When a new connection arrives, we cannot assume it contains some data + * to read, meaning the event loop may not get notifications and the protocol + * handler will never be called. So in order to avoid DDoS we always register + * this session in the timeout_queue for further lookup. + * + * The protocol handler is in charge to remove the session from the + * timeout_queue. + */ + mk_sched_conn_timeout_add(conn, sched); + + /* Linux trace message */ + MK_LT_SCHED(remote_fd, "REGISTERED"); + + return conn; +} + +static void mk_sched_thread_lists_init() +{ + struct mk_list *sched_cs_incomplete; + + /* mk_tls_sched_cs_incomplete */ + sched_cs_incomplete = mk_mem_alloc(sizeof(struct mk_list)); + mk_list_init(sched_cs_incomplete); + MK_TLS_SET(mk_tls_sched_cs_incomplete, sched_cs_incomplete); +} + +/* Register thread information. The caller thread is the thread information's owner */ +static int mk_sched_register_thread(struct mk_server *server) +{ + struct mk_sched_ctx *ctx = server->sched_ctx; + struct mk_sched_worker *worker; + + /* + * If this thread slept inside this section, some other thread may touch + * server->worker_id. + * So protect it with a mutex, only one thread may handle server->worker_id. + * + * Note : Let's use the platform agnostic atomics we implemented in cmetrics here + * instead of a lock. + */ + worker = &ctx->workers[server->worker_id]; + worker->idx = server->worker_id++; + worker->tid = pthread_self(); + +#if defined(__linux__) + /* + * Under Linux does not exists the difference between process and + * threads, everything is a thread in the kernel task struct, and each + * one has it's own numerical identificator: PID . + * + * Here we want to know what's the PID associated to this running + * task (which is different from parent Monkey PID), it can be + * retrieved with gettid() but Glibc does not export to userspace + * the syscall, we need to call it directly through syscall(2). + */ + worker->pid = syscall(__NR_gettid); +#elif defined(__APPLE__) + uint64_t tid; + pthread_threadid_np(NULL, &tid); + worker->pid = tid; +#else + worker->pid = 0xdeadbeef; +#endif + + /* Initialize lists */ + mk_list_init(&worker->timeout_queue); + worker->request_handler = NULL; + + return worker->idx; +} + +static void mk_signal_thread_sigpipe_safe() +{ +#ifndef _WIN32 + sigset_t old; + sigset_t set; + + sigemptyset(&set); + sigaddset(&set, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &set, &old); +#endif +} + +/* created thread, all these calls are in the thread context */ +void *mk_sched_launch_worker_loop(void *data) +{ + int ret; + int wid; + unsigned long len; + char *thread_name = 0; + struct mk_list *head; + struct mk_sched_worker_cb *wcb; + struct mk_sched_worker *sched = NULL; + struct mk_sched_notif *notif = NULL; + struct mk_sched_thread_conf *thinfo = data; + struct mk_sched_ctx *ctx; + struct mk_server *server; + + server = thinfo->server; + ctx = server->sched_ctx; + + /* Avoid SIGPIPE signals on this thread */ + mk_signal_thread_sigpipe_safe(); + + /* Init specific thread cache */ + mk_sched_thread_lists_init(); + mk_cache_worker_init(); + + /* Virtual hosts: initialize per thread-vhost data */ + mk_vhost_fdt_worker_init(server); + + /* Register working thread */ + wid = mk_sched_register_thread(server); + sched = &ctx->workers[wid]; + sched->loop = mk_event_loop_create(MK_EVENT_QUEUE_SIZE); + if (!sched->loop) { + mk_err("Error creating Scheduler loop"); + exit(EXIT_FAILURE); + } + + + sched->mem_pagesize = mk_utils_get_system_page_size(); + + /* + * Create the notification instance and link it to the worker + * thread-scope list. + */ + notif = mk_mem_alloc_z(sizeof(struct mk_sched_notif)); + MK_TLS_SET(mk_tls_sched_worker_notif, notif); + + /* Register the scheduler channel to signal active workers */ + ret = mk_event_channel_create(sched->loop, + &sched->signal_channel_r, + &sched->signal_channel_w, + notif); + if (ret < 0) { + exit(EXIT_FAILURE); + } + + mk_list_init(&sched->event_free_queue); + mk_list_init(&sched->threads); + mk_list_init(&sched->threads_purge); + + /* + * ULONG_MAX BUG test only + * ======================= + * to test the workaround we can use the following value: + * + * thinfo->closed_connections = 1000; + */ + + //thinfo->ctx = thconf->ctx; + + /* Rename worker */ + mk_string_build(&thread_name, &len, "monkey: wrk/%i", sched->idx); + mk_utils_worker_rename(thread_name); + mk_mem_free(thread_name); + + /* Export known scheduler node to context thread */ + MK_TLS_SET(mk_tls_sched_worker_node, sched); + mk_plugin_core_thread(server); + + if (server->scheduler_mode == MK_SCHEDULER_REUSEPORT) { + sched->listeners = mk_server_listen_init(server); + if (!sched->listeners) { + exit(EXIT_FAILURE); + } + } + + /* Unlock the conditional initializator */ + pthread_mutex_lock(&server->pth_mutex); + server->pth_init = MK_TRUE; + pthread_cond_signal(&server->pth_cond); + pthread_mutex_unlock(&server->pth_mutex); + + /* Invoke custom worker-callbacks defined by the scheduler (lib) */ + mk_list_foreach(head, &server->sched_worker_callbacks) { + wcb = mk_list_entry(head, struct mk_sched_worker_cb, _head); + wcb->cb_func(wcb->data); + } + + mk_mem_free(thinfo); + + /* init server thread loop */ + mk_server_worker_loop(server); + + return 0; +} + +/* Create thread which will be listening for incomings requests */ +int mk_sched_launch_thread(struct mk_server *server, pthread_t *tout) +{ + pthread_t tid; + pthread_attr_t attr; + struct mk_sched_thread_conf *thconf; + + server->pth_init = MK_FALSE; + + /* + * This lock is used for the 'pth_cond' conditional. Once the worker + * thread is ready it will signal the condition. + */ + pthread_mutex_lock(&server->pth_mutex); + + /* Thread data */ + thconf = mk_mem_alloc_z(sizeof(struct mk_sched_thread_conf)); + thconf->server = server; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + if (pthread_create(&tid, &attr, mk_sched_launch_worker_loop, + (void *) thconf) != 0) { + mk_libc_error("pthread_create"); + pthread_mutex_unlock(&server->pth_mutex); + return -1; + } + + *tout = tid; + + /* Block until the child thread is ready */ + while (!server->pth_init) { + pthread_cond_wait(&server->pth_cond, &server->pth_mutex); + } + + pthread_mutex_unlock(&server->pth_mutex); + + return 0; +} + +/* + * The scheduler nodes are an array of struct mk_sched_worker type, + * each worker thread belongs to a scheduler node, on this function we + * allocate a scheduler node per number of workers defined. + */ +int mk_sched_init(struct mk_server *server) +{ + int size; + struct mk_sched_ctx *ctx; + + ctx = mk_mem_alloc_z(sizeof(struct mk_sched_ctx)); + if (!ctx) { + mk_libc_error("malloc"); + return -1; + } + + size = (sizeof(struct mk_sched_worker) * server->workers); + ctx->workers = mk_mem_alloc(size); + if (!ctx->workers) { + mk_libc_error("malloc"); + mk_mem_free(ctx); + return -1; + } + memset(ctx->workers, '\0', size); + + /* Initialize helpers */ + pthread_mutex_init(&server->pth_mutex, NULL); + pthread_cond_init(&server->pth_cond, NULL); + server->pth_init = MK_FALSE; + + /* Map context into server context */ + server->sched_ctx = ctx; + + /* The mk_thread_prepare call was replaced by mk_http_thread_initialize_tls + * which is called earlier. + */ + + return 0; +} + +int mk_sched_exit(struct mk_server *server) +{ + struct mk_sched_ctx *ctx; + + ctx = server->sched_ctx; + mk_sched_worker_cb_free(server); + mk_mem_free(ctx->workers); + mk_mem_free(ctx); + + return 0; +} + +void mk_sched_set_request_list(struct rb_root *list) +{ + MK_TLS_SET(mk_tls_sched_cs, list); +} + +int mk_sched_remove_client(struct mk_sched_conn *conn, + struct mk_sched_worker *sched, + struct mk_server *server) +{ + struct mk_event *event; + + /* + * Close socket and change status: we must invoke mk_epoll_del() + * because when the socket is closed is cleaned from the queue by + * the Kernel at its leisure, and we may get false events if we rely + * on that. + */ + event = &conn->event; + MK_TRACE("[FD %i] Scheduler remove", event->fd); + + mk_event_del(sched->loop, event); + + /* Invoke plugins in stage 50 */ + mk_plugin_stage_run_50(event->fd, server); + + sched->closed_connections++; + + /* Unlink from the red-black tree */ + //rb_erase(&conn->_rb_head, &sched->rb_queue); + mk_sched_conn_timeout_del(conn); + + /* Close at network layer level */ + conn->net->close(conn->net->plugin, event->fd); + + /* Release and return */ + mk_channel_clean(&conn->channel); + mk_sched_event_free(&conn->event); + conn->status = MK_SCHED_CONN_CLOSED; + + MK_LT_SCHED(remote_fd, "DELETE_CLIENT"); + return 0; +} + +/* FIXME: nobody is using this function, check back later */ +struct mk_sched_conn *mk_sched_get_connection(struct mk_sched_worker *sched, + int remote_fd) +{ + (void) sched; + (void) remote_fd; + return NULL; +} + +/* + * For a given connection number, remove all resources associated: it can be + * used on any context such as: timeout, I/O errors, request finished, + * exceptions, etc. + */ +int mk_sched_drop_connection(struct mk_sched_conn *conn, + struct mk_sched_worker *sched, + struct mk_server *server) +{ + mk_sched_threads_destroy_conn(sched, conn); + return mk_sched_remove_client(conn, sched, server); +} + +int mk_sched_check_timeouts(struct mk_sched_worker *sched, + struct mk_server *server) +{ + int client_timeout; + struct mk_sched_conn *conn; + struct mk_list *head; + struct mk_list *temp; + + /* PENDING CONN TIMEOUT */ + mk_list_foreach_safe(head, temp, &sched->timeout_queue) { + conn = mk_list_entry(head, struct mk_sched_conn, timeout_head); + if (conn->event.type & MK_EVENT_IDLE) { + continue; + } + + client_timeout = conn->arrive_time + server->timeout; + + /* Check timeout */ + if (client_timeout <= server->clock_context->log_current_utime) { + MK_TRACE("Scheduler, closing fd %i due TIMEOUT", + conn->event.fd); + MK_LT_SCHED(conn->event.fd, "TIMEOUT_CONN_PENDING"); + conn->protocol->cb_close(conn, sched, MK_SCHED_CONN_TIMEOUT, + server); + mk_sched_drop_connection(conn, sched, server); + } + } + + return 0; +} + +static int sched_thread_cleanup(struct mk_sched_worker *sched, + struct mk_list *list) +{ + int c = 0; + struct mk_list *tmp; + struct mk_list *head; + struct mk_http_thread *mth; + (void) sched; + + mk_list_foreach_safe(head, tmp, list) { + mth = mk_list_entry(head, struct mk_http_thread, _head); + mk_http_thread_destroy(mth); + c++; + } + + return c; + +} + +int mk_sched_threads_purge(struct mk_sched_worker *sched) +{ + int c = 0; + + c = sched_thread_cleanup(sched, &sched->threads_purge); + return c; +} + +int mk_sched_threads_destroy_all(struct mk_sched_worker *sched) +{ + int c = 0; + + c = sched_thread_cleanup(sched, &sched->threads_purge); + c += sched_thread_cleanup(sched, &sched->threads); + + return c; +} + +/* + * Destroy the thread contexts associated to the particular + * connection. + * + * Return the number of contexts destroyed. + */ +int mk_sched_threads_destroy_conn(struct mk_sched_worker *sched, + struct mk_sched_conn *conn) +{ + int c = 0; + struct mk_list *tmp; + struct mk_list *head; + struct mk_http_thread *mth; + (void) sched; + + mk_list_foreach_safe(head, tmp, &sched->threads) { + mth = mk_list_entry(head, struct mk_http_thread, _head); + if (mth->session->conn == conn) { + mk_http_thread_destroy(mth); + c++; + } + } + return c; +} + +/* + * Scheduler events handler: lookup for event handler and invoke + * proper callbacks. + */ +int mk_sched_event_read(struct mk_sched_conn *conn, + struct mk_sched_worker *sched, + struct mk_server *server) +{ + int ret = 0; + +#ifdef MK_HAVE_TRACE + MK_TRACE("[FD %i] Connection Handler / read", conn->event.fd); +#endif + + /* + * When the event loop notify that there is some readable information + * from the socket, we need to invoke the protocol handler associated + * to this connection and also pass as a reference the 'read()' function + * that handle 'read I/O' operations, e.g: + * + * - plain sockets through liana will use just read(2) + * - ssl though mbedtls should use mk_mbedtls_read(..) + */ + ret = conn->protocol->cb_read(conn, sched, server); + if (ret == -1) { + if (errno == EAGAIN) { + MK_TRACE("[FD %i] EAGAIN: need to read more data", conn->event.fd); + return 1; + } + return -1; + } + + return ret; +} + +int mk_sched_event_write(struct mk_sched_conn *conn, + struct mk_sched_worker *sched, + struct mk_server *server) +{ + int ret = -1; + size_t count; + struct mk_event *event; + + MK_TRACE("[FD %i] Connection Handler / write", conn->event.fd); + + ret = mk_channel_write(&conn->channel, &count); + if (ret == MK_CHANNEL_FLUSH || ret == MK_CHANNEL_BUSY) { + return 0; + } + else if (ret == MK_CHANNEL_DONE || ret == MK_CHANNEL_EMPTY) { + if (conn->protocol->cb_done) { + ret = conn->protocol->cb_done(conn, sched, server); + } + if (ret == -1) { + return -1; + } + else if (ret == 0) { + event = &conn->event; + mk_event_add(sched->loop, event->fd, + MK_EVENT_CONNECTION, + MK_EVENT_READ, + conn); + } + return 0; + } + else if (ret & MK_CHANNEL_ERROR) { + return -1; + } + + /* avoid to make gcc cry :_( */ + return -1; +} + +int mk_sched_event_close(struct mk_sched_conn *conn, + struct mk_sched_worker *sched, + int type, struct mk_server *server) +{ + MK_TRACE("[FD %i] Connection Handler, closed", conn->event.fd); + mk_event_del(sched->loop, &conn->event); + + if (type != MK_EP_SOCKET_DONE) { + conn->protocol->cb_close(conn, sched, type, server); + } + /* + * Remove the socket from the scheduler and make sure + * to disable all notifications. + */ + mk_sched_drop_connection(conn, sched, server); + return 0; +} + +void mk_sched_event_free(struct mk_event *event) +{ + struct mk_sched_worker *sched = mk_sched_get_thread_conf(); + + if ((event->type & MK_EVENT_IDLE) != 0) { + return; + } + + event->type |= MK_EVENT_IDLE; + mk_list_add(&event->_head, &sched->event_free_queue); +} + +/* Register a new callback function to invoke when a worker is created */ +int mk_sched_worker_cb_add(struct mk_server *server, + void (*cb_func) (void *), + void *data) +{ + struct mk_sched_worker_cb *wcb; + + wcb = mk_mem_alloc(sizeof(struct mk_sched_worker_cb)); + if (!wcb) { + return -1; + } + + wcb->cb_func = cb_func; + wcb->data = data; + mk_list_add(&wcb->_head, &server->sched_worker_callbacks); + return 0; +} + +void mk_sched_worker_cb_free(struct mk_server *server) +{ + struct mk_list *tmp; + struct mk_list *head; + struct mk_sched_worker_cb *wcb; + + mk_list_foreach_safe(head, tmp, &server->sched_worker_callbacks) { + wcb = mk_list_entry(head, struct mk_sched_worker_cb, _head); + mk_list_del(&wcb->_head); + mk_mem_free(wcb); + } +} + +int mk_sched_send_signal(struct mk_sched_worker *worker, uint64_t val) +{ + ssize_t n; + + /* When using libevent _mk_event_channel_create creates a unix socket + * instead of a pipe and windows doesn't us calling read / write on a + * socket instead of recv / send + */ + +#ifdef _WIN32 + n = send(worker->signal_channel_w, &val, sizeof(uint64_t), 0); +#else + n = write(worker->signal_channel_w, &val, sizeof(uint64_t)); +#endif + + if (n < 0) { + mk_libc_error("write"); + + return 0; + } + + return 1; +} + +int mk_sched_broadcast_signal(struct mk_server *server, uint64_t val) +{ + int i; + int count = 0; + struct mk_sched_ctx *ctx; + struct mk_sched_worker *worker; + + ctx = server->sched_ctx; + for (i = 0; i < server->workers; i++) { + worker = &ctx->workers[i]; + + count += mk_sched_send_signal(worker, val); + } + + return count; +} + +/* + * Wait for all workers to finish: this function assumes that previously a + * MK_SCHED_SIGNAL_FREE_ALL was sent to the worker channels. + */ +int mk_sched_workers_join(struct mk_server *server) +{ + int i; + int count = 0; + struct mk_sched_ctx *ctx; + struct mk_sched_worker *worker; + + ctx = server->sched_ctx; + for (i = 0; i < server->workers; i++) { + worker = &ctx->workers[i]; + pthread_join(worker->tid, NULL); + count++; + } + + return count; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_server.c b/fluent-bit/lib/monkey/mk_server/mk_server.c new file mode 100644 index 00000000..a84ef448 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_server.c @@ -0,0 +1,679 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/mk_info.h> +#include <monkey/monkey.h> +#include <monkey/mk_config.h> +#include <monkey/mk_scheduler.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_server.h> +#include <monkey/mk_server_tls.h> +#include <monkey/mk_scheduler.h> +#include <monkey/mk_core.h> +#include <monkey/mk_fifo.h> +#include <monkey/mk_http_thread.h> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +#ifndef _WIN32 +#include <sys/time.h> +#include <sys/resource.h> +#endif + +pthread_key_t mk_server_fifo_key; + +static int mk_server_lib_notify_event_loop_break(struct mk_sched_worker *sched); + +/* Return the number of clients that can be attended */ +unsigned int mk_server_capacity(struct mk_server *server) +{ + int ret; + int cur; + +#ifndef _WIN32 + struct rlimit lim; + + /* Limit by system */ + getrlimit(RLIMIT_NOFILE, &lim); + cur = lim.rlim_cur; + + if (server->fd_limit > cur) { + lim.rlim_cur = server->fd_limit; + lim.rlim_max = server->fd_limit; + + ret = setrlimit(RLIMIT_NOFILE, &lim); + if (ret == -1) { + mk_warn("Could not increase FDLimit to %i.", server->fd_limit); + } + else { + cur = server->fd_limit; + } + } + else if (server->fd_limit > 0) { + cur = server->fd_limit; + } + +#else + ret = 0; + cur = INT_MAX; + + /* This is not the right way to plug this, according to raymond chen the only limit + * to fd count is free memory in their winsock provider and there are no other limits + * that I know of but I should still look for a more elegant solution. (even if it + * was just ignoring the server_capacity limit in scheduler.c: _next_target) + */ +#endif + + return cur; +} + +static inline +struct mk_sched_conn *mk_server_listen_handler(struct mk_sched_worker *sched, + void *data, + struct mk_server *server) +{ + int ret; + int client_fd = -1; + struct mk_sched_conn *conn; + struct mk_server_listen *listener = data; + + client_fd = mk_socket_accept(listener->server_fd); + if (mk_unlikely(client_fd == -1)) { + MK_TRACE("[server] Accept connection failed: %s", strerror(errno)); + goto error; + } + + conn = mk_sched_add_connection(client_fd, listener, sched, server); + if (mk_unlikely(!conn)) { + goto error; + } + + ret = mk_event_add(sched->loop, client_fd, + MK_EVENT_CONNECTION, MK_EVENT_READ, conn); + if (mk_unlikely(ret != 0)) { + mk_err("[server] Error registering file descriptor: %s", + strerror(errno)); + goto error; + } + + sched->accepted_connections++; + MK_TRACE("[server] New connection arrived: FD %i", client_fd); + return conn; + +error: + if (client_fd != -1) { + listener->network->network->close(listener->network, client_fd); + } + + return NULL; +} + +void mk_server_listen_free() +{ + struct mk_list *list; + struct mk_list *tmp; + struct mk_list *head; + struct mk_server_listen *listener; + + list = MK_TLS_GET(mk_tls_server_listen); + mk_list_foreach_safe(head, tmp, list) { + listener = mk_list_entry(head, struct mk_server_listen, _head); + mk_list_del(&listener->_head); + mk_mem_free(listener); + } +} + +void mk_server_listen_exit(struct mk_list *list) +{ + struct mk_list *tmp; + struct mk_list *head; + struct mk_server_listen *listen; + + if (!list) { + return; + } + + mk_list_foreach_safe(head, tmp, list) { + listen = mk_list_entry(head, struct mk_server_listen, _head); + mk_event_closesocket(listen->server_fd); + mk_list_del(&listen->_head); + mk_mem_free(listen); + } + + mk_mem_free(list); +} + +struct mk_list *mk_server_listen_init(struct mk_server *server) +{ + int server_fd; + int reuse_port = MK_FALSE; + struct mk_list *head; + struct mk_list *listeners; + struct mk_event *event; + struct mk_server_listen *listener; + struct mk_sched_handler *protocol; + struct mk_plugin *plugin; + struct mk_config_listener *listen; + + if (!server) { + goto error; + } + + listeners = mk_mem_alloc(sizeof(struct mk_list)); + mk_list_init(listeners); + + if (server->scheduler_mode == MK_SCHEDULER_REUSEPORT) { + reuse_port = MK_TRUE; + } + + mk_list_foreach(head, &server->listeners) { + listen = mk_list_entry(head, struct mk_config_listener, _head); + + server_fd = mk_socket_server(listen->port, + listen->address, + reuse_port, + server); + if (server_fd >= 0) { + if (mk_socket_set_tcp_defer_accept(server_fd) != 0) { +#if defined (__linux__) + mk_warn("[server] Could not set TCP_DEFER_ACCEPT"); +#endif + } + + listener = mk_mem_alloc_z(sizeof(struct mk_server_listen)); + + /* configure the internal event_state */ + event = &listener->event; + event->fd = server_fd; + event->type = MK_EVENT_LISTENER; + event->mask = MK_EVENT_EMPTY; + event->status = MK_EVENT_NONE; + + /* continue with listener setup and linking */ + listener->server_fd = server_fd; + listener->listen = listen; + + if (listen->flags & MK_CAP_HTTP) { + protocol = mk_sched_handler_cap(MK_CAP_HTTP); + if (!protocol) { + mk_err("HTTP protocol not supported"); + exit(EXIT_FAILURE); + } + listener->protocol = protocol; + } + +#ifdef MK_HAVE_HTTP2 + if (listen->flags & MK_CAP_HTTP2) { + protocol = mk_sched_handler_cap(MK_CAP_HTTP2); + if (!protocol) { + mk_err("HTTP2 protocol not supported"); + exit(EXIT_FAILURE); + } + listener->protocol = protocol; + } +#endif + listener->network = mk_plugin_cap(MK_CAP_SOCK_PLAIN, server); + + if (listen->flags & MK_CAP_SOCK_TLS) { + plugin = mk_plugin_cap(MK_CAP_SOCK_TLS, server); + if (!plugin) { + mk_err("SSL/TLS not supported"); + exit(EXIT_FAILURE); + } + listener->network = plugin; + } + + mk_list_add(&listener->_head, listeners); + } + else { + mk_err("[server] Failed to bind server socket to %s:%s.", + listen->address, + listen->port); + return NULL; + } + } + + if (reuse_port == MK_TRUE) { + MK_TLS_SET(mk_tls_server_listen, listeners); + } + + return listeners; + +error: + return NULL; +} + +/* Here we launch the worker threads to attend clients */ +void mk_server_launch_workers(struct mk_server *server) +{ + int i; + pthread_t skip; + + /* Launch workers */ + for (i = 0; i < server->workers; i++) { + /* Spawn the thread */ + mk_sched_launch_thread(server, &skip); + } +} + +/* + * When using the FIFO interface, this function get's the FIFO worker + * context and register the pipe file descriptor into the main event + * loop. + * + * note: this function is invoked by each worker thread. + */ +static int mk_server_fifo_worker_setup(struct mk_event_loop *evl) +{ + int ret; + struct mk_fifo_worker *fw; + + fw = pthread_getspecific(mk_server_fifo_key); + if (!fw) { + return -1; + } + + ret = mk_event_add(evl, fw->channel[0], + MK_EVENT_FIFO, MK_EVENT_READ, + fw); + if (ret != 0) { + mk_err("[server] Error registering fifo worker channel: %s", + strerror(errno)); + return -1; + } + + return 0; +} + +/* + * The loop_balancer() runs in the main process context and is considered + * the old-fashion way to handle connections. It have an event queue waiting + * for connections, once one arrives, it decides which worker (thread) may + * handle it registering the accept(2)ed file descriptor on the worker + * event monitored queue. + */ +void mk_server_loop_balancer(struct mk_server *server) +{ + size_t bytes; + uint64_t val; + int operation_flag; + struct mk_list *head; + struct mk_list *listeners; + struct mk_server_listen *listener; + struct mk_event *event; + struct mk_event_loop *evl; + struct mk_sched_worker *sched; + struct mk_event management_event; + + /* Init the listeners */ + listeners = mk_server_listen_init(server); + if (!listeners) { + mk_err("Failed to initialize listen sockets."); + return; + } + + /* Create an event loop context */ + evl = mk_event_loop_create(MK_EVENT_QUEUE_SIZE); + if (!evl) { + mk_err("Could not initialize event loop"); + exit(EXIT_FAILURE); + } + + /* Register the listeners */ + mk_list_foreach(head, listeners) { + listener = mk_list_entry(head, struct mk_server_listen, _head); + mk_event_add(evl, listener->server_fd, + MK_EVENT_LISTENER, MK_EVENT_READ, + listener); + } + + memset(&management_event, 0, sizeof(struct mk_event)); + + mk_event_add(evl, + server->lib_ch_manager[0], + MK_EVENT_NOTIFICATION, + MK_EVENT_READ, + &management_event); + + operation_flag = MK_TRUE; + while (operation_flag) { + mk_event_wait(evl); + mk_event_foreach(event, evl) { + if (event->mask & MK_EVENT_READ) { + /* This signal is sent by mk_stop and both this and + * mk_lib_worker are expecting it. + */ + if (server->lib_ch_manager[0] == event->fd) { +#ifdef _WIN32 + bytes = recv(event->fd, &val, sizeof(uint64_t), MSG_WAITALL); +#else + bytes = read(event->fd, &val, sizeof(uint64_t)); +#endif + + if (bytes <= 0) { + return; + } + + if (val == MK_SERVER_SIGNAL_STOP) { + operation_flag = MK_FALSE; + + break; + } + + continue; + } + + /* + * Accept connection: determinate which thread may work on this + * new connection. + */ + sched = mk_sched_next_target(server); + if (sched != NULL) { + mk_server_listen_handler(sched, event, server); + + mk_server_lib_notify_event_loop_break(sched); + +#ifdef MK_HAVE_TRACE + int i; + struct mk_sched_ctx *ctx = server->sched_ctx; + + for (i = 0; i < server->workers; i++) { + MK_TRACE("Worker Status"); + MK_TRACE(" WID %i / conx = %llu", + ctx->workers[i].idx, + ctx->workers[i].accepted_connections - + ctx->workers[i].closed_connections); + } +#endif + } + else { + mk_warn("[server] Over capacity."); + } + } + else if (event->mask & MK_EVENT_CLOSE) { + mk_err("[server] Error on socket %d: %s", + event->fd, strerror(errno)); + } + } + } + mk_event_loop_destroy(evl); + mk_server_listen_exit(listeners); +} + +/* + * This function is called when the scheduler is running in the REUSEPORT + * mode. That means that each worker is listening on shared TCP ports. + * + * When using shared TCP ports the Kernel decides to which worker the + * connection will be assigned. + */ +void mk_server_worker_loop(struct mk_server *server) +{ + int ret = -1; + int timeout_fd; + uint64_t val; + struct mk_event *event; + struct mk_event_loop *evl; + struct mk_list *list; + struct mk_list *head; + struct mk_sched_conn *conn; + struct mk_sched_worker *sched; + struct mk_server_listen *listener; + struct mk_server_timeout *server_timeout; + + /* Get thread conf */ + sched = mk_sched_get_thread_conf(); + evl = sched->loop; + + /* + * The worker will NOT process any connection until the master + * process through mk_server_loop() send us the green light + * signal MK_SERVER_SIGNAL_START. + */ + mk_event_wait(evl); + mk_event_foreach(event, evl) { + if ((event->mask & MK_EVENT_READ) && + event->type == MK_EVENT_NOTIFICATION) { + if (event->fd == sched->signal_channel_r) { + /* When using libevent _mk_event_channel_create creates a unix socket + * instead of a pipe and windows doesn't us calling read / write on a + * socket instead of recv / send + */ +#ifdef _WIN32 + ret = recv(event->fd, &val, sizeof(val), MSG_WAITALL); +#else + ret = read(event->fd, &val, sizeof(val)); +#endif + if (ret < 0) { + mk_libc_error("read"); + continue; + } + if (val == MK_SERVER_SIGNAL_START) { + MK_TRACE("Worker %i started (SIGNAL_START)", sched->idx); + break; + } + } + } + } + + if (server->scheduler_mode == MK_SCHEDULER_REUSEPORT) { + /* Register listeners */ + list = MK_TLS_GET(mk_tls_server_listen); + mk_list_foreach(head, list) { + listener = mk_list_entry(head, struct mk_server_listen, _head); + mk_event_add(sched->loop, listener->server_fd, + MK_EVENT_LISTENER, MK_EVENT_READ, + listener); + } + } + + /* + * If running in library mode, register the FIFO pipe file descriptors + * into the main event loop. + */ + if (server->lib_mode == MK_TRUE) { + mk_server_fifo_worker_setup(evl); + } + + /* create a new timeout file descriptor */ + server_timeout = mk_mem_alloc_z(sizeof(struct mk_server_timeout)); + MK_TLS_SET(mk_tls_server_timeout, server_timeout); + timeout_fd = mk_event_timeout_create(evl, server->timeout, 0, server_timeout); + + while (1) { + mk_event_wait(evl); + mk_event_foreach(event, evl) { + ret = 0; + if (event->type & MK_EVENT_IDLE) { + continue; + } + + if (event->type == MK_EVENT_CONNECTION) { + conn = (struct mk_sched_conn *) event; + + if (event->mask & MK_EVENT_WRITE) { + MK_TRACE("[FD %i] Event WRITE", event->fd); + ret = mk_sched_event_write(conn, sched, server); + } + + if (event->mask & MK_EVENT_READ) { + MK_TRACE("[FD %i] Event READ", event->fd); + ret = mk_sched_event_read(conn, sched, server); + } + + + if (event->mask & MK_EVENT_CLOSE && ret != -1) { + MK_TRACE("[FD %i] Event CLOSE", event->fd); + ret = -1; + } + + if (ret < 0 && conn->status != MK_SCHED_CONN_CLOSED) { + MK_TRACE("[FD %i] Event FORCE CLOSE | ret = %i", + event->fd, ret); + mk_sched_event_close(conn, sched, MK_EP_SOCKET_CLOSED, + server); + } + } + else if (event->type == MK_EVENT_LISTENER) { + /* + * A new connection have been accepted..or failed, despite + * the result, we let the loop continue processing the other + * events triggered. + */ + conn = mk_server_listen_handler(sched, event, server); + if (conn) { + //conn->event.mask = MK_EVENT_READ + //goto speed; + } + continue; + } + else if (event->type == MK_EVENT_CUSTOM) { + /* + * We got an event associated to a custom interface, that + * means a plugin registered some file descriptor on this + * event loop and an event was triggered. We pass the control + * to the defined event handler. + */ + event->handler(event); + } + else if (event->type == MK_EVENT_NOTIFICATION) { +#ifdef _WIN32 + ret = recv(event->fd, &val, sizeof(val), MSG_WAITALL); +#else + ret = read(event->fd, &val, sizeof(val)); +#endif + if (ret < 0) { + mk_libc_error("read"); + continue; + } + + if (event->fd == sched->signal_channel_r) { + if (val == MK_SCHED_SIGNAL_DEADBEEF) { + //FIXME:mk_sched_sync_counters(); + continue; + } + else if (val == MK_SCHED_SIGNAL_FREE_ALL) { + if (timeout_fd > 0) { + mk_event_timeout_destroy(evl, server_timeout); + } + mk_mem_free(MK_TLS_GET(mk_tls_server_timeout)); + mk_server_listen_exit(sched->listeners); + mk_event_loop_destroy(evl); + mk_sched_worker_free(server); + return; + } + else if (val == MK_SCHED_SIGNAL_EVENT_LOOP_BREAK) { + /* NOTE: This is just a notification that's sent to break out + * of the libevent loop in windows after accepting a new + * client + */ + MK_TRACE("New client accepted, awesome!"); + } + } + else if (event->fd == timeout_fd) { + mk_sched_check_timeouts(sched, server); + } + continue; + } + else if (event->type == MK_EVENT_THREAD) { + mk_http_thread_event(event); + continue; + } + else if (event->type == MK_EVENT_FIFO) { + mk_fifo_worker_read(event); + continue; + } + } + mk_sched_threads_purge(sched); + mk_sched_event_free_all(sched); + } +} + +static int mk_server_lib_notify_event_loop_break(struct mk_sched_worker *sched) +{ + uint64_t val; + + /* Check the channel is valid (enabled by library mode) */ + if (sched->signal_channel_w <= 0) { + return -1; + } + + val = MK_SCHED_SIGNAL_EVENT_LOOP_BREAK; + +#ifdef _WIN32 + return send(sched->signal_channel_w, &val, sizeof(uint64_t), 0); +#else + return write(sched->signal_channel_w, &val, sizeof(uint64_t)); +#endif +} + +static int mk_server_lib_notify_started(struct mk_server *server) +{ + uint64_t val; + + /* Check the channel is valid (enabled by library mode) */ + if (server->lib_ch_start[1] <= 0) { + return -1; + } + + val = MK_SERVER_SIGNAL_START; + +#ifdef _WIN32 + return send(server->lib_ch_start[1], &val, sizeof(uint64_t), 0); +#else + return write(server->lib_ch_start[1], &val, sizeof(uint64_t)); +#endif +} + +void mk_server_loop(struct mk_server *server) +{ + uint64_t val; + + /* Rename worker */ + mk_utils_worker_rename("monkey: server"); + + if (server->lib_mode == MK_FALSE) { + mk_info("HTTP Server started"); + } + + /* Wake up workers */ + val = MK_SERVER_SIGNAL_START; + mk_sched_broadcast_signal(server, val); + + /* Signal lib caller (if any) */ + mk_server_lib_notify_started(server); + + /* + * When using REUSEPORT mode on the Scheduler, we need to signal + * them so they can start processing connections. + */ + if (server->scheduler_mode == MK_SCHEDULER_REUSEPORT) { + /* do thing :) */ + } + else { + /* FIXME!: this old mode needs some checks on library mode */ + mk_server_loop_balancer(server); + } +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_socket.c b/fluent-bit/lib/monkey/mk_server/mk_socket.c new file mode 100644 index 00000000..0277ab1c --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_socket.c @@ -0,0 +1,402 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#ifndef SOL_TCP +#define SOL_TCP IPPROTO_TCP +#endif + +#include <monkey/monkey.h> +#include <monkey/mk_info.h> +#include <monkey/mk_socket.h> +#include <monkey/mk_kernel.h> +#include <monkey/mk_net.h> +#include <monkey/mk_core.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_plugin.h> + +#include <time.h> + +/* + * Example from: + * http://www.baus.net/on-tcp_cork + */ +int mk_socket_set_cork_flag(int fd, int state) +{ + MK_TRACE("Socket, set Cork Flag FD %i to %s", fd, (state ? "ON" : "OFF")); + +#if defined (TCP_CORK) + return setsockopt(fd, SOL_TCP, TCP_CORK, &state, sizeof(state)); +#elif defined (TCP_NOPUSH) + return setsockopt(fd, SOL_SOCKET, TCP_NOPUSH, &state, sizeof(state)); +#endif + + return 0; +} + +int mk_socket_set_nonblocking(int sockfd) +{ + + MK_TRACE("Socket, set FD %i to non-blocking", sockfd); + +#ifdef _WIN32 + u_long flags; + + flags = 0; + if (SOCKET_ERROR == ioctlsocket(sockfd, FIONBIO, &flags)) { + mk_err("Can't set to non-blocking mode socket %i", sockfd); + return -1; + } +#else + if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK) == -1) { + mk_err("Can't set to non-blocking mode socket %i", sockfd); + return -1; + } + fcntl(sockfd, F_SETFD, FD_CLOEXEC); +#endif + + return 0; +} + +/* + * Enable the TCP_FASTOPEN feature for server side implemented in + * Linux Kernel >= 3.7, for more details read here: + * + * TCP Fast Open: expediting web services: http://lwn.net/Articles/508865/ + */ +int mk_socket_set_tcp_fastopen(int sockfd) +{ +#if defined (__linux__) + int qlen = 5; + return setsockopt(sockfd, SOL_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen)); +#endif + + (void) sockfd; + return -1; +} + +int mk_socket_set_tcp_nodelay(int sockfd) +{ + int on = 1; + + return setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &on, sizeof(on)); +} + +int mk_socket_set_tcp_defer_accept(int sockfd) +{ +#if defined (__linux__) + int timeout = 0; + + return setsockopt(sockfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &timeout, sizeof(int)); +#else + (void) sockfd; + return -1; +#endif +} + +int mk_socket_set_tcp_reuseport(int sockfd) +{ + int on = 1; + return setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)); +} + +int mk_socket_create(int domain, int type, int protocol) +{ + int fd; + +#ifdef SOCK_CLOEXEC + fd = socket(domain, type | SOCK_CLOEXEC, protocol); +#else + fd = socket(domain, type, protocol); + +#ifndef _WIN32 + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif +#endif + + if (fd == -1) { + mk_libc_error("socket"); + return -1; + } + + return fd; +} + +int mk_socket_open(char *path, int async) +{ + int ret; + int socket_fd; + struct sockaddr_un address; + + socket_fd = mk_socket_create(PF_UNIX, SOCK_STREAM, 0); + if (socket_fd == -1) { + return -1; + } + + memset(&address, '\0', sizeof(struct sockaddr_un)); + address.sun_family = AF_UNIX; + snprintf(address.sun_path, sizeof(address.sun_path), "%s", path); + + if (async == MK_TRUE) { + mk_socket_set_nonblocking(socket_fd); + } + + ret = connect(socket_fd, (struct sockaddr *) &address, + sizeof(struct sockaddr_un)); + if (ret == -1) { + if (errno == EINPROGRESS) { + return socket_fd; + } + else { +#ifdef MK_HAVE_TRACE + mk_libc_error("connect"); +#endif + close(socket_fd); + return -1; + } + } + + return socket_fd; +} + + +int mk_socket_connect(char *host, int port, int async) +{ + int ret; + int socket_fd = -1; + char *port_str = 0; + unsigned long len; + struct addrinfo hints; + struct addrinfo *res, *rp; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + mk_string_build(&port_str, &len, "%d", port); + + ret = getaddrinfo(host, port_str, &hints, &res); + mk_mem_free(port_str); + if(ret != 0) { + mk_err("Can't get addr info: %s", gai_strerror(ret)); + return -1; + } + for (rp = res; rp != NULL; rp = rp->ai_next) { + socket_fd = mk_socket_create(rp->ai_family, + rp->ai_socktype, rp->ai_protocol); + + if (socket_fd == -1) { + mk_warn("Error creating client socket, retrying"); + continue; + } + + if (async == MK_TRUE) { + mk_socket_set_nonblocking(socket_fd); + } + + ret = connect(socket_fd, + (struct sockaddr *) rp->ai_addr, rp->ai_addrlen); + if (ret == -1) { + if (errno == EINPROGRESS) { + break; + } + else { + printf("%s", strerror(errno)); + perror("connect"); + exit(1); + close(socket_fd); + continue; + } + } + break; + } + freeaddrinfo(res); + + if (rp == NULL) + return -1; + + return socket_fd; +} + +int mk_socket_reset(int socket) +{ + int status = 1; + + if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &status, sizeof(int)) == -1) { + mk_libc_error("socket"); + exit(EXIT_FAILURE); + } + + return 0; +} + +int mk_socket_bind(int socket_fd, const struct sockaddr *addr, + socklen_t addrlen, int backlog, struct mk_server *server) +{ + int ret; + + ret = bind(socket_fd, addr, addrlen); + if( ret == -1 ) { + mk_warn("Error binding socket"); + return ret; + } + + /* + * Enable TCP_FASTOPEN by default: if for some reason this call fail, + * it will not affect the behavior of the server, in order to succeed, + * Monkey must be running in a Linux system with Kernel >= 3.7 and the + * tcp_fastopen flag enabled here: + * + * # cat /proc/sys/net/ipv4/tcp_fastopen + * + * To enable this feature just do: + * + * # echo 1 > /proc/sys/net/ipv4/tcp_fastopen + */ + if (server->kernel_features & MK_KERNEL_TCP_FASTOPEN) { + ret = mk_socket_set_tcp_fastopen(socket_fd); + if (ret == -1) { + mk_warn("Could not set TCP_FASTOPEN"); + } + } + + ret = listen(socket_fd, backlog); + if(ret == -1 ) { + return -1; + } + + return ret; +} + +/* Just IPv4 for now... */ +int mk_socket_server(char *port, char *listen_addr, + int reuse_port, struct mk_server *server) +{ + int ret; + int socket_fd = -1; + struct addrinfo hints; + struct addrinfo *res, *rp; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + mk_net_init(); + + ret = getaddrinfo(listen_addr, port, &hints, &res); + if(ret != 0) { + mk_err("Can't get addr info: %s", gai_strerror(ret)); + return -1; + } + + for (rp = res; rp != NULL; rp = rp->ai_next) { + socket_fd = mk_socket_create(rp->ai_family, + rp->ai_socktype, rp->ai_protocol); + if (socket_fd == -1) { + mk_warn("Error creating server socket, retrying"); + continue; + } + + ret = mk_socket_set_tcp_nodelay(socket_fd); + if (ret == -1) { + mk_warn("Could not set TCP_NODELAY"); + } + + mk_socket_reset(socket_fd); + + /* Check if reuse port can be enabled on this socket */ + if (reuse_port == MK_TRUE && + (server->kernel_features & MK_KERNEL_SO_REUSEPORT)) { + ret = mk_socket_set_tcp_reuseport(socket_fd); + if (ret == -1) { + mk_warn("Could not use SO_REUSEPORT, using fair balancing mode"); + server->scheduler_mode = MK_SCHEDULER_FAIR_BALANCING; + } + } + + ret = mk_socket_bind(socket_fd, rp->ai_addr, rp->ai_addrlen, + MK_SOMAXCONN, server); + if(ret == -1) { + mk_err("Cannot listen on %s:%s", listen_addr, port); + freeaddrinfo(res); + return -1; + } + break; + } + freeaddrinfo(res); + + if (rp == NULL) + return -1; + + return socket_fd; +} + +int mk_socket_ip_str(int socket_fd, char **buf, int size, unsigned long *len) +{ + int ret; + struct sockaddr_storage addr; + socklen_t s_len = sizeof(addr); + + ret = getpeername(socket_fd, (struct sockaddr *) &addr, &s_len); + + if (mk_unlikely(ret == -1)) { + MK_TRACE("[FD %i] Can't get addr for this socket", socket_fd); + return -1; + } + + errno = 0; + + if(addr.ss_family == AF_INET) { + if((inet_ntop(AF_INET, &((struct sockaddr_in *)&addr)->sin_addr, + *buf, size)) == NULL) { + mk_warn("mk_socket_ip_str: Can't get the IP text form (%i)", errno); + return -1; + } + } + else if(addr.ss_family == AF_INET6) { + if((inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&addr)->sin6_addr, + *buf, size)) == NULL) { + mk_warn("mk_socket_ip_str: Can't get the IP text form (%i)", errno); + return -1; + } + } + + *len = strlen(*buf); + return 0; +} + +int mk_socket_accept(int server_fd) +{ + int remote_fd; + + struct sockaddr sock_addr; + socklen_t socket_size = sizeof(struct sockaddr); + +#ifdef MK_HAVE_ACCEPT4 + remote_fd = accept4(server_fd, &sock_addr, &socket_size, + SOCK_NONBLOCK | SOCK_CLOEXEC); +#else + remote_fd = accept(server_fd, &sock_addr, &socket_size); + mk_socket_set_nonblocking(remote_fd); +#endif + + return remote_fd; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_stream.c b/fluent-bit/lib/monkey/mk_server/mk_stream.c new file mode 100644 index 00000000..df0f1b0b --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_stream.c @@ -0,0 +1,338 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/monkey.h> +#include <monkey/mk_stream.h> +#include <assert.h> + +/* Create a new channel */ +struct mk_channel *mk_channel_new(int type, int fd) +{ + struct mk_channel *channel; + + channel = mk_mem_alloc(sizeof(struct mk_channel)); + if (!channel) { + return NULL; + } + channel->type = type; + channel->fd = fd; + channel->status = MK_CHANNEL_OK; + mk_list_init(&channel->streams); + + return channel; +} + +int mk_channel_release(struct mk_channel *channel) +{ + mk_mem_free(channel); + return 0; +} + +static inline size_t channel_write_in_file(struct mk_channel *channel, + struct mk_stream_input *in) +{ + ssize_t bytes = 0; + + MK_TRACE("[CH %i] STREAM_FILE [fd=%i], bytes=%lu", + channel->fd, in->fd, in->bytes_total); + + /* Direct write */ + bytes = mk_sched_conn_sendfile(channel, + in->fd, + &in->bytes_offset, + in->bytes_total + ); + MK_TRACE("[CH=%d] [FD=%i] WRITE STREAM FILE: %lu bytes", + channel->fd, in->fd, bytes); + + return bytes; +} + +size_t mk_stream_size(struct mk_stream *stream) +{ + return (stream->bytes_total - stream->bytes_offset); +} + +/* + * It 'intent' to write a few streams over the channel and alter the + * channel notification side if required: READ -> WRITE. + */ +int mk_channel_flush(struct mk_channel *channel) +{ + int ret = 0; + size_t count = 0; + size_t total = 0; + uint32_t stop = (MK_CHANNEL_DONE | MK_CHANNEL_ERROR | MK_CHANNEL_EMPTY); + + do { + ret = mk_channel_write(channel, &count); + total += count; + +#ifdef MK_HAVE_TRACE + MK_TRACE("Channel flush: %d bytes", count); + if (ret & MK_CHANNEL_DONE) { + MK_TRACE("Channel was empty"); + } + if (ret & MK_CHANNEL_ERROR) { + MK_TRACE("Channel error"); + } + if (ret & MK_CHANNEL_EMPTY) { + MK_TRACE("Channel empty"); + } +#endif + } while (total <= 4096 && ((ret & stop) == 0)); + + if (ret == MK_CHANNEL_DONE) { + MK_TRACE("Channel done"); + return ret; + } + else if (ret & (MK_CHANNEL_FLUSH | MK_CHANNEL_BUSY)) { + MK_TRACE("Channel FLUSH | BUSY"); + if ((channel->event->mask & MK_EVENT_WRITE) == 0) { + mk_event_add(mk_sched_loop(), + channel->fd, + MK_EVENT_CONNECTION, + MK_EVENT_WRITE, + channel->event); + } + } + + return ret; +} + +int mk_stream_in_release(struct mk_stream_input *in) +{ + if (in->cb_finished) { + in->cb_finished(in); + } + + mk_stream_input_unlink(in); + if (in->dynamic == MK_TRUE) { + mk_mem_free(in); + } + + return 0; +} + +int mk_channel_stream_write(struct mk_stream *stream, size_t *count) +{ + ssize_t bytes = 0; + struct mk_iov *iov; + struct mk_list *tmp; + struct mk_list *head; + struct mk_channel *channel; + struct mk_stream_input *input; + + channel = stream->channel; + + /* Validate channel status */ + if (channel->status != MK_CHANNEL_OK) { + return -MK_CHANNEL_ERROR; + } + + /* Iterate inputs and process stream */ + mk_list_foreach_safe(head, tmp, &stream->inputs) { + input = mk_list_entry(head, struct mk_stream_input, _head); + if (input->type == MK_STREAM_FILE) { + bytes = channel_write_in_file(channel, input); + } + else if (input->type == MK_STREAM_IOV) { + iov = input->buffer; + if (!iov) { + return MK_CHANNEL_EMPTY; + } + + bytes = mk_sched_conn_writev(channel, iov); + + MK_TRACE("[CH %i] STREAM_IOV, wrote %d bytes", + channel->fd, bytes); + if (bytes > 0) { + /* Perform the adjustment on mk_iov */ + mk_iov_consume(iov, bytes); + } + } + else if (input->type == MK_STREAM_RAW) { + bytes = mk_sched_conn_write(channel, + input->buffer, input->bytes_total); + MK_TRACE("[CH %i] STREAM_RAW, bytes=%lu/%lu\n", + channel->fd, bytes, input->bytes_total); + } + + if (bytes > 0) { + *count = bytes; + mk_stream_input_consume(input, bytes); + + /* notification callback, optional */ + if (stream->cb_bytes_consumed) { + stream->cb_bytes_consumed(stream, bytes); + } + + if (input->cb_consumed) { + input->cb_consumed(input, bytes); + } + + if (input->bytes_total == 0) { + MK_TRACE("Input done, unlinking (channel=%p)", channel); + mk_stream_in_release(input); + } + MK_TRACE("[CH %i] CHANNEL_FLUSH", channel->fd); + } + else if (bytes < 0) { + mk_stream_in_release(input); + return -MK_CHANNEL_ERROR; + } + else if (bytes == 0) { + mk_stream_in_release(input); + return -MK_CHANNEL_ERROR; + } + } + + return bytes; +} + +/* It perform a direct stream I/O write through the network layer */ +int mk_channel_write(struct mk_channel *channel, size_t *count) +{ + ssize_t bytes = -1; + struct mk_iov *iov; + struct mk_stream *stream = NULL; + struct mk_stream_input *input; + + errno = 0; + + if (mk_list_is_empty(&channel->streams) == 0) { + MK_TRACE("[CH %i] CHANNEL_EMPTY", channel->fd); + return MK_CHANNEL_EMPTY; + } + + /* Get the input source */ + stream = mk_list_entry_first(&channel->streams, struct mk_stream, _head); + if (mk_list_is_empty(&stream->inputs) == 0) { + return MK_CHANNEL_EMPTY; + } + input = mk_list_entry_first(&stream->inputs, struct mk_stream_input, _head); + + /* + * Based on the Stream Input type we consume on that way, not all inputs + * requires to read from buffer, e.g: Static File, Pipes. + */ + if (channel->type == MK_CHANNEL_SOCKET) { + if (input->type == MK_STREAM_FILE) { + bytes = channel_write_in_file(channel, input); + } + else if (input->type == MK_STREAM_IOV) { + iov = input->buffer; + if (!iov) { + return MK_CHANNEL_EMPTY; + } + + bytes = mk_sched_conn_writev(channel, iov); + + MK_TRACE("[CH %i] STREAM_IOV, wrote %d bytes", + channel->fd, bytes); + if (bytes > 0) { + /* Perform the adjustment on mk_iov */ + mk_iov_consume(iov, bytes); + } + } + else if (input->type == MK_STREAM_RAW) { + bytes = mk_sched_conn_write(channel, + input->buffer, input->bytes_total); + MK_TRACE("[CH %i] STREAM_RAW, bytes=%lu/%lu", + channel->fd, bytes, input->bytes_total); + if (bytes > 0) { + /* DEPRECATED: consume_raw(input, bytes); */ + } + } + + if (bytes > 0) { + *count = bytes; + mk_stream_input_consume(input, bytes); + + /* notification callback, optional */ + if (stream->cb_bytes_consumed) { + stream->cb_bytes_consumed(stream, bytes); + } + + if (input->cb_consumed) { + input->cb_consumed(input, bytes); + } + + if (input->bytes_total == 0) { + MK_TRACE("Input done, unlinking (channel=%p)", channel); + mk_stream_in_release(input); + } + + if (mk_list_is_empty(&stream->inputs) == 0) { + /* Everytime the stream is empty, we notify the trigger the cb */ + if (stream->cb_finished) { + stream->cb_finished(stream); + } + + if (mk_channel_is_empty(channel) == 0) { + MK_TRACE("[CH %i] CHANNEL_DONE", channel->fd); + return MK_CHANNEL_DONE; + } + else { + MK_TRACE("[CH %i] CHANNEL_FLUSH", channel->fd); + return MK_CHANNEL_FLUSH; + } + } + + MK_TRACE("[CH %i] CHANNEL_FLUSH", channel->fd); + return MK_CHANNEL_FLUSH; + } + else if (bytes < 0) { + if (errno == EAGAIN) { + return MK_CHANNEL_BUSY; + } + + mk_stream_in_release(input); + return MK_CHANNEL_ERROR; + } + else if (bytes == 0) { + mk_stream_in_release(input); + return MK_CHANNEL_ERROR; + } + } + + return MK_CHANNEL_ERROR; +} + +/* Remove any dynamic memory associated */ +int mk_channel_clean(struct mk_channel *channel) +{ + struct mk_list *tmp; + struct mk_list *tmp_in; + struct mk_list *head; + struct mk_list *head_in; + struct mk_stream *stream; + struct mk_stream_input *in; + + mk_list_foreach_safe(head, tmp, &channel->streams) { + stream = mk_list_entry(head, struct mk_stream, _head); + mk_list_foreach_safe(head_in, tmp_in, &stream->inputs) { + in = mk_list_entry(head_in, struct mk_stream_input, _head); + mk_stream_in_release(in); + } + mk_stream_release(stream); + } + + return 0; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_user.c b/fluent-bit/lib/monkey/mk_server/mk_user.c new file mode 100644 index 00000000..7200ff08 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_user.c @@ -0,0 +1,175 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/monkey.h> +#include <monkey/mk_user.h> +#include <monkey/mk_http.h> +#include <monkey/mk_http_status.h> +#include <monkey/mk_core.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_config.h> + +#ifndef _WIN32 +#include <pwd.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <grp.h> + +int mk_user_init(struct mk_http_session *cs, struct mk_http_request *sr, + struct mk_server *server) +{ + int limit; + const int offset = 2; /* The user is defined after the '/~' string, so offset = 2 */ + const int user_len = 255; + char user[/*user_len*/ 255]; /* VC++ Doesn't allow for this to be a const int*/ + char *user_uri; + struct passwd *s_user; + + if (sr->uri_processed.len <= 2) { + return -1; + } + + limit = mk_string_char_search(sr->uri_processed.data + offset, '/', + sr->uri_processed.len); + + if (limit == -1) { + limit = (sr->uri_processed.len) - offset; + } + + if (limit + offset >= (user_len)) { + return -1; + } + + memcpy(user, sr->uri_processed.data + offset, limit); + user[limit] = '\0'; + + MK_TRACE("user: '%s'", user); + + /* Check system user */ + if ((s_user = getpwnam(user)) == NULL) { + mk_http_error(MK_CLIENT_NOT_FOUND, cs, sr, server); + return -1; + } + + if (sr->uri_processed.len > (unsigned int) (offset+limit)) { + user_uri = mk_mem_alloc(sr->uri_processed.len); + if (!user_uri) { + return -1; + } + + memcpy(user_uri, + sr->uri_processed.data + (offset + limit), + sr->uri_processed.len - offset - limit); + user_uri[sr->uri_processed.len - offset - limit] = '\0'; + + mk_string_build(&sr->real_path.data, &sr->real_path.len, + "%s/%s%s", + s_user->pw_dir, server->conf_user_pub, user_uri); + mk_mem_free(user_uri); + } + else { + mk_string_build(&sr->real_path.data, &sr->real_path.len, + "%s/%s", s_user->pw_dir, server->conf_user_pub); + } + + sr->user_home = MK_TRUE; + return 0; +} + +/* Change process user */ +int mk_user_set_uidgid(struct mk_server *server) +{ + struct passwd *usr; + + /* Launched by root ? */ + if (geteuid() == 0 && server->user) { + struct rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl)) { + mk_warn("cannot get resource limits"); + } + + /* Check if user exists */ + if ((usr = getpwnam(server->user)) == NULL) { + mk_err("Invalid user '%s'", server->user); + goto out; + } + + if (initgroups(server->user, usr->pw_gid) != 0) { + mk_err("Initgroups() failed"); + } + + /* Change process UID and GID */ + if (setegid(usr->pw_gid) == -1) { + mk_err("I cannot change the GID to %u", usr->pw_gid); + } + + if (seteuid(usr->pw_uid) == -1) { + mk_err("I cannot change the UID to %u", usr->pw_uid); + } + + server->is_seteuid = MK_TRUE; + } + + out: + /* Variables set for run checks on file permission */ + //FIXME + //EUID = geteuid(); + //EGID = getegid(); + + return 0; +} + +/* Return process to the original user */ +int mk_user_undo_uidgid(struct mk_server *server) +{ + if (server->is_seteuid == MK_TRUE) { + if (setegid(0) < 0) { + mk_err("Can't restore effective GID"); + } + if (seteuid(0) < 0) { + mk_err("Can't restore effective UID"); + } + } + return 0; +} + +#else +/* + None of these functionalities are going to be available in windows at the moment +*/ + +int mk_user_init(struct mk_http_session* cs, struct mk_http_request* sr, + struct mk_server* server) +{ + return -1; +} + +int mk_user_set_uidgid(struct mk_server* server) +{ + mk_err("Cannot impersonate users in windows"); + + return 0; +} + +int mk_user_undo_uidgid(struct mk_server* server) +{ + return 0; +} +#endif diff --git a/fluent-bit/lib/monkey/mk_server/mk_utils.c b/fluent-bit/lib/monkey/mk_server/mk_utils.c new file mode 100644 index 00000000..443c0ec3 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_utils.c @@ -0,0 +1,589 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/* local headers */ +#include <monkey/monkey.h> +#include <monkey/mk_core.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_config.h> +#include <monkey/mk_socket.h> +#include <monkey/mk_clock.h> +#include <monkey/mk_user.h> +#include <monkey/mk_cache.h> +#include <monkey/mk_tls.h> + +#include <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <time.h> +#include <inttypes.h> + +#ifdef _WIN32 +#include <winsock2.h> +#else +#include <sys/socket.h> +#endif + +/* stacktrace */ +#ifndef _WIN32 +#include <dlfcn.h> +#endif + +#ifdef MK_HAVE_BACKTRACE +#include <execinfo.h> +#endif + +#define MK_UTILS_GMT_DATEFORMAT "%a, %d %b %Y %H:%M:%S GMT" + +#ifdef _WIN32 +static struct tm* localtime_r(const time_t* timep, struct tm* result) +{ + localtime_s(result, timep); + + return result; +} + +static struct tm* gmtime_r(const time_t* timep, struct tm* result) +{ + gmtime_s(result, timep); + + return result; +} + +static time_t timegm(struct tm* timeptr) +{ + return _mkgmtime(timeptr); +} +#endif + +#ifdef _WIN32 +int mk_utils_get_system_core_count() +{ + SYSTEM_LOGICAL_PROCESSOR_INFORMATION *proc_info_buffer; + unsigned int result_entry_count; + unsigned int entry_index; + DWORD result_length; + int result_code; + int core_count; + + core_count = 1; + result_length = 0; + proc_info_buffer = NULL; + + result_code = GetLogicalProcessorInformation(proc_info_buffer, &result_length); + /* We're passing a null buffer, result_code has to be false */ + + if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) { + result_entry_count = result_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); + proc_info_buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION *) _alloca(result_length); + + if(NULL != proc_info_buffer) { + result_code = GetLogicalProcessorInformation(proc_info_buffer, &result_length); + + if (0 != result_code) { + core_count = 0; + + for(entry_index = 0 ; entry_index < result_entry_count ; entry_index++) { + if(RelationProcessorCore == proc_info_buffer[entry_index].Relationship) { + core_count++; + } + } + } + } + + /* Athread stack allocation error is a pretty serious + * error so in that case we let someone else handle it by returning a + * sane default (1 core) + */ + } + + return core_count; +} + +int mk_utils_get_system_page_size() +{ + SYSTEM_INFO si; + + GetSystemInfo(&si); + + return si.dwPageSize; +} + +#else +int mk_utils_get_system_core_count() +{ + return sysconf(_SC_NPROCESSORS_ONLN); +} + +int mk_utils_get_system_page_size() +{ + return sysconf(_SC_PAGESIZE); +} + +#endif + + +/* Date helpers */ +static const char mk_date_wd[][6] = {"Sun, ", "Mon, ", "Tue, ", "Wed, ", "Thu, ", "Fri, ", "Sat, "}; +static const char mk_date_ym[][5] = {"Jan ", "Feb ", "Mar ", "Apr ", "May ", "Jun ", "Jul ", + "Aug ", "Sep ", "Oct ", "Nov ", "Dec "}; + +static int mk_utils_gmt_cache_get(char **data, time_t date) +{ + unsigned int i; + struct mk_gmt_cache *gcache = MK_TLS_GET(mk_tls_cache_gmtext); + + if (mk_unlikely(!gcache)) { + return MK_FALSE; + } + + for (i = 0; i < MK_GMT_CACHES; i++) { + if (date == gcache[i].time) { + memcpy(*data, gcache[i].text, 32); + gcache[i].hits++; + return MK_TRUE; + } + } + + return MK_FALSE; +} + +static void mk_utils_gmt_cache_add(char *data, time_t time) +{ + unsigned int i, min = 0; + struct mk_gmt_cache *gcache = MK_TLS_GET(mk_tls_cache_gmtext); + + for (i = 1; i < MK_GMT_CACHES; i++) { + if (gcache[i].hits < gcache[min].hits) + min = i; + } + + gcache[min].hits = 1; + gcache[min].time = time; + memcpy(gcache[min].text, data, 32); +} + +/* + *This function given a unix time, set in a mk_ptr_t + * the date in the RFC1123 format like: + * + * Wed, 23 Jun 2010 22:32:01 GMT + * + * it also adds a 'CRLF' at the end + */ +int mk_utils_utime2gmt(char **data, time_t date) +{ + const int size = 31; + unsigned short year, mday, hour, min, sec; + char *buf=0; + struct tm *gtm; + + if (date == 0) { + if ((date = time(NULL)) == -1) { + return -1; + } + } + else { + /* Maybe it's converted already? */ + if (mk_utils_gmt_cache_get(data, date) == MK_TRUE) { + return size; + } + } + + /* Convert unix time to struct tm */ + gtm = MK_TLS_GET(mk_tls_cache_gmtime); + + /* If this function was invoked from a non-thread context it should exit */ + mk_bug(!gtm); + gtm = gmtime_r(&date, gtm); + if (!gtm) { + return -1; + } + + /* struct tm -> tm_year counts number of years after 1900 */ + year = gtm->tm_year + 1900; + + /* Signed division is slow, by using unsigned we gain 25% speed */ + mday = gtm->tm_mday; + hour = gtm->tm_hour; + min = gtm->tm_min; + sec = gtm->tm_sec; + + /* Compose template */ + buf = *data; + + /* Week day */ + memcpy(buf, mk_date_wd[gtm->tm_wday], 5); + buf += 5; + + /* Day of the month */ + *buf++ = ('0' + (mday / 10)); + *buf++ = ('0' + (mday % 10)); + *buf++ = ' '; + + /* Month */ + memcpy(buf, mk_date_ym[gtm->tm_mon], 4); + buf += 4; + + /* Year */ + *buf++ = ('0' + (year / 1000) % 10); + *buf++ = ('0' + (year / 100) % 10); + *buf++ = ('0' + (year / 10) % 10); + *buf++ = ('0' + (year % 10)); + *buf++ = ' '; + + /* Hour */ + *buf++ = ('0' + (hour / 10)); + *buf++ = ('0' + (hour % 10)); + *buf++ = ':'; + + /* Minutes */ + *buf++ = ('0' + (min / 10)); + *buf++ = ('0' + (min % 10)); + *buf++ = ':'; + + /* Seconds */ + *buf++ = ('0' + (sec / 10)); + *buf++ = ('0' + (sec % 10)); + + /* GMT Time zone + CRLF */ + memcpy(buf, " GMT\r\n\0", 7); + + /* Add new entry to the cache */ + mk_utils_gmt_cache_add(*data, date); + + /* Set mk_ptr_t data len */ + return size; +} + +time_t mk_utils_gmt2utime(char *date) +{ + time_t new_unix_time; + struct tm t_data; + memset(&t_data, 0, sizeof(struct tm)); + + +#ifdef _WIN32 +#pragma message("Since there is no strptime in windows we'll parse the date in a really crude way just to get it out of the way") + + if (0 != strcmp(MK_UTILS_GMT_DATEFORMAT, "%a, %d %b %Y %H:%M:%S GMT")) { + return -1; + } + + { + char *token; + + token = strtok(date, " "); /* "%a, " */ + + if (NULL == token) { + return -1; + } + + token = strtok(NULL, " "); /* "%d " */ + + if (NULL == token) { + return -1; + } + + t_data.tm_mday = strtol(token, NULL, 10); + + token = strtok(NULL, " "); /* "%b " */ + + if (NULL == token) { + return -1; + } + + if(0 == _strnicmp(token, "jan", 3)){ + t_data.tm_mon = 0; + } + else if(0 == _strnicmp(token, "feb", 3)){ + t_data.tm_mon = 1; + } + else if(0 == _strnicmp(token, "mar", 3)){ + t_data.tm_mon = 2; + } + else if(0 == _strnicmp(token, "apr", 3)){ + t_data.tm_mon = 3; + } + else if(0 == _strnicmp(token, "may", 3)){ + t_data.tm_mon = 4; + } + else if(0 == _strnicmp(token, "jun", 3)){ + t_data.tm_mon = 5; + } + else if(0 == _strnicmp(token, "jul", 3)){ + t_data.tm_mon = 6; + } + else if(0 == _strnicmp(token, "aug", 3)){ + t_data.tm_mon = 7; + } + else if(0 == _strnicmp(token, "sep", 3)){ + t_data.tm_mon = 8; + } + else if(0 == _strnicmp(token, "oct", 3)){ + t_data.tm_mon = 9; + } + else if(0 == _strnicmp(token, "nov", 3)){ + t_data.tm_mon = 10; + } + else if(0 == _strnicmp(token, "dec", 3)){ + t_data.tm_mon = 11; + } + else { + return -1; + } + + token = strtok(NULL, " "); /* "%Y " */ + + if (NULL == token) { + return -1; + } + + t_data.tm_year = strtol(token, NULL, 10); + + token = strtok(NULL, ":"); /* "%H:" */ + + if (NULL == token) { + return -1; + } + + t_data.tm_hour = strtol(token, NULL, 10); + + token = strtok(NULL, ":"); /* "%M:" */ + + if (NULL == token) { + return -1; + } + + t_data.tm_min = strtol(token, NULL, 10); + + token = strtok(NULL, " "); /* "%S " */ + + if (NULL == token) { + return -1; + } + + t_data.tm_sec = strtol(token, NULL, 10); + } + +#else + if (!strptime(date, MK_UTILS_GMT_DATEFORMAT, (struct tm*)&t_data)) { + return -1; + } +#endif + + new_unix_time = timegm((struct tm *) &t_data); + + return (new_unix_time); +} + +int mk_buffer_cat(mk_ptr_t *p, char *buf1, int len1, char *buf2, int len2) +{ + /* Validate lengths */ + if (mk_unlikely(len1 < 0 || len2 < 0)) { + return -1; + } + + /* alloc space */ + p->data = (char *) mk_mem_alloc(len1 + len2 + 1); + + /* copy data */ + memcpy(p->data, buf1, len1); + memcpy(p->data + len1, buf2, len2); + p->data[len1 + len2] = '\0'; + + /* assign len */ + p->len = len1 + len2; + + return 0; +} + +/* Convert hexadecimal to int */ +int mk_utils_hex2int(char *hex, int len) +{ + int i = 0; + int res = 0; + char c; + + while ((c = *hex++) && i < len) { + res *= 0x10; + + if (c >= 'a' && c <= 'f') { + res += (c - 0x57); + } + else if (c >= 'A' && c <= 'F') { + res += (c - 0x37); + } + else if (c >= '0' && c <= '9') { + res += (c - 0x30); + } + else { + return -1; + } + i++; + } + + if (res < 0) { + return -1; + } + + return res; +} + +/* If the URI contains hexa format characters it will return + * convert the Hexa values to ASCII character + */ +char *mk_utils_url_decode(mk_ptr_t uri) +{ + int tmp, hex_result; + unsigned int i; + int buf_idx = 0; + char *buf; + char hex[3]; + + if ((tmp = mk_string_char_search(uri.data, '%', uri.len)) < 0) { + return NULL; + } + + i = tmp; + + buf = mk_mem_alloc_z(uri.len + 1); + if (i > 0) { + memcpy(buf, uri.data, i); + buf_idx = i; + } + + while (i < uri.len) { + if (uri.data[i] == '%' && i + 2 < uri.len) { + memcpy(hex, uri.data + i + 1, 2); + hex[2] = '\0'; + + hex_result = mk_utils_hex2int(hex, 2); + + if (hex_result != -1) { + buf[buf_idx] = hex_result; + } + else { + mk_mem_free(buf); + return NULL; + } + i += 2; + } + else { + buf[buf_idx] = uri.data[i]; + } + i++; + buf_idx++; + } + buf[buf_idx] = '\0'; + + return buf; +} + +#ifndef MK_HAVE_BACKTRACE +void mk_utils_stacktrace(void) {} +#else +void mk_utils_stacktrace(void) +{ + unsigned int i; + int ret; + size_t size; + void *arr[10]; + Dl_info d; + + printf("[stack trace]\n"); + size = backtrace(arr, 10); + + for (i = 1; i < size && i < 10; i++) { + ret = dladdr(arr[i], &d); + if (ret == 0 || !d.dli_sname) { + printf(" #%i 0x%016" PRIxPTR " in \?\?\?\?\?\?\?()\n", + (i - 1), (uintptr_t) arr[i]); + continue; + } + + printf(" #%i 0x%016" PRIxPTR " in %s() from %s\n", + (i - 1), (uintptr_t) arr[i], d.dli_sname, d.dli_fname); + } +} +#endif + + + +/* + * This hash generation function is taken originally from Redis source code: + * + * https://github.com/antirez/redis/blob/unstable/src/dict.c#L109 + * + * ---- + * MurmurHash2, by Austin Appleby + * Note - This code makes a few assumptions about how your machine behaves - + * 1. We can read a 4-byte value from any address without crashing + * 2. sizeof(int) == 4 + * + * And it has a few limitations - + * + * 1. It will not work incrementally. + * 2. It will not produce the same results on little-endian and big-endian + * machines. + */ +unsigned int mk_utils_gen_hash(const void *key, int len) +{ + /* 'm' and 'r' are mixing constants generated offline. + They're not really 'magic', they just happen to work well. */ + uint32_t seed = 5381; + const uint32_t m = 0x5bd1e995; + const int r = 24; + + /* Initialize the hash to a 'random' value */ + uint32_t h = seed ^ len; + + /* Mix 4 bytes at a time into the hash */ + const unsigned char *data = (const unsigned char *)key; + + while(len >= 4) { + uint32_t k = *(uint32_t*) data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + /* Handle the last few bytes of the input array */ + switch(len) { + case 3: h ^= data[2] << 16; // fallthrough + case 2: h ^= data[1] << 8; // fallthrough + case 1: h ^= data[0]; h *= m; + }; + + /* Do a few final mixes of the hash to ensure the last few + * bytes are well-incorporated. */ + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return (unsigned int) h; +} diff --git a/fluent-bit/lib/monkey/mk_server/mk_vhost.c b/fluent-bit/lib/monkey/mk_server/mk_vhost.c new file mode 100644 index 00000000..2dc35fb1 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/mk_vhost.c @@ -0,0 +1,821 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <monkey/mk_info.h> +#include <monkey/monkey.h> +#include <monkey/mk_core.h> +#include <monkey/mk_vhost.h> +#include <monkey/mk_vhost_tls.h> +#include <monkey/mk_utils.h> +#include <monkey/mk_http_status.h> +#include <monkey/mk_info.h> + +#include <mk_core/mk_dirent.h> + +#include <re.h> +#include <sys/stat.h> +#include <fcntl.h> + +static int str_to_regex(char *str, regex_t *reg) +{ + char *p = str; + regex_t *result; + + while (*p) { + if (*p == ' ') *p = '|'; + p++; + } + + result = re_compile(str); + + memcpy(reg, result, REGEXP_SIZE); + + return 0; +} + +/* + * This function is triggered upon thread creation (inside the thread + * context), here we configure per-thread data. + */ +int mk_vhost_fdt_worker_init(struct mk_server *server) +{ + int i; + int j; + struct mk_vhost *h; + struct mk_list *list; + struct mk_list *head; + struct vhost_fdt_host *fdt; + struct vhost_fdt_hash_table *ht; + struct vhost_fdt_hash_chain *hc; + + if (server->fdt == MK_FALSE) { + return -1; + } + + /* + * We are under a thread context and the main configuration is + * already in place. Now for every existent virtual host we are + * going to create the File Descriptor Table (FDT) which aims to + * hold references of 'open and shared' file descriptors under + * the Virtual Host context. + */ + + /* + * Under an initialization context we need to protect this critical + * section + */ + pthread_mutex_lock(&server->vhost_fdt_mutex); + + /* + * Initialize the thread FDT/Hosts list and create an entry per + * existent virtual host + */ + list = mk_mem_alloc_z(sizeof(struct mk_list)); + mk_list_init(list); + + mk_list_foreach(head, &server->hosts) { + h = mk_list_entry(head, struct mk_vhost, _head); + + fdt = mk_mem_alloc(sizeof(struct vhost_fdt_host)); + fdt->host = h; + + /* Initialize hash table */ + for (i = 0; i < VHOST_FDT_HASHTABLE_SIZE; i++) { + ht = &fdt->hash_table[i]; + ht->av_slots = VHOST_FDT_HASHTABLE_CHAINS; + + /* for each chain under the hash table, set the fd */ + for (j = 0; j < VHOST_FDT_HASHTABLE_CHAINS; j++) { + hc = &ht->chain[j]; + hc->fd = -1; + hc->hash = 0; + hc->readers = 0; + } + } + mk_list_add(&fdt->_head, list); + } + + MK_TLS_SET(mk_tls_vhost_fdt, list); + pthread_mutex_unlock(&server->vhost_fdt_mutex); + + return 0; +} + +int mk_vhost_fdt_worker_exit(struct mk_server *server) +{ + struct mk_list *list; + struct mk_list *head; + struct mk_list *tmp; + struct vhost_fdt_host *fdt; + + if (server->fdt == MK_FALSE) { + return -1; + } + + list = MK_TLS_GET(mk_tls_vhost_fdt); + mk_list_foreach_safe(head, tmp, list) { + fdt = mk_list_entry(head, struct vhost_fdt_host, _head); + mk_list_del(&fdt->_head); + mk_mem_free(fdt); + } + + mk_mem_free(list); + return 0; +} + + +static inline +struct vhost_fdt_hash_table *mk_vhost_fdt_table_lookup(int id, struct mk_vhost *host) +{ + struct mk_list *head; + struct mk_list *list; + struct vhost_fdt_host *fdt_host; + struct vhost_fdt_hash_table *ht = NULL; + + list = MK_TLS_GET(mk_tls_vhost_fdt); + mk_list_foreach(head, list) { + fdt_host = mk_list_entry(head, struct vhost_fdt_host, _head); + if (fdt_host->host == host) { + ht = &fdt_host->hash_table[id]; + return ht; + } + } + + return ht; +} + +static inline +struct vhost_fdt_hash_chain +*mk_vhost_fdt_chain_lookup(unsigned int hash, struct vhost_fdt_hash_table *ht) +{ + int i; + struct vhost_fdt_hash_chain *hc = NULL; + + for (i = 0; i < VHOST_FDT_HASHTABLE_CHAINS; i++) { + hc = &ht->chain[i]; + if (hc->hash == hash) { + return hc; + } + } + + return NULL; +} + + +static inline int mk_vhost_fdt_open(int id, unsigned int hash, + struct mk_http_request *sr, + struct mk_server *server) +{ + int i; + int fd = -1; + struct vhost_fdt_hash_table *ht = NULL; + struct vhost_fdt_hash_chain *hc; + + if (server->fdt == MK_FALSE) { + return open(sr->real_path.data, sr->file_info.flags_read_only); + } + + ht = mk_vhost_fdt_table_lookup(id, sr->host_conf); + if (mk_unlikely(!ht)) { + return open(sr->real_path.data, sr->file_info.flags_read_only); + } + + /* We got the hash table, now look around the chains array */ + hc = mk_vhost_fdt_chain_lookup(hash, ht); + if (hc) { + /* Increment the readers and return the shared FD */ + hc->readers++; + sr->vhost_fdt_id = id; + sr->vhost_fdt_hash = hash; + sr->vhost_fdt_enabled = MK_TRUE; + return hc->fd; + } + + /* + * Get here means that no entry exists in the hash table for the + * requested file descriptor and hash, we must try to open the file + * and register the entry in the table. + */ + fd = open(sr->real_path.data, sr->file_info.flags_read_only); + if (fd == -1) { + return -1; + } + + /* If chains are full, just return the new FD, bad luck... */ + if (ht->av_slots <= 0) { + return fd; + } + + /* Register the new entry in an available slot */ + for (i = 0; i < VHOST_FDT_HASHTABLE_CHAINS; i++) { + hc = &ht->chain[i]; + if (hc->fd == -1) { + hc->fd = fd; + hc->hash = hash; + hc->readers++; + ht->av_slots--; + + sr->vhost_fdt_id = id; + sr->vhost_fdt_hash = hash; + sr->vhost_fdt_enabled = MK_TRUE; + + return fd; + } + } + + return fd; +} + +static inline int mk_vhost_fdt_close(struct mk_http_request *sr, + struct mk_server *server) +{ + int id; + unsigned int hash; + struct vhost_fdt_hash_table *ht = NULL; + struct vhost_fdt_hash_chain *hc; + + if (server->fdt == MK_FALSE || sr->vhost_fdt_enabled == MK_FALSE) { + if (sr->in_file.fd > 0) { + return close(sr->in_file.fd); + } + return -1; + } + + id = sr->vhost_fdt_id; + hash = sr->vhost_fdt_hash; + + ht = mk_vhost_fdt_table_lookup(id, sr->host_conf); + if (mk_unlikely(!ht)) { + return close(sr->in_file.fd); + } + + /* We got the hash table, now look around the chains array */ + hc = mk_vhost_fdt_chain_lookup(hash, ht); + if (hc) { + /* Increment the readers and check if we should close */ + hc->readers--; + sr->vhost_fdt_enabled = MK_FALSE; + + if (hc->readers == 0) { + hc->fd = -1; + hc->hash = 0; + ht->av_slots++; + return close(sr->in_file.fd); + } + else { + return 0; + } + } + return close(sr->in_file.fd); +} + + +int mk_vhost_open(struct mk_http_request *sr, struct mk_server *server) +{ + int id; + int off; + unsigned int hash; + + off = sr->host_conf->documentroot.len; + hash = mk_utils_gen_hash(sr->real_path.data + off, + sr->real_path.len - off); + id = (hash % VHOST_FDT_HASHTABLE_SIZE); + + return mk_vhost_fdt_open(id, hash, sr, server); +} + +int mk_vhost_close(struct mk_http_request *sr, struct mk_server *server) +{ + return mk_vhost_fdt_close(sr, server); +} + +struct mk_vhost_handler *mk_vhost_handler_match(char *match, + void (*cb)(struct mk_http_request *, + void *), + void *data) +{ + int ret; + struct mk_vhost_handler *h; + + h = mk_mem_alloc(sizeof(struct mk_vhost_handler)); + if (!h) { + return NULL; + } + h->name = NULL; + h->cb = cb; + h->data = data; + h->match = mk_mem_alloc(REGEXP_SIZE); + if (!h->match) { + mk_mem_free(h); + return NULL; + } + mk_list_init(&h->params); + + ret = str_to_regex(match, h->match); + if (ret == -1) { + mk_mem_free(h); + return NULL; + } + + return h; +} + +/* + * Open a virtual host configuration file and return a structure with + * definitions. + */ +struct mk_vhost *mk_vhost_read(char *path) +{ + int ret; + char *tmp; + char *host_low; + struct stat checkdir; + struct mk_vhost *host; + struct mk_vhost_alias *new_alias; + struct mk_vhost_error_page *err_page; + struct mk_rconf *cnf; + struct mk_rconf_section *section_host; + struct mk_rconf_section *section_ep; + struct mk_rconf_section *section_handlers; + struct mk_rconf_entry *entry_ep; + struct mk_string_line *entry; + struct mk_list *head, *list, *line; + struct mk_vhost_handler *h_handler; + struct mk_vhost_handler_param *h_param; + + /* Read configuration file */ + cnf = mk_rconf_open(path); + if (!cnf) { + mk_err("Configuration error, aborting."); + exit(EXIT_FAILURE); + } + + /* Read 'HOST' section */ + section_host = mk_rconf_section_get(cnf, "HOST"); + if (!section_host) { + mk_err("Invalid config file %s", path); + return NULL; + } + + /* Alloc configuration node */ + host = mk_mem_alloc_z(sizeof(struct mk_vhost)); + host->config = cnf; + host->file = mk_string_dup(path); + + /* Init list for host name aliases */ + mk_list_init(&host->server_names); + + /* Init list for custom error pages */ + mk_list_init(&host->error_pages); + + /* Init list for content handlers */ + mk_list_init(&host->handlers); + + /* Lookup Servername */ + list = mk_rconf_section_get_key(section_host, "Servername", MK_RCONF_LIST); + if (!list) { + mk_err("Hostname does not contain a Servername"); + exit(EXIT_FAILURE); + } + + mk_list_foreach(head, list) { + entry = mk_list_entry(head, struct mk_string_line, _head); + if (entry->len > MK_HOSTNAME_LEN - 1) { + continue; + } + + /* Hostname to lowercase */ + host_low = mk_string_tolower(entry->val); + + /* Alloc node */ + new_alias = mk_mem_alloc_z(sizeof(struct mk_vhost_alias)); + new_alias->name = mk_mem_alloc_z(entry->len + 1); + strncpy(new_alias->name, host_low, entry->len); + mk_mem_free(host_low); + + new_alias->len = entry->len; + + mk_list_add(&new_alias->_head, &host->server_names); + } + mk_string_split_free(list); + + /* Lookup document root handled by a mk_ptr_t */ + host->documentroot.data = mk_rconf_section_get_key(section_host, + "DocumentRoot", + MK_RCONF_STR); + if (!host->documentroot.data) { + mk_err("Missing DocumentRoot entry on %s file", path); + mk_rconf_free(cnf); + mk_mem_free(host->file); + mk_mem_free(host); + return NULL; + } + + host->documentroot.len = strlen(host->documentroot.data); + + /* Validate document root configured */ + if (stat(host->documentroot.data, &checkdir) == -1) { + mk_err("Invalid path to DocumentRoot in %s", path); + } + else if (!(checkdir.st_mode & S_IFDIR)) { + mk_err("DocumentRoot variable in %s has an invalid directory path", path); + } + + if (mk_list_is_empty(&host->server_names) == 0) { + mk_rconf_free(cnf); + mk_mem_free(host->file); + mk_mem_free(host); + return NULL; + } + + /* Check Virtual Host redirection */ + host->header_redirect.data = NULL; + host->header_redirect.len = 0; + + tmp = mk_rconf_section_get_key(section_host, + "Redirect", + MK_RCONF_STR); + if (tmp) { + host->header_redirect.data = mk_string_dup(tmp); + host->header_redirect.len = strlen(tmp); + mk_mem_free(tmp); + } + + /* Error Pages */ + section_ep = mk_rconf_section_get(cnf, "ERROR_PAGES"); + if (section_ep) { + mk_list_foreach(head, §ion_ep->entries) { + entry_ep = mk_list_entry(head, struct mk_rconf_entry, _head); + + int ep_status = -1; + char *ep_file = NULL; + unsigned long len; + + ep_status = atoi(entry_ep->key); + ep_file = entry_ep->val; + + /* Validate input values */ + if (ep_status < MK_CLIENT_BAD_REQUEST || + ep_status > MK_SERVER_HTTP_VERSION_UNSUP || + ep_file == NULL) { + continue; + } + + /* Alloc error page node */ + err_page = mk_mem_alloc_z(sizeof(struct mk_vhost_error_page)); + err_page->status = ep_status; + err_page->file = mk_string_dup(ep_file); + err_page->real_path = NULL; + mk_string_build(&err_page->real_path, &len, "%s/%s", + host->documentroot.data, err_page->file); + + MK_TRACE("Map error page: status %i -> %s", err_page->status, err_page->file); + + /* Link page to the error page list */ + mk_list_add(&err_page->_head, &host->error_pages); + } + } + + /* Handlers */ + int i; + int params; + struct mk_list *head_line; + + section_handlers = mk_rconf_section_get(cnf, "HANDLERS"); + if (!section_handlers) { + return host; + } + mk_list_foreach(head, §ion_handlers->entries) { + entry_ep = mk_list_entry(head, struct mk_rconf_entry, _head); + if (strncasecmp(entry_ep->key, "Match", strlen(entry_ep->key)) == 0) { + line = mk_string_split_line(entry_ep->val); + if (!line) { + continue; + } + h_handler = mk_mem_alloc(sizeof(struct mk_vhost_handler)); + if (!h_handler) { + exit(EXIT_FAILURE); + } + h_handler->match = mk_mem_alloc(REGEXP_SIZE); + if (!h_handler->match) { + mk_mem_free(h_handler); + exit(EXIT_FAILURE); + } + h_handler->cb = NULL; + mk_list_init(&h_handler->params); + + i = 0; + params = 0; + mk_list_foreach(head_line, line) { + entry = mk_list_entry(head_line, struct mk_string_line, _head); + switch (i) { + case 0: + ret = str_to_regex(entry->val, h_handler->match); + if (ret == -1) { + return NULL; + } + break; + case 1: + h_handler->name = mk_string_dup(entry->val); + break; + default: + /* link parameters */ + h_param = mk_mem_alloc(sizeof(struct mk_vhost_handler_param)); + h_param->p.data = mk_string_dup(entry->val); + h_param->p.len = entry->len; + mk_list_add(&h_param->_head, &h_handler->params); + params++; + }; + i++; + } + h_handler->n_params = params; + mk_string_split_free(line); + + if (i < 2) { + mk_err("[Host Handlers] invalid Match value\n"); + exit(EXIT_FAILURE); + } + mk_list_add(&h_handler->_head, &host->handlers); + } + } + + + return host; +} + +int mk_vhost_map_handlers(struct mk_server *server) +{ + int n = 0; + struct mk_list *head; + struct mk_list *head_handler; + struct mk_vhost *host; + struct mk_vhost_handler *h_handler; + struct mk_plugin *p; + + mk_list_foreach(head, &server->hosts) { + host = mk_list_entry(head, struct mk_vhost, _head); + mk_list_foreach(head_handler, &host->handlers) { + h_handler = mk_list_entry(head_handler, + struct mk_vhost_handler, _head); + + /* Lookup plugin by name */ + p = mk_plugin_lookup(h_handler->name, server); + if (!p) { + mk_err("Plugin '%s' was not loaded", h_handler->name); + continue; + } + + if (p->hooks != MK_PLUGIN_STAGE) { + mk_err("Plugin '%s' is not a handler", h_handler->name); + continue; + } + + h_handler->handler = p; + n++; + } + } + + return n; +} + +void mk_vhost_set_single(char *path, struct mk_server *server) +{ + struct mk_vhost *host; + struct mk_vhost_alias *halias; + struct stat checkdir; + + /* Set the default host */ + host = mk_mem_alloc_z(sizeof(struct mk_vhost)); + mk_list_init(&host->error_pages); + mk_list_init(&host->server_names); + + /* Prepare the unique alias */ + halias = mk_mem_alloc_z(sizeof(struct mk_vhost_alias)); + halias->name = mk_string_dup("127.0.0.1"); + mk_list_add(&halias->_head, &host->server_names); + + host->documentroot.data = mk_string_dup(path); + host->documentroot.len = strlen(path); + host->header_redirect.data = NULL; + + /* Validate document root configured */ + if (stat(host->documentroot.data, &checkdir) == -1) { + mk_err("Invalid path to DocumentRoot in %s", path); + exit(EXIT_FAILURE); + } + else if (!(checkdir.st_mode & S_IFDIR)) { + mk_err("DocumentRoot variable in %s has an invalid directory path", path); + exit(EXIT_FAILURE); + } + mk_list_add(&host->_head, &server->hosts); + mk_list_init(&host->handlers); +} + +/* Given a configuration directory, start reading the virtual host entries */ +void mk_vhost_init(char *path, struct mk_server *server) +{ + DIR *dir; + unsigned long len; + char *buf = 0; + char *sites = 0; + char *file; + struct mk_vhost *p_host; /* debug */ + struct dirent *ent; + struct file_info f_info; + int ret; + + if (!server->conf_sites) { + mk_warn("[vhost] skipping default site"); + return; + } + + /* Read default virtual host file */ + mk_string_build(&sites, &len, "%s/%s/", + path, server->conf_sites); + ret = mk_file_get_info(sites, &f_info, MK_FILE_EXISTS); + if (ret == -1 || f_info.is_directory == MK_FALSE) { + mk_mem_free(sites); + sites = server->conf_sites; + } + + mk_string_build(&buf, &len, "%s/default", sites); + + p_host = mk_vhost_read(buf); + if (!p_host) { + mk_err("Error parsing main configuration file 'default'"); + } + mk_list_add(&p_host->_head, &server->hosts); + server->nhosts++; + mk_mem_free(buf); + buf = NULL; + + + /* Read all virtual hosts defined in sites/ */ + if (!(dir = opendir(sites))) { + mk_mem_free(sites); + mk_err("Could not open %s", sites); + exit(EXIT_FAILURE); + } + + /* Reading content */ + while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.') { + continue; + } + if (strcmp((char *) ent->d_name, "..") == 0) { + continue; + } + if (ent->d_name[strlen(ent->d_name) - 1] == '~') { + continue; + } + if (strcasecmp((char *) ent->d_name, "default") == 0) { + continue; + } + file = NULL; + mk_string_build(&file, &len, "%s/%s", sites, ent->d_name); + + p_host = mk_vhost_read(file); + mk_mem_free(file); + if (!p_host) { + continue; + } + else { + mk_list_add(&p_host->_head, &server->hosts); + server->nhosts++; + } + } + closedir(dir); + mk_mem_free(sites); +} + + +/* Lookup a registered virtual host based on the given 'host' input */ +int mk_vhost_get(mk_ptr_t host, struct mk_vhost **vhost, + struct mk_vhost_alias **alias, + struct mk_server *server) +{ + struct mk_vhost *entry_host; + struct mk_vhost_alias *entry_alias; + struct mk_list *head_vhost, *head_alias; + + mk_list_foreach(head_vhost, &server->hosts) { + entry_host = mk_list_entry(head_vhost, struct mk_vhost, _head); + mk_list_foreach(head_alias, &entry_host->server_names) { + entry_alias = mk_list_entry(head_alias, struct mk_vhost_alias, _head); + if (entry_alias->len == host.len && + strncmp(entry_alias->name, host.data, host.len) == 0) { + *vhost = entry_host; + *alias = entry_alias; + return 0; + } + } + } + + return -1; +} + +static void mk_vhost_handler_free(struct mk_vhost_handler *h) +{ + struct mk_list *tmp; + struct mk_list *head; + struct mk_vhost_handler_param *param; + + /* Release Params */ + mk_list_foreach_safe(head, tmp, &h->params) { + param = mk_list_entry(head, struct mk_vhost_handler_param, _head); + mk_list_del(¶m->_head); + mk_mem_free(param->p.data); + mk_mem_free(param); + } + + mk_mem_free(h->match); + mk_mem_free(h->name); + mk_mem_free(h); +} + +int mk_vhost_destroy(struct mk_vhost *vh) +{ + struct mk_vhost_alias *halias = NULL; + struct mk_vhost_handler *hhandler; + struct mk_vhost_error_page *ep; + struct mk_list *head; + struct mk_list *tmp; + + if (vh) { + /* Free aliases or servernames */ + mk_list_foreach_safe(head, tmp, &vh->server_names) { + halias = mk_list_entry(head, struct mk_vhost_alias, _head); + if (halias) { + mk_list_del(&halias->_head); + if (halias->name) { + mk_mem_free(halias->name); + } + mk_mem_free(halias); + } + } + + /* Handlers */ + mk_list_foreach_safe(head, tmp, &vh->handlers) { + hhandler = mk_list_entry(head, struct mk_vhost_handler, _head); + if (hhandler) { + mk_vhost_handler_free(hhandler); + } + } + + /* Free error pages */ + mk_list_foreach_safe(head, tmp, &vh->error_pages) { + ep = mk_list_entry(head, struct mk_vhost_error_page, _head); + if (ep) { + mk_list_del(&ep->_head); + if (ep->file) { + mk_mem_free(ep->file); + } + if (ep->real_path) { + mk_mem_free(ep->real_path); + } + mk_mem_free(ep); + } + } + mk_ptr_free(&vh->documentroot); + + /* Free source configuration */ + if (vh->config) { + mk_rconf_free(vh->config); + } + mk_list_del(&vh->_head); + if (vh->file) { + mk_mem_free(vh->file); + } + + mk_mem_free(vh); + } + return 0; +} + +void mk_vhost_free_all(struct mk_server *server) +{ + struct mk_vhost *host; + struct mk_list *head; + struct mk_list *tmp; + + mk_list_foreach_safe(head, tmp, &server->hosts) { + host = mk_list_entry(head, struct mk_vhost, _head); + mk_vhost_destroy(host); + } +} diff --git a/fluent-bit/lib/monkey/mk_server/monkey.c b/fluent-bit/lib/monkey/mk_server/monkey.c new file mode 100644 index 00000000..68513d21 --- /dev/null +++ b/fluent-bit/lib/monkey/mk_server/monkey.c @@ -0,0 +1,241 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Monkey HTTP Server + * ================== + * Copyright 2001-2017 Eduardo Silva <eduardo@monkey.io> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _GNU_SOURCE + +#include <mk_core/mk_pthread.h> +#include <mk_core/mk_event.h> + +#include <monkey/mk_scheduler.h> +#include <monkey/mk_plugin.h> +#include <monkey/mk_clock.h> +#include <monkey/mk_thread.h> +#include <monkey/mk_mimetype.h> +#include <monkey/mk_http_thread.h> + +pthread_once_t mk_server_tls_setup_once = PTHREAD_ONCE_INIT; + +static void mk_set_up_tls_keys() +{ + MK_INIT_INITIALIZE_TLS_UNIVERSAL(); + MK_INIT_INITIALIZE_TLS(); + + mk_http_thread_initialize_tls(); +} + +void mk_server_info(struct mk_server *server) +{ + struct mk_list *head; + struct mk_plugin *p; + struct mk_config_listener *l; + +#ifdef _WIN32 + printf(MK_BANNER_ENTRY "Process ID is %ld\n", (long)GetCurrentProcessId()); +#else + printf(MK_BANNER_ENTRY "Process ID is %ld\n", (long) getpid()); +#endif + mk_list_foreach(head, &server->listeners) { + l = mk_list_entry(head, struct mk_config_listener, _head); + printf(MK_BANNER_ENTRY "Server listening on %s:%s\n", + l->address, l->port); + } + printf(MK_BANNER_ENTRY + "%i threads, may handle up to %i client connections\n", + server->workers, server->server_capacity); + + /* List loaded plugins */ + printf(MK_BANNER_ENTRY "Loaded Plugins: "); + mk_list_foreach(head, &server->plugins) { + p = mk_list_entry(head, struct mk_plugin, _head); + printf("%s ", p->shortname); + } + printf("\n"); + +#ifdef __linux__ + char tmp[64]; + + if (mk_kernel_features_print(tmp, sizeof(tmp), server) > 0) { + printf(MK_BANNER_ENTRY "Linux Features: %s\n", tmp); + } +#endif + + fflush(stdout); +} + +/* Initialize Monkey Server */ +struct mk_server *mk_server_create() +{ + int ret; + int kern_version; + int kern_features; + struct mk_server *server; + + server = mk_mem_alloc_z(sizeof(struct mk_server)); + if (!server) { + return NULL; + } + + /* I'll try to leave both initializations here because + * it should be possible to run in windows using the accept + * backend in which case it doesn't make sense to tie the net stack + * initialization to libevent. + */ + mk_net_init(); + mk_event_init(); + + /* Library mode: event loop */ + server->lib_mode = MK_TRUE; + server->lib_evl = mk_event_loop_create(8); + if (!server->lib_evl) { + mk_mem_free(server); + return NULL; + } + + /* Library mode: channel manager */ + + memset(&server->lib_ch_event, 0, sizeof(struct mk_event)); + + ret = mk_event_channel_create(server->lib_evl, + &server->lib_ch_manager[0], + &server->lib_ch_manager[1], + &server->lib_ch_event); + + if (ret != 0) { + mk_event_loop_destroy(server->lib_evl); + mk_mem_free(server); + return NULL; + } + + /* Library mode: start event loop */ + server->lib_evl_start = mk_event_loop_create(1); + if (!server->lib_evl_start) { + mk_event_loop_destroy(server->lib_evl); + mk_mem_free(server); + return NULL; + } + + memset(&server->lib_ch_start_event, 0, sizeof(struct mk_event)); + + ret = mk_event_channel_create(server->lib_evl_start, + &server->lib_ch_start[0], + &server->lib_ch_start[1], + &server->lib_ch_start_event); + + if (ret != 0) { + mk_event_loop_destroy(server->lib_evl); + mk_event_loop_destroy(server->lib_evl_start); + mk_mem_free(server); + return NULL; + } + + /* Initialize linked list heads */ + mk_list_init(&server->plugins); + mk_list_init(&server->sched_worker_callbacks); + mk_list_init(&server->stage10_handler); + mk_list_init(&server->stage20_handler); + mk_list_init(&server->stage30_handler); + mk_list_init(&server->stage40_handler); + mk_list_init(&server->stage50_handler); + server->scheduler_mode = -1; + + mk_core_init(); + + /* Init thread keys */ + pthread_once(&mk_server_tls_setup_once, mk_set_up_tls_keys); + + /* Init Kernel version data */ + kern_version = mk_kernel_version(); + kern_features = mk_kernel_features(kern_version); + + server->kernel_version = kern_version; + server->kernel_features = kern_features; + +#ifdef MK_HAVE_TRACE + MK_TRACE("Monkey TRACE is enabled"); + //pthread_mutex_init(&mutex_trace, (pthread_mutexattr_t *) NULL); +#endif + +#ifdef LINUX_TRACE + mk_info("Linux Trace enabled"); +#endif + + mk_config_set_init_values(server); + + mk_mimetype_init(server); + + pthread_mutex_init(&server->vhost_fdt_mutex, NULL); + + return server; +} + +int mk_server_setup(struct mk_server *server) +{ + int ret; + pthread_t tid; + + /* Core and Scheduler setup */ + mk_config_start_configure(server); + mk_config_signature(server); + + mk_sched_init(server); + + + /* Clock init that must happen before starting threads */ + mk_clock_sequential_init(server); + + /* Load plugins */ + mk_plugin_api_init(server); + mk_plugin_load_all(server); + + /* Workers: logger and clock */ + ret = mk_utils_worker_spawn((void *) mk_clock_worker_init, server, &tid); + if (ret != 0) { + return -1; + } + + /* Configuration sanity check */ + mk_config_sanity_check(server); + + /* Invoke Plugin PRCTX hooks */ + mk_plugin_core_process(server); + + /* Launch monkey http workers */ + mk_server_launch_workers(server); + + return 0; +} + +void mk_exit_all(struct mk_server *server) +{ + uint64_t val; + + /* Distribute worker signals to stop working */ + val = MK_SCHED_SIGNAL_FREE_ALL; + mk_sched_broadcast_signal(server, val); + + /* Wait for all workers to finish */ + mk_sched_workers_join(server); + + /* Continue exiting */ + mk_plugin_exit_all(server); + mk_clock_exit(server); + + mk_sched_exit(server); + mk_config_free_all(server); +} |