Commit 3bd73672 authored by David Goulet's avatar David Goulet 🆘
Browse files

Merge branch 'tor-gitlab/mr/327'

Conflicts:
  src/test/test.c

  Minor comment issue.
parents 973fcf05 ebb826f4
......@@ -5466,6 +5466,77 @@ pt_parse_transport_line(const or_options_t *options,
return r;
}
/**
* Parse a flag describing an extra dirport for a directory authority.
*
* Right now, the supported format is exactly:
* `{upload,download,voting}=http://[IP:PORT]/`.
* Other URL schemes, and other suffixes, might be supported in the future.
*
* Only call this function if `flag` starts with one of the above strings.
*
* Return 0 on success, and -1 on failure.
*
* If `ds` is provided, then add any parsed dirport to `ds`. If `ds` is NULL,
* take no action other than parsing.
**/
static int
parse_dirauth_dirport(dir_server_t *ds, const char *flag)
{
tor_assert(flag);
auth_dirport_usage_t usage;
if (!strcasecmpstart(flag, "upload=")) {
usage = AUTH_USAGE_UPLOAD;
} else if (!strcasecmpstart(flag, "download=")) {
usage = AUTH_USAGE_DOWNLOAD;
} else if (!strcasecmpstart(flag, "vote=")) {
usage = AUTH_USAGE_VOTING;
} else {
// We shouldn't get called with a flag that we don't recognize.
tor_assert_nonfatal_unreached();
return -1;
}
const char *eq = strchr(flag, '=');
tor_assert(eq);
const char *target = eq + 1;
// Find the part inside the http://{....}/
if (strcmpstart(target, "http://")) {
log_warn(LD_CONFIG, "Unsupported URL scheme in authority flag %s", flag);
return -1;
}
const char *addr = target + strlen("http://");
const char *eos = strchr(addr, '/');
size_t addr_len;
if (eos && strcmp(eos, "/")) {
log_warn(LD_CONFIG, "Unsupported URL prefix in authority flag %s", flag);
return -1;
} else if (eos) {
addr_len = eos - addr;
} else {
addr_len = strlen(addr);
}
// Finally, parse the addr:port part.
char *addr_string = tor_strndup(addr, addr_len);
tor_addr_port_t dirport;
memset(&dirport, 0, sizeof(dirport));
int rv = tor_addr_port_parse(LOG_WARN, addr_string,
&dirport.addr, &dirport.port, -1);
if (ds != NULL && rv == 0) {
trusted_dir_server_add_dirport(ds, usage, &dirport);
} else if (rv == -1) {
log_warn(LD_CONFIG, "Unable to parse address in authority flag %s",flag);
}
tor_free(addr_string);
return rv;
}
/** Read the contents of a DirAuthority line from <b>line</b>. If
* <b>validate_only</b> is 0, and the line is well-formed, and it
* shares any bits with <b>required_type</b> or <b>required_type</b>
......@@ -5486,6 +5557,7 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
char v3_digest[DIGEST_LEN];
dirinfo_type_t type = 0;
double weight = 1.0;
smartlist_t *extra_dirports = smartlist_new();
memset(v3_digest, 0, sizeof(v3_digest));
......@@ -5554,6 +5626,12 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
}
ipv6_addrport_ptr = &ipv6_addrport;
}
} else if (!strcasecmpstart(flag, "upload=") ||
!strcasecmpstart(flag, "download=") ||
!strcasecmpstart(flag, "vote=")) {
// We'll handle these after creating the authority object.
smartlist_add(extra_dirports, flag);
flag = NULL; // prevent double-free.
} else {
log_warn(LD_CONFIG, "Unrecognized flag '%s' on DirAuthority line",
flag);
......@@ -5597,6 +5675,13 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
goto err;
}
if (validate_only) {
SMARTLIST_FOREACH_BEGIN(extra_dirports, const char *, cp) {
if (parse_dirauth_dirport(NULL, cp) < 0)
goto err;
} SMARTLIST_FOREACH_END(cp);
}
if (!validate_only && (!required_type || required_type & type)) {
dir_server_t *ds;
if (required_type)
......@@ -5608,16 +5693,23 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type,
ipv6_addrport_ptr,
digest, v3_digest, type, weight)))
goto err;
SMARTLIST_FOREACH_BEGIN(extra_dirports, const char *, cp) {
if (parse_dirauth_dirport(ds, cp) < 0)
goto err;
} SMARTLIST_FOREACH_END(cp);
dir_server_add(ds);
}
r = 0;
goto done;
err:
err:
r = -1;
done:
done:
SMARTLIST_FOREACH(extra_dirports, char*, s, tor_free(s));
smartlist_free(extra_dirports);
SMARTLIST_FOREACH(items, char*, s, tor_free(s));
smartlist_free(items);
tor_free(addrport);
......
......@@ -16,6 +16,8 @@
#include "core/or/or.h"
#include "feature/nodelist/routerstatus_st.h"
struct smartlist_t;
/** Represents information about a single trusted or fallback directory
* server. */
struct dir_server_t {
......@@ -48,6 +50,10 @@ struct dir_server_t {
time_t addr_current_at; /**< When was the document that we derived the
* address information from published? */
/** Authority only. Can be null. If present, a list of auth_dirport_t
* representing HTTP dirports for this authority. */
struct smartlist_t *auth_dirports;
routerstatus_t fake_status; /**< Used when we need to pass this trusted
* dir_server_t to
* directory_request_set_routerstatus.
......
......@@ -1119,6 +1119,7 @@ directory_request_set_routerstatus(directory_request_t *req,
{
req->routerstatus = status;
}
/**
* Helper: update the addresses, ports, and identities in <b>req</b>
* from the routerstatus object in <b>req</b>. Return 0 on success.
......@@ -1161,7 +1162,7 @@ directory_request_set_dir_from_routerstatus(directory_request_t *req)
return -1;
}
/* At this point, if we are a client making a direct connection to a
/* At this point, if we are a client making a direct connection to a
* directory server, we have selected a server that has at least one address
* allowed by ClientUseIPv4/6 and Reachable{"",OR,Dir}Addresses. This
* selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if
......@@ -1176,6 +1177,37 @@ directory_request_set_dir_from_routerstatus(directory_request_t *req)
return -1;
}
/* One last thing: If we're talking to an authority, we might want to use
* a special HTTP port for it based on our purpose.
*/
if (req->indirection == DIRIND_DIRECT_CONN && status->is_authority) {
const dir_server_t *ds = router_get_trusteddirserver_by_digest(
status->identity_digest);
if (ds) {
const tor_addr_port_t *v4 = NULL;
if (authdir_mode_v3(get_options())) {
// An authority connecting to another authority should always
// prefer the VOTING usage, if one is specifically configured.
v4 = trusted_dir_server_get_dirport_exact(
ds, AUTH_USAGE_VOTING, AF_INET);
}
if (! v4) {
// Everybody else should prefer a usage dependent on their
// the dir_purpose.
auth_dirport_usage_t usage =
auth_dirport_usage_for_purpose(req->dir_purpose);
v4 = trusted_dir_server_get_dirport(ds, usage, AF_INET);
}
tor_assert_nonfatal(v4);
if (v4) {
// XXXX We could, if we wanted, also select a v6 address. But a v4
// address must exist here, and we as a relay are required to support
// ipv4. So we just that.
tor_addr_port_copy(&use_dir_ap, v4);
}
}
}
directory_request_set_or_addr_port(req, &use_or_ap);
directory_request_set_dir_addr_port(req, &use_dir_ap);
directory_request_set_directory_id_digest(req, status->identity_digest);
......@@ -1194,7 +1226,7 @@ directory_initiate_request,(directory_request_t *request))
tor_assert_nonfatal(
! directory_request_dir_contact_info_specified(request));
if (directory_request_set_dir_from_routerstatus(request) < 0) {
return;
return; // or here XXXX
}
}
......@@ -1309,6 +1341,8 @@ directory_initiate_request,(directory_request_t *request))
entry_guard_cancel(&guard_state);
}
// XXXX This is the case where we replace.
switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr,
port, &socket_error)) {
case -1:
......
......@@ -43,6 +43,14 @@
#include "feature/dirclient/dir_server_st.h"
#include "feature/nodelist/node_st.h"
/** Information about an (HTTP) dirport for a directory authority. */
struct auth_dirport_t {
/** What is the intended usage for this dirport? One of AUTH_USAGE_* */
auth_dirport_usage_t usage;
/** What is the correct address/port ? */
tor_addr_port_t dirport;
};
/** Global list of a dir_server_t object for each directory
* authority. */
static smartlist_t *trusted_dir_servers = NULL;
......@@ -66,6 +74,11 @@ add_trusted_dir_to_nodelist_addr_set(const dir_server_t *dir)
/* IPv6 DirPort is not a thing yet for authorities. */
nodelist_add_addr_to_address_set(&dir->ipv6_addr, dir->ipv6_orport, 0);
}
if (dir->auth_dirports) {
SMARTLIST_FOREACH_BEGIN(dir->auth_dirports, const auth_dirport_t *, p) {
nodelist_add_addr_to_address_set(&p->dirport.addr, 0, p->dirport.port);
} SMARTLIST_FOREACH_END(p);
}
}
/** Go over the trusted directory server list and add their address(es) to the
......@@ -256,7 +269,10 @@ MOCK_IMPL(int, router_digest_is_trusted_dir_type,
/** Return true iff the given address matches a trusted directory that matches
* at least one bit of type.
*
* If type is NO_DIRINFO or ALL_DIRINFO, any authority is matched. */
* If type is NO_DIRINFO or ALL_DIRINFO, any authority is matched.
*
* Only ORPorts' addresses are considered.
*/
bool
router_addr_is_trusted_dir_type(const tor_addr_t *addr, dirinfo_type_t type)
{
......@@ -281,6 +297,42 @@ router_addr_is_trusted_dir_type(const tor_addr_t *addr, dirinfo_type_t type)
return false;
}
/** Return an appropriate usage value describing which authdir port to use
* for a given directory connection purpose.
*/
auth_dirport_usage_t
auth_dirport_usage_for_purpose(int purpose)
{
switch (purpose) {
case DIR_PURPOSE_FETCH_SERVERDESC:
case DIR_PURPOSE_FETCH_EXTRAINFO:
case DIR_PURPOSE_FETCH_CONSENSUS:
case DIR_PURPOSE_FETCH_CERTIFICATE:
case DIR_PURPOSE_FETCH_MICRODESC:
return AUTH_USAGE_DOWNLOAD;
case DIR_PURPOSE_UPLOAD_DIR:
return AUTH_USAGE_UPLOAD;
case DIR_PURPOSE_UPLOAD_VOTE:
case DIR_PURPOSE_UPLOAD_SIGNATURES:
case DIR_PURPOSE_FETCH_DETACHED_SIGNATURES:
case DIR_PURPOSE_FETCH_STATUS_VOTE:
return AUTH_USAGE_VOTING;
case DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2:
case DIR_PURPOSE_SERVER:
case DIR_PURPOSE_UPLOAD_RENDDESC_V2:
case DIR_PURPOSE_FETCH_RENDDESC_V2:
case DIR_PURPOSE_UPLOAD_HSDESC:
case DIR_PURPOSE_FETCH_HSDESC:
case DIR_PURPOSE_HAS_FETCHED_HSDESC:
default:
tor_assert_nonfatal_unreached();
return AUTH_USAGE_LEGACY;
}
}
/** Create a directory server at <b>address</b>:<b>port</b>, with OR identity
* key <b>digest</b> which has DIGEST_LEN bytes. If <b>address</b> is NULL,
* add ourself. If <b>is_authority</b>, this is a directory authority. Return
......@@ -357,6 +409,7 @@ dir_server_new(int is_authority,
ent->fake_status.ipv4_dirport = ent->ipv4_dirport;
ent->fake_status.ipv4_orport = ent->ipv4_orport;
ent->fake_status.ipv6_orport = ent->ipv6_orport;
ent->fake_status.is_authority = !! is_authority;
return ent;
}
......@@ -404,10 +457,98 @@ trusted_dir_server_new(const char *nickname, const char *address,
ipv6_addrport,
digest,
v3_auth_digest, type, weight);
if (ipv4_dirport) {
tor_addr_port_t p;
memset(&p, 0, sizeof(p));
tor_addr_copy(&p.addr, &ipv4_addr);
p.port = ipv4_dirport;
trusted_dir_server_add_dirport(result, AUTH_USAGE_LEGACY, &p);
}
tor_free(hostname);
return result;
}
/**
* Add @a dirport as an HTTP DirPort contact point for the directory authority
* @a ds, for use when contacting that authority for the given @a usage.
*
* Multiple ports of the same usage are allowed; if present, then only
* the first one of each address family is currently used.
*/
void
trusted_dir_server_add_dirport(dir_server_t *ds,
auth_dirport_usage_t usage,
const tor_addr_port_t *dirport)
{
tor_assert(ds);
tor_assert(dirport);
if (BUG(! ds->is_authority)) {
return;
}
if (ds->auth_dirports == NULL) {
ds->auth_dirports = smartlist_new();
}
auth_dirport_t *port = tor_malloc_zero(sizeof(auth_dirport_t));
port->usage = usage;
tor_addr_port_copy(&port->dirport, dirport);
smartlist_add(ds->auth_dirports, port);
}
/**
* Helper for trusted_dir_server_get_dirport: only return the exact requested
* usage type.
*/
const tor_addr_port_t *
trusted_dir_server_get_dirport_exact(const dir_server_t *ds,
auth_dirport_usage_t usage,
int addr_family)
{
tor_assert(ds);
tor_assert_nonfatal(addr_family == AF_INET || addr_family == AF_INET6);
if (ds->auth_dirports == NULL)
return NULL;
SMARTLIST_FOREACH_BEGIN(ds->auth_dirports, const auth_dirport_t *, port) {
if (port->usage == usage &&
tor_addr_family(&port->dirport.addr) == addr_family) {
return &port->dirport;
}
} SMARTLIST_FOREACH_END(port);
return NULL;
}
/**
* Return the DirPort of the authority @a ds for with the usage type
* @a usage and address family @a addr_family. If none is found, try
* again with an AUTH_USAGE_LEGACY dirport, if there is one. Return NULL
* if no port can be found.
*/
const tor_addr_port_t *
trusted_dir_server_get_dirport(const dir_server_t *ds,
auth_dirport_usage_t usage,
int addr_family)
{
const tor_addr_port_t *port;
while (1) {
port = trusted_dir_server_get_dirport_exact(ds, usage, addr_family);
if (port)
return port;
// If we tried LEGACY, there is no fallback from this point.
if (usage == AUTH_USAGE_LEGACY)
return NULL;
// Try again with LEGACY.
usage = AUTH_USAGE_LEGACY;
}
}
/** Return a new dir_server_t for a fallback directory server at
* <b>addr</b>:<b>or_port</b>/<b>dir_port</b>, with identity key digest
* <b>id_digest</b> */
......@@ -447,6 +588,10 @@ dir_server_free_(dir_server_t *ds)
if (!ds)
return;
if (ds->auth_dirports) {
SMARTLIST_FOREACH(ds->auth_dirports, auth_dirport_t *, p, tor_free(p));
smartlist_free(ds->auth_dirports);
}
tor_free(ds->nickname);
tor_free(ds->description);
tor_free(ds->address);
......
......@@ -11,6 +11,28 @@
#ifndef TOR_DIRLIST_H
#define TOR_DIRLIST_H
typedef struct auth_dirport_t auth_dirport_t;
/**
* Different usages for an authority's HTTP directory port.
*
* Historically, only legacy ports existed; proposal 330 added multiple types
* of dirport to better enable authorities to offload work and resist DoS
* attacks.
**/
typedef enum auth_dirport_usage_t {
/** Flag for an authority's dirport that is intended for misc/legacy
* usage. May be used when no other dirport is available. */
AUTH_USAGE_LEGACY,
/** Flag for an authority's dirport that is intended for descriptor uploads
* only. */
AUTH_USAGE_UPLOAD,
/** Flag for an authority's dirport that is intended for voting only */
AUTH_USAGE_VOTING,
/** Flag for an authority's dirport that is intended for relay downloads
* only. */
AUTH_USAGE_DOWNLOAD,
} auth_dirport_usage_t;
int get_n_authorities(dirinfo_type_t type);
const smartlist_t *router_get_trusted_dir_servers(void);
const smartlist_t *router_get_fallback_dir_servers(void);
......@@ -18,6 +40,8 @@ smartlist_t *router_get_trusted_dir_servers_mutable(void);
smartlist_t *router_get_fallback_dir_servers_mutable(void);
void mark_all_dirservers_up(smartlist_t *server_list);
auth_dirport_usage_t auth_dirport_usage_for_purpose(int purpose);
dir_server_t *router_get_trusteddirserver_by_digest(const char *d);
dir_server_t *router_get_fallback_dirserver_by_digest(
const char *digest);
......@@ -28,6 +52,14 @@ MOCK_DECL(dir_server_t *, trusteddirserver_get_by_v3_auth_digest,
MOCK_DECL(int, router_digest_is_trusted_dir_type,
(const char *digest, dirinfo_type_t type));
const tor_addr_port_t *trusted_dir_server_get_dirport(const dir_server_t *ds,
auth_dirport_usage_t usage,
int addr_family);
const tor_addr_port_t *trusted_dir_server_get_dirport_exact(
const dir_server_t *ds,
auth_dirport_usage_t usage,
int addr_family);
bool router_addr_is_trusted_dir_type(const tor_addr_t *addr,
dirinfo_type_t type);
#define router_addr_is_trusted_dir(d) \
......@@ -41,6 +73,9 @@ dir_server_t *trusted_dir_server_new(const char *nickname, const char *address,
const tor_addr_port_t *addrport_ipv6,
const char *digest, const char *v3_auth_digest,
dirinfo_type_t type, double weight);
void trusted_dir_server_add_dirport(dir_server_t *ds,
auth_dirport_usage_t usage,
const tor_addr_port_t *dirport);
dir_server_t *fallback_dir_server_new(const tor_addr_t *addr,
uint16_t dir_port, uint16_t or_port,
const tor_addr_port_t *addrport_ipv6,
......
......@@ -170,6 +170,7 @@ src_test_test_SOURCES += \
src/test/test_crypto_rng.c \
src/test/test_data.c \
src/test/test_dir.c \
src/test/test_dirauth_ports.c \
src/test/test_dirvote.c \
src/test/test_dir_common.c \
src/test/test_dir_handle_get.c \
......
/* Copyright (c) 2001-2004, Roger Dingledine.
->a * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */
......@@ -673,6 +672,7 @@ struct testgroup_t testgroups[] = {
{ "crypto/pem/", pem_tests },
{ "crypto/rng/", crypto_rng_tests },
{ "dir/", dir_tests },
{ "dir/auth/ports/", dirauth_port_tests },
{ "dir/auth/process_descs/", process_descs_tests },
{ "dir/md/", microdesc_tests },
{ "dirauth/dirvote/", dirvote_tests},
......
......@@ -120,6 +120,7 @@ extern struct testcase_t crypto_ope_tests[];
extern struct testcase_t crypto_openssl_tests[];
extern struct testcase_t crypto_rng_tests[];
extern struct testcase_t crypto_tests[];
extern struct testcase_t dirauth_port_tests[];
extern struct testcase_t dir_handle_get_tests[];
extern struct testcase_t dir_tests[];
extern struct testcase_t dirvote_tests[];
......
/* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2021, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#include "orconfig.h"
#define CONFIG_PRIVATE
#include "core/or/or.h"
#include "feature/dirclient/dir_server_st.h"
#include "feature/nodelist/dirlist.h"
#include "app/config/config.h"
#include "test/test.h"
#include "test/log_test_helpers.h"
static void
test_dirauth_port_parsing(void *arg)
{
(void)arg;
// This one is okay.
int rv = parse_dir_authority_line(
"moria1 orport=9101 "
"v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
"upload=http://128.31.0.39:9131/ "
"download=http://128.31.0.39:9131 "
"vote=http://128.31.0.39:9131/ "
"128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
NO_DIRINFO, 1);
tt_int_op(rv,OP_EQ,0);
// These have bad syntax.
setup_capture_of_logs(LOG_WARN);
rv = parse_dir_authority_line(
"moria1 orport=9101 "
"v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
"uploadx=http://128.31.0.39:9131/ "
"128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
NO_DIRINFO, 1);
tt_int_op(rv,OP_EQ,0);
expect_log_msg_containing("Unrecognized flag");
mock_clean_saved_logs();
rv = parse_dir_authority_line(
"moria1 orport=9101 "
"v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
"upload=https://128.31.0.39:9131/ " // https is not recognized
"128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
NO_DIRINFO, 1);
tt_int_op(rv,OP_EQ,-1);
expect_log_msg_containing("Unsupported URL scheme");
mock_clean_saved_logs();
rv = parse_dir_authority_line(
"moria1 orport=9101 "
"v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
"upload=http://128.31.0.39:9131/tor " // suffix is not supported
"128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
NO_DIRINFO, 1);
tt_int_op(rv,OP_EQ,-1);
expect_log_msg_containing("Unsupported URL prefix");
mock_clean_saved_logs();
rv = parse_dir_authority_line(
"moria1 orport=9101 "
"v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
"upload=http://128.31.0.256:9131/ " // "256" is not ipv4.
"128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
NO_DIRINFO, 1);
tt_int_op(rv,OP_EQ,-1);
expect_log_msg_containing("Unable to parse address");
rv = parse_dir_authority_line(
"moria1 orport=9101 "
"v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
"upload=http://xyz.example.com/ " // hostnames not supported.
"128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
NO_DIRINFO, 1);
tt_int_op(rv,OP_EQ,-1);
expect_log_msg_containing("Unable to parse address");
done:
teardown_capture_of_logs();
}
static void
test_dirauth_port_lookup(void *arg)
{
(void)arg;
clear_dir_servers();
int rv = parse_dir_authority_line(
"moria1 orport=9101 "
"v3ident=D586D18309DED4CD6D57C18FDB97EFA96D330566 "
"upload=http://128.31.0.40:9132/ "
"download=http://128.31.0.41:9133 "
"vote=http://128.31.0.42:9134/ "
"128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31",
NO_DIRINFO, 0);
tt_int_op(rv,OP_EQ,0);