diff options
author | David Goulet <dgoulet@torproject.org> | 2020-10-20 10:57:24 -0400 |
---|---|---|
committer | David Goulet <dgoulet@torproject.org> | 2020-10-27 10:43:42 -0400 |
commit | 4f5cea1f592d9e9e6c69fc0e772dd46a0fa43799 (patch) | |
tree | ad94055219fcf1db9f0445f584283a5af397ef00 /src/feature/metrics | |
parent | a882d1bf0abbbcf2bc4f8c039f9b82262462292c (diff) | |
download | tor-4f5cea1f592d9e9e6c69fc0e772dd46a0fa43799.tar.gz tor-4f5cea1f592d9e9e6c69fc0e772dd46a0fa43799.zip |
conn: New Metrics listener port
If MetricsPort is defined, listen on it and handle the incoming request.
Signed-off-by: David Goulet <dgoulet@torproject.org>
Diffstat (limited to 'src/feature/metrics')
-rw-r--r-- | src/feature/metrics/metrics.c | 210 | ||||
-rw-r--r-- | src/feature/metrics/metrics.h | 16 |
2 files changed, 215 insertions, 11 deletions
diff --git a/src/feature/metrics/metrics.c b/src/feature/metrics/metrics.c index 5f6fe776b7..886182bc90 100644 --- a/src/feature/metrics/metrics.c +++ b/src/feature/metrics/metrics.c @@ -8,25 +8,68 @@ #include "orconfig.h" -#include "lib/container/smartlist.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_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" -/** Return newly allocated string containing the output of all subsystems +/** 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. */ -char * +buf_t * metrics_get_output(const metrics_format_t fmt) { - char *data; - smartlist_t *chunks = smartlist_new(); + buf_t *data = buf_new(); /* Go over all subsystems that exposes a metrics store. */ for (unsigned i = 0; i < n_tor_subsystems; ++i) { @@ -40,17 +83,164 @@ metrics_get_output(const metrics_format_t fmt) if (sys->get_metrics && (stores = sys->get_metrics())) { SMARTLIST_FOREACH_BEGIN(stores, const metrics_store_t *, store) { - smartlist_add(chunks, metrics_store_get_output(fmt, store)); + metrics_store_get_output(fmt, store, data); } SMARTLIST_FOREACH_END(store); } } - data = smartlist_join_strings(chunks, "\n", 0, NULL); + return data; +} + +/** Process what is in the inbuf of this connection of type metrics. + * + * Return 0 on success else -1 on error which will close the connection. */ +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 = fetch_from_buf_http(conn->inbuf, &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. */ + 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); - SMARTLIST_FOREACH(chunks, char *, c, tor_free(c)); - smartlist_free(chunks); + /* 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 */ - return data; + 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); + } + + 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; } /** Initialize the subsystem. */ diff --git a/src/feature/metrics/metrics.h b/src/feature/metrics/metrics.h index a30c271bea..b4bbe28b27 100644 --- a/src/feature/metrics/metrics.h +++ b/src/feature/metrics/metrics.h @@ -9,13 +9,27 @@ #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. */ -char *metrics_get_output(const metrics_format_t fmt); +buf_t *metrics_get_output(const metrics_format_t fmt); + +/* Connection. */ +int metrics_connection_process_inbuf(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) */ |