paulo@0: /* paulo@0: * $Id: gt_node.c,v 1.59 2005/01/04 15:00:51 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: paulo@0: #include "gt_node.h" paulo@0: #include "gt_node_list.h" paulo@0: paulo@0: #include "gt_utils.h" paulo@0: paulo@0: #include "gt_packet.h" paulo@0: #include "gt_query_route.h" paulo@0: #include "gt_share_state.h" paulo@0: paulo@0: #include "gt_node_cache.h" paulo@0: paulo@0: #include "io/rx_stack.h" /* gt_rx_stack_free */ paulo@0: #include "io/tx_stack.h" /* gt_tx_stack_free, gt_tx_stack_queue */ paulo@0: paulo@0: #include "transfer/push_proxy.h" paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* maps ids -> node, so we dont have to keep TCPC ptrs in paulo@0: * various data structures */ paulo@0: static Dataset *node_ids; paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void node_add (GtNode *node) paulo@0: { paulo@0: if (!node_ids) paulo@0: node_ids = dataset_new (DATASET_HASH); paulo@0: paulo@0: if (!node->ip) paulo@0: return; paulo@0: paulo@0: dataset_insert (&node_ids, &node->ip, sizeof (node->ip), node, 0); paulo@0: } paulo@0: paulo@0: static void node_remove (GtNode *node) paulo@0: { paulo@0: if (!node) paulo@0: return; paulo@0: paulo@0: if (!node->ip) paulo@0: return; paulo@0: paulo@0: dataset_remove (node_ids, &node->ip, sizeof (node->ip)); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: GtNode *gt_node_new () paulo@0: { paulo@0: GtNode *node; paulo@0: paulo@0: if (!(node = MALLOC (sizeof (GtNode)))) paulo@0: return NULL; paulo@0: paulo@0: return node; paulo@0: } paulo@0: paulo@0: static void free_node (GtNode *node) paulo@0: { paulo@0: if (!node) paulo@0: return; paulo@0: paulo@0: gt_node_disconnect (GT_CONN(node)); paulo@0: gt_conn_remove (node); paulo@0: paulo@0: free (node); paulo@0: } paulo@0: paulo@0: /* NOTE: this isnt safe to call at all times */ paulo@0: void gt_node_free (GtNode *node) paulo@0: { paulo@0: node_remove (node); paulo@0: free_node (node); paulo@0: } paulo@0: paulo@0: /* set the node to use the TCP connection */ paulo@0: void gt_node_connect (GtNode *node, TCPC *c) paulo@0: { paulo@0: assert (GT_CONN(node) == NULL); paulo@0: assert (GT_NODE(c) == NULL); paulo@0: paulo@0: node->c = c; paulo@0: c->udata = node; paulo@0: } paulo@0: paulo@0: /* put the node into some data structures to keep track of it */ paulo@0: static void track_node (GtNode *node, TCPC *c) paulo@0: { paulo@0: if (node->ip) paulo@0: assert (node->ip == c->host); paulo@0: paulo@0: /* fill in peer info (in network byte order) */ paulo@0: node->ip = c->host; paulo@0: assert (node->ip != 0); paulo@0: paulo@0: gt_conn_add (node); paulo@0: node_add (node); paulo@0: } paulo@0: paulo@0: /* instantiate a node from an existing connection */ paulo@0: GtNode *gt_node_instantiate (TCPC *c) paulo@0: { paulo@0: GtNode *node; paulo@0: BOOL existed = FALSE; paulo@0: paulo@0: if (!c || !c->host) paulo@0: return NULL; paulo@0: paulo@0: /* TODO: We should really lookup the port in Listen-IP header, right? */ paulo@0: node = gt_node_lookup (c->host, 0); paulo@0: paulo@0: if (node) paulo@0: { paulo@0: existed = TRUE; paulo@0: paulo@0: /* abort if already connected/connecting */ paulo@0: if (node->state != GT_NODE_DISCONNECTED) paulo@0: return NULL; paulo@0: } paulo@0: else paulo@0: { paulo@0: if (!(node = gt_node_new ())) paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: assert (node->c == NULL); paulo@0: paulo@0: /* attach this node to the connection and vice-versa */ paulo@0: gt_node_connect (node, c); paulo@0: paulo@0: if (!existed) paulo@0: track_node (node, c); paulo@0: paulo@0: return node; paulo@0: } paulo@0: paulo@0: static int free_one (ds_data_t *key, ds_data_t *value, void *udata) paulo@0: { paulo@0: GtNode *node = value->data; paulo@0: paulo@0: /* don't call gt_node_free here while iterating through the paulo@0: * Dataset because it will cause us to miss items when the paulo@0: * Dataset is resized */ paulo@0: free_node (node); paulo@0: paulo@0: return DS_CONTINUE | DS_REMOVE; paulo@0: } paulo@0: paulo@0: void gt_node_remove_all (void) paulo@0: { paulo@0: dataset_foreach_ex (node_ids, DS_FOREACH_EX(free_one), NULL); paulo@0: dataset_clear (node_ids); paulo@0: node_ids = NULL; paulo@0: } paulo@0: paulo@0: BOOL gt_node_freeable (GtNode *node) paulo@0: { paulo@0: time_t now; paulo@0: paulo@0: if (node->state != GT_NODE_DISCONNECTED) paulo@0: return FALSE; paulo@0: paulo@0: now = time (NULL); paulo@0: paulo@0: /* keep hosts with whom we've had a connection for a good while */ paulo@0: if (node->vitality > 0 && now - node->vitality <= 30 * EDAYS) paulo@0: return FALSE; paulo@0: paulo@0: if (now - node->start_connect_time <= 30 * EMINUTES) paulo@0: return FALSE; paulo@0: paulo@0: /* yeah, sure, free the node if you want */ paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Check if this node supports the vendor message packet inside 'pkt', paulo@0: * and then send the vendor message if so. paulo@0: * paulo@0: * The 'version' field of the VMSG is mangled to be the minimum supported paulo@0: * by both this node and the remote end. paulo@0: */ paulo@0: BOOL gt_node_send_if_supported (GtNode *node, GtPacket *pkt) paulo@0: { paulo@0: gt_vendor_msg_t vmsg; paulo@0: unsigned char *vendor; paulo@0: uint16_t id; paulo@0: uint16_t ver; paulo@0: uint16_t *send_ver; paulo@0: paulo@0: gt_packet_seek (pkt, GNUTELLA_HDR_LEN); paulo@0: vendor = gt_packet_get_ustr (pkt, 4); paulo@0: id = gt_packet_get_uint16 (pkt); paulo@0: ver = gt_packet_get_uint16 (pkt); paulo@0: paulo@0: if (gt_packet_error (pkt)) paulo@0: return FALSE; paulo@0: paulo@0: memset (&vmsg, 0, sizeof(vmsg)); paulo@0: memcpy (&vmsg.vendor_id, vendor, 4); paulo@0: vmsg.id = id; paulo@0: paulo@0: send_ver = dataset_lookup (node->vmsgs_supported, &vmsg, sizeof(vmsg)); paulo@0: if (!send_ver) paulo@0: return FALSE; paulo@0: paulo@0: /* XXX: we've no good facility for writing in the middle of the packet */ paulo@0: memcpy (&pkt->data[GNUTELLA_HDR_LEN + VMSG_HDR_LEN - 2], send_ver, 2); paulo@0: paulo@0: if (gt_packet_send (GT_CONN(node), pkt) < 0) paulo@0: return FALSE; paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: BOOL gt_node_send (GtNode *node, GtPacket *packet) paulo@0: { paulo@0: /* don't queue the packet if the node isn't in a state to send it */ paulo@0: if (!(node->state & (GT_NODE_CONNECTED | GT_NODE_CONNECTING_2))) paulo@0: return FALSE; paulo@0: paulo@0: /* enable this at some point in the future */ paulo@0: #if 0 paulo@0: assert (GT_CONN(node) != NULL); paulo@0: #endif paulo@0: if (!GT_CONN(node) || GT_CONN(node)->fd < 0) paulo@0: return FALSE; paulo@0: paulo@0: return gt_tx_stack_queue (node->tx_stack, packet->data, packet->len); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: GtNode *gt_node_lookup (in_addr_t ip, in_port_t port) paulo@0: { paulo@0: return dataset_lookup (node_ids, &ip, sizeof (ip)); paulo@0: } paulo@0: paulo@0: GtNode *gt_node_register (in_addr_t ip, in_port_t port, paulo@0: gt_node_class_t klass) paulo@0: { paulo@0: GtNode *node; paulo@0: paulo@0: if (GNUTELLA_LOCAL_MODE) paulo@0: { paulo@0: if (!net_match_host (ip, "LOCAL")) paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: if (!ip) paulo@0: return NULL; paulo@0: paulo@0: /* TODO: there is probably a problem here if a node is already paulo@0: * connected and we're informed about it falsely some other way. */ paulo@0: if ((node = dataset_lookup (node_ids, &ip, sizeof (ip)))) paulo@0: { paulo@0: if (klass != GT_NODE_NONE) paulo@0: gt_node_class_set (node, klass); paulo@0: paulo@0: return node; paulo@0: } paulo@0: paulo@0: if (!(node = gt_node_new ())) paulo@0: return NULL; paulo@0: paulo@0: node->ip = ip; paulo@0: node->gt_port = port; paulo@0: paulo@0: node_add (node); paulo@0: gt_conn_add (node); paulo@0: paulo@0: if (klass != GT_NODE_NONE) paulo@0: gt_node_class_set (node, klass); paulo@0: paulo@0: /* remove this node from the node cache in order to keep the cache paulo@0: * conherent with the node list */ paulo@0: gt_node_cache_del_ipv4 (ip, port); paulo@0: paulo@0: return node; paulo@0: } paulo@0: paulo@0: void gt_node_error (TCPC *c, const char *fmt, ...) paulo@0: { paulo@0: static char buf[4096]; paulo@0: va_list args; paulo@0: paulo@0: assert (GT_CONN(GT_NODE(c)) == c); paulo@0: paulo@0: if (!fmt) paulo@0: { paulo@0: GT->DBGSOCK (GT, c, "[%hu] error: %s", GT_NODE(c)->gt_port, paulo@0: GIFT_NETERROR ()); paulo@0: return; paulo@0: } paulo@0: paulo@0: va_start (args, fmt); paulo@0: vsnprintf (buf, sizeof (buf) - 1, fmt, args); paulo@0: va_end (args); paulo@0: paulo@0: GT->DBGSOCK (GT, c, "error: %s", buf); paulo@0: } paulo@0: paulo@0: void gt_node_disconnect (TCPC *c) paulo@0: { paulo@0: GtNode *node; paulo@0: paulo@0: if (!c) paulo@0: return; paulo@0: paulo@0: node = GT_NODE(c); paulo@0: assert (node->c == c); paulo@0: paulo@0: /* remove node timers */ paulo@0: timer_remove_zero (&node->handshake_timer); paulo@0: timer_remove_zero (&node->search_timer); paulo@0: timer_remove_zero (&node->query_route_timer); paulo@0: paulo@0: /* destroy existing received buffers for this connection */ paulo@0: gt_rx_stack_free (node->rx_stack); paulo@0: node->rx_stack = NULL; paulo@0: paulo@0: /* destroy send buffers */ paulo@0: gt_tx_stack_free (node->tx_stack); paulo@0: node->tx_stack = NULL; paulo@0: paulo@0: /* remove the node from push proxy status */ paulo@0: gt_push_proxy_del (node); paulo@0: paulo@0: /* reset connection status flags */ paulo@0: node->verified = FALSE; paulo@0: node->firewalled = FALSE; paulo@0: node->incoming = FALSE; paulo@0: node->rx_inflated = FALSE; paulo@0: node->tx_deflated = FALSE; paulo@0: node->vmsgs_sent = FALSE; paulo@0: paulo@0: /* close the connection for this node, if any */ paulo@0: tcp_close_null (&node->c); paulo@0: paulo@0: node->pings_with_noreply = 0; paulo@0: paulo@0: /* clear verification connection */ paulo@0: tcp_close_null (&node->gt_port_verify); paulo@0: paulo@0: free (node->ping_guid); paulo@0: node->ping_guid = NULL; paulo@0: paulo@0: dataset_clear (node->hdr); paulo@0: node->hdr = NULL; paulo@0: paulo@0: dataset_clear (node->vmsgs_supported); paulo@0: node->vmsgs_supported = NULL; paulo@0: paulo@0: gt_share_state_free (node->share_state); paulo@0: node->share_state = NULL; paulo@0: paulo@0: gt_query_router_free (node->query_router); paulo@0: node->query_router = NULL; paulo@0: node->query_router_counter = 0; paulo@0: paulo@0: /* update the last time if this node was connected */ paulo@0: node->last_connect_duration = time (NULL) - node->start_connect_time; paulo@0: node->total_connect_duration += node->last_connect_duration; paulo@0: paulo@0: gt_node_state_set (node, GT_NODE_DISCONNECTED); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: char *gt_node_class_str (gt_node_class_t klass) paulo@0: { paulo@0: switch (klass) paulo@0: { paulo@0: case GT_NODE_NONE: return "NONE"; paulo@0: case GT_NODE_LEAF: return "LEAF"; paulo@0: case GT_NODE_ULTRA: return "ULTRAPEER"; paulo@0: case GT_NODE_DEAD: return "DEAD (freeing node)"; paulo@0: default: return ""; paulo@0: } paulo@0: } paulo@0: paulo@0: char *gt_node_state_str (gt_node_state_t state) paulo@0: { paulo@0: switch (state) paulo@0: { paulo@0: case GT_NODE_DISCONNECTED: return "Disconnected"; paulo@0: case GT_NODE_CONNECTING_1: return "Connecting (handshaking)"; paulo@0: case GT_NODE_CONNECTING_2: return "Connecting (awaiting ping response)"; paulo@0: case GT_NODE_CONNECTED: return "Connected"; paulo@0: default: return ""; paulo@0: } paulo@0: } paulo@0: paulo@0: char *gt_node_str (GtNode *node) paulo@0: { paulo@0: static char buf[128]; paulo@0: paulo@0: snprintf (buf, sizeof (buf) - 1, "%s:%hu", net_ip_str (node->ip), paulo@0: node->gt_port); paulo@0: paulo@0: return buf; paulo@0: } paulo@0: paulo@0: void gt_node_state_set (GtNode *node, gt_node_state_t state) paulo@0: { paulo@0: gt_node_state_t old_state = node->state; paulo@0: paulo@0: if (old_state != state) paulo@0: { paulo@0: node->state = state; paulo@0: gt_conn_set_state (node, old_state, state); paulo@0: } paulo@0: } paulo@0: paulo@0: void gt_node_class_set (GtNode *node, gt_node_class_t klass) paulo@0: { paulo@0: gt_node_class_t old_class = node->klass; paulo@0: paulo@0: if (old_class != klass) paulo@0: { paulo@0: /* quiet, please */ paulo@0: #if 0 paulo@0: if (old_class == GT_NODE_NONE) paulo@0: { paulo@0: GT->dbg (GT, "%-24s %s", gt_node_str (node), paulo@0: gt_node_class_str (klass)); paulo@0: } paulo@0: else paulo@0: { paulo@0: GT->dbg (GT, "%-24s %s (%s)", gt_node_str (node), paulo@0: gt_node_class_str (klass), gt_node_class_str (old_class)); paulo@0: } paulo@0: #endif paulo@0: paulo@0: node->klass = klass; paulo@0: gt_conn_set_class (node, old_class, klass); paulo@0: } paulo@0: }