Show nodes.c syntax highlighted
/*
* $Id: nodes.c 14737 2007-09-02 00:32:36Z cbiere $
*
* Copyright (c) 2001-2004, Raphael Manfredi
*
*----------------------------------------------------------------------
* This file is part of gtk-gnutella.
*
* gtk-gnutella is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gtk-gnutella is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gtk-gnutella; if not, write to the Free Software
* Foundation, Inc.:
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*----------------------------------------------------------------------
*/
/**
* @ingroup core
* @file
*
* Gnutella node management.
*
* @author Raphael Manfredi
* @date 2001-2004
*/
#include "common.h"
RCSID("$Id: nodes.c 14737 2007-09-02 00:32:36Z cbiere $")
#include <zlib.h> /* Z_DEFAULT_COMPRESSION, Z_OK */
#include "sockets.h"
#include "search.h"
#include "share.h"
#include "routing.h"
#include "hosts.h"
#include "nodes.h"
#include "gmsg.h"
#include "mq.h"
#include "mq_tcp.h"
#include "mq_udp.h"
#include "sq.h"
#include "tx.h"
#include "tx_link.h"
#include "tx_deflate.h"
#include "tx_dgram.h"
#include "rxbuf.h"
#include "rx.h"
#include "rx_link.h"
#include "rx_inflate.h"
#include "pmsg.h"
#include "pcache.h"
#include "bsched.h"
#include "http.h"
#include "version.h"
#include "alive.h"
#include "uploads.h" /* For handle_push_request() */
#include "whitelist.h"
#include "gnet_stats.h"
#include "ban.h"
#include "hcache.h"
#include "qrp.h"
#include "vmsg.h"
#include "token.h"
#include "hostiles.h"
#include "clock.h"
#include "hsep.h"
#include "dq.h"
#include "dh.h"
#include "ioheader.h"
#include "settings.h"
#include "features.h"
#include "udp.h"
#include "tsync.h"
#include "geo_ip.h"
#include "extensions.h"
#include "bh_upload.h"
#include "tls_cache.h"
#include "lib/adns.h"
#include "lib/aging.h"
#include "lib/atoms.h"
#include "lib/cq.h"
#include "lib/dbus_util.h"
#include "lib/file.h"
#include "lib/getdate.h"
#include "lib/hashlist.h"
#include "lib/iovec.h"
#include "lib/endian.h"
#include "lib/getline.h"
#include "lib/glib-missing.h"
#include "lib/header.h"
#include "lib/listener.h"
#include "lib/misc.h"
#include "lib/tm.h"
#include "lib/utf8.h"
#include "lib/walloc.h"
#include "lib/zlib_util.h"
#include "if/gnet_property.h"
#include "if/gnet_property_priv.h"
#include "lib/override.h" /* Must be the last header included */
#define CONNECT_PONGS_COUNT 10 /**< Amoung of pongs to send */
#define CONNECT_PONGS_LOW 5 /**< Amoung of pongs sent if saturated */
#define BYE_MAX_SIZE 4096 /**< Maximum size for the Bye message */
#define NODE_SEND_BUFSIZE 4096 /**< TCP send buffer size - 4K */
#define NODE_SEND_LEAF_BUFSIZE 1024 /**< TCP send buffer size for leaves */
#define MAX_GGEP_PAYLOAD 1536 /**< In ping, pong, push */
#define MAX_HOP_COUNT 255 /**< Architecturally defined maximum */
#define NODE_LEGACY_DEGREE 8 /**< Older node without X-Degree */
#define NODE_LEGACY_TTL 7 /**< Older node without X-Max-TTL */
#define NODE_USELESS_GRACE 20 /**< No kick if condition too recent */
#define NODE_UP_USELESS_GRACE 600 /**< No kick if condition too recent */
#define SHUTDOWN_GRACE_DELAY 120 /**< Grace time for shutdowning nodes */
#define BYE_GRACE_DELAY 30 /**< Bye sent, give time to propagate */
#define MAX_WEIRD_MSG 5 /**< End link after so much weirds */
#define MAX_TX_RX_RATIO 85 /**< Max TX/RX ratio for shortage */
#define MIN_TX_FOR_RATIO 1000 /**< TX packets before enforcing ratio */
#define ALIVE_PERIOD 20 /**< Seconds between each alive ping */
#define ALIVE_PERIOD_LEAF 120 /**< Idem, for leaves <-> ultrapeers */
#define ALIVE_MAX_PENDING 6 /**< Max unanswered pings in a row */
#define ALIVE_MAX_PENDING_LEAF 4 /**< Max unanswered pings in a row (leaves) */
#define NODE_MIN_UP_CONNECTIONS 25 /**< Min 25 peer connections for UP */
#define NODE_MIN_UPTIME 3600 /**< Minumum uptime to become an UP */
#define NODE_MIN_AVG_UPTIME 10800 /**< Average uptime to become an UP */
#define NODE_AVG_LEAF_MEM 262144 /**< Average memory used by leaf */
#define NODE_CASUAL_FD 10 /**< # of fds we might use casually */
#define NODE_UPLOAD_QUEUE_FD 5 /**< # of fds/upload slot we can queue */
#define NODE_TX_BUFSIZ 1024 /**< Buffer size for TX deflation */
#define NODE_TX_FLUSH 16384 /**< Flush deflator every 16K */
#define NODE_AUTO_SWITCH_MIN 1800 /**< Don't switch too often UP - leaf */
#define NODE_AUTO_SWITCH_MAX 61200 /**< Max between switches (17 hours) */
#define NODE_UP_NO_LEAF_MAX 3600 /**< Don't remain UP if no leaves */
#define NODE_TSYNC_WAIT_MS 5000 /**< Wait time after connecting (5s) */
#define NODE_TSYNC_PERIOD_MS 300000 /**< Synchronize every 5 minutes */
#define NODE_TSYNC_CHECK 15 /**< 15 secs before a timeout */
#define TCP_CRAWLER_FREQ 300 /**< once every 5 minutes */
#define UDP_CRAWLER_FREQ 120 /**< once every 2 minutes */
const gchar *start_rfc822_date; /**< RFC822 format of start_time */
static GSList *sl_nodes;
static GSList *sl_nodes_without_broken_gtkg;
static GHashTable *nodes_by_id;
static GHashTable *nodes_by_guid;
static gnutella_node_t *udp_node;
static gnutella_node_t *udp6_node;
static gnutella_node_t *browse_node;
static gchar *payload_inflate_buffer;
static gint payload_inflate_buffer_len;
/* These two contain connected and connectING(!) nodes. */
static GHashTable *ht_connected_nodes = NULL;
static guint32 connected_node_count = 0;
#define NO_METADATA GUINT_TO_POINTER(1) /**< No metadata for host */
static GHashTable *unstable_servent = NULL;
static GSList *unstable_servents = NULL;
static struct aging *tcp_crawls;
static struct aging *udp_crawls;
typedef struct node_bad_client {
const char *vendor;
int errors;
} node_bad_client_t;
static int node_error_threshold = 6; /**< This requires an average uptime of
1 hour for an ultrapeer */
static time_t node_error_cleanup_timer = 6 * 3600; /**< 6 hours */
static GSList *sl_proxies; /* Our push proxies */
static guint32 shutdown_nodes;
static gboolean allow_gnet_connections = FALSE;
GHookList node_added_hook_list;
/**
* For use by node_added_hook_list hooks, since we can't add a parameter
* at list invoke time.
*/
struct gnutella_node *node_added;
/**
* Structure used for asynchronous reaction to peer mode changes.
*/
static struct {
gboolean changed;
node_peer_t new;
} peermode = { FALSE, NODE_P_UNKNOWN };
/**
* Types of bad nodes for node_is_bad().
*/
enum node_bad {
NODE_BAD_OK = 0, /**< Node is fine */
NODE_BAD_IP, /**< Node has a bad (unstable) IP */
NODE_BAD_VENDOR, /**< Node has a bad vendor string */
NODE_BAD_NO_VENDOR /**< Node has no vendor string */
};
static guint connected_node_cnt = 0;
static guint compressed_node_cnt = 0;
static guint compressed_leaf_cnt = 0;
static gint pending_byes = 0; /* Used when shutdowning servent */
static gboolean in_shutdown = FALSE;
static guint32 leaf_to_up_switch = NODE_AUTO_SWITCH_MIN;
static time_t no_leaves_connected = 0;
static const gchar no_reason[] = "<no reason>"; /* Don't translate this */
static query_hashvec_t *query_hashvec;
static void node_disable_read(struct gnutella_node *n);
static gboolean node_data_ind(rxdrv_t *rx, pmsg_t *mb);
static void node_bye_sent(struct gnutella_node *n);
static void call_node_process_handshake_ack(gpointer obj, header_t *header);
static void node_send_qrt(struct gnutella_node *n,
struct routing_table *query_table);
static void node_send_patch_step(struct gnutella_node *n);
static void node_bye_flags(guint32 mask, gint code, const gchar *message);
static void node_bye_all_but_one(struct gnutella_node *nskip,
gint code, const gchar *message);
static void node_set_current_peermode(node_peer_t mode);
static enum node_bad node_is_bad(struct gnutella_node *n);
static gnutella_node_t *node_udp_create(enum net_type net);
static gnutella_node_t *node_browse_create(void);
static gboolean node_remove_useless_leaf(gboolean *is_gtkg);
static gboolean node_remove_useless_ultra(gboolean *is_gtkg);
/***
*** Callbacks
***/
static listeners_t node_added_listeners = NULL;
static listeners_t node_removed_listeners = NULL;
static listeners_t node_info_changed_listeners = NULL;
static listeners_t node_flags_changed_listeners = NULL;
void
node_add_node_added_listener(node_added_listener_t l)
{
LISTENER_ADD(node_added, l);
}
void
node_remove_node_added_listener(node_added_listener_t l)
{
LISTENER_REMOVE(node_added, l);
}
void
node_add_node_removed_listener(node_removed_listener_t l)
{
LISTENER_ADD(node_removed, l);
}
void
node_remove_node_removed_listener(node_removed_listener_t l)
{
LISTENER_REMOVE(node_removed, l);
}
void
node_add_node_info_changed_listener(node_info_changed_listener_t l)
{
LISTENER_ADD(node_info_changed, l);
}
void
node_remove_node_info_changed_listener(node_info_changed_listener_t l)
{
LISTENER_REMOVE(node_info_changed, l);
}
void
node_add_node_flags_changed_listener(node_flags_changed_listener_t l)
{
LISTENER_ADD(node_flags_changed, l);
}
void
node_remove_node_flags_changed_listener(node_flags_changed_listener_t l)
{
LISTENER_REMOVE(node_flags_changed, l);
}
static void
node_fire_node_added(gnutella_node_t *n)
{
n->last_update = tm_time();
LISTENER_EMIT(node_added, (NODE_ID(n)));
}
static void
node_fire_node_removed(gnutella_node_t *n)
{
n->last_update = tm_time();
LISTENER_EMIT(node_removed, (NODE_ID(n)));
}
static void
node_fire_node_info_changed(gnutella_node_t *n)
{
LISTENER_EMIT(node_info_changed, (NODE_ID(n)));
}
static void
node_fire_node_flags_changed(gnutella_node_t *n)
{
LISTENER_EMIT(node_flags_changed, (NODE_ID(n)));
}
/***
*** Utilities
***/
/**
* Free atom string key from hash table.
*/
static void
free_key(gpointer key, gpointer unused_val, gpointer unused_x)
{
(void) unused_val;
(void) unused_x;
atom_str_free(key);
}
/**
* Free atom string key from hash table and return TRUE.
*/
static gboolean
free_key_true(gpointer key, gpointer unused_val, gpointer unused_x)
{
(void) unused_val;
(void) unused_x;
atom_str_free(key);
return TRUE;
}
/**
* Clear hash table whose keys are atoms and values ignored.
*/
static void
string_table_clear(GHashTable *ht)
{
g_assert(ht != NULL);
g_hash_table_foreach_remove(ht, free_key_true, NULL);
}
/**
* Dispose of hash table whose keys are atoms and values ignored.
*/
static void
string_table_free(GHashTable **ht_ptr)
{
g_assert(ht_ptr);
if (*ht_ptr) {
GHashTable *ht = *ht_ptr;
g_hash_table_foreach(ht, free_key, NULL);
g_hash_table_destroy(ht);
*ht_ptr = NULL;
}
}
/**
* Sends a PING to the node over UDP (if enabled).
*/
static void
node_send_udp_ping(struct gnutella_node *n)
{
udp_send_ping(NULL, n->addr, n->port, TRUE);
}
/***
*** Time Sync operations.
***/
/**
* Send "Time Sync" via UDP if we know the remote IP:port, via TCP otherwise.
*/
static void
node_tsync_udp(cqueue_t *unused_cq, gpointer obj)
{
gnutella_node_t *n = obj;
gnutella_node_t *udp = NULL, *tn;
(void) unused_cq;
g_assert(!NODE_IS_UDP(n));
g_assert(n->attrs & NODE_A_TIME_SYNC);
n->tsync_ev = NULL; /* has been freed before calling this function */
/*
* If we did not get replies within the reasonable time period, we
* marked the node with NODE_F_TSYNC_TCP to use TCP instead of UDP.
*/
if (
!(n->flags & NODE_F_TSYNC_TCP) &&
is_host_addr(n->gnet_addr)
)
udp = node_udp_get_addr_port(n->gnet_addr, n->gnet_port);
tn = udp ? udp : n;
if (!host_is_valid(tn->addr, tn->port))
return;
tsync_send(tn, NODE_ID(n));
/*
* Next sync will occur in NODE_TSYNC_PERIOD_MS milliseconds.
*/
n->tsync_ev =
cq_insert(callout_queue, NODE_TSYNC_PERIOD_MS, node_tsync_udp, n);
}
/**
* Invoked when we determined that the node supports Time Sync.
*/
void
node_can_tsync(gnutella_node_t *n)
{
g_assert(!NODE_IS_UDP(n));
if (n->attrs & NODE_A_TIME_SYNC)
return;
n->attrs |= NODE_A_TIME_SYNC;
/*
* Schedule a time sync in NODE_TSYNC_WAIT_MS milliseconds.
*/
n->tsync_ev =
cq_insert(callout_queue, NODE_TSYNC_WAIT_MS, node_tsync_udp, n);
}
/**
* Sent "probe" time sync via TCP to the specified node to compute the RTT...
*/
static void
node_tsync_tcp(gnutella_node_t *n)
{
g_assert(!NODE_IS_UDP(n));
g_assert(n->attrs & NODE_A_TIME_SYNC);
tsync_send(n, NODE_ID(n));
}
/***
*** Private functions
***/
/**
* Check whether we already have the host.
*/
static gboolean
node_ht_connected_nodes_has(const host_addr_t addr, guint16 port)
{
gnet_host_t host;
gnet_host_set(&host, addr, port);
return NULL != g_hash_table_lookup(ht_connected_nodes, &host);
}
/**
* Check whether we already have the host.
*/
static gnet_host_t *
node_ht_connected_nodes_find(const host_addr_t addr, guint16 port)
{
gnet_host_t host;
gboolean found;
gpointer orig_host, metadata;
gnet_host_set(&host, addr, port);
found = g_hash_table_lookup_extended(ht_connected_nodes, &host,
&orig_host, &metadata);
return found ? orig_host : NULL;
}
/**
* Add host to the hash table host cache.
*/
static void
node_ht_connected_nodes_add(const host_addr_t addr, guint16 port)
{
gnet_host_t *host;
if (node_ht_connected_nodes_has(addr, port))
return;
host = walloc(sizeof *host);
gnet_host_set(host, addr, port);
g_hash_table_insert(ht_connected_nodes, host, NO_METADATA);
connected_node_count++;
}
/**
* Remove host from the hash table host cache.
*/
static void
node_ht_connected_nodes_remove(const host_addr_t addr, guint16 port)
{
gnet_host_t *orig_host;
orig_host = node_ht_connected_nodes_find(addr, port);
if (orig_host) {
g_hash_table_remove(ht_connected_nodes, orig_host);
g_assert(connected_node_count > 0);
connected_node_count--;
wfree(orig_host, sizeof *orig_host);
}
}
/**
* Dumps a gnutella message (debug).
*/
static void
message_dump(const struct gnutella_node *n)
{
printf("Node %s: ", node_addr(n));
printf("Func 0x%.2x ", gnutella_header_get_function(&n->header));
printf("TTL = %u ", gnutella_header_get_ttl(&n->header));
printf("hops = %u ", gnutella_header_get_hops(&n->header));
printf(" data = %u", (guint) gmsg_size(&n->header));
switch (gnutella_header_get_function(&n->header)) {
case GTA_MSG_INIT_RESPONSE:
{
guint32 ip, count, total;
guint16 port;
port = peek_le16(n->data);
ip = peek_be32(n->data + 2);
count = peek_le32(n->data + 6);
total = peek_le32(n->data + 10);
printf(" Host = %s Port = %u Count = %u Total = %u",
ip_to_string(ip), port, count, total);
}
break;
case GTA_MSG_PUSH_REQUEST:
{
guint32 ip, idx;
guint16 port;
idx = peek_le32(n->data + 16);
ip = peek_be32(n->data + 20);
port = peek_le16(n->data + 24);
printf(" Index = %u Host = %s Port = %u ", idx, ip_to_string(ip),
port);
}
break;
}
printf("\n");
}
/**
* Check whether node is a gtk-gnutella node.
*/
static inline gboolean
node_is_gtkg(const struct gnutella_node *n)
{
return 0 != (NODE_F_GTKG & n->flags);
}
/**
* Extract IP/port information out of the Query Hit into `ip' and `port'.
*/
static void
node_extract_host(const struct gnutella_node *n,
host_addr_t *ha, guint16 *port)
{
/* Read Query Hit info */
*ha = host_addr_get_ipv4(gnutella_search_results_get_host_ip(n->data));
*port = gnutella_search_results_get_host_port(n->data);
}
/**
* Check the Ultrapeer requirements, returning TRUE if we can become an UP.
*/
static gboolean
can_become_ultra(time_t now)
{
gboolean avg_servent_uptime;
gboolean avg_ip_uptime;
gboolean node_uptime;
gboolean not_firewalled;
gboolean good_udp_support;
gboolean enough_conn;
gboolean enough_fd;
gboolean enough_mem;
gboolean enough_bw;
const gchar *ok = "** OK **";
const gchar *no = "-- NO --";
/* Uptime requirements */
avg_servent_uptime = get_average_servent_uptime(now) >= NODE_MIN_AVG_UPTIME;
avg_ip_uptime =
get_average_ip_lifetime(now, NET_TYPE_IPV4) >= NODE_MIN_AVG_UPTIME ||
get_average_ip_lifetime(now, NET_TYPE_IPV6) >= NODE_MIN_AVG_UPTIME;
node_uptime = delta_time(now, GNET_PROPERTY(start_stamp)) > NODE_MIN_UPTIME;
/* Connectivity requirements */
not_firewalled = !GNET_PROPERTY(is_firewalled) &&
!GNET_PROPERTY(is_udp_firewalled);
/*
* Require proper UDP support to be enabled. An efficient UP must be
* able to perform OOB-proxying of queries from firewalled leaves, lest
* the query hits will have to be routed back on the Gnutella network.
* --RAM, 2006-08-18
*/
good_udp_support =
GNET_PROPERTY(proxy_oob_queries) &&
udp_active() && (
host_is_valid(listen_addr(), socket_listen_port()) ||
host_is_valid(listen_addr6(), socket_listen_port())
);
/*
* System requirements
*
* We don't count all the banned fd, since we can now steal the necessary
* descriptors out of the banned pool if we run short of fd. We need to
* provision for possible PARQ active queuing, which is why we scale the
* `max_uploads' parameter.
*
* Likewise, we assume that at most 1/4th of the downloads will actually
* be active at one time (meaning one fd for the connection and one fd
* for the file being written to). We count "max_uploads" twice because
* those have one also two fd (for the connection and the file).
*/
enough_fd = (GNET_PROPERTY(max_leaves) + GNET_PROPERTY(max_connections)
+ GNET_PROPERTY(max_downloads) + (GNET_PROPERTY(max_downloads) / 4)
+ (GNET_PROPERTY(max_uploads) * (1 + NODE_UPLOAD_QUEUE_FD))
+ GNET_PROPERTY(max_uploads)
+ (GNET_PROPERTY(max_banned_fd) / 10) + NODE_CASUAL_FD)
< GNET_PROPERTY(sys_nofile);
enough_mem = (GNET_PROPERTY(max_leaves) * NODE_AVG_LEAF_MEM +
(GNET_PROPERTY(max_leaves) + GNET_PROPERTY(max_connections))
* GNET_PROPERTY(node_sendqueue_size))
< 1024 / 2 * GNET_PROPERTY(sys_physmem);
/* Bandwidth requirements */
enough_bw = bsched_enough_up_bandwidth();
/* Connection requirements */
enough_conn = GNET_PROPERTY(up_connections) >= NODE_MIN_UP_CONNECTIONS;
#define OK(b) ((b) ? ok : no)
if (GNET_PROPERTY(node_debug) > 3) {
g_message("Checking Ultrapeer criteria:");
g_message("> Sufficient average uptime : %s", OK(avg_servent_uptime));
g_message("> Sufficient IP address uptime: %s", OK(avg_ip_uptime));
g_message("> Sufficient node uptime : %s", OK(node_uptime));
g_message("> Node not firewalled : %s", OK(not_firewalled));
g_message("> Enough min peer connections : %s", OK(enough_conn));
g_message("> Enough file descriptors : %s", OK(enough_fd));
g_message("> Enough physical memory : %s", OK(enough_mem));
g_message("> Enough available bandwidth : %s", OK(enough_bw));
g_message("> Good UDP support : %s", OK(good_udp_support));
}
#undef OK
/*
* Let them see the results of our checks in the GUI.
*/
gnet_prop_set_boolean_val(PROP_UP_REQ_AVG_SERVENT_UPTIME,
avg_servent_uptime);
gnet_prop_set_boolean_val(PROP_UP_REQ_AVG_IP_UPTIME, avg_ip_uptime);
gnet_prop_set_boolean_val(PROP_UP_REQ_NODE_UPTIME, node_uptime);
gnet_prop_set_boolean_val(PROP_UP_REQ_NOT_FIREWALLED, not_firewalled);
gnet_prop_set_boolean_val(PROP_UP_REQ_ENOUGH_CONN, enough_conn);
gnet_prop_set_boolean_val(PROP_UP_REQ_ENOUGH_FD, enough_fd);
gnet_prop_set_boolean_val(PROP_UP_REQ_ENOUGH_MEM, enough_mem);
gnet_prop_set_boolean_val(PROP_UP_REQ_ENOUGH_BW, enough_bw);
gnet_prop_set_boolean_val(PROP_UP_REQ_GOOD_UDP, good_udp_support);
gnet_prop_set_timestamp_val(PROP_NODE_LAST_ULTRA_CHECK, now);
return avg_servent_uptime && avg_ip_uptime && node_uptime &&
not_firewalled && enough_fd && enough_mem && enough_bw &&
good_udp_support &&
!GNET_PROPERTY(ancient_version);
/* Old versions don't become ultrapeers */
}
/**
* Low frequency node timer.
*/
void
node_slow_timer(time_t now)
{
if (udp_active()) {
static time_t last_ping;
/**
* Periodically emit an UHC ping to a random node to keep the cache
* fresh and diverse.
*/
if (!last_ping || delta_time(now, last_ping) > 120) {
host_addr_t addr;
guint16 port;
last_ping = now;
if (hcache_get_caught(HOST_ANY, &addr, &port)) {
udp_send_ping(NULL, addr, port, TRUE);
}
}
}
/*
* Clear `no_leaves_connected' if we have something connected, or
* record the first time at which we came here with no leaf connected.
*/
if (GNET_PROPERTY(current_peermode) == NODE_P_ULTRA) {
if (GNET_PROPERTY(node_leaf_count))
no_leaves_connected = 0;
else if (no_leaves_connected == 0)
no_leaves_connected = now;
} else
no_leaves_connected = 0;
/*
* If we're in "auto" mode and we're still running as a leaf node,
* evaluate our ability to become an ultra node.
*
* NB: we test for configured_peermode == NODE_P_ULTRA because we
* can switch to leaf even when the user wants to be an ultra node
* when we make a very bad ultra peer and it is best for the network
* that we be a leaf node.
*/
if (
(GNET_PROPERTY(configured_peermode) == NODE_P_AUTO ||
GNET_PROPERTY(configured_peermode) == NODE_P_ULTRA) &&
GNET_PROPERTY(current_peermode) == NODE_P_LEAF &&
delta_time(now, GNET_PROPERTY(node_last_ultra_leaf_switch)) >
(time_delta_t) leaf_to_up_switch &&
can_become_ultra(now)
) {
g_warning("being promoted to Ultrapeer status");
gnet_prop_set_guint32_val(PROP_CURRENT_PEERMODE, NODE_P_ULTRA);
gnet_prop_set_timestamp_val(PROP_NODE_LAST_ULTRA_LEAF_SWITCH, now);
return;
}
/*
* If we're in "auto" mode and we've been promoted to an ultra node,
* evaluate how good we are and whether we would not be better off
* running as a leaf node.
*
* We double the time we'll spend as a leaf node before switching
* again to UP mode to avoid endless switches between UP and leaf.
* We limit that doubling to NODE_AUTO_SWITCH_MAX, to ensure that if
* we can become one, then we should do so on a regular basis.
*/
if (
GNET_PROPERTY(configured_peermode) == NODE_P_AUTO &&
GNET_PROPERTY(current_peermode) == NODE_P_ULTRA &&
delta_time(now, GNET_PROPERTY(node_last_ultra_leaf_switch))
> NODE_AUTO_SWITCH_MIN &&
!can_become_ultra(now)
) {
leaf_to_up_switch *= 2;
leaf_to_up_switch = MIN(leaf_to_up_switch, NODE_AUTO_SWITCH_MAX);
g_warning("being demoted from Ultrapeer status (for %u secs)",
leaf_to_up_switch);
gnet_prop_set_guint32_val(PROP_CURRENT_PEERMODE, NODE_P_LEAF);
gnet_prop_set_timestamp_val(PROP_NODE_LAST_ULTRA_LEAF_SWITCH, now);
return;
}
/*
* If we're running in ultra node and we are TCP-firewalled, then
* switch to leaf mode.
*
* We don't check whether they are firewalled if they asked to run as
* an ultranode here -- this will be caught by the check below when
* no leaf can connect.
*/
if (
GNET_PROPERTY(configured_peermode) == NODE_P_AUTO &&
GNET_PROPERTY(current_peermode) == NODE_P_ULTRA &&
GNET_PROPERTY(is_firewalled)
) {
g_warning("firewalled node being demoted from Ultrapeer status");
gnet_prop_set_guint32_val(PROP_CURRENT_PEERMODE, NODE_P_LEAF);
gnet_prop_set_timestamp_val(PROP_NODE_LAST_ULTRA_LEAF_SWITCH, now);
return;
}
/*
* If we're running as an ultra node (whether automaatically promoted
* or configured explicitly to run as such) and we have seen no leaf
* node connection for some time, then we're a bad node: we're taking
* an ultranode slot in a high outdegree network with a low TTL and
* are therefore harming the propagation of queries to leaf nodes,
* since we have none.
*
* Therefore, we'll be better off running as a leaf node.
*/
if (
GNET_PROPERTY(current_peermode) == NODE_P_ULTRA &&
no_leaves_connected != 0 &&
delta_time(now, no_leaves_connected) > NODE_UP_NO_LEAF_MAX
) {
leaf_to_up_switch *= 2;
leaf_to_up_switch = MIN(leaf_to_up_switch, NODE_AUTO_SWITCH_MAX);
g_warning(
"demoted from Ultrapeer status for %d secs due to missing leaves",
leaf_to_up_switch);
gnet_prop_set_guint32_val(PROP_CURRENT_PEERMODE, NODE_P_LEAF);
gnet_prop_set_timestamp_val(PROP_NODE_LAST_ULTRA_LEAF_SWITCH, now);
return;
}
}
static inline void
node_error_cleanup(void)
{
GSList *sl;
GSList *to_remove = NULL;
for (sl = unstable_servents; sl != NULL; sl = g_slist_next(sl)) {
node_bad_client_t *bad_node = sl->data;
g_assert(bad_node != NULL);
if (--bad_node->errors == 0)
to_remove = g_slist_prepend(to_remove, bad_node);
}
for (sl = to_remove; sl != NULL; sl = g_slist_next(sl)) {
node_bad_client_t *bad_node = sl->data;
g_assert(bad_node != NULL);
g_assert(bad_node->vendor != NULL);
if (GNET_PROPERTY(node_debug) > 1)
g_warning("[nodes up] Unbanning client: %s", bad_node->vendor);
g_hash_table_remove(unstable_servent, bad_node->vendor);
unstable_servents = g_slist_remove(unstable_servents, bad_node);
atom_str_free_null(&bad_node->vendor);
wfree(bad_node, sizeof(*bad_node));
}
g_slist_free(to_remove);
}
static void
node_tls_refresh(struct gnutella_node *n)
{
node_check(n);
if (
(n->flags & NODE_F_CAN_TLS) &&
n->gnet_port &&
is_host_addr(n->gnet_addr)
) {
time_t seen;
seen = tls_cache_get_timestamp(n->gnet_addr, n->gnet_port);
if (!seen || delta_time(tm_time(), seen) > 60) {
tls_cache_insert(n->gnet_addr, n->gnet_port);
}
}
}
void
node_supports_tls(struct gnutella_node *n)
{
node_check(n);
n->flags |= NODE_F_CAN_TLS;
node_tls_refresh(n);
}
/**
* Periodic node heartbeat timer.
*/
void
node_timer(time_t now)
{
const GSList *sl;
if ((now % node_error_cleanup_timer) == 0)
node_error_cleanup();
/*
* Asynchronously react to current peermode change.
* See comment in node_set_current_peermode().
*/
if (peermode.changed) {
peermode.changed = FALSE;
node_set_current_peermode(peermode.new);
}
for (sl = sl_nodes; NULL != sl; /* empty */ ) {
struct gnutella_node *n = sl->data;
/*
* NB: As the list `sl_nodes' might be modified, the next
* link has to be before any changes might apply!
*/
sl = g_slist_next(sl);
node_tls_refresh(n);
/*
* If we're sending a BYE message, check whether the whole TX
* stack finally flushed.
*/
if (n->flags & NODE_F_BYE_SENT) {
g_assert(n->outq);
if (mq_pending(n->outq) == 0)
node_bye_sent(n);
}
/*
* No timeout during shutdowns, or when `stop_host_get' is set.
*/
if (!(in_shutdown || GNET_PROPERTY(stop_host_get))) {
if (n->status == GTA_NODE_REMOVING) {
if (
delta_time(now, n->last_update) >
(time_delta_t) GNET_PROPERTY(entry_removal_timeout)
) {
node_real_remove(n);
continue;
}
} else if (NODE_IS_CONNECTING(n)) {
if (
delta_time(now, n->last_update) >
(time_delta_t) GNET_PROPERTY(node_connecting_timeout)
) {
node_send_udp_ping(n);
node_remove(n, _("Timeout"));
hcache_add(HCACHE_TIMEOUT, n->addr, 0, "timeout");
}
} else if (n->status == GTA_NODE_SHUTDOWN) {
if (delta_time(now, n->shutdown_date) > n->shutdown_delay) {
gchar reason[1024];
g_strlcpy(reason, n->error_str, sizeof reason);
node_remove(n, _("Shutdown (%s)"), reason);
}
} else if (
GNET_PROPERTY(current_peermode) == NODE_P_ULTRA &&
NODE_IS_ULTRA(n)
) {
time_delta_t quiet = delta_time(now, n->last_tx);
/*
* Ultra node connected to another ultra node.
*
* There is no longer any flow-control or activity
* timeout between an ultra node and a leaf, as long
* as they reply to eachother alive pings.
* --RAM, 11/12/2003
*/
if (
quiet >
(time_delta_t) GNET_PROPERTY(node_connected_timeout) &&
NODE_MQUEUE_COUNT(n)
) {
hcache_add(HCACHE_TIMEOUT, n->addr, 0,
"activity timeout");
node_bye_if_writable(n, 405, "Activity timeout");
} else if (
NODE_IN_TX_FLOW_CONTROL(n) &&
delta_time(now, n->tx_flowc_date) >
(time_delta_t) GNET_PROPERTY(node_tx_flowc_timeout)
) {
hcache_add(HCACHE_UNSTABLE, n->addr, 0,
"flow-controlled too long");
node_bye(n, 405, "Flow-controlled for too long (%d sec%s)",
GNET_PROPERTY(node_tx_flowc_timeout),
GNET_PROPERTY(node_tx_flowc_timeout) == 1 ? "" : "s");
}
}
}
if (n->searchq != NULL)
sq_process(n->searchq, now);
/*
* Sanity checks for connected nodes.
*/
if (n->status == GTA_NODE_CONNECTED) {
time_delta_t tx_quiet = delta_time(now, n->last_tx);
time_delta_t rx_quiet = delta_time(now, n->last_rx);
if (n->n_weird >= MAX_WEIRD_MSG) {
g_message("Removing %s <%s> due to security violation",
node_addr(n), node_vendor(n));
node_bye_if_writable(n, 412, "Security violation");
return;
}
#if 0
/* FIXME: Disabled because it's nonsense. The ratio sent:received
* can be very high due to OOB reply indications for example and
* indicates no bad condition for this peer at all.
*/
if (
!NODE_IS_LEAF(n) &&
n->sent > MIN_TX_FOR_RATIO &&
(n->received == 0 || n->sent / n->received > MAX_TX_RX_RATIO)
) {
node_bye_if_writable(n, 405, "Reception shortage");
return;
}
#endif
/*
* If quiet period is nearing timeout and node supports
* time-sync, send them one if none is pending.
*/
if (
GNET_PROPERTY(node_connected_timeout) > 2*NODE_TSYNC_CHECK &&
MAX(tx_quiet, rx_quiet) >
(time_delta_t) GNET_PROPERTY(node_connected_timeout) -
NODE_TSYNC_CHECK &&
(n->attrs & NODE_A_TIME_SYNC) &&
!(n->flags & NODE_F_TSYNC_WAIT)
) {
node_tsync_tcp(n);
n->flags |= NODE_F_TSYNC_WAIT;
}
/*
* Only send "alive" pings if we have not received anything
* for a while and if some time has elapsed since our last
* attempt to send such a ping.
* --RAM, 01/11/2003
*/
if (
NODE_IS_ESTABLISHED(n) &&
delta_time(now, n->last_rx) > n->alive_period
) {
guint32 last;
guint32 avg;
time_delta_t period;
/*
* Take the round-trip time of the ping/pongs as a base for
* computing the time we should space our pings. Indeed,
* if the round-trip is 90s (taking an extreme example) due
* to queuing and TCP/IP clogging and we send pings every 20
* seconds, we will have sent 4 before getting a chance to see
* any reply back!
* -RAM, 01/11/2003
*/
alive_get_roundtrip_ms(n->alive_pings, &avg, &last);
last = MAX(avg, last) / 1000; /* Convert ms to seconds */
period = MAX(n->alive_period, (time_delta_t) last);
if (
delta_time(now, n->last_alive_ping) > period &&
!alive_send_ping(n->alive_pings)
) {
node_bye(n, 406, "No reply to alive pings");
return;
}
}
/*
* Check whether we need to send more QRT patch updates.
*/
if (n->qrt_update != NULL) {
g_assert(NODE_IS_CONNECTED(n));
node_send_patch_step(n);
if (!NODE_IS_CONNECTED(n))
return;
}
/*
* Check RX flow control.
*/
if (n->rxfc != NULL) {
struct node_rxfc_mon *rxfc = n->rxfc;
if (
delta_time(now, rxfc->start_half_period)
> NODE_RX_FC_HALF_PERIOD
) {
time_t total;
gdouble fc_ratio;
guint32 max_ratio;
/*
* If we're a leaf node, we allow the ultrapeer to flow
* control our incoming connection for 95% of the time.
* Being flow controlled means we're not getting that much
* queries, and we can't send ours, but as long as we have
* a non-null window to send our queries, that's fine.
*/
max_ratio = GNET_PROPERTY(current_peermode) == NODE_P_LEAF
? 95
: GNET_PROPERTY(node_rx_flowc_ratio);
if (rxfc->fc_start) { /* In flow control */
rxfc->fc_accumulator += delta_time(now, rxfc->fc_start);
rxfc->fc_start = now;
}
total = rxfc->fc_accumulator + rxfc->fc_last_half;
/* New period begins */
rxfc->fc_last_half = rxfc->fc_accumulator;
rxfc->fc_accumulator = 0;
rxfc->start_half_period = now;
fc_ratio = (gdouble) total / (2.0 * NODE_RX_FC_HALF_PERIOD);
fc_ratio *= 100.0;
if ((guint32) fc_ratio > max_ratio) {
node_bye(n, 405,
"Remotely flow-controlled too often "
"(%.2f%% > %d%% of time)", fc_ratio, max_ratio);
return;
}
/* Dispose of monitoring if we're not flow-controlled */
if (total == 0) {
wfree(n->rxfc, sizeof(*n->rxfc));
n->rxfc = NULL;
}
}
}
}
/*
* Rotate `qrelayed' on a regular basis into `qrelayed_old' and
* dispose of previous `qrelayed_old'.
*/
if (
n->qrelayed != NULL &&
delta_time(now, n->qrelayed_created) >=
(time_delta_t) GNET_PROPERTY(node_queries_half_life)
) {
GHashTable *new;
if (n->qrelayed_old != NULL) {
new = n->qrelayed_old;
string_table_clear(new);
} else
new = g_hash_table_new(g_str_hash, g_str_equal);
n->qrelayed_old = n->qrelayed;
n->qrelayed = new;
n->qrelayed_created = now;
}
}
sq_process(sq_global_queue(), now);
}
struct node_id {
guint64 value;
};
static inline guint64
node_id_value(const node_id_t node_id)
{
return node_id->value;
}
gboolean
node_id_self(const node_id_t node_id)
{
return 0 == node_id_value(node_id);
}
node_id_t
node_id_get_self(void)
{
static const struct node_id NODE_SELF_ID;
return &NODE_SELF_ID;
}
guint
node_id_hash(gconstpointer key)
{
node_id_t p = key;
return uint64_hash(p);
}
gboolean
node_id_eq(const node_id_t p, const node_id_t q)
{
guint64 a = node_id_value(p), b = node_id_value(q);
return uint64_eq(&a, &b);
}
const gchar *
node_id_to_string(const node_id_t node_id)
{
static gchar buf[UINT64_DEC_BUFLEN];
uint64_to_string_buf(node_id_value(node_id), buf, sizeof buf);
return buf;
}
node_id_t
node_id_ref(const node_id_t node_id)
{
return (node_id_t) atom_uint64_get(&node_id->value);
}
void
node_id_unref(const node_id_t node_id)
{
g_assert(node_id);
g_assert(node_id != node_id_get_self());
atom_uint64_free(&node_id->value);
}
static node_id_t
node_id_new(const struct gnutella_node *n)
{
static struct node_id counter;
node_id_t node_id;
node_check(n);
counter.value++;
node_id = node_id_ref(&counter);
gm_hash_table_insert_const(nodes_by_id, node_id, n);
return node_id;
}
/**
* Network init.
*/
void
node_init(void)
{
time_t now = clock_loc2gmt(tm_time());
rxbuf_init();
g_assert(23 == sizeof(gnutella_header_t));
header_features_add(FEATURES_CONNECTIONS, "browse",
BH_VERSION_MAJOR, BH_VERSION_MINOR);
g_hook_list_init(&node_added_hook_list, sizeof(GHook));
node_added_hook_list.seq_id = 1;
node_added = NULL;
/* Max: 128 unique words / URNs! */
query_hashvec = qhvec_alloc(QRP_HVEC_MAX);
unstable_servent = g_hash_table_new(NULL, NULL);
ht_connected_nodes = g_hash_table_new(host_hash, host_eq);
nodes_by_id = g_hash_table_new(node_id_hash, node_id_eq_func);
nodes_by_guid = g_hash_table_new(guid_hash, guid_eq);
start_rfc822_date = atom_str_get(timestamp_rfc822_to_string(now));
gnet_prop_set_timestamp_val(PROP_START_STAMP, now);
udp_node = node_udp_create(NET_TYPE_IPV4);
udp6_node = node_udp_create(NET_TYPE_IPV6);
browse_node = node_browse_create();
payload_inflate_buffer_len = settings_max_msg_size();
payload_inflate_buffer = g_malloc(payload_inflate_buffer_len);
/*
* Limit replies to TCP/UDP crawls from a single IP.
*/
tcp_crawls = aging_make(TCP_CRAWLER_FREQ,
host_addr_hash_func, host_addr_eq_func, wfree_host_addr);
udp_crawls = aging_make(UDP_CRAWLER_FREQ,
host_addr_hash_func, host_addr_eq_func, wfree_host_addr);
/*
* Signal we support flags in the size header via "sflag/0.1"
*/
header_features_add(FEATURES_CONNECTIONS, "sflag", 0, 1);
}
/**
* Change the socket RX buffer size for all the currently connected nodes.
*/
void
node_set_socket_rx_size(gint rx_size)
{
GSList *sl;
g_assert(rx_size > 0);
for (sl = sl_nodes; sl != NULL; sl = g_slist_next(sl)) {
struct gnutella_node *n = sl->data;
if (n->socket) {
socket_check(n->socket);
socket_recv_buf(n->socket, rx_size, TRUE);
}
}
}
/*
* Nodes
*/
guint
connected_nodes(void)
{
return connected_node_cnt;
}
guint
node_count(void)
{
return connected_node_count - shutdown_nodes -
GNET_PROPERTY(node_leaf_count);
}
/**
* Amount of node connections we would like to keep.
*
* @return 0 if none.
*/
guint
node_keep_missing(void)
{
gint missing;
switch ((node_peer_t) GNET_PROPERTY(current_peermode)) {
case NODE_P_LEAF:
missing = GNET_PROPERTY(max_ultrapeers)
- GNET_PROPERTY(node_ultra_count);
return MAX(0, missing);
case NODE_P_NORMAL:
case NODE_P_ULTRA:
missing = GNET_PROPERTY(up_connections)
- (GNET_PROPERTY(node_ultra_count)
+ GNET_PROPERTY(node_normal_count));
return MAX(0, missing);
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
break;
}
g_assert_not_reached();
return 0;
}
/**
* Amount of node connections we would like to have.
*
* @return 0 if none.
*/
guint
node_missing(void)
{
gint missing;
switch ((node_peer_t) GNET_PROPERTY(current_peermode)) {
case NODE_P_LEAF:
missing = GNET_PROPERTY(max_ultrapeers)
- GNET_PROPERTY(node_ultra_count);
return MAX(0, missing);
case NODE_P_NORMAL:
case NODE_P_ULTRA:
missing = GNET_PROPERTY(max_connections)
- (GNET_PROPERTY(node_ultra_count)
+ GNET_PROPERTY(node_normal_count));
return MAX(0, missing);
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
break;
}
g_assert_not_reached();
return 0;
}
/**
* Amount of leaves we're missing (0 if not in ultra mode).
*/
guint
node_leaves_missing(void)
{
gint missing;
if (GNET_PROPERTY(current_peermode) != NODE_P_ULTRA)
return 0;
missing = GNET_PROPERTY(max_leaves) - GNET_PROPERTY(node_leaf_count);
return MAX(0, missing);
}
/**
* @return this node's outdegree, i.e. the maximum amount of peer connections
* that we can support.
*/
guint
node_outdegree(void)
{
switch ((node_peer_t) GNET_PROPERTY(current_peermode)) {
case NODE_P_LEAF:
return GNET_PROPERTY(max_ultrapeers);
case NODE_P_NORMAL:
case NODE_P_ULTRA:
return GNET_PROPERTY(max_connections);
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
break;
}
g_assert_not_reached();
return 0;
}
/**
* Parse the first handshake line to determine the protocol version.
* The major and minor are returned in `major' and `minor' respectively.
*/
static void
get_protocol_version(const gchar *handshake, guint *major, guint *minor)
{
const gchar *s;
s = &handshake[GNUTELLA_HELLO_LENGTH];
if (0 == parse_major_minor(s, NULL, major, minor))
return;
if (GNET_PROPERTY(node_debug))
g_warning("Unable to parse version number in HELLO, assuming 0.4");
if (GNET_PROPERTY(node_debug) > 2) {
guint len = strlen(handshake);
dump_hex(stderr, "First HELLO Line", handshake, MIN(len, 80));
}
*major = 0;
*minor = 4;
}
/**
* Decrement the proper node count property, depending on the peermode.
*/
static void
node_type_count_dec(struct gnutella_node *n)
{
switch (n->peermode) {
case NODE_P_LEAF:
g_assert(GNET_PROPERTY(node_leaf_count) > 0);
gnet_prop_decr_guint32(PROP_NODE_LEAF_COUNT);
return;
case NODE_P_NORMAL:
g_assert(GNET_PROPERTY(node_normal_count) > 0);
gnet_prop_decr_guint32(PROP_NODE_NORMAL_COUNT);
return;
case NODE_P_ULTRA:
g_assert(GNET_PROPERTY(node_ultra_count) > 0);
gnet_prop_decr_guint32(PROP_NODE_ULTRA_COUNT);
return;
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
return;
}
g_assert_not_reached();
}
static struct gnutella_node *
node_alloc(void)
{
static const struct gnutella_node zero_node;
struct gnutella_node *n;
n = walloc(sizeof *n);
*n = zero_node;
n->magic = NODE_MAGIC;
return n;
}
/**
* Physically dispose of node.
*/
void
node_real_remove(gnutella_node_t *n)
{
g_return_if_fail(n);
node_check(n);
/*
* Tell the frontend that the node was removed.
*/
node_fire_node_removed(n);
sl_nodes = g_slist_remove(sl_nodes, n);
sl_nodes_without_broken_gtkg =
g_slist_remove(sl_nodes_without_broken_gtkg, n);
g_hash_table_remove(nodes_by_id, NODE_ID(n));
/*
* Now that the node was removed from the list of known nodes, we
* can add the host to HL_VALID iff the node was marked NODE_F_VALID,
* meaning we identified it as a Gnutella server, even though we
* might not have been granted a full connection.
* --RAM, 13/01/2002
*/
if (
!NODE_IS_LEAF(n) &&
is_host_addr(n->gnet_addr) &&
(n->flags & NODE_F_VALID)
)
hcache_add_valid((n->attrs & NODE_A_ULTRA) ? HOST_ULTRA : HOST_ANY,
n->gnet_addr, n->gnet_port, "save valid");
/*
* The io_opaque structure is not freed by node_remove(), so that code
* can still peruse the headers after node_remove() has been called.
*/
if (n->io_opaque) /* I/O data */
io_free(n->io_opaque);
/*
* The freeing of the vendor string is delayed, because the GUI update
* code reads it. When this routine is called, the GUI line has been
* removed, so it's safe to do it now.
*/
atom_str_free_null(&n->vendor);
/*
* The RX stack needs to be dismantled asynchronously, to not be freed
* whilst on the "data reception" interrupt path.
*/
if (n->rx)
rx_free(n->rx);
/*
* The TX stack is dismantled asynchronously as well to be on the
* safe side.
*/
if (n->outq)
mq_free(n->outq);
if (n->alive_pings) /* Must be freed after the TX stack */
alive_free(n->alive_pings);
node_id_unref(NODE_ID(n));
n->id = NULL;
n->magic = 0;
wfree(n, sizeof(*n));
}
/**
* The vectorized (message-wise) version of node_remove().
*/
static G_GNUC_PRINTF(2, 0) void
node_remove_v(struct gnutella_node *n, const gchar *reason, va_list ap)
{
node_check(n);
g_assert(n->status != GTA_NODE_REMOVING);
g_assert(!NODE_IS_UDP(n));
if (reason && no_reason != reason) {
gm_vsnprintf(n->error_str, sizeof n->error_str, reason, ap);
n->remove_msg = n->error_str;
} else if (n->status != GTA_NODE_SHUTDOWN) /* Preserve shutdown error */
n->remove_msg = NULL;
if (GNET_PROPERTY(node_debug) > 3)
g_message("Node %s <%s> removed: %s", node_addr(n), node_vendor(n),
n->remove_msg ? n->remove_msg : "<no reason>");
if (GNET_PROPERTY(node_debug) > 4) {
g_message("NODE [%d.%d] %s <%s> TX=%d (drop=%d) RX=%d (drop=%d) "
"Dup=%d Bad=%d W=%d",
n->proto_major, n->proto_minor, node_addr(n), node_vendor(n),
n->sent, n->tx_dropped, n->received, n->rx_dropped,
n->n_dups, n->n_bad, n->n_weird);
g_message("NODE \"%s%s\" %s PING (drop=%d acpt=%d spec=%d sent=%d) "
"PONG (rcvd=%d sent=%d)",
(n->attrs & NODE_A_PONG_CACHING) ? "new" : "old",
(n->attrs & NODE_A_PONG_ALIEN) ? "-alien" : "",
node_addr(n),
n->n_ping_throttle, n->n_ping_accepted, n->n_ping_special,
n->n_ping_sent, n->n_pong_received, n->n_pong_sent);
}
if (n->routing_data) {
routing_node_remove(n);
n->routing_data = NULL;
}
if (n->qrt_update) {
qrt_update_free(n->qrt_update);
n->qrt_update = NULL;
}
if (n->qrt_receive) {
qrt_receive_free(n->qrt_receive);
n->qrt_receive = NULL;
}
if (n->recv_query_table) {
qrt_unref(n->recv_query_table);
n->recv_query_table = NULL;
/*
* I decided to NOT call qrp_leaf_changed() here even if
* the node was a leaf node. Why? Because that could cause
* the regeneration of the last-hop QRP table and all we could
* do is clear some slots in the table to get less entries.
* Entries that could be filled by the next leaf that will come
* to fill the free leaf slot.
*
* Since having less slots means we'll get less queries, but
* having a new table means generating a patch and therefore
* consuming network resources, it's not clear what the gain
* would be. Better wait for the new leaf to have sent its
* patch to update.
*
* --RAM, 2004-08-04
*/
}
if (n->sent_query_table) {
qrt_unref(n->sent_query_table);
n->sent_query_table = NULL;
}
if (n->qrt_info) {
WFREE_NULL(n->qrt_info, sizeof(*n->qrt_info));
}
if (n->rxfc) {
WFREE_NULL(n->rxfc, sizeof(*n->rxfc));
}
if (n->status == GTA_NODE_CONNECTED) { /* Already did if shutdown */
g_assert(connected_node_cnt > 0);
connected_node_cnt--;
if (n->attrs & NODE_A_RX_INFLATE) {
if (n->flags & NODE_F_LEAF) {
g_assert(compressed_leaf_cnt > 0);
compressed_leaf_cnt--;
}
g_assert(compressed_node_cnt > 0);
compressed_node_cnt--;
}
node_type_count_dec(n);
}
if (n->status == GTA_NODE_SHUTDOWN) {
shutdown_nodes--;
}
if (n->hello.ptr) {
WFREE_NULL(n->hello.ptr, n->hello.size);
}
/* n->io_opaque will be freed by node_real_remove() */
/* n->vendor will be freed by node_real_remove() */
if (n->allocated) {
G_FREE_NULL(n->data);
n->allocated = 0;
}
if (n->searchq) {
sq_free(n->searchq);
n->searchq = NULL;
}
if (n->rx) /* RX stack freed by node_real_remove() */
node_disable_read(n);
if (n->outq) /* TX stack freed by node_real_remove() */
mq_shutdown(n->outq); /* Prevents any further output */
if (n->socket) {
socket_check(n->socket);
g_assert(n->socket->resource.node == n);
socket_free_null(&n->socket);
}
cq_cancel(callout_queue, &n->tsync_ev);
n->status = GTA_NODE_REMOVING;
n->flags &= ~(NODE_F_WRITABLE|NODE_F_READABLE|NODE_F_BYE_SENT);
n->last_update = tm_time();
node_ht_connected_nodes_remove(n->gnet_addr, n->gnet_port);
node_proxying_remove(n);
if (n->flags & NODE_F_EOF_WAIT) {
g_assert(pending_byes > 0);
pending_byes--;
}
if (is_host_addr(n->proxy_addr)) {
sl_proxies = g_slist_remove(sl_proxies, n);
}
string_table_free(&n->qseen);
string_table_free(&n->qrelayed);
string_table_free(&n->qrelayed_old);
if (n->guid) {
g_hash_table_remove(nodes_by_guid, n->guid);
atom_guid_free_null(&n->guid);
}
if (!in_shutdown) {
if (n->attrs & NODE_A_CAN_HSEP) {
hsep_connection_close(n);
}
if (NODE_IS_LEAF(n)) {
/* Purge dynamic queries for that node */
dq_node_removed(NODE_ID(n));
}
node_fire_node_info_changed(n);
node_fire_node_flags_changed(n);
}
}
/**
* Called when node_bye() or node_shutdown() is called during the time we're
* in shutdown mode, processing the messages we might still read from the
* socket.
*/
static void
node_recursive_shutdown_v(
struct gnutella_node *n,
const gchar *where, const gchar *reason, va_list ap)
{
gchar *fmt, *p;
g_assert(n->status == GTA_NODE_SHUTDOWN);
g_assert(n->error_str);
g_assert(reason);
/* XXX: Could n->error_str contain a format string? Rather make sure
* there isn't any. */
for (p = n->error_str; *p != '\0'; p++)
if (*p == '%')
*p = 'X';
fmt = g_strdup_printf("%s (%s) [within %s]", where, reason, n->error_str);
node_remove_v(n, fmt, ap);
G_FREE_NULL(fmt);
}
/**
* Removes or shuts down the given node.
*/
void
node_remove_by_id(const node_id_t node_id)
{
gnutella_node_t *node;
node = node_by_id(node_id);
if (node) {
if (node == udp_node || node == udp6_node) {
/* Ignore */
} else if (NODE_IS_WRITABLE(node)) {
node_bye(node, 201, "User manual removal");
} else {
node_remove(node, no_reason);
node_real_remove(node);
}
}
}
/**
* Check whether node has been identified as having a bad IP or vendor string.
*
* @return NODE_BAD_OK if node is OK, the reason why the node is bad otherwise.
*
* @note when we're low on pongs, we never refuse a connection, so this
* routine always returns NODE_BAD_OK.
*/
static enum node_bad
node_is_bad(struct gnutella_node *n)
{
node_bad_client_t *bad_client = NULL;
node_check(n);
if (!GNET_PROPERTY(node_monitor_unstable_ip))
return NODE_BAD_OK; /* User disabled monitoring of unstable IPs */
if (host_low_on_pongs)
return NODE_BAD_OK; /* Can't refuse connection */
if (n->vendor == NULL) {
if (GNET_PROPERTY(node_debug))
g_warning("no vendor name in %s node headers from %s",
NODE_IS_LEAF(n) ? "leaf" :
NODE_IS_ULTRA(n) ? "ultra" : "legacy",
node_addr(n));
return NODE_BAD_NO_VENDOR;
}
g_assert(n->vendor != NULL);
g_assert(is_host_addr(n->addr));
if (hcache_node_is_bad(n->addr)) {
if (GNET_PROPERTY(node_debug))
g_warning("[nodes up] Unstable peer %s (%s)",
host_addr_to_string(n->addr),
n->vendor);
return NODE_BAD_IP;
}
if (!GNET_PROPERTY(node_monitor_unstable_servents))
return NODE_BAD_OK; /* No monitoring of unstable servents */
bad_client = g_hash_table_lookup(unstable_servent, n->vendor);
if (bad_client == NULL)
return NODE_BAD_OK;
if (bad_client->errors > node_error_threshold) {
if (GNET_PROPERTY(node_debug))
g_warning("[nodes up] Banned client: %s", n->vendor);
return NODE_BAD_VENDOR;
}
return NODE_BAD_OK;
}
/**
* Gives a specific vendor a bad mark. If a vendor + version gets to many
* marks, we won't try to connect to it anymore.
*/
void
node_mark_bad_vendor(struct gnutella_node *n)
{
struct node_bad_client *bad_client = NULL;
time_t now;
if (in_shutdown)
return;
/*
* If the user doesn't want us to protect against unstable IPs, then we
* can stop right now. Protecting against unstable servent name will
* also be ignored, to prevent marking a servent as unstable while we
* are actually connecting to the same IP over and over again
*/
if (!GNET_PROPERTY(node_monitor_unstable_ip))
return;
node_check(n);
g_assert(NET_TYPE_LOCAL == host_addr_net(n->addr) || is_host_addr(n->addr));
/*
* Only mark Ultrapeers as bad nodes. Leaves aren't expected to have
* high uptimes
*/
if (!(n->attrs & NODE_A_ULTRA))
return;
/*
* Do not mark nodes as bad with which we did not connect at all, we
* don't know it's behaviour in this case.
*/
if (n->connect_date == 0)
return;
now = tm_time();
/* Don't mark a node with whom we could stay a long time as being bad */
if (
delta_time(now, n->connect_date) >
node_error_cleanup_timer / node_error_threshold
) {
if (GNET_PROPERTY(node_debug) > 1)
g_message("[nodes up] "
"%s not marking as bad. Connected for: %d (min: %d)",
host_addr_to_string(n->addr),
(gint) delta_time(now, n->connect_date),
(gint) (node_error_cleanup_timer / node_error_threshold));
return;
}
hcache_add(HCACHE_UNSTABLE, n->addr, 0, "vendor banned");
if (!GNET_PROPERTY(node_monitor_unstable_servents))
return; /* The user doesn't want us to monitor unstable servents. */
if (n->vendor == NULL)
return;
g_assert(n->vendor != NULL);
bad_client = g_hash_table_lookup(unstable_servent, n->vendor);
if (bad_client == NULL) {
bad_client = walloc0(sizeof(*bad_client));
bad_client->errors = 0;
bad_client->vendor = atom_str_get(n->vendor);
gm_hash_table_insert_const(unstable_servent,
bad_client->vendor, bad_client);
unstable_servents = g_slist_prepend(unstable_servents, bad_client);
}
g_assert(bad_client != NULL);
bad_client->errors++;
if (GNET_PROPERTY(node_debug))
g_warning("[nodes up] Increased error counter (%d) for client: %s",
bad_client->errors,
n->vendor);
}
/**
* Make sure that the vendor of the connecting node does not already use
* more than "unique_nodes" percent of the slots of its kind.
*
* @return TRUE if accepting the node would make us use more slots than
* what the user has configured as acceptable.
*
* @note when low on pongs, monopoly protection is disabled to avoid the
* host contacting the web caches just because it cannot fulfill its
* anti-monopoly requirements.
*/
static gboolean
node_avoid_monopoly(struct gnutella_node *n)
{
guint up_cnt = 0;
guint leaf_cnt = 0;
guint normal_cnt = 0;
GSList *sl;
g_assert(UNSIGNED(GNET_PROPERTY(unique_nodes) <= 100));
if (host_low_on_pongs)
return FALSE;
if (
!n->vendor ||
(n->flags & NODE_F_CRAWLER) ||
GNET_PROPERTY(unique_nodes) == 100
)
return FALSE;
for (sl = sl_nodes; sl; sl = g_slist_next(sl)) {
struct gnutella_node *node = sl->data;
if (node->status != GTA_NODE_CONNECTED || node->vendor == NULL)
continue;
/*
* Node vendor strings are compared up to the specified delimitor,
* i.e. we don't want to take the version number into account.
*
* The vendor name and the version are normally separated with a "/"
* but some people wrongly use " " as the separator.
*/
if (ascii_strcasecmp_delimit(n->vendor, node->vendor, "/ 012345678"))
continue;
if ((node->attrs & NODE_A_ULTRA) || (node->flags & NODE_F_ULTRA))
up_cnt++;
else if (node->flags & NODE_F_LEAF)
leaf_cnt++;
else
normal_cnt++;
}
/* Include current node into counter as well */
if ((n->attrs & NODE_A_ULTRA) || (n->flags & NODE_F_ULTRA))
up_cnt++;
else if (n->flags & NODE_F_LEAF)
leaf_cnt++;
else
normal_cnt++;
switch ((node_peer_t) GNET_PROPERTY(current_peermode)) {
case NODE_P_ULTRA:
if ((n->attrs & NODE_A_ULTRA) || (n->flags & NODE_F_ULTRA)) {
gint max;
max = GNET_PROPERTY(max_connections)
- GNET_PROPERTY(normal_connections);
if (max > 1 && up_cnt * 100 > max * GNET_PROPERTY(unique_nodes))
return TRUE; /* Disallow */
} else if (n->flags & NODE_F_LEAF) {
if (
GNET_PROPERTY(max_leaves) > 1 &&
leaf_cnt * 100 > GNET_PROPERTY(max_leaves)
* GNET_PROPERTY(unique_nodes)
)
return TRUE;
} else {
if (
GNET_PROPERTY(normal_connections) > 1 &&
normal_cnt * 100 > GNET_PROPERTY(normal_connections)
* GNET_PROPERTY(unique_nodes)
)
return TRUE;
}
return FALSE;
case NODE_P_LEAF:
if (
GNET_PROPERTY(max_ultrapeers) > 1 &&
up_cnt * 100 > GNET_PROPERTY(max_ultrapeers)
* GNET_PROPERTY(unique_nodes)
)
return TRUE; /* Dissallow */
return FALSE;
case NODE_P_NORMAL:
if (
GNET_PROPERTY(max_connections) > 1 &&
normal_cnt * 100 > GNET_PROPERTY(max_connections)
* GNET_PROPERTY(unique_nodes)
)
return TRUE;
return FALSE;
case NODE_P_AUTO:
return FALSE;
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
g_assert_not_reached();
break;
}
g_assert_not_reached();
return FALSE;
}
/**
* When we only have "reserve_gtkg_nodes" percent slots left, make sure the
* connecting node is a GTKG node or refuse the connection.
*
* @return TRUE if we should reserve the slot for GTKG, i.e. refuse `n'.
*/
static gboolean
node_reserve_slot(struct gnutella_node *n)
{
guint up_cnt = 0; /* GTKG UPs */
guint leaf_cnt = 0; /* GTKG leafs */
guint normal_cnt = 0; /* GTKG normal nodes */
GSList *sl;
g_assert(UNSIGNED(GNET_PROPERTY(reserve_gtkg_nodes)) <= 100);
if (node_is_gtkg(n))
return FALSE;
if (
!n->vendor ||
(n->flags & NODE_F_CRAWLER) ||
!GNET_PROPERTY(reserve_gtkg_nodes)
)
return FALSE;
for (sl = sl_nodes; sl; sl = sl->next) {
struct gnutella_node *node = sl->data;
if (node->status != GTA_NODE_CONNECTED || node->vendor == NULL)
continue;
if (!node_is_gtkg(node))
continue;
/*
* Count GTKG nodes we are already connected to, by type
*/
if ((node->attrs & NODE_A_ULTRA) || (node->attrs & NODE_F_ULTRA))
up_cnt++;
else if (node->flags & NODE_F_LEAF)
leaf_cnt++;
else
normal_cnt++;
}
/*
* For a given max population `max', already filled by `x' nodes out
* of which `y' are GTKG ones, we want to make sure that we can have
* "reserve_gtkg_nodes" percent of the slots (i.e. `g' percent) used
* by GTKG.
*
* In other words, we want to ensure that we can have "g*max/100" slots
* used by GTKG. We have already `x' slots used, that leaves "max - x"
* ones free. To be able to have our quota of GTKG slots, we need to
* reserve slots to GTKG when "max - x" <= "g*max/100 - y". I.e.
* when `x' >= max - g*max/100 + y.
*/
switch ((node_peer_t) GNET_PROPERTY(current_peermode)) {
case NODE_P_ULTRA:
if ((n->attrs & NODE_A_ULTRA) || (n->flags & NODE_F_ULTRA)) {
gint max, gtkg_min;
/*
* If we would reserve a slot to GTKG but we can get rid of
* a useless ultra, then do so before checking. If we don't
* remove a useless GTKG node, then this will make room for
* the current connection.
*/
max = GNET_PROPERTY(max_connections)
- GNET_PROPERTY(normal_connections);
gtkg_min = GNET_PROPERTY(reserve_gtkg_nodes) * max / 100;
if (GNET_PROPERTY(node_ultra_count) >= max + up_cnt - gtkg_min) {
gboolean is_gtkg;
if (node_remove_useless_ultra(&is_gtkg) && is_gtkg)
up_cnt--;
}
if (GNET_PROPERTY(node_ultra_count) >= max + up_cnt - gtkg_min)
return TRUE;
} else if (n->flags & NODE_F_LEAF) {
gint gtkg_min;
/*
* If we would reserve a slot to GTKG but we can get rid of
* a useless leaf, then do so before checking. If we don't
* remove a useless GTKG node, then this will make room for
* the current connection.
*/
gtkg_min = GNET_PROPERTY(reserve_gtkg_nodes)
* GNET_PROPERTY(max_leaves) / 100;
if (
GNET_PROPERTY(node_leaf_count)
>= GNET_PROPERTY(max_leaves) + leaf_cnt - gtkg_min
) {
gboolean is_gtkg;
if (node_remove_useless_leaf(&is_gtkg) && is_gtkg)
leaf_cnt--;
}
if (
GNET_PROPERTY(node_leaf_count)
>= GNET_PROPERTY(max_leaves) + leaf_cnt - gtkg_min
)
return TRUE;
} else {
gint gtkg_min;
gtkg_min = GNET_PROPERTY(reserve_gtkg_nodes)
* GNET_PROPERTY(normal_connections) / 100;
if (
GNET_PROPERTY(node_normal_count) >=
GNET_PROPERTY(normal_connections) + normal_cnt - gtkg_min
)
return TRUE;
}
return FALSE;
case NODE_P_LEAF:
if (GNET_PROPERTY(max_ultrapeers) > 0 ) {
gint gtkg_min;
gtkg_min = GNET_PROPERTY(reserve_gtkg_nodes)
* GNET_PROPERTY(max_ultrapeers) / 100;
if (GNET_PROPERTY(node_ultra_count)
>= GNET_PROPERTY(max_ultrapeers) + up_cnt - gtkg_min)
return TRUE;
}
return FALSE;
case NODE_P_NORMAL:
if (GNET_PROPERTY(max_connections) > 0) {
gint gtkg_min;
gtkg_min = GNET_PROPERTY(reserve_gtkg_nodes)
* GNET_PROPERTY(max_connections) / 100;
if (
GNET_PROPERTY(node_normal_count) >=
GNET_PROPERTY(max_connections) + normal_cnt - gtkg_min
)
return TRUE;
}
return FALSE;
case NODE_P_AUTO:
return FALSE;
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
g_assert_not_reached();
break;
}
g_assert_not_reached();
return FALSE;
}
/**
* Terminate connection with remote node, but keep structure around for a
* while, for displaying purposes, and also to prevent the node from being
* physically reclaimed within this stack frame.
*
* It will be reclaimed on the "idle" stack frame, via node_real_remove().
*/
void
node_remove(struct gnutella_node *n, const gchar *reason, ...)
{
va_list args;
node_check(n);
if (n->status == GTA_NODE_REMOVING)
return;
va_start(args, reason);
node_remove_v(n, reason, args);
va_end(args);
}
/**
* Determine if the node with specified IP and port is connected. If
* so, schedule it to be removed.
*
* @param addr The address of the node.
* @param port A port number of zero means to match all connections to the
* host. Often the port is redundant [from a user perspective] as
* it is not often that two nodes will be at the same IP and
* connected to us.
* @return The number of nodes that have been removed.
*/
guint
node_remove_by_addr(const host_addr_t addr, guint16 port)
{
const GSList *sl;
guint n_removed = 0;
for (sl = sl_nodes; sl; sl = g_slist_next(sl)) {
const struct gnutella_node *n = sl->data;
if ((!port || n->port == port) && host_addr_equal(n->addr, addr)) {
node_remove_by_id(NODE_ID(n));
n_removed++;
if (port)
break;
}
}
return n_removed;
}
/**
* The vectorized version of node_eof().
*/
static void
node_eof_v(struct gnutella_node *n, const gchar *reason, va_list args)
{
const gchar *format;
node_check(n);
/*
* If the Gnutella connection was established, we should have got a BYE
* to cleanly shutdown.
*/
if (n->flags & NODE_F_ESTABLISHED)
node_mark_bad_vendor(n);
if (n->flags & NODE_F_BYE_SENT) {
g_assert(n->status == GTA_NODE_SHUTDOWN);
if (GNET_PROPERTY(node_debug) > 4) {
va_list dbargs;
printf("EOF-style error during BYE to %s:\n (BYE) ", node_addr(n));
VA_COPY(dbargs, args);
vprintf(reason, dbargs);
va_end(dbargs);
printf("\n");
}
}
/*
* Call node_remove_v() with supplied message unless we already sent a BYE
* message, in which case we're done since the remote end most probably
* read it and closed the connection.
*/
socket_eof(n->socket);
if (n->flags & NODE_F_CLOSING) /* Bye sent or explicit shutdown */
format = NULL; /* Reuse existing reason */
else
format = reason;
node_remove_v(n, format, args);
}
/**
* Got an EOF condition, or a read error, whilst reading Gnet data from node.
*
* Terminate connection with remote node, but keep structure around for a
* while, for displaying purposes.
*/
void
node_eof(struct gnutella_node *n, const gchar *reason, ...)
{
va_list args;
node_check(n);
va_start(args, reason);
node_eof_v(n, reason, args);
va_end(args);
}
/**
* Enter shutdown mode: prevent further writes, drop read broadcasted messages,
* and make sure we flush the buffers at the fastest possible speed.
*/
static void
node_shutdown_mode(struct gnutella_node *n, guint32 delay)
{
/*
* If node is already in shutdown node, simply update the delay.
*/
n->shutdown_delay = delay;
if (n->status == GTA_NODE_SHUTDOWN)
return;
if (n->status == GTA_NODE_CONNECTED) { /* Free Gnet slot */
connected_node_cnt--;
g_assert(connected_node_cnt <= INT_MAX);
if (n->attrs & NODE_A_RX_INFLATE) {
if (n->flags & NODE_F_LEAF)
compressed_leaf_cnt--;
compressed_node_cnt--;
g_assert(compressed_node_cnt <= INT_MAX);
g_assert(compressed_leaf_cnt <= INT_MAX);
}
node_type_count_dec(n);
}
n->status = GTA_NODE_SHUTDOWN;
n->flags &= ~(NODE_F_WRITABLE|NODE_F_READABLE);
n->shutdown_date = tm_time();
mq_discard(n->outq); /* Discard any further data */
node_flushq(n); /* Fast queue flushing */
shutdown_nodes++;
node_fire_node_info_changed(n);
node_fire_node_flags_changed(n);
}
/**
* The vectorized version of node_shutdown().
*/
static void
node_shutdown_v(struct gnutella_node *n, const gchar *reason, va_list args)
{
node_check(n);
if (n->status == GTA_NODE_SHUTDOWN) {
node_recursive_shutdown_v(n, "Shutdown", reason, args);
return;
}
n->flags |= NODE_F_CLOSING;
if (reason) {
gm_vsnprintf(n->error_str, sizeof n->error_str, reason, args);
n->remove_msg = n->error_str;
} else {
n->remove_msg = "Unknown reason";
n->error_str[0] = '\0';
}
node_shutdown_mode(n, SHUTDOWN_GRACE_DELAY);
}
/**
* Stop sending data to node, but keep reading buffered data from it, until
* we hit a Bye packet or EOF. In that mode, we don't relay Queries we may
* read, but replies and pushes are still routed back to other nodes.
*
* This is mostly called when a fatal write error happens, but we want to
* see whether the node did not send us a Bye we haven't read yet.
*/
void
node_shutdown(struct gnutella_node *n, const gchar *reason, ...)
{
va_list args;
va_start(args, reason);
node_shutdown_v(n, reason, args);
va_end(args);
}
/**
* The vectorized version of node_bye().
*/
static void
node_bye_v(struct gnutella_node *n, gint code, const gchar *reason, va_list ap)
{
gnutella_header_t head;
gchar reason_fmt[1024];
size_t len;
gint sendbuf_len;
gchar *reason_base = &reason_fmt[2]; /* Leading 2 bytes for code */
node_check(n);
g_assert(!NODE_IS_UDP(n));
if (n->status == GTA_NODE_SHUTDOWN) {
node_recursive_shutdown_v(n, "Bye", reason, ap);
return;
}
n->flags |= NODE_F_CLOSING;
if (reason) {
gm_vsnprintf(n->error_str, sizeof n->error_str, reason, ap);
n->remove_msg = n->error_str;
} else {
n->remove_msg = NULL;
n->error_str[0] = '\0';
}
/*
* Discard all the queued entries, we're not going to send them.
* The only message that may remain is the oldest partially sent.
*/
if (n->searchq)
sq_clear(n->searchq);
mq_clear(n->outq);
/*
* Build the bye message.
*/
len = gm_snprintf(reason_base, sizeof reason_fmt - 3,
"%s", n->error_str);
/* XXX Add X-Try and X-Try-Ultrapeers */
if (code != 200) {
len += gm_snprintf(reason_base + len, sizeof reason_fmt - len - 3,
"\r\n"
"Server: %s\r\n"
"\r\n",
version_string);
}
g_assert(len <= sizeof reason_fmt - 3);
reason_base[len] = '\0';
len += 2 + 1; /* 2 for the leading code, 1 for the trailing NUL */
gnutella_bye_set_code(reason_fmt, code);
message_set_muid(&head, GTA_MSG_BYE);
gnutella_header_set_function(&head, GTA_MSG_BYE);
gnutella_header_set_ttl(&head, 1);
gnutella_header_set_hops(&head, 0);
gnutella_header_set_size(&head, len);
/*
* Send the bye message, enlarging the TCP input buffer to make sure
* we can atomically send the message plus the remaining queued data.
*/
sendbuf_len = NODE_SEND_BUFSIZE + mq_size(n->outq) +
len + sizeof(head) + 1024; /* Slightly larger, for flow-control */
socket_send_buf(n->socket, sendbuf_len, FALSE);
gmsg_split_sendto_one(n, &head, reason_fmt, len + sizeof(head));
/*
* Whether we sent the message or not, enter shutdown mode.
*
* We'll stay in the shutdown mode for some time, then we'll kick the node
* out. But not doing it immediately gives a chance for the message to
* proagate AND be read by the remote node.
*
* When sending is delayed, we will periodically check for the
* NODE_F_BYE_SENT condition and change the shutdown delay to a much
* shorter period when the TX queue is emptied.
*
* In shutdown mode, we'll also preserve the existing error message for
* node_remove().
*
* NB: To know whether we sent it or not, we need to probe the size
* of the TX stack, since there is a possible compression stage that
* can delay sending data for a little while. That's why we
* use mq_pending() and not mq_size().
*/
if (mq_pending(n->outq) == 0) {
if (GNET_PROPERTY(node_debug))
g_message("successfully sent BYE %d \"%s\" to %s (%s)",
code, n->error_str, node_addr(n), node_vendor(n));
socket_tx_shutdown(n->socket);
node_shutdown_mode(n, BYE_GRACE_DELAY);
} else {
if (GNET_PROPERTY(node_debug))
g_message("delayed sending of BYE %d \"%s\" to %s (%s)",
code, n->error_str, node_addr(n), node_vendor(n));
n->flags |= NODE_F_BYE_SENT;
node_shutdown_mode(n, SHUTDOWN_GRACE_DELAY);
}
}
/**
* Terminate connection by sending a bye message to the remote node. Upon
* reception of that message, the connection will be closed by the remote
* party.
*
* This is otherwise equivalent to the node_shutdown() call.
*/
void
node_bye(gnutella_node_t *n, gint code, const gchar * reason, ...)
{
va_list args;
va_start(args, reason);
node_bye_v(n, code, reason, args);
va_end(args);
}
/**
* If node is writable, act as if node_bye() had been called.
* Otherwise, act as if node_remove() had been called.
*/
void
node_bye_if_writable(
struct gnutella_node *n, gint code, const gchar *reason, ...)
{
va_list args;
va_start(args, reason);
if (NODE_IS_WRITABLE(n))
node_bye_v(n, code, reason, args);
else
node_remove_v(n, reason, args);
}
/**
* Is there a node connected with this IP/port?
*
* The port is tested only when `incoming' is FALSE, i.e. we allow
* only one incoming connection per IP, even when there are several
* instances, all on different ports.
*/
gboolean
node_is_connected(const host_addr_t addr, guint16 port, gboolean incoming)
{
if (is_my_address_and_port(addr, port)) {
return TRUE;
}
/*
* If incoming is TRUE we have to do an exhaustive search because
* we have to ignore the port. Otherwise we can use the fast
* hashtable lookup.
* -- Richard, 29/04/2004
*/
if (incoming) {
const GSList *sl;
for (sl = sl_nodes; sl; sl = g_slist_next(sl)) {
const struct gnutella_node *n = sl->data;
if (
n->status != GTA_NODE_REMOVING &&
n->status != GTA_NODE_SHUTDOWN &&
n->port == port &&
host_addr_equal(n->addr, addr)
) {
return TRUE;
}
}
return FALSE;
} else {
return node_ht_connected_nodes_has(addr, port);
}
}
/**
* Are we directly connected to that host?
*/
gboolean
node_host_is_connected(const host_addr_t addr, guint16 port)
{
/* Check our local address */
return is_my_address(addr) ||
node_ht_connected_nodes_has(addr, port);
}
/**
* Build CONNECT_PONGS_COUNT pongs to emit as an X-Try header.
* We stick to strict formatting rules: no line of more than 76 chars.
*
* @return a pointer to static data.
*
* @bug
* XXX Refactoring note: there is a need for generic header formatting
* routines, and especially the dumping routing, which could be taught
* basic formatting and splitting so that very long lines are dumped using
* continuations. --RAM, 10/01/2002
*/
static const gchar *
formatted_connection_pongs(const gchar *field, host_type_t htype, gint num)
{
struct gnutella_host hosts[CONNECT_PONGS_COUNT];
const gchar *line = "";
gint hcount;
g_assert(num > 0 && num <= CONNECT_PONGS_COUNT);
hcount = hcache_fill_caught_array(htype, hosts, num);
g_assert(hcount >= 0 && hcount <= num);
/* The most a pong can take is "xxx.xxx.xxx.xxx:yyyyy, ", i.e. 23 */
if (hcount) {
gint i;
gpointer fmt = header_fmt_make(field, ", ",
23 /* 23 == PONG_LEN */ * CONNECT_PONGS_COUNT + 30);
for (i = 0; i < hcount; i++) {
header_fmt_append_value(fmt, gnet_host_to_string(&hosts[i]));
}
header_fmt_end(fmt);
line = header_fmt_to_string(fmt);
header_fmt_free(fmt);
}
return line; /* Pointer to static data */
}
/**
* qsort() callback for sorting GTKG nodes at the front.
*/
static gint
node_gtkg_cmp(const void *np1, const void *np2)
{
const gnutella_node_t *n1 = *(const gnutella_node_t **) np1;
const gnutella_node_t *n2 = *(const gnutella_node_t **) np2;
if (node_is_gtkg(n1)) {
return node_is_gtkg(n2) ? 0 : -1;
} else if (node_is_gtkg(n2)) {
return 1;
} else {
return 0;
}
}
/**
* Inflate UDP payload, updating node internal data structures to reflect
* the new payload size..
*
* @return success status, FALSE meaning the message was accounted as dropped
* already.
*/
static gboolean
node_inflate_payload(gnutella_node_t *n)
{
gint outlen = payload_inflate_buffer_len;
gint ret;
g_assert(NODE_IS_UDP(n));
gnet_stats_count_general(GNR_UDP_RX_COMPRESSED, 1);
if (!zlib_is_valid_header(n->data, n->size)) {
if (GNET_PROPERTY(udp_debug))
g_warning("UDP got %s with non-deflated payload from %s",
gmsg_infostr_full_split(&n->header, n->data), node_addr(n));
gnet_stats_count_dropped(n, MSG_DROP_INFLATE_ERROR);
return FALSE;
}
/*
* Start of payload looks OK, attempt inflation.
*/
ret = zlib_inflate_into(n->data, n->size, payload_inflate_buffer, &outlen);
if (ret != Z_OK) {
if (GNET_PROPERTY(udp_debug))
g_warning("UDP cannot inflate %s from %s: %s",
gmsg_infostr_full_split(&n->header, n->data), node_addr(n),
zlib_strerror(ret));
gnet_stats_count_dropped(n, MSG_DROP_INFLATE_ERROR);
return FALSE;
}
/*
* Inflation worked, update the header and the data pointers.
*/
n->data = payload_inflate_buffer;
gnutella_header_set_ttl(&n->header,
gnutella_header_get_ttl(&n->header) & ~GTA_UDP_DEFLATED);
gnutella_header_set_size(&n->header, outlen);
if (GNET_PROPERTY(udp_debug))
g_message("UDP inflated %d-byte payload from %s into %s",
n->size, node_addr(n),
gmsg_infostr_full_split(&n->header, n->data));
n->size = outlen;
return TRUE;
}
/**
* Generate the "Peers:" and "Leaves:" headers in a static buffer.
*
* @return ready-to-insert header chunk, with all lines ending with "\r\n".
*/
static gchar *
node_crawler_headers(struct gnutella_node *n)
{
static gchar buf[8192]; /* 8 KB */
gnutella_node_t **ultras = NULL; /* Array of ultra nodes */
gnutella_node_t **leaves = NULL; /* Array of `leaves' */
size_t ultras_len = 0; /* Size of `ultras' */
size_t leaves_len = 0; /* Size of `leaves' */
gint ux = 0; /* Index in `ultras' */
gint lx = 0; /* Index in `leaves' */
gint uw = 0; /* Amount of ultras written */
gint lw = 0; /* Amount of leaves written */
GSList *sl;
gint maxsize;
gint rw;
gint count;
if (GNET_PROPERTY(node_ultra_count)) {
ultras_len = GNET_PROPERTY(node_ultra_count) * sizeof ultras[0];
ultras = walloc(ultras_len);
}
if (GNET_PROPERTY(node_leaf_count)) {
leaves_len = GNET_PROPERTY(node_leaf_count) * sizeof leaves[0];
leaves = walloc(leaves_len);
}
for (sl = sl_nodes; sl; sl = g_slist_next(sl)) {
gnutella_node_t *cn = sl->data;
if (!NODE_IS_ESTABLISHED(cn))
continue;
if (!is_host_addr(cn->gnet_addr)) /* No information yet */
continue;
if (NODE_IS_ULTRA(cn)) {
g_assert((guint) ux < GNET_PROPERTY(node_ultra_count));
ultras[ux++] = cn;
continue;
}
if (NODE_IS_LEAF(cn)) {
g_assert((guint) lx < GNET_PROPERTY(node_leaf_count));
leaves[lx++] = cn;
continue;
}
}
/*
* Put gtk-gnutella nodes at the front of the array, so that their
* addresses are listed first, in case we cannot list everyone.
*/
if (ux)
qsort(ultras, ux, sizeof(gnutella_node_t *), node_gtkg_cmp);
if (lx)
qsort(leaves, lx, sizeof(gnutella_node_t *), node_gtkg_cmp);
/*
* Avoid sending an incomplete trailing IP address by roughly avoiding
* any write if less than 32 chars are available in the buffer.
*/
maxsize = sizeof(buf) - 32;
/*
* First, the peers.
*/
rw = gm_snprintf(buf, sizeof(buf), "Peers: ");
for (count = 0; count < ux && rw < maxsize; count++) {
struct gnutella_node *cn = ultras[count];
if (cn == n) /* Don't show the crawler itself */
continue;
if (uw > 0)
rw += gm_snprintf(&buf[rw], sizeof(buf)-rw, ", ");
rw += gm_snprintf(&buf[rw], sizeof(buf)-rw, "%s",
host_addr_port_to_string(cn->gnet_addr, cn->gnet_port));
uw++; /* One more ultra written */
}
rw += gm_snprintf(&buf[rw], sizeof(buf)-rw, "\r\n");
if (GNET_PROPERTY(current_peermode) != NODE_P_ULTRA || rw >= maxsize)
goto cleanup;
/*
* We're an ultranode, list our leaves.
*/
rw += gm_snprintf(&buf[rw], sizeof(buf)-rw, "Leaves: ");
for (count = 0; count < lx && rw < maxsize; count++) {
struct gnutella_node *cn = leaves[count];
if (cn == n) /* Don't show the crawler itself */
continue;
if (lw > 0)
rw += gm_snprintf(&buf[rw], sizeof(buf)-rw, ", ");
rw += gm_snprintf(&buf[rw], sizeof(buf)-rw, "%s",
host_addr_port_to_string(cn->gnet_addr, cn->gnet_port));
lw++; /* One more leaf written */
}
rw += gm_snprintf(&buf[rw], sizeof(buf)-rw, "\r\n");
if (GNET_PROPERTY(node_debug)) g_message(
"TCP crawler sending %d/%d ultra%s and %d/%d lea%s to %s",
uw, ux, uw == 1 ? "" : "s",
lw, lx, lw == 1 ? "f" : "ves",
node_addr(n));
/* FALL THROUGH */
cleanup:
if (ultras)
wfree(ultras, ultras_len);
if (leaves)
wfree(leaves, leaves_len);
return buf;
}
/**
* Send error message to remote end, a node presumably.
*
* @param s the connected socket (mandatory)
* @param n the node (optional, NULL if not available)
* @param code the error code to report
* @param msg the error message (printf format)
* @param ap variable argument pointer, arguments for the error message
*/
static void
send_error(
struct gnutella_socket *s, struct gnutella_node *n,
int code, const gchar *msg, va_list ap)
{
gchar gnet_response[2048];
gchar msg_tmp[256];
size_t rw;
ssize_t sent;
gboolean saturated = bsched_saturated(BSCHED_BWS_GOUT);
const gchar *version;
gchar *token;
gchar xlive[128];
gchar xtoken[128];
gint pongs = saturated ? CONNECT_PONGS_LOW : CONNECT_PONGS_COUNT;
socket_check(s);
g_assert(n == NULL || n->socket == s);
gm_vsnprintf(msg_tmp, sizeof(msg_tmp)-1, msg, ap);
/*
* Try to limit the size of our reply if we're saturating bandwidth.
*/
if (saturated) {
xlive[0] = '\0';
version = version_short_string;
token = socket_omit_token(s) ? NULL : tok_short_version();
} else {
gm_snprintf(xlive, sizeof(xlive),
"X-Live-Since: %s\r\n", start_rfc822_date);
version = version_string;
token = socket_omit_token(s) ? NULL : tok_version();
}
if (token)
gm_snprintf(xtoken, sizeof(xtoken), "X-Token: %s\r\n", token);
else
xtoken[0] = '\0';
/*
* If we have a node and we know that it is NOT a gtk-gnutella node,
* chances are it will not care about the token and the X-Live-Since.
*
* If it is a genuine gtk-gnutella node, give it the maximum amount
* of pongs though, to make it easier for the node to get a connection.
*/
if (n != NULL && n->vendor != NULL) {
if (node_is_gtkg(n)) {
if (!(n->flags & NODE_F_FAKE_NAME)) /* A genuine GTKG peer */
pongs = CONNECT_PONGS_COUNT; /* Give it the maximum */
} else {
xlive[0] = '\0';
xtoken[0] = '\0';
}
}
/*
* Do not send them any pong on 403 and 406 errors, even if GTKG.
* When banning, the error code is 550 and does not warrant pongs either.
*/
if (code == 403 || code == 406 || code == 550)
pongs = 0;
/*
* Build the response.
*/
rw = gm_snprintf(gnet_response, sizeof(gnet_response),
"GNUTELLA/0.6 %d %s\r\n"
"User-Agent: %s\r\n"
"Remote-IP: %s\r\n"
"%s" /* X-Token */
"%s" /* X-Live-Since */
"%s" /* X-Ultrapeer */
"%s" /* X-Try */
"%s" /* X-Try-Ultrapeers */
"\r\n",
code, msg_tmp, version, host_addr_to_string(s->addr), xtoken, xlive,
GNET_PROPERTY(current_peermode) == NODE_P_NORMAL ? "" :
GNET_PROPERTY(current_peermode) == NODE_P_LEAF ?
"X-Ultrapeer: False\r\n": "X-Ultrapeer: True\r\n",
(GNET_PROPERTY(current_peermode) == NODE_P_NORMAL && pongs) ?
formatted_connection_pongs("X-Try", HOST_ANY, pongs) : "",
(GNET_PROPERTY(current_peermode) != NODE_P_NORMAL && pongs) ?
formatted_connection_pongs("X-Try-Ultrapeers", HOST_ULTRA, pongs)
: ""
);
g_assert(rw < sizeof(gnet_response));
sent = bws_write(BSCHED_BWS_GOUT, &s->wio, gnet_response, rw);
if ((ssize_t) -1 == sent) {
if (GNET_PROPERTY(node_debug))
g_warning("Unable to send back error %d (%s) to node %s: %s",
code, msg_tmp, host_addr_to_string(s->addr), g_strerror(errno));
} else if ((size_t) sent < rw) {
if (GNET_PROPERTY(node_debug)) {
g_warning("Only sent %d out of %d bytes of error %d (%s) "
"to node %s: %s", (int) sent, (int) rw, code, msg_tmp,
host_addr_to_string(s->addr), g_strerror(errno));
}
} else if (GNET_PROPERTY(node_debug) > 2) {
g_message("----Sent error %d to node %s (%d bytes):\n%.*s----",
code, host_addr_to_string(s->addr),
(int) rw, (int) rw, gnet_response);
}
}
/**
* Send error message to remote end, a node presumably.
*
* @attention
* NB: We don't need a node to call this routine, only a socket.
*/
void
send_node_error(struct gnutella_socket *s, int code, const gchar *msg, ...)
{
va_list args;
va_start(args, msg);
send_error(s, NULL, code, msg, args);
va_end(args);
}
/**
* Send error message to remote node.
*/
static void
node_send_error(struct gnutella_node *n, int code, const gchar *msg, ...)
{
va_list args;
va_start(args, msg);
send_error(n->socket, n, code, msg, args);
va_end(args);
}
/**
* Request that node becomes our push-proxy.
*/
static void
send_proxy_request(gnutella_node_t *n)
{
g_assert(n->attrs & NODE_A_CAN_VENDOR);
g_assert(GNET_PROPERTY(is_firewalled));
g_assert(!is_host_addr(n->proxy_addr)); /* Not proxying us yet */
n->flags |= NODE_F_PROXY;
vmsg_send_proxy_req(n, GNET_PROPERTY(servent_guid));
}
/**
* Called when we were not firewalled and suddenly become firewalled.
* Send proxy requests to our current connections.
*/
void
node_became_firewalled(void)
{
GSList *sl;
guint sent = 0;
g_assert(GNET_PROPERTY(is_firewalled));
for (sl = sl_nodes; sl; sl = g_slist_next(sl)) {
struct gnutella_node *n = sl->data;
if (socket_listen_port() && sent < 10 && n->attrs & NODE_A_CAN_VENDOR) {
vmsg_send_tcp_connect_back(n, socket_listen_port());
sent++;
if (GNET_PROPERTY(node_debug))
g_message("sent TCP connect back request to %s",
host_addr_port_to_string(n->addr, n->port));
}
if (NODE_IS_LEAF(n))
continue;
if (!is_host_addr(n->proxy_addr) && (n->attrs & NODE_A_CAN_VENDOR))
send_proxy_request(n);
}
}
/**
* Called when we were not firewalled and suddenly become UDP firewalled.
* Send UDP connect back requests to our current connections.
*/
void
node_became_udp_firewalled(void)
{
GSList *sl;
guint sent = 0;
g_assert(GNET_PROPERTY(is_udp_firewalled));
if (0 == socket_listen_port())
return;
for (sl = sl_nodes; sl; sl = g_slist_next(sl)) {
struct gnutella_node *n = sl->data;
if (0 == (n->attrs & NODE_A_CAN_VENDOR))
continue;
vmsg_send_udp_connect_back(n, socket_listen_port());
if (GNET_PROPERTY(node_debug))
g_message("sent UDP connect back request to %s",
host_addr_port_to_string(n->addr, n->port));
if (10 == ++sent)
break;
}
}
/***
*** TX deflate callbacks
***/
static void
node_add_tx_deflated(gpointer o, gint amount)
{
gnutella_node_t *n = o;
n->tx_deflated += amount;
}
static void
node_tx_shutdown(gpointer o, const gchar *reason, ...)
{
gnutella_node_t *n = o;
va_list args;
va_start(args, reason);
node_shutdown_v(n, reason, args);
va_end(args);
}
static struct tx_deflate_cb node_tx_deflate_cb = {
node_add_tx_deflated, /* add_tx_deflated */
node_tx_shutdown, /* shutdown */
};
/***
*** TX link callbacks
***/
static void
node_add_tx_written(gpointer o, gint amount)
{
gnutella_node_t *n = o;
n->tx_written += amount;
}
static void
node_tx_eof_remove(gpointer o, const gchar *reason, ...)
{
gnutella_node_t *n = o;
va_list args;
va_start(args, reason);
socket_eof(n->socket);
node_remove_v(n, reason, args);
va_end(args);
}
static void
node_tx_eof_shutdown(gpointer o, const gchar *reason, ...)
{
gnutella_node_t *n = o;
va_list args;
va_start(args, reason);
socket_eof(n->socket);
node_shutdown_v(n, reason, args);
va_end(args);
}
static void
node_tx_unflushq(gpointer o)
{
gnutella_node_t *n = o;
node_unflushq(n);
}
static struct tx_link_cb node_tx_link_cb = {
node_add_tx_written, /* add_tx_written */
node_tx_eof_remove, /* eof_remove */
node_tx_eof_shutdown, /* eof_shutdown */
node_tx_unflushq, /* unflushq */
};
/***
*** TX datagram callbacks
***/
static struct tx_dgram_cb node_tx_dgram_cb = {
node_add_tx_written, /* add_tx_written */
};
/***
*** RX inflate callbacks
***/
static void
node_add_rx_inflated(gpointer o, gint amount)
{
gnutella_node_t *n = o;
n->rx_inflated += amount;
}
static void
node_rx_inflate_error(gpointer o, const gchar *reason, ...)
{
gnutella_node_t *n = o;
va_list args;
va_start(args, reason);
node_mark_bad_vendor(n);
node_bye_v(n, 501, reason, args);
va_end(args);
}
static struct rx_inflate_cb node_rx_inflate_cb = {
node_add_rx_inflated, /* add_rx_inflated */
node_rx_inflate_error, /* inflate_error */
};
/***
*** RX link callbacks
***/
static void
node_add_rx_given(gpointer o, ssize_t amount)
{
gnutella_node_t *n = o;
n->rx_given += amount;
}
static void
node_rx_read_error(gpointer o, const gchar *reason, ...)
{
gnutella_node_t *n = o;
va_list args;
va_start(args, reason);
node_eof_v(n, reason, args);
va_end(args);
}
static void
node_rx_got_eof(gpointer o)
{
gnutella_node_t *n = o;
if (n->n_ping_sent <= 2 && n->n_pong_received)
node_eof(n, NG_("Got %d connection pong", "Got %d connection pongs",
n->n_pong_received), n->n_pong_received);
else
node_eof(n, "Failed (EOF)");
}
static struct rx_link_cb node_rx_link_cb = {
node_add_rx_given, /* add_rx_given */
node_rx_read_error, /* read_error */
node_rx_got_eof, /* got_eof */
};
/**
* Called when we know that we're connected to the node, at the end of
* the handshaking (both for incoming and outgoing connections).
*/
static void
node_is_now_connected(struct gnutella_node *n)
{
gboolean peermode_changed = FALSE;
gnet_host_t host;
txdrv_t *tx;
socket_check(n->socket);
/*
* Temporary: if node is not a broken GTKG node, record it.
*/
if (!(n->attrs & NODE_A_NO_DUPS))
sl_nodes_without_broken_gtkg =
g_slist_prepend(sl_nodes_without_broken_gtkg, n);
/*
* Cleanup hanshaking objects.
*/
if (n->io_opaque) /* None for outgoing 0.4 connections */
io_free(n->io_opaque);
if (n->socket->getline) {
getline_free(n->socket->getline);
n->socket->getline = NULL;
}
/*
* Terminate crawler connection that goes through the whole 3-way
* handshaking protocol.
*/
if (n->flags & NODE_F_CRAWLER) {
node_remove(n, _("Sent crawling info"));
return;
}
/*
* Make sure we did not change peermode whilst performing the 3-way
* handshaking with this node.
*/
peermode_changed =
n->start_peermode != GNET_PROPERTY(current_peermode) ||
n->start_peermode != peermode.new;
/*
* Determine correct peer mode.
*
* If we're a leaf node and we connected to an ultranode, send it
* our query routing table.
*/
n->peermode = NODE_P_NORMAL;
if (n->flags & NODE_F_ULTRA) {
if (GNET_PROPERTY(current_peermode) != NODE_P_NORMAL)
n->peermode = NODE_P_ULTRA;
} else if (n->flags & NODE_F_LEAF) {
if (GNET_PROPERTY(current_peermode) == NODE_P_ULTRA)
n->peermode = NODE_P_LEAF;
} else if (n->attrs & NODE_A_ULTRA)
n->peermode = NODE_P_ULTRA;
/* If peermode did not change, current_peermode = leaf => node is Ultra */
g_assert(peermode_changed ||
GNET_PROPERTY(current_peermode) != NODE_P_LEAF || NODE_IS_ULTRA(n));
/*
* Update state, and mark node as valid.
*/
n->status = GTA_NODE_CONNECTED;
n->flags |= NODE_F_VALID;
n->last_update = n->connect_date = tm_time();
connected_node_cnt++;
/*
* Count nodes by type.
*/
switch (n->peermode) {
case NODE_P_LEAF:
gnet_prop_incr_guint32(PROP_NODE_LEAF_COUNT);
break;
case NODE_P_NORMAL:
gnet_prop_incr_guint32(PROP_NODE_NORMAL_COUNT);
break;
case NODE_P_ULTRA:
gnet_prop_set_guint32_val(PROP_NODE_ULTRA_COUNT,
GNET_PROPERTY(node_ultra_count) + 1);
break;
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
break;
}
/*
* Determine the frequency at which we will send "alive pings", and at
* which we shall accept regular pings on that connection.
*/
n->ping_throttle = PING_REG_THROTTLE;
switch ((node_peer_t) GNET_PROPERTY(current_peermode)) {
case NODE_P_NORMAL:
n->alive_period = ALIVE_PERIOD;
break;
case NODE_P_ULTRA:
if (n->peermode == NODE_P_LEAF) {
n->alive_period = ALIVE_PERIOD_LEAF;
n->ping_throttle = PING_LEAF_THROTTLE;
} else
n->alive_period = ALIVE_PERIOD;
break;
case NODE_P_LEAF:
n->alive_period = ALIVE_PERIOD_LEAF;
break;
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
g_error("Invalid peer mode %d", GNET_PROPERTY(current_peermode));
break;
}
/*
* Create the RX stack, and enable reception of data.
*/
gnet_host_set(&host, n->addr, n->port);
{
struct rx_link_args args;
args.cb = &node_rx_link_cb;
args.bws = n->peermode == NODE_P_LEAF
? BSCHED_BWS_GLIN : BSCHED_BWS_GIN;
args.wio = &n->socket->wio;
n->rx = rx_make(n, &host, rx_link_get_ops(), &args);
}
if (n->attrs & NODE_A_RX_INFLATE) {
struct rx_inflate_args args;
if (GNET_PROPERTY(node_debug) > 4)
g_message("receiving compressed data from node %s", node_addr(n));
args.cb = &node_rx_inflate_cb;
n->rx = rx_make_above(n->rx, rx_inflate_get_ops(), &args);
if (n->flags & NODE_F_LEAF)
compressed_leaf_cnt++;
compressed_node_cnt++;
}
rx_set_data_ind(n->rx, node_data_ind);
rx_enable(n->rx);
n->flags |= NODE_F_READABLE;
/*
* Create the TX stack, as we're going to transmit Gnet messages.
*/
{
struct tx_link_args args;
args.cb = &node_tx_link_cb;
args.bws = n->peermode == NODE_P_LEAF
? BSCHED_BWS_GLOUT : BSCHED_BWS_GOUT;
args.wio = &n->socket->wio;
tx = tx_make(n, &host, tx_link_get_ops(), &args); /* Cannot fail */
}
/*
* If we committed on compressing traffic, install layer.
*/
if (n->attrs & NODE_A_TX_DEFLATE) {
struct tx_deflate_args args;
txdrv_t *ctx;
if (GNET_PROPERTY(node_debug) > 4)
g_message("sending compressed data to node %s", node_addr(n));
args.cq = callout_queue;
args.cb = &node_tx_deflate_cb;
args.nagle = TRUE;
args.gzip = FALSE;
args.buffer_size = NODE_TX_BUFSIZ;
args.buffer_flush = NODE_TX_FLUSH;
ctx = tx_make_above(tx, tx_deflate_get_ops(), &args);
if (ctx == NULL) {
tx_free(tx);
node_remove(n, _("Cannot setup compressing TX stack"));
return;
}
tx = ctx; /* Use compressing stack */
}
g_assert(tx);
n->outq = mq_tcp_make(GNET_PROPERTY(node_sendqueue_size), n, tx);
n->flags |= NODE_F_WRITABLE;
n->alive_pings = alive_make(n, n->alive_period == ALIVE_PERIOD ?
ALIVE_MAX_PENDING : ALIVE_MAX_PENDING_LEAF);
/*
* In ultra mode, we're not broadcasting queries blindly, we're using
* dynamic querying, so there is no need for a per-node search queue.
*/
if (GNET_PROPERTY(current_peermode) != NODE_P_ULTRA)
n->searchq = sq_make(n);
/*
* Terminate connection if the peermode changed during handshaking.
*/
if (peermode_changed) {
node_bye(n, 504, "Switched between Leaf/Ultra during handshake");
return;
}
/*
* Initiate QRP sending if we're a leaf node or if we're an ultra node
* and the remote note is an UP supporting last-hop QRP.
*/
if (
NODE_IS_ULTRA(n) &&
(GNET_PROPERTY(current_peermode) == NODE_P_LEAF ||
(GNET_PROPERTY(current_peermode) == NODE_P_ULTRA &&
(n->attrs & NODE_A_UP_QRP)))
) {
struct routing_table *qrt = qrt_get_table();
/*
* If we don't even have our first QRT computed yet, we
* will send it to the ultranode when node_qrt_changed()
* is called by the computation code.
*/
if (qrt) {
node_send_qrt(n, qrt);
if (!NODE_IS_CONNECTED(n))
return;
}
}
/*
* Set the socket's send buffer size to a small value, to make sure we
* flow control early. Use their setup for the receive buffer.
*/
socket_send_buf(n->socket, NODE_IS_LEAF(n) ?
NODE_SEND_LEAF_BUFSIZE : NODE_SEND_BUFSIZE, TRUE);
socket_recv_buf(n->socket, GNET_PROPERTY(node_rx_size) * 1024, TRUE);
/*
* If we have an incoming connection, send an "alive" ping.
* Otherwise, send a "handshaking" ping.
*/
if (n->flags & NODE_F_INCOMING)
alive_send_ping(n->alive_pings);
else
pcache_outgoing_connection(n); /* Will send proper handshaking ping */
/*
* If node supports vendor-specific messages, advertise the set we support.
*
* If we are firewalled, and remote node supports vendor-specific
* messages, send a connect back, to see whether we are firewalled.
*/
if (n->attrs & NODE_A_CAN_VENDOR) {
vmsg_send_messages_supported(n);
vmsg_send_features_supported(n);
if (GNET_PROPERTY(is_firewalled)) {
if (0 != socket_listen_port())
vmsg_send_tcp_connect_back(n, socket_listen_port());
if (!NODE_IS_LEAF(n))
send_proxy_request(n);
}
if (udp_active()) {
if (!GNET_PROPERTY(recv_solicited_udp))
udp_send_ping(NULL, n->addr, n->port, FALSE);
else if (
GNET_PROPERTY(is_udp_firewalled) &&
0 != socket_listen_port()
)
vmsg_send_udp_connect_back(n, socket_listen_port());
}
}
/*
* If we're an Ultranode, we're going to monitor the queries sent by
* our leaves and by our neighbours.
*/
if (GNET_PROPERTY(current_peermode) != NODE_P_LEAF) {
if (NODE_IS_LEAF(n))
n->qseen = g_hash_table_new(g_str_hash, g_str_equal);
else {
if (GNET_PROPERTY(node_watch_similar_queries)) {
n->qrelayed = g_hash_table_new(g_str_hash, g_str_equal);
n->qrelayed_created = tm_time();
}
}
}
/*
* Update the GUI.
*/
node_fire_node_info_changed(n);
node_fire_node_flags_changed(n);
node_added = n;
g_hook_list_invoke(&node_added_hook_list, TRUE);
node_added = NULL;
}
/**
* Received a Bye message from remote node.
*/
static void
node_got_bye(struct gnutella_node *n)
{
guint16 code;
const gchar *message = n->data + 2;
const gchar *p;
guchar c;
guint cnt;
gboolean warned = FALSE;
gboolean is_plain_message = TRUE;
guint message_len = n->size - 2;
code = peek_le16(n->data);
/*
* Codes are supposed to be 2xx, 4xx or 5xx.
*
* But older GnucDNA wer bugged enough to forget about the code and
* started to emit the message right away. Fortunately, we can
* detect this because the two ASCII bytes will make the code
* appear out of range... We force code 901 when we detect and
* correct this bug.
*
* --RAM, 2004-10-19, revised 2005-09-30
*/
if (code > 999) {
guchar c1 = n->data[0];
guchar c2 = n->data[1];
if (is_ascii_alnum(c1) && is_ascii_alnum(c2)) {
message = n->data;
message_len = n->size;
code = 901;
}
}
/*
* The first line can end with <cr><lf>, in which case we have an RFC-822
* style header in the packet. Since the packet may not be NUL terminated,
* perform the scan manually.
*/
for (cnt = 0, p = message; cnt < message_len; cnt++, p++) {
c = *p;
if (c == '\0') { /* NUL marks the end of the message */
if (GNET_PROPERTY(node_debug) && cnt != message_len - 1)
g_warning("BYE message %u from %s <%s> has early NUL",
code, node_addr(n), node_vendor(n));
break;
} else if (c == '\r') {
if (++cnt < n->size) {
if ((c = *(++p)) == '\n') {
is_plain_message = FALSE;
message_len = (p - message + 1) - 2; /* 2 = len("\r\n") */
break;
} else {
p--; /* Undo our look-ahead */
cnt--;
}
}
continue;
}
if (c && c < ' ' && !warned) {
warned = TRUE;
if (GNET_PROPERTY(node_debug))
g_warning("BYE message %u from %s <%s> contains control chars",
code, node_addr(n), node_vendor(n));
}
}
if (!is_plain_message) {
/* XXX parse header */
if (GNET_PROPERTY(node_debug))
g_message("----Bye Message from %s:\n%.*s----",
node_addr(n), (gint) n->size - 2, message);
}
if (GNET_PROPERTY(node_debug))
g_warning("%s node %s (%s) sent us BYE %d %.*s",
node_type(n), node_addr(n), node_vendor(n),
code, (int) MIN(120, message_len), message);
node_remove(n, _("Got BYE %d %.*s"), code,
(int) MIN(120, message_len), message);
}
/**
* Whether they want to be "online" within Gnutella or not.
*/
void
node_set_online_mode(gboolean on)
{
GSList *sl;
if (allow_gnet_connections == on) /* No change? */
return;
allow_gnet_connections = on;
if (on)
return;
/*
* They're disallowing Gnutella connections.
*/
for (sl = sl_nodes; sl; sl = g_slist_next(sl)) {
struct gnutella_node *n = sl->data;
if (n->status == GTA_NODE_REMOVING)
continue;
node_bye_if_writable(n, 202, "User going offline");
}
}
/**
* Called from the property system when current peermode is changed.
*/
void
node_current_peermode_changed(node_peer_t mode)
{
/*
* Only record the fact that it changed.
*
* We'll react by calling node_set_current_peermode() later, in the
* node_timer() routine, so that we do not close connections in the
* middle of the handshaking handling routing.
*/
peermode.changed = TRUE;
peermode.new = mode;
}
/**
* Called from the node timer when the current peermode has changed.
*
* We call this "asynchronously" because the current peermode can change
* during handshaking, when we accept the guidance of the remote ultrapeer
* to become a leaf node.
*/
static void
node_set_current_peermode(node_peer_t mode)
{
static node_peer_t old_mode = NODE_P_UNKNOWN;
const gchar *msg = NULL;
if (NODE_P_UNKNOWN == old_mode)
old_mode = GNET_PROPERTY(configured_peermode);
switch (mode) {
case NODE_P_NORMAL:
msg = "normal";
node_bye_flags(NODE_F_LEAF, 203, "Becoming a regular node");
if (old_mode == NODE_P_LEAF)
node_bye_flags(NODE_F_ULTRA, 203, "Becoming a regular node");
break;
case NODE_P_ULTRA:
msg = "ultra";
if (old_mode == NODE_P_LEAF)
node_bye_flags(NODE_F_ULTRA, 203, "Becoming an ultra node");
break;
case NODE_P_LEAF:
msg = "leaf";
if (old_mode != NODE_P_LEAF)
node_bye_flags(0xffffffff, 203, "Becoming a leaf node");
break;
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
g_error("unhandled mode %d", mode);
break;
}
g_assert(msg != NULL);
if (GNET_PROPERTY(node_debug) > 2)
g_message("Switching to \"%s\" peer mode", msg);
if (old_mode != NODE_P_UNKNOWN) { /* Not at init time */
bsched_set_peermode(mode); /* Adapt Gnet bandwidth */
pcache_set_peermode(mode); /* Adapt pong cache lifetime */
qrp_peermode_changed(); /* Compute proper routing table */
sq_set_peermode(mode); /* Possibly discard the global SQ */
}
dbus_util_send_message(DBS_EVT_PEERMODE_CHANGE, msg);
old_mode = mode;
}
static guint
feed_host_cache_from_string(const gchar *s, host_type_t type, const gchar *name)
{
guint n;
g_assert((guint) type < HOST_MAX);
g_assert(s);
for (n = 0; NULL != s; s = strchr(s, ',')) {
host_addr_t addr;
guint16 port;
if (',' == s[0])
s++;
s = skip_ascii_spaces(s);
string_to_host_addr(s, &s, &addr);
if (!is_host_addr(addr))
continue;
if (':' == s[0]) {
guint32 u;
gint error;
s++;
u = parse_uint32(s, &s, 10, &error);
port = (error || u < 1024 || u > 65535) ? 0 : u;
} else {
port = GTA_PORT;
}
if (!port)
continue;
hcache_add_caught(type, addr, port, name);
n++;
}
return n;
}
/**
* Extract host:port information out of a header field and add those to our
* pong cache. If ``gnet'' is TRUE, the header names without a leading
* "X-" are checked as variants as well.
*
* @param header a valid header_t.
* @param sender the host_type_t of the sender, if unknown use HOST_ANY.
* @param gnet should be set to TRUE if the headers come from a Gnutella
* handshake.
* @param peer the peer address who sent the headers.
*
* @return the amount of valid peer addresses we parsed.
*
* The syntax we expect is:
*
* <header>: <peer> ("," <peer>)*
*
* peer = <host> [":" <port>] [any except ","]*
* header = "Alt" | Listen-Ip" | "Listen-Ip" |
* "My-Address" | "Node" | "Try" | "Try-Ultrapeers"
*
*/
guint
feed_host_cache_from_headers(header_t *header,
host_type_t sender, gboolean gnet, const host_addr_t peer)
{
static const struct {
const gchar *name; /* Name of the header */
gboolean sender; /* Host type is derived from sender */
gboolean gnet; /* Definitely a Gnutella network header */
host_type_t type; /* Default type, sender will override */
} headers[] = {
{ "X-Alt", FALSE, FALSE, HOST_ANY },
{ "X-Listen-Ip", TRUE, TRUE, HOST_ANY },
{ "X-My-Address", TRUE, TRUE, HOST_ANY },
{ "X-Node", TRUE, TRUE, HOST_ANY },
{ "X-Node-IPv6", TRUE, TRUE, HOST_ANY },
{ "X-Try", FALSE, TRUE, HOST_ANY },
{ "X-Try-Ultrapeers", FALSE, TRUE, HOST_ULTRA },
};
guint i, n = 0;
g_assert(header);
g_assert(UNSIGNED(sender) < HOST_MAX);
for (;;) {
for (i = 0; i < G_N_ELEMENTS(headers); i++) {
const gchar *val, *name, *p;
host_type_t type;
guint r;
/*
* One cannot assume that the same port will always be used for
* Gnutella connections and HTTP connections. Do not collect
* addresses from ambiguous headers unless we're low on pongs.
*/
if (!gnet && !headers[i].gnet && !host_low_on_pongs)
continue;
name = headers[i].name;
if (gnet && NULL != (p = is_strprefix(name, "X-")))
name = p;
type = headers[i].sender ? sender : headers[i].type;
val = header_get(header, name);
if (!val)
continue;
r = feed_host_cache_from_string(val, type, name);
n += r;
if (GNET_PROPERTY(node_debug) > 0) {
if (r > 0)
g_message("peer %s sent %u pong%s in %s header",
host_addr_to_string(peer), r, 1 == r ? "" : "s", name);
else
g_message("peer %s sent an unparseable %s header",
host_addr_to_string(peer), name);
}
}
if (!gnet)
break;
gnet = FALSE;
}
return n;
}
/**
* Extract the header pongs from the header (X-Try lines).
* The node is only given for tracing purposes.
*/
static void
extract_header_pongs(header_t *header, struct gnutella_node *n)
{
feed_host_cache_from_headers(header,
NODE_P_ULTRA == n->peermode ? HOST_ULTRA : HOST_ANY,
TRUE, n->addr);
}
/**
* Try to determine whether headers contain an indication of our own IP.
*
* @return 0 if none found, or the indicated IP address.
*/
static host_addr_t
extract_my_addr(header_t *header)
{
const gchar *field;
host_addr_t addr;
field = header_get(header, "Remote-Ip");
if (!field)
field = header_get(header, "X-Remote-Ip");
if (field)
string_to_host_addr(field, NULL, &addr);
else
addr = zero_host_addr;
return addr;
}
/**
* Checks for a Remote-IP or X-Remote-IP header and updates our IP address if
* the current IP address is not enforced. Note that settings_addr_changed()
* doesn't trust a single source.
*
* @param peer the IPv4 address of the peer who sent the header
* @param head a header_t holding headers sent by the peer
*/
void
node_check_remote_ip_header(const host_addr_t peer, header_t *head)
{
host_addr_t addr;
g_assert(head != NULL);
/*
* Remote-IP -- IP address of this node as seen from remote node
*
* Modern nodes include our own IP, as they see it, in the
* handshake headers and reply, whether it indicates a success or not.
* Use it as an opportunity to automatically detect changes.
* --RAM, 13/01/2002
*/
if (GNET_PROPERTY(force_local_ip))
return;
addr = extract_my_addr(head);
if (!is_host_addr(addr) || is_my_address(addr))
return;
if (GNET_PROPERTY(node_debug) > 0) {
const gchar *ua;
ua = header_get(head, "User-Agent");
if (!ua)
ua = header_get(head, "Server");
if (!ua)
ua = "Unknown";
{
gchar buf[HOST_ADDR_BUFLEN];
host_addr_to_string_buf(addr, buf, sizeof buf);
g_message("Peer %s reported different IP address: %s (%s)\n",
host_addr_to_string(peer), buf, ua);
}
}
settings_addr_changed(addr, peer);
}
/**
* Analyses status lines we get from incoming handshakes (final ACK) or
* outgoing handshakes (inital REPLY, after our HELLO)
*
* @return TRUE if acknowledgment was OK, FALSE if an error occurred, in
* which case the node was removed with proper status.
*
* If `code' is not NULL, it is filled with the returned code, or -1 if
* we were unable to parse the status.
*/
static gboolean
analyse_status(struct gnutella_node *n, gint *code)
{
struct gnutella_socket *s = n->socket;
const gchar *status;
gint ack_code;
guint major = 0, minor = 0;
const gchar *ack_message = "";
gboolean ack_ok = FALSE;
gboolean incoming = (n->flags & NODE_F_INCOMING) ? TRUE : FALSE;
const gchar *what = incoming ? "acknowledgment" : "reply";
socket_check(s);
status = getline_str(s->getline);
ack_code = http_status_parse(status, "GNUTELLA",
&ack_message, &major, &minor);
if (code)
*code = ack_code;
if (GNET_PROPERTY(node_debug))
g_message("%s: code=%d, message=\"%s\", proto=%u.%u",
incoming ? "ACK" : "REPLY",
ack_code, ack_message, major, minor);
if (ack_code == -1) {
if (GNET_PROPERTY(node_debug)) {
if (incoming || 0 != strcmp(status, "GNUTELLA OK")) {
g_warning("weird GNUTELLA %s status line from %s",
what, host_addr_to_string(n->addr));
dump_hex(stderr, "Status Line", status,
MIN(getline_length(s->getline), 80));
} else
g_warning("node %s gave a 0.4 reply to our 0.6 HELLO, dropping",
node_addr(n));
}
hcache_add(HCACHE_UNSTABLE, n->addr, 0, "bad ack_code");
} else {
ack_ok = TRUE;
n->flags |= NODE_F_VALID; /* This is a Gnutella node */
}
if (ack_ok && (major != n->proto_major || minor != n->proto_minor)) {
if (GNET_PROPERTY(node_debug)) {
if (incoming)
g_warning("node %s handshaked at %d.%d and now acks at %d.%d, "
"adjusting", host_addr_to_string(n->addr),
n->proto_major, n->proto_minor, major, minor);
else
g_warning("node %s was sent %d.%d HELLO but supports %d.%d "
"only, adjusting", host_addr_to_string(n->addr),
n->proto_major, n->proto_minor, major, minor);
}
n->proto_major = major;
n->proto_minor = minor;
}
/*
* Is the connection OK?
*/
if (!ack_ok) {
node_remove(n, _("Weird HELLO %s"), what);
} else if (ack_code < 200 || ack_code >= 300) {
if (ack_code == 401) {
/* Unauthorized */
hcache_add(HCACHE_UNSTABLE, n->addr, 0, "unauthorized");
}
if (ack_code == 503) {
/* Busy */
hcache_add(HCACHE_BUSY, n->addr, 0, "ack_code 503");
}
node_remove(n, _("HELLO %s error %d (%s)"),
what, ack_code, ack_message);
ack_ok = FALSE;
} else if (!incoming && ack_code == 204) {
node_remove(n, _("Shielded node"));
ack_ok = FALSE;
}
if (GTA_NODE_REMOVING == n->status) {
ack_ok = FALSE;
}
return ack_ok;
}
/**
* Can node accept connection?
*
* If `handshaking' is true, we're still in the handshaking phase, otherwise
* we're already connected and can send a BYE.
*
* @return TRUE if we can accept the connection, FALSE otherwise, with
* the node being removed.
*/
static gboolean
node_can_accept_connection(struct gnutella_node *n, gboolean handshaking)
{
g_assert(handshaking || n->status == GTA_NODE_CONNECTED);
g_assert(n->attrs & (NODE_A_NO_ULTRA|NODE_A_CAN_ULTRA));
/*
* Deny cleanly if they deactivated "online mode".
*/
if (handshaking && !allow_gnet_connections) {
node_send_error(n, 403,
"Gnet connections currently disabled");
node_remove(n, _("Gnet connections disabled"));
return FALSE;
}
/*
* Always accept crawler connections.
*/
if (n->flags & NODE_F_CRAWLER)
return TRUE;
/*
* If we are handshaking, we have not incremented the node counts yet.
* Hence we can do >= tests against the limits.
*/
switch ((node_peer_t) GNET_PROPERTY(current_peermode)) {
case NODE_P_ULTRA:
if (n->flags & NODE_F_FORCE)
return TRUE;
/*
* If we're an ultra node, we need to enforce leaf counts.
*
* We also enforce ultra node counts if we're issuing an outgoing
* connection, but for incoming ones, we'll try to let the other
* node become a leaf node, so don't enforce if we're still in the
* handshaking phase.
*/
if (n->flags & NODE_F_LEAF) {
/*
* Try to preference compressed leaf nodes too
* -- JA, 08/06/2003
*/
if (
GNET_PROPERTY(prefer_compressed_gnet) &&
GNET_PROPERTY(up_connections) <=
GNET_PROPERTY(node_leaf_count) - compressed_leaf_cnt &&
!(n->attrs & NODE_A_CAN_INFLATE)
) {
node_send_error(n, 403,
"Compressed connection prefered");
node_remove(n, _("Connection not compressed"));
return FALSE;
}
/*
* Remove leaves that do not allow queries when we are
* running out of slots.
*/
if (GNET_PROPERTY(node_leaf_count) >= GNET_PROPERTY(max_leaves))
(void) node_remove_useless_leaf(NULL);
if (
handshaking &&
GNET_PROPERTY(node_leaf_count) >= GNET_PROPERTY(max_leaves)
) {
node_send_error(n, 503, "Too many leaf connections (%d max)",
GNET_PROPERTY(max_leaves));
node_remove(n, _("Too many leaves (%d max)"),
GNET_PROPERTY(max_leaves));
return FALSE;
}
if (
!handshaking &&
GNET_PROPERTY(node_leaf_count) > GNET_PROPERTY(max_leaves)
) {
node_bye(n, 503, "Too many leaf connections (%d max)",
GNET_PROPERTY(max_leaves));
return FALSE;
}
} else if (n->attrs & NODE_A_ULTRA) {
guint ultra_max;
/*
* Try to preference compressed ultrapeer connections too
* -- JA, 08/06/2003
*/
if (
GNET_PROPERTY(prefer_compressed_gnet) &&
GNET_PROPERTY(up_connections) <=
GNET_PROPERTY(node_ultra_count) -
(compressed_node_cnt - compressed_leaf_cnt) &&
!(n->attrs & NODE_A_CAN_INFLATE)
) {
node_send_error(n, 403,
"Compressed connection prefered");
node_remove(n, _("Connection not compressed"));
return FALSE;
}
ultra_max = GNET_PROPERTY(max_connections) > GNET_PROPERTY(normal_connections)
? GNET_PROPERTY(max_connections) - GNET_PROPERTY(normal_connections) : 0;
if (GNET_PROPERTY(node_ultra_count) >= ultra_max)
(void) node_remove_useless_ultra(NULL);
if (
handshaking &&
GNET_PROPERTY(node_ultra_count) >= ultra_max
) {
node_send_error(n, 503,
"Too many ultra connections (%d max)", ultra_max);
node_remove(n, _("Too many ultra nodes (%d max)"), ultra_max);
return FALSE;
}
if (!handshaking && GNET_PROPERTY(node_ultra_count) > ultra_max) {
node_bye(n, 503,
"Too many ultra connections (%d max)", ultra_max);
return FALSE;
}
}
/*
* Enforce preference for compression only with non-leaf nodes.
*/
if (handshaking) {
guint connected;
connected = GNET_PROPERTY(node_normal_count)
+ GNET_PROPERTY(node_ultra_count);
if (
GNET_PROPERTY(prefer_compressed_gnet) &&
!(n->attrs & NODE_A_CAN_INFLATE) &&
(
((n->flags & NODE_F_INCOMING) &&
connected >= GNET_PROPERTY(up_connections) &&
connected > compressed_node_cnt)
||
(n->flags & NODE_F_LEAF)
)
) {
node_send_error(n, 403,
"Gnet connection not compressed");
node_remove(n, _("Connection not compressed"));
return FALSE;
}
}
/*
* If we have already enough normal nodes, reject a normal node.
*/
if (
handshaking &&
(n->attrs & NODE_A_NO_ULTRA) &&
GNET_PROPERTY(node_normal_count)
>= GNET_PROPERTY(normal_connections)
) {
if (GNET_PROPERTY(normal_connections))
node_send_error(n, 503, "Too many normal nodes (%d max)",
GNET_PROPERTY(normal_connections));
else
node_send_error(n, 403, "Normal nodes refused");
node_remove(n, _("Rejected normal node (%d max)"),
GNET_PROPERTY(normal_connections));
return FALSE;
}
break;
case NODE_P_NORMAL:
if (n->flags & NODE_F_FORCE)
return TRUE;
if (handshaking) {
guint connected;
connected = GNET_PROPERTY(node_normal_count)
+ GNET_PROPERTY(node_ultra_count);
if (
(n->attrs & (NODE_A_CAN_ULTRA|NODE_A_ULTRA)) == NODE_A_CAN_ULTRA
) {
node_send_error(n, 503, "Cannot accept leaf node");
node_remove(n, _("Rejected leaf node"));
return FALSE;
}
if (connected >= GNET_PROPERTY(max_connections)) {
node_send_error(n, 503, "Too many Gnet connections (%d max)",
GNET_PROPERTY(max_connections));
node_remove(n, _("Too many nodes (%d max)"),
GNET_PROPERTY(max_connections));
return FALSE;
}
if (
GNET_PROPERTY(prefer_compressed_gnet) &&
(n->flags & NODE_F_INCOMING) &&
!(n->attrs & NODE_A_CAN_INFLATE) &&
connected >= GNET_PROPERTY(up_connections) &&
connected > compressed_node_cnt
) {
node_send_error(n, 403,
"Gnet connection not compressed");
node_remove(n, _("Connection not compressed"));
return FALSE;
}
} else if (
GNET_PROPERTY(node_normal_count) + GNET_PROPERTY(node_ultra_count)
> GNET_PROPERTY(max_connections)
) {
node_bye(n, 503, "Too many Gnet connections (%d max)",
GNET_PROPERTY(max_connections));
return FALSE;
}
break;
case NODE_P_LEAF:
/* Even forced connections are not acceptable unless
* the remote node is an ultrapeer. Note: There is also
* an assertion in node_process_handshake_header().
*/
if ((n->flags & NODE_F_FORCE) && (n->attrs & NODE_A_ULTRA))
return TRUE;
if (handshaking) {
/*
* If we're a leaf node, we can only accept incoming connections
* from an ultra node.
*
* The Ultrapeer specs say that two leaf nodes not finding
* Ultrapeers could connect to each other like two normal nodes,
* but I don't want to support that. It's insane.
* --RAM, 11/01/2003
*/
if (!(n->attrs & NODE_A_ULTRA)) {
node_send_error(n, 204, "Shielded leaf node (%d peers max)",
GNET_PROPERTY(max_ultrapeers));
node_remove(n, _("Sent shielded indication"));
return FALSE;
}
if (!(n->attrs & NODE_A_ULTRA)) {
node_send_error(n, 503, "Looking for an ultra node");
node_remove(n, _("Not an ultra node"));
return FALSE;
}
if (
GNET_PROPERTY(node_ultra_count) >= GNET_PROPERTY(max_ultrapeers)
) {
node_send_error(n, 503, "Too many ultra connections (%d max)",
GNET_PROPERTY(max_ultrapeers));
node_remove(n, _("Too many ultra nodes (%d max)"),
GNET_PROPERTY(max_ultrapeers));
return FALSE;
}
/*
* Honour the prefer compressed connection setting. Even when making
* outgoing connections in leaf mode
* -- JA 24/5/2003
*/
if (
GNET_PROPERTY(prefer_compressed_gnet) &&
GNET_PROPERTY(up_connections)
<= GNET_PROPERTY(node_ultra_count) - compressed_node_cnt &&
!(n->attrs & NODE_A_CAN_INFLATE)
) {
node_send_error(n, 403,
"Compressed connection prefered");
node_remove(n, _("Connection not compressed"));
return FALSE;
}
} else if (
GNET_PROPERTY(node_ultra_count) > GNET_PROPERTY(max_ultrapeers)
) {
node_bye(n, 503, "Too many ultra connections (%d max)",
GNET_PROPERTY(max_ultrapeers));
return FALSE;
}
break;
case NODE_P_AUTO:
case NODE_P_CRAWLER:
case NODE_P_UDP:
case NODE_P_UNKNOWN:
g_assert_not_reached();
break;
}
/*
* If a specific client version has proven to be very unstable during this
* version, don't connect to it.
* -- JA 17/7/200
*/
if (n->attrs & NODE_A_ULTRA) {
const gchar *msg = "Unknown error";
enum node_bad bad = node_is_bad(n);
switch (bad) {
case NODE_BAD_OK:
break;
case NODE_BAD_IP:
msg = _("Unstable IP address");
break;
case NODE_BAD_VENDOR:
msg = _("Servent version appears unstable");
break;
case NODE_BAD_NO_VENDOR:
msg = _("No vendor string supplied");
break;
}
if (NODE_BAD_OK != bad) {
node_send_error(n, 403, "%s", msg);
node_remove(n, _("Not connecting: %s"), msg);
return FALSE;
}
}
return TRUE;
}
/**
* Check whether we can accept a servent supporting a foreign protocol.
* Must be called during handshaking.
*
* @return TRUE if OK, FALSE if connection was denied.
*/
static gboolean
node_can_accept_protocol(struct gnutella_node *n, header_t *head)
{
const gchar *field;
/*
* Accept -- protocols supported
*
* We ban ultrapeers claiming support for "application/x-gnutella2" if
* we are an ultranode ourselves.
*
* Study has shown that this closed protocol is not inter-operating
* well with Gnutella: it is more comparable to massive leaching.
* See the various GDF articles written on the subject that prove this.
* --RAM, 25/01/2003
*/
field = header_get(head, "Accept");
if (field) {
if (
GNET_PROPERTY(current_peermode) != NODE_P_LEAF &&
!(n->flags & NODE_F_LEAF) &&
strstr(field, "application/x-gnutella2") /* XXX parse the "," */
) {
static const gchar msg[] = N_("Protocol not acceptable");
node_send_error(n, 406, msg);
node_remove(n, _(msg));
return FALSE;
}
}
return TRUE;
}
/**
* This routine is called to process the whole 0.6+ final handshake header
* acknowledgement we get back after welcoming an incoming node.
*/
static void
node_process_handshake_ack(struct gnutella_node *n, header_t *head)
{
struct gnutella_socket *s = n->socket;
gboolean ack_ok;
const gchar *field;
gboolean qrp_final_set = FALSE;
socket_check(s);
if (GNET_PROPERTY(node_debug)) {
g_message("got final acknowledgment headers from node %s:",
host_addr_to_string(n->addr));
dump_hex(stdout, "Status Line", getline_str(s->getline),
MIN(getline_length(s->getline), 80));
g_message("------ Header Dump:");
header_dump(head, stderr);
g_message("------");
fflush(stderr);
}
ack_ok = analyse_status(n, NULL);
extract_header_pongs(head, n); /* Some servents always send X-Try-* */
if (!ack_ok)
return; /* s->getline will have been freed by node removal */
/*
* Get rid of the acknowledgment status line.
*/
getline_free(s->getline);
s->getline = NULL;
/*
* Content-Encoding -- compression accepted by the remote side
*/
field = header_get(head, "Content-Encoding");
if (field) {
/* XXX needs more rigourous parsing */
if (strstr(field, "deflate"))
n->attrs |= NODE_A_RX_INFLATE; /* We shall decompress input */
}
if (
!GNET_PROPERTY(gnet_deflate_enabled) &&
(n->attrs & NODE_A_RX_INFLATE)
) {
g_warning("Content-Encoding \"deflate\" although disabled - from"
" node %s <%s>", node_addr(n), node_vendor(n));
node_bye(n, 400, "Compression was not accepted");
return;
}
/* X-Ultrapeer -- support for ultra peer mode */
field = header_get(head, "X-Ultrapeer");
if (field && 0 == ascii_strcasecmp(field, "false")) {
n->attrs &= ~NODE_A_ULTRA;
if (GNET_PROPERTY(current_peermode) == NODE_P_ULTRA) {
n->flags |= NODE_F_LEAF; /* Remote accepted to become leaf */
if (GNET_PROPERTY(node_debug)) g_warning(
"node %s <%s> accepted to become our leaf",
node_addr(n), node_vendor(n));
}
}
/*
* X-Query-Routing -- QRP protocol in use by remote servent (negotiated)
*
* This header is present in the 3rd handshake only when the two servents
* advertised different support. This last indication is the highest
* version supported by the remote end, that is less or equals to ours.
* (If not present, it means the remote end implicitly expects us to
* comply with his older version.)
*
* If we don't support that version, we'll BYE the servent later.
*/
field = header_get(head, "X-Query-Routing");
if (field) {
guint major, minor;
parse_major_minor(field, NULL, &major, &minor);
if (major >= n->qrp_major || minor >= n->qrp_minor)
if (GNET_PROPERTY(node_debug)) g_warning(
"node %s <%s> now claims QRP version %u.%u, "
"but advertised %u.%u earlier",
node_addr(n), node_vendor(n), major, minor,
(guint) n->qrp_major, (guint) n->qrp_minor);
n->qrp_major = (guint8) major;
n->qrp_minor = (guint8) minor;
qrp_final_set = TRUE;
}
/*
* Inst