tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

connstats.c (9148B)


      1 /* Copyright (c) 2001 Matej Pfajfar.
      2 * Copyright (c) 2001-2004, Roger Dingledine.
      3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
      4 * Copyright (c) 2007-2021, The Tor Project, Inc. */
      5 /* See LICENSE for licensing information */
      6 
      7 /**
      8 * @file connstats.c
      9 * @brief Count bidirectional vs one-way connections.
     10 *
     11 * Connection statistics, use to track one-way and bidirectional connections.
     12 *
     13 * Note that this code counts concurrent connections in each
     14 * BIDI_INTERVAL-second interval, not total connections.  It can tell you what
     15 * fraction of connections are bidirectional at each time, not necessarily
     16 * what number are bidirectional.
     17 **/
     18 
     19 #include "orconfig.h"
     20 #include "core/or/or.h"
     21 #include "feature/stats/connstats.h"
     22 #include "app/config/config.h"
     23 
     24 /** Start of the current connection stats interval or 0 if we're not
     25 * collecting connection statistics. */
     26 static time_t start_of_conn_stats_interval;
     27 
     28 /** Initialize connection stats. */
     29 void
     30 conn_stats_init(time_t now)
     31 {
     32  start_of_conn_stats_interval = now;
     33 }
     34 
     35 /** Count connections on which we read and wrote less than this many bytes
     36 * as "below threshold." */
     37 #define BIDI_THRESHOLD 20480
     38 
     39 /** Count connections that we read or wrote at least this factor as many
     40 * bytes from/to than we wrote or read to/from as mostly reading or
     41 * writing. */
     42 #define BIDI_FACTOR 10
     43 
     44 /** Interval length in seconds for considering read and written bytes for
     45 * connection stats. */
     46 #define BIDI_INTERVAL 10
     47 
     48 /** Start of next BIDI_INTERVAL second interval. */
     49 static time_t bidi_next_interval = 0;
     50 
     51 /** A single grouped set of connection type counts. */
     52 typedef struct conn_counts_t {
     53  /** Number of connections that we read and wrote less than BIDI_THRESHOLD
     54   * bytes from/to in BIDI_INTERVAL seconds. */
     55  uint32_t below_threshold;
     56 
     57  /** Number of connections that we read at least BIDI_FACTOR times more
     58   * bytes from than we wrote to in BIDI_INTERVAL seconds. */
     59  uint32_t mostly_read;
     60 
     61  /** Number of connections that we wrote at least BIDI_FACTOR times more
     62   * bytes to than we read from in BIDI_INTERVAL seconds. */
     63  uint32_t mostly_written;
     64 
     65  /** Number of connections that we read and wrote at least BIDI_THRESHOLD
     66   * bytes from/to, but not BIDI_FACTOR times more in either direction in
     67   * BIDI_INTERVAL seconds. */
     68  uint32_t both_read_and_written;
     69 } conn_counts_t ;
     70 
     71 /** A collection of connection counts, over all OR connections. */
     72 static conn_counts_t counts;
     73 /** A collection of connection counts, over IPv6 OR connections only. */
     74 static conn_counts_t counts_ipv6;
     75 
     76 /** Entry in a map from connection ID to the number of read and written
     77 * bytes on this connection in a BIDI_INTERVAL second interval. */
     78 typedef struct bidi_map_entry_t {
     79  HT_ENTRY(bidi_map_entry_t) node;
     80  uint64_t conn_id; /**< Connection ID */
     81  size_t read; /**< Number of read bytes */
     82  size_t written; /**< Number of written bytes */
     83  bool is_ipv6; /**< True if this is an IPv6 connection */
     84 } bidi_map_entry_t;
     85 
     86 /** Map of OR connections together with the number of read and written
     87 * bytes in the current BIDI_INTERVAL second interval. */
     88 static HT_HEAD(bidimap, bidi_map_entry_t) bidi_map =
     89     HT_INITIALIZER();
     90 
     91 /** Hashtable helper: return true if @a a and @a b have the same key. */
     92 static int
     93 bidi_map_ent_eq(const bidi_map_entry_t *a, const bidi_map_entry_t *b)
     94 {
     95  return a->conn_id == b->conn_id;
     96 }
     97 
     98 /** Hashtable helper: compute a digest for the key of @a entry. */
     99 static unsigned
    100 bidi_map_ent_hash(const bidi_map_entry_t *entry)
    101 {
    102  return (unsigned) entry->conn_id;
    103 }
    104 
    105 HT_PROTOTYPE(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
    106             bidi_map_ent_eq);
    107 HT_GENERATE2(bidimap, bidi_map_entry_t, node, bidi_map_ent_hash,
    108             bidi_map_ent_eq, 0.6, tor_reallocarray_, tor_free_);
    109 
    110 /** Release all storage held in connstats.c */
    111 void
    112 conn_stats_free_all(void)
    113 {
    114  bidi_map_entry_t **ptr, **next, *ent;
    115  for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) {
    116    ent = *ptr;
    117    next = HT_NEXT_RMV(bidimap, &bidi_map, ptr);
    118    tor_free(ent);
    119  }
    120  HT_CLEAR(bidimap, &bidi_map);
    121 }
    122 
    123 /** Reset counters for conn statistics. */
    124 void
    125 conn_stats_reset(time_t now)
    126 {
    127  start_of_conn_stats_interval = now;
    128  memset(&counts, 0, sizeof(counts));
    129  memset(&counts_ipv6, 0, sizeof(counts_ipv6));
    130  conn_stats_free_all();
    131 }
    132 
    133 /** Stop collecting connection stats in a way that we can re-start doing
    134 * so in conn_stats_init(). */
    135 void
    136 conn_stats_terminate(void)
    137 {
    138  conn_stats_reset(0);
    139 }
    140 
    141 /**
    142 * Record a single entry @a ent in the counts structure @a cnt.
    143 */
    144 static void
    145 add_entry_to_count(conn_counts_t *cnt, const bidi_map_entry_t *ent)
    146 {
    147  if (ent->read + ent->written < BIDI_THRESHOLD)
    148    cnt->below_threshold++;
    149  else if (ent->read >= ent->written * BIDI_FACTOR)
    150    cnt->mostly_read++;
    151  else if (ent->written >= ent->read * BIDI_FACTOR)
    152    cnt->mostly_written++;
    153  else
    154    cnt->both_read_and_written++;
    155 }
    156 
    157 /**
    158 * Count all the connection information we've received during the current
    159 * period in 'bidimap', and store that information in the appropriate count
    160 * structures.
    161 **/
    162 static void
    163 collect_period_statistics(void)
    164 {
    165  bidi_map_entry_t **ptr, **next, *ent;
    166  for (ptr = HT_START(bidimap, &bidi_map); ptr; ptr = next) {
    167    ent = *ptr;
    168    add_entry_to_count(&counts, ent);
    169    if (ent->is_ipv6)
    170      add_entry_to_count(&counts_ipv6, ent);
    171    next = HT_NEXT_RMV(bidimap, &bidi_map, ptr);
    172    tor_free(ent);
    173  }
    174  log_info(LD_GENERAL, "%d below threshold, %d mostly read, "
    175           "%d mostly written, %d both read and written.",
    176           counts.below_threshold, counts.mostly_read, counts.mostly_written,
    177           counts.both_read_and_written);
    178 }
    179 
    180 /** We read <b>num_read</b> bytes and wrote <b>num_written</b> from/to OR
    181 * connection <b>conn_id</b> in second <b>when</b>. If this is the first
    182 * observation in a new interval, sum up the last observations. Add bytes
    183 * for this connection. */
    184 void
    185 conn_stats_note_or_conn_bytes(uint64_t conn_id, size_t num_read,
    186                              size_t num_written, time_t when,
    187                              bool is_ipv6)
    188 {
    189  if (!start_of_conn_stats_interval)
    190    return;
    191  /* Initialize */
    192  if (bidi_next_interval == 0)
    193    bidi_next_interval = when + BIDI_INTERVAL;
    194  /* Sum up last period's statistics */
    195  if (when >= bidi_next_interval) {
    196    collect_period_statistics();
    197    while (when >= bidi_next_interval)
    198      bidi_next_interval += BIDI_INTERVAL;
    199  }
    200  /* Add this connection's bytes. */
    201  if (num_read > 0 || num_written > 0) {
    202    bidi_map_entry_t *entry, lookup;
    203    lookup.conn_id = conn_id;
    204    entry = HT_FIND(bidimap, &bidi_map, &lookup);
    205    if (entry) {
    206      entry->written += num_written;
    207      entry->read += num_read;
    208      entry->is_ipv6 |= is_ipv6;
    209    } else {
    210      entry = tor_malloc_zero(sizeof(bidi_map_entry_t));
    211      entry->conn_id = conn_id;
    212      entry->written = num_written;
    213      entry->read = num_read;
    214      entry->is_ipv6 = is_ipv6;
    215      HT_INSERT(bidimap, &bidi_map, entry);
    216    }
    217  }
    218 }
    219 
    220 /** Return a newly allocated string containing the connection statistics
    221 * until <b>now</b>, or NULL if we're not collecting conn stats. Caller must
    222 * ensure start_of_conn_stats_interval is in the past. */
    223 char *
    224 conn_stats_format(time_t now)
    225 {
    226  char *result, written_at[ISO_TIME_LEN+1];
    227 
    228  if (!start_of_conn_stats_interval)
    229    return NULL; /* Not initialized. */
    230 
    231  tor_assert(now >= start_of_conn_stats_interval);
    232 
    233  format_iso_time(written_at, now);
    234  tor_asprintf(&result,
    235               "conn-bi-direct %s (%d s) "
    236                    "%"PRIu32",%"PRIu32",%"PRIu32",%"PRIu32"\n"
    237               "ipv6-conn-bi-direct %s (%d s) "
    238                    "%"PRIu32",%"PRIu32",%"PRIu32",%"PRIu32"\n",
    239               written_at,
    240               (unsigned) (now - start_of_conn_stats_interval),
    241               counts.below_threshold,
    242               counts.mostly_read,
    243               counts.mostly_written,
    244               counts.both_read_and_written,
    245               written_at,
    246               (unsigned) (now - start_of_conn_stats_interval),
    247               counts_ipv6.below_threshold,
    248               counts_ipv6.mostly_read,
    249               counts_ipv6.mostly_written,
    250               counts_ipv6.both_read_and_written);
    251 
    252  return result;
    253 }
    254 
    255 /** If 24 hours have passed since the beginning of the current conn stats
    256 * period, write conn stats to $DATADIR/stats/conn-stats (possibly
    257 * overwriting an existing file) and reset counters.  Return when we would
    258 * next want to write conn stats or 0 if we never want to write. */
    259 time_t
    260 conn_stats_save(time_t now)
    261 {
    262  char *str = NULL;
    263 
    264  if (!start_of_conn_stats_interval)
    265    return 0; /* Not initialized. */
    266  if (start_of_conn_stats_interval + WRITE_STATS_INTERVAL > now)
    267    goto done; /* Not ready to write */
    268 
    269  /* Generate history string. */
    270  str = conn_stats_format(now);
    271 
    272  /* Reset counters. */
    273  conn_stats_reset(now);
    274 
    275  /* Try to write to disk. */
    276  if (!check_or_create_data_subdir("stats")) {
    277    write_to_data_subdir("stats", "conn-stats", str, "connection statistics");
    278  }
    279 
    280 done:
    281  tor_free(str);
    282  return start_of_conn_stats_interval + WRITE_STATS_INTERVAL;
    283 }