Skip to content

Commit

Permalink
Merge 3477a73 into 035166e
Browse files Browse the repository at this point in the history
  • Loading branch information
asn-d6 committed Sep 16, 2018
2 parents 035166e + 3477a73 commit 0fcc136
Show file tree
Hide file tree
Showing 15 changed files with 267 additions and 17 deletions.
5 changes: 5 additions & 0 deletions changes/bug4700
@@ -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.
5 changes: 5 additions & 0 deletions doc/tor.1.txt
Expand Up @@ -2845,6 +2845,11 @@ 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.

[[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
Expand Down
1 change: 1 addition & 0 deletions src/app/config/config.c
Expand Up @@ -457,6 +457,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),
Expand Down
49 changes: 49 additions & 0 deletions src/core/or/connection_edge.c
Expand Up @@ -818,6 +818,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. */
Expand All @@ -838,6 +878,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 */
Expand Down Expand Up @@ -3631,6 +3672,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);

Expand Down
6 changes: 6 additions & 0 deletions src/core/or/connection_edge.h
Expand Up @@ -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 *);
Expand Down Expand Up @@ -254,6 +256,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) */
1 change: 1 addition & 0 deletions src/core/or/or.h
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions src/feature/hs/hs_common.c
Expand Up @@ -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;
Expand Down
45 changes: 44 additions & 1 deletion src/feature/hs/hs_config.c
Expand Up @@ -145,6 +145,31 @@ 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 {
log_warn(LD_CONFIG, "%s must be 'haproxy'.", 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. */
Expand Down Expand Up @@ -188,6 +213,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 :). */
Expand All @@ -196,7 +226,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. */
};

Expand Down Expand Up @@ -262,6 +292,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;

Expand All @@ -288,6 +319,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
Expand Down
4 changes: 4 additions & 0 deletions src/feature/hs/hs_ident.h
Expand Up @@ -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;

Expand Down
13 changes: 13 additions & 0 deletions src/feature/hs/hs_service.c
Expand Up @@ -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. */
Expand Down
15 changes: 15 additions & 0 deletions src/feature/hs/hs_service.h
Expand Up @@ -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. */
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -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
Expand Down
17 changes: 1 addition & 16 deletions src/test/test_extorport.c
Expand Up @@ -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>
Expand Down Expand Up @@ -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)
{
Expand Down
19 changes: 19 additions & 0 deletions src/test/test_helpers.c
Expand Up @@ -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 *
Expand Down
3 changes: 3 additions & 0 deletions src/test/test_helpers.h
Expand Up @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 0fcc136

Please sign in to comment.