/src/tor/src/feature/metrics/metrics.c
Line | Count | Source |
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 | | metrics_request_allowed(const tor_addr_t *peer_addr) |
42 | 0 | { |
43 | 0 | tor_assert(peer_addr); |
44 | |
|
45 | 0 | return metrics_policy_permits_address(peer_addr); |
46 | 0 | } |
47 | | |
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) |
52 | 0 | { |
53 | 0 | char date[RFC1123_TIME_LEN+1]; |
54 | 0 | buf_t *buf = buf_new_with_capacity(128 + data_len); |
55 | |
|
56 | 0 | format_rfc1123_time(date, approx_time()); |
57 | 0 | buf_add_printf(buf, "HTTP/1.0 200 OK\r\nDate: %s\r\n", date); |
58 | 0 | buf_add_printf(buf, "Content-Type: text/plain; charset=utf-8\r\n"); |
59 | 0 | buf_add_printf(buf, "Content-Length: %" TOR_PRIuSZ "\r\n", data_len); |
60 | 0 | buf_add_string(buf, "\r\n"); |
61 | |
|
62 | 0 | connection_buf_add_buf(conn, buf); |
63 | 0 | buf_free(buf); |
64 | 0 | } |
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 | | metrics_get_output(const metrics_format_t fmt) |
72 | 0 | { |
73 | 0 | buf_t *data = buf_new(); |
74 | | |
75 | | /* Go over all subsystems that exposes a metrics store. */ |
76 | 0 | for (unsigned i = 0; i < n_tor_subsystems; ++i) { |
77 | 0 | const smartlist_t *stores; |
78 | 0 | const subsys_fns_t *sys = tor_subsystems[i]; |
79 | | |
80 | | /* Skip unsupported subsystems. */ |
81 | 0 | if (!sys->supported) { |
82 | 0 | continue; |
83 | 0 | } |
84 | | |
85 | 0 | if (sys->get_metrics && (stores = sys->get_metrics())) { |
86 | 0 | SMARTLIST_FOREACH_BEGIN(stores, const metrics_store_t *, store) { |
87 | 0 | metrics_store_get_output(fmt, store, data); |
88 | 0 | } SMARTLIST_FOREACH_END(store); |
89 | 0 | } |
90 | 0 | } |
91 | |
|
92 | 0 | return data; |
93 | 0 | } |
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 | | metrics_connection_process_inbuf(connection_t *conn) |
101 | 0 | { |
102 | 0 | int ret = -1; |
103 | 0 | char *headers = NULL, *command = NULL, *url = NULL; |
104 | 0 | const char *errmsg = NULL; |
105 | |
|
106 | 0 | tor_assert(conn); |
107 | 0 | tor_assert(conn->type == CONN_TYPE_METRICS); |
108 | |
|
109 | 0 | 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 | 0 | errmsg = NULL; |
113 | 0 | goto err; |
114 | 0 | } |
115 | | |
116 | 0 | const int http_status = |
117 | 0 | connection_fetch_from_buf_http(conn, &headers, 1024, NULL, NULL, 1024, 0); |
118 | 0 | if (http_status < 0) { |
119 | 0 | errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; |
120 | 0 | goto err; |
121 | 0 | } else if (http_status == 0) { |
122 | | /* no HTTP request yet. */ |
123 | 0 | ret = 0; |
124 | 0 | goto done; |
125 | 0 | } |
126 | | |
127 | 0 | const int cmd_status = parse_http_command(headers, &command, &url); |
128 | 0 | if (cmd_status < 0) { |
129 | 0 | errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; |
130 | 0 | goto err; |
131 | 0 | } else if (strcmpstart(command, "GET")) { |
132 | 0 | errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; |
133 | 0 | goto err; |
134 | 0 | } |
135 | 0 | tor_assert(url); |
136 | | |
137 | | /* Where we expect the query to come for. */ |
138 | 0 | #define EXPECTED_URL_PATH "/metrics" |
139 | 0 | #define EXPECTED_URL_PATH_LEN (sizeof(EXPECTED_URL_PATH) - 1) /* No NUL */ |
140 | |
|
141 | 0 | if (!strcmpstart(url, EXPECTED_URL_PATH) && |
142 | 0 | strlen(url) == EXPECTED_URL_PATH_LEN) { |
143 | 0 | buf_t *data = metrics_get_output(the_format); |
144 | |
|
145 | 0 | write_metrics_http_response(buf_datalen(data), conn); |
146 | 0 | connection_buf_add_buf(conn, data); |
147 | 0 | buf_free(data); |
148 | 0 | } else { |
149 | 0 | errmsg = "HTTP/1.0 404 Not Found\r\n\r\n"; |
150 | 0 | goto err; |
151 | 0 | } |
152 | | |
153 | 0 | ret = 0; |
154 | 0 | goto done; |
155 | | |
156 | 0 | err: |
157 | 0 | if (errmsg) { |
158 | 0 | log_info(LD_EDGE, "HTTP metrics error: saying %s", escaped(errmsg)); |
159 | 0 | connection_buf_add(errmsg, strlen(errmsg), conn); |
160 | 0 | } |
161 | 0 | connection_mark_and_flush(conn); |
162 | |
|
163 | 0 | done: |
164 | 0 | tor_free(headers); |
165 | 0 | tor_free(command); |
166 | 0 | tor_free(url); |
167 | |
|
168 | 0 | return ret; |
169 | 0 | } |
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 | | metrics_parse_ports(or_options_t *options, smartlist_t *ports, |
176 | | char **err_msg_out) |
177 | 0 | { |
178 | 0 | int num_elems, ok = 0, ret = -1; |
179 | 0 | const char *addrport_str = NULL, *fmt_str = NULL; |
180 | 0 | smartlist_t *elems = NULL; |
181 | 0 | port_cfg_t *cfg = NULL; |
182 | |
|
183 | 0 | tor_assert(options); |
184 | 0 | tor_assert(ports); |
185 | | |
186 | | /* No metrics port to configure, just move on . */ |
187 | 0 | if (!options->MetricsPort_lines) { |
188 | 0 | return 0; |
189 | 0 | } |
190 | | |
191 | 0 | elems = smartlist_new(); |
192 | | |
193 | | /* Split between the protocol and the address/port. */ |
194 | 0 | num_elems = smartlist_split_string(elems, |
195 | 0 | options->MetricsPort_lines->value, " ", |
196 | 0 | SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 2); |
197 | 0 | if (num_elems < 1) { |
198 | 0 | *err_msg_out = tor_strdup("MetricsPort is missing port."); |
199 | 0 | goto end; |
200 | 0 | } |
201 | | |
202 | 0 | addrport_str = smartlist_get(elems, 0); |
203 | 0 | 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 | 0 | } else { |
209 | 0 | tor_asprintf(err_msg_out, "MetricsPort unknown format: %s", fmt_str); |
210 | 0 | goto end; |
211 | 0 | } |
212 | 0 | } |
213 | | |
214 | | /* Port configuration with default address. */ |
215 | 0 | cfg = port_cfg_new(0); |
216 | 0 | cfg->type = CONN_TYPE_METRICS_LISTENER; |
217 | | |
218 | | /* Parse the port first. Then an address if any can be found. */ |
219 | 0 | cfg->port = (int) tor_parse_long(addrport_str, 10, 0, 65535, &ok, NULL); |
220 | 0 | if (ok) { |
221 | 0 | tor_addr_parse(&cfg->addr, "127.0.0.1"); |
222 | 0 | } 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 | 0 | "resolve."); |
228 | 0 | goto end; |
229 | 0 | } |
230 | 0 | } |
231 | | /* Add it to the ports list. */ |
232 | 0 | 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 | 0 | options->MetricsPort_set = 1; |
237 | | |
238 | | /* Success. */ |
239 | 0 | ret = 0; |
240 | |
|
241 | 0 | end: |
242 | 0 | if (ret != 0) { |
243 | 0 | port_cfg_free(cfg); |
244 | 0 | } |
245 | 0 | SMARTLIST_FOREACH(elems, char *, e, tor_free(e)); |
246 | 0 | smartlist_free(elems); |
247 | 0 | return ret; |
248 | 0 | } |
249 | | |
250 | | /** Called when conn has gotten its socket closed. */ |
251 | | int |
252 | | metrics_connection_reached_eof(connection_t *conn) |
253 | 0 | { |
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 | 0 | } |
260 | | |
261 | | /** Called when conn has no more bytes left on its outbuf. Return 0 indicating |
262 | | * success. */ |
263 | | int |
264 | | metrics_connection_finished_flushing(connection_t *conn) |
265 | 0 | { |
266 | 0 | tor_assert(conn); |
267 | 0 | return 0; |
268 | 0 | } |
269 | | |
270 | | /** Initialize the subsystem. */ |
271 | | void |
272 | | metrics_init(void) |
273 | 0 | { |
274 | 0 | } |
275 | | |
276 | | /** Cleanup and free any global memory of this subsystem. */ |
277 | | void |
278 | | metrics_cleanup(void) |
279 | 0 | { |
280 | 0 | } |