File: tcache.c

package info (click to toggle)
gtk-gnutella 0.98.3-1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 50,104 kB
  • sloc: ansic: 269,840; sh: 10,787; xml: 1,347; yacc: 819; perl: 199; makefile: 100; python: 23; sed: 16
file content (405 lines) | stat: -rw-r--r-- 11,076 bytes parent folder | download
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: */