Line data Source code
1 : /* Copyright (c) 2007-2021, The Tor Project, Inc. */
2 : /* See LICENSE for licensing information */
3 :
4 : /**
5 : * @file metrics.c
6 : * @brief Metrics subsystem.
7 : **/
8 :
9 : #include "orconfig.h"
10 :
11 : #include "core/or/or.h"
12 :
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"
21 :
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"
28 :
29 : #include "feature/dircommon/directory.h"
30 : #include "feature/metrics/metrics.h"
31 :
32 : #include "app/config/config.h"
33 : #include "app/main/subsysmgr.h"
34 :
35 : /** Metrics format driver set by the MetricsPort option. */
36 : static metrics_format_t the_format = METRICS_FORMAT_PROMETHEUS;
37 :
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 6 : metrics_request_allowed(const tor_addr_t *peer_addr)
42 : {
43 6 : tor_assert(peer_addr);
44 :
45 6 : return metrics_policy_permits_address(peer_addr);
46 : }
47 :
48 : /** Helper: For a metrics port connection, write the HTTP response header
49 : * using the data length passed. */
50 : static void
51 1 : write_metrics_http_response(const size_t data_len, connection_t *conn)
52 : {
53 1 : char date[RFC1123_TIME_LEN+1];
54 1 : buf_t *buf = buf_new_with_capacity(128 + data_len);
55 :
56 1 : format_rfc1123_time(date, approx_time());
57 1 : buf_add_printf(buf, "HTTP/1.0 200 OK\r\nDate: %s\r\n", date);
58 1 : buf_add_printf(buf, "Content-Type: text/plain; charset=utf-8\r\n");
59 1 : buf_add_printf(buf, "Content-Length: %" TOR_PRIuSZ "\r\n", data_len);
60 1 : buf_add_string(buf, "\r\n");
61 :
62 1 : connection_buf_add_buf(conn, buf);
63 1 : buf_free(buf);
64 1 : }
65 :
66 : /** Return newly allocated buffer containing the output of all subsystems
67 : * having metrics.
68 : *
69 : * This is used to output the content on the MetricsPort. */
70 : buf_t *
71 1 : metrics_get_output(const metrics_format_t fmt)
72 : {
73 1 : buf_t *data = buf_new();
74 :
75 : /* Go over all subsystems that exposes a metrics store. */
76 22 : for (unsigned i = 0; i < n_tor_subsystems; ++i) {
77 21 : const smartlist_t *stores;
78 21 : const subsys_fns_t *sys = tor_subsystems[i];
79 :
80 : /* Skip unsupported subsystems. */
81 21 : if (!sys->supported) {
82 3 : continue;
83 : }
84 :
85 18 : if (sys->get_metrics && (stores = sys->get_metrics())) {
86 3 : SMARTLIST_FOREACH_BEGIN(stores, const metrics_store_t *, store) {
87 1 : metrics_store_get_output(fmt, store, data);
88 1 : } SMARTLIST_FOREACH_END(store);
89 : }
90 : }
91 :
92 1 : return data;
93 : }
94 :
95 : /** Process what is in the inbuf of this connection of type metrics.
96 : *
97 : * Return 0 on success else -1 on error for which the connection is marked for
98 : * close. */
99 : int
100 6 : metrics_connection_process_inbuf(connection_t *conn)
101 : {
102 6 : int ret = -1;
103 6 : char *headers = NULL, *command = NULL, *url = NULL;
104 6 : const char *errmsg = NULL;
105 :
106 6 : tor_assert(conn);
107 6 : tor_assert(conn->type == CONN_TYPE_METRICS);
108 :
109 6 : 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 1 : errmsg = NULL;
113 1 : goto err;
114 : }
115 :
116 5 : const int http_status =
117 5 : connection_fetch_from_buf_http(conn, &headers, 1024, NULL, NULL, 1024, 0);
118 5 : if (http_status < 0) {
119 0 : errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
120 0 : goto err;
121 5 : } else if (http_status == 0) {
122 : /* no HTTP request yet. */
123 1 : ret = 0;
124 1 : goto done;
125 : }
126 :
127 4 : const int cmd_status = parse_http_command(headers, &command, &url);
128 4 : if (cmd_status < 0) {
129 1 : errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
130 1 : goto err;
131 3 : } else if (strcmpstart(command, "GET")) {
132 1 : errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
133 1 : goto err;
134 : }
135 2 : tor_assert(url);
136 :
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 */
140 :
141 2 : if (!strcmpstart(url, EXPECTED_URL_PATH) &&
142 1 : strlen(url) == EXPECTED_URL_PATH_LEN) {
143 1 : buf_t *data = metrics_get_output(the_format);
144 :
145 1 : write_metrics_http_response(buf_datalen(data), conn);
146 1 : connection_buf_add_buf(conn, data);
147 1 : buf_free(data);
148 : } else {
149 1 : errmsg = "HTTP/1.0 404 Not Found\r\n\r\n";
150 1 : goto err;
151 : }
152 :
153 1 : ret = 0;
154 1 : goto done;
155 :
156 3 : err:
157 1 : if (errmsg) {
158 3 : log_info(LD_EDGE, "HTTP metrics error: saying %s", escaped(errmsg));
159 3 : connection_buf_add(errmsg, strlen(errmsg), conn);
160 : }
161 4 : connection_mark_and_flush(conn);
162 :
163 6 : done:
164 6 : tor_free(headers);
165 6 : tor_free(command);
166 6 : tor_free(url);
167 :
168 6 : return ret;
169 : }
170 :
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. */
174 : int
175 589 : metrics_parse_ports(or_options_t *options, smartlist_t *ports,
176 : char **err_msg_out)
177 : {
178 589 : int num_elems, ok = 0, ret = -1;
179 589 : const char *addrport_str = NULL, *fmt_str = NULL;
180 589 : smartlist_t *elems = NULL;
181 589 : port_cfg_t *cfg = NULL;
182 :
183 589 : tor_assert(options);
184 589 : tor_assert(ports);
185 :
186 : /* No metrics port to configure, just move on . */
187 589 : if (!options->MetricsPort_lines) {
188 : return 0;
189 : }
190 :
191 1 : elems = smartlist_new();
192 :
193 : /* Split between the protocol and the address/port. */
194 2 : num_elems = smartlist_split_string(elems,
195 1 : options->MetricsPort_lines->value, " ",
196 : SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 2);
197 1 : if (num_elems < 1) {
198 0 : *err_msg_out = tor_strdup("MetricsPort is missing port.");
199 0 : goto end;
200 : }
201 :
202 1 : addrport_str = smartlist_get(elems, 0);
203 1 : if (num_elems >= 2) {
204 : /* Parse the format if any. */
205 0 : fmt_str = smartlist_get(elems, 1);
206 0 : if (!strcasecmp(fmt_str, "prometheus")) {
207 0 : the_format = METRICS_FORMAT_PROMETHEUS;
208 : } else {
209 0 : tor_asprintf(err_msg_out, "MetricsPort unknown format: %s", fmt_str);
210 0 : goto end;
211 : }
212 : }
213 :
214 : /* Port configuration with default address. */
215 1 : cfg = port_cfg_new(0);
216 1 : cfg->type = CONN_TYPE_METRICS_LISTENER;
217 :
218 : /* Parse the port first. Then an address if any can be found. */
219 1 : cfg->port = (int) tor_parse_long(addrport_str, 10, 0, 65535, &ok, NULL);
220 1 : if (ok) {
221 1 : tor_addr_parse(&cfg->addr, "127.0.0.1");
222 : } else {
223 : /* We probably have a host:port situation */
224 0 : if (tor_addr_port_lookup(addrport_str, &cfg->addr,
225 0 : (uint16_t *) &cfg->port) < 0) {
226 0 : *err_msg_out = tor_strdup("MetricsPort address/port failed to parse or "
227 : "resolve.");
228 0 : goto end;
229 : }
230 : }
231 : /* Add it to the ports list. */
232 1 : smartlist_add(ports, cfg);
233 :
234 : /* It is set. MetricsPort doesn't support the NoListen options or such that
235 : * would prevent from being a real listener port. */
236 1 : options->MetricsPort_set = 1;
237 :
238 : /* Success. */
239 1 : ret = 0;
240 :
241 : end:
242 1 : if (ret != 0) {
243 0 : port_cfg_free(cfg);
244 : }
245 2 : SMARTLIST_FOREACH(elems, char *, e, tor_free(e));
246 1 : smartlist_free(elems);
247 1 : return ret;
248 : }
249 :
250 : /** Called when conn has gotten its socket closed. */
251 : int
252 0 : metrics_connection_reached_eof(connection_t *conn)
253 : {
254 0 : tor_assert(conn);
255 :
256 0 : log_info(LD_EDGE, "Metrics connection reached EOF. Closing.");
257 0 : connection_mark_for_close(conn);
258 0 : return 0;
259 : }
260 :
261 : /** Called when conn has no more bytes left on its outbuf. Return 0 indicating
262 : * success. */
263 : int
264 0 : metrics_connection_finished_flushing(connection_t *conn)
265 : {
266 0 : tor_assert(conn);
267 0 : return 0;
268 : }
269 :
270 : /** Initialize the subsystem. */
271 : void
272 244 : metrics_init(void)
273 : {
274 244 : }
275 :
276 : /** Cleanup and free any global memory of this subsystem. */
277 : void
278 235 : metrics_cleanup(void)
279 : {
280 235 : }
|