tor/src/feature/nodelist/nodefamily.c

417 lines
12 KiB
C

/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2020, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* \file nodefamily.c
* \brief Code to manipulate encoded, reference-counted node families. We
* use these tricks to save space, since these families would otherwise
* require a large number of tiny allocations.
**/
#include "core/or/or.h"
#include "feature/nodelist/nickname.h"
#include "feature/nodelist/nodefamily.h"
#include "feature/nodelist/nodefamily_st.h"
#include "feature/nodelist/nodelist.h"
#include "feature/relay/router.h"
#include "feature/nodelist/routerlist.h"
#include "ht.h"
#include "siphash.h"
#include "lib/container/smartlist.h"
#include "lib/ctime/di_ops.h"
#include "lib/defs/digest_sizes.h"
#include "lib/log/util_bug.h"
#include <stdlib.h>
#include <string.h>
/**
* Allocate and return a blank node family with space to hold <b>n_members</b>
* members.
*/
static nodefamily_t *
nodefamily_alloc(int n_members)
{
size_t alloc_len = offsetof(nodefamily_t, family_members) +
NODEFAMILY_ARRAY_SIZE(n_members);
nodefamily_t *nf = tor_malloc_zero(alloc_len);
nf->n_members = n_members;
return nf;
}
/**
* Hashtable hash implementation.
*/
static inline unsigned int
nodefamily_hash(const nodefamily_t *nf)
{
return (unsigned) siphash24g(nf->family_members,
NODEFAMILY_ARRAY_SIZE(nf->n_members));
}
/**
* Hashtable equality implementation.
*/
static inline unsigned int
nodefamily_eq(const nodefamily_t *a, const nodefamily_t *b)
{
return (a->n_members == b->n_members) &&
fast_memeq(a->family_members, b->family_members,
NODEFAMILY_ARRAY_SIZE(a->n_members));
}
static HT_HEAD(nodefamily_map, nodefamily_t) the_node_families
= HT_INITIALIZER();
HT_PROTOTYPE(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash,
nodefamily_eq)
HT_GENERATE2(nodefamily_map, nodefamily_t, ht_ent, nodefamily_hash,
node_family_eq, 0.6, tor_reallocarray_, tor_free_)
/**
* Parse the family declaration in <b>s</b>, returning the canonical
* <b>nodefamily_t</b> for its members. Return NULL on error.
*
* If <b>rsa_id_self</b> is provided, it is a DIGEST_LEN-byte digest
* for the router that declared this family: insert it into the
* family declaration if it is not there already.
*
* If NF_WARN_MALFORMED is set in <b>flags</b>, warn about any
* elements that we can't parse. (By default, we log at info.)
*
* If NF_REJECT_MALFORMED is set in <b>flags</b>, treat any unparseable
* elements as an error. (By default, we simply omit them.)
**/
nodefamily_t *
nodefamily_parse(const char *s, const uint8_t *rsa_id_self,
unsigned flags)
{
smartlist_t *sl = smartlist_new();
smartlist_split_string(sl, s, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
nodefamily_t *result = nodefamily_from_members(sl, rsa_id_self, flags, NULL);
SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
smartlist_free(sl);
return result;
}
/**
* Canonicalize the family list <b>s</b>, returning a newly allocated string.
*
* The canonicalization rules are fully specified in dir-spec.txt, but,
* briefly: $hexid entries are put in caps, $hexid[=~]foo entries are
* truncated, nicknames are put into lowercase, unrecognized entries are left
* alone, and everything is sorted.
**/
char *
nodefamily_canonicalize(const char *s, const uint8_t *rsa_id_self,
unsigned flags)
{
smartlist_t *sl = smartlist_new();
smartlist_t *result_members = smartlist_new();
smartlist_split_string(sl, s, NULL, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
nodefamily_t *nf = nodefamily_from_members(sl, rsa_id_self, flags,
result_members);
char *formatted = nodefamily_format(nf);
smartlist_split_string(result_members, formatted, NULL,
SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
smartlist_sort_strings(result_members);
char *combined = smartlist_join_strings(result_members, " ", 0, NULL);
nodefamily_free(nf);
SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
smartlist_free(sl);
SMARTLIST_FOREACH(result_members, char *, cp, tor_free(cp));
smartlist_free(result_members);
tor_free(formatted);
return combined;
}
/**
* qsort helper for encoded nodefamily elements.
**/
static int
compare_members(const void *a, const void *b)
{
return fast_memcmp(a, b, NODEFAMILY_MEMBER_LEN);
}
/**
* Parse the member strings in <b>members</b>, returning their canonical
* <b>nodefamily_t</b>. Return NULL on error.
*
* If <b>rsa_id_self</b> is provided, it is a DIGEST_LEN-byte digest
* for the router that declared this family: insert it into the
* family declaration if it is not there already.
*
* The <b>flags</b> element is interpreted as in nodefamily_parse().
*
* If <b>unrecognized</b> is provided, fill it copies of any unrecognized
* members. (Note that malformed $hexids are not considered unrecognized.)
**/
nodefamily_t *
nodefamily_from_members(const smartlist_t *members,
const uint8_t *rsa_id_self,
unsigned flags,
smartlist_t *unrecognized_out)
{
const int n_self = rsa_id_self ? 1 : 0;
int n_bad_elements = 0;
int n_members = smartlist_len(members) + n_self;
nodefamily_t *tmp = nodefamily_alloc(n_members);
uint8_t *ptr = NODEFAMILY_MEMBER_PTR(tmp, 0);
SMARTLIST_FOREACH_BEGIN(members, const char *, cp) {
bool bad_element = true;
if (is_legal_nickname(cp)) {
ptr[0] = NODEFAMILY_BY_NICKNAME;
tor_assert(strlen(cp) < DIGEST_LEN); // guaranteed by is_legal_nickname
memcpy(ptr+1, cp, strlen(cp));
tor_strlower((char*) ptr+1);
bad_element = false;
} else if (is_legal_hexdigest(cp)) {
char digest_buf[DIGEST_LEN];
char nn_buf[MAX_NICKNAME_LEN+1];
char nn_char=0;
if (hex_digest_nickname_decode(cp, digest_buf, &nn_char, nn_buf)==0) {
bad_element = false;
ptr[0] = NODEFAMILY_BY_RSA_ID;
memcpy(ptr+1, digest_buf, DIGEST_LEN);
}
} else {
if (unrecognized_out)
smartlist_add_strdup(unrecognized_out, cp);
}
if (bad_element) {
const int severity = (flags & NF_WARN_MALFORMED) ? LOG_WARN : LOG_INFO;
log_fn(severity, LD_GENERAL,
"Bad element %s while parsing a node family.",
escaped(cp));
++n_bad_elements;
} else {
ptr += NODEFAMILY_MEMBER_LEN;
}
} SMARTLIST_FOREACH_END(cp);
if (n_bad_elements && (flags & NF_REJECT_MALFORMED))
goto err;
if (rsa_id_self) {
/* Add self. */
ptr[0] = NODEFAMILY_BY_RSA_ID;
memcpy(ptr+1, rsa_id_self, DIGEST_LEN);
}
n_members -= n_bad_elements;
/* Sort tmp into canonical order. */
qsort(tmp->family_members, n_members, NODEFAMILY_MEMBER_LEN,
compare_members);
/* Remove duplicates. */
int i;
for (i = 0; i < n_members-1; ++i) {
uint8_t *thisptr = NODEFAMILY_MEMBER_PTR(tmp, i);
uint8_t *nextptr = NODEFAMILY_MEMBER_PTR(tmp, i+1);
if (fast_memeq(thisptr, nextptr, NODEFAMILY_MEMBER_LEN)) {
memmove(thisptr, nextptr, (n_members-i-1)*NODEFAMILY_MEMBER_LEN);
--n_members;
--i;
}
}
int n_members_alloc = tmp->n_members;
tmp->n_members = n_members;
/* See if we already allocated this family. */
nodefamily_t *found = HT_FIND(nodefamily_map, &the_node_families, tmp);
if (found) {
/* If we did, great: incref it and return it. */
++found->refcnt;
tor_free(tmp);
return found;
} else {
/* If not, insert it into the hashtable. */
if (n_members_alloc != n_members) {
/* Compact the family if needed */
nodefamily_t *tmp2 = nodefamily_alloc(n_members);
memcpy(tmp2->family_members, tmp->family_members,
n_members * NODEFAMILY_MEMBER_LEN);
tor_free(tmp);
tmp = tmp2;
}
tmp->refcnt = 1;
HT_INSERT(nodefamily_map, &the_node_families, tmp);
return tmp;
}
err:
tor_free(tmp);
return NULL;
}
/**
* Drop our reference to <b>family</b>, freeing it if there are no more
* references.
*/
void
nodefamily_free_(nodefamily_t *family)
{
if (family == NULL)
return;
--family->refcnt;
if (family->refcnt == 0) {
HT_REMOVE(nodefamily_map, &the_node_families, family);
tor_free(family);
}
}
/**
* Return true iff <b>family</b> contains the SHA1 RSA1024 identity
* <b>rsa_id</b>.
*/
bool
nodefamily_contains_rsa_id(const nodefamily_t *family,
const uint8_t *rsa_id)
{
if (family == NULL)
return false;
unsigned i;
for (i = 0; i < family->n_members; ++i) {
const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i);
if (ptr[0] == NODEFAMILY_BY_RSA_ID &&
fast_memeq(ptr+1, rsa_id, DIGEST_LEN)) {
return true;
}
}
return false;
}
/**
* Return true iff <b>family</b> contains the nickname <b>name</b>.
*/
bool
nodefamily_contains_nickname(const nodefamily_t *family,
const char *name)
{
if (family == NULL)
return false;
unsigned i;
for (i = 0; i < family->n_members; ++i) {
const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i);
// note that the strcasecmp() is safe because there is always at least one
// NUL in the encoded nickname, because all legal nicknames are less than
// DIGEST_LEN bytes long.
if (ptr[0] == NODEFAMILY_BY_NICKNAME && !strcasecmp((char*)ptr+1, name)) {
return true;
}
}
return false;
}
/**
* Return true if <b>family</b> contains the nickname or the RSA ID for
* <b>node</b>
**/
bool
nodefamily_contains_node(const nodefamily_t *family,
const node_t *node)
{
return
nodefamily_contains_nickname(family, node_get_nickname(node))
||
nodefamily_contains_rsa_id(family, node_get_rsa_id_digest(node));
}
/**
* Look up every entry in <b>family</b>, and add add the corresponding
* node_t to <b>out</b>.
**/
void
nodefamily_add_nodes_to_smartlist(const nodefamily_t *family,
smartlist_t *out)
{
if (!family)
return;
unsigned i;
for (i = 0; i < family->n_members; ++i) {
const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i);
const node_t *node = NULL;
switch (ptr[0]) {
case NODEFAMILY_BY_NICKNAME:
node = node_get_by_nickname((char*)ptr+1, NNF_NO_WARN_UNNAMED);
break;
case NODEFAMILY_BY_RSA_ID:
node = node_get_by_id((char*)ptr+1);
break;
default:
/* LCOV_EXCL_START */
tor_assert_nonfatal_unreached();
break;
/* LCOV_EXCL_STOP */
}
if (node)
smartlist_add(out, (void *)node);
}
}
/**
* Encode <b>family</b> as a space-separated string.
*/
char *
nodefamily_format(const nodefamily_t *family)
{
if (!family)
return tor_strdup("");
unsigned i;
smartlist_t *sl = smartlist_new();
for (i = 0; i < family->n_members; ++i) {
const uint8_t *ptr = NODEFAMILY_MEMBER_PTR(family, i);
switch (ptr[0]) {
case NODEFAMILY_BY_NICKNAME:
smartlist_add_strdup(sl, (char*)ptr+1);
break;
case NODEFAMILY_BY_RSA_ID: {
char buf[HEX_DIGEST_LEN+2];
buf[0]='$';
base16_encode(buf+1, sizeof(buf)-1, (char*)ptr+1, DIGEST_LEN);
tor_strupper(buf);
smartlist_add_strdup(sl, buf);
break;
}
default:
/* LCOV_EXCL_START */
tor_assert_nonfatal_unreached();
break;
/* LCOV_EXCL_STOP */
}
}
char *result = smartlist_join_strings(sl, " ", 0, NULL);
SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
smartlist_free(sl);
return result;
}
/**
* Free all storage held in the nodefamily map.
**/
void
nodefamily_free_all(void)
{
HT_CLEAR(nodefamily_map, &the_node_families);
}