summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/app/config/config.c1
-rw-r--r--src/core/or/connection_edge.c49
-rw-r--r--src/core/or/connection_edge.h6
-rw-r--r--src/core/or/or.h1
-rw-r--r--src/feature/hs/hs_common.c5
-rw-r--r--src/feature/hs/hs_config.c48
-rw-r--r--src/feature/hs/hs_ident.h4
-rw-r--r--src/feature/hs/hs_service.c13
-rw-r--r--src/feature/hs/hs_service.h15
-rw-r--r--src/test/test_extorport.c17
-rw-r--r--src/test/test_helpers.c19
-rw-r--r--src/test/test_helpers.h3
-rw-r--r--src/test/test_hs_service.c96
13 files changed, 260 insertions, 17 deletions
diff --git a/src/app/config/config.c b/src/app/config/config.c
index 0b2c391fea..abb3407043 100644
--- a/src/app/config/config.c
+++ b/src/app/config/config.c
@@ -460,6 +460,7 @@ static config_var_t option_vars_[] = {
VAR("HiddenServiceMaxStreams",LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceMaxStreamsCloseCircuit",LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL),
+ VAR("HiddenServiceExportCircuitID", LINELIST_S, RendConfigLines, NULL),
VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"),
V(HidServAuth, LINELIST, NULL),
V(ClientOnionAuthDir, FILENAME, NULL),
diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c
index 40772670ee..d0fad67009 100644
--- a/src/core/or/connection_edge.c
+++ b/src/core/or/connection_edge.c
@@ -839,6 +839,46 @@ connected_cell_format_payload(uint8_t *payload_out,
return connected_payload_len;
}
+/* This is an onion service client connection: Export the client circuit ID
+ * according to the HAProxy proxy protocol. */
+STATIC void
+export_hs_client_circuit_id(edge_connection_t *edge_conn,
+ hs_circuit_id_protocol_t protocol)
+{
+ /* We only support HAProxy right now. */
+ if (protocol != HS_CIRCUIT_ID_PROTOCOL_HAPROXY)
+ return;
+
+ char *buf = NULL;
+ const char dst_ipv6[] = "::1";
+ /* See RFC4193 regarding fc00::/7 */
+ const char src_ipv6_prefix[] = "fc00:dead:beef:4dad:";
+ uint16_t dst_port = 0;
+ uint16_t src_port = 1; /* default value */
+ uint32_t gid = 0; /* default value */
+
+ /* Generate a GID and source port for this client */
+ if (edge_conn->on_circuit != NULL) {
+ gid = TO_ORIGIN_CIRCUIT(edge_conn->on_circuit)->global_identifier;
+ src_port = gid & 0x0000ffff;
+ }
+
+ /* Grab the original dest port from the hs ident */
+ if (edge_conn->hs_ident) {
+ dst_port = edge_conn->hs_ident->orig_virtual_port;
+ }
+
+ /* Build the string */
+ tor_asprintf(&buf, "PROXY TCP6 %s:%x:%x %s %d %d\r\n",
+ src_ipv6_prefix,
+ gid >> 16, gid & 0x0000ffff,
+ dst_ipv6, src_port, dst_port);
+
+ connection_buf_add(buf, strlen(buf), TO_CONN(edge_conn));
+
+ tor_free(buf);
+}
+
/** Connected handler for exit connections: start writing pending
* data, deliver 'CONNECTED' relay cells as appropriate, and check
* any pending data that may have been received. */
@@ -859,6 +899,7 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn)
rep_hist_note_exit_stream_opened(conn->port);
conn->state = EXIT_CONN_STATE_OPEN;
+
connection_watch_events(conn, READ_EVENT); /* stop writing, keep reading */
if (connection_get_outbuf_len(conn)) /* in case there are any queued relay
* cells */
@@ -3652,6 +3693,14 @@ handle_hs_exit_conn(circuit_t *circ, edge_connection_t *conn)
hs_inc_rdv_stream_counter(origin_circ);
+ /* If it's an onion service connection, we might want to include the proxy
+ * protocol header: */
+ if (conn->hs_ident) {
+ hs_circuit_id_protocol_t circuit_id_protocol =
+ hs_service_exports_circuit_id(&conn->hs_ident->identity_pk);
+ export_hs_client_circuit_id(conn, circuit_id_protocol);
+ }
+
/* Connect tor to the hidden service destination. */
connection_exit_connect(conn);
diff --git a/src/core/or/connection_edge.h b/src/core/or/connection_edge.h
index 71ea95d6e5..6dee93f1f9 100644
--- a/src/core/or/connection_edge.h
+++ b/src/core/or/connection_edge.h
@@ -14,6 +14,8 @@
#include "lib/testsupport/testsupport.h"
+#include "feature/hs/hs_service.h"
+
edge_connection_t *TO_EDGE_CONN(connection_t *);
entry_connection_t *TO_ENTRY_CONN(connection_t *);
entry_connection_t *EDGE_TO_ENTRY_CONN(edge_connection_t *);
@@ -260,6 +262,10 @@ STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn,
rewrite_result_t *out);
STATIC int connection_ap_process_http_connect(entry_connection_t *conn);
+STATIC void
+export_hs_client_circuit_id(edge_connection_t *edge_conn,
+ hs_circuit_id_protocol_t protocol);
+
#endif /* defined(CONNECTION_EDGE_PRIVATE) */
#endif /* !defined(TOR_CONNECTION_EDGE_H) */
diff --git a/src/core/or/or.h b/src/core/or/or.h
index 4d8b6d7871..7557c1321e 100644
--- a/src/core/or/or.h
+++ b/src/core/or/or.h
@@ -26,6 +26,7 @@
#include "lib/cc/compat_compiler.h"
#include "lib/cc/torint.h"
#include "lib/container/map.h"
+#include "lib/container/buffers.h"
#include "lib/container/smartlist.h"
#include "lib/crypt_ops/crypto_cipher.h"
#include "lib/crypt_ops/crypto_rsa.h"
diff --git a/src/feature/hs/hs_common.c b/src/feature/hs/hs_common.c
index 12405a79cb..c36892e0f8 100644
--- a/src/feature/hs/hs_common.c
+++ b/src/feature/hs/hs_common.c
@@ -882,6 +882,11 @@ hs_set_conn_addr_port(const smartlist_t *ports, edge_connection_t *conn)
smartlist_free(matching_ports);
if (chosen_port) {
if (!(chosen_port->is_unix_addr)) {
+ /* save the original destination before we overwrite it */
+ if (conn->hs_ident) {
+ conn->hs_ident->orig_virtual_port = TO_CONN(conn)->port;
+ }
+
/* Get a non-AF_UNIX connection ready for connection_exit_connect() */
tor_addr_copy(&TO_CONN(conn)->addr, &chosen_port->real_addr);
TO_CONN(conn)->port = chosen_port->real_port;
diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c
index eaeb58829a..93d7403dfb 100644
--- a/src/feature/hs/hs_config.c
+++ b/src/feature/hs/hs_config.c
@@ -145,6 +145,34 @@ helper_parse_uint64(const char *opt, const char *value, uint64_t min,
return ret;
}
+/** Helper function: Given a configuration option and its value, parse the
+ * value as a hs_circuit_id_protocol_t. On success, ok is set to 1 and ret is
+ * the parse value. On error, ok is set to 0 and the "none"
+ * hs_circuit_id_protocol_t is returned. This function logs on error. */
+static hs_circuit_id_protocol_t
+helper_parse_circuit_id_protocol(const char *key, const char *value, int *ok)
+{
+ tor_assert(value);
+ tor_assert(ok);
+
+ hs_circuit_id_protocol_t ret = HS_CIRCUIT_ID_PROTOCOL_NONE;
+ *ok = 0;
+
+ if (! strcasecmp(value, "haproxy")) {
+ *ok = 1;
+ ret = HS_CIRCUIT_ID_PROTOCOL_HAPROXY;
+ } else if (! strcasecmp(value, "none")) {
+ *ok = 1;
+ ret = HS_CIRCUIT_ID_PROTOCOL_NONE;
+ } else {
+ log_warn(LD_CONFIG, "%s must be 'haproxy' or 'none'.", key);
+ goto err;
+ }
+
+ err:
+ return ret;
+}
+
/* Return the service version by trying to learn it from the key on disk if
* any. If nothing is found, the current service configured version is
* returned. */
@@ -188,6 +216,11 @@ config_has_invalid_options(const config_line_t *line_,
NULL /* End marker. */
};
+ const char *opts_exclude_v2[] = {
+ "HiddenServiceExportCircuitID",
+ NULL /* End marker. */
+ };
+
/* Defining the size explicitly allows us to take advantage of the compiler
* which warns us if we ever bump the max version but forget to grow this
* array. The plus one is because we have a version 0 :). */
@@ -196,7 +229,7 @@ config_has_invalid_options(const config_line_t *line_,
} exclude_lists[HS_VERSION_MAX + 1] = {
{ NULL }, /* v0. */
{ NULL }, /* v1. */
- { NULL }, /* v2 */
+ { opts_exclude_v2 }, /* v2 */
{ opts_exclude_v3 }, /* v3. */
};
@@ -262,6 +295,7 @@ config_service_v3(const config_line_t *line_,
hs_service_config_t *config)
{
int have_num_ip = 0;
+ bool export_circuit_id = false; /* just to detect duplicate options */
const char *dup_opt_seen = NULL;
const config_line_t *line;
@@ -288,6 +322,18 @@ config_service_v3(const config_line_t *line_,
have_num_ip = 1;
continue;
}
+ if (!strcasecmp(line->key, "HiddenServiceExportCircuitID")) {
+ config->circuit_id_protocol =
+ helper_parse_circuit_id_protocol(line->key, line->value, &ok);
+ if (!ok || export_circuit_id) {
+ if (export_circuit_id) {
+ dup_opt_seen = line->key;
+ }
+ goto err;
+ }
+ export_circuit_id = true;
+ continue;
+ }
}
/* We do not load the key material for the service at this stage. This is
diff --git a/src/feature/hs/hs_ident.h b/src/feature/hs/hs_ident.h
index 92d15b0523..ab87d16d17 100644
--- a/src/feature/hs/hs_ident.h
+++ b/src/feature/hs/hs_ident.h
@@ -111,6 +111,10 @@ typedef struct hs_ident_edge_conn_t {
* in the onion address. */
ed25519_public_key_t identity_pk;
+ /* The original virtual port that was used by the client to access the onion
+ * service, regardless of the internal port forwarding that might have
+ * happened on the service-side. */
+ uint16_t orig_virtual_port;
/* XXX: Client authorization. */
} hs_ident_edge_conn_t;
diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c
index 28e3f73340..643240fb68 100644
--- a/src/feature/hs/hs_service.c
+++ b/src/feature/hs/hs_service.c
@@ -3767,6 +3767,19 @@ hs_service_set_conn_addr_port(const origin_circuit_t *circ,
return -1;
}
+/** Does the service with identity pubkey <b>pk</b> export the circuit IDs of
+ * its clients? */
+hs_circuit_id_protocol_t
+hs_service_exports_circuit_id(const ed25519_public_key_t *pk)
+{
+ hs_service_t *service = find_service(hs_service_map, pk);
+ if (!service) {
+ return HS_CIRCUIT_ID_PROTOCOL_NONE;
+ }
+
+ return service->config.circuit_id_protocol;
+}
+
/* Add to file_list every filename used by a configured hidden service, and to
* dir_list every directory path used by a configured hidden service. This is
* used by the sandbox subsystem to whitelist those. */
diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h
index 735266071f..6fb15b9d37 100644
--- a/src/feature/hs/hs_service.h
+++ b/src/feature/hs/hs_service.h
@@ -161,6 +161,15 @@ typedef struct hs_service_authorized_client_t {
curve25519_public_key_t client_pk;
} hs_service_authorized_client_t;
+/** Which protocol to use for exporting HS client circuit ID. */
+typedef enum {
+ /** Don't expose the circuit id. */
+ HS_CIRCUIT_ID_PROTOCOL_NONE,
+
+ /** Use the HAProxy proxy protocol. */
+ HS_CIRCUIT_ID_PROTOCOL_HAPROXY
+} hs_circuit_id_protocol_t;
+
/* Service configuration. The following are set from the torrc options either
* set by the configuration file or by the control port. Nothing else should
* change those values. */
@@ -210,6 +219,9 @@ typedef struct hs_service_config_t {
/* Is this service ephemeral? */
unsigned int is_ephemeral : 1;
+
+ /* Does this service export the circuit ID of its clients? */
+ hs_circuit_id_protocol_t circuit_id_protocol;
} hs_service_config_t;
/* Service state. */
@@ -316,6 +328,9 @@ void hs_service_upload_desc_to_dir(const char *encoded_desc,
const ed25519_public_key_t *blinded_pk,
const routerstatus_t *hsdir_rs);
+hs_circuit_id_protocol_t
+hs_service_exports_circuit_id(const ed25519_public_key_t *pk);
+
#ifdef HS_SERVICE_PRIVATE
#ifdef TOR_UNIT_TESTS
diff --git a/src/test/test_extorport.c b/src/test/test_extorport.c
index 1ac1c68d7e..71be9131c7 100644
--- a/src/test/test_extorport.c
+++ b/src/test/test_extorport.c
@@ -17,6 +17,7 @@
#include "core/or/or_connection_st.h"
#include "test/test.h"
+#include "test/test_helpers.h"
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
@@ -89,22 +90,6 @@ connection_write_to_buf_impl_replacement(const char *string, size_t len,
buf_add(conn->outbuf, string, len);
}
-static char *
-buf_get_contents(buf_t *buf, size_t *sz_out)
-{
- char *out;
- *sz_out = buf_datalen(buf);
- if (*sz_out >= ULONG_MAX)
- return NULL; /* C'mon, really? */
- out = tor_malloc(*sz_out + 1);
- if (buf_get_bytes(buf, out, (unsigned long)*sz_out) != 0) {
- tor_free(out);
- return NULL;
- }
- out[*sz_out] = '\0'; /* Hopefully gratuitous. */
- return out;
-}
-
static void
test_ext_or_write_command(void *arg)
{
diff --git a/src/test/test_helpers.c b/src/test/test_helpers.c
index 0a2da100ba..6ac73bff5c 100644
--- a/src/test/test_helpers.c
+++ b/src/test/test_helpers.c
@@ -125,6 +125,25 @@ connection_write_to_buf_mock(const char *string, size_t len,
buf_add(conn->outbuf, string, len);
}
+char *
+buf_get_contents(buf_t *buf, size_t *sz_out)
+{
+ tor_assert(buf);
+ tor_assert(sz_out);
+
+ char *out;
+ *sz_out = buf_datalen(buf);
+ if (*sz_out >= ULONG_MAX)
+ return NULL; /* C'mon, really? */
+ out = tor_malloc(*sz_out + 1);
+ if (buf_get_bytes(buf, out, (unsigned long)*sz_out) != 0) {
+ tor_free(out);
+ return NULL;
+ }
+ out[*sz_out] = '\0'; /* Hopefully gratuitous. */
+ return out;
+}
+
/* Set up a fake origin circuit with the specified number of cells,
* Return a pointer to the newly-created dummy circuit */
circuit_t *
diff --git a/src/test/test_helpers.h b/src/test/test_helpers.h
index 3196c93e6b..72bf7f2f71 100644
--- a/src/test/test_helpers.h
+++ b/src/test/test_helpers.h
@@ -4,6 +4,8 @@
#ifndef TOR_TEST_HELPERS_H
#define TOR_TEST_HELPERS_H
+#define BUFFERS_PRIVATE
+
#include "core/or/or.h"
const char *get_yesterday_date_str(void);
@@ -18,6 +20,7 @@ void helper_setup_fake_routerlist(void);
#define GET(path) "GET " path " HTTP/1.0\r\n\r\n"
void connection_write_to_buf_mock(const char *string, size_t len,
connection_t *conn, int compressed);
+char *buf_get_contents(buf_t *buf, size_t *sz_out);
int mock_tor_addr_lookup__fail_on_bad_addrs(const char *name,
uint16_t family, tor_addr_t *out);
diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c
index 41ad25d171..334bde3e7b 100644
--- a/src/test/test_hs_service.c
+++ b/src/test/test_hs_service.c
@@ -10,6 +10,7 @@
#define CIRCUITLIST_PRIVATE
#define CONFIG_PRIVATE
#define CONNECTION_PRIVATE
+#define CONNECTION_EDGE_PRIVATE
#define CRYPTO_PRIVATE
#define HS_COMMON_PRIVATE
#define HS_SERVICE_PRIVATE
@@ -33,6 +34,9 @@
#include "core/or/circuitbuild.h"
#include "core/or/circuitlist.h"
#include "core/or/circuituse.h"
+#include "core/mainloop/connection.h"
+#include "core/or/connection_edge.h"
+#include "core/or/edge_connection_st.h"
#include "lib/crypt_ops/crypto_rand.h"
#include "lib/fs/dir.h"
#include "feature/dirauth/dirvote.h"
@@ -2003,6 +2007,96 @@ test_authorized_client_config_equal(void *arg)
tor_free(config2);
}
+/** Test that client circuit ID gets correctly exported */
+static void
+test_export_client_circuit_id(void *arg)
+{
+ origin_circuit_t *or_circ = NULL;
+ size_t sz;
+ char *cp1=NULL, *cp2=NULL;
+ connection_t *conn = NULL;
+
+ (void) arg;
+
+ MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock);
+
+ hs_service_init();
+
+ /* Create service */
+ hs_service_t *service = helper_create_service();
+ /* Check that export circuit ID detection works */
+ service->config.circuit_id_protocol = HS_CIRCUIT_ID_PROTOCOL_NONE;
+ tt_int_op(0, OP_EQ,
+ hs_service_exports_circuit_id(&service->keys.identity_pk));
+ service->config.circuit_id_protocol = HS_CIRCUIT_ID_PROTOCOL_HAPROXY;
+ tt_int_op(1, OP_EQ,
+ hs_service_exports_circuit_id(&service->keys.identity_pk));
+
+ /* Create client connection */
+ conn = test_conn_get_connection(AP_CONN_STATE_CIRCUIT_WAIT, CONN_TYPE_AP, 0);
+
+ /* Create client edge conn hs_ident */
+ edge_connection_t *edge_conn = TO_EDGE_CONN(conn);
+ edge_conn->hs_ident = hs_ident_edge_conn_new(&service->keys.identity_pk);
+ edge_conn->hs_ident->orig_virtual_port = 42;
+
+ /* Create rend circuit */
+ or_circ = origin_circuit_new();
+ or_circ->base_.purpose = CIRCUIT_PURPOSE_C_REND_JOINED;
+ edge_conn->on_circuit = TO_CIRCUIT(or_circ);
+ or_circ->global_identifier = 666;
+
+ /* Export circuit ID */
+ export_hs_client_circuit_id(edge_conn, service->config.circuit_id_protocol);
+
+ /* Check contents */
+ cp1 = buf_get_contents(conn->outbuf, &sz);
+ tt_str_op(cp1, OP_EQ,
+ "PROXY TCP6 fc00:dead:beef:4dad::0:29a ::1 666 42\r\n");
+
+ /* Change circ GID and see that the reported circuit ID also changes */
+ or_circ->global_identifier = 22;
+
+ /* check changes */
+ export_hs_client_circuit_id(edge_conn, service->config.circuit_id_protocol);
+ cp2 = buf_get_contents(conn->outbuf, &sz);
+ tt_str_op(cp1, OP_NE, cp2);
+ tor_free(cp1);
+
+ /* Check that GID with UINT32_MAX works. */
+ or_circ->global_identifier = UINT32_MAX;
+
+ export_hs_client_circuit_id(edge_conn, service->config.circuit_id_protocol);
+ cp1 = buf_get_contents(conn->outbuf, &sz);
+ tt_str_op(cp1, OP_EQ,
+ "PROXY TCP6 fc00:dead:beef:4dad::ffff:ffff ::1 65535 42\r\n");
+ tor_free(cp1);
+
+ /* Check that GID with UINT16_MAX works. */
+ or_circ->global_identifier = UINT16_MAX;
+
+ export_hs_client_circuit_id(edge_conn, service->config.circuit_id_protocol);
+ cp1 = buf_get_contents(conn->outbuf, &sz);
+ tt_str_op(cp1, OP_EQ,
+ "PROXY TCP6 fc00:dead:beef:4dad::0:ffff ::1 65535 42\r\n");
+ tor_free(cp1);
+
+ /* Check that GID with UINT16_MAX + 7 works. */
+ or_circ->global_identifier = UINT16_MAX + 7;
+
+ export_hs_client_circuit_id(edge_conn, service->config.circuit_id_protocol);
+ cp1 = buf_get_contents(conn->outbuf, &sz);
+ tt_str_op(cp1, OP_EQ, "PROXY TCP6 fc00:dead:beef:4dad::1:6 ::1 6 42\r\n");
+
+ done:
+ UNMOCK(connection_write_to_buf_impl_);
+ circuit_free_(TO_CIRCUIT(or_circ));
+ connection_free_minimal(conn);
+ hs_service_free(service);
+ tor_free(cp1);
+ tor_free(cp2);
+}
+
struct testcase_t hs_service_tests[] = {
{ "e2e_rend_circuit_setup", test_e2e_rend_circuit_setup, TT_FORK,
NULL, NULL },
@@ -2044,6 +2138,8 @@ struct testcase_t hs_service_tests[] = {
NULL, NULL },
{ "authorized_client_config_equal", test_authorized_client_config_equal,
TT_FORK, NULL, NULL },
+ { "export_client_circuit_id", test_export_client_circuit_id, TT_FORK,
+ NULL, NULL },
END_OF_TESTCASES
};