diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app/config/config.c | 7 | ||||
-rw-r--r-- | src/app/config/or_options_st.h | 6 | ||||
-rw-r--r-- | src/core/mainloop/connection.c | 17 | ||||
-rw-r--r-- | src/core/mainloop/connection.h | 6 | ||||
-rw-r--r-- | src/core/or/policies.c | 32 | ||||
-rw-r--r-- | src/core/or/policies.h | 1 | ||||
-rw-r--r-- | src/feature/control/control_fmt.c | 2 | ||||
-rw-r--r-- | src/feature/control/control_getinfo.c | 2 | ||||
-rw-r--r-- | src/feature/metrics/metrics.c | 210 | ||||
-rw-r--r-- | src/feature/metrics/metrics.h | 16 |
10 files changed, 286 insertions, 13 deletions
diff --git a/src/app/config/config.c b/src/app/config/config.c index 6c17bb0d8c..e14437aa6f 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -91,6 +91,7 @@ #include "feature/dirclient/dirclient_modes.h" #include "feature/hibernate/hibernate.h" #include "feature/hs/hs_config.h" +#include "feature/metrics/metrics.h" #include "feature/nodelist/dirlist.h" #include "feature/nodelist/networkstatus.h" #include "feature/nodelist/nickname.h" @@ -560,6 +561,8 @@ static const config_var_t option_vars_[] = { OBSOLETE("MaxOnionsPending"), V(MaxOnionQueueDelay, MSEC_INTERVAL, "1750 msec"), V(MaxUnparseableDescSizeToLog, MEMUNIT, "10 MB"), + VPORT(MetricsPort), + V(MetricsPortPolicy, LINELIST, NULL), VAR("MyFamily", LINELIST, MyFamily_lines, NULL), V(NewCircuitPeriod, INTERVAL, "30 seconds"), OBSOLETE("NamingAuthoritativeDirectory"), @@ -6461,6 +6464,10 @@ parse_ports(or_options_t *options, int validate_only, *msg = tor_strdup("Invalid HTTPTunnelPort configuration"); goto err; } + if (metrics_parse_ports(options, ports, msg) < 0) { + goto err; + } + { unsigned control_port_flags = CL_PORT_NO_STREAM_OPTIONS | CL_PORT_WARN_NONLOCAL; diff --git a/src/app/config/or_options_st.h b/src/app/config/or_options_st.h index 3ccd2c9761..7a72547fd3 100644 --- a/src/app/config/or_options_st.h +++ b/src/app/config/or_options_st.h @@ -164,6 +164,8 @@ struct or_options_t { struct config_line_t *ORPort_lines; /** Ports to listen on for extended OR connections. */ struct config_line_t *ExtORPort_lines; + /** Ports to listen on for Metrics connections. */ + struct config_line_t *MetricsPort_lines; /** Ports to listen on for SOCKS connections. */ struct config_line_t *SocksPort_lines; /** Ports to listen on for transparent pf/netfilter connections. */ @@ -223,6 +225,7 @@ struct or_options_t { unsigned int DNSPort_set : 1; unsigned int ExtORPort_set : 1; unsigned int HTTPTunnelPort_set : 1; + unsigned int MetricsPort_set : 1; /**@}*/ /** Whether to publish our descriptor regardless of all our self-tests @@ -1076,6 +1079,9 @@ struct or_options_t { **/ int DormantCanceledByStartup; + /** List of policy allowed to query the Metrics port. */ + struct config_line_t *MetricsPortPolicy; + /** * Configuration objects for individual modules. * diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index 7a17d7ff9d..ebf15fcc9e 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -99,6 +99,7 @@ #include "feature/hibernate/hibernate.h" #include "feature/hs/hs_common.h" #include "feature/hs/hs_ident.h" +#include "feature/metrics/metrics.h" #include "feature/nodelist/nodelist.h" #include "feature/nodelist/routerlist.h" #include "feature/relay/dns.h" @@ -218,7 +219,8 @@ static smartlist_t *outgoing_addrs = NULL; case CONN_TYPE_AP_TRANS_LISTENER: \ case CONN_TYPE_AP_NATD_LISTENER: \ case CONN_TYPE_AP_DNS_LISTENER: \ - case CONN_TYPE_AP_HTTP_CONNECT_LISTENER + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: \ + case CONN_TYPE_METRICS_LISTENER /**************************************************************/ @@ -283,6 +285,8 @@ conn_type_to_string(int type) case CONN_TYPE_EXT_OR: return "Extended OR"; case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener"; case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return "HTTP tunnel listener"; + case CONN_TYPE_METRICS_LISTENER: return "Metrics listener"; + case CONN_TYPE_METRICS: return "Metrics"; default: log_warn(LD_BUG, "unknown connection type %d", type); tor_snprintf(buf, sizeof(buf), "unknown [%d]", type); @@ -2025,6 +2029,10 @@ connection_handle_listener_read(connection_t *conn, int new_type) log_notice(LD_CONTROL, "New control connection opened from %s.", fmt_and_decorate_addr(&addr)); } + if (new_type == CONN_TYPE_METRICS) { + log_info(LD_CONTROL, "New metrics connection opened from %s.", + fmt_and_decorate_addr(&addr)); + } } else if (conn->socket_family == AF_UNIX && conn->type != CONN_TYPE_AP) { tor_assert(conn->type == CONN_TYPE_CONTROL_LISTENER); @@ -3893,6 +3901,8 @@ connection_handle_read_impl(connection_t *conn) return connection_handle_listener_read(conn, CONN_TYPE_DIR); case CONN_TYPE_CONTROL_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_CONTROL); + case CONN_TYPE_METRICS_LISTENER: + return connection_handle_listener_read(conn, CONN_TYPE_METRICS); case CONN_TYPE_AP_DNS_LISTENER: /* This should never happen; eventdns.c handles the reads here. */ tor_fragile_assert(); @@ -5108,6 +5118,8 @@ connection_process_inbuf(connection_t *conn, int package_partial) return connection_dir_process_inbuf(TO_DIR_CONN(conn)); case CONN_TYPE_CONTROL: return connection_control_process_inbuf(TO_CONTROL_CONN(conn)); + case CONN_TYPE_METRICS: + return metrics_connection_process_inbuf(conn); default: log_err(LD_BUG,"got unexpected conn type %d.", conn->type); tor_fragile_assert(); @@ -5671,6 +5683,9 @@ assert_connection_ok(connection_t *conn, time_t now) tor_assert(conn->state >= CONTROL_CONN_STATE_MIN_); tor_assert(conn->state <= CONTROL_CONN_STATE_MAX_); break; + case CONN_TYPE_METRICS: + /* No state. */ + break; default: tor_assert(0); } diff --git a/src/core/mainloop/connection.h b/src/core/mainloop/connection.h index ee3dce49f4..9dab28c3d9 100644 --- a/src/core/mainloop/connection.h +++ b/src/core/mainloop/connection.h @@ -73,8 +73,12 @@ struct buf_t; #define CONN_TYPE_EXT_OR_LISTENER 17 /** Type for sockets listening for HTTP CONNECT tunnel connections. */ #define CONN_TYPE_AP_HTTP_CONNECT_LISTENER 18 +/** Type for sockets listening for Metrics query connections. */ +#define CONN_TYPE_METRICS_LISTENER 19 +/** Type for connections from metrics listener. */ +#define CONN_TYPE_METRICS 20 -#define CONN_TYPE_MAX_ 19 +#define CONN_TYPE_MAX_ 21 /* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in * struct connection_t. */ diff --git a/src/core/or/policies.c b/src/core/or/policies.c index 0dc440cc96..24e9c0ce9f 100644 --- a/src/core/or/policies.c +++ b/src/core/or/policies.c @@ -48,6 +48,8 @@ static smartlist_t *socks_policy = NULL; /** Policy that addresses for incoming directory connections must match. */ static smartlist_t *dir_policy = NULL; +/** Policy for incoming MetricsPort connections that must match. */ +static smartlist_t *metrics_policy = NULL; /** Policy that addresses for incoming router descriptors must match in order * to be published by us. */ static smartlist_t *authdir_reject_policy = NULL; @@ -1060,6 +1062,15 @@ socks_policy_permits_address(const tor_addr_t *addr) return addr_policy_permits_tor_addr(addr, 1, socks_policy); } +/** Return 1 if <b>addr</b> is permitted to connect to our metrics port, + * based on <b>socks_policy</b>. Else return 0. + */ +int +metrics_policy_permits_address(const tor_addr_t *addr) +{ + return addr_policy_permits_tor_addr(addr, 1, metrics_policy); +} + /** Return true iff the address <b>addr</b> is in a country listed in the * case-insensitive list of country codes <b>cc_list</b>. */ static int @@ -1218,6 +1229,22 @@ load_policy_from_option(config_line_t *config, const char *option_name, return 0; } +/** Helper: Parse the MetricsPortPolicy option into the metrics_policy and set + * the reject all by default. + * + * Return 0 on success else -1. */ +static int +parse_metrics_port_policy(const or_options_t *options) +{ + if (load_policy_from_option(options->MetricsPortPolicy, "MetricsPortPolicy", + &metrics_policy, -1) < 0) { + return -1; + } + /* It is a reject all by default. */ + append_exit_policy_string(&metrics_policy, "reject *:*"); + return 0; +} + /** Set all policies based on <b>options</b>, which should have been validated * first by validate_addr_policies. */ int @@ -1239,6 +1266,9 @@ policies_parse_from_options(const or_options_t *options) if (load_policy_from_option(options->AuthDirBadExit, "AuthDirBadExit", &authdir_badexit_policy, ADDR_POLICY_REJECT) < 0) ret = -1; + if (parse_metrics_port_policy(options) < 0) { + ret = -1; + } if (parse_reachable_addresses() < 0) ret = -1; return ret; @@ -3074,6 +3104,8 @@ policies_free_all(void) socks_policy = NULL; addr_policy_list_free(dir_policy); dir_policy = NULL; + addr_policy_list_free(metrics_policy); + metrics_policy = NULL; addr_policy_list_free(authdir_reject_policy); authdir_reject_policy = NULL; addr_policy_list_free(authdir_invalid_policy); diff --git a/src/core/or/policies.h b/src/core/or/policies.h index c8502a5516..17bd7c869f 100644 --- a/src/core/or/policies.h +++ b/src/core/or/policies.h @@ -102,6 +102,7 @@ void reachable_addr_choose_from_dir_server(const dir_server_t *ds, int dir_policy_permits_address(const tor_addr_t *addr); int socks_policy_permits_address(const tor_addr_t *addr); +int metrics_policy_permits_address(const tor_addr_t *addr); int authdir_policy_permits_address(const tor_addr_t *addr, uint16_t port); int authdir_policy_valid_address(const tor_addr_t *addr, uint16_t port); int authdir_policy_badexit_address(const tor_addr_t *addr, uint16_t port); diff --git a/src/feature/control/control_fmt.c b/src/feature/control/control_fmt.c index d76e6ad8dd..014427c5b5 100644 --- a/src/feature/control/control_fmt.c +++ b/src/feature/control/control_fmt.c @@ -206,6 +206,8 @@ entry_connection_describe_status_for_controller(const entry_connection_t *conn) case CONN_TYPE_AP_DNS_LISTENER: client_protocol = "DNS"; break; case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: client_protocol = "HTTPCONNECT"; break; + case CONN_TYPE_METRICS_LISTENER: + client_protocol = "METRICS"; break; default: client_protocol = "UNKNOWN"; } smartlist_add_asprintf(descparts, "CLIENT_PROTOCOL=%s", diff --git a/src/feature/control/control_getinfo.c b/src/feature/control/control_getinfo.c index 461b8eeb94..cfac59d499 100644 --- a/src/feature/control/control_getinfo.c +++ b/src/feature/control/control_getinfo.c @@ -287,6 +287,8 @@ getinfo_helper_listeners(control_connection_t *control_conn, type = CONN_TYPE_AP_DNS_LISTENER; else if (!strcmp(question, "net/listeners/control")) type = CONN_TYPE_CONTROL_LISTENER; + else if (!strcmp(question, "net/listeners/metrics")) + type = CONN_TYPE_METRICS_LISTENER; else return 0; /* unknown key */ 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) */ |