/** * DBMW foreach iterator to remove old entries. * @return TRUE if entry must be deleted. */ static bool guid_prune_old_entries(void *key, void *value, size_t u_len, void *u_data) { const guid_t *guid = key; const struct guiddata *gd = value; time_delta_t d; double p = 0.0; bool expired; (void) u_len; (void) u_data; /* * We reuse the statistical probability model of DHT nodes to project * whether it makes sense to keep an entry. */ d = delta_time(tm_time(), gd->last_time); if (gd->create_time == gd->last_time) { expired = d > GUID_STABLE_LIFETIME; } else { p = stable_still_alive_probability(gd->create_time, gd->last_time); expired = p < GUID_STABLE_PROBA; } if (GNET_PROPERTY(guid_debug) > 5) { g_debug("GUID cached %s life=%s last_seen=%s, p=%.2f%%%s", guid_hex_str(guid), compact_time(delta_time(gd->last_time, gd->create_time)), compact_time2(d), p * 100.0, expired ? " [EXPIRED]" : ""); } return expired; }
/** * Fill supplied buffer with the formatted string describing the message. * * @param data start of the G2 message * @param len length of the message * @param buf buffer where formatted string is written * @param buflen length of the destination buffer * * @return the amount of bytes written. */ size_t g2_msg_infostr_to_buf(const void *data, size_t len, char *buf, size_t buflen) { enum g2_msg m; const guid_t *muid = NULL; g_assert(size_is_non_negative(len)); g_assert(size_is_non_negative(buflen)); /* * Check whether we need to decompile the packet to access the GUID, which * is the payload of the root element in the tree. Given the way things * are serialized, that would be the last 16 bytes of the message, so * we don't have to deserialize everything just to access it. */ m = g2_msg_type(data, len); switch (m) { case G2_MSG_Q2: case G2_MSG_QA: case G2_MSG_QH2: if (len > GUID_RAW_SIZE) muid = const_ptr_add_offset(data, len - GUID_RAW_SIZE); /* FALL THROUGH */ default: break; } return str_bprintf(buf, buflen, "/%s (%zu byte%s)%s%s", g2_msg_type_name(m), len, plural(len), NULL == muid ? "" : " #", NULL == muid ? "" : guid_hex_str(muid)); }
/** * Send a Gnutella ping to the specified host via UDP, using the * specified MUID. */ void udp_connect_back(const host_addr_t addr, uint16 port, const struct guid *muid) { if (udp_send_ping(muid, addr, port, FALSE)) { if (GNET_PROPERTY(udp_debug) > 19) g_debug("UDP queued connect-back PING #%s to %s\n", guid_hex_str(muid), host_addr_port_to_string(addr, port)); } }
/** * Flush current /QH2. * * Depending how the QH2 builder is configured, this either sends the message * to the target node or invokes a processing callback. */ static void g2_build_qh2_flush(struct g2_qh2_builder *ctx) { pmsg_t *mb; g_assert(ctx != NULL); g_assert(ctx->t != NULL); g_assert((ctx->n != NULL) ^ (ctx->cb != NULL)); /* * Restore the order of children in the root packet to be the order we * used when we added the nodes, since we prepend new children. */ g2_tree_reverse_children(ctx->t); /* * If sending over UDP, ask for reliable delivery of the query hit. * To be able to monitor the fate of the message, we asssociate a free * routine to it. */ if (ctx->to_udp) { struct g2_qh2_pmsg_info *pmi; WALLOC0(pmi); pmi->magic = G2_QH2_PMI_MAGIC; pmi->hub_id = nid_ref(NODE_ID(ctx->hub)); mb = g2_build_pmsg_extended(ctx->t, g2_qh2_pmsg_free, pmi); pmsg_mark_reliable(mb); } else { mb = g2_build_pmsg(ctx->t); } if (GNET_PROPERTY(g2_debug) > 3) { g_debug("%s(): flushing the following hit for " "Q2 #%s to %s%s (%d bytes):", G_STRFUNC, guid_hex_str(ctx->muid), NULL == ctx->n ? stacktrace_function_name(ctx->cb) : node_infostr(ctx->n), NULL == ctx->n ? "()" : "", pmsg_size(mb)); g2_tfmt_tree_dump(ctx->t, stderr, G2FMT_O_PAYLOAD | G2FMT_O_PAYLEN); } if (ctx->n != NULL) g2_node_send(ctx->n, mb); else (*ctx->cb)(mb, ctx->arg); ctx->messages++; ctx->current_size = 0; g2_tree_free_null(&ctx->t); }
/** * Send an UDP ping to the host cache. */ static void uhc_send_ping(void) { g_assert(uhc_connecting); guid_random_muid(&uhc_ctx.muid); if (udp_send_ping(&uhc_ctx.muid, uhc_ctx.addr, uhc_ctx.port, TRUE)) { if (GNET_PROPERTY(bootstrap_debug) || GNET_PROPERTY(log_uhc_pings_tx)) { g_debug("BOOT sent UDP SCP ping #%s to %s:%u", guid_hex_str(&uhc_ctx.muid), uhc_ctx.host, uhc_ctx.port); } /* * Give GUI feedback. */ { char msg[256]; str_bprintf(msg, sizeof msg, _("Sent ping to UDP host cache %s:%u"), uhc_ctx.host, uhc_ctx.port); gcu_statusbar_message(msg); } /* * Arm a timer to see whether we should not try to ping another * host cache if we don't get a timely reply. */ g_assert(uhc_ctx.timeout_ev == NULL); uhc_ctx.timeout_ev = cq_main_insert(UHC_TIMEOUT, uhc_ping_timeout, NULL); } else { g_warning("BOOT failed to send UDP SCP to %s", host_addr_port_to_string(uhc_ctx.addr, uhc_ctx.port)); uhc_try_next(); } }
/** * Add GUID to the banned list or refresh the fact that we are still seeing * it as being worth banning. */ void guid_add_banned(const struct guid *guid) { struct guiddata *gd; struct guiddata new_gd; gd = get_guiddata(guid); if (NULL == gd) { gd = &new_gd; gd->create_time = gd->last_time = tm_time(); gnet_stats_inc_general(GNR_BANNED_GUID_HELD); if (GNET_PROPERTY(guid_debug)) { g_debug("GUID banning %s", guid_hex_str(guid)); } } else { gd->last_time = tm_time(); } dbmw_write(db_guid, guid, gd, sizeof *gd); }
/** * 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(); } }
/** * Determine unique filename for `file' in `path', with optional trailing * extension `ext'. If no `ext' is wanted, one must supply an empty string. * * @param path A directory path. * @param file The basename for the resulting pathname. * @param ext An optional filename extension to be appended to the basename. * @param name_is_uniq An optional callback to decide whether a created * pathname is uniq. If omitted, the default is file_does_not_exist(). * * @returns the chosen unique complete filename as a pointer which must be * freed via hfree(). */ char * filename_unique(const char *path, const char *name, const char *ext, bool (*name_is_uniq)(const char *pathname)) { char filename_buf[FILENAME_MAXBYTES]; char name_buf[FILENAME_MAXBYTES]; char mid_buf[32]; char ext_buf[32]; const char *mid; char *pathname; size_t name_len, mid_len, ext_len; int i; g_assert(path); g_assert(name); g_assert(ext); g_assert(is_absolute_path(path)); STATIC_ASSERT(sizeof filename_buf > sizeof mid_buf + sizeof ext_buf + GUID_HEX_SIZE); /** * NOTE: The generated filename must not exceed FILENAME_MAXBYTES * because such a file cannot be created. In reality, it depends * on the filesystem as well and the limit might be even smaller. * In any case, we don't want to cut-off arbitrary bytes but * at least preserve the filename extension and the (potential) * UTF-8 encoding. */ /* Because "ext" can be an additional extension like .BAD rather than * one that indicates the filetype, try to preserve the next "extension" * as well, if there's any. */ mid = strrchr(name, '.'); if (NULL == mid || mid == name || strlen(mid) >= sizeof mid_buf) { mid = strchr(name, '\0'); } ext_len = strlen(ext); mid_len = strlen(mid); name_len = strlen(name) - mid_len; ext_len = MIN(ext_len, sizeof ext_buf - 1); mid_len = MIN(mid_len, sizeof mid_buf - 1); name_len = MIN(name_len, sizeof name_buf - 1); if (name_len + mid_len + ext_len >= sizeof filename_buf) { g_assert(name_len >= ext_len); name_len -= ext_len; } /* Truncate strings so that an UTF-8 encoding is preserved */ ext_len = utf8_truncate(ext, ext_buf, ext_len + 1); mid_len = utf8_truncate(mid, mid_buf, mid_len + 1); name_len = utf8_truncate(name, name_buf, name_len + 1); str_bprintf(filename_buf, sizeof filename_buf, "%s%s%s", name_buf, mid_buf, ext_buf); pathname = unique_pathname(path, filename_buf, name_is_uniq); if (pathname) goto finish; if (!is_directory(path)) return NULL; /* * Looks like we need to make the filename more unique. Append .00, then * .01, etc... until .99. */ while (name_len + mid_len + ext_len + 3 >= sizeof filename_buf) { g_assert(name_len > 0); name_len--; } name_len = utf8_truncate(name, name_buf, name_len + 1); for (i = 0; i < 100; i++) { str_bprintf(filename_buf, sizeof filename_buf, "%s.%02u%s%s", name_buf, i, mid_buf, ext_buf); pathname = unique_pathname(path, filename_buf, name_is_uniq); if (pathname) goto finish; } /* * OK, no luck. Try with a few random numbers then. */ while (name_len + mid_len + ext_len + 9 >= sizeof filename_buf) { g_assert(name_len > 0); name_len--; } name_len = utf8_truncate(name, name_buf, name_len + 1); for (i = 0; i < 100; i++) { str_bprintf(filename_buf, sizeof filename_buf, "%s.%x%s%s", name_buf, (unsigned) random_u32(), mid_buf, ext_buf); pathname = unique_pathname(path, filename_buf, name_is_uniq); if (pathname) goto finish; } /* * Bad luck. Allocate a random GUID then. */ while ( name_len + mid_len + ext_len + GUID_HEX_SIZE + 1 >= sizeof filename_buf ) { g_assert(name_len > 0); name_len--; } name_len = utf8_truncate(name, name_buf, name_len + 1); { struct guid guid; guid_random_fill(&guid); str_bprintf(filename_buf, sizeof filename_buf, "%s.%s%s%s", name_buf, guid_hex_str(&guid), mid_buf, ext_buf); } pathname = unique_pathname(path, filename_buf, name_is_uniq); if (pathname) goto finish; /* * This may also be the result of permission problems or inode * exhaustion. */ g_warning("%s(): no luck with random number generator", G_STRFUNC); finish: return pathname; }
/** * 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; }