summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Perry <mikeperry-git@torproject.org>2017-12-22 05:43:44 +0000
committerMike Perry <mikeperry-git@torproject.org>2018-01-19 22:21:48 +0000
commit20a3f611057cb81c489ccf9d40438bc5d930d766 (patch)
tree07e57d6add0144cbd3f7f7b5adf351f0749fd991
parenta86324d1fa7b8dec8f84d3ab8bd246780b962c92 (diff)
downloadtor-20a3f611057cb81c489ccf9d40438bc5d930d766.tar.gz
tor-20a3f611057cb81c489ccf9d40438bc5d930d766.zip
Implement layer 2 and layer 3 guard pinning via torrc.
Block circuit canibalization when HSRendezvousMiddleNodes is active. Also make it apply to all HS circuits, not just rends.
-rw-r--r--changes/bug138374
-rw-r--r--doc/tor.1.txt94
-rw-r--r--src/or/circuitbuild.c257
-rw-r--r--src/or/circuituse.c66
-rw-r--r--src/or/circuituse.h3
-rw-r--r--src/or/config.c8
-rw-r--r--src/or/or.h8
7 files changed, 426 insertions, 14 deletions
diff --git a/changes/bug13837 b/changes/bug13837
new file mode 100644
index 0000000000..5791dcee58
--- /dev/null
+++ b/changes/bug13837
@@ -0,0 +1,4 @@
+ o Major features (Onion Services):
+ - Provide torrc options to pin the second and third hops of onion service
+ circuits to a list of nodes. The option HSLayer2Guards pins the second hop,
+ and the option HSLayer3Guards pins the third hop. Closes ticket 13837.
diff --git a/doc/tor.1.txt b/doc/tor.1.txt
index 115f09bc31..8e27a25de4 100644
--- a/doc/tor.1.txt
+++ b/doc/tor.1.txt
@@ -1531,6 +1531,100 @@ The following options are useful only for clients (that is, if
If no nodes in Tor2webRendezvousPoints are currently available for
use, Tor will choose a random node when building HS circuits.
+[[_HSLayer2Nodes]] **_HSLayer2Nodes** __node__,__node__,__...__::
+ A list of identity fingerprints, nicknames, country codes, and
+ address patterns of nodes that are allowed to be used as the
+ second hop in all client or service-side Onion Service circuits.
+ This option mitigates attacks where the adversary runs middle nodes
+ and induces your client or service to create many circuits, in order
+ to discover your primary guard node.
+ (Default: Any node in the network may be used in the second hop.)
+ +
+ (Example:
+ _HSLayer2Nodes ABCD1234CDEF5678ABCD1234CDEF5678ABCD1234, \{cc}, 255.254.0.0/8) +
+ +
+ When this is set, the resulting hidden service paths will
+ look like:
+ +
+ C - G - L2 - M - Rend +
+ C - G - L2 - M - HSDir +
+ C - G - L2 - M - Intro +
+ S - G - L2 - M - Rend +
+ S - G - L2 - M - HSDir +
+ S - G - L2 - M - Intro +
+ +
+ where C is this client, S is the service, G is the Guard node,
+ L2 is a node from this option, and M is a random middle node.
+ Rend, HSDir, and Intro point selection is not affected by this
+ option.
+ +
+ This option may be combined with _HSLayer3Nodes to create
+ paths of the form:
+ +
+ C - G - L2 - L3 - Rend +
+ C - G - L2 - L3 - M - HSDir +
+ C - G - L2 - L3 - M - Intro +
+ S - G - L2 - L3 - M - Rend +
+ S - G - L2 - L3 - HSDir +
+ S - G - L2 - L3 - Intro +
+ +
+ ExcludeNodes have higher priority than _HSLayer2Nodes,
+ which means that nodes specified in ExcludeNodes will not be
+ picked.
+ +
+ This option is meant to be managed by a Tor controller such as
+ https://github.com/mikeperry-tor/vanguards that selects and
+ updates this set of nodes for you. Hence it does not do load
+ balancing if fewer than 20 nodes are selected, and if no nodes in
+ _HSLayer2Nodes are currently available for use, Tor will not work.
+ Please use extreme care if you are setting this option manually.
+
+[[_HSLayer3Nodes]] **_HSLayer3Nodes** __node__,__node__,__...__::
+ A list of identity fingerprints, nicknames, country codes, and
+ address patterns of nodes that are allowed to be used as the
+ third hop in all client and service-side Onion Service circuits.
+ This option mitigates attacks where the adversary runs middle nodes
+ and induces your client or service to create many circuits, in order
+ to discover your primary or Layer2 guard nodes.
+ (Default: Any node in the network may be used in the third hop.)
+ +
+ (Example:
+ _HSLayer3Nodes ABCD1234CDEF5678ABCD1234CDEF5678ABCD1234, \{cc}, 255.254.0.0/8) +
+ +
+ When this is set by itself, the resulting hidden service paths
+ will look like: +
+ C - G - M - L3 - Rend +
+ C - G - M - L3 - M - HSDir +
+ C - G - M - L3 - M - Intro +
+ S - G - M - L3 - M - Rend +
+ S - G - M - L3 - HSDir +
+ S - G - M - L3 - Intro +
+ where C is this client, S is the service, G is the Guard node,
+ L2 is a node from this option, and M is a random middle node.
+ Rend, HSDir, and Intro point selection is not affected by this
+ option.
+ +
+ While it is possible to use this option by itself, it should be
+ combined with _HSLayer2Nodes to create paths of the form:
+ +
+ C - G - L2 - L3 - Rend +
+ C - G - L2 - L3 - M - HSDir +
+ C - G - L2 - L3 - M - Intro +
+ S - G - L2 - L3 - M - Rend +
+ S - G - L2 - L3 - HSDir +
+ S - G - L2 - L3 - Intro +
+ +
+ ExcludeNodes have higher priority than _HSLayer3Nodes,
+ which means that nodes specified in ExcludeNodes will not be
+ picked.
+ +
+ This option is meant to be managed by a Tor controller such as
+ https://github.com/mikeperry-tor/vanguards that selects and
+ updates this set of nodes for you. Hence it does not do load
+ balancing if fewer than 20 nodes are selected, and if no nodes in
+ _HSLayer3Nodes are currently available for use, Tor will not work.
+ Please use extreme care if you are setting this option manually.
+
[[UseMicrodescriptors]] **UseMicrodescriptors** **0**|**1**|**auto**::
Microdescriptors are a smaller version of the information that Tor needs
in order to build its circuits. Using microdescriptors makes Tor clients
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index a350f6c142..707654adf2 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -1659,6 +1659,37 @@ route_len_for_purpose(uint8_t purpose, extend_info_t *exit_ei)
int routelen = DEFAULT_ROUTE_LEN;
int known_purpose = 0;
+ if (circuit_should_use_vanguards(purpose)) {
+ /* Clients want an extra hop for rends to avoid linkability.
+ * Services want it for intro points to avoid publishing their
+ * layer3 guards.
+ * Ex: C - G - L2 - L3 - R
+ * S - G - L2 - L3 - I
+ */
+ if (purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND ||
+ purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)
+ return routelen+1;
+
+ /* If we only have Layer2 vanguards, then we do not need
+ * the extra hop for linkabilty reasons (see below).
+ * This means all hops can be of the form:
+ * S/C - G - L2 - M - R/HSDir/I
+ */
+ if (get_options()->HSLayer2Nodes && !get_options()->HSLayer3Nodes)
+ return routelen+1;
+
+ /* For connections to hsdirs, clients want two extra hops
+ * when using layer3 guards, to avoid linkability.
+ * Same goes for intro points. Note that the route len
+ * includes the intro point or hsdir, hence the +2.
+ * Ex: C - G - L2 - L3 - M - I
+ * S - G - L2 - L3 - M - R
+ */
+ if (purpose == CIRCUIT_PURPOSE_S_CONNECT_REND ||
+ purpose == CIRCUIT_PURPOSE_C_INTRODUCING)
+ return routelen+2;
+ }
+
if (!exit_ei)
return routelen;
@@ -2123,6 +2154,98 @@ pick_rendezvous_node(router_crn_flags_t flags)
return router_choose_random_node(NULL, options->ExcludeNodes, flags);
}
+/*
+ * Helper function to pick a configured restricted middle node
+ * (either HSLayer2Nodes or HSLayer3Nodes).
+ *
+ * Make sure that the node we chose is alive, and not excluded,
+ * and return it.
+ *
+ * The exclude_set is a routerset of nodes that the selected node
+ * must not match, and the exclude_list is a simple list of nodes
+ * that the selected node must not be in. Either or both may be
+ * NULL.
+ *
+ * Return NULL if no usable nodes could be found. */
+static const node_t *
+pick_restricted_middle_node(router_crn_flags_t flags,
+ const routerset_t *pick_from,
+ const routerset_t *exclude_set,
+ const smartlist_t *exclude_list,
+ int position_hint)
+{
+ const node_t *middle_node = NULL;
+
+ smartlist_t *whitelisted_live_middles = smartlist_new();
+ smartlist_t *all_live_nodes = smartlist_new();
+
+ tor_assert(pick_from);
+
+ /* Add all running nodes to all_live_nodes */
+ router_add_running_nodes_to_smartlist(all_live_nodes,
+ (flags & CRN_NEED_UPTIME) != 0,
+ (flags & CRN_NEED_CAPACITY) != 0,
+ (flags & CRN_NEED_GUARD) != 0,
+ (flags & CRN_NEED_DESC) != 0,
+ (flags & CRN_PREF_ADDR) != 0,
+ (flags & CRN_DIRECT_CONN) != 0);
+
+ /* Filter all_live_nodes to only add live *and* whitelisted middles
+ * to the list whitelisted_live_middles. */
+ SMARTLIST_FOREACH_BEGIN(all_live_nodes, node_t *, live_node) {
+ if (routerset_contains_node(pick_from, live_node)) {
+ smartlist_add(whitelisted_live_middles, live_node);
+ }
+ } SMARTLIST_FOREACH_END(live_node);
+
+ /* Honor ExcludeNodes */
+ if (exclude_set) {
+ routerset_subtract_nodes(whitelisted_live_middles, exclude_set);
+ }
+
+ if (exclude_list) {
+ smartlist_subtract(whitelisted_live_middles, exclude_list);
+ }
+
+ /**
+ * Max number of restricted nodes before we alert the user and try
+ * to load balance for them.
+ *
+ * The most agressive vanguard design had 16 nodes at layer3.
+ * Let's give a small ceiling above that. */
+#define MAX_SANE_RESTRICTED_NODES 20
+ /* If the user (or associated tor controller) selected only a few nodes,
+ * assume they took load balancing into account and don't do it for them.
+ *
+ * If there are a lot of nodes in here, assume they did not load balance
+ * and do it for them, but also warn them that they may be Doing It Wrong.
+ */
+ if (smartlist_len(whitelisted_live_middles) <=
+ MAX_SANE_RESTRICTED_NODES) {
+ middle_node = smartlist_choose(whitelisted_live_middles);
+ } else {
+ static ratelim_t pinned_notice_limit = RATELIM_INIT(24*3600);
+ log_fn_ratelim(&pinned_notice_limit, LOG_NOTICE, LD_CIRC,
+ "Your _HSLayer%dNodes setting has resulted "
+ "in %d total nodes. This is a lot of nodes. "
+ "You may want to consider using a Tor controller "
+ "to select and update a smaller set of nodes instead.",
+ position_hint, smartlist_len(whitelisted_live_middles));
+
+ /* NO_WEIGHTING here just means don't take node flags into account
+ * (ie: use consensus measurement only). This is done so that
+ * we don't further surprise the user by not using Exits that they
+ * specified at all */
+ middle_node = node_sl_choose_by_bandwidth(whitelisted_live_middles,
+ NO_WEIGHTING);
+ }
+
+ smartlist_free(whitelisted_live_middles);
+ smartlist_free(all_live_nodes);
+
+ return middle_node;
+}
+
/** Return a pointer to a suitable router to be the exit node for the
* circuit of purpose <b>purpose</b> that we're about to build (or NULL
* if no router is suitable).
@@ -2410,6 +2533,118 @@ cpath_get_n_hops(crypt_path_t **head_ptr)
#endif /* defined(TOR_UNIT_TESTS) */
+/**
+ * Build a list of nodes to exclude from the choice of this middle
+ * hop, based on already chosen nodes.
+ *
+ * XXX: At present, this function does not exclude any nodes from
+ * the vanguard circuits. See
+ * https://trac.torproject.org/projects/tor/ticket/24487
+ */
+static smartlist_t *
+build_middle_exclude_list(uint8_t purpose,
+ cpath_build_state_t *state,
+ crypt_path_t *head,
+ int cur_len)
+{
+ smartlist_t *excluded;
+ const node_t *r;
+ crypt_path_t *cpath;
+ int i;
+
+ excluded = smartlist_new();
+
+ /* Add the exit to the exclude list (note that the exit/last hop is always
+ * chosen first in circuit_establish_circuit()). */
+ if ((r = build_state_get_exit_node(state))) {
+ nodelist_add_node_and_family(excluded, r);
+ }
+
+ /* XXX: We don't apply any other previously selected node restrictions for
+ * vanguards, and allow nodes to be reused for those hop positions in the
+ * same circuit. This is because after many rotations, you get to learn
+ * inner guard nodes through the nodes that are not selected for outer
+ * hops.
+ *
+ * The alternative is building the circuit in reverse. Reverse calls to
+ * onion_extend_cpath() (ie: select outer hops first) would then have the
+ * property that you don't gain information about inner hops by observing
+ * outer ones. See https://trac.torproject.org/projects/tor/ticket/24487
+ * for this.
+ *
+ * (Note further that we can and do still exclude the exit in the block
+ * above, because it is chosen first in circuit_establish_circuit()..) */
+ if (circuit_should_use_vanguards(purpose)) {
+ return excluded;
+ }
+
+ for (i = 0, cpath = head; cpath && i < cur_len; ++i, cpath=cpath->next) {
+ if ((r = node_get_by_id(cpath->extend_info->identity_digest))) {
+ nodelist_add_node_and_family(excluded, r);
+ }
+ }
+
+ return excluded;
+}
+
+/** Return true if we MUST use vanguards for picking this middle node. */
+static int
+middle_node_must_be_vanguard(const or_options_t *options,
+ uint8_t purpose, int cur_len)
+{
+ /* If this is not a hidden service circuit, don't use vanguards */
+ if (!circuit_purpose_is_hidden_service(purpose)) {
+ return 0;
+ }
+
+ /* If we have sticky L2 nodes, and this is an L2 pick, use vanguards */
+ if (options->HSLayer2Nodes && cur_len == 1) {
+ return 1;
+ }
+
+ /* If we have sticky L3 nodes, and this is an L3 pick, use vanguards */
+ if (options->HSLayer3Nodes && cur_len == 2) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/** Pick a sticky vanguard middle node or return NULL if not found.
+ * See doc of pick_restricted_middle_node() for argument details. */
+static const node_t *
+pick_vanguard_middle_node(const or_options_t *options,
+ router_crn_flags_t flags, int cur_len,
+ const smartlist_t *excluded)
+{
+ const routerset_t *vanguard_routerset = NULL;
+ const node_t *node = NULL;
+
+ /* Pick the right routerset based on the current hop */
+ if (cur_len == 1) {
+ vanguard_routerset = options->HSLayer2Nodes;
+ } else if (cur_len == 2) {
+ vanguard_routerset = options->HSLayer3Nodes;
+ } else {
+ /* guaranteed by middle_node_should_be_vanguard() */
+ tor_assert_nonfatal_unreached();
+ return NULL;
+ }
+
+ node = pick_restricted_middle_node(flags, vanguard_routerset,
+ options->ExcludeNodes, excluded,
+ cur_len+1);
+
+ if (!node) {
+ static ratelim_t pinned_warning_limit = RATELIM_INIT(300);
+ log_fn_ratelim(&pinned_warning_limit, LOG_WARN, LD_CIRC,
+ "Could not find a node that matches the configured "
+ "_HSLayer%dNodes set", cur_len+1);
+ }
+
+ return node;
+}
+
/** A helper function used by onion_extend_cpath(). Use <b>purpose</b>
* and <b>state</b> and the cpath <b>head</b> (currently populated only
* to length <b>cur_len</b> to decide a suitable middle hop for a
@@ -2422,9 +2657,7 @@ choose_good_middle_server(uint8_t purpose,
crypt_path_t *head,
int cur_len)
{
- int i;
- const node_t *r, *choice;
- crypt_path_t *cpath;
+ const node_t *choice;
smartlist_t *excluded;
const or_options_t *options = get_options();
router_crn_flags_t flags = CRN_NEED_DESC;
@@ -2433,20 +2666,20 @@ choose_good_middle_server(uint8_t purpose,
log_debug(LD_CIRC, "Contemplating intermediate hop #%d: random choice.",
cur_len+1);
- excluded = smartlist_new();
- if ((r = build_state_get_exit_node(state))) {
- nodelist_add_node_and_family(excluded, r);
- }
- for (i = 0, cpath = head; i < cur_len; ++i, cpath=cpath->next) {
- if ((r = node_get_by_id(cpath->extend_info->identity_digest))) {
- nodelist_add_node_and_family(excluded, r);
- }
- }
+
+ excluded = build_middle_exclude_list(purpose, state, head, cur_len);
if (state->need_uptime)
flags |= CRN_NEED_UPTIME;
if (state->need_capacity)
flags |= CRN_NEED_CAPACITY;
+
+ /** If a hidden service circuit wants a specific middle node, pin it. */
+ if (middle_node_must_be_vanguard(options, purpose, cur_len)) {
+ log_debug(LD_GENERAL, "Picking a sticky node (cur_len = %d)", cur_len);
+ return pick_vanguard_middle_node(options, flags, cur_len, excluded);
+ }
+
choice = router_choose_random_node(excluded, options->ExcludeNodes, flags);
smartlist_free(excluded);
return choice;
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index f04448ffce..e7be8fa22a 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -1762,8 +1762,22 @@ circuit_build_failed(origin_circuit_t *circ)
TO_CIRCUIT(circ)->n_circ_id, circ->global_identifier);
}
if (n_chan_id && !already_marked) {
- /* New guard API: we failed. */
- if (circ->guard_state)
+ /*
+ * If we have guard state (new guard API) and our path selection
+ * code actually chose a full path, then blame the failure of this
+ * circuit on the guard.
+ *
+ * Note that we deliberately use circuit_get_cpath_len() (and not
+ * circuit_get_cpath_opened_len()) because we only want to ensure
+ * that a full path is *chosen*. This is different than a full path
+ * being *built*. We only want to blame *build* failures on this
+ * guard. Path selection failures can happen spuriously for a number
+ * of reasons (such as aggressive/invalid user-specified path
+ * restrictions in the torrc, as well as non-user reasons like
+ * exitpolicy issues), and so should not be counted here.
+ */
+ if (circ->guard_state &&
+ circuit_get_cpath_len(circ) >= circ->build_state->desired_path_len)
entry_guard_failed(&circ->guard_state);
/* if there are any one-hop streams waiting on this circuit, fail
* them now so they can retry elsewhere. */
@@ -1856,6 +1870,53 @@ have_enough_path_info(int need_exit)
return router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN;
}
+/**
+ * Tell us if a circuit is a hidden service circuit.
+ */
+int
+circuit_purpose_is_hidden_service(uint8_t purpose)
+{
+ /* Client-side purpose */
+ if (purpose >= CIRCUIT_PURPOSE_C_INTRODUCING &&
+ purpose <= CIRCUIT_PURPOSE_C_REND_JOINED) {
+ return 1;
+ }
+
+ /* Service-side purpose */
+ if (purpose >= CIRCUIT_PURPOSE_S_ESTABLISH_INTRO &&
+ purpose <= CIRCUIT_PURPOSE_S_REND_JOINED) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Return true if this circuit purpose should use vanguards
+ * or pinned Layer2 or Layer3 guards.
+ *
+ * This function takes both the circuit purpose and the
+ * torrc options for pinned middles/vanguards into account
+ * (ie: the circuit must be a hidden service circuit and
+ * vanguards/pinned middles must be enabled for it to return
+ * true).
+ */
+int
+circuit_should_use_vanguards(uint8_t purpose)
+{
+ const or_options_t *options = get_options();
+
+ /* Only hidden service circuits use vanguards */
+ if (!circuit_purpose_is_hidden_service(purpose))
+ return 0;
+
+ /* Pinned middles are effectively vanguards */
+ if (options->HSLayer2Nodes || options->HSLayer3Nodes)
+ return 1;
+
+ return 0;
+}
+
/** Launch a new circuit with purpose <b>purpose</b> and exit node
* <b>extend_info</b> (or NULL to select a random exit node). If flags
* contains CIRCLAUNCH_NEED_UPTIME, choose among routers with high uptime. If
@@ -1892,6 +1953,7 @@ circuit_launch_by_extend_info(uint8_t purpose,
if ((extend_info || purpose != CIRCUIT_PURPOSE_C_GENERAL) &&
purpose != CIRCUIT_PURPOSE_TESTING &&
+ !circuit_should_use_vanguards(purpose) &&
!onehop_tunnel && !need_specific_rp) {
/* see if there are appropriate circs available to cannibalize. */
/* XXX if we're planning to add a hop, perhaps we want to look for
diff --git a/src/or/circuituse.h b/src/or/circuituse.h
index 2b0f983f1a..71c818b978 100644
--- a/src/or/circuituse.h
+++ b/src/or/circuituse.h
@@ -63,6 +63,9 @@ int hostname_in_track_host_exits(const or_options_t *options,
const char *address);
void mark_circuit_unusable_for_new_conns(origin_circuit_t *circ);
+int circuit_purpose_is_hidden_service(uint8_t);
+int circuit_should_use_vanguards(uint8_t);
+
#ifdef TOR_UNIT_TESTS
/* Used only by circuituse.c and test_circuituse.c */
diff --git a/src/or/config.c b/src/or/config.c
index e5c10498c4..ebb536b0f9 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -404,6 +404,8 @@ static config_var_t option_vars_[] = {
V(Socks5ProxyPassword, STRING, NULL),
VAR("KeyDirectory", FILENAME, KeyDirectory_option, NULL),
V(KeyDirectoryGroupReadable, BOOL, "0"),
+ VAR("_HSLayer2Nodes", ROUTERSET, HSLayer2Nodes, NULL),
+ VAR("_HSLayer3Nodes", ROUTERSET, HSLayer3Nodes, NULL),
V(KeepalivePeriod, INTERVAL, "5 minutes"),
V(KeepBindCapabilities, AUTOBOOL, "auto"),
VAR("Log", LINELIST, Logs, NULL),
@@ -1647,6 +1649,8 @@ options_need_geoip_info(const or_options_t *options, const char **reason_out)
routerset_needs_geoip(options->ExitNodes) ||
routerset_needs_geoip(options->ExcludeExitNodes) ||
routerset_needs_geoip(options->ExcludeNodes) ||
+ routerset_needs_geoip(options->HSLayer2Nodes) ||
+ routerset_needs_geoip(options->HSLayer3Nodes) ||
routerset_needs_geoip(options->Tor2webRendezvousPoints);
if (routerset_usage && reason_out) {
@@ -2088,6 +2092,10 @@ options_act(const or_options_t *old_options)
options->ExcludeExitNodes) ||
!routerset_equal(old_options->EntryNodes, options->EntryNodes) ||
!routerset_equal(old_options->ExitNodes, options->ExitNodes) ||
+ !routerset_equal(old_options->HSLayer2Nodes,
+ options->HSLayer2Nodes) ||
+ !routerset_equal(old_options->HSLayer3Nodes,
+ options->HSLayer3Nodes) ||
!routerset_equal(old_options->Tor2webRendezvousPoints,
options->Tor2webRendezvousPoints) ||
options->StrictNodes != old_options->StrictNodes) {
diff --git a/src/or/or.h b/src/or/or.h
index 2617d2d87d..54e9786c75 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -3876,6 +3876,14 @@ typedef struct {
/** A routerset that should be used when picking RPs for HS circuits. */
routerset_t *Tor2webRendezvousPoints;
+ /** A routerset that should be used when picking middle nodes for HS
+ * circuits. */
+ routerset_t *HSLayer2Nodes;
+
+ /** A routerset that should be used when picking third-hop nodes for HS
+ * circuits. */
+ routerset_t *HSLayer3Nodes;
+
/** Onion Services in HiddenServiceSingleHopMode make one-hop (direct)
* circuits between the onion service server, and the introduction and
* rendezvous points. (Onion service descriptors are still posted using