summaryrefslogtreecommitdiff
path: root/src/feature/metrics/metrics.c
blob: 9a72fe714509dd22262157e78852a89ee6194cd8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/* Copyright (c) 2007-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * @file metrics.c
 * @brief Metrics subsystem.
 **/

#include "orconfig.h"

#include "core/or/or.h"

#include "lib/encoding/confline.h"
#include "lib/log/util_bug.h"
#include "lib/malloc/malloc.h"
#include "lib/metrics/metrics_store.h"
#include "lib/net/resolve.h"
#include "lib/string/printf.h"
#include "lib/net/nettypes.h"
#include "lib/net/address.h"

#include "core/mainloop/connection.h"
#include "core/or/connection_or.h"
#include "core/or/connection_st.h"
#include "core/or/policies.h"
#include "core/or/port_cfg_st.h"
#include "core/proto/proto_http.h"

#include "feature/dircommon/directory.h"
#include "feature/metrics/metrics.h"

#include "app/config/config.h"
#include "app/main/subsysmgr.h"

/** Metrics format driver set by the MetricsPort option. */
static metrics_format_t the_format = METRICS_FORMAT_PROMETHEUS;

/** Return true iff the given peer address is allowed by our MetricsPortPolicy
 * option that is is in that list. */
static bool
metrics_request_allowed(const tor_addr_t *peer_addr)
{
  tor_assert(peer_addr);

  return metrics_policy_permits_address(peer_addr);
}

/** Helper: For a metrics port connection, write the HTTP response header
 * using the data length passed. */
static void
write_metrics_http_response(const size_t data_len, connection_t *conn)
{
  char date[RFC1123_TIME_LEN+1];
  buf_t *buf = buf_new_with_capacity(128 + data_len);

  format_rfc1123_time(date, approx_time());
  buf_add_printf(buf, "HTTP/1.0 200 OK\r\nDate: %s\r\n", date);
  buf_add_printf(buf, "Content-Type: text/plain; charset=utf-8\r\n");
  buf_add_printf(buf, "Content-Length: %" TOR_PRIuSZ "\r\n", data_len);
  buf_add_string(buf, "\r\n");

  connection_buf_add_buf(conn, buf);
  buf_free(buf);
}

/** Return newly allocated buffer containing the output of all subsystems
 * having metrics.
 *
 * This is used to output the content on the MetricsPort. */
buf_t *
metrics_get_output(const metrics_format_t fmt)
{
  buf_t *data = buf_new();

  /* Go over all subsystems that exposes a metrics store. */
  for (unsigned i = 0; i < n_tor_subsystems; ++i) {
    const smartlist_t *stores;
    const subsys_fns_t *sys = tor_subsystems[i];

    /* Skip unsupported subsystems. */
    if (!sys->supported) {
      continue;
    }

    if (sys->get_metrics && (stores = sys->get_metrics())) {
      SMARTLIST_FOREACH_BEGIN(stores, const metrics_store_t *, store) {
        metrics_store_get_output(fmt, store, data);
      } SMARTLIST_FOREACH_END(store);
    }
  }

  return data;
}

/** Process what is in the inbuf of this connection of type metrics.
 *
 * Return 0 on success else -1 on error for which the connection is marked for
 * close. */
int
metrics_connection_process_inbuf(connection_t *conn)
{
  int ret = -1;
  char *headers = NULL, *command = NULL, *url = NULL;
  const char *errmsg = NULL;

  tor_assert(conn);
  tor_assert(conn->type == CONN_TYPE_METRICS);

  if (!metrics_request_allowed(&conn->addr)) {
    /* Close connection. Don't bother returning anything if you are not
     * allowed by being on the policy list. */
    errmsg = NULL;
    goto err;
  }

  const int http_status =
    connection_fetch_from_buf_http(conn, &headers, 1024, NULL, NULL, 1024, 0);
  if (http_status < 0) {
    errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
    goto err;
  } else if (http_status == 0) {
    /* no HTTP request yet. */
    ret = 0;
    goto done;
  }

  const int cmd_status = parse_http_command(headers, &command, &url);
  if (cmd_status < 0) {
    errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n";
    goto err;
  } else if (strcmpstart(command, "GET")) {
    errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n";
    goto err;
  }
  tor_assert(url);

  /* Where we expect the query to come for. */
#define EXPECTED_URL_PATH "/metrics"
#define EXPECTED_URL_PATH_LEN (sizeof(EXPECTED_URL_PATH) - 1) /* No NUL */

  if (!strcmpstart(url, EXPECTED_URL_PATH) &&
      strlen(url) == EXPECTED_URL_PATH_LEN) {
    buf_t *data = metrics_get_output(the_format);

    write_metrics_http_response(buf_datalen(data), conn);
    connection_buf_add_buf(conn, data);
    buf_free(data);
  } else {
    errmsg = "HTTP/1.0 404 Not Found\r\n\r\n";
    goto err;
  }

  ret = 0;
  goto done;

 err:
  if (errmsg) {
    log_info(LD_EDGE, "HTTP metrics error: saying %s", escaped(errmsg));
    connection_buf_add(errmsg, strlen(errmsg), conn);
  }
  connection_mark_and_flush(conn);

 done:
  tor_free(headers);
  tor_free(command);
  tor_free(url);

  return ret;
}

/** Parse metrics ports from options. On success, add the port to the ports
 * list and return 0. On failure, set err_msg_out to a newly allocated string
 * describing the problem and return -1. */
int
metrics_parse_ports(or_options_t *options, smartlist_t *ports,
                    char **err_msg_out)
{
  int num_elems, ok = 0, ret = -1;
  const char *addrport_str = NULL, *fmt_str = NULL;
  smartlist_t *elems = NULL;
  port_cfg_t *cfg = NULL;

  tor_assert(options);
  tor_assert(ports);

  /* No metrics port to configure, just move on . */
  if (!options->MetricsPort_lines) {
    return 0;
  }

  elems = smartlist_new();

  /* Split between the protocol and the address/port. */
  num_elems = smartlist_split_string(elems,
                                     options->MetricsPort_lines->value, " ",
                                     SPLIT_SKIP_SPACE | SPLIT_IGNORE_BLANK, 2);
  if (num_elems < 1) {
    *err_msg_out = tor_strdup("MetricsPort is missing port.");
    goto end;
  }

  addrport_str = smartlist_get(elems, 0);
  if (num_elems >= 2) {
    /* Parse the format if any. */
    fmt_str = smartlist_get(elems, 1);
    if (!strcasecmp(fmt_str, "prometheus")) {
      the_format = METRICS_FORMAT_PROMETHEUS;
    } else {
      tor_asprintf(err_msg_out, "MetricsPort unknown format: %s", fmt_str);
      goto end;
    }
  }

  /* Port configuration with default address. */
  cfg = port_cfg_new(0);
  cfg->type = CONN_TYPE_METRICS_LISTENER;

  /* Parse the port first. Then an address if any can be found. */
  cfg->port = (int) tor_parse_long(addrport_str, 10, 0, 65535, &ok, NULL);
  if (ok) {
    tor_addr_parse(&cfg->addr, "127.0.0.1");
  } else {
    /* We probably have a host:port situation */
    if (tor_addr_port_lookup(addrport_str, &cfg->addr,
                             (uint16_t *) &cfg->port) < 0) {
      *err_msg_out = tor_strdup("MetricsPort address/port failed to parse or "
                                "resolve.");
      goto end;
    }
  }
  /* Add it to the ports list. */
  smartlist_add(ports, cfg);

  /* It is set. MetricsPort doesn't support the NoListen options or such that
   * would prevent from being a real listener port. */
  options->MetricsPort_set = 1;

  /* Success. */
  ret = 0;

 end:
  if (ret != 0) {
    port_cfg_free(cfg);
  }
  SMARTLIST_FOREACH(elems, char *, e, tor_free(e));
  smartlist_free(elems);
  return ret;
}

/** Called when conn has gotten its socket closed. */
int
metrics_connection_reached_eof(connection_t *conn)
{
  tor_assert(conn);

  log_info(LD_EDGE, "Metrics connection reached EOF. Closing.");
  connection_mark_for_close(conn);
  return 0;
}

/** Called when conn has no more bytes left on its outbuf. Return 0 indicating
 * success. */
int
metrics_connection_finished_flushing(connection_t *conn)
{
  tor_assert(conn);
  return 0;
}

/** Initialize the subsystem. */
void
metrics_init(void)
{
}

/** Cleanup and free any global memory of this subsystem. */
void
metrics_cleanup(void)
{
}