Merge branch 'maint-0.4.8'
[tor.git] / src / feature / metrics / metrics.c
blob5c10d553d3b03ea83968725d4a06894de58bded4
1 /* Copyright (c) 2007-2021, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
4 /**
5 * @file metrics.c
6 * @brief Metrics subsystem.
7 **/
9 #include "orconfig.h"
11 #include "core/or/or.h"
13 #include "lib/encoding/confline.h"
14 #include "lib/log/util_bug.h"
15 #include "lib/malloc/malloc.h"
16 #include "lib/metrics/metrics_store.h"
17 #include "lib/net/resolve.h"
18 #include "lib/string/printf.h"
19 #include "lib/net/nettypes.h"
20 #include "lib/net/address.h"
22 #include "core/mainloop/connection.h"
23 #include "core/or/connection_or.h"
24 #include "core/or/connection_st.h"
25 #include "core/or/policies.h"
26 #include "core/or/port_cfg_st.h"
27 #include "core/proto/proto_http.h"
29 #include "feature/dircommon/directory.h"
30 #include "feature/metrics/metrics.h"
32 #include "app/config/config.h"
33 #include "app/main/subsysmgr.h"
35 /** Metrics format driver set by the MetricsPort option. */
36 static metrics_format_t the_format = METRICS_FORMAT_PROMETHEUS;
38 /** Return true iff the given peer address is allowed by our MetricsPortPolicy
39 * option that is is in that list. */
40 static bool
41 metrics_request_allowed(const tor_addr_t *peer_addr)
43 tor_assert(peer_addr);
45 return metrics_policy_permits_address(peer_addr);
48 /** Helper: For a metrics port connection, write the HTTP response header
49 * using the data length passed. */
50 static void
51 write_metrics_http_response(const size_t data_len, connection_t *conn)
53 char date[RFC1123_TIME_LEN+1];
54 buf_t *buf = buf_new_with_capacity(128 + data_len);
56 format_rfc1123_time(date, approx_time());
57 buf_add_printf(buf, "HTTP/1.0 200 OK\r\nDate: %s\r\n", date);
58 buf_add_printf(buf, "Content-Type: text/plain; charset=utf-8\r\n");
59 buf_add_printf(buf, "Content-Length: %" TOR_PRIuSZ "\r\n", data_len);
60 buf_add_string(buf, "\r\n");
62 connection_buf_add_buf(conn, buf);
63 buf_free(buf);
66 /** Return newly allocated buffer containing the output of all subsystems
67 * having metrics.
69 * This is used to output the content on the MetricsPort. */
70 buf_t *
71 metrics_get_output(const metrics_format_t fmt)
73 buf_t *data = buf_new();
75 /* Go over all subsystems that exposes a metrics store. */
76 for (unsigned i = 0; i < n_tor_subsystems; ++i) {
77 const smartlist_t *stores;
78 const subsys_fns_t *sys = tor_subsystems[i];
80 /* Skip unsupported subsystems. */
81 if (!sys->supported) {
82 continue;
85 if (sys->get_metrics && (stores = sys->get_metrics())) {
86 SMARTLIST_FOREACH_BEGIN(stores, const metrics_store_t *, store) {
87 metrics_store_get_output(fmt, store, data);
88 } SMARTLIST_FOREACH_END(store);
92 return data;
95 /** Process what is in the inbuf of this connection of type metrics.
97 * Return 0 on success else -1 on error for which the connection is marked for
98 * close. */
99 int
100 metrics_connection_process_inbuf(connection_t *conn)
102 int ret = -1;
103 char *headers = NULL, *command = NULL, *url = NULL;
104 const char *errmsg = NULL;
106 tor_assert(conn);
107 tor_assert(conn->type == CONN_TYPE_METRICS);
109 if (!metrics_request_allowed(&conn->addr)) {
110 /* Close connection. Don't bother returning anything if you are not
111 * allowed by being on the policy list. */
112 errmsg = NULL;
113 goto err;
116 const int http_status =
117 connection_fetch_from_buf_http(conn, &headers, 1024, NULL, NULL, 1024, 0);
118 if (http_status < 0) {
119 errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
120 goto err;
121 } else if (http_status == 0) {
122 /* no HTTP request yet. */
123 ret = 0;
124 goto done;
127 const int cmd_status = parse_http_command(headers, &command, &url);
128 if (cmd_status < 0) {
129 errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
130 goto err;
131 } else if (strcmpstart(command, "GET")) {
132 errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
133 goto err;
135 tor_assert(url);
137 /* Where we expect the query to come for. */
138 #define EXPECTED_URL_PATH "/metrics"
139 #define EXPECTED_URL_PATH_LEN (sizeof(EXPECTED_URL_PATH) - 1) /* No NUL */
141 if (!strcmpstart(url, EXPECTED_URL_PATH) &&
142 strlen(url) == EXPECTED_URL_PATH_LEN) {
143 buf_t *data = metrics_get_output(the_format);
145 write_metrics_http_response(buf_datalen(data), conn);
146 connection_buf_add_buf(conn, data);
147 buf_free(data);
148 } else {
149 errmsg = "HTTP/1.0 404 Not Found\r\n\r\n";
150 goto err;
153 ret = 0;
154 goto done;
156 err:
157 if (errmsg) {
158 log_info(LD_EDGE, "HTTP metrics error: saying %s", escaped(errmsg));
159 connection_buf_add(errmsg, strlen(errmsg), conn);
161 connection_mark_and_flush(conn);
163 done:
164 tor_free(headers);
165 tor_free(command);
166 tor_free(url);
168 return ret;
171 /** Parse metrics ports from options. On success, add the port to the ports
172 * list and return 0. On failure, set err_msg_out to a newly allocated string
173 * describing the problem and return -1. */
175 metrics_parse_ports(or_options_t *options, smartlist_t *ports,
176 char **err_msg_out)
178 int num_elems, ok = 0, ret = -1;
179 const char *addrport_str = NULL, *fmt_str = NULL;
180 smartlist_t *elems = NULL;
181 port_cfg_t *cfg = NULL;
183 tor_assert(options);
184 tor_assert(ports);
186 /* No metrics port to configure, just move on . */
187 if (!options->MetricsPort_lines) {
188 return 0;
191 elems = smartlist_new();
193 /* Split between the protocol and the address/port. */
194 num_elems = smartlist_split_string(elems,
195 options->MetricsPort_lines->value, " ",
196 SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 2);
197 if (num_elems < 1) {
198 *err_msg_out = tor_strdup("MetricsPort is missing port.");
199 goto end;
202 addrport_str = smartlist_get(elems, 0);
203 if (num_elems >= 2) {
204 /* Parse the format if any. */
205 fmt_str = smartlist_get(elems, 1);
206 if (!strcasecmp(fmt_str, "prometheus")) {
207 the_format = METRICS_FORMAT_PROMETHEUS;
208 } else {
209 tor_asprintf(err_msg_out, "MetricsPort unknown format: %s", fmt_str);
210 goto end;
214 /* Port configuration with default address. */
215 cfg = port_cfg_new(0);
216 cfg->type = CONN_TYPE_METRICS_LISTENER;
218 /* Parse the port first. Then an address if any can be found. */
219 cfg->port = (int) tor_parse_long(addrport_str, 10, 0, 65535, &ok, NULL);
220 if (ok) {
221 tor_addr_parse(&cfg->addr, "127.0.0.1");
222 } else {
223 /* We probably have a host:port situation */
224 if (tor_addr_port_lookup(addrport_str, &cfg->addr,
225 (uint16_t *) &cfg->port) < 0) {
226 *err_msg_out = tor_strdup("MetricsPort address/port failed to parse or "
227 "resolve.");
228 goto end;
231 /* Add it to the ports list. */
232 smartlist_add(ports, cfg);
234 /* It is set. MetricsPort doesn't support the NoListen options or such that
235 * would prevent from being a real listener port. */
236 options->MetricsPort_set = 1;
238 /* Success. */
239 ret = 0;
241 end:
242 if (ret != 0) {
243 port_cfg_free(cfg);
245 SMARTLIST_FOREACH(elems, char *, e, tor_free(e));
246 smartlist_free(elems);
247 return ret;
250 /** Called when conn has gotten its socket closed. */
252 metrics_connection_reached_eof(connection_t *conn)
254 tor_assert(conn);
256 log_info(LD_EDGE, "Metrics connection reached EOF. Closing.");
257 connection_mark_for_close(conn);
258 return 0;
261 /** Called when conn has no more bytes left on its outbuf. Return 0 indicating
262 * success. */
264 metrics_connection_finished_flushing(connection_t *conn)
266 tor_assert(conn);
267 return 0;
270 /** Initialize the subsystem. */
271 void
272 metrics_init(void)
276 /** Cleanup and free any global memory of this subsystem. */
277 void
278 metrics_cleanup(void)