/** * Send a new Gnutella ping message to the specified host. * * @param muid the MUID to use (allocated randomly if NULL) * @param addr address to which ping should be sent * @param port port number * @param uhc_ping if TRUE, include the "SCP" GGEP extension * * @return TRUE if we sent the ping, FALSE it we throttled it. */ bool udp_send_ping(const struct guid *muid, const host_addr_t addr, uint16 port, bool uhc_ping) { gnutella_msg_init_t *m; uint32 size; /* * Don't send too frequent pings: they may throttle us anyway. */ if (aging_lookup(udp_aging_pings, &addr)) { if (GNET_PROPERTY(udp_debug) > 1) { g_warning("UDP throttling %sping to %s", uhc_ping ? "UHC " : "", host_addr_to_string(addr)); } return FALSE; } if (uhc_ping && GNET_PROPERTY(log_uhc_pings_tx)) { g_debug("UDP UHC sending ping to %s", host_addr_to_string(addr)); } m = build_ping_msg(muid, 1, uhc_ping, &size); return udp_send_ping_with_callback(m, size, addr, port, NULL, NULL, FALSE); }
/** * Log whitelist item. */ static void log_whitelist_item(const struct whitelist *item, const char *what) { const char *host; uint8 bits; if (item->host != NULL) { host = 0 == item->port ? item->host->name : host_port_to_string(item->host->name, ipv4_unspecified, item->port); } else { host = 0 == item->port ? host_addr_to_string(item->addr) : host_addr_port_to_string(item->addr, item->port); } if (is_host_addr(item->addr)) { bits = item->bits == addr_default_mask(item->addr) ? 0 : item->bits; } else { bits = 0; } g_debug("WLIST %s %s%s%s%s", what, item->use_tls ? "tls:" : "", host, bits == 0 ? "" : "/", bits == 0 ? "" : uint32_to_string(bits)); }
/** * Select proper RX layer for semi-reliable UDP traffic. * * Source address is checked for hostile hosts in order to enforce a * total blackout. * * @param utp UDP traffic type * @param from source address * @param len length of message, for logging only * * @return the RX layer if found, NULL if none or the address is hostile. */ static rxdrv_t * udp_get_rx_semi_reliable(enum udp_traffic utp, host_addr_t from, size_t len) { unsigned i = 0; if (hostiles_is_bad(from)) { if (GNET_PROPERTY(udp_debug)) { hostiles_flags_t flags = hostiles_check(from); g_warning("UDP got %s (%zu bytes) from hostile %s (%s) -- dropped", udp_traffic_to_string(utp), len, host_addr_to_string(from), hostiles_flags_to_string(flags)); } gnet_stats_inc_general(GNR_UDP_SR_RX_FROM_HOSTILE_IP); return NULL; /* Ignore message */ } switch (host_addr_net(from)) { case NET_TYPE_IPV4: i = 0; break; case NET_TYPE_IPV6: i = 1; break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: g_assert_not_reached(); } return SEMI_RELIABLE_GTA == utp ? rx_sr_gta[i] : SEMI_RELIABLE_GND == utp ? rx_sr_gnd[i] : NULL; }
static void host_lookup_callback(const gchar *hostname, gpointer key) { const struct nid *node_id = key; gnet_node_info_t info; struct node_data *data; host_addr_t addr; guint16 port; if (!ht_pending_lookups) goto finish; if (!remove_item(ht_pending_lookups, node_id)) goto finish; data = find_node(node_id); if (!data) goto finish; guc_node_fill_info(node_id, &info); g_assert(node_id == info.node_id); addr = info.addr; port = info.port; guc_node_clear_info(&info); WFREE_NULL(data->host, data->host_size); if (hostname) { const gchar *host; gchar *to_free; if (utf8_is_valid_string(hostname)) { to_free = NULL; host = hostname; } else { to_free = locale_to_utf8_normalized(hostname, UNI_NORM_GUI); host = to_free; } data->host_size = w_concat_strings(&data->host, host, " (", host_addr_port_to_string(addr, port), ")", (void *) 0); G_FREE_NULL(to_free); } else { statusbar_gui_warning(10, _("Reverse lookup for %s failed"), host_addr_to_string(addr)); data->host_size = w_concat_strings(&data->host, host_addr_port_to_string(addr, port), (void *) 0); } finish: nid_unref(node_id); }
/** * Start a "discovery rpc" sequence. * * @param np existing NAT-PMP gateway (NULL if unknown yet) * @param retries amount of retries before timeouting * @param cb callback to invoke on completion / timeout * @param arg user-defined callback argument */ static void natpmp_rpc_discover(natpmp_t *np, unsigned retries, natpmp_discover_cb_t cb, void *arg) { struct natpmp_rpc *rd; host_addr_t addr; pmsg_t *mb; if (np != NULL) { natpmp_check(np); addr = np->gateway; } else { /* * If we can't determine the default gateway, we can't go much further. * We notify of the discovery failure synchronously. */ if (0 != getgateway(&addr)) { if (GNET_PROPERTY(natpmp_debug)) g_warning("NATPMP cannot find default gateway"); (*cb)(FALSE, NULL, arg); return; } else { if (GNET_PROPERTY(natpmp_debug)) { g_info("NATPMP gateway is %s", host_addr_to_string(addr)); } } } /* * Build the discovery request: * * 0 1 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Vers = 0 | OP = 0 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ mb = pmsg_new(PMSG_P_DATA, NULL, 2); pmsg_write_u8(mb, NATPMP_VERSION); pmsg_write_u8(mb, NATPMP_OP_DISCOVERY); /* * Initiate asynchronous iteration discovery. */ rd = natpmp_rpc_alloc(np, addr, NATPMP_OP_DISCOVERY, mb); rd->cb.discovery = cb; rd->arg = arg; if (retries != 0) rd->retries = MIN(retries, rd->retries); cq_main_insert(1, natpmp_rpc_iterate, rd); }
static int iprange_net4_collision(const void *p, const void *q) { const struct iprange_net4 *a = p, *b = q; g_warning("iprange_sync(): %s/%u overlaps with %s/%u", ip_to_string(a->ip), a->bits, host_addr_to_string(host_addr_get_ipv4(b->ip)), b->bits); return a->bits < b->bits ? 1 : -1; }
static inline int iprange_net_collision(const void *p, const void *q) { const struct iprange_net *a = p, *b = q; g_warning("iprange_sync(): %s/0x%x overlaps with %s/0x%x", ip_to_string(a->ip), a->mask, host_addr_to_string(host_addr_get_ipv4(b->ip)), b->mask); return a->mask < b->mask ? 1 : -1; }
static void adns_reply_ready(const struct adns_response *ans) { time_t now = tm_time(); g_assert(ans != NULL); if (ans->common.reverse) { if (common_dbg > 1) { const struct adns_reverse_reply *reply = &ans->reply.reverse; g_debug("%s: resolved \"%s\" to \"%s\".", G_STRFUNC, host_addr_to_string(reply->addr), reply->hostname); } } else { const struct adns_reply *reply = &ans->reply.by_addr; size_t num; num = count_addrs(reply->addrs, G_N_ELEMENTS(reply->addrs)); num = MAX(1, num); /* For negative caching */ if (common_dbg > 1) { size_t i; for (i = 0; i < num; i++) { g_debug("%s: resolved \"%s\" to \"%s\".", G_STRFUNC, reply->hostname, host_addr_to_string(reply->addrs[i])); } } if (!adns_cache_lookup(adns_cache, now, reply->hostname, NULL, 0)) { adns_cache_add(adns_cache, now, reply->hostname, reply->addrs, num); } } g_assert(ans->common.user_callback); adns_invoke_user_callback(ans); }
/** * Copies user_callback and user_data from the query buffer to the * reply buffer. This function won't fail. However, if gethostbyname() * fails ``reply->addr'' will be set to zero. */ static void adns_gethostbyname(const struct adns_request *req, struct adns_response *ans) { g_assert(NULL != req); g_assert(NULL != ans); ans->common = req->common; if (req->common.reverse) { const struct adns_reverse_query *query = &req->query.reverse; struct adns_reverse_reply *reply = &ans->reply.reverse; const char *host; if (common_dbg > 1) { g_debug("%s: resolving \"%s\" ...", G_STRFUNC, host_addr_to_string(query->addr)); } reply->addr = query->addr; host = host_addr_to_name(query->addr); clamp_strcpy(reply->hostname, sizeof reply->hostname, host ? host : ""); } else { const struct adns_query *query = &req->query.by_addr; struct adns_reply *reply = &ans->reply.by_addr; GSList *sl_addr, *sl; size_t i = 0; if (common_dbg > 1) { g_debug("%s: resolving \"%s\" ...", G_STRFUNC, query->hostname); } clamp_strcpy(reply->hostname, sizeof reply->hostname, query->hostname); sl_addr = name_to_host_addr(query->hostname, query->net); for (sl = sl_addr; NULL != sl; sl = g_slist_next(sl)) { host_addr_t *addr = sl->data; g_assert(addr); if (i >= G_N_ELEMENTS(reply->addrs)) { break; } reply->addrs[i++] = *addr; } host_addr_free_list(&sl_addr); if (i < G_N_ELEMENTS(reply->addrs)) { reply->addrs[i] = zero_host_addr; } } }
/** * Redefine callback invoked when we got the whole HTTP reply. * * @param ha the HTTP async request descriptor * @param s the socket on which we got the reply * @param status the first HTTP status line * @param header the parsed header structure */ static void gwc_got_reply(const http_async_t *ha, const gnutella_socket_t *s, const char *status, const header_t *header) { if (GNET_PROPERTY(bootstrap_debug) > 3) g_debug("GWC got reply from %s", http_async_url(ha)); if (GNET_PROPERTY(bootstrap_debug) > 5) { g_debug("----Got GWC reply from %s:", host_addr_to_string(s->addr)); if (log_printable(LOG_STDERR)) { fprintf(stderr, "%s\n", status); header_dump(stderr, header, "----"); } } }
/** * Redefine callback invoked when we got the whole HTTP reply. */ static void soap_got_reply(const http_async_t *ha, const struct gnutella_socket *s, const char *status, const header_t *header) { soap_rpc_t *sr = http_async_get_opaque(ha); soap_rpc_check(sr); if (GNET_PROPERTY(soap_trace) & SOCK_TRACE_IN) { g_debug("----Got SOAP HTTP reply from %s:", host_addr_to_string(s->addr)); if (log_printable(LOG_STDERR)) { fprintf(stderr, "%s\n", status); header_dump(stderr, header, "----"); } } }
/** * RPC timeout callback. */ static void g2_rpc_timeout(cqueue_t *cq, void *obj) { struct g2_rpc *gr = obj; g2_rpc_check(gr); if (GNET_PROPERTY(g2_rpc_debug) > 1) { g_debug("%s(): /%s RPC to %s timed out, calling %s()", G_STRFUNC, g2_msg_type_name(gr->key.type), host_addr_to_string(gr->key.addr), stacktrace_function_name(gr->cb)); } cq_zero(cq, &gr->timeout_ev); (*gr->cb)(NULL, NULL, gr->arg); g2_rpc_free(gr, FALSE); }
/** * Called when we get a reply from the ADNS process. */ static void whitelist_dns_cb(const host_addr_t *addrs, size_t n, void *udata) { struct whitelist_dns *ctx = udata; struct whitelist *item = ctx->item; if (ctx->generation != whitelist_generation) { if (GNET_PROPERTY(whitelist_debug)) log_whitelist_item(item, "late DNS resolution"); if (!ctx->revalidate) { whitelist_free(item); } } else { item->host->last_resolved = tm_time(); if (n < 1) { if (GNET_PROPERTY(whitelist_debug)) log_whitelist_item(item, "could not DNS-resolve"); if (ctx->revalidate) { item->addr = ipv4_unspecified; item->bits = 0; } else { whitelist_free(item); } } else { item->addr = addrs[random_value(n - 1)]; /* Pick one randomly */ item->bits = addr_default_mask(item->addr); if (GNET_PROPERTY(whitelist_debug) > 1) { g_debug("WLIST DNS-resolved %s as %s (out of %zu result%s)", item->host->name, host_addr_to_string(item->addr), n, plural(n)); } if (!ctx->revalidate) { whitelist_add(item); } } } WFREE(ctx); }
/** * Write an IPv4 or IPv6 address. */ void pmsg_write_ipv4_or_ipv6_addr(pmsg_t *mb, host_addr_t addr) { g_assert(pmsg_is_writable(mb)); /* Not shared, or would corrupt data */ g_assert(pmsg_available(mb) >= 17); switch (host_addr_net(addr)) { case NET_TYPE_IPV4: pmsg_write_u8(mb, 4); pmsg_write_be32(mb, host_addr_ipv4(addr)); break; case NET_TYPE_IPV6: pmsg_write_u8(mb, 16); pmsg_write(mb, host_addr_ipv6(&addr), 16); break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: g_error("unexpected address in pmsg_write_ipv4_or_ipv6_addr(): %s", host_addr_to_string(addr)); } }
/** * @return A pointer to a static buffer holding the host address as string. */ const gchar * uploads_gui_host_string(const gnet_upload_info_t *u) { static gchar buf[1024]; const gchar *peer; if (u->gnet_port && is_host_addr(u->gnet_addr)) { peer = host_addr_port_to_string(u->gnet_addr, u->gnet_port); } else { peer = NULL; } concat_strings(buf, sizeof buf, host_addr_to_string(u->addr), u->encrypted ? " (E) " : " ", peer ? " <" : "", peer ? peer : "", peer ? ">" : "", (void *) 0); return buf; }
/** * Update internal information about the NAT-PMP gateway upon reception * of an RPC reply. */ static void natpmp_update(natpmp_t *np, unsigned sssoe) { time_delta_t d; unsigned conservative_sssoe; natpmp_check(np); d = delta_time(tm_time(), np->last_update); conservative_sssoe = uint_saturate_add(np->sssoe, 7 * d / 8); if (sssoe < conservative_sssoe && conservative_sssoe - sssoe > 1) { np->rebooted = TRUE; if (GNET_PROPERTY(natpmp_debug) > 1) { g_debug("NATPMP new SSSOE=%u < conservative SSSOE=%u, %s rebooted", sssoe, conservative_sssoe, host_addr_to_string(np->gateway)); } } np->last_update = tm_time(); np->sssoe = sssoe; }
/** * Looks for ``hostname'' in ``cache'' wrt to cache->timeout. If * ``hostname'' is not found or the entry is expired, FALSE will be * returned. Expired entries will be removed! ``addr'' is allowed to * be NULL, otherwise the cached IP will be stored into the variable * ``addr'' points to. * * @param addrs An array of host_addr_t items. If not NULL, up to * ``n'' items will be copied from the cache. * @param n The number of items "addrs" can hold. * @return The number of cached addresses for the given hostname. */ static size_t adns_cache_lookup(adns_cache_t *cache, time_t now, const char *hostname, host_addr_t *addrs, size_t n) { adns_cache_entry_t *entry; g_assert(NULL != cache); g_assert(NULL != hostname); g_assert(0 == n || NULL != addrs); entry = hikset_lookup(cache->ht, hostname); if (entry) { if (delta_time(now, entry->timestamp) < cache->timeout) { size_t i; for (i = 0; i < n; i++) { if (i < entry->n) { addrs[i] = entry->addrs[i]; if (common_dbg > 0) g_debug("%s: \"%s\" cached (addr=%s)", G_STRFUNC, entry->hostname, host_addr_to_string(addrs[i])); } else { addrs[i] = zero_host_addr; } } } else { if (common_dbg > 0) { g_debug("%s: removing \"%s\" from cache", G_STRFUNC, entry->hostname); } hikset_remove(cache->ht, hostname); adns_cache_free_entry(cache, entry->id); entry = NULL; } } return entry ? entry->n : 0; }
/** * Called from parse_dispatch_lines() for each complete line of output. * * @return FALSE to stop processing of any remaining data. */ static bool ghc_host_line(struct parse_context *ctx, const char *buf, size_t len) { if (GNET_PROPERTY(bootstrap_debug) > 2) g_debug("BOOT GHC host line #%u (%zu bytes): %s", ctx->processed + 1, len, buf); if (len) { host_addr_t addr; uint16 port; if (string_to_host_addr_port(buf, NULL, &addr, &port)) { ctx->processed++; hcache_add_caught(HOST_ULTRA, addr, port, "GHC"); if (GNET_PROPERTY(bootstrap_debug) > 1) g_debug("BOOT collected %s from GHC %s", host_addr_to_string(addr), http_async_url(ctx->handle)); } } return TRUE; }
static void print_upload_info(struct gnutella_shell *sh, const struct gnet_upload_info *info) { char buf[1024]; g_return_if_fail(sh); g_return_if_fail(info); str_bprintf(buf, sizeof buf, "%-3.3s %-16.40s %s %s@%s %s%s%s", info->encrypted ? "(E)" : "", host_addr_to_string(info->addr), iso3166_country_cc(info->country), compact_size(info->range_end - info->range_start, GNET_PROPERTY(display_metric_units)), short_size(info->range_start, GNET_PROPERTY(display_metric_units)), info->name ? "\"" : "<", info->name ? info->name : "none", info->name ? "\"" : ">"); shell_write(sh, buf); shell_write(sh, "\n"); /* Terminate line */ }
/** * Create a security token from host address and port using specified key. * * Optionally, extra contextual data may be given (i.e. the token is not * only based on the address and port) to make the token more unique to * a specific context. * * @param stg the security token generator * @param n key index to use * @param tok where security token is written * @param addr address of the host for which we're generating a token * @param port port of the host for which we're generating a token * @param data optional contextual data * @param len length of contextual data */ static void sectoken_generate_n(sectoken_gen_t *stg, size_t n, sectoken_t *tok, host_addr_t addr, uint16 port, const void *data, size_t len) { char block[8]; char enc[8]; char *p = block; sectoken_gen_check(stg); g_assert(tok != NULL); g_assert(size_is_non_negative(n)); g_assert(n < stg->keycnt); g_assert((NULL != data) == (len != 0)); switch (host_addr_net(addr)) { case NET_TYPE_IPV4: p = poke_be32(p, host_addr_ipv4(addr)); break; case NET_TYPE_IPV6: { uint val; val = binary_hash(host_addr_ipv6(&addr), 16); p = poke_be32(p, val); } break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: g_error("unexpected address for security token generation: %s", host_addr_to_string(addr)); } p = poke_be16(p, port); p = poke_be16(p, 0); /* Filler */ g_assert(p == &block[8]); STATIC_ASSERT(sizeof(tok->v) == sizeof(uint32)); STATIC_ASSERT(sizeof(block) == sizeof(enc)); tea_encrypt(&stg->keys[n], enc, block, sizeof block); /* * If they gave contextual data, encrypt them by block of 8 bytes, * filling the last partial block with zeroes if needed. */ if (data != NULL) { const void *q = data; size_t remain = len; char denc[8]; STATIC_ASSERT(sizeof(denc) == sizeof(enc)); while (remain != 0) { size_t fill = MIN(remain, 8U); unsigned i; if (fill != 8U) ZERO(&block); memcpy(block, q, fill); remain -= fill; q = const_ptr_add_offset(q, fill); /* * Encrypt block of contextual data (possibly filled with trailing * zeroes) and merge back the result into the main encryption * output with XOR. */ tea_encrypt(&stg->keys[n], denc, block, sizeof block); for (i = 0; i < sizeof denc; i++) enc[i] ^= denc[i]; } } poke_be32(tok->v, tea_squeeze(enc, sizeof enc)); }
/** * Callback for adns_resolve(), invoked when the resolution is complete. */ static void uhc_host_resolved(const host_addr_t *addrs, size_t n, void *uu_udata) { (void) uu_udata; g_assert(addrs); /* * If resolution failed, try again if possible. */ if (0 == n) { if (GNET_PROPERTY(bootstrap_debug)) g_warning("could not resolve UDP host cache \"%s\"", uhc_ctx.host); uhc_try_next(); return; } if (n > 1) { size_t i; host_addr_t *hav; /* Current UHC was moved to tail by uhc_get_next() */ struct uhc *uhc = hash_list_tail(uhc_list); /* * UHC resolved to multiple endpoints. Could be roundrobbin or * IPv4 and IPv6 addresss. Adding them as seperate entries: if the * IPv6 is unreachable we have an opportunity to skip it. * -- JA 24/7/2011 * * Shuffle the address array before appending them to the UHC list. * --RAM, 2015-10-01 */ hav = HCOPY_ARRAY(addrs, n); SHUFFLE_ARRAY_N(hav, n); for (i = 0; i < n; i++) { const char *host = host_addr_port_to_string(hav[i], uhc_ctx.port); g_debug("BOOT UDP host cache \"%s\" resolved to %s (#%zu)", uhc_ctx.host, host, i + 1); uhc_list_append(host); } hash_list_remove(uhc_list, uhc); /* Replaced by IP address list */ uhc_free(&uhc); /* * We're going to continue and process the first address (in our * shuffled array). Make sure it is put at the end of the list * and marked as being used, mimicing what uhc_get_next() would do. * --RAM, 2015-10-01 */ { struct uhc key; key.host = host_addr_port_to_string(hav[0], uhc_ctx.port); uhc = hash_list_lookup(uhc_list, &key); g_assert(uhc != NULL); /* We added the entry above! */ uhc->stamp = tm_time(); uhc->used++; hash_list_moveto_tail(uhc_list, uhc); } uhc_ctx.addr = hav[0]; /* Struct copy */ HFREE_NULL(hav); } else { uhc_ctx.addr = addrs[0]; } if (GNET_PROPERTY(bootstrap_debug)) g_debug("BOOT UDP host cache \"%s\" resolved to %s", uhc_ctx.host, host_addr_to_string(uhc_ctx.addr)); /* * Now send the ping. */ uhc_send_ping(); }
/** * Notification from the socket layer that we got a new datagram. * * @param s the receiving socket (with s->addr and s->port set) * @param data start of received data (not necessarily s->buf) * @param len length of received data (not necessarily s->pos) * @param truncated whether received datagram was truncated * * If `truncated' is true, then the message was too large for the * socket buffer. */ void udp_received(const gnutella_socket_t *s, const void *data, size_t len, bool truncated) { gnutella_node_t *n; bool bogus = FALSE; bool dht = FALSE; bool rudp = FALSE; /* * This must be regular Gnutella / DHT traffic. */ inet_udp_got_incoming(s->addr); /* * We need to identify semi-reliable UDP traffic early, because that * traffic needs to go through the RX stack to reassemble the final * payload out of the many fragments, or to process the acknowledgments. * * We have to apply heuristics however because the leading 8 bytes could * be just a part of gnutella message (the first 8 bytes of a GUID). * One thing is certain though: if the size is less than that of a a * Gnutella header, it has to be semi-reliable UDP traffic... * * Because semi-reliable UDP uses small payloads, much smaller than our * socket buffer, the datagram cannot be truncated. */ if (!truncated) { enum udp_traffic utp; rxdrv_t *rx; utp = udp_intuit_traffic_type(s, data, len); switch (utp) { case GNUTELLA: goto unreliable; case RUDP: rudp = TRUE; gnet_stats_count_general(GNR_RUDP_RX_BYTES, len); goto rudp; /* Don't account this message in UDP statistics */ case DHT: dht = TRUE; goto unreliable; case UNKNOWN: goto unknown; case SEMI_RELIABLE_GTA: case SEMI_RELIABLE_GND: break; } /* * We are going to treat this message a a semi-reliable UDP fragment. * * Account the size of the payload for traffic purposes, then redirect * the message to the RX layer that reassembles and dispatches these * messages. */ bws_udp_count_read(len, FALSE); /* We know it's not DHT traffic */ rx = udp_get_rx_semi_reliable(utp, s->addr, len); if (rx != NULL) { gnet_host_t from; gnet_host_set(&from, s->addr, s->port); ut_got_message(rx, data, len, &from); } return; } unknown: /* * Discriminate between Gnutella UDP and DHT messages, so that we * can account received data with the proper bandwidth scheduler. */ if (len >= GTA_HEADER_SIZE) dht = GTA_MSG_DHT == gnutella_header_get_function(data); /* FALL THROUGH */ unreliable: /* * Account for Gnutella / DHT incoming UDP traffic. */ bws_udp_count_read(len, dht); /* FALL THROUGH */ rudp: /* * The RUDP layer is used to implement firewalled-to-firewalled transfers * via a mini TCP-like layer built on top of UDP. Therefore, it is used * as the basis for higher-level connections (HTTP) and will have to be * accounted for once the type of traffic is known, by upper layers, as * part of the upload/download traffic. * * Of course, the higher levels will never see all the bytes that pass * through, such as acknowledgments or retransmissions, but that is also * the case for TCP-based sockets. * --RAM, 2012-11-02. */ /* * If we get traffic from a bogus IP (unroutable), warn, for now. */ if (bogons_check(s->addr)) { bogus = TRUE; if (GNET_PROPERTY(udp_debug)) { g_warning("UDP %sdatagram (%zu byte%s) received from bogus IP %s", truncated ? "truncated " : "", len, 1 == len ? "" : "s", host_addr_to_string(s->addr)); } gnet_stats_inc_general(GNR_UDP_BOGUS_SOURCE_IP); } /* * Get proper pseudo-node. * * These routines can return NULL if the address/port combination is * not correct, but this will be handled by udp_is_valid_gnet(). */ n = dht ? node_dht_get_addr_port(s->addr, s->port) : node_udp_get_addr_port(s->addr, s->port); if (!udp_is_valid_gnet(n, s, truncated, data, len)) return; /* * RUDP traffic does not go to the upper Gnutella processing layers. */ if (rudp) { /* Not ready for prime time */ #if 0 rudp_handle_packet(s->addr, s->port. data, len); #endif return; } /* * Process message as if it had been received from regular Gnet by * another node, only we'll use a special "pseudo UDP node" as origin. */ if (GNET_PROPERTY(udp_debug) > 19 || (bogus && GNET_PROPERTY(udp_debug))) g_debug("UDP got %s from %s%s", gmsg_infostr_full(data, len), bogus ? "BOGUS " : "", host_addr_port_to_string(s->addr, s->port)); node_udp_process(n, s, data, len); }
/** * Handle reply to a discovery request. * * @param payload the received reply * @param len length of reply * @param rd the RPC request descriptor * * @return TRUE if we successfully processed the reply and notified the * user code about the outcome of the request, FALSE if we need to resend * the request. */ static bool natpmp_handle_discovery_reply( const void *payload, size_t len, struct natpmp_rpc *rd) { bstr_t *bs; uint8 version; uint8 code; uint16 result; uint32 ip; host_addr_t wan_ip; natpmp_t *np; natpmp_rpc_check(rd); /** * A NAT gateway will reply with the following message: * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Vers = 0 | OP = 128 + 0 | Result Code | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Seconds Since Start of Epoch | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | External IP Address (a.b.c.d) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * The first 32-bits are always present, the remaining of the packet * may or may not be there depending on the result code. */ bs = bstr_open(payload, len, GNET_PROPERTY(natpmp_debug) ? BSTR_F_ERROR : 0); /* * Make sure we got a valid reply. */ bstr_read_u8(bs, &version); bstr_read_u8(bs, &code); bstr_read_be16(bs, &result); if (bstr_has_error(bs)) goto error; if (GNET_PROPERTY(natpmp_debug) > 5) { g_debug("NATPMP version=%u, code=%u, result_code=%u (%s)", version, code, result, natpmp_strerror(result)); } if (version != NATPMP_VERSION || code != NATPMP_REPLY_OFF + rd->op) goto error; if (NATPMP_E_OK != result) goto failed; bstr_read_be32(bs, &rd->sssoe); bstr_read_be32(bs, &ip); if (bstr_has_error(bs)) goto error; wan_ip = host_addr_get_ipv4(ip); if (GNET_PROPERTY(natpmp_debug) > 5) { g_debug("NATPMP SSSOE=%u, WAN IP is %s", rd->sssoe, host_addr_to_string(wan_ip)); } if (!host_addr_is_routable(wan_ip)) goto failed; /* * Good, we got a valid reply from the gateway, with a routable WAN IP. */ if (rd->np != NULL) { natpmp_check(rd->np); np = rd->np; natpmp_update(np, rd->sssoe); np->wan_ip = wan_ip; } else { np = natpmp_alloc(rd->gateway, rd->sssoe, wan_ip); } (*rd->cb.discovery)(TRUE, np, rd->arg); bstr_free(&bs); return TRUE; /* OK */ failed: if (GNET_PROPERTY(natpmp_debug)) g_warning("NATPMP did not find any suitable NAT-PMP gateway"); (*rd->cb.discovery)(FALSE, rd->np, rd->arg); return TRUE; /* We're done for now */ error: if (GNET_PROPERTY(natpmp_debug)) { if (bstr_has_error(bs)) { g_warning("NATPMP parsing error while processing discovery reply " "(%zu byte%s): %s", len, 1 == len ? "" : "s", bstr_error(bs)); } else { g_warning("NATPMP inconsistent discovery reply (%zu byte%s)", len, 1 == len ? "" : "s"); } } bstr_free(&bs); return FALSE; }
/** * Displays assorted status information */ enum shell_reply shell_exec_status(struct gnutella_shell *sh, int argc, const char *argv[]) { const char *cur; const option_t options[] = { { "i", &cur }, }; int parsed; char buf[2048]; time_t now; shell_check(sh); g_assert(argv); g_assert(argc > 0); parsed = shell_options_parse(sh, argv, options, G_N_ELEMENTS(options)); if (parsed < 0) return REPLY_ERROR; argv += parsed; /* args[0] is first command argument */ argc -= parsed; /* counts only command arguments now */ now = tm_time(); /* Leading flags */ { char flags[47]; const char *fw; const char *fd; const char *pmp; const char *dht; /* * The flags are displayed as followed: * * UMP port mapping configured via UPnP * NMP port mapping configured via NAT-PMP * pmp port mapping available (UPnP or NAT-PMP), un-configured * CLK clock, GTKG expired * !FD or FD red or yellow bombs for fd shortage * STL upload stalls * gUL/yUL/rUL green, yellow or red upload early stalling levels * CPU cpu overloaded * MOV file moving * SHA SHA-1 rebuilding or verifying * TTH TTH rebuilding or verifying * LIB library rescan * :FW or FW indicates whether hole punching is possible * udp or UDP indicates UDP firewalling (lowercased for hole punching) * TCP indicates TCP-firewalled * - the happy face: no firewall * sDH/lDH/bDH seeded, own KUID looking or bootstrapping DHT * A or P active or passive DHT mode * UP or LF ultrapeer or leaf mode */ pmp = (GNET_PROPERTY(upnp_possible) || GNET_PROPERTY(natpmp_possible)) ? "pmp " : empty; if ( (GNET_PROPERTY(enable_upnp) || GNET_PROPERTY(enable_natpmp)) && GNET_PROPERTY(port_mapping_successful) ) { pmp = GNET_PROPERTY(enable_natpmp) ? "NMP " : "UMP "; } if (dht_enabled()) { dht = empty; switch ((enum dht_bootsteps) GNET_PROPERTY(dht_boot_status)) { case DHT_BOOT_NONE: case DHT_BOOT_SHUTDOWN: break; case DHT_BOOT_SEEDED: dht = "sDH "; break; case DHT_BOOT_OWN: dht = "lDH "; break; case DHT_BOOT_COMPLETING: dht = "bDH "; break; case DHT_BOOT_COMPLETED: dht = dht_is_active() ? "A " : "P "; break; case DHT_BOOT_MAX_VALUE: g_assert_not_reached(); } } else { dht = empty; } if (GNET_PROPERTY(is_firewalled) && GNET_PROPERTY(is_udp_firewalled)) { fw = GNET_PROPERTY(recv_solicited_udp) ? ":FW " : "FW "; } else if (GNET_PROPERTY(is_firewalled)) { fw = "TCP "; } else if (GNET_PROPERTY(is_udp_firewalled)) { fw = GNET_PROPERTY(recv_solicited_udp) ? "udp " : "UDP "; } else { fw = "- "; } if (GNET_PROPERTY(file_descriptor_runout)) { fd = "!FD "; } else if (GNET_PROPERTY(file_descriptor_shortage)) { fd = "FD "; } else { fd = empty; } gm_snprintf(flags, sizeof flags, "<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>", pmp, GNET_PROPERTY(download_queue_frozen) ? "DFZ " : empty, GNET_PROPERTY(ancient_version) ? "CLK " : empty, fd, GNET_PROPERTY(uploads_stalling) ? "STL " : empty, GNET_PROPERTY(uploads_bw_ignore_stolen) ? "gUL " : empty, GNET_PROPERTY(uploads_bw_uniform) ? "yUL " : empty, GNET_PROPERTY(uploads_bw_no_stealing) ? "rUL " : empty, GNET_PROPERTY(overloaded_cpu) ? "CPU " : empty, GNET_PROPERTY(file_moving) ? "MOV " : empty, (GNET_PROPERTY(sha1_rebuilding) || GNET_PROPERTY(sha1_verifying)) ? "SHA " : empty, (GNET_PROPERTY(tth_rebuilding) || GNET_PROPERTY(tth_verifying)) ? "TTH " : empty, GNET_PROPERTY(library_rebuilding) ? "LIB " : empty, fw, dht, settings_is_ultra() ? "UP" : "LF"); gm_snprintf(buf, sizeof buf, "+%s+\n" "| %-18s%51s |\n" "|%s|\n", dashes, "Status", flags, equals); shell_write(sh, buf); } /* General status */ { const char *blackout; short_string_t leaf_switch; short_string_t ultra_check; leaf_switch = timestamp_get_string( GNET_PROPERTY(node_last_ultra_leaf_switch)); ultra_check = timestamp_get_string( GNET_PROPERTY(node_last_ultra_check)); if (GNET_PROPERTY(is_firewalled) && GNET_PROPERTY(is_udp_firewalled)) { blackout = GNET_PROPERTY(recv_solicited_udp) ? "TCP,udp" : "TCP,UDP"; } else if (GNET_PROPERTY(is_firewalled)) { blackout = "TCP"; } else if (GNET_PROPERTY(is_udp_firewalled)) { blackout = GNET_PROPERTY(recv_solicited_udp) ? "udp" : "UDP"; } else { blackout = "None"; } gm_snprintf(buf, sizeof buf, "| Mode: %-9s Last Switch: %-19s%2s|\n" "| Uptime: %-9s Last Check: %-19s%2s|\n" "| Port: %-9u Blackout: %-7s%14s|\n" "|%s|\n", GNET_PROPERTY(online_mode) ? node_peermode_to_string(GNET_PROPERTY(current_peermode)) : "offline", GNET_PROPERTY(node_last_ultra_leaf_switch) ? leaf_switch.str : "never", space, short_time(delta_time(now, GNET_PROPERTY(start_stamp))), GNET_PROPERTY(node_last_ultra_check) ? ultra_check.str : "never", space, socket_listen_port(), blackout, space, equals); shell_write(sh, buf); } /* IPv4 info */ switch (GNET_PROPERTY(network_protocol)) { case NET_USE_BOTH: case NET_USE_IPV4: gm_snprintf(buf, sizeof buf, "| IPv4: %-44s Since: %-12s|\n", host_addr_to_string(listen_addr()), short_time(delta_time(now, GNET_PROPERTY(current_ip_stamp)))); shell_write(sh, buf); } /* IPv6 info */ switch (GNET_PROPERTY(network_protocol)) { case NET_USE_BOTH: gm_snprintf(buf, sizeof buf, "|%s|\n", dashes); shell_write(sh, buf); /* FALL THROUGH */ case NET_USE_IPV6: gm_snprintf(buf, sizeof buf, "| IPv6: %-44s Since: %-12s|\n", host_addr_to_string(listen_addr6()), short_time(delta_time(now, GNET_PROPERTY(current_ip6_stamp)))); shell_write(sh, buf); } /* Node counts */ gm_snprintf(buf, sizeof buf, "|%s|\n" "| Peers: %-7u Ultra %4u/%-7u Leaf %4u/%-6u Legacy %4u/%-4u |\n" "| Downloads %4u/%-4u Uploads %4u/%-7u Browse %4u/%-4u |\n" "|%s|\n", equals, GNET_PROPERTY(node_ultra_count) + GNET_PROPERTY(node_leaf_count) + GNET_PROPERTY(node_normal_count), GNET_PROPERTY(node_ultra_count), settings_is_ultra() ? GNET_PROPERTY(max_connections) : GNET_PROPERTY(max_ultrapeers), GNET_PROPERTY(node_leaf_count), GNET_PROPERTY(max_leaves), GNET_PROPERTY(node_normal_count), GNET_PROPERTY(normal_connections), GNET_PROPERTY(dl_active_count), GNET_PROPERTY(dl_running_count), GNET_PROPERTY(ul_running), GNET_PROPERTY(ul_registered), GNET_PROPERTY(html_browse_served) + GNET_PROPERTY(qhits_browse_served), GNET_PROPERTY(html_browse_count) + GNET_PROPERTY(qhits_browse_count), equals); shell_write(sh, buf); /* Bandwidths */ { const bool metric = GNET_PROPERTY(display_metric_units); short_string_t gnet_in, http_in, leaf_in, gnet_out, http_out, leaf_out; short_string_t dht_in, dht_out; gnet_bw_stats_t bw_stats, bw2_stats; const char *bwtype = cur ? "(cur)" : "(avg)"; gnet_get_bw_stats(BW_GNET_IN, &bw_stats); gnet_get_bw_stats(BW_GNET_UDP_IN, &bw2_stats); gnet_in = short_rate_get_string( cur ? bw_stats.current + bw2_stats.current : bw_stats.average + bw2_stats.average, metric); gnet_get_bw_stats(BW_GNET_OUT, &bw_stats); gnet_get_bw_stats(BW_GNET_UDP_OUT, &bw2_stats); gnet_out = short_rate_get_string( cur ? bw_stats.current + bw2_stats.current : bw_stats.average + bw2_stats.average, metric); gnet_get_bw_stats(BW_HTTP_IN, &bw_stats); http_in = short_rate_get_string( cur ? bw_stats.current : bw_stats.average, metric); gnet_get_bw_stats(BW_HTTP_OUT, &bw_stats); http_out = short_rate_get_string( cur ? bw_stats.current : bw_stats.average, metric); gnet_get_bw_stats(BW_LEAF_IN, &bw_stats); leaf_in = short_rate_get_string( cur ? bw_stats.current : bw_stats.average, metric); gnet_get_bw_stats(BW_LEAF_OUT, &bw_stats); leaf_out = short_rate_get_string( cur ? bw_stats.current : bw_stats.average, metric); gnet_get_bw_stats(BW_DHT_IN, &bw_stats); dht_in = short_rate_get_string( cur ? bw_stats.current : bw_stats.average, metric); gnet_get_bw_stats(BW_DHT_OUT, &bw_stats); dht_out = short_rate_get_string( cur ? bw_stats.current : bw_stats.average, metric); gm_snprintf(buf, sizeof buf, "| %-70s|\n" "|%71s|\n" "| %5s In: %13s %13s %13s %13s |\n" "| %5s Out: %13s %13s %13s %13s |\n", "Bandwidth:" " Gnutella Leaf HTTP DHT", dashes, bwtype, gnet_in.str, leaf_in.str, http_in.str, dht_in.str, bwtype, gnet_out.str, leaf_out.str, http_out.str, dht_out.str); shell_write(sh, buf); } { char line[128]; bool metric = GNET_PROPERTY(display_metric_units); gm_snprintf(buf, sizeof buf, "|%s|\n", equals); shell_write(sh, buf); concat_strings(line, sizeof line, "Shares ", uint64_to_string(shared_files_scanned()), " file", shared_files_scanned() == 1 ? "" : "s", " ", short_kb_size(shared_kbytes_scanned(), metric), " total", (void *) 0); gm_snprintf(buf, sizeof buf, "| %-35s Up: %-11s Down: %-11s |\n", line, short_byte_size(GNET_PROPERTY(ul_byte_count), metric), short_byte_size2(GNET_PROPERTY(dl_byte_count), metric)); shell_write(sh, buf); gm_snprintf(buf, sizeof buf, "+%s+\n", dashes); shell_write(sh, buf); } return REPLY_READY; }
/** * UDP RPC reply (or timeout) callback. */ static void natpmp_rpc_reply(enum urpc_ret type, host_addr_t addr, uint16 port, const void *payload, size_t len, void *arg) { struct natpmp_rpc *rd = arg; natpmp_rpc_check(rd); if (GNET_PROPERTY(natpmp_debug) > 4) { g_debug("NATPMP %s for \"%s\" #%u (%lu byte%s) from %s", URPC_TIMEOUT == type ? "timeout" : "got reply", natpmp_op_to_string(rd->op), rd->count, (unsigned long) len, 1 == len ? "" : "s", host_addr_port_to_string(addr, port)); } if (URPC_TIMEOUT == type) goto iterate; /* * Silently discard a reply not coming from the host to whom we * sent the RPC. */ if (!host_addr_equal(addr, rd->gateway)) { if (GNET_PROPERTY(natpmp_debug)) { g_warning("NATPMP discarding reply from %s (sent %s to %s)", host_addr_port_to_string(addr, port), natpmp_op_to_string(rd->op), host_addr_to_string(rd->gateway)); } goto iterate; } /* * Dispatch reply processing. */ switch (rd->op) { case NATPMP_OP_DISCOVERY: if (!natpmp_handle_discovery_reply(payload, len, rd)) goto iterate; break; case NATPMP_OP_MAP_TCP: case NATPMP_OP_MAP_UDP: if (!natpmp_handle_mapping_reply(payload, len, rd)) goto iterate; break; case NATPMP_OP_INVALID: g_assert_not_reached(); } /* * All done, request was successful. */ natpmp_rpc_free(rd); return; iterate: natpmp_rpc_iterate(NULL, rd); }
/** * Notification that a message was received that could be the answer to * a pending RPC. * * @param n the node from which we got the message * @param t the received message tree * * @return TRUE if the message was indeed an RPC reply, FALSE otherwise. */ bool g2_rpc_answer(const gnutella_node_t *n, const g2_tree_t *t) { struct g2_rpc *gr; struct g2_rpc_key key; enum g2_msg type; type = g2_msg_name_type(g2_tree_name(t)); key.type = g2_rpc_send_type(type); key.addr = n->addr; gr = hevset_lookup(g2_rpc_pending, &key); if (NULL == gr) { /* * No known RPC, but wait... we can receive a /QKA when we issue a /Q2 * and the query key we knew for the remote host has expired, hence * we must look whether there is not a /Q2 pending as well in that * case. Once again, the lack of MUID in these messages is a handicap. */ if (G2_MSG_QKA == type) { key.type = G2_MSG_Q2; gr = hevset_lookup(g2_rpc_pending, &key); if (gr != NULL) goto found; /* Sent a /Q2, got a /QKA back */ } if (GNET_PROPERTY(g2_rpc_debug) > 1) { g_debug("%s(): unexpected /%s RPC reply from %s", G_STRFUNC, g2_msg_type_name(key.type), node_infostr(n)); } return FALSE; } found: /* * Got a reply for an RPC we sent, based solely on message type and * source address of the message. This is weak, but G2 works like that. * * Weakness comes from the fact that we cannot have multiple RPCs with * a same IP address but towards different ports, nor have concurrent * RPCs with the same host for several different by similar requests * (although this can be viewed as anti-hammering, servers should protect * against that in different ways, a crippled protocol not being an answer). * * The only transaction where we could use the MUID is /Q2 -> /QA but we * leave that check to the GUESS layer and make sure here that we have only * one single RPC transaction at a time with a given IP address. */ if (GNET_PROPERTY(g2_rpc_debug) > 2) { g_debug("%s(): /%s RPC to %s got a /%s reply, calling %s()", G_STRFUNC, g2_msg_type_name(gr->key.type), host_addr_to_string(gr->key.addr), g2_tree_name(t), stacktrace_function_name(gr->cb)); } (*gr->cb)(n, t, gr->arg); g2_rpc_free(gr, FALSE); return TRUE; }
/** * Loads the whitelist into memory. */ static void G_COLD whitelist_retrieve(void) { char line[1024]; FILE *f; filestat_t st; unsigned linenum = 0; file_path_t fp[1]; whitelist_generation++; file_path_set(fp, settings_config_dir(), whitelist_file); f = file_config_open_read_norename("Host Whitelist", fp, N_ITEMS(fp)); if (!f) return; if (fstat(fileno(f), &st)) { g_warning("%s(): fstat() failed: %m", G_STRFUNC); fclose(f); return; } while (fgets(line, sizeof line, f)) { pslist_t *sl_addr, *sl; const char *endptr, *start; host_addr_t addr; uint16 port; uint8 bits; bool item_ok; bool use_tls; char *hname; linenum++; if (!file_line_chomp_tail(line, sizeof line, NULL)) { g_warning("%s(): line %u too long, aborting", G_STRFUNC, linenum); break; } if (file_line_is_skipable(line)) continue; sl_addr = NULL; addr = zero_host_addr; endptr = NULL; hname = NULL; endptr = is_strprefix(line, "tls:"); if (endptr) { use_tls = TRUE; start = endptr; } else { use_tls = FALSE; start = line; } port = 0; if (string_to_host_addr_port(start, &endptr, &addr, &port)) { sl_addr = name_to_host_addr(host_addr_to_string(addr), settings_dns_net()); } else if (string_to_host_or_addr(start, &endptr, &addr)) { uchar c = *endptr; switch (c) { case '\0': case ':': case '/': break; default: if (!is_ascii_space(c)) endptr = NULL; } if (!endptr) { g_warning("%s(): line %d: " "expected a hostname or IP address \"%s\"", G_STRFUNC, linenum, line); continue; } /* Terminate the string for name_to_host_addr() */ hname = h_strndup(start, endptr - start); } else { g_warning("%s(): line %d: expected hostname or IP address \"%s\"", G_STRFUNC, linenum, line); continue; } g_assert(sl_addr != NULL || hname != NULL); g_assert(NULL != endptr); bits = 0; item_ok = TRUE; /* * When an explicit address is given (no hostname) and with no * port, one can suffix the address with bits to indicate a CIDR * range of whitelisted addresses. */ if (0 == port) { /* Ignore trailing items separated by a space */ while ('\0' != *endptr && !is_ascii_space(*endptr)) { uchar c = *endptr++; if (':' == c) { int error; uint32 v; if (0 != port) { g_warning("%s(): line %d: multiple colons after host", G_STRFUNC, linenum); item_ok = FALSE; break; } v = parse_uint32(endptr, &endptr, 10, &error); port = (error || v > 0xffff) ? 0 : v; if (0 == port) { g_warning("%s(): line %d: " "invalid port value after host", G_STRFUNC, linenum); item_ok = FALSE; break; } } else if ('/' == c) { const char *ep; uint32 mask; if (0 != bits) { g_warning("%s(): line %d: " "multiple slashes after host", G_STRFUNC, linenum); item_ok = FALSE; break; } if (string_to_ip_strict(endptr, &mask, &ep)) { if (!host_addr_is_ipv4(addr)) { g_warning("%s(): line %d: " "IPv4 netmask after non-IPv4 address", G_STRFUNC, linenum); item_ok = FALSE; break; } endptr = ep; if (0 == (bits = netmask_to_cidr(mask))) { g_warning("%s(): line %d: " "IPv4 netmask after non-IPv4 address", G_STRFUNC, linenum); item_ok = FALSE; break; } } else { int error; uint32 v; v = parse_uint32(endptr, &endptr, 10, &error); if ( error || 0 == v || (v > 32 && host_addr_is_ipv4(addr)) || (v > 128 && host_addr_is_ipv6(addr)) ) { g_warning("%s(): line %d: " "invalid numeric netmask after host", G_STRFUNC, linenum); item_ok = FALSE; break; } bits = v; } } else { g_warning("%s(): line %d: " "unexpected character after host", G_STRFUNC, linenum); item_ok = FALSE; break; } } } if (item_ok) { struct whitelist *item; if (hname) { item = whitelist_hostname_create(use_tls, hname, port); whitelist_dns_resolve(item, FALSE); } else { PSLIST_FOREACH(sl_addr, sl) { host_addr_t *aptr = sl->data; g_assert(aptr != NULL); item = whitelist_addr_create(use_tls, *aptr, port, bits); whitelist_add(item); } } } else {
/** * Check version of servent, and if it's a gtk-gnutella more recent than we * are, record that fact and change the status bar. * * The `addr' is being passed solely for the tok_version_valid() call. * * @returns TRUE if we properly checked the version, FALSE if we got something * looking as gtk-gnutella but which failed the token-based sanity checks. */ bool version_check(const char *str, const char *token, const host_addr_t addr) { version_t their_version; version_ext_t their_version_ext; version_t *target_version; int cmp; const char *version; const char *end; bool extended; if (!version_parse(str, &their_version, &end)) return TRUE; /* Not gtk-gnutella, or unparseable */ /* * Check for extended version information (git commit ID, dirty status). */ ZERO(&their_version_ext); extended = version_ext_parse(end, &their_version_ext); if (!extended) { /* Structure could have been partially filled */ ZERO(&their_version_ext); } /* * Is their version a development one, or a release? */ if (their_version.tag == 'u') target_version = &last_dev_version; else target_version = &last_rel_version; cmp = version_cmp(target_version, &their_version); if (GNET_PROPERTY(version_debug) > 1) version_dump(str, &their_version, cmp == 0 ? "=" : cmp > 0 ? "-" : "+"); /* * Check timestamp. */ version_stamp(str, &their_version); their_version_ext.version = their_version; /* Struct copy */ if (GNET_PROPERTY(version_debug) > 3) g_debug("VERSION time=%d", (int) their_version.timestamp); /* * If version claims something older than TOKEN_START_DATE, then * there must be a token present. */ if (delta_time(their_version.timestamp, 0) >= TOKEN_START_DATE) { tok_error_t error; if (token == NULL) { if (GNET_PROPERTY(version_debug)) { g_debug("got GTKG vendor string \"%s\" without token!", str); } return FALSE; /* Can't be correct */ } error = tok_version_valid(str, token, strlen(token), addr); /* * Unfortunately, if our token has expired, we can no longer * validate the tokens of the remote peers, since they are using * a different set of keys. * * This means an expired GTKG will blindly trust well-formed remote * tokens at face value. But it's their fault, they should not run * an expired version! * --RAM, 2005-12-21 */ if (error == TOK_BAD_KEYS && tok_is_ancient(tm_time())) error = TOK_OK; /* Our keys have expired, cannot validate */ if (error != TOK_OK) { if (GNET_PROPERTY(version_debug)) { g_debug("vendor string \"%s\" [%s] has wrong token " "\"%s\": %s ", str, host_addr_to_string(addr), token, tok_strerror(error)); } return FALSE; } /* * OK, so now we know we can "trust" this version string as being * probably genuine. It makes sense to extract version information * out of it. */ } if (cmp > 0) /* We're more recent */ return TRUE; /* * If timestamp is greater and we were comparing against a stable * release, and cmp == 0, then this means an update in SVN about * a "released" version, probably alpha/beta. */ if ( cmp == 0 && (delta_time(their_version.timestamp, target_version->timestamp) > 0 || their_version.build > target_version->build) && target_version == &last_rel_version ) { if (GNET_PROPERTY(version_debug) > 3) g_debug("VERSION is a SVN update of a release"); if (version_build_cmp(&last_dev_version, &their_version) > 0) { if (GNET_PROPERTY(version_debug) > 3) g_debug("VERSION is less recent than latest dev we know"); return TRUE; } target_version = &last_dev_version; } /* * Their version is more recent, but is unstable -- only continue if * our version is also unstable. */ if (cmp < 0 && their_version.tag == 'u' && our_version.tag != 'u') return TRUE; if ( delta_time(their_version.timestamp, target_version->timestamp) < 0 || their_version.build <= target_version->build ) return TRUE; if ( delta_time(their_version.timestamp, our_version.timestamp) == 0 && their_version.build <= our_version.build ) return TRUE; /* * We found a more recent version than the last version seen. */ if (GNET_PROPERTY(version_debug) > 1) g_debug("more recent %s VERSION \"%s\"", target_version == &last_dev_version ? "dev" : "rel", str); *target_version = their_version; /* struct copy */ /* * Signal new version to user. * * Unless they run a development version, don't signal development * updates to them: they're probably not interested. */ version = version_ext_str(&their_version_ext, FALSE); /* No OS name */ g_message("more recent %s version of gtk-gnutella: %s", target_version == &last_dev_version ? "development" : "released", version); if (target_version == &last_rel_version) version_new_found(version, TRUE); else if (our_version.tag == 'u') version_new_found(version, FALSE); return TRUE; }