1 /* Copyright (c) 2020-2021, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
3 
4 /**
5  * \file test_metrics.c
6  * \brief Test lib/metrics and feature/metrics functionalities
7  */
8 
9 #define CONFIG_PRIVATE
10 #define CONNECTION_PRIVATE
11 #define MAINLOOP_PRIVATE
12 #define METRICS_STORE_ENTRY_PRIVATE
13 
14 #include "test/test.h"
15 #include "test/test_helpers.h"
16 #include "test/log_test_helpers.h"
17 
18 #include "app/config/config.h"
19 
20 #include "core/mainloop/connection.h"
21 #include "core/mainloop/mainloop.h"
22 #include "core/or/connection_st.h"
23 #include "core/or/policies.h"
24 #include "core/or/port_cfg_st.h"
25 
26 #include "feature/metrics/metrics.h"
27 
28 #include "lib/encoding/confline.h"
29 #include "lib/metrics/metrics_store.h"
30 
31 #define TEST_METRICS_ENTRY_NAME    "entryA"
32 #define TEST_METRICS_ENTRY_HELP    "Description of entryA"
33 #define TEST_METRICS_ENTRY_LABEL_1 "label=farfadet"
34 #define TEST_METRICS_ENTRY_LABEL_2 "label=ponki"
35 
36 static void
set_metrics_port(or_options_t * options)37 set_metrics_port(or_options_t *options)
38 {
39   const char *port = "MetricsPort 9035"; /* Default to 127.0.0.1 */
40   const char *policy = "MetricsPortPolicy accept 1.2.3.4";
41 
42   config_get_lines(port, &options->MetricsPort_lines, 0);
43   config_get_lines(policy, &options->MetricsPortPolicy, 0);
44 
45   /* Parse and validate policy. */
46   policies_parse_from_options(options);
47 }
48 
49 static void
test_config(void * arg)50 test_config(void *arg)
51 {
52   char *err_msg = NULL;
53   tor_addr_t addr;
54   smartlist_t *ports = smartlist_new();
55   or_options_t *options = get_options_mutable();
56 
57   (void) arg;
58 
59   set_metrics_port(options);
60 
61   int ret = metrics_parse_ports(options, ports, &err_msg);
62   tt_int_op(ret, OP_EQ, 0);
63   tt_int_op(smartlist_len(ports), OP_EQ, 1);
64 
65   /* Validate the configured port. */
66   const port_cfg_t *cfg = smartlist_get(ports, 0);
67   tt_assert(tor_addr_eq_ipv4h(&cfg->addr, 0x7f000001));
68   tt_int_op(cfg->port, OP_EQ, 9035);
69   tt_int_op(cfg->type, OP_EQ, CONN_TYPE_METRICS_LISTENER);
70 
71   /* Address of the policy should be permitted. */
72   tor_addr_from_ipv4h(&addr, 0x01020304); /* 1.2.3.4 */
73   ret = metrics_policy_permits_address(&addr);
74   tt_int_op(ret, OP_EQ, true);
75 
76   /* Anything else, should not. */
77   tor_addr_from_ipv4h(&addr, 0x01020305); /* 1.2.3.5 */
78   ret = metrics_policy_permits_address(&addr);
79   tt_int_op(ret, OP_EQ, false);
80 
81  done:
82   SMARTLIST_FOREACH(ports, port_cfg_t *, c, port_cfg_free(c));
83   smartlist_free(ports);
84   or_options_free(options);
85   tor_free(err_msg);
86 }
87 
88 static char _c_buf[256];
89 #define CONTAINS(conn, msg) \
90   do { \
91     tt_int_op(buf_datalen(conn->outbuf), OP_EQ, (strlen(msg))); \
92     memset(_c_buf, 0, sizeof(_c_buf)); \
93     buf_get_bytes(conn->outbuf, _c_buf, (strlen(msg))); \
94     tt_str_op(_c_buf, OP_EQ, (msg)); \
95     tt_int_op(buf_datalen(conn->outbuf), OP_EQ, 0); \
96   } while (0)
97 
98 #define WRITE(conn, msg) \
99   buf_add(conn->inbuf, (msg), (strlen(msg)));
100 
101 /* Free the previous conn object if any and allocate a new connection. In
102  * order to be allowed, set its address to 1.2.3.4 as per the policy. */
103 #define NEW_ALLOWED_CONN()                              \
104   do {                                                  \
105     close_closeable_connections();                      \
106     conn = connection_new(CONN_TYPE_METRICS, AF_INET);  \
107     tor_addr_from_ipv4h(&conn->addr, 0x01020304);       \
108   } while (0)
109 
110 static void
test_connection(void * arg)111 test_connection(void *arg)
112 {
113   int ret;
114   connection_t *conn = NULL;
115   or_options_t *options = get_options_mutable();
116 
117   (void) arg;
118 
119   /* Notice that in this test, we will allocate a new connection at every test
120    * case. This is because the metrics_connection_process_inbuf() marks for
121    * close the connection in case of an error and thus we can't call again an
122    * inbuf process function on a marked for close connection. */
123 
124   tor_init_connection_lists();
125 
126   /* Setup policy. */
127   set_metrics_port(options);
128 
129   /* Set 1.2.3.5 IP, we should get rejected. */
130   NEW_ALLOWED_CONN();
131   tor_addr_from_ipv4h(&conn->addr, 0x01020305);
132   ret = metrics_connection_process_inbuf(conn);
133   tt_int_op(ret, OP_EQ, -1);
134 
135   /* No HTTP request yet. */
136   NEW_ALLOWED_CONN();
137   ret = metrics_connection_process_inbuf(conn);
138   tt_int_op(ret, OP_EQ, 0);
139   connection_free_minimal(conn);
140 
141   /* Bad request. */
142   NEW_ALLOWED_CONN();
143   WRITE(conn, "HTTP 4.7\r\n\r\n");
144   ret = metrics_connection_process_inbuf(conn);
145   tt_int_op(ret, OP_EQ, -1);
146   CONTAINS(conn, "HTTP/1.0 400 Bad Request\r\n\r\n");
147 
148   /* Path not found. */
149   NEW_ALLOWED_CONN();
150   WRITE(conn, "GET /badpath HTTP/1.0\r\n\r\n");
151   ret = metrics_connection_process_inbuf(conn);
152   tt_int_op(ret, OP_EQ, -1);
153   CONTAINS(conn, "HTTP/1.0 404 Not Found\r\n\r\n");
154 
155   /* Method not allowed. */
156   NEW_ALLOWED_CONN();
157   WRITE(conn, "POST /something HTTP/1.0\r\n\r\n");
158   ret = metrics_connection_process_inbuf(conn);
159   tt_int_op(ret, OP_EQ, -1);
160   CONTAINS(conn, "HTTP/1.0 405 Method Not Allowed\r\n\r\n");
161 
162   /* Ask for metrics. The content should be above 0. We don't test the
163    * validity of the returned content but it is certainly not an error. */
164   NEW_ALLOWED_CONN();
165   WRITE(conn, "GET /metrics HTTP/1.0\r\n\r\n");
166   ret = metrics_connection_process_inbuf(conn);
167   tt_int_op(ret, OP_EQ, 0);
168   tt_int_op(buf_datalen(conn->outbuf), OP_GT, 0);
169 
170  done:
171   or_options_free(options);
172   connection_free_minimal(conn);
173 }
174 
175 static void
test_prometheus(void * arg)176 test_prometheus(void *arg)
177 {
178   metrics_store_t *store = NULL;
179   metrics_store_entry_t *entry = NULL;
180   buf_t *buf = buf_new();
181   char *output = NULL;
182 
183   (void) arg;
184 
185   /* Fresh new store. No entries. */
186   store = metrics_store_new();
187   tt_assert(store);
188 
189   /* Add entry and validate its content. */
190   entry = metrics_store_add(store, METRICS_TYPE_COUNTER,
191                             TEST_METRICS_ENTRY_NAME,
192                             TEST_METRICS_ENTRY_HELP);
193   tt_assert(entry);
194   metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1);
195 
196   static const char *expected =
197     "# HELP " TEST_METRICS_ENTRY_NAME " " TEST_METRICS_ENTRY_HELP "\n"
198     "# TYPE " TEST_METRICS_ENTRY_NAME " counter\n"
199     TEST_METRICS_ENTRY_NAME "{" TEST_METRICS_ENTRY_LABEL_1 "} 0\n";
200 
201   metrics_store_get_output(METRICS_FORMAT_PROMETHEUS, store, buf);
202   output = buf_extract(buf, NULL);
203   tt_str_op(expected, OP_EQ, output);
204 
205  done:
206   buf_free(buf);
207   tor_free(output);
208   metrics_store_free(store);
209 }
210 
211 static void
test_store(void * arg)212 test_store(void *arg)
213 {
214   metrics_store_t *store = NULL;
215   metrics_store_entry_t *entry = NULL;
216 
217   (void) arg;
218 
219   /* Fresh new store. No entries. */
220   store = metrics_store_new();
221   tt_assert(store);
222   tt_assert(!metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME));
223 
224   /* Add entry and validate its content. */
225   entry = metrics_store_add(store, METRICS_TYPE_COUNTER,
226                             TEST_METRICS_ENTRY_NAME,
227                             TEST_METRICS_ENTRY_HELP);
228   tt_assert(entry);
229   tt_int_op(entry->type, OP_EQ, METRICS_TYPE_COUNTER);
230   tt_str_op(entry->name, OP_EQ, TEST_METRICS_ENTRY_NAME);
231   tt_str_op(entry->help, OP_EQ, TEST_METRICS_ENTRY_HELP);
232   tt_uint_op(entry->u.counter.value, OP_EQ, 0);
233 
234   /* Access the entry. */
235   tt_assert(metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME));
236 
237   /* Add a label to the entry to make it unique. */
238   metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_1);
239   tt_int_op(metrics_store_entry_has_label(entry, TEST_METRICS_ENTRY_LABEL_1),
240             OP_EQ, true);
241 
242   /* Update entry's value. */
243   metrics_store_entry_update(entry, 42);
244   tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 42);
245   metrics_store_entry_update(entry, 42);
246   tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 84);
247   metrics_store_entry_reset(entry);
248   tt_int_op(metrics_store_entry_get_value(entry), OP_EQ, 0);
249 
250   /* Add a new entry of same name but different label. */
251   /* Add entry and validate its content. */
252   entry = metrics_store_add(store, METRICS_TYPE_COUNTER,
253                             TEST_METRICS_ENTRY_NAME,
254                             TEST_METRICS_ENTRY_HELP);
255   tt_assert(entry);
256   metrics_store_entry_add_label(entry, TEST_METRICS_ENTRY_LABEL_2);
257 
258   /* Make sure _both_ entries are there. */
259   const smartlist_t *entries =
260     metrics_store_get_all(store, TEST_METRICS_ENTRY_NAME);
261   tt_assert(entries);
262   tt_int_op(smartlist_len(entries), OP_EQ, 2);
263 
264  done:
265   metrics_store_free(store);
266 }
267 
268 struct testcase_t metrics_tests[] = {
269 
270   { "config", test_config, TT_FORK, NULL, NULL },
271   { "connection", test_connection, TT_FORK, NULL, NULL },
272   { "prometheus", test_prometheus, TT_FORK, NULL, NULL },
273   { "store", test_store, TT_FORK, NULL, NULL },
274 
275   END_OF_TESTCASES
276 };
277 
278