annotate src/gt_connect.c @ 0:d39e1d0d75b6

initial add
author paulo@hit-nxdomain.opendns.com
date Sat, 20 Feb 2010 21:18:28 -0800
parents
children
rev   line source
paulo@0 1 /*
paulo@0 2 * $Id: gt_connect.c,v 1.55 2005/01/04 15:03:40 mkern Exp $
paulo@0 3 *
paulo@0 4 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net)
paulo@0 5 *
paulo@0 6 * This program is free software; you can redistribute it and/or modify it
paulo@0 7 * under the terms of the GNU General Public License as published by the
paulo@0 8 * Free Software Foundation; either version 2, or (at your option) any
paulo@0 9 * later version.
paulo@0 10 *
paulo@0 11 * This program is distributed in the hope that it will be useful, but
paulo@0 12 * WITHOUT ANY WARRANTY; without even the implied warranty of
paulo@0 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
paulo@0 14 * General Public License for more details.
paulo@0 15 */
paulo@0 16
paulo@0 17 #include "gt_gnutella.h"
paulo@0 18 #include "gt_version.h"
paulo@0 19
paulo@0 20 #include "gt_connect.h"
paulo@0 21 #include "gt_accept.h"
paulo@0 22 #include "gt_packet.h"
paulo@0 23
paulo@0 24 #include "gt_node.h"
paulo@0 25 #include "gt_node_list.h"
paulo@0 26 #include "gt_utils.h"
paulo@0 27
paulo@0 28 #include "gt_search.h"
paulo@0 29 #include "gt_netorg.h"
paulo@0 30
paulo@0 31 #include "gt_node_cache.h"
paulo@0 32
paulo@0 33 #include "message/gt_message.h" /* gnutella_start_connection */
paulo@0 34
paulo@0 35 /*****************************************************************************/
paulo@0 36
paulo@0 37 static void send_connect (int fd, input_id id, TCPC *c);
paulo@0 38 static void recv_headers (int fd, input_id id, TCPC *c);
paulo@0 39 static void send_response (int fd, input_id id, TCPC *c);
paulo@0 40 static BOOL send_final (TCPC *c);
paulo@0 41
paulo@0 42 /*****************************************************************************/
paulo@0 43
paulo@0 44 static BOOL handshake_timeout (TCPC *c)
paulo@0 45 {
paulo@0 46 GtNode *node = GT_NODE(c);
paulo@0 47
paulo@0 48 node->handshake_timer = 0;
paulo@0 49
paulo@0 50 if (!(node->state & GT_NODE_CONNECTED))
paulo@0 51 {
paulo@0 52 gt_node_disconnect (c);
paulo@0 53 return FALSE;
paulo@0 54 }
paulo@0 55
paulo@0 56 return FALSE;
paulo@0 57 }
paulo@0 58
paulo@0 59 void gnutella_set_handshake_timeout (TCPC *c, time_t delay)
paulo@0 60 {
paulo@0 61 timer_remove (GT_NODE(c)->handshake_timer);
paulo@0 62
paulo@0 63 GT_NODE(c)->handshake_timer = timer_add (delay,
paulo@0 64 (TimerCallback)handshake_timeout,
paulo@0 65 c);
paulo@0 66 }
paulo@0 67
paulo@0 68 int gt_connect (GtNode *node)
paulo@0 69 {
paulo@0 70 TCPC *c;
paulo@0 71
paulo@0 72 if (!node)
paulo@0 73 return -1;
paulo@0 74
paulo@0 75 #if 0
paulo@0 76 if (GT_CONN(node) != NULL)
paulo@0 77 {
paulo@0 78 GT->dbg (GT, "duplicate connection?: %p", GT_CONN(node));
paulo@0 79 return -1;
paulo@0 80 }
paulo@0 81
paulo@0 82 if (node->state != GT_NODE_DISCONNECTED)
paulo@0 83 {
paulo@0 84 GT->dbg (GT, "state = %i??", node->state);
paulo@0 85 return -1;
paulo@0 86 }
paulo@0 87 #endif
paulo@0 88
paulo@0 89 /* this must be called only on disconnected nodes */
paulo@0 90 assert (GT_CONN(node) == NULL);
paulo@0 91 assert (node->state == GT_NODE_DISCONNECTED);
paulo@0 92
paulo@0 93 #if 0
paulo@0 94 if (!conn_auth (c, TRUE))
paulo@0 95 return -1;
paulo@0 96 #endif
paulo@0 97
paulo@0 98 /* set this early: gt_netorg relies on this being set in order
paulo@0 99 * to check if it should access the gwebcaches */
paulo@0 100 node->start_connect_time = time (NULL);
paulo@0 101
paulo@0 102 /* make sure port is valid */
paulo@0 103 if (node->gt_port == 0)
paulo@0 104 {
paulo@0 105 GT->DBGFN (GT, "bad port on node %s", net_ip_str (node->ip));
paulo@0 106 return -1;
paulo@0 107 }
paulo@0 108
paulo@0 109 /* make outgoing connection */
paulo@0 110 if (!(c = tcp_open (node->ip, node->gt_port, FALSE)))
paulo@0 111 return -1;
paulo@0 112
paulo@0 113 gt_node_connect (node, c);
paulo@0 114
paulo@0 115 gt_node_state_set (node, GT_NODE_CONNECTING_1);
paulo@0 116 node->incoming = FALSE;
paulo@0 117
paulo@0 118 /* set the connection timeout */
paulo@0 119 gnutella_set_handshake_timeout (c, TIMEOUT_1 * SECONDS);
paulo@0 120
paulo@0 121 input_add (c->fd, c, INPUT_WRITE,
paulo@0 122 (InputCallback)send_connect, 0);
paulo@0 123
paulo@0 124 return c->fd;
paulo@0 125 }
paulo@0 126
paulo@0 127 static void send_connect (int fd, input_id id, TCPC *c)
paulo@0 128 {
paulo@0 129 if (net_sock_error (c->fd))
paulo@0 130 {
paulo@0 131 gt_node_disconnect (c);
paulo@0 132 return;
paulo@0 133 }
paulo@0 134
paulo@0 135 /* Send the connect string along with our headers */
paulo@0 136 if (!gnutella_send_connection_headers (c, "GNUTELLA CONNECT/0.6"))
paulo@0 137 {
paulo@0 138 gt_node_error (c, NULL);
paulo@0 139 gt_node_disconnect (c);
paulo@0 140 return;
paulo@0 141 }
paulo@0 142
paulo@0 143 /* we connected ok, so give the peer some more time */
paulo@0 144 gnutella_set_handshake_timeout (c, TIMEOUT_2 * SECONDS);
paulo@0 145
paulo@0 146 input_remove (id);
paulo@0 147 input_add (fd, c, INPUT_READ,
paulo@0 148 (InputCallback)recv_headers, 0);
paulo@0 149 }
paulo@0 150
paulo@0 151 BOOL gnutella_parse_response_headers (char *reply, Dataset **r_headers)
paulo@0 152 {
paulo@0 153 int code; /* 200, 404, ... */
paulo@0 154 char *response;
paulo@0 155 Dataset *headers = NULL;
paulo@0 156
paulo@0 157 response = string_sep (&reply, "\r\n");
paulo@0 158
paulo@0 159 if (!response)
paulo@0 160 return FALSE;
paulo@0 161
paulo@0 162 /* */ string_sep (&response, " "); /* shift past HTTP/1.1 */
paulo@0 163 code = ATOI (string_sep (&response, " ")); /* shift past 200 */
paulo@0 164
paulo@0 165 /* parse the headers */
paulo@0 166 gt_http_header_parse (reply, &headers);
paulo@0 167
paulo@0 168 if (r_headers)
paulo@0 169 *r_headers = headers;
paulo@0 170 else
paulo@0 171 dataset_clear (headers);
paulo@0 172
paulo@0 173 if (code >= 200 && code <= 299)
paulo@0 174 return TRUE;
paulo@0 175
paulo@0 176 return FALSE;
paulo@0 177 }
paulo@0 178
paulo@0 179 static time_t parse_uptime (Dataset *d)
paulo@0 180 {
paulo@0 181 char *str;
paulo@0 182 int days, hours, mins;
paulo@0 183 int n;
paulo@0 184
paulo@0 185 if (!(str = dataset_lookupstr (d, "uptime")))
paulo@0 186 return 0;
paulo@0 187
paulo@0 188 string_lower (str);
paulo@0 189
paulo@0 190 if ((n = sscanf (str, "%dd %dh %dm", &days, &hours, &mins)) != 3)
paulo@0 191 return 0;
paulo@0 192
paulo@0 193 if (HANDSHAKE_DEBUG)
paulo@0 194 {
paulo@0 195 GT->dbg (GT, "uptime parsed: %d days, %d hours, %d minutes",
paulo@0 196 days, hours, mins);
paulo@0 197 }
paulo@0 198
paulo@0 199 return days * EDAYS + hours * EHOURS + mins * EMINUTES;
paulo@0 200 }
paulo@0 201
paulo@0 202 /* look in a header field for nodes, and register them */
paulo@0 203 static void extract_nodes (Dataset *d, in_addr_t src,
paulo@0 204 const char *field, gt_node_class_t klass)
paulo@0 205 {
paulo@0 206 char *str;
paulo@0 207 char *value;
paulo@0 208 time_t now;
paulo@0 209
paulo@0 210 now = time (NULL);
paulo@0 211
paulo@0 212 if (!(str = dataset_lookupstr (d, field)))
paulo@0 213 return;
paulo@0 214
paulo@0 215 while ((value = string_sep (&str, ",")))
paulo@0 216 {
paulo@0 217 in_addr_t ip;
paulo@0 218 in_port_t port;
paulo@0 219
paulo@0 220 ip = net_ip (string_sep (&value, ":"));
paulo@0 221 port = ATOI (value);
paulo@0 222
paulo@0 223 if (port == (in_port_t) -1 || port == 0)
paulo@0 224 continue;
paulo@0 225
paulo@0 226 if (ip == INADDR_NONE || ip == 0)
paulo@0 227 continue;
paulo@0 228
paulo@0 229 if (gt_is_local_ip (ip, src))
paulo@0 230 continue;
paulo@0 231
paulo@0 232 gt_node_cache_add_ipv4 (ip, port, klass, now, 0, src);
paulo@0 233 }
paulo@0 234
paulo@0 235 gt_node_cache_trace ();
paulo@0 236 }
paulo@0 237
paulo@0 238 static void recv_headers (int fd, input_id id, TCPC *c)
paulo@0 239 {
paulo@0 240 FDBuf *buf;
paulo@0 241 char *response;
paulo@0 242 size_t response_len = 0;
paulo@0 243 int n;
paulo@0 244 BOOL ok;
paulo@0 245 time_t uptime;
paulo@0 246 GtNode *node = GT_NODE(c);
paulo@0 247
paulo@0 248 buf = tcp_readbuf (c);
paulo@0 249
paulo@0 250 if ((n = fdbuf_delim (buf, "\n")) < 0)
paulo@0 251 {
paulo@0 252 GT->DBGFN (GT, "error reading headers: %s", GIFT_NETERROR ());
paulo@0 253 gt_node_disconnect (c);
paulo@0 254 return;
paulo@0 255 }
paulo@0 256
paulo@0 257 if (gt_fdbuf_full (buf))
paulo@0 258 {
paulo@0 259 gt_node_disconnect (c);
paulo@0 260 return;
paulo@0 261 }
paulo@0 262
paulo@0 263 if (n > 0)
paulo@0 264 return;
paulo@0 265
paulo@0 266 response = fdbuf_data (buf, &response_len);
paulo@0 267 if (!gt_http_header_terminated (response, response_len))
paulo@0 268 return;
paulo@0 269
paulo@0 270 fdbuf_release (buf);
paulo@0 271
paulo@0 272 if (HANDSHAKE_DEBUG)
paulo@0 273 GT->DBGSOCK (GT, c, "node handshake response:\n%s", response);
paulo@0 274
paulo@0 275 /* parse and store the response */
paulo@0 276 ok = gnutella_parse_response_headers (response, &node->hdr);
paulo@0 277
paulo@0 278 /* extract nodes */
paulo@0 279 extract_nodes (node->hdr, node->ip, "x-try-ultrapeers", GT_NODE_ULTRA);
paulo@0 280 extract_nodes (node->hdr, node->ip, "x-try", GT_NODE_NONE);
paulo@0 281
paulo@0 282 /* grab the uptime from the "Uptime: " header and update this node */
paulo@0 283 if ((uptime = parse_uptime (node->hdr)) > 0)
paulo@0 284 {
paulo@0 285 gt_node_cache_add_ipv4 (node->ip, node->gt_port,
paulo@0 286 GT_NODE_ULTRA, time (NULL), uptime, node->ip);
paulo@0 287
paulo@0 288 /* XXX: remove the item immediately so we trigger the side effect of
paulo@0 289 * adding this node to the stable list */
paulo@0 290 gt_node_cache_del_ipv4 (node->ip, node->gt_port);
paulo@0 291 }
paulo@0 292
paulo@0 293 if (!ok)
paulo@0 294 {
paulo@0 295 gt_node_disconnect (c);
paulo@0 296 return;
paulo@0 297 }
paulo@0 298
paulo@0 299 input_remove (id);
paulo@0 300 input_add (fd, c, INPUT_WRITE,
paulo@0 301 (InputCallback)send_response, 0);
paulo@0 302 }
paulo@0 303
paulo@0 304 static void send_response (int fd, input_id id, TCPC *c)
paulo@0 305 {
paulo@0 306 if (net_sock_error (c->fd))
paulo@0 307 {
paulo@0 308 gt_node_error (c, NULL);
paulo@0 309 gt_node_disconnect (c);
paulo@0 310 return;
paulo@0 311 }
paulo@0 312
paulo@0 313 if (!gnutella_auth_connection (c))
paulo@0 314 {
paulo@0 315 gt_node_error (c, "[outgoing] connection not authorized");
paulo@0 316 gt_node_disconnect (c);
paulo@0 317 return;
paulo@0 318 }
paulo@0 319
paulo@0 320 if (!send_final (c))
paulo@0 321 {
paulo@0 322 gt_node_error (c, NULL);
paulo@0 323 GT->DBGSOCK (GT, c, "error at stage 3 of handshake");
paulo@0 324 gt_node_disconnect (c);
paulo@0 325 return;
paulo@0 326 }
paulo@0 327
paulo@0 328 /* ok, startup this connection */
paulo@0 329 input_remove (id);
paulo@0 330 input_add (fd, c, INPUT_WRITE,
paulo@0 331 (InputCallback)gnutella_start_connection, 0);
paulo@0 332 }
paulo@0 333
paulo@0 334 /*****************************************************************************/
paulo@0 335
paulo@0 336 static GtNode *append_node (TCPC *c, GtNode *node, String *s)
paulo@0 337 {
paulo@0 338 if (s->str[s->len - 1] != ' ')
paulo@0 339 string_append (s, ",");
paulo@0 340
paulo@0 341 string_appendf (s, "%s:%hu", net_ip_str (node->ip), node->gt_port);
paulo@0 342 return NULL;
paulo@0 343 }
paulo@0 344
paulo@0 345 static void append_crawler_headers (String *msg)
paulo@0 346 {
paulo@0 347 if (gt_conn_length (GT_NODE_ULTRA, GT_NODE_CONNECTED) > 0)
paulo@0 348 {
paulo@0 349 string_append (msg, "Peers: ");
paulo@0 350 gt_conn_foreach (GT_CONN_FOREACH(append_node), msg,
paulo@0 351 GT_NODE_ULTRA, GT_NODE_CONNECTED, 0);
paulo@0 352 string_append (msg, "\r\n");
paulo@0 353 }
paulo@0 354
paulo@0 355 if (GT_SELF->klass & GT_NODE_ULTRA &&
paulo@0 356 gt_conn_length (GT_NODE_LEAF, GT_NODE_CONNECTED) > 0)
paulo@0 357 {
paulo@0 358 string_append (msg, "Leaves: ");
paulo@0 359 gt_conn_foreach (GT_CONN_FOREACH(append_node), msg,
paulo@0 360 GT_NODE_LEAF, GT_NODE_CONNECTED, 0);
paulo@0 361 string_append (msg, "\r\n");
paulo@0 362 }
paulo@0 363 }
paulo@0 364
paulo@0 365 BOOL gnutella_send_connection_headers (TCPC *c, const char *header)
paulo@0 366 {
paulo@0 367 String *msg;
paulo@0 368
paulo@0 369 if (!(msg = string_new (NULL, 0, 0, TRUE)))
paulo@0 370 return FALSE;
paulo@0 371
paulo@0 372 string_appendf (msg, "%s\r\n", header);
paulo@0 373
paulo@0 374 string_append (msg, "X-Query-Routing: 0.1\r\n");
paulo@0 375 string_appendf (msg, "X-Ultrapeer: %s\r\n",
paulo@0 376 (GT_SELF->klass & GT_NODE_ULTRA) ? "True" : "False");
paulo@0 377
paulo@0 378 /* append the client and version we are using */
paulo@0 379 string_appendf (msg, "User-Agent: %s\r\n", gt_version ());
paulo@0 380
paulo@0 381 /* Add a header describing the remote IP of the peer */
paulo@0 382 string_appendf (msg, "Remote-IP: %s\r\n", net_peer_ip (c->fd));
paulo@0 383
paulo@0 384 /* let remote end know it's ok to send vendor messages */
paulo@0 385 string_appendf (msg, "Vendor-Message: 0.1\r\n");
paulo@0 386
paulo@0 387 /* support transmission of pings/pongs with GGEP appended */
paulo@0 388 string_append (msg, "GGEP: 0.5\r\n");
paulo@0 389
paulo@0 390 /* If this is the limewire crawler, append "Peers: " and "Leaves: "
paulo@0 391 * headers and close the connection */
paulo@0 392 if (!c->outgoing && dataset_lookupstr (GT_NODE(c)->hdr, "crawler"))
paulo@0 393 append_crawler_headers (msg);
paulo@0 394
paulo@0 395 /* append willingness to receive compressed data */
paulo@0 396 string_append (msg, "Accept-Encoding: deflate\r\n");
paulo@0 397
paulo@0 398 /* check whether the remote node sent us Accept-Encoding: deflate
paulo@0 399 * already */
paulo@0 400 gnutella_mark_compression (GT_NODE(c));
paulo@0 401
paulo@0 402 /* compress data if we must */
paulo@0 403 if (GT_NODE(c)->tx_deflated)
paulo@0 404 string_append (msg, "Content-Encoding: deflate\r\n");
paulo@0 405
paulo@0 406 /* Add message terminator */
paulo@0 407 string_append (msg, "\r\n");
paulo@0 408
paulo@0 409 if (HANDSHAKE_DEBUG)
paulo@0 410 GT->DBGSOCK (GT, c, "sending node headers:\n%s", msg->str);
paulo@0 411
paulo@0 412 if (tcp_send (c, msg->str, msg->len) <= 0)
paulo@0 413 {
paulo@0 414 string_free (msg);
paulo@0 415 return FALSE;
paulo@0 416 }
paulo@0 417
paulo@0 418 string_free (msg);
paulo@0 419 return TRUE;
paulo@0 420 }
paulo@0 421
paulo@0 422 static BOOL send_final (TCPC *c)
paulo@0 423 {
paulo@0 424 String *s;
paulo@0 425 int ret;
paulo@0 426 int len;
paulo@0 427
paulo@0 428 if (!(s = string_new (NULL, 0, 0, TRUE)))
paulo@0 429 return FALSE;
paulo@0 430
paulo@0 431 /* append header acceptance line */
paulo@0 432 string_append (s, "GNUTELLA/0.6 200 OK\r\n");
paulo@0 433
paulo@0 434 /* mark the connection as complete */
paulo@0 435 gnutella_mark_compression (GT_NODE(c));
paulo@0 436
paulo@0 437 if (GT_NODE(c)->tx_deflated)
paulo@0 438 string_append (s, "Content-Encoding: deflate\r\n");
paulo@0 439
paulo@0 440 /* append msg terminator */
paulo@0 441 string_append (s, "\r\n");
paulo@0 442
paulo@0 443 if (HANDSHAKE_DEBUG)
paulo@0 444 GT->DBGSOCK (GT, c, "sending final handshake:\n%s", s->str);
paulo@0 445
paulo@0 446 len = s->len;
paulo@0 447 ret = tcp_send (c, s->str, s->len);
paulo@0 448
paulo@0 449 string_free (s);
paulo@0 450
paulo@0 451 if (ret != len)
paulo@0 452 return FALSE;
paulo@0 453
paulo@0 454 return TRUE;
paulo@0 455 }
paulo@0 456
paulo@0 457 /*****************************************************************************/
paulo@0 458 /* CONNECTABILITY TESTING */
paulo@0 459
paulo@0 460 static void connect_test_result (GtNode *node, TCPC *c, BOOL success)
paulo@0 461 {
paulo@0 462 GT->DBGFN (GT, "connect test to %s %s", net_ip_str (node->ip),
paulo@0 463 (success ? "succeeded" : "failed"));
paulo@0 464
paulo@0 465 node->firewalled = (success ? FALSE : TRUE);
paulo@0 466 node->verified = TRUE;
paulo@0 467
paulo@0 468 if (c)
paulo@0 469 {
paulo@0 470 tcp_close (c);
paulo@0 471 node->gt_port_verify = NULL;
paulo@0 472 }
paulo@0 473 }
paulo@0 474
paulo@0 475 static void test_connectable (int fd, input_id id, TCPC *c)
paulo@0 476 {
paulo@0 477 GtNode *node;
paulo@0 478
paulo@0 479 node = c->udata;
paulo@0 480
paulo@0 481 if (net_sock_error (c->fd))
paulo@0 482 {
paulo@0 483 connect_test_result (node, c, FALSE);
paulo@0 484 return;
paulo@0 485 }
paulo@0 486
paulo@0 487 /*
paulo@0 488 * Send two newlines, because some firewalls will let connections pass
paulo@0 489 * through, but no data.
paulo@0 490 */
paulo@0 491 tcp_send (c, "\n\n", 2);
paulo@0 492 connect_test_result (node, c, TRUE);
paulo@0 493 }
paulo@0 494
paulo@0 495 /*
paulo@0 496 * Test if the port of a peer we are connected to is connectable. This lets a
paulo@0 497 * node know if it's firewalled. We could use this info to mangle the 'push'
paulo@0 498 * flag on query hits from this node if it is a leaf.
paulo@0 499 *
paulo@0 500 * Mangling query hits would break any future checksum or signing algorithm on
paulo@0 501 * query hits though, so that isn't done.
paulo@0 502 */
paulo@0 503 void gt_connect_test (GtNode *node, in_port_t port)
paulo@0 504 {
paulo@0 505 TCPC *new_c;
paulo@0 506
paulo@0 507 if (!port)
paulo@0 508 {
paulo@0 509 node->firewalled = TRUE;
paulo@0 510 return;
paulo@0 511 }
paulo@0 512
paulo@0 513 /* this needs some kind of local mode switch */
paulo@0 514 #if 0
paulo@0 515 if (net_match_host (GT_NODE(c)->ip, "LOCAL"))
paulo@0 516 {
paulo@0 517 GT_NODE(c)->firewalled = TRUE;
paulo@0 518 return;
paulo@0 519 }
paulo@0 520 #endif
paulo@0 521
paulo@0 522 if (!node->incoming)
paulo@0 523 return;
paulo@0 524
paulo@0 525 GT->DBGFN (GT, "starting connect test on %s:%hu",
paulo@0 526 net_ip_str (node->ip), port);
paulo@0 527
paulo@0 528 if (!(new_c = tcp_open (node->ip, port, FALSE)))
paulo@0 529 {
paulo@0 530 GT->DBGFN (GT, "failed to open test connection to %s:%hu",
paulo@0 531 net_ip_str (node->ip), node->gt_port);
paulo@0 532 return;
paulo@0 533 }
paulo@0 534
paulo@0 535 if (node->gt_port_verify)
paulo@0 536 tcp_close (node->gt_port_verify);
paulo@0 537
paulo@0 538 /* keep track of this connection */
paulo@0 539 node->gt_port_verify = new_c;
paulo@0 540 new_c->udata = node;
paulo@0 541
paulo@0 542 input_add (new_c->fd, new_c, INPUT_WRITE,
paulo@0 543 (InputCallback)test_connectable, TIMEOUT_DEF);
paulo@0 544 }