tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

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 }