Coverage Report

Created: 2025-11-11 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}