commit 8dda7bfdb84a5e4b3b422592f811a465e37bb155 Author: David Goulet dgoulet@torproject.org Date: Wed Jan 27 21:49:56 2021 -0500
relay: Add bloomfiter of relay address + {OR|Dir}Port
In order to deny re-entry in the network, we now keep a bloomfilter of relay ORPort + address and authorities ORPort + address and DirPort + address combinations.
So when an Exit stream is handled, we deny anything connecting back into the network on the ORPorts for relays and on the ORPort+DirPort for the authorities.
Related to #2667
Signed-off-by: David Goulet dgoulet@torproject.org --- src/core/or/address_set.c | 74 +++++++++++++++++++++++++++++++++++++++++ src/core/or/address_set.h | 18 +++++++++- src/feature/nodelist/dirlist.c | 9 +++-- src/feature/nodelist/nodelist.c | 64 +++++++++++++++++++++++++++++------ src/feature/nodelist/nodelist.h | 5 ++- 5 files changed, 154 insertions(+), 16 deletions(-)
diff --git a/src/core/or/address_set.c b/src/core/or/address_set.c index d618ffc3d6..fcddc55e9f 100644 --- a/src/core/or/address_set.c +++ b/src/core/or/address_set.c @@ -69,3 +69,77 @@ address_set_probably_contains(const address_set_t *set, { return bloomfilt_probably_contains(set, addr); } + +/* Length of the item is an address (IPv4 or IPv6) and a 2 byte port. We use + * 16 bytes for the address here (IPv6) since we do not know which family + * the given address in the item thus in the case of IPv4, the extra bytes + * are simply zeroes to accomodate. */ +#define BLOOMFILT_ADDR_PORT_ITEM_LEN (16 + sizeof(uint16_t)) + +/** Build an item for the bloomfilter consisting of an address and port pair. + * + * If the given address is _not_ AF_INET or AF_INET6, then the item is an + * array of 0s. + * + * Return a pointer to a static buffer containing the item. Next call to this + * function invalidates its previous content. */ +static const uint8_t * +build_addr_port_item(const tor_addr_t *addr, const uint16_t port) +{ + static uint8_t data[BLOOMFILT_ADDR_PORT_ITEM_LEN]; + + memset(data, 0, sizeof(data)); + switch (tor_addr_family(addr)) { + case AF_INET: + memcpy(data, &addr->addr.in_addr.s_addr, 4); + break; + case AF_INET6: + memcpy(data, &addr->addr.in6_addr.s6_addr, 16); + break; + case AF_UNSPEC: + /* Leave the 0. */ + break; + default: + /* LCOV_EXCL_START */ + tor_fragile_assert(); + /* LCOV_EXCL_STOP */ + } + + memcpy(data + 16, &port, sizeof(port)); + return data; +} + +/** Return a hash value for the given item that the bloomfilter will use. */ +static uint64_t +bloomfilt_addr_port_hash(const struct sipkey *key, + const void *item) +{ + return siphash24(item, BLOOMFILT_ADDR_PORT_ITEM_LEN, key); +} + +/** Allocate and return an addr_port_set_t, suitable for holding up to + * max_address_guess distinct values. */ +addr_port_set_t * +addr_port_set_new(int max_addresses_guess) +{ + uint8_t k[BLOOMFILT_KEY_LEN]; + crypto_rand((void*)k, sizeof(k)); + return bloomfilt_new(max_addresses_guess, bloomfilt_addr_port_hash, k); +} + +/** Add an address and port pair to the given set. */ +void +addr_port_set_add(addr_port_set_t *set, const tor_addr_t *addr, uint16_t port) +{ + bloomfilt_add(set, build_addr_port_item(addr, port)); +} + +/** Return true if the given address and port pair are in the set. Of course, + * this is a bloomfilter and thus in rare occasion, a false positive happens + * thus the "probably". */ +bool +addr_port_set_probably_contains(const addr_port_set_t *set, + const tor_addr_t *addr, uint16_t port) +{ + return !!bloomfilt_probably_contains(set, build_addr_port_item(addr, port)); +} diff --git a/src/core/or/address_set.h b/src/core/or/address_set.h index 33887a7c9e..2c78ab0576 100644 --- a/src/core/or/address_set.h +++ b/src/core/or/address_set.h @@ -13,13 +13,14 @@ #include "lib/cc/torint.h" #include "lib/container/bloomfilt.h"
+struct tor_addr_t; + /** * An address_set_t represents a set of tor_addr_t values. The implementation * is probabilistic: false negatives cannot occur but false positives are * possible. */ typedef struct bloomfilt_t address_set_t; -struct tor_addr_t;
address_set_t *address_set_new(int max_addresses_guess); #define address_set_free(set) bloomfilt_free(set) @@ -28,4 +29,19 @@ void address_set_add_ipv4h(address_set_t *set, uint32_t addr); int address_set_probably_contains(const address_set_t *set, const struct tor_addr_t *addr);
+/** + * An addr_port_set_t represents a set of tor_addr_t values with a uint16_t + * port value. The implementation is probabilistic: false negatives cannot + * occur but false positives are possible. + */ +typedef struct bloomfilt_t addr_port_set_t; + +addr_port_set_t *addr_port_set_new(int max_addresses_guess); +#define addr_port_set_free(s) bloomfilt_free(s) +void addr_port_set_add(addr_port_set_t *set, + const struct tor_addr_t *addr, uint16_t port); +bool addr_port_set_probably_contains(const addr_port_set_t *set, + const struct tor_addr_t *addr, + uint16_t port); + #endif /* !defined(TOR_ADDRESS_SET_H) */ diff --git a/src/feature/nodelist/dirlist.c b/src/feature/nodelist/dirlist.c index 576a6a2e49..423c4106e2 100644 --- a/src/feature/nodelist/dirlist.c +++ b/src/feature/nodelist/dirlist.c @@ -58,10 +58,13 @@ add_trusted_dir_to_nodelist_addr_set(const dir_server_t *dir) tor_assert(dir); tor_assert(dir->is_authority);
- /* Add IPv4 and then IPv6 if applicable. */ - nodelist_add_addr_to_address_set(&dir->ipv4_addr); + /* Add IPv4 and then IPv6 if applicable. For authorities, we add the ORPort + * and DirPort so re-entry into the network back to them is not possible. */ + nodelist_add_addr_to_address_set(&dir->ipv4_addr, dir->ipv4_orport, + dir->ipv4_dirport); if (!tor_addr_is_null(&dir->ipv6_addr)) { - nodelist_add_addr_to_address_set(&dir->ipv6_addr); + /* IPv6 DirPort is not a thing yet for authorities. */ + nodelist_add_addr_to_address_set(&dir->ipv6_addr, dir->ipv6_orport, 0); } }
diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index cfa8b69f81..0c9bdb3c0e 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -135,6 +135,10 @@ typedef struct nodelist_t { /* Set of addresses that belong to nodes we believe in. */ address_set_t *node_addrs;
+ /* Set of addresses + port that belong to nodes we know and that we don't + * allow network re-entry towards them. */ + addr_port_set_t *reentry_set; + /* The valid-after time of the last live consensus that initialized the * nodelist. We use this to detect outdated nodelists that need to be * rebuilt using a newer consensus. */ @@ -447,40 +451,58 @@ node_addrs_changed(node_t *node) static void node_add_to_address_set(const node_t *node) { - if (!the_nodelist || !the_nodelist->node_addrs) + if (!the_nodelist || + !the_nodelist->node_addrs || !the_nodelist->reentry_set) return;
- /* These various address sources can be redundant, but it's likely faster - * to add them all than to compare them all for equality. */ + /* These various address sources can be redundant, but it's likely faster to + * add them all than to compare them all for equality. + * + * For relays, we only add the ORPort in the addr+port set since we want to + * allow re-entry into the network to the DirPort so the self reachability + * test succeeds and thus the 0 value for the DirPort. */
if (node->rs) { if (!tor_addr_is_null(&node->rs->ipv4_addr)) - nodelist_add_addr_to_address_set(&node->rs->ipv4_addr); + nodelist_add_addr_to_address_set(&node->rs->ipv4_addr, + node->rs->ipv4_orport, 0); if (!tor_addr_is_null(&node->rs->ipv6_addr)) - nodelist_add_addr_to_address_set(&node->rs->ipv6_addr); + nodelist_add_addr_to_address_set(&node->rs->ipv6_addr, + node->rs->ipv6_orport, 0); } if (node->ri) { if (!tor_addr_is_null(&node->ri->ipv4_addr)) - nodelist_add_addr_to_address_set(&node->ri->ipv4_addr); + nodelist_add_addr_to_address_set(&node->ri->ipv4_addr, + node->ri->ipv4_orport, 0); if (!tor_addr_is_null(&node->ri->ipv6_addr)) - nodelist_add_addr_to_address_set(&node->ri->ipv6_addr); + nodelist_add_addr_to_address_set(&node->ri->ipv6_addr, + node->ri->ipv6_orport, 0); } if (node->md) { if (!tor_addr_is_null(&node->md->ipv6_addr)) - nodelist_add_addr_to_address_set(&node->md->ipv6_addr); + nodelist_add_addr_to_address_set(&node->md->ipv6_addr, + node->md->ipv6_orport, 0); } }
/** Add the given address into the nodelist address set. */ void -nodelist_add_addr_to_address_set(const tor_addr_t *addr) +nodelist_add_addr_to_address_set(const tor_addr_t *addr, + uint16_t or_port, uint16_t dir_port) { if (BUG(!addr) || tor_addr_is_null(addr) || (!tor_addr_is_v4(addr) && !tor_addr_is_v6(addr)) || - !the_nodelist || !the_nodelist->node_addrs) { + !the_nodelist || !the_nodelist->node_addrs || + !the_nodelist->reentry_set) { return; } address_set_add(the_nodelist->node_addrs, addr); + if (or_port != 0) { + addr_port_set_add(the_nodelist->reentry_set, addr, or_port); + } + if (dir_port != 0) { + addr_port_set_add(the_nodelist->reentry_set, addr, dir_port); + } }
/** Return true if <b>addr</b> is the address of some node in the nodelist. @@ -497,6 +519,21 @@ nodelist_probably_contains_address(const tor_addr_t *addr) return address_set_probably_contains(the_nodelist->node_addrs, addr); }
+/** Return true if <b>addr</b> is the address of some node in the nodelist and + * corresponds also to the given port. If not, probably return false. */ +bool +nodelist_reentry_probably_contains(const tor_addr_t *addr, uint16_t port) +{ + if (BUG(!addr) || BUG(!port)) + return false; + + if (!the_nodelist || !the_nodelist->reentry_set) + return false; + + return addr_port_set_probably_contains(the_nodelist->reentry_set, + addr, port); +} + /** Add <b>ri</b> to an appropriate node in the nodelist. If we replace an * old routerinfo, and <b>ri_old_out</b> is not NULL, set *<b>ri_old_out</b> * to the previous routerinfo. @@ -628,10 +665,13 @@ nodelist_set_consensus(const networkstatus_t *ns) * v6). Then we add the number of configured trusted authorities we have. */ int estimated_addresses = smartlist_len(ns->routerstatus_list) * get_estimated_address_per_node(); - estimated_addresses += (get_n_authorities(V3_DIRINFO & BRIDGE_DIRINFO) * + estimated_addresses += (get_n_authorities(V3_DIRINFO | BRIDGE_DIRINFO) * get_estimated_address_per_node()); address_set_free(the_nodelist->node_addrs); + addr_port_set_free(the_nodelist->reentry_set); the_nodelist->node_addrs = address_set_new(estimated_addresses); + /* Times two here is for both the ORPort and DirPort. */ + the_nodelist->reentry_set = addr_port_set_new(estimated_addresses * 2);
SMARTLIST_FOREACH_BEGIN(ns->routerstatus_list, routerstatus_t *, rs) { node_t *node = node_get_or_create(rs->identity_digest); @@ -858,6 +898,8 @@ nodelist_free_all(void)
address_set_free(the_nodelist->node_addrs); the_nodelist->node_addrs = NULL; + addr_port_set_free(the_nodelist->reentry_set); + the_nodelist->reentry_set = NULL;
tor_free(the_nodelist); } diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index b762fb339c..ada0a640a1 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -35,7 +35,10 @@ node_t *nodelist_add_microdesc(microdesc_t *md); void nodelist_set_consensus(const networkstatus_t *ns); void nodelist_ensure_freshness(const networkstatus_t *ns); int nodelist_probably_contains_address(const tor_addr_t *addr); -void nodelist_add_addr_to_address_set(const tor_addr_t *addr); +bool nodelist_reentry_probably_contains(const tor_addr_t *addr, + uint16_t port); +void nodelist_add_addr_to_address_set(const tor_addr_t *addr, + uint16_t or_port, uint16_t dir_port);
void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md); void nodelist_remove_routerinfo(routerinfo_t *ri);