Mercurial > hg > index.fcgi > gift-gnutella > gift-gnutella-0.0.11-1pba
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:53b3d0de1468 |
---|---|
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 */ | |
16 | |
17 #include "gt_gnutella.h" | |
18 | |
19 #include "gt_node.h" | |
20 #include "gt_node_list.h" | |
21 #include "gt_netorg.h" | |
22 | |
23 #include "gt_connect.h" | |
24 #include "gt_accept.h" | |
25 | |
26 #include "gt_packet.h" | |
27 | |
28 #include "gt_node_cache.h" | |
29 #include "gt_web_cache.h" | |
30 | |
31 /*****************************************************************************/ | |
32 | |
33 /* how often we check the network's condition */ | |
34 #define MAINTAIN_INTERVAL (10 * SECONDS) | |
35 | |
36 /* how often to check to disconnect idle nodes */ | |
37 #define IDLE_DISCONNECT_INTERVAL (2 * MINUTES) | |
38 | |
39 /* how often to trim the node list */ | |
40 #define CLEANUP_INTERVAL (15 * MINUTES) | |
41 | |
42 /* how often to clear indications of connecting to nodes */ | |
43 #define RETRY_ALL_INTERVAL (60 * MINUTES) | |
44 | |
45 /* maximum number of unreplied pings before disconnecting from a node */ | |
46 #define MAX_UNREPLIED_PINGS 10 | |
47 | |
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") | |
51 | |
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") | |
54 | |
55 /*****************************************************************************/ | |
56 | |
57 /* timer for initiating/closing connections */ | |
58 static timer_id maintain_timer; | |
59 | |
60 /* timer for disconnecting connections */ | |
61 static timer_id disconnect_timer; | |
62 | |
63 /* timer for disconnecting idle nodes */ | |
64 static timer_id idle_disconnect_timer; | |
65 | |
66 /* timer for cleaning up the node list */ | |
67 static timer_id cleanup_timer; | |
68 | |
69 /* timer to clear 'tried' indicators to retry connecting */ | |
70 static timer_id retry_all_timer; | |
71 | |
72 /*****************************************************************************/ | |
73 | |
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 } | |
80 | |
81 static GtNode *node_ping (TCPC *c, GtNode *node, GtPacket *packet) | |
82 { | |
83 gt_packet_send (c, packet); | |
84 | |
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++; | |
88 | |
89 return NULL; | |
90 } | |
91 | |
92 static void ping_hosts_ttl (uint8_t ttl) | |
93 { | |
94 GtPacket *packet; | |
95 | |
96 if (!(packet = gt_packet_new (GT_MSG_PING, ttl, NULL))) | |
97 return; | |
98 | |
99 gt_conn_foreach (GT_CONN_FOREACH(node_ping), packet, | |
100 GT_NODE_NONE, GT_NODE_CONNECTED, 0); | |
101 | |
102 gt_packet_free (packet); | |
103 } | |
104 | |
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; | |
111 | |
112 need_connections = gt_conn_need_connections (GT_NODE_ULTRA); | |
113 | |
114 if (now - last_ping < 30 * SECONDS && !need_connections) | |
115 return; | |
116 | |
117 last_ping = now; | |
118 | |
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 } | |
131 | |
132 ping_hosts_ttl (ttl); | |
133 } | |
134 | |
135 /*****************************************************************************/ | |
136 | |
137 static void disconnect_no_query_route (void) | |
138 { | |
139 int nr_supernodes; | |
140 | |
141 /* only disconnect if theres other nodes to fallback on */ | |
142 nr_supernodes = gt_conn_length (GT_NODE_ULTRA, GT_NODE_CONNECTED); | |
143 | |
144 if (nr_supernodes > 0) | |
145 { | |
146 gt_conn_foreach (node_disconnect_one, NULL, | |
147 GT_NODE_LEAF, GT_NODE_CONNECTED, 0); | |
148 } | |
149 } | |
150 | |
151 static void report_connected_leaf (int connected) | |
152 { | |
153 static int last_connected = 0; | |
154 | |
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 } | |
162 | |
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 } | |
172 | |
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 } | |
182 | |
183 int gt_conn_need_connections (gt_node_class_t klass) | |
184 { | |
185 int connected; | |
186 int desired; | |
187 | |
188 connected = gt_conn_length (klass, GT_NODE_CONNECTED); | |
189 | |
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); | |
193 | |
194 if (GT_SELF->klass & GT_NODE_ULTRA) | |
195 desired = get_need_as_ultra (klass); | |
196 else | |
197 desired = get_need_as_leaf (klass); | |
198 | |
199 return desired - connected; | |
200 } | |
201 | |
202 static void disconnect_hosts (gt_node_class_t klass, int excess) | |
203 { | |
204 int connected; | |
205 | |
206 connected = gt_conn_length (klass, GT_NODE_CONNECTED); | |
207 | |
208 GT->DBGFN (GT, "too many connections (%d)[%s], disconnecting %d", | |
209 connected, gt_node_class_str (klass), excess); | |
210 | |
211 while (excess-- > 0) | |
212 { | |
213 GtNode *node = gt_conn_random (klass, GT_NODE_CONNECTED); | |
214 | |
215 /* TODO: send BYE message here */ | |
216 | |
217 assert (GT_CONN(node) != NULL); | |
218 gt_node_disconnect (GT_CONN(node)); | |
219 } | |
220 } | |
221 | |
222 static BOOL disconnect_excess_timer (void *udata) | |
223 { | |
224 int leaf_excess; | |
225 int ultra_excess; | |
226 | |
227 leaf_excess = gt_conn_need_connections (GT_NODE_LEAF); | |
228 ultra_excess = gt_conn_need_connections (GT_NODE_ULTRA); | |
229 | |
230 if (leaf_excess < 0) | |
231 disconnect_hosts (GT_NODE_LEAF, -leaf_excess); | |
232 | |
233 if (ultra_excess < 0) | |
234 disconnect_hosts (GT_NODE_ULTRA, -ultra_excess); | |
235 | |
236 disconnect_timer = 0; | |
237 return FALSE; | |
238 } | |
239 | |
240 static GtNode *collect_each_node (TCPC *c, GtNode *node, List **nodes) | |
241 { | |
242 if (node->tried_connect) | |
243 return NULL; | |
244 | |
245 if (!node->gt_port) | |
246 return NULL; | |
247 | |
248 /* mark having tried to to connect to this node already */ | |
249 node->tried_connect = TRUE; | |
250 | |
251 *nodes = list_append (*nodes, node); | |
252 | |
253 /* stop iterating if we have enough nodes */ | |
254 if (list_length (*nodes) >= TRY_CONNECT_NODE_LIST) | |
255 return node; | |
256 | |
257 return NULL; | |
258 } | |
259 | |
260 static GtNode *clear_try_bit (TCPC *c, GtNode *node, void *udata) | |
261 { | |
262 node->tried_connect = FALSE; | |
263 return NULL; | |
264 } | |
265 | |
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 } | |
275 | |
276 return FALSE; | |
277 } | |
278 | |
279 static BOOL register_cached (struct cached_node *cached, void *udata) | |
280 { | |
281 GtNode *node; | |
282 | |
283 node = gt_node_lookup (cached->addr.ip, cached->addr.port); | |
284 | |
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); | |
292 | |
293 free (cached); | |
294 return TRUE; | |
295 } | |
296 | |
297 node = gt_node_register (cached->addr.ip, cached->addr.port, | |
298 cached->klass); | |
299 | |
300 /* we've got to free the node, Jim */ | |
301 free (cached); | |
302 | |
303 /* this happens if the address is invalid or a mem failure */ | |
304 if (!node) | |
305 return TRUE; | |
306 | |
307 gt_connect (node); | |
308 node->tried_connect = TRUE; | |
309 | |
310 return TRUE; | |
311 } | |
312 | |
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 } | |
321 | |
322 return TRUE; | |
323 } | |
324 | |
325 /*****************************************************************************/ | |
326 | |
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; | |
336 | |
337 /* the total amount of nodes we should try */ | |
338 nr = TRY_CONNECT_NODE_LIST + TRY_CONNECT_NODE_CACHE; | |
339 | |
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 */ | |
344 | |
345 while (total < nr) | |
346 { | |
347 gt_conn_foreach (GT_CONN_FOREACH(collect_each_node), &nodes, | |
348 GT_NODE_NONE, GT_NODE_DISCONNECTED, 0); | |
349 | |
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); | |
354 | |
355 cached = gt_node_cache_get_remove (count); | |
356 | |
357 /* registered nodes can still slip into our node cache, argh */ | |
358 cached = list_foreach_remove (cached, | |
359 (ListForeachFunc)prune_registered, | |
360 NULL); | |
361 | |
362 len = list_length (nodes) + list_length (cached); | |
363 | |
364 total += len; | |
365 | |
366 if (len == 0) | |
367 break; | |
368 | |
369 nodes = list_foreach_remove (nodes, (ListForeachFunc)connect_each, | |
370 NULL); | |
371 assert (nodes == NULL); | |
372 | |
373 cached = list_foreach_remove (cached, (ListForeachFunc)register_cached, | |
374 NULL); | |
375 assert (cached == NULL); | |
376 } | |
377 | |
378 return total; | |
379 } | |
380 | |
381 static void maintain_class (gt_node_class_t klass, time_t now) | |
382 { | |
383 int connected; | |
384 int need; | |
385 | |
386 connected = gt_conn_length (klass, GT_NODE_CONNECTED); | |
387 need = gt_conn_need_connections (klass); | |
388 | |
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); | |
396 | |
397 /* 0 == perfection */ | |
398 if (need == 0) | |
399 return; | |
400 | |
401 /* disconnect some nodes */ | |
402 if (need < 0) | |
403 { | |
404 if (disconnect_timer) | |
405 return; | |
406 | |
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 } | |
418 | |
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; | |
437 | |
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); | |
440 | |
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 } | |
447 | |
448 GT->dbg (GT, "Retrying to connect to nodes..."); | |
449 | |
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); | |
453 | |
454 return; | |
455 } | |
456 } | |
457 | |
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; | |
462 | |
463 GT->DBGSOCK (GT, node->c, "%d unreplied pings. disconnecting", | |
464 node->pings_with_noreply); | |
465 | |
466 gt_node_disconnect (c); | |
467 return NULL; | |
468 } | |
469 | |
470 /*****************************************************************************/ | |
471 | |
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; | |
479 | |
480 now = time (NULL); | |
481 | |
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 (); | |
485 | |
486 #if 0 | |
487 trace_list (connections); | |
488 #endif | |
489 | |
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); | |
496 | |
497 maintain_class (GT_NODE_ULTRA, now); | |
498 maintain_class (GT_NODE_LEAF, now); | |
499 | |
500 return TRUE; | |
501 } | |
502 | |
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 } | |
509 | |
510 static BOOL cleanup (void *udata) | |
511 { | |
512 /* trim excess nodes */ | |
513 gt_conn_trim (); | |
514 | |
515 /* save to disk important nodes from the node list */ | |
516 gt_node_list_save (); | |
517 | |
518 /* save to disk important nodes from the node cache */ | |
519 gt_node_cache_save (); | |
520 | |
521 return TRUE; | |
522 } | |
523 | |
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); | |
536 | |
537 return TRUE; | |
538 } | |
539 /*****************************************************************************/ | |
540 | |
541 void gt_netorg_init (void) | |
542 { | |
543 if (maintain_timer != 0) | |
544 return; | |
545 | |
546 /* load the node cache */ | |
547 gt_node_cache_init (); | |
548 | |
549 /* setup the links maintain timer */ | |
550 maintain_timer = timer_add (MAINTAIN_INTERVAL, | |
551 maintain, NULL); | |
552 | |
553 idle_disconnect_timer = timer_add (IDLE_DISCONNECT_INTERVAL, | |
554 idle_disconnect, NULL); | |
555 | |
556 cleanup_timer = timer_add (CLEANUP_INTERVAL, | |
557 cleanup, NULL); | |
558 | |
559 retry_all_timer = timer_add (RETRY_ALL_INTERVAL, | |
560 retry_all, NULL); | |
561 | |
562 /* call it now so we don't have to wait the first time */ | |
563 maintain (NULL); | |
564 } | |
565 | |
566 void gt_netorg_cleanup (void) | |
567 { | |
568 /* save the node cache */ | |
569 gt_node_cache_cleanup (); | |
570 | |
571 timer_remove_zero (&disconnect_timer); | |
572 | |
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 } |