view src/gt_netorg.c @ 0:d39e1d0d75b6

initial add
author paulo@hit-nxdomain.opendns.com
date Sat, 20 Feb 2010 21:18:28 -0800
parents
children
line source
1 /*
2 * $Id: gt_netorg.c,v 1.47 2005/01/04 15:00:51 mkern Exp $
3 *
4 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net)
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 */
17 #include "gt_gnutella.h"
19 #include "gt_node.h"
20 #include "gt_node_list.h"
21 #include "gt_netorg.h"
23 #include "gt_connect.h"
24 #include "gt_accept.h"
26 #include "gt_packet.h"
28 #include "gt_node_cache.h"
29 #include "gt_web_cache.h"
31 /*****************************************************************************/
33 /* how often we check the network's condition */
34 #define MAINTAIN_INTERVAL (10 * SECONDS)
36 /* how often to check to disconnect idle nodes */
37 #define IDLE_DISCONNECT_INTERVAL (2 * MINUTES)
39 /* how often to trim the node list */
40 #define CLEANUP_INTERVAL (15 * MINUTES)
42 /* how often to clear indications of connecting to nodes */
43 #define RETRY_ALL_INTERVAL (60 * MINUTES)
45 /* maximum number of unreplied pings before disconnecting from a node */
46 #define MAX_UNREPLIED_PINGS 10
48 /* how many connections attempts each maintain loop for nodes previously
49 * registered */
50 #define TRY_CONNECT_NODE_LIST gt_config_get_int("connect/node_list=3")
52 /* how many connection attempts for nodes in the pong cache */
53 #define TRY_CONNECT_NODE_CACHE gt_config_get_int("connect/node_cache=7")
55 /*****************************************************************************/
57 /* timer for initiating/closing connections */
58 static timer_id maintain_timer;
60 /* timer for disconnecting connections */
61 static timer_id disconnect_timer;
63 /* timer for disconnecting idle nodes */
64 static timer_id idle_disconnect_timer;
66 /* timer for cleaning up the node list */
67 static timer_id cleanup_timer;
69 /* timer to clear 'tried' indicators to retry connecting */
70 static timer_id retry_all_timer;
72 /*****************************************************************************/
74 static GtNode *node_disconnect_one (TCPC *c, GtNode *node, void *udata)
75 {
76 GT->DBGFN (GT, "[%s]: disconnecting", net_ip_str (GT_NODE(c)->ip));
77 gt_node_disconnect (c);
78 return NULL;
79 }
81 static GtNode *node_ping (TCPC *c, GtNode *node, GtPacket *packet)
82 {
83 gt_packet_send (c, packet);
85 /* ->pings_with_noreply gets set to zero when the node sends a pong */
86 if (gt_packet_ttl (packet) == 1)
87 node->pings_with_noreply++;
89 return NULL;
90 }
92 static void ping_hosts_ttl (uint8_t ttl)
93 {
94 GtPacket *packet;
96 if (!(packet = gt_packet_new (GT_MSG_PING, ttl, NULL)))
97 return;
99 gt_conn_foreach (GT_CONN_FOREACH(node_ping), packet,
100 GT_NODE_NONE, GT_NODE_CONNECTED, 0);
102 gt_packet_free (packet);
103 }
105 static void ping_hosts (time_t now)
106 {
107 static time_t last_ping;
108 static time_t last_keep_alive;
109 BOOL need_connections;
110 uint8_t ttl;
112 need_connections = gt_conn_need_connections (GT_NODE_ULTRA);
114 if (now - last_ping < 30 * SECONDS && !need_connections)
115 return;
117 last_ping = now;
119 /* ping to get more hosts if we need connections */
120 if (now - last_keep_alive >= 1 * MINUTES)
121 {
122 /* do a keepalive */
123 ttl = 1;
124 last_keep_alive = now;
125 }
126 else
127 {
128 /* get more hosts */
129 ttl = 7;
130 }
132 ping_hosts_ttl (ttl);
133 }
135 /*****************************************************************************/
137 static void disconnect_no_query_route (void)
138 {
139 int nr_supernodes;
141 /* only disconnect if theres other nodes to fallback on */
142 nr_supernodes = gt_conn_length (GT_NODE_ULTRA, GT_NODE_CONNECTED);
144 if (nr_supernodes > 0)
145 {
146 gt_conn_foreach (node_disconnect_one, NULL,
147 GT_NODE_LEAF, GT_NODE_CONNECTED, 0);
148 }
149 }
151 static void report_connected_leaf (int connected)
152 {
153 static int last_connected = 0;
155 if (connected != last_connected)
156 {
157 GT->DBGFN (GT, "connected=%d nodes=%d", connected,
158 gt_conn_length (GT_NODE_NONE, GT_NODE_ANY));
159 last_connected = connected;
160 }
161 }
163 static int get_need_as_ultra (gt_node_class_t klass)
164 {
165 switch (klass)
166 {
167 case GT_NODE_ULTRA: return GT_PEER_CONNECTIONS;
168 case GT_NODE_LEAF: return GT_LEAF_CONNECTIONS;
169 default: return 0;
170 }
171 }
173 static int get_need_as_leaf (gt_node_class_t klass)
174 {
175 switch (klass)
176 {
177 case GT_NODE_ULTRA: return GT_SHIELDED_CONNECTIONS;
178 case GT_NODE_LEAF: return 0; /* no leaf<->leaf connections allowed */
179 default: return 0;
180 }
181 }
183 int gt_conn_need_connections (gt_node_class_t klass)
184 {
185 int connected;
186 int desired;
188 connected = gt_conn_length (klass, GT_NODE_CONNECTED);
190 /* don't call this with multiple classes -- the need of one
191 * class could cancel a surplus of the other */
192 assert (klass == GT_NODE_ULTRA || klass == GT_NODE_LEAF);
194 if (GT_SELF->klass & GT_NODE_ULTRA)
195 desired = get_need_as_ultra (klass);
196 else
197 desired = get_need_as_leaf (klass);
199 return desired - connected;
200 }
202 static void disconnect_hosts (gt_node_class_t klass, int excess)
203 {
204 int connected;
206 connected = gt_conn_length (klass, GT_NODE_CONNECTED);
208 GT->DBGFN (GT, "too many connections (%d)[%s], disconnecting %d",
209 connected, gt_node_class_str (klass), excess);
211 while (excess-- > 0)
212 {
213 GtNode *node = gt_conn_random (klass, GT_NODE_CONNECTED);
215 /* TODO: send BYE message here */
217 assert (GT_CONN(node) != NULL);
218 gt_node_disconnect (GT_CONN(node));
219 }
220 }
222 static BOOL disconnect_excess_timer (void *udata)
223 {
224 int leaf_excess;
225 int ultra_excess;
227 leaf_excess = gt_conn_need_connections (GT_NODE_LEAF);
228 ultra_excess = gt_conn_need_connections (GT_NODE_ULTRA);
230 if (leaf_excess < 0)
231 disconnect_hosts (GT_NODE_LEAF, -leaf_excess);
233 if (ultra_excess < 0)
234 disconnect_hosts (GT_NODE_ULTRA, -ultra_excess);
236 disconnect_timer = 0;
237 return FALSE;
238 }
240 static GtNode *collect_each_node (TCPC *c, GtNode *node, List **nodes)
241 {
242 if (node->tried_connect)
243 return NULL;
245 if (!node->gt_port)
246 return NULL;
248 /* mark having tried to to connect to this node already */
249 node->tried_connect = TRUE;
251 *nodes = list_append (*nodes, node);
253 /* stop iterating if we have enough nodes */
254 if (list_length (*nodes) >= TRY_CONNECT_NODE_LIST)
255 return node;
257 return NULL;
258 }
260 static GtNode *clear_try_bit (TCPC *c, GtNode *node, void *udata)
261 {
262 node->tried_connect = FALSE;
263 return NULL;
264 }
266 static BOOL prune_registered (struct cached_node *cached, void *udata)
267 {
268 if (gt_node_lookup (cached->addr.ip, cached->addr.port))
269 {
270 GT->DBGFN (GT, "pruning %s (already registered)",
271 net_ip_str (cached->addr.ip), cached->addr.port);
272 free (cached);
273 return TRUE;
274 }
276 return FALSE;
277 }
279 static BOOL register_cached (struct cached_node *cached, void *udata)
280 {
281 GtNode *node;
283 node = gt_node_lookup (cached->addr.ip, cached->addr.port);
285 if (node)
286 {
287 /*
288 * Argh, gt_node_lookup only matches by IP
289 * This should be assert (0)
290 */
291 assert (node->gt_port != cached->addr.port);
293 free (cached);
294 return TRUE;
295 }
297 node = gt_node_register (cached->addr.ip, cached->addr.port,
298 cached->klass);
300 /* we've got to free the node, Jim */
301 free (cached);
303 /* this happens if the address is invalid or a mem failure */
304 if (!node)
305 return TRUE;
307 gt_connect (node);
308 node->tried_connect = TRUE;
310 return TRUE;
311 }
313 static BOOL connect_each (GtNode *node, void *udata)
314 {
315 if (gt_connect (node) < 0)
316 {
317 GT->err (GT, "Failed to connect to node %s:%hu: %s",
318 net_ip_str (node->ip), node->gt_port, GIFT_NETERROR());
319 return TRUE;
320 }
322 return TRUE;
323 }
325 /*****************************************************************************/
327 /* returns number of nodes we will try to connect to */
328 static size_t try_some_nodes (time_t now)
329 {
330 List *nodes = NULL;
331 List *cached = NULL;
332 size_t total = 0;
333 size_t nr;
334 size_t len;
335 size_t count;
337 /* the total amount of nodes we should try */
338 nr = TRY_CONNECT_NODE_LIST + TRY_CONNECT_NODE_CACHE;
340 /*
341 * Iterate the node (pong) cache and node list until we
342 * have seen 'nr' nodes or there are no more hosts to try.
343 */
345 while (total < nr)
346 {
347 gt_conn_foreach (GT_CONN_FOREACH(collect_each_node), &nodes,
348 GT_NODE_NONE, GT_NODE_DISCONNECTED, 0);
350 /* grab at most nr - total nodes (still need to fix the preceeding
351 * call to gt_conn_foreach() to respect 'total') */
352 count = MIN (nr - total, TRY_CONNECT_NODE_CACHE);
353 assert (count >= 0);
355 cached = gt_node_cache_get_remove (count);
357 /* registered nodes can still slip into our node cache, argh */
358 cached = list_foreach_remove (cached,
359 (ListForeachFunc)prune_registered,
360 NULL);
362 len = list_length (nodes) + list_length (cached);
364 total += len;
366 if (len == 0)
367 break;
369 nodes = list_foreach_remove (nodes, (ListForeachFunc)connect_each,
370 NULL);
371 assert (nodes == NULL);
373 cached = list_foreach_remove (cached, (ListForeachFunc)register_cached,
374 NULL);
375 assert (cached == NULL);
376 }
378 return total;
379 }
381 static void maintain_class (gt_node_class_t klass, time_t now)
382 {
383 int connected;
384 int need;
386 connected = gt_conn_length (klass, GT_NODE_CONNECTED);
387 need = gt_conn_need_connections (klass);
389 /*
390 * print the number of nodes connected if it has changed
391 * XXX: print leaves from ultrapeers and leaves too.
392 * damn static variables to hell
393 */
394 if (klass == GT_NODE_ULTRA)
395 report_connected_leaf (connected);
397 /* 0 == perfection */
398 if (need == 0)
399 return;
401 /* disconnect some nodes */
402 if (need < 0)
403 {
404 if (disconnect_timer)
405 return;
407 /*
408 * Disconnect the node soon, because it could happen that
409 * someone will disconnect from us first, causing cascading
410 * disconnects.
411 */
412 GT->DBGFN (GT, "starting disconnect timer...");
413 disconnect_timer = timer_add (4 * SECONDS,
414 (TimerCallback)disconnect_excess_timer,
415 NULL);
416 return;
417 }
419 /*
420 * If try_some_nodes() returns 0, then there are no nodes in the node
421 * cache nor any on the node list that we haven't tried yet. In that case,
422 * we need to contact the gwebcaches and hope a fresh infusion of nodes
423 * will help. While we wait, we retry all the nodes we already tried by
424 * clearing node->tried_connect for each node, which otherwise prevents
425 * from recontacting the nodes.
426 *
427 * We will "block" on the gwebcaches if the bandwidth is completely
428 * saturated and we can't get a reply from anyone, or if there are no
429 * ultrapeers with connection slots available. The gwebcache subsystem
430 * imposes its own limits on how often it will contact gwebcaches, so if
431 * we do end up in this situation, hopefully we will simply spend most of
432 * the time unconnected rather than hammering the gwebcaches.
433 */
434 if (try_some_nodes (now) == 0)
435 {
436 size_t len;
438 len = gt_conn_length (GT_NODE_NONE, GT_NODE_ANY);
439 GT->dbg (GT, "try_some_nodes() returned 0. node list len=%u", len);
441 if (connected == 0 || len < 20)
442 {
443 /* try to get more hosts */
444 GT->dbg (GT, "No hosts to try. Looking in gwebcaches...");
445 gt_web_cache_update ();
446 }
448 GT->dbg (GT, "Retrying to connect to nodes...");
450 /* while we are waiting for the gwebcaches, try each node again */
451 gt_conn_foreach (GT_CONN_FOREACH(clear_try_bit), NULL,
452 GT_NODE_NONE, GT_NODE_ANY, 0);
454 return;
455 }
456 }
458 static GtNode *disconnect_no_ping_replies (TCPC *c, GtNode *node, void *udata)
459 {
460 if (node->pings_with_noreply < MAX_UNREPLIED_PINGS)
461 return NULL;
463 GT->DBGSOCK (GT, node->c, "%d unreplied pings. disconnecting",
464 node->pings_with_noreply);
466 gt_node_disconnect (c);
467 return NULL;
468 }
470 /*****************************************************************************/
472 /*
473 * This is the main network maintainence function. All connections to the
474 * network are initiated from here.
475 */
476 static BOOL maintain (void *udata)
477 {
478 time_t now;
480 now = time (NULL);
482 /* disconnect nodes without query routing if we are not a supernode */
483 if (!(GT_SELF->klass & GT_NODE_ULTRA))
484 disconnect_no_query_route ();
486 #if 0
487 trace_list (connections);
488 #endif
490 /*
491 * Send pings to all connected nodes. We used to do this only every
492 * minute, but because some nodes have short timeouts if they receive
493 * nothing from you, we now do it every MAINTAIN_INTERVAL.
494 */
495 ping_hosts (now);
497 maintain_class (GT_NODE_ULTRA, now);
498 maintain_class (GT_NODE_LEAF, now);
500 return TRUE;
501 }
503 static BOOL idle_disconnect (void *udata)
504 {
505 gt_conn_foreach (GT_CONN_FOREACH(disconnect_no_ping_replies), NULL,
506 GT_NODE_NONE, GT_NODE_CONNECTED, 0);
507 return TRUE;
508 }
510 static BOOL cleanup (void *udata)
511 {
512 /* trim excess nodes */
513 gt_conn_trim ();
515 /* save to disk important nodes from the node list */
516 gt_node_list_save ();
518 /* save to disk important nodes from the node cache */
519 gt_node_cache_save ();
521 return TRUE;
522 }
524 static BOOL retry_all (void *udata)
525 {
526 /*
527 * Clear the 'tried' bit for all nodes, so if we start looking for nodes
528 * we try reconnecting to the ones we know about instead of contacting the
529 * gwebcaches.
530 *
531 * NOTE: should all the nodes be possibly retried (GT_NODE_ANY) or
532 * only those that are disconnected (GT_NODE_DISCONNECTED)?
533 */
534 gt_conn_foreach (GT_CONN_FOREACH(clear_try_bit), NULL,
535 GT_NODE_NONE, GT_NODE_ANY, 0);
537 return TRUE;
538 }
539 /*****************************************************************************/
541 void gt_netorg_init (void)
542 {
543 if (maintain_timer != 0)
544 return;
546 /* load the node cache */
547 gt_node_cache_init ();
549 /* setup the links maintain timer */
550 maintain_timer = timer_add (MAINTAIN_INTERVAL,
551 maintain, NULL);
553 idle_disconnect_timer = timer_add (IDLE_DISCONNECT_INTERVAL,
554 idle_disconnect, NULL);
556 cleanup_timer = timer_add (CLEANUP_INTERVAL,
557 cleanup, NULL);
559 retry_all_timer = timer_add (RETRY_ALL_INTERVAL,
560 retry_all, NULL);
562 /* call it now so we don't have to wait the first time */
563 maintain (NULL);
564 }
566 void gt_netorg_cleanup (void)
567 {
568 /* save the node cache */
569 gt_node_cache_cleanup ();
571 timer_remove_zero (&disconnect_timer);
573 timer_remove_zero (&maintain_timer);
574 timer_remove_zero (&idle_disconnect_timer);
575 timer_remove_zero (&cleanup_timer);
576 timer_remove_zero (&retry_all_timer);
577 }