aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes/bug238205
-rw-r--r--src/or/hs_circuit.c66
-rw-r--r--src/or/hs_client.c27
-rw-r--r--src/or/hs_common.c78
-rw-r--r--src/or/hs_config.c16
-rw-r--r--src/or/hs_service.c50
-rw-r--r--src/test/include.am4
7 files changed, 153 insertions, 93 deletions
diff --git a/changes/bug23820 b/changes/bug23820
new file mode 100644
index 0000000000..4e920d0498
--- /dev/null
+++ b/changes/bug23820
@@ -0,0 +1,5 @@
+ o Minor bugfixes (IPv6, v3 single onion services):
+ - Remove buggy code for IPv6-only v3 single onion services, and reject
+ attempts to configure them. This release supports IPv4, dual-stack, and
+ IPv6-only v3 hidden services; and IPv4 and dual-stack v3 single onion
+ services. Fixes bug 23820; bugfix on 0.3.2.1-alpha.
diff --git a/src/or/hs_circuit.c b/src/or/hs_circuit.c
index e1e513c5f4..a23edc36a6 100644
--- a/src/or/hs_circuit.c
+++ b/src/or/hs_circuit.c
@@ -343,6 +343,17 @@ send_establish_intro(const hs_service_t *service,
memwipe(payload, 0, sizeof(payload));
}
+/* Return a string constant describing the anonymity of service. */
+static const char *
+get_service_anonymity_string(const hs_service_t *service)
+{
+ if (service->config.is_single_onion) {
+ return "single onion";
+ } else {
+ return "hidden";
+ }
+}
+
/* For a given service, the ntor onion key and a rendezvous cookie, launch a
* circuit to the rendezvous point specified by the link specifiers. On
* success, a circuit identifier is attached to the circuit with the needed
@@ -370,7 +381,15 @@ launch_rendezvous_point_circuit(const hs_service_t *service,
&data->onion_pk,
service->config.is_single_onion);
if (info == NULL) {
- /* We are done here, we can't extend to the rendezvous point. */
+ /* We are done here, we can't extend to the rendezvous point.
+ * If you're running an IPv6-only v3 single onion service on 0.3.2 or with
+ * 0.3.2 clients, and somehow disable the option check, it will fail here.
+ */
+ log_fn(LOG_PROTOCOL_WARN, LD_REND,
+ "Not enough info to open a circuit to a rendezvous point for "
+ "%s service %s.",
+ get_service_anonymity_string(service),
+ safe_str_client(service->onion_address));
goto end;
}
@@ -392,17 +411,19 @@ launch_rendezvous_point_circuit(const hs_service_t *service,
}
}
if (circ == NULL) {
- log_warn(LD_REND, "Giving up on launching rendezvous circuit to %s "
- "for service %s",
+ log_warn(LD_REND, "Giving up on launching a rendezvous circuit to %s "
+ "for %s service %s",
safe_str_client(extend_info_describe(info)),
+ get_service_anonymity_string(service),
safe_str_client(service->onion_address));
goto end;
}
log_info(LD_REND, "Rendezvous circuit launched to %s with cookie %s "
- "for service %s",
+ "for %s service %s",
safe_str_client(extend_info_describe(info)),
safe_str_client(hex_str((const char *) data->rendezvous_cookie,
REND_COOKIE_LEN)),
+ get_service_anonymity_string(service),
safe_str_client(service->onion_address));
tor_assert(circ->build_state);
/* Rendezvous circuit have a specific timeout for the time spent on trying
@@ -533,7 +554,10 @@ retry_service_rendezvous_point(const origin_circuit_t *circ)
}
/* Using an extend info object ei, set all possible link specifiers in lspecs.
- * IPv4, legacy ID and ed25519 ID are mandatory thus MUST be present in ei. */
+ * legacy ID is mandatory thus MUST be present in ei. If IPv4 is not present,
+ * logs a BUG() warning, and returns an empty smartlist. Clients never make
+ * direct connections to rendezvous points, so they should always have an
+ * IPv4 address in ei. */
static void
get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs)
{
@@ -542,7 +566,11 @@ get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs)
tor_assert(ei);
tor_assert(lspecs);
- /* IPv4 is mandatory. */
+ /* We require IPv4, we will add IPv6 support in a later tor version */
+ if (BUG(!tor_addr_is_v4(&ei->addr))) {
+ return;
+ }
+
ls = link_specifier_new();
link_specifier_set_ls_type(ls, LS_IPV4);
link_specifier_set_un_ipv4_addr(ls, tor_addr_to_ipv4h(&ei->addr));
@@ -560,15 +588,15 @@ get_lspecs_from_extend_info(const extend_info_t *ei, smartlist_t *lspecs)
link_specifier_set_ls_len(ls, link_specifier_getlen_un_legacy_id(ls));
smartlist_add(lspecs, ls);
- /* ed25519 ID is mandatory. */
- ls = link_specifier_new();
- link_specifier_set_ls_type(ls, LS_ED25519_ID);
- memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity,
- link_specifier_getlen_un_ed25519_id(ls));
- link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls));
- smartlist_add(lspecs, ls);
-
- /* XXX: IPv6 is not clearly a thing in extend_info_t? */
+ /* ed25519 ID is only included if the node has it. */
+ if (!ed25519_public_key_is_zero(&ei->ed_identity)) {
+ ls = link_specifier_new();
+ link_specifier_set_ls_type(ls, LS_ED25519_ID);
+ memcpy(link_specifier_getarray_un_ed25519_id(ls), &ei->ed_identity,
+ link_specifier_getlen_un_ed25519_id(ls));
+ link_specifier_set_ls_len(ls, link_specifier_getlen_un_ed25519_id(ls));
+ smartlist_add(lspecs, ls);
+ }
}
/* Using the given descriptor intro point ip, the extend information of the
@@ -1053,6 +1081,14 @@ hs_circ_send_introduce1(origin_circuit_t *intro_circ,
* object which is used to build the content of the cell. */
setup_introduce1_data(ip, rend_circ->build_state->chosen_exit,
subcredential, &intro1_data);
+ /* If we didn't get any link specifiers, it's because our extend info was
+ * bad. */
+ if (BUG(!intro1_data.link_specifiers) ||
+ !smartlist_len(intro1_data.link_specifiers)) {
+ log_warn(LD_REND, "Unable to get link specifiers for INTRODUCE1 cell on "
+ "circuit %u.", TO_CIRCUIT(intro_circ)->n_circ_id);
+ goto done;
+ }
/* Final step before we encode a cell, we setup the circuit identifier which
* will generate both the rendezvous cookie and client keypair for this
diff --git a/src/or/hs_client.c b/src/or/hs_client.c
index 18b76e8b38..9ac653c721 100644
--- a/src/or/hs_client.c
+++ b/src/or/hs_client.c
@@ -776,15 +776,24 @@ client_get_random_intro(const ed25519_public_key_t *service_pk)
const hs_descriptor_t *desc;
const hs_desc_encrypted_data_t *enc_data;
const or_options_t *options = get_options();
+ /* Calculate the onion address for logging purposes */
+ char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1];
tor_assert(service_pk);
desc = hs_cache_lookup_as_client(service_pk);
+ /* Assume the service is v3 if the descriptor is missing. This is ok,
+ * because we only use the address in log messages */
+ hs_build_address(service_pk,
+ desc ? desc->plaintext_data.version : HS_VERSION_THREE,
+ onion_address);
if (desc == NULL || !hs_client_any_intro_points_usable(service_pk,
desc)) {
log_info(LD_REND, "Unable to randomly select an introduction point "
- "because descriptor %s.",
- (desc) ? "doesn't have usable intro point" : "is missing");
+ "for service %s because descriptor %s. We can't connect.",
+ safe_str_client(onion_address),
+ (desc) ? "doesn't have any usable intro points"
+ : "is missing (assuming v3 onion address)");
goto end;
}
@@ -812,6 +821,10 @@ client_get_random_intro(const ed25519_public_key_t *service_pk)
if (ei == NULL) {
/* We can get here for instance if the intro point is a private address
* and we aren't allowed to extend to those. */
+ log_info(LD_REND, "Unable to select introduction point with auth key %s "
+ "for service %s, because we could not extend to it.",
+ safe_str_client(ed25519_fmt(&ip->auth_key_cert->signed_key)),
+ safe_str_client(onion_address));
continue;
}
@@ -840,14 +853,20 @@ client_get_random_intro(const ed25519_public_key_t *service_pk)
* set, we are forced to not use anything. */
ei = ei_excluded;
if (options->StrictNodes) {
- log_warn(LD_REND, "Every introduction points are in the ExcludeNodes set "
- "and StrictNodes is set. We can't connect.");
+ log_warn(LD_REND, "Every introduction point for service %s is in the "
+ "ExcludeNodes set and StrictNodes is set. We can't connect.",
+ safe_str_client(onion_address));
extend_info_free(ei);
ei = NULL;
+ } else {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND, "Every introduction point for service "
+ "%s is unusable or we can't extend to it. We can't connect.",
+ safe_str_client(onion_address));
}
end:
smartlist_free(usable_ips);
+ memwipe(onion_address, 0, sizeof(onion_address));
return ei;
}
diff --git a/src/or/hs_common.c b/src/or/hs_common.c
index 42c15c80d3..f79aeb5981 100644
--- a/src/or/hs_common.c
+++ b/src/or/hs_common.c
@@ -1638,24 +1638,22 @@ hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str)
/* From a list of link specifier, an onion key and if we are requesting a
* direct connection (ex: single onion service), return a newly allocated
- * extend_info_t object. This function checks the firewall policies and if we
- * are allowed to extend to the chosen address.
+ * extend_info_t object. This function always returns an extend info with
+ * an IPv4 address, or NULL.
*
- * if either IPv4 or legacy ID is missing, error.
- * if not direct_conn, IPv4 is prefered.
- * if direct_conn, IPv6 is prefered if we have one available.
- * if firewall does not allow the chosen address, error.
- *
- * Return NULL if we can fulfill the conditions. */
+ * It performs the following checks:
+ * if either IPv4 or legacy ID is missing, return NULL.
+ * if direct_conn, and we can't reach the IPv4 address, return NULL.
+ */
extend_info_t *
hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
const curve25519_public_key_t *onion_key,
int direct_conn)
{
- int have_v4 = 0, have_v6 = 0, have_legacy_id = 0, have_ed25519_id = 0;
+ int have_v4 = 0, have_legacy_id = 0, have_ed25519_id = 0;
char legacy_id[DIGEST_LEN] = {0};
- uint16_t port_v4 = 0, port_v6 = 0, port = 0;
- tor_addr_t addr_v4, addr_v6, *addr = NULL;
+ uint16_t port_v4 = 0;
+ tor_addr_t addr_v4;
ed25519_public_key_t ed25519_pk;
extend_info_t *info = NULL;
@@ -1671,14 +1669,6 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
port_v4 = link_specifier_get_un_ipv4_port(ls);
have_v4 = 1;
break;
- case LS_IPV6:
- /* Skip if we already seen a v6. */
- if (have_v6) continue;
- tor_addr_from_ipv6_bytes(&addr_v6,
- (const char *) link_specifier_getconstarray_un_ipv6_addr(ls));
- port_v6 = link_specifier_get_un_ipv6_port(ls);
- have_v6 = 1;
- break;
case LS_LEGACY_ID:
/* Make sure we do have enough bytes for the legacy ID. */
if (link_specifier_getlen_un_legacy_id(ls) < sizeof(legacy_id)) {
@@ -1700,55 +1690,45 @@ hs_get_extend_info_from_lspecs(const smartlist_t *lspecs,
}
} SMARTLIST_FOREACH_END(ls);
- /* IPv4 and legacy ID are mandatory. */
+ /* Legacy ID is mandatory, and we require IPv4. */
if (!have_v4 || !have_legacy_id) {
goto done;
}
- /* By default, we pick IPv4 but this might change to v6 if certain
- * conditions are met. */
- addr = &addr_v4; port = port_v4;
- /* If we are NOT in a direct connection, we'll use our Guard and a 3-hop
- * circuit so we can't extend in IPv6. And at this point, we do have an IPv4
- * address available so go to validation. */
+ /* We know we have IPv4, because we just checked. */
if (!direct_conn) {
+ /* All clients can extend to any IPv4 via a 3-hop path. */
goto validate;
- }
-
- /* From this point on, we have a request for a direct connection to the
- * rendezvous point so make sure we can actually connect through our
- * firewall. We'll prefer IPv6. */
-
- /* IPv6 test. */
- if (have_v6 &&
- fascist_firewall_allows_address_addr(&addr_v6, port_v6,
- FIREWALL_OR_CONNECTION, 1, 1)) {
- /* Direct connection and we can reach it in IPv6 so go for it. */
- addr = &addr_v6; port = port_v6;
- goto validate;
- }
- /* IPv4 test and we are sure we have a v4 because of the check above. */
- if (fascist_firewall_allows_address_addr(&addr_v4, port_v4,
- FIREWALL_OR_CONNECTION, 0, 0)) {
+ } else if (direct_conn &&
+ fascist_firewall_allows_address_addr(&addr_v4, port_v4,
+ FIREWALL_OR_CONNECTION,
+ 0, 0)) {
/* Direct connection and we can reach it in IPv4 so go for it. */
- addr = &addr_v4; port = port_v4;
goto validate;
+
+ /* We will add support for falling back to a 3-hop path in a later
+ * release. */
+ } else {
+ /* If we can't reach IPv4, return NULL. */
+ goto done;
}
+ /* We will add support for IPv6 in a later release. */
+
validate:
/* We'll validate now that the address we've picked isn't a private one. If
* it is, are we allowing to extend to private address? */
- if (!extend_info_addr_is_allowed(addr)) {
- log_warn(LD_REND, "Requested address is private and it is not "
- "allowed to extend to it: %s:%u",
- fmt_addr(&addr_v4), port_v4);
+ if (!extend_info_addr_is_allowed(&addr_v4)) {
+ log_fn(LOG_PROTOCOL_WARN, LD_REND,
+ "Requested address is private and we are not allowed to extend to "
+ "it: %s:%u", fmt_addr(&addr_v4), port_v4);
goto done;
}
/* We do have everything for which we think we can connect successfully. */
info = extend_info_new(NULL, legacy_id,
(have_ed25519_id) ? &ed25519_pk : NULL, NULL,
- onion_key, addr, port);
+ onion_key, &addr_v4, port_v4);
done:
return info;
}
diff --git a/src/or/hs_config.c b/src/or/hs_config.c
index 5f9282ea79..fa5c1ab176 100644
--- a/src/or/hs_config.c
+++ b/src/or/hs_config.c
@@ -424,11 +424,19 @@ config_generic_service(const config_line_t *line_,
}
}
- /* Check if we are configured in non anonymous mode and single hop mode
- * meaning every service become single onion. */
- if (rend_service_allow_non_anonymous_connection(options) &&
- rend_service_non_anonymous_mode_enabled(options)) {
+ /* Check if we are configured in non anonymous mode meaning every service
+ * becomes a single onion service. */
+ if (rend_service_non_anonymous_mode_enabled(options)) {
config->is_single_onion = 1;
+ /* We will add support for IPv6-only v3 single onion services in a future
+ * Tor version. This won't catch "ReachableAddresses reject *4", but that
+ * option doesn't work anyway. */
+ if (options->ClientUseIPv4 == 0 && config->version == HS_VERSION_THREE) {
+ log_warn(LD_CONFIG, "IPv6-only v3 single onion services are not "
+ "supported. Set HiddenServiceSingleHopMode 0 and "
+ "HiddenServiceNonAnonymousMode 0, or set ClientUseIPv4 1.");
+ goto err;
+ }
}
/* Success */
diff --git a/src/or/hs_service.c b/src/or/hs_service.c
index 4b67bb4d42..a2082b3914 100644
--- a/src/or/hs_service.c
+++ b/src/or/hs_service.c
@@ -375,7 +375,14 @@ service_intro_point_free_(void *obj)
/* Return a newly allocated service intro point and fully initialized from the
* given extend_info_t ei if non NULL. If is_legacy is true, we also generate
- * the legacy key. On error, NULL is returned. */
+ * the legacy key. On error, NULL is returned.
+ *
+ * If ei is NULL, returns a hs_service_intro_point_t with an empty link
+ * specifier list and no onion key. (This is used for testing.)
+ *
+ * ei must be an extend_info_t containing an IPv4 address. (We will add supoort
+ * for IPv6 in a later release.) When calling extend_info_from_node(), pass
+ * 0 in for_direct_connection to make sure ei always has an IPv4 address. */
STATIC hs_service_intro_point_t *
service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy)
{
@@ -427,8 +434,8 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy)
goto done;
}
- /* We'll try to add all link specifier. Legacy, IPv4 and ed25519 are
- * mandatory. */
+ /* We'll try to add all link specifiers. Legacy is mandatory.
+ * IPv4 or IPv6 is required, and we always send IPv4. */
ls = hs_desc_link_specifier_new(ei, LS_IPV4);
/* It is impossible to have an extend info object without a v4. */
if (BUG(!ls)) {
@@ -450,11 +457,7 @@ service_intro_point_new(const extend_info_t *ei, unsigned int is_legacy)
smartlist_add(ip->base.link_specifiers, ls);
}
- /* IPv6 is optional. */
- ls = hs_desc_link_specifier_new(ei, LS_IPV6);
- if (ls) {
- smartlist_add(ip->base.link_specifiers, ls);
- }
+ /* IPv6 is not supported in this release. */
/* Finally, copy onion key from the extend_info_t object. */
memcpy(&ip->onion_key, &ei->curve25519_onion_key, sizeof(ip->onion_key));
@@ -1520,10 +1523,17 @@ build_all_descriptors(time_t now)
/* Randomly pick a node to become an introduction point but not present in the
* given exclude_nodes list. The chosen node is put in the exclude list
* regardless of success or not because in case of failure, the node is simply
- * unsusable from that point on. If direct_conn is set, try to pick a node
- * that our local firewall/policy allows to directly connect to and if not,
- * fallback to a normal 3-hop node. Return a newly allocated service intro
- * point ready to be used for encoding. NULL on error. */
+ * unsusable from that point on.
+ *
+ * If direct_conn is set, try to pick a node that our local firewall/policy
+ * allows us to connect to directly. If we can't find any, return NULL.
+ * This function supports selecting dual-stack nodes for direct single onion
+ * service IPv6 connections. But it does not send IPv6 addresses in link
+ * specifiers. (Current clients don't use IPv6 addresses to extend, and
+ * direct client connections to intro points are not supported.)
+ *
+ * Return a newly allocated service intro point ready to be used for encoding.
+ * Return NULL on error. */
static hs_service_intro_point_t *
pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes)
{
@@ -1537,12 +1547,9 @@ pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes)
node = router_choose_random_node(exclude_nodes, get_options()->ExcludeNodes,
direct_conn ? direct_flags : flags);
- if (node == NULL && direct_conn) {
- /* Unable to find a node for direct connection, let's fall back to a
- * normal 3-hop node. */
- node = router_choose_random_node(exclude_nodes,
- get_options()->ExcludeNodes, flags);
- }
+ /* Unable to find a node. When looking for a node for a direct connection,
+ * we could try a 3-hop path instead. We'll add support for this in a later
+ * release. */
if (!node) {
goto err;
}
@@ -1553,8 +1560,11 @@ pick_intro_point(unsigned int direct_conn, smartlist_t *exclude_nodes)
smartlist_add(exclude_nodes, (void *) node);
/* We do this to ease our life but also this call makes appropriate checks
- * of the node object such as validating ntor support for instance. */
- info = extend_info_from_node(node, direct_conn);
+ * of the node object such as validating ntor support for instance.
+ *
+ * We must provide an extend_info for clients to connect over a 3-hop path,
+ * so we don't pass direct_conn here. */
+ info = extend_info_from_node(node, 0);
if (BUG(info == NULL)) {
goto err;
}
diff --git a/src/test/include.am b/src/test/include.am
index 50cee693ab..7300b85412 100644
--- a/src/test/include.am
+++ b/src/test/include.am
@@ -42,8 +42,10 @@ TESTS += src/test/test src/test/test-slow src/test/test-memwipe \
TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-v2-min hs-v3-min \
single-onion-v23
# only run if we can ping6 ::1 (localhost)
+# IPv6-only v3 single onion services don't work yet, so we don't test the
+# single-onion-v23-ipv6 flavor
TEST_CHUTNEY_FLAVORS_IPV6 = bridges+ipv6-min ipv6-exit-min hs-v23-ipv6 \
- single-onion-v23-ipv6
+ single-onion-ipv6
# only run if we can find a stable (or simply another) version of tor
TEST_CHUTNEY_FLAVORS_MIXED = mixed+hs-v23