diff options
Diffstat (limited to 'src/feature/metrics')
-rw-r--r-- | src/feature/metrics/.may_include | 1 | ||||
-rw-r--r-- | src/feature/metrics/include.am | 10 | ||||
-rw-r--r-- | src/feature/metrics/metrics.c | 280 | ||||
-rw-r--r-- | src/feature/metrics/metrics.h | 37 | ||||
-rw-r--r-- | src/feature/metrics/metrics_sys.c | 37 | ||||
-rw-r--r-- | src/feature/metrics/metrics_sys.h | 22 |
6 files changed, 387 insertions, 0 deletions
diff --git a/src/feature/metrics/.may_include b/src/feature/metrics/.may_include new file mode 100644 index 0000000000..424c745c12 --- /dev/null +++ b/src/feature/metrics/.may_include @@ -0,0 +1 @@ +*.h diff --git a/src/feature/metrics/include.am b/src/feature/metrics/include.am new file mode 100644 index 0000000000..0e875f43ad --- /dev/null +++ b/src/feature/metrics/include.am @@ -0,0 +1,10 @@ + +# ADD_C_FILE: INSERT SOURCES HERE. +LIBTOR_APP_A_SOURCES += \ + src/feature/metrics/metrics.c \ + src/feature/metrics/metrics_sys.c + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/feature/metrics/metrics.h \ + src/feature/metrics/metrics_sys.h diff --git a/src/feature/metrics/metrics.c b/src/feature/metrics/metrics.c new file mode 100644 index 0000000000..9a72fe7145 --- /dev/null +++ b/src/feature/metrics/metrics.c @@ -0,0 +1,280 @@ +/* Copyright (c) 2007-2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics.c + * @brief Metrics subsystem. + **/ + +#include "orconfig.h" + +#include "core/or/or.h" + +#include "lib/encoding/confline.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/metrics/metrics_store.h" +#include "lib/net/resolve.h" +#include "lib/string/printf.h" +#include "lib/net/nettypes.h" +#include "lib/net/address.h" + +#include "core/mainloop/connection.h" +#include "core/or/connection_or.h" +#include "core/or/connection_st.h" +#include "core/or/policies.h" +#include "core/or/port_cfg_st.h" +#include "core/proto/proto_http.h" + +#include "feature/dircommon/directory.h" +#include "feature/metrics/metrics.h" + +#include "app/config/config.h" +#include "app/main/subsysmgr.h" + +/** Metrics format driver set by the MetricsPort option. */ +static metrics_format_t the_format = METRICS_FORMAT_PROMETHEUS; + +/** Return true iff the given peer address is allowed by our MetricsPortPolicy + * option that is is in that list. */ +static bool +metrics_request_allowed(const tor_addr_t *peer_addr) +{ + tor_assert(peer_addr); + + return metrics_policy_permits_address(peer_addr); +} + +/** Helper: For a metrics port connection, write the HTTP response header + * using the data length passed. */ +static void +write_metrics_http_response(const size_t data_len, connection_t *conn) +{ + char date[RFC1123_TIME_LEN+1]; + buf_t *buf = buf_new_with_capacity(128 + data_len); + + format_rfc1123_time(date, approx_time()); + buf_add_printf(buf, "HTTP/1.0 200 OK\r\nDate: %s\r\n", date); + buf_add_printf(buf, "Content-Type: text/plain; charset=utf-8\r\n"); + buf_add_printf(buf, "Content-Length: %" TOR_PRIuSZ "\r\n", data_len); + buf_add_string(buf, "\r\n"); + + connection_buf_add_buf(conn, buf); + buf_free(buf); +} + +/** Return newly allocated buffer containing the output of all subsystems + * having metrics. + * + * This is used to output the content on the MetricsPort. */ +buf_t * +metrics_get_output(const metrics_format_t fmt) +{ + buf_t *data = buf_new(); + + /* Go over all subsystems that exposes a metrics store. */ + for (unsigned i = 0; i < n_tor_subsystems; ++i) { + const smartlist_t *stores; + const subsys_fns_t *sys = tor_subsystems[i]; + + /* Skip unsupported subsystems. */ + if (!sys->supported) { + continue; + } + + if (sys->get_metrics && (stores = sys->get_metrics())) { + SMARTLIST_FOREACH_BEGIN(stores, const metrics_store_t *, store) { + metrics_store_get_output(fmt, store, data); + } SMARTLIST_FOREACH_END(store); + } + } + + return data; +} + +/** Process what is in the inbuf of this connection of type metrics. + * + * Return 0 on success else -1 on error for which the connection is marked for + * close. */ +int +metrics_connection_process_inbuf(connection_t *conn) +{ + int ret = -1; + char *headers = NULL, *command = NULL, *url = NULL; + const char *errmsg = NULL; + + tor_assert(conn); + tor_assert(conn->type == CONN_TYPE_METRICS); + + if (!metrics_request_allowed(&conn->addr)) { + /* Close connection. Don't bother returning anything if you are not + * allowed by being on the policy list. */ + errmsg = NULL; + goto err; + } + + const int http_status = + connection_fetch_from_buf_http(conn, &headers, 1024, NULL, NULL, 1024, 0); + if (http_status < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } else if (http_status == 0) { + /* no HTTP request yet. */ + ret = 0; + goto done; + } + + const int cmd_status = parse_http_command(headers, &command, &url); + if (cmd_status < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } else if (strcmpstart(command, "GET")) { + errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; + goto err; + } + tor_assert(url); + + /* Where we expect the query to come for. */ +#define EXPECTED_URL_PATH "/metrics" +#define EXPECTED_URL_PATH_LEN (sizeof(EXPECTED_URL_PATH) - 1) /* No NUL */ + + if (!strcmpstart(url, EXPECTED_URL_PATH) && + strlen(url) == EXPECTED_URL_PATH_LEN) { + buf_t *data = metrics_get_output(the_format); + + write_metrics_http_response(buf_datalen(data), conn); + connection_buf_add_buf(conn, data); + buf_free(data); + } else { + errmsg = "HTTP/1.0 404 Not Found\r\n\r\n"; + goto err; + } + + ret = 0; + goto done; + + err: + if (errmsg) { + log_info(LD_EDGE, "HTTP metrics error: saying %s", escaped(errmsg)); + connection_buf_add(errmsg, strlen(errmsg), conn); + } + connection_mark_and_flush(conn); + + done: + tor_free(headers); + tor_free(command); + tor_free(url); + + return ret; +} + +/** Parse metrics ports from options. On success, add the port to the ports + * list and return 0. On failure, set err_msg_out to a newly allocated string + * describing the problem and return -1. */ +int +metrics_parse_ports(or_options_t *options, smartlist_t *ports, + char **err_msg_out) +{ + int num_elems, ok = 0, ret = -1; + const char *addrport_str = NULL, *fmt_str = NULL; + smartlist_t *elems = NULL; + port_cfg_t *cfg = NULL; + + tor_assert(options); + tor_assert(ports); + + /* No metrics port to configure, just move on . */ + if (!options->MetricsPort_lines) { + return 0; + } + + elems = smartlist_new(); + + /* Split between the protocol and the address/port. */ + num_elems = smartlist_split_string(elems, + options->MetricsPort_lines->value, " ", + SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 2); + if (num_elems < 1) { + *err_msg_out = tor_strdup("MetricsPort is missing port."); + goto end; + } + + addrport_str = smartlist_get(elems, 0); + if (num_elems >= 2) { + /* Parse the format if any. */ + fmt_str = smartlist_get(elems, 1); + if (!strcasecmp(fmt_str, "prometheus")) { + the_format = METRICS_FORMAT_PROMETHEUS; + } else { + tor_asprintf(err_msg_out, "MetricsPort unknown format: %s", fmt_str); + goto end; + } + } + + /* Port configuration with default address. */ + cfg = port_cfg_new(0); + cfg->type = CONN_TYPE_METRICS_LISTENER; + + /* Parse the port first. Then an address if any can be found. */ + cfg->port = (int) tor_parse_long(addrport_str, 10, 0, 65535, &ok, NULL); + if (ok) { + tor_addr_parse(&cfg->addr, "127.0.0.1"); + } else { + /* We probably have a host:port situation */ + if (tor_addr_port_lookup(addrport_str, &cfg->addr, + (uint16_t *) &cfg->port) < 0) { + *err_msg_out = tor_strdup("MetricsPort address/port failed to parse or " + "resolve."); + goto end; + } + } + /* Add it to the ports list. */ + smartlist_add(ports, cfg); + + /* It is set. MetricsPort doesn't support the NoListen options or such that + * would prevent from being a real listener port. */ + options->MetricsPort_set = 1; + + /* Success. */ + ret = 0; + + end: + if (ret != 0) { + port_cfg_free(cfg); + } + SMARTLIST_FOREACH(elems, char *, e, tor_free(e)); + smartlist_free(elems); + return ret; +} + +/** Called when conn has gotten its socket closed. */ +int +metrics_connection_reached_eof(connection_t *conn) +{ + tor_assert(conn); + + log_info(LD_EDGE, "Metrics connection reached EOF. Closing."); + connection_mark_for_close(conn); + return 0; +} + +/** Called when conn has no more bytes left on its outbuf. Return 0 indicating + * success. */ +int +metrics_connection_finished_flushing(connection_t *conn) +{ + tor_assert(conn); + return 0; +} + +/** Initialize the subsystem. */ +void +metrics_init(void) +{ +} + +/** Cleanup and free any global memory of this subsystem. */ +void +metrics_cleanup(void) +{ +} diff --git a/src/feature/metrics/metrics.h b/src/feature/metrics/metrics.h new file mode 100644 index 0000000000..e072519d10 --- /dev/null +++ b/src/feature/metrics/metrics.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics.h + * @brief Header for feature/metrics/metrics.c + **/ + +#ifndef TOR_FEATURE_METRICS_METRICS_H +#define TOR_FEATURE_METRICS_METRICS_H + +#include "lib/buf/buffers.h" +#include "lib/container/smartlist.h" + +#include "app/config/or_options_st.h" + +#include "lib/metrics/metrics_common.h" + +struct connection_t; + +/* Initializer / Cleanup. */ +void metrics_init(void); +void metrics_cleanup(void); + +/* Accessors. */ +buf_t *metrics_get_output(const metrics_format_t fmt); + +/* Connection. */ +int metrics_connection_process_inbuf(struct connection_t *conn); +int metrics_connection_reached_eof(struct connection_t *conn); +int metrics_connection_finished_flushing(struct connection_t *conn); + +/* Configuration. */ +int metrics_parse_ports(or_options_t *options, smartlist_t *ports, + char **err_msg_out); + +#endif /* !defined(TOR_FEATURE_METRICS_METRICS_H) */ diff --git a/src/feature/metrics/metrics_sys.c b/src/feature/metrics/metrics_sys.c new file mode 100644 index 0000000000..419318068e --- /dev/null +++ b/src/feature/metrics/metrics_sys.c @@ -0,0 +1,37 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_sys.c + * @brief Setup and tear down the metrics subsystem. + **/ + +#include "lib/subsys/subsys.h" + +#include "feature/metrics/metrics.h" +#include "feature/metrics/metrics_sys.h" + +static int +subsys_metrics_initialize(void) +{ + metrics_init(); + return 0; +} + +static void +subsys_metrics_shutdown(void) +{ + metrics_cleanup(); +} + +const subsys_fns_t sys_metrics = { + SUBSYS_DECLARE_LOCATION(), + + .name = "metrics", + .supported = true, + .level = METRICS_SUBSYS_LEVEL, + + .initialize = subsys_metrics_initialize, + .shutdown = subsys_metrics_shutdown, +}; + diff --git a/src/feature/metrics/metrics_sys.h b/src/feature/metrics/metrics_sys.h new file mode 100644 index 0000000000..30c1b14836 --- /dev/null +++ b/src/feature/metrics/metrics_sys.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_sys.h + * @brief Header for feature/metrics/metrics_sys.c + **/ + +#ifndef TOR_FEATURE_METRICS_METRICS_SYS_H +#define TOR_FEATURE_METRICS_METRICS_SYS_H + +extern const struct subsys_fns_t sys_metrics; + +/** + * Subsystem level for the metrics system. + * + * Defined here so that it can be shared between the real and stub + * definitions. + **/ +#define METRICS_SUBSYS_LEVEL (99) + +#endif /* !defined(TOR_FEATURE_METRICS_METRICS_SYS_H) */ |