1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
|
/*
* Copyright (c) 2009, Raphael Manfredi
*
*----------------------------------------------------------------------
* This file is part of gtk-gnutella.
*
* gtk-gnutella is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gtk-gnutella is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gtk-gnutella; if not, write to the Free Software
* Foundation, Inc.:
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*----------------------------------------------------------------------
*/
/**
* @ingroup dht
* @file
*
* Security token caching.
*
* Security tokens are used in the Gnutella DHT to make sure a node can
* issue a STORE on another node only if it recently contacted that node
* to perform a FIND_NODE operation. In effect, this prevents someone
* performing massive STORE operations blindly.
*
* Security tokens have a short lifespan and must be re-acquired often.
* How often depends on the vendors, but it is safe to assume that the
* minimum lifetime will be the replication period defined in Kademlia specs,
* i.e. one hour at least. It can be much longer, but there's no way to
* know for sure.
*
* When performing node lookup operations, security tokens are collected along
* the way, as FIND_NODE RPCs are issued to identify the k-closest nodes for
* a given KUID. Unfortunately, FIND_NODE operations are expensive in terms
* of network bandwidth and delays, and we need to perform some caching to
* speed things a little bit (well, significantly).
*
* For publishing operations, we can rely on the root caching to be able to
* quickly seed the lookup shortlist. But still we need to contact all the
* nodes to be able to STORE to them. Here comes the security token cache...
*
* Whenever a security token is collected from a node lookup, it is given
* to the security token cache where it will remain for some time. Whenever
* we initiate a node lookup in order to perform a STORE, we look whether
* we already know fresh security tokens for some of the nodes that we put in
* the shortlist. This gives us an indication that it is likely for the node
* to still be alive (it freshly replied) and it can prevent a FIND_NODE to
* be issued to that node -- that really is up to the lookup logic to decide
* depending on the replies for FIND_NODE it gets from other contacted nodes.
*
* The cache is made of one single DBMW database which maps a KUID target to
* its latest known security token.
*
* @author Raphael Manfredi
* @date 2009
*/
#include "common.h"
#include "tcache.h"
#include "lookup.h"
#include "token.h"
#include "if/gnet_property_priv.h"
#include "if/dht/kuid.h"
#include "if/core/settings.h" /* For settings_dht_db_dir() */
#include "core/gnet_stats.h"
#include "lib/atoms.h"
#include "lib/cq.h"
#include "lib/map.h"
#include "lib/dbmw.h"
#include "lib/dbstore.h"
#include "lib/misc.h"
#include "lib/tm.h"
#include "lib/stringify.h"
#include "lib/walloc.h"
#include "lib/override.h" /* Must be the last header included */
#define TOK_DB_CACHE_SIZE 4096 /**< Cached amount of tokens */
#define TOK_MAP_CACHE_SIZE 64 /**< Amount of SDBM pages to cache */
#define TOK_LIFE (5*3600) /**< Cached token lifetime in seconds */
#define TCACHE_PRUNE_PERIOD (3600 * 1000) /**< 1 hour in ms */
static time_delta_t token_life; /**< Lifetime of our cached tokens */
static cperiodic_t *tcache_prune_ev;
/**
* DBM wrapper to associate a target KUID its STORE security token.
*/
static dbmw_t *db_tokdata;
static char db_tcache_base[] = "dht_tokens";
static char db_tcache_what[] = "DHT security tokens";
/**
* Information about a target KUID that is stored to disk.
* The structure is serialized first, not written as-is.
*/
struct tokdata {
time_t last_update; /**< When we last updated the security token */
uint8 length; /**< Token length (0 if none) */
void *token; /**< Token binary data -- walloc()-ed */
};
/**
* Get tokdata from database, returning NULL if not found.
*/
static struct tokdata *
get_tokdata(const kuid_t *id)
{
struct tokdata *td;
td = dbmw_read(db_tokdata, id, NULL);
if (NULL == td) {
if (dbmw_has_ioerr(db_tokdata)) {
g_warning("DBMW \"%s\" I/O error, bad things could happen...",
dbmw_name(db_tokdata));
}
}
return td;
}
/**
* Delete known-to-be existing token data for specified KUID from database.
*/
static void
delete_tokdata(const kuid_t *id)
{
dbmw_delete(db_tokdata, id);
gnet_stats_count_general(GNR_DHT_CACHED_TOKENS_HELD, -1);
if (GNET_PROPERTY(dht_tcache_debug) > 2)
g_debug("DHT TCACHE security token from %s reclaimed",
kuid_to_hex_string(id));
}
/**
* Serialization routine for tokdata.
*/
static void
serialize_tokdata(pmsg_t *mb, const void *data)
{
const struct tokdata *td = data;
pmsg_write_time(mb, td->last_update);
pmsg_write_u8(mb, td->length);
pmsg_write(mb, td->token, td->length);
}
/**
* Deserialization routine for tokdata.
*/
static void
deserialize_tokdata(bstr_t *bs, void *valptr, size_t len)
{
struct tokdata *td = valptr;
g_assert(sizeof *td == len);
bstr_read_time(bs, &td->last_update);
bstr_read_u8(bs, &td->length);
if (td->length != 0) {
td->token = walloc(td->length);
bstr_read(bs, td->token, td->length);
} else {
td->token = NULL;
}
}
/**
* Free routine for tokdata, to release internally allocated memory, not
* the structure itself.
*/
static void
free_tokdata(void *valptr, size_t len)
{
struct tokdata *td = valptr;
g_assert(sizeof *td == len);
WFREE_NULL(td->token, td->length);
}
/**
* Map iterator to record security tokens in the database.
*/
static void
record_token(void *key, void *value, void *unused_u)
{
kuid_t *id = key;
lookup_token_t *ltok = value;
struct tokdata td;
(void) unused_u;
td.last_update = ltok->retrieved;
td.length = ltok->token->length;
td.token = td.length ? wcopy(ltok->token->v, td.length) : NULL;
if (GNET_PROPERTY(dht_tcache_debug) > 4) {
char buf[80];
bin_to_hex_buf(td.token, td.length, buf, sizeof buf);
g_debug("DHT TCACHE adding security token for %s: %u-byte \"%s\"",
kuid_to_hex_string(id), td.length, buf);
}
/*
* Data is put in the DBMW cache and the dynamically allocated token
* will be freed via free_tokdata() when the cached entry is released.
*/
if (!dbmw_exists(db_tokdata, id->v))
gnet_stats_count_general(GNR_DHT_CACHED_TOKENS_HELD, +1);
dbmw_write(db_tokdata, id->v, &td, sizeof td);
}
/**
* Record security tokens collected during a node lookup from the supplied map.
*
* @param tokens a map containing KUID => lookup_token_t
*/
void
tcache_record(map_t *tokens)
{
map_foreach(tokens, record_token, NULL);
}
/**
* Retrieve cached security token for a given KUID.
*
* @param id the KUID for which we'd like the security token
* @param len_ptr where the length of the security token is written
* @param tok_ptr where the address of the security token is written
* @param time_ptr where the last update time of token is writen
*
* @return TRUE if we found a token, with len_ptr and tok_ptr filled with
* the information about the length and the token pointer. Information is
* returned from a static memory buffer so it must be perused immediately.
*/
bool
tcache_get(const kuid_t *id,
uint8 *len_ptr, const void **tok_ptr, time_t *time_ptr)
{
struct tokdata *td;
g_assert(id != NULL);
td = get_tokdata(id);
if (NULL == td)
return FALSE;
if (delta_time(tm_time(), td->last_update) > token_life) {
delete_tokdata(id);
return FALSE;
}
if (len_ptr != NULL) *len_ptr = td->length;
if (tok_ptr != NULL) *tok_ptr = td->token;
if (time_ptr != NULL) *time_ptr = td->last_update;
if (GNET_PROPERTY(dht_tcache_debug) > 4) {
char buf[80];
bin_to_hex_buf(td->token, td->length, buf, sizeof buf);
g_debug("DHT TCACHE security token for %s is %u-byte \"%s\" (%s)",
kuid_to_hex_string(id), td->length, buf,
compact_time(delta_time(tm_time(), td->last_update)));
}
gnet_stats_count_general(GNR_DHT_CACHED_TOKENS_HITS, 1);
return TRUE;
}
/**
* Remove cached token for specified KUID.
*
* @return TRUE if entry existed
*/
bool
tcache_remove(const kuid_t *id)
{
if (!dbmw_exists(db_tokdata, id))
return FALSE;
delete_tokdata(id);
return TRUE;
}
/**
* DBMW foreach iterator to remove old entries.
* @return TRUE if entry must be deleted.
*/
static bool
tk_prune_old(void *key, void *value, size_t u_len, void *u_data)
{
const kuid_t *id = key;
const struct tokdata *td = value;
time_delta_t d;
(void) u_len;
(void) u_data;
d = delta_time(tm_time(), td->last_update);
if (GNET_PROPERTY(dht_tcache_debug) > 2 && d > token_life) {
g_debug("DHT TCACHE security token from %s expired",
kuid_to_hex_string(id));
}
return d > token_life;
}
/**
* Prune the database, removing expired tokens.
*/
static void
tcache_prune_old(void)
{
if (GNET_PROPERTY(dht_tcache_debug)) {
g_debug("DHT TCACHE pruning expired tokens (%zu)",
dbmw_count(db_tokdata));
}
dbmw_foreach_remove(db_tokdata, tk_prune_old, NULL);
gnet_stats_set_general(GNR_DHT_CACHED_TOKENS_HELD, dbmw_count(db_tokdata));
if (GNET_PROPERTY(dht_tcache_debug)) {
g_debug("DHT TCACHE pruned expired tokens (%zu remaining)",
dbmw_count(db_tokdata));
}
}
/**
* Callout queue periodic event to expire old entries.
*/
static bool
tcache_periodic_prune(void *unused_obj)
{
(void) unused_obj;
tcache_prune_old();
return TRUE; /* Keep calling */
}
/**
* Initialize security token caching.
*/
G_GNUC_COLD void
tcache_init(void)
{
dbstore_kv_t kv = { KUID_RAW_SIZE, NULL, sizeof(struct tokdata),
sizeof(struct tokdata) + MAX_INT_VAL(uint8) };
dbstore_packing_t packing =
{ serialize_tokdata, deserialize_tokdata, free_tokdata };
g_assert(NULL == db_tokdata);
g_assert(NULL == tcache_prune_ev);
/* Legacy: remove after 0.97 -- RAM, 2011-05-03 */
dbstore_move(settings_config_dir(), settings_dht_db_dir(), db_tcache_base);
db_tokdata = dbstore_create(db_tcache_what, settings_dht_db_dir(),
db_tcache_base, kv, packing, TOK_DB_CACHE_SIZE, kuid_hash, kuid_eq,
GNET_PROPERTY(dht_storage_in_memory));
dbmw_set_map_cache(db_tokdata, TOK_MAP_CACHE_SIZE);
token_life = MIN(TOK_LIFE, token_lifetime());
if (GNET_PROPERTY(dht_tcache_debug))
g_debug("DHT cached token lifetime set to %u secs",
(unsigned) token_life);
tcache_prune_ev = cq_periodic_main_add(TCACHE_PRUNE_PERIOD,
tcache_periodic_prune, NULL);
}
/**
* Close security token caching.
*/
void
tcache_close(void)
{
dbstore_delete(db_tokdata);
db_tokdata = NULL;
cq_periodic_remove(&tcache_prune_ev);
}
/* vi: set ts=4 sw=4 cindent: */
|