#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"
static metrics_format_t the_format = METRICS_FORMAT_PROMETHEUS;
static bool
metrics_request_allowed(const tor_addr_t *peer_addr)
{
tor_assert(peer_addr);
return metrics_policy_permits_address(peer_addr);
}
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);
}
buf_t *
metrics_get_output(const metrics_format_t fmt)
{
buf_t *data = buf_new();
for (unsigned i = 0; i < n_tor_subsystems; ++i) {
const smartlist_t *stores;
const subsys_fns_t *sys = tor_subsystems[i];
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;
}
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)) {
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) {
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);
#define EXPECTED_URL_PATH "/metrics"
#define EXPECTED_URL_PATH_LEN (sizeof(EXPECTED_URL_PATH) - 1)
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;
}
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);
if (!options->MetricsPort_lines) {
return 0;
}
elems = smartlist_new();
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) {
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;
}
}
cfg = port_cfg_new(0);
cfg->type = CONN_TYPE_METRICS_LISTENER;
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 {
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;
}
}
smartlist_add(ports, cfg);
options->MetricsPort_set = 1;
ret = 0;
end:
if (ret != 0) {
port_cfg_free(cfg);
}
SMARTLIST_FOREACH(elems, char *, e, tor_free(e));
smartlist_free(elems);
return ret;
}
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;
}
void
metrics_init(void)
{
}
void
metrics_cleanup(void)
{
}