/** * Send a Gnutella ping message to the specified host. * * @param m the Ping message to send * @param size size of the Ping message, in bytes * @param addr address to which ping should be sent * @param port port number * @param cb if non-NULL, callback to invoke on reply or timeout * @param arg additional callback argument * @param multiple whether multiple replies (Pongs) are expected * * @return TRUE if we sent the ping, FALSE it we throttled it. */ static bool udp_send_ping_with_callback( gnutella_msg_init_t *m, uint32 size, const host_addr_t addr, uint16 port, udp_ping_cb_t cb, void *arg, bool multiple) { struct gnutella_node *n = node_udp_get_addr_port(addr, port); if (n != NULL) { const guid_t *muid = gnutella_header_get_muid(m); if (udp_ping_register(muid, addr, port, cb, arg, multiple)) { aging_insert(udp_aging_pings, wcopy(&addr, sizeof addr), GUINT_TO_POINTER(1)); udp_send_msg(n, m, size); return TRUE; } } return FALSE; }
/** * Free routine for query hit message. */ static void dh_pmsg_free(pmsg_t *mb, void *arg) { struct dh_pmsg_info *pmi = arg; const struct guid *muid; dqhit_t *dh; g_assert(pmsg_is_extended(mb)); muid = gnutella_header_get_muid(pmsg_start(mb)); dh = dh_locate(muid); if (dh == NULL) goto cleanup; /* * It can happen that an initial query hit comes and is queued for * transmission, but the node is so clogged we don't actually send * it before the entry expires in our tracking tables. When we later * get the ACK that it was sent, we can therefore get obsolete data. * Hence we're very careful updating the stats, and we can't assert * that we're tracking everything correctly. * --RAM, 2004-09-04 */ if (pmsg_was_sent(mb)) dh->hits_sent += pmi->hits; if (dh->msg_queued == 0) /* We did not expect this ACK */ goto cleanup; dh->msg_queued--; if (dh->hits_queued >= pmi->hits) dh->hits_queued -= pmi->hits; /* FALL THROUGH */ cleanup: WFREE(pmi); }
/** * Upon reception of an UDP pong, check whether we had a matching registered * ping bearing the given MUID. * * If there was a callback atttached to the reception of a reply, invoke it * before returning UDP_PONG_HANDLED. * * The ``host'' paramaeter MUST be a stack or static pointer to a gnet_host_t, * and NOT the address of a dynamically allocated host because gnet_host_copy() * is going to be used on it. * * @param n the gnutella node replying * @param host if non-NULL, filled with the host to whom we sent the ping * * @return TRUE if indeed this was a reply for a ping we sent. */ enum udp_pong_status udp_ping_is_registered(const struct gnutella_node *n, gnet_host_t *host) { const struct guid *muid = gnutella_header_get_muid(&n->header); if (udp_pings) { struct udp_ping *ping; ping = hash_list_remove(udp_pings, muid); if (ping != NULL) { if (host != NULL) { /* * Let caller know the exact IP:port of the host we contacted, * since the replying party can use a different port (which * we may not be able to contact, whereas we know the targeted * port did cause a reply). */ gnet_host_copy(host, ping->host); } if (ping->callback) { (*ping->callback->cb)(UDP_PING_REPLY, n, ping->callback->data); if (ping->callback->multiple) { ping->callback->got_reply = TRUE; ping->added = tm_time(); /* Delay expiration */ hash_list_append(udp_pings, ping); } else { udp_ping_free(ping); } return UDP_PONG_HANDLED; } udp_ping_free(ping); return UDP_PONG_SOLICITED; } } return UDP_PONG_UNSOLICITED; }
/** * Called when a pong with an "IPP" extension was received. */ void uhc_ipp_extract(gnutella_node_t *n, const char *payload, int paylen, enum net_type type) { int i, cnt; int len = NET_TYPE_IPV6 == type ? 18 : 6; const void *p; g_assert(0 == paylen % len); cnt = paylen / len; if (GNET_PROPERTY(bootstrap_debug)) g_debug("extracting %d host%s in UDP IPP pong #%s from %s", cnt, plural(cnt), guid_hex_str(gnutella_header_get_muid(&n->header)), node_addr(n)); for (i = 0, p = payload; i < cnt; i++, p = const_ptr_add_offset(p, len)) { host_addr_t ha; uint16 port; host_ip_port_peek(p, type, &ha, &port); hcache_add_caught(HOST_ULTRA, ha, port, "UDP-HC"); if (GNET_PROPERTY(bootstrap_debug) > 2) g_debug("BOOT collected %s from UDP IPP pong from %s", host_addr_port_to_string(ha, port), node_addr(n)); } if (!uhc_connecting) return; /* * Check whether this was a reply from our request. * * The reply could come well after we decided it timed out and picked * another UDP host cache, which ended-up replying, so we must really * check whether we're still in a probing cycle. */ if (!guid_eq(&uhc_ctx.muid, gnutella_header_get_muid(&n->header))) return; if (GNET_PROPERTY(bootstrap_debug)) { g_debug("BOOT UDP cache \"%s\" replied: got %d host%s from %s", uhc_ctx.host, cnt, plural(cnt), node_addr(n)); } /* * Terminate the probing cycle if we got hosts. */ if (cnt > 0) { char msg[256]; cq_cancel(&uhc_ctx.timeout_ev); uhc_connecting = FALSE; str_bprintf(msg, sizeof(msg), NG_("Got %d host from UDP host cache %s", "Got %d hosts from UDP host cache %s", cnt), cnt, uhc_ctx.host); gcu_statusbar_message(msg); } else { uhc_try_next(); } }
/** * Route query hits from one node to the other. */ void dh_route(gnutella_node_t *src, gnutella_node_t *dest, int count) { pmsg_t *mb; struct dh_pmsg_info *pmi; const struct guid *muid; dqhit_t *dh; mqueue_t *mq; g_assert( gnutella_header_get_function(&src->header) == GTA_MSG_SEARCH_RESULTS); g_assert(count >= 0); if (!NODE_IS_WRITABLE(dest)) goto drop_shutdown; muid = gnutella_header_get_muid(&src->header); dh = dh_locate(muid); g_assert(dh != NULL); /* Must have called dh_got_results() first! */ if (GNET_PROPERTY(dh_debug) > 19) { g_debug("DH #%s got %d hit%s: " "msg=%u, hits_recv=%u, hits_sent=%u, hits_queued=%u", guid_hex_str(muid), count, plural(count), dh->msg_recv, dh->hits_recv, dh->hits_sent, dh->hits_queued); } mq = dest->outq; /* * Can we forward the message? */ switch (dh_can_forward(dh, mq, FALSE)) { case DH_DROP_FC: goto drop_flow_control; case DH_DROP_THROTTLE: goto drop_throttle; case DH_DROP_TRANSIENT: goto drop_transient; case DH_FORWARD: default: break; } /* * Allow message through. */ WALLOC(pmi); pmi->hits = count; dh->hits_queued += count; dh->msg_queued++; g_assert(dh->hits_queued >= UNSIGNED(count)); /* * Magic: we create an extended version of a pmsg_t that contains a * free routine, which will be invoked when the message queue frees * the message. * * This enables us to track how much results we already queued/sent. */ if (NODE_IS_UDP(dest)) { gnet_host_t to; pmsg_t *mbe; gnet_host_set(&to, dest->addr, dest->port); /* * With GUESS we may route back a query hit to an UDP node. */ if (GNET_PROPERTY(guess_server_debug) > 19) { g_debug("GUESS sending %d hit%s (%s) for #%s to %s", count, plural(count), NODE_CAN_SR_UDP(dest) ? "reliably" : NODE_CAN_INFLATE(dest) ? "possibly deflated" : "uncompressed", guid_hex_str(muid), node_infostr(dest)); } /* * Attempt to compress query hit if the destination supports it. * * If we're going to send the hit using semi-reliable UDP, there's * no need to compress beforehand, since the transport layer will * attempt its own compression anyway. */ if (!NODE_CAN_SR_UDP(dest) && NODE_CAN_INFLATE(dest)) { mb = gmsg_split_to_deflated_pmsg(&src->header, src->data, src->size + GTA_HEADER_SIZE); if (gnutella_header_get_ttl(pmsg_start(mb)) & GTA_UDP_DEFLATED) gnet_stats_inc_general(GNR_UDP_TX_COMPRESSED); } else { mb = gmsg_split_to_pmsg(&src->header, src->data, src->size + GTA_HEADER_SIZE); } mbe = pmsg_clone_extend(mb, dh_pmsg_free, pmi); pmsg_free(mb); if (NODE_CAN_SR_UDP(dest)) pmsg_mark_reliable(mbe); mq_udp_putq(mq, mbe, &to); } else { mb = gmsg_split_to_pmsg_extend(&src->header, src->data, src->size + GTA_HEADER_SIZE, dh_pmsg_free, pmi); mq_tcp_putq(mq, mb, src); if (GNET_PROPERTY(dh_debug) > 19) { g_debug("DH enqueued %d hit%s for #%s to %s", count, plural(count), guid_hex_str(muid), node_infostr(dest)); } } return; drop_shutdown: gnet_stats_count_dropped(src, MSG_DROP_SHUTDOWN); return; drop_flow_control: gnet_stats_count_dropped(src, MSG_DROP_FLOW_CONTROL); gnet_stats_count_flowc(&src->header, TRUE); return; drop_throttle: gnet_stats_count_dropped(src, MSG_DROP_THROTTLE); return; drop_transient: gnet_stats_count_dropped(src, MSG_DROP_TRANSIENT); return; }