paulo@0: /* paulo@0: * $Id: gt_connect.c,v 1.55 2005/01/04 15:03:40 mkern Exp $ paulo@0: * paulo@0: * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) paulo@0: * paulo@0: * This program is free software; you can redistribute it and/or modify it paulo@0: * under the terms of the GNU General Public License as published by the paulo@0: * Free Software Foundation; either version 2, or (at your option) any paulo@0: * later version. paulo@0: * paulo@0: * This program is distributed in the hope that it will be useful, but paulo@0: * WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU paulo@0: * General Public License for more details. paulo@0: */ paulo@0: paulo@0: #include "gt_gnutella.h" paulo@0: #include "gt_version.h" paulo@0: paulo@0: #include "gt_connect.h" paulo@0: #include "gt_accept.h" paulo@0: #include "gt_packet.h" paulo@0: paulo@0: #include "gt_node.h" paulo@0: #include "gt_node_list.h" paulo@0: #include "gt_utils.h" paulo@0: paulo@0: #include "gt_search.h" paulo@0: #include "gt_netorg.h" paulo@0: paulo@0: #include "gt_node_cache.h" paulo@0: paulo@0: #include "message/gt_message.h" /* gnutella_start_connection */ paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void send_connect (int fd, input_id id, TCPC *c); paulo@0: static void recv_headers (int fd, input_id id, TCPC *c); paulo@0: static void send_response (int fd, input_id id, TCPC *c); paulo@0: static BOOL send_final (TCPC *c); paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static BOOL handshake_timeout (TCPC *c) paulo@0: { paulo@0: GtNode *node = GT_NODE(c); paulo@0: paulo@0: node->handshake_timer = 0; paulo@0: paulo@0: if (!(node->state & GT_NODE_CONNECTED)) paulo@0: { paulo@0: gt_node_disconnect (c); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: void gnutella_set_handshake_timeout (TCPC *c, time_t delay) paulo@0: { paulo@0: timer_remove (GT_NODE(c)->handshake_timer); paulo@0: paulo@0: GT_NODE(c)->handshake_timer = timer_add (delay, paulo@0: (TimerCallback)handshake_timeout, paulo@0: c); paulo@0: } paulo@0: paulo@0: int gt_connect (GtNode *node) paulo@0: { paulo@0: TCPC *c; paulo@0: paulo@0: if (!node) paulo@0: return -1; paulo@0: paulo@0: #if 0 paulo@0: if (GT_CONN(node) != NULL) paulo@0: { paulo@0: GT->dbg (GT, "duplicate connection?: %p", GT_CONN(node)); paulo@0: return -1; paulo@0: } paulo@0: paulo@0: if (node->state != GT_NODE_DISCONNECTED) paulo@0: { paulo@0: GT->dbg (GT, "state = %i??", node->state); paulo@0: return -1; paulo@0: } paulo@0: #endif paulo@0: paulo@0: /* this must be called only on disconnected nodes */ paulo@0: assert (GT_CONN(node) == NULL); paulo@0: assert (node->state == GT_NODE_DISCONNECTED); paulo@0: paulo@0: #if 0 paulo@0: if (!conn_auth (c, TRUE)) paulo@0: return -1; paulo@0: #endif paulo@0: paulo@0: /* set this early: gt_netorg relies on this being set in order paulo@0: * to check if it should access the gwebcaches */ paulo@0: node->start_connect_time = time (NULL); paulo@0: paulo@0: /* make sure port is valid */ paulo@0: if (node->gt_port == 0) paulo@0: { paulo@0: GT->DBGFN (GT, "bad port on node %s", net_ip_str (node->ip)); paulo@0: return -1; paulo@0: } paulo@0: paulo@0: /* make outgoing connection */ paulo@0: if (!(c = tcp_open (node->ip, node->gt_port, FALSE))) paulo@0: return -1; paulo@0: paulo@0: gt_node_connect (node, c); paulo@0: paulo@0: gt_node_state_set (node, GT_NODE_CONNECTING_1); paulo@0: node->incoming = FALSE; paulo@0: paulo@0: /* set the connection timeout */ paulo@0: gnutella_set_handshake_timeout (c, TIMEOUT_1 * SECONDS); paulo@0: paulo@0: input_add (c->fd, c, INPUT_WRITE, paulo@0: (InputCallback)send_connect, 0); paulo@0: paulo@0: return c->fd; paulo@0: } paulo@0: paulo@0: static void send_connect (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* Send the connect string along with our headers */ paulo@0: if (!gnutella_send_connection_headers (c, "GNUTELLA CONNECT/0.6")) paulo@0: { paulo@0: gt_node_error (c, NULL); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* we connected ok, so give the peer some more time */ paulo@0: gnutella_set_handshake_timeout (c, TIMEOUT_2 * SECONDS); paulo@0: paulo@0: input_remove (id); paulo@0: input_add (fd, c, INPUT_READ, paulo@0: (InputCallback)recv_headers, 0); paulo@0: } paulo@0: paulo@0: BOOL gnutella_parse_response_headers (char *reply, Dataset **r_headers) paulo@0: { paulo@0: int code; /* 200, 404, ... */ paulo@0: char *response; paulo@0: Dataset *headers = NULL; paulo@0: paulo@0: response = string_sep (&reply, "\r\n"); paulo@0: paulo@0: if (!response) paulo@0: return FALSE; paulo@0: paulo@0: /* */ string_sep (&response, " "); /* shift past HTTP/1.1 */ paulo@0: code = ATOI (string_sep (&response, " ")); /* shift past 200 */ paulo@0: paulo@0: /* parse the headers */ paulo@0: gt_http_header_parse (reply, &headers); paulo@0: paulo@0: if (r_headers) paulo@0: *r_headers = headers; paulo@0: else paulo@0: dataset_clear (headers); paulo@0: paulo@0: if (code >= 200 && code <= 299) paulo@0: return TRUE; paulo@0: paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: static time_t parse_uptime (Dataset *d) paulo@0: { paulo@0: char *str; paulo@0: int days, hours, mins; paulo@0: int n; paulo@0: paulo@0: if (!(str = dataset_lookupstr (d, "uptime"))) paulo@0: return 0; paulo@0: paulo@0: string_lower (str); paulo@0: paulo@0: if ((n = sscanf (str, "%dd %dh %dm", &days, &hours, &mins)) != 3) paulo@0: return 0; paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: { paulo@0: GT->dbg (GT, "uptime parsed: %d days, %d hours, %d minutes", paulo@0: days, hours, mins); paulo@0: } paulo@0: paulo@0: return days * EDAYS + hours * EHOURS + mins * EMINUTES; paulo@0: } paulo@0: paulo@0: /* look in a header field for nodes, and register them */ paulo@0: static void extract_nodes (Dataset *d, in_addr_t src, paulo@0: const char *field, gt_node_class_t klass) paulo@0: { paulo@0: char *str; paulo@0: char *value; paulo@0: time_t now; paulo@0: paulo@0: now = time (NULL); paulo@0: paulo@0: if (!(str = dataset_lookupstr (d, field))) paulo@0: return; paulo@0: paulo@0: while ((value = string_sep (&str, ","))) paulo@0: { paulo@0: in_addr_t ip; paulo@0: in_port_t port; paulo@0: paulo@0: ip = net_ip (string_sep (&value, ":")); paulo@0: port = ATOI (value); paulo@0: paulo@0: if (port == (in_port_t) -1 || port == 0) paulo@0: continue; paulo@0: paulo@0: if (ip == INADDR_NONE || ip == 0) paulo@0: continue; paulo@0: paulo@0: if (gt_is_local_ip (ip, src)) paulo@0: continue; paulo@0: paulo@0: gt_node_cache_add_ipv4 (ip, port, klass, now, 0, src); paulo@0: } paulo@0: paulo@0: gt_node_cache_trace (); paulo@0: } paulo@0: paulo@0: static void recv_headers (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: FDBuf *buf; paulo@0: char *response; paulo@0: size_t response_len = 0; paulo@0: int n; paulo@0: BOOL ok; paulo@0: time_t uptime; paulo@0: GtNode *node = GT_NODE(c); paulo@0: paulo@0: buf = tcp_readbuf (c); paulo@0: paulo@0: if ((n = fdbuf_delim (buf, "\n")) < 0) paulo@0: { paulo@0: GT->DBGFN (GT, "error reading headers: %s", GIFT_NETERROR ()); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (gt_fdbuf_full (buf)) paulo@0: { paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (n > 0) paulo@0: return; paulo@0: paulo@0: response = fdbuf_data (buf, &response_len); paulo@0: if (!gt_http_header_terminated (response, response_len)) paulo@0: return; paulo@0: paulo@0: fdbuf_release (buf); paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "node handshake response:\n%s", response); paulo@0: paulo@0: /* parse and store the response */ paulo@0: ok = gnutella_parse_response_headers (response, &node->hdr); paulo@0: paulo@0: /* extract nodes */ paulo@0: extract_nodes (node->hdr, node->ip, "x-try-ultrapeers", GT_NODE_ULTRA); paulo@0: extract_nodes (node->hdr, node->ip, "x-try", GT_NODE_NONE); paulo@0: paulo@0: /* grab the uptime from the "Uptime: " header and update this node */ paulo@0: if ((uptime = parse_uptime (node->hdr)) > 0) paulo@0: { paulo@0: gt_node_cache_add_ipv4 (node->ip, node->gt_port, paulo@0: GT_NODE_ULTRA, time (NULL), uptime, node->ip); paulo@0: paulo@0: /* XXX: remove the item immediately so we trigger the side effect of paulo@0: * adding this node to the stable list */ paulo@0: gt_node_cache_del_ipv4 (node->ip, node->gt_port); paulo@0: } paulo@0: paulo@0: if (!ok) paulo@0: { paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: input_remove (id); paulo@0: input_add (fd, c, INPUT_WRITE, paulo@0: (InputCallback)send_response, 0); paulo@0: } paulo@0: paulo@0: static void send_response (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: gt_node_error (c, NULL); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (!gnutella_auth_connection (c)) paulo@0: { paulo@0: gt_node_error (c, "[outgoing] connection not authorized"); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (!send_final (c)) paulo@0: { paulo@0: gt_node_error (c, NULL); paulo@0: GT->DBGSOCK (GT, c, "error at stage 3 of handshake"); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* ok, startup this connection */ paulo@0: input_remove (id); paulo@0: input_add (fd, c, INPUT_WRITE, paulo@0: (InputCallback)gnutella_start_connection, 0); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static GtNode *append_node (TCPC *c, GtNode *node, String *s) paulo@0: { paulo@0: if (s->str[s->len - 1] != ' ') paulo@0: string_append (s, ","); paulo@0: paulo@0: string_appendf (s, "%s:%hu", net_ip_str (node->ip), node->gt_port); paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: static void append_crawler_headers (String *msg) paulo@0: { paulo@0: if (gt_conn_length (GT_NODE_ULTRA, GT_NODE_CONNECTED) > 0) paulo@0: { paulo@0: string_append (msg, "Peers: "); paulo@0: gt_conn_foreach (GT_CONN_FOREACH(append_node), msg, paulo@0: GT_NODE_ULTRA, GT_NODE_CONNECTED, 0); paulo@0: string_append (msg, "\r\n"); paulo@0: } paulo@0: paulo@0: if (GT_SELF->klass & GT_NODE_ULTRA && paulo@0: gt_conn_length (GT_NODE_LEAF, GT_NODE_CONNECTED) > 0) paulo@0: { paulo@0: string_append (msg, "Leaves: "); paulo@0: gt_conn_foreach (GT_CONN_FOREACH(append_node), msg, paulo@0: GT_NODE_LEAF, GT_NODE_CONNECTED, 0); paulo@0: string_append (msg, "\r\n"); paulo@0: } paulo@0: } paulo@0: paulo@0: BOOL gnutella_send_connection_headers (TCPC *c, const char *header) paulo@0: { paulo@0: String *msg; paulo@0: paulo@0: if (!(msg = string_new (NULL, 0, 0, TRUE))) paulo@0: return FALSE; paulo@0: paulo@0: string_appendf (msg, "%s\r\n", header); paulo@0: paulo@0: string_append (msg, "X-Query-Routing: 0.1\r\n"); paulo@0: string_appendf (msg, "X-Ultrapeer: %s\r\n", paulo@0: (GT_SELF->klass & GT_NODE_ULTRA) ? "True" : "False"); paulo@0: paulo@0: /* append the client and version we are using */ paulo@0: string_appendf (msg, "User-Agent: %s\r\n", gt_version ()); paulo@0: paulo@0: /* Add a header describing the remote IP of the peer */ paulo@0: string_appendf (msg, "Remote-IP: %s\r\n", net_peer_ip (c->fd)); paulo@0: paulo@0: /* let remote end know it's ok to send vendor messages */ paulo@0: string_appendf (msg, "Vendor-Message: 0.1\r\n"); paulo@0: paulo@0: /* support transmission of pings/pongs with GGEP appended */ paulo@0: string_append (msg, "GGEP: 0.5\r\n"); paulo@0: paulo@0: /* If this is the limewire crawler, append "Peers: " and "Leaves: " paulo@0: * headers and close the connection */ paulo@0: if (!c->outgoing && dataset_lookupstr (GT_NODE(c)->hdr, "crawler")) paulo@0: append_crawler_headers (msg); paulo@0: paulo@0: /* append willingness to receive compressed data */ paulo@0: string_append (msg, "Accept-Encoding: deflate\r\n"); paulo@0: paulo@0: /* check whether the remote node sent us Accept-Encoding: deflate paulo@0: * already */ paulo@0: gnutella_mark_compression (GT_NODE(c)); paulo@0: paulo@0: /* compress data if we must */ paulo@0: if (GT_NODE(c)->tx_deflated) paulo@0: string_append (msg, "Content-Encoding: deflate\r\n"); paulo@0: paulo@0: /* Add message terminator */ paulo@0: string_append (msg, "\r\n"); paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "sending node headers:\n%s", msg->str); paulo@0: paulo@0: if (tcp_send (c, msg->str, msg->len) <= 0) paulo@0: { paulo@0: string_free (msg); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: string_free (msg); paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: static BOOL send_final (TCPC *c) paulo@0: { paulo@0: String *s; paulo@0: int ret; paulo@0: int len; paulo@0: paulo@0: if (!(s = string_new (NULL, 0, 0, TRUE))) paulo@0: return FALSE; paulo@0: paulo@0: /* append header acceptance line */ paulo@0: string_append (s, "GNUTELLA/0.6 200 OK\r\n"); paulo@0: paulo@0: /* mark the connection as complete */ paulo@0: gnutella_mark_compression (GT_NODE(c)); paulo@0: paulo@0: if (GT_NODE(c)->tx_deflated) paulo@0: string_append (s, "Content-Encoding: deflate\r\n"); paulo@0: paulo@0: /* append msg terminator */ paulo@0: string_append (s, "\r\n"); paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "sending final handshake:\n%s", s->str); paulo@0: paulo@0: len = s->len; paulo@0: ret = tcp_send (c, s->str, s->len); paulo@0: paulo@0: string_free (s); paulo@0: paulo@0: if (ret != len) paulo@0: return FALSE; paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: /* CONNECTABILITY TESTING */ paulo@0: paulo@0: static void connect_test_result (GtNode *node, TCPC *c, BOOL success) paulo@0: { paulo@0: GT->DBGFN (GT, "connect test to %s %s", net_ip_str (node->ip), paulo@0: (success ? "succeeded" : "failed")); paulo@0: paulo@0: node->firewalled = (success ? FALSE : TRUE); paulo@0: node->verified = TRUE; paulo@0: paulo@0: if (c) paulo@0: { paulo@0: tcp_close (c); paulo@0: node->gt_port_verify = NULL; paulo@0: } paulo@0: } paulo@0: paulo@0: static void test_connectable (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: GtNode *node; paulo@0: paulo@0: node = c->udata; paulo@0: paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: connect_test_result (node, c, FALSE); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Send two newlines, because some firewalls will let connections pass paulo@0: * through, but no data. paulo@0: */ paulo@0: tcp_send (c, "\n\n", 2); paulo@0: connect_test_result (node, c, TRUE); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Test if the port of a peer we are connected to is connectable. This lets a paulo@0: * node know if it's firewalled. We could use this info to mangle the 'push' paulo@0: * flag on query hits from this node if it is a leaf. paulo@0: * paulo@0: * Mangling query hits would break any future checksum or signing algorithm on paulo@0: * query hits though, so that isn't done. paulo@0: */ paulo@0: void gt_connect_test (GtNode *node, in_port_t port) paulo@0: { paulo@0: TCPC *new_c; paulo@0: paulo@0: if (!port) paulo@0: { paulo@0: node->firewalled = TRUE; paulo@0: return; paulo@0: } paulo@0: paulo@0: /* this needs some kind of local mode switch */ paulo@0: #if 0 paulo@0: if (net_match_host (GT_NODE(c)->ip, "LOCAL")) paulo@0: { paulo@0: GT_NODE(c)->firewalled = TRUE; paulo@0: return; paulo@0: } paulo@0: #endif paulo@0: paulo@0: if (!node->incoming) paulo@0: return; paulo@0: paulo@0: GT->DBGFN (GT, "starting connect test on %s:%hu", paulo@0: net_ip_str (node->ip), port); paulo@0: paulo@0: if (!(new_c = tcp_open (node->ip, port, FALSE))) paulo@0: { paulo@0: GT->DBGFN (GT, "failed to open test connection to %s:%hu", paulo@0: net_ip_str (node->ip), node->gt_port); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (node->gt_port_verify) paulo@0: tcp_close (node->gt_port_verify); paulo@0: paulo@0: /* keep track of this connection */ paulo@0: node->gt_port_verify = new_c; paulo@0: new_c->udata = node; paulo@0: paulo@0: input_add (new_c->fd, new_c, INPUT_WRITE, paulo@0: (InputCallback)test_connectable, TIMEOUT_DEF); paulo@0: }