|  | @@ -23,7 +23,17 @@ typedef struct geoip_entry_t {
 | 
	
		
			
				|  |  |    intptr_t country; /**< An index into geoip_countries */
 | 
	
		
			
				|  |  |  } geoip_entry_t;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/** A list of lowercased two-letter country codes. */
 | 
	
		
			
				|  |  | +/** DOCDOC */
 | 
	
		
			
				|  |  | +#define REQUEST_HIST_LEN 3
 | 
	
		
			
				|  |  | +#define REQUEST_HIST_PERIOD (8*60*60)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +typedef struct geoip_country_t {
 | 
	
		
			
				|  |  | +  char countrycode[3];
 | 
	
		
			
				|  |  | +  uint32_t n_v2_ns_requests[REQUEST_HIST_LEN];
 | 
	
		
			
				|  |  | +  uint32_t n_v3_ns_requests[REQUEST_HIST_LEN];
 | 
	
		
			
				|  |  | +} geoip_country_t;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +/** A list of geoip_country_t */
 | 
	
		
			
				|  |  |  static smartlist_t *geoip_countries = NULL;
 | 
	
		
			
				|  |  |  /** A map from lowercased country codes to their position in geoip_countries.
 | 
	
		
			
				|  |  |   * The index is encoded in the pointer, and 1 is added so that NULL can mean
 | 
	
	
		
			
				|  | @@ -48,15 +58,19 @@ geoip_add_entry(uint32_t low, uint32_t high, const char *country)
 | 
	
		
			
				|  |  |    _idxplus1 = strmap_get_lc(country_idxplus1_by_lc_code, country);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    if (!_idxplus1) {
 | 
	
		
			
				|  |  | -    char *c = tor_strdup(country);
 | 
	
		
			
				|  |  | -    tor_strlower(c);
 | 
	
		
			
				|  |  | +    geoip_country_t *c = tor_malloc_zero(sizeof(geoip_country_t));
 | 
	
		
			
				|  |  | +    strlcpy(c->countrycode, country, sizeof(c->countrycode));
 | 
	
		
			
				|  |  | +    tor_strlower(c->countrycode);
 | 
	
		
			
				|  |  |      smartlist_add(geoip_countries, c);
 | 
	
		
			
				|  |  |      idx = smartlist_len(geoip_countries) - 1;
 | 
	
		
			
				|  |  |      strmap_set_lc(country_idxplus1_by_lc_code, country, (void*)(idx+1));
 | 
	
		
			
				|  |  |    } else {
 | 
	
		
			
				|  |  |      idx = ((uintptr_t)_idxplus1)-1;
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  tor_assert(!strcasecmp(smartlist_get(geoip_countries, idx), country));
 | 
	
		
			
				|  |  | +  {
 | 
	
		
			
				|  |  | +    geoip_country_t *c = smartlist_get(geoip_countries, idx);
 | 
	
		
			
				|  |  | +    tor_assert(!strcasecmp(c->countrycode, country));
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |    ent = tor_malloc_zero(sizeof(geoip_entry_t));
 | 
	
		
			
				|  |  |    ent->ip_low = low;
 | 
	
		
			
				|  |  |    ent->ip_high = high;
 | 
	
	
		
			
				|  | @@ -198,9 +212,10 @@ geoip_get_n_countries(void)
 | 
	
		
			
				|  |  |  const char *
 | 
	
		
			
				|  |  |  geoip_get_country_name(int num)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -  if (geoip_countries && num >= 0 && num < smartlist_len(geoip_countries))
 | 
	
		
			
				|  |  | -    return smartlist_get(geoip_countries, num);
 | 
	
		
			
				|  |  | -  else
 | 
	
		
			
				|  |  | +  if (geoip_countries && num >= 0 && num < smartlist_len(geoip_countries)) {
 | 
	
		
			
				|  |  | +    geoip_country_t *c = smartlist_get(geoip_countries, num);
 | 
	
		
			
				|  |  | +    return c->countrycode;
 | 
	
		
			
				|  |  | +  } else
 | 
	
		
			
				|  |  |      return "??";
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -226,9 +241,13 @@ typedef struct clientmap_entry_t {
 | 
	
		
			
				|  |  |  /** Map from client IP address to last time seen. */
 | 
	
		
			
				|  |  |  static HT_HEAD(clientmap, clientmap_entry_t) client_history =
 | 
	
		
			
				|  |  |       HT_INITIALIZER();
 | 
	
		
			
				|  |  | -/** Time at which we started tracking client history. */
 | 
	
		
			
				|  |  | +/** Time at which we started tracking client IP history. */
 | 
	
		
			
				|  |  |  static time_t client_history_starts = 0;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/** DOCDOC */
 | 
	
		
			
				|  |  | +static time_t current_request_period_starts = 0;
 | 
	
		
			
				|  |  | +static int n_old_request_periods = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /** Hashtable helper: compute a hash of a clientmap_entry_t. */
 | 
	
		
			
				|  |  |  static INLINE unsigned
 | 
	
		
			
				|  |  |  clientmap_entry_hash(const clientmap_entry_t *a)
 | 
	
	
		
			
				|  | @@ -268,8 +287,23 @@ geoip_note_client_seen(geoip_client_action_t action,
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  /* DOCDOC */
 | 
	
		
			
				|  |  | +  while (current_request_period_starts + REQUEST_HIST_PERIOD >= now) {
 | 
	
		
			
				|  |  | +    SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
 | 
	
		
			
				|  |  | +        memmove(&c->n_v2_ns_requests[0], &c->n_v2_ns_requests[1],
 | 
	
		
			
				|  |  | +                sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
 | 
	
		
			
				|  |  | +        memmove(&c->n_v3_ns_requests[0], &c->n_v3_ns_requests[1],
 | 
	
		
			
				|  |  | +                sizeof(uint32_t)*(REQUEST_HIST_LEN-1));
 | 
	
		
			
				|  |  | +        c->n_v2_ns_requests[REQUEST_HIST_LEN-1] = 0;
 | 
	
		
			
				|  |  | +        c->n_v3_ns_requests[REQUEST_HIST_LEN-1] = 0;
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +    current_request_period_starts += REQUEST_HIST_PERIOD;
 | 
	
		
			
				|  |  | +    if (n_old_request_periods < REQUEST_HIST_PERIOD-1)
 | 
	
		
			
				|  |  | +      ++n_old_request_periods;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    /* We use the low 3 bits of the time to encode the action. Since we're
 | 
	
		
			
				|  |  | -   * potentially remembering times of clients, we don't want to make
 | 
	
		
			
				|  |  | +   * potentially remembering tons of clients, we don't want to make
 | 
	
		
			
				|  |  |     * clientmap_entry_t larger than it has to be. */
 | 
	
		
			
				|  |  |    now = (now & ~ACTION_MASK) | (((int)action) & ACTION_MASK);
 | 
	
		
			
				|  |  |    lookup.ipaddr = addr;
 | 
	
	
		
			
				|  | @@ -282,8 +316,23 @@ geoip_note_client_seen(geoip_client_action_t action,
 | 
	
		
			
				|  |  |      ent->last_seen = now;
 | 
	
		
			
				|  |  |      HT_INSERT(clientmap, &client_history, ent);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | -  if (!client_history_starts)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (action == GEOIP_CLIENT_NETWORKSTATUS ||
 | 
	
		
			
				|  |  | +      action == GEOIP_CLIENT_NETWORKSTATUS_V2) {
 | 
	
		
			
				|  |  | +    int country_idx = geoip_get_country_by_ip(addr);
 | 
	
		
			
				|  |  | +    if (country_idx >= 0 && country_idx < smartlist_len(geoip_countries)) {
 | 
	
		
			
				|  |  | +      geoip_country_t *country = smartlist_get(geoip_countries, country_idx);
 | 
	
		
			
				|  |  | +      if (action == GEOIP_CLIENT_NETWORKSTATUS)
 | 
	
		
			
				|  |  | +        ++country->n_v3_ns_requests[REQUEST_HIST_LEN-1];
 | 
	
		
			
				|  |  | +      else
 | 
	
		
			
				|  |  | +        ++country->n_v2_ns_requests[REQUEST_HIST_LEN-1];
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (!client_history_starts) {
 | 
	
		
			
				|  |  |      client_history_starts = now;
 | 
	
		
			
				|  |  | +    current_request_period_starts = now;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /** HT_FOREACH helper: remove a clientmap_entry_t from the hashtable if it's
 | 
	
	
		
			
				|  | @@ -350,6 +399,9 @@ _c_hist_compare(const void **_a, const void **_b)
 | 
	
		
			
				|  |  |      return strcmp(a->country, b->country);
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +/*DOCDOC*/
 | 
	
		
			
				|  |  | +#define GEOIP_MIN_OBSERVATION_TIME (12*60*60)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  /** Return a newly allocated comma-separated string containing entries for all
 | 
	
		
			
				|  |  |   * the countries from which we've seen enough clients connect. The entry
 | 
	
		
			
				|  |  |   * format is cc=num where num is the number of IPs we've seen connecting from
 | 
	
	
		
			
				|  | @@ -361,7 +413,7 @@ geoip_get_client_history(time_t now, geoip_client_action_t action)
 | 
	
		
			
				|  |  |    char *result = NULL;
 | 
	
		
			
				|  |  |    if (!geoip_is_loaded())
 | 
	
		
			
				|  |  |      return NULL;
 | 
	
		
			
				|  |  | -  if (client_history_starts < (now - 12*60*60)) {
 | 
	
		
			
				|  |  | +  if (client_history_starts < (now - GEOIP_MIN_OBSERVATION_TIME)) {
 | 
	
		
			
				|  |  |      char buf[32];
 | 
	
		
			
				|  |  |      smartlist_t *chunks = NULL;
 | 
	
		
			
				|  |  |      smartlist_t *entries = NULL;
 | 
	
	
		
			
				|  | @@ -435,11 +487,43 @@ geoip_get_client_history(time_t now, geoip_client_action_t action)
 | 
	
		
			
				|  |  |    return result;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  /**DOCDOC*/
 | 
	
		
			
				|  |  | +char *
 | 
	
		
			
				|  |  | +geoip_get_request_history(time_t now, geoip_client_action_t action)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +  smartlist_t *entries;
 | 
	
		
			
				|  |  | +  char *result;
 | 
	
		
			
				|  |  | +  if (client_history_starts >= (now - GEOIP_MIN_OBSERVATION_TIME))
 | 
	
		
			
				|  |  | +    return NULL;
 | 
	
		
			
				|  |  | +  if (action != GEOIP_CLIENT_NETWORKSTATUS &&
 | 
	
		
			
				|  |  | +      action != GEOIP_CLIENT_NETWORKSTATUS_V2)
 | 
	
		
			
				|  |  | +    return NULL;
 | 
	
		
			
				|  |  | +  if (!geoip_countries)
 | 
	
		
			
				|  |  | +    return NULL;
 | 
	
		
			
				|  |  | +  entries = smartlist_create();
 | 
	
		
			
				|  |  | +  SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, {
 | 
	
		
			
				|  |  | +      uint32_t *n = (action == GEOIP_CLIENT_NETWORKSTATUS)
 | 
	
		
			
				|  |  | +        ? c->n_v3_ns_requests : c->n_v2_ns_requests;
 | 
	
		
			
				|  |  | +      uint32_t tot = 0;
 | 
	
		
			
				|  |  | +      int i;
 | 
	
		
			
				|  |  | +      char buf[32];
 | 
	
		
			
				|  |  | +      for (i=0; i < REQUEST_HIST_LEN; ++i)
 | 
	
		
			
				|  |  | +        tot += n[i];
 | 
	
		
			
				|  |  | +      tor_snprintf(buf, sizeof(buf), "%s=%ld", c->countrycode, (long)n);
 | 
	
		
			
				|  |  | +      smartlist_add(entries, tor_strdup(buf));
 | 
	
		
			
				|  |  | +  });
 | 
	
		
			
				|  |  | +  smartlist_sort_strings(entries);
 | 
	
		
			
				|  |  | +  result = smartlist_join_strings(entries, ",", 0, NULL);
 | 
	
		
			
				|  |  | +  SMARTLIST_FOREACH(entries, char *, cp, tor_free(cp));
 | 
	
		
			
				|  |  | +  return result;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  void
 | 
	
		
			
				|  |  |  dump_geoip_stats(void)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  #ifdef ENABLE_GEOIP_STATS
 | 
	
		
			
				|  |  |    time_t now = time(NULL);
 | 
	
		
			
				|  |  | +  time_t request_start;
 | 
	
		
			
				|  |  |    char *filename = get_datadir_fname("geoip-stats");
 | 
	
		
			
				|  |  |    char *data_v2 = NULL, *data_v3 = NULL;
 | 
	
		
			
				|  |  |    char since[ISO_TIME_LEN+1], written[ISO_TIME_LEN+1];
 | 
	
	
		
			
				|  | @@ -450,13 +534,25 @@ dump_geoip_stats(void)
 | 
	
		
			
				|  |  |    data_v3 = geoip_get_client_history(now, GEOIP_CLIENT_NETWORKSTATUS);
 | 
	
		
			
				|  |  |    format_iso_time(since, geoip_get_history_start());
 | 
	
		
			
				|  |  |    format_iso_time(written, now);
 | 
	
		
			
				|  |  | -  if (!data_v2 || !data_v3)
 | 
	
		
			
				|  |  | -    goto done;
 | 
	
		
			
				|  |  | -  out = start_writing_to_stdio_file(filename, 0, 0600, &open_file);
 | 
	
		
			
				|  |  | +  out = start_writing_to_stdio_file(filename, OPEN_FLAGS_REPLACE,
 | 
	
		
			
				|  |  | +                                    0600, &open_file);
 | 
	
		
			
				|  |  |    if (!out)
 | 
	
		
			
				|  |  |      goto done;
 | 
	
		
			
				|  |  | -  if (fprintf(out, "written %s\nstarted-at %s\nns %s\nns-v2%s\n",
 | 
	
		
			
				|  |  | -              written, since, data_v3, data_v2) < 0)
 | 
	
		
			
				|  |  | +  if (fprintf(out, "written %s\nstarted-at %s\nns-ips %s\nns-v2-ips%s\n",
 | 
	
		
			
				|  |  | +              written, since,
 | 
	
		
			
				|  |  | +              data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0)
 | 
	
		
			
				|  |  | +    goto done;
 | 
	
		
			
				|  |  | +  tor_free(data_v2);
 | 
	
		
			
				|  |  | +  tor_free(data_v3);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  request_start = current_request_period_starts -
 | 
	
		
			
				|  |  | +    (n_old_request_periods * REQUEST_HIST_PERIOD);
 | 
	
		
			
				|  |  | +  format_iso_time(since, request_start);
 | 
	
		
			
				|  |  | +  data_v2 = geoip_get_request_history(now, GEOIP_CLIENT_NETWORKSTATUS_V2);
 | 
	
		
			
				|  |  | +  data_v3 = geoip_get_request_history(now, GEOIP_CLIENT_NETWORKSTATUS);
 | 
	
		
			
				|  |  | +  if (fprintf(out, "requests-start %s\nn-ns-reqs %s\nn-v2-ns_reqs%s\n",
 | 
	
		
			
				|  |  | +              since,
 | 
	
		
			
				|  |  | +              data_v3 ? data_v3 : "", data_v2 ? data_v2 : "") < 0)
 | 
	
		
			
				|  |  |      goto done;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    finish_writing_to_file(open_file);
 | 
	
	
		
			
				|  | @@ -495,7 +591,7 @@ static void
 | 
	
		
			
				|  |  |  clear_geoip_db(void)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |    if (geoip_countries) {
 | 
	
		
			
				|  |  | -    SMARTLIST_FOREACH(geoip_countries, char *, cp, tor_free(cp));
 | 
	
		
			
				|  |  | +    SMARTLIST_FOREACH(geoip_countries, geoip_country_t *, c, tor_free(c));
 | 
	
		
			
				|  |  |      smartlist_free(geoip_countries);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |    if (country_idxplus1_by_lc_code)
 |