metrics.c (7557B)
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 { 43 tor_assert(peer_addr); 44 45 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 write_metrics_http_response(const size_t data_len, connection_t *conn) 52 { 53 char date[RFC1123_TIME_LEN+1]; 54 buf_t *buf = buf_new_with_capacity(128 + data_len); 55 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"); 61 62 connection_buf_add_buf(conn, buf); 63 buf_free(buf); 64 } 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 { 73 buf_t *data = buf_new(); 74 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]; 79 80 /* Skip unsupported subsystems. */ 81 if (!sys->supported) { 82 continue; 83 } 84 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); 89 } 90 } 91 92 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 metrics_connection_process_inbuf(connection_t *conn) 101 { 102 int ret = -1; 103 char *headers = NULL, *command = NULL, *url = NULL; 104 const char *errmsg = NULL; 105 106 tor_assert(conn); 107 tor_assert(conn->type == CONN_TYPE_METRICS); 108 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; 114 } 115 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; 125 } 126 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; 134 } 135 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 if (!strcmpstart(url, EXPECTED_URL_PATH) && 142 strlen(url) == EXPECTED_URL_PATH_LEN) { 143 buf_t *data = metrics_get_output(the_format); 144 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; 151 } 152 153 ret = 0; 154 goto done; 155 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); 160 } 161 connection_mark_and_flush(conn); 162 163 done: 164 tor_free(headers); 165 tor_free(command); 166 tor_free(url); 167 168 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 metrics_parse_ports(or_options_t *options, smartlist_t *ports, 176 char **err_msg_out) 177 { 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; 182 183 tor_assert(options); 184 tor_assert(ports); 185 186 /* No metrics port to configure, just move on . */ 187 if (!options->MetricsPort_lines) { 188 return 0; 189 } 190 191 elems = smartlist_new(); 192 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; 200 } 201 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; 211 } 212 } 213 214 /* Port configuration with default address. */ 215 cfg = port_cfg_new(0); 216 cfg->type = CONN_TYPE_METRICS_LISTENER; 217 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; 229 } 230 } 231 /* Add it to the ports list. */ 232 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 options->MetricsPort_set = 1; 237 238 /* Success. */ 239 ret = 0; 240 241 end: 242 if (ret != 0) { 243 port_cfg_free(cfg); 244 } 245 SMARTLIST_FOREACH(elems, char *, e, tor_free(e)); 246 smartlist_free(elems); 247 return ret; 248 } 249 250 /** Called when conn has gotten its socket closed. */ 251 int 252 metrics_connection_reached_eof(connection_t *conn) 253 { 254 tor_assert(conn); 255 256 log_info(LD_EDGE, "Metrics connection reached EOF. Closing."); 257 connection_mark_for_close(conn); 258 return 0; 259 } 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 { 266 tor_assert(conn); 267 return 0; 268 } 269 270 /** Initialize the subsystem. */ 271 void 272 metrics_init(void) 273 { 274 } 275 276 /** Cleanup and free any global memory of this subsystem. */ 277 void 278 metrics_cleanup(void) 279 { 280 }