LCOV - code coverage report
Current view: top level - feature/metrics - metrics.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 99 121 81.8 %
Date: 2021-11-24 03:28:48 Functions: 7 9 77.8 %

          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 : }

Generated by: LCOV version 1.14