Merge remote-tracking branch 'ahf-github/asn/bugs4700_2'

This commit is contained in:
Nick Mathewson 2018-09-21 09:40:16 -04:00
commit 0e4c42a912
15 changed files with 292 additions and 17 deletions

5
changes/bug4700 Normal file
View File

@ -0,0 +1,5 @@
o Minor features (onion services):
- Version 3 onion services can now use the per-service
HiddenServiceExportCircuitID option to differentiate client circuits by
using the HAProxy proxy protocol which assigns IP addresses to inbound client
circuits. Closes ticket 4700. Patch by Mahrud Sayrafi.

View File

@ -2845,6 +2845,33 @@ The following options are used to configure a hidden service.
not an authorization mechanism; it is instead meant to be a mild
inconvenience to port-scanners.) (Default: 0)
[[HiddenServiceExportCircuitID]] **HiddenServiceExportCircuitID** __protocol__::
The onion service will use the given protocol to expose the global circuit
identifier of each inbound client circuit via the selected protocol. The only
protocol supported right now \'haproxy\'. This option is only for v3
services. (Default: none) +
+
The haproxy option works in the following way: when the feature is
enabled, the Tor process will write a header line when a client is connecting
to the onion service. The header will look like this: +
+
"PROXY TCP6 fc00:dead:beef:4dad::ffff:ffff ::1 65535 42\r\n" +
+
We encode the "global circuit identifier" as the last 32-bits of the first
IPv6 address. All other values in the header can safely be ignored. You can
compute the global circuit identifier using the following formula given the
IPv6 address "fc00:dead:beef:4dad::AABB:CCDD": +
+
global_circuit_id = (0xAA << 24) + (0xBB << 16) + (0xCC << 8) + 0xDD; +
+
In the case above, where the last 32-bit is 0xffffffff, the global circuit
identifier would be 4294967295. You can use this value together with Tor's
control port where it is possible to terminate a circuit given the global
circuit identifier. For more information about this see controls-spec.txt. +
+
The HAProxy version 1 proxy protocol is described in detail at
https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
[[HiddenServiceMaxStreams]] **HiddenServiceMaxStreams** __N__::
The maximum number of simultaneous streams (connections) per rendezvous
circuit. The maximum value allowed is 65535. (Setting this to 0 will allow

View File

@ -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),

View File

@ -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);

View File

@ -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) */

View File

@ -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"

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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. */

View File

@ -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

View File

@ -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)
{

View File

@ -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 *

View File

@ -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);

View File

@ -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
};