/** * Handle reception of a /LNI */ static void g2_node_handle_lni(gnutella_node_t *n, const g2_tree_t *t) { g2_tree_t *c; /* * Handle the children of /LNI. */ G2_TREE_CHILD_FOREACH(t, c) { enum g2_lni_child ct = TOKENIZE(g2_tree_name(c), g2_lni_children); const char *payload; size_t paylen; switch (ct) { case G2_LNI_GU: /* the node's GUID */ payload = g2_tree_node_payload(c, &paylen); if (GUID_RAW_SIZE == paylen) node_set_guid(n, (guid_t *) payload, TRUE); break; case G2_LNI_NA: /* the node's address, with listening port */ { host_addr_t addr; uint16 port; if (g2_node_parse_address(c, &addr, &port)) { if (host_address_is_usable(addr)) n->gnet_addr = addr; n->gnet_port = port; } } break; case G2_LNI_LS: /* library statistics */ payload = g2_tree_node_payload(c, &paylen); if (paylen >= 8) { uint32 files = peek_le32(payload); uint32 kbytes = peek_le32(&payload[4]); n->gnet_files_count = files; n->gnet_kbytes_count = kbytes; n->flags |= NODE_F_SHARED_INFO; } break; case G2_LNI_V: /* vendor code */ payload = g2_tree_node_payload(c, &paylen); if (paylen >= 4) n->vcode.u32 = peek_be32(payload); break; case G2_LNI_UP: /* uptime */ payload = g2_tree_node_payload(c, &paylen); if (paylen <= 4) n->up_date = tm_time() - vlint_decode(payload, paylen); break; } } }
g2_node_drop(const char *routine, gnutella_node_t *n, const g2_tree_t *t, const char *fmt, ...) { if (GNET_PROPERTY(g2_debug) || GNET_PROPERTY(log_dropped_g2)) { va_list args; char buf[256]; va_start(args, fmt); if (fmt != NULL) str_vbprintf(ARYLEN(buf), fmt, args); else buf[0] = '\0'; g_debug("%s(): dropping /%s from %s%s%s", routine, g2_tree_name(t), node_infostr(n), NULL == fmt ? "" : ": ", buf); va_end(args); } gnet_stats_count_dropped(n, MSG_DROP_G2_UNEXPECTED); if (GNET_PROPERTY(log_dropped_g2)) { g2_tfmt_tree_dump(t, stderr, G2FMT_O_PAYLEN); } }
/** * Tree message iterator to handle "NH" nodes and extract their IP:port. */ static void g2_node_extract_nh(void *data, void *udata) { const g2_tree_t *t = data; (void) udata; if (0 == strcmp("NH", g2_tree_name(t))) { host_addr_t addr; uint16 port; if ( g2_node_parse_address(t, &addr, &port) && host_is_valid(addr, port) ) { hcache_add_caught(HOST_G2HUB, addr, port, "/KHL/NH"); } } }
/** * Fetch the MUID in the message, if any is architected. * * @param t the message tree * @param buf the buffer to fill with a copy of the MUID * * @return a pointer to `buf' if OK and we filled the MUID, NULL if there is * no valid MUID in the message or the message is not carrying any MUID. */ guid_t * g2_msg_get_muid(const g2_tree_t *t, guid_t *buf) { enum g2_msg m; const void *payload; size_t paylen; size_t offset; g_assert(t != NULL); g_assert(buf != NULL); m = g2_msg_name_type(g2_tree_name(t)); switch (m) { case G2_MSG_Q2: case G2_MSG_QA: offset = 0; break; case G2_MSG_QH2: offset = 1; /* First payload byte is the hop count */ break; default: return NULL; /* No MUID in message */ } payload = g2_tree_node_payload(t, &paylen); if (NULL == payload || paylen < GUID_RAW_SIZE + offset) return NULL; /* * Copy the MUID in the supplied buffer for alignment purposes, since * the MUID is offset by 1 byte in /QH2 messages, and return that aligned * pointer. */ memcpy(buf, const_ptr_add_offset(payload, offset), GUID_RAW_SIZE); return buf; }
/** * Tree message iterator to handle "CH" nodes and extract their IP:port. */ static void g2_node_extract_ch(void *data, void *udata) { const g2_tree_t *t = data; (void) udata; if (0 == strcmp("CH", g2_tree_name(t))) { const char *payload; size_t paylen; payload = g2_tree_node_payload(t, &paylen); if (10 == paylen) { /* IPv4:port + 32-bit timestamp */ host_addr_t addr = host_addr_peek_ipv4(payload); uint16 port = peek_le16(&payload[4]); if (host_is_valid(addr, port) && !hostiles_is_bad(addr)) guess_add_hub(addr, port); } } }
/** * Handle reception of a /Q2 */ static void g2_node_handle_q2(gnutella_node_t *n, const g2_tree_t *t) { const guid_t *muid; size_t paylen; const g2_tree_t *c; char *dn = NULL; char *md = NULL; uint32 iflags = 0; search_request_info_t sri; bool has_interest = FALSE; node_inc_rx_query(n); /* * As a G2 leaf, we cannot handle queries coming from UDP because we * are not supposed to get any! */ if (NODE_IS_UDP(n)) { g2_node_drop(G_STRFUNC, n, t, "coming from UDP"); return; } /* * The MUID of the query is the payload of the root node. */ muid = g2_tree_node_payload(t, &paylen); if (paylen != GUID_RAW_SIZE) { g2_node_drop(G_STRFUNC, n, t, "missing MUID"); return; } /* * Make sure we have never seen this query already. * * To be able to leverage on Gnutella's routing table to detect duplicates * over a certain lifespan, we are going to fake a minimal Gnutella header * with a message type of GTA_MSG_G2_SEARCH, which is never actually used * on the network. * * The TTL and hops are set to 1 and 0 initially, so that the message seems * to come from a neighbouring host and cannot be forwarded. * * When that is done, we will be able to call route_message() and have * all the necessary bookkeeping done for us. */ { struct route_dest dest; gnutella_header_set_muid(&n->header, muid); gnutella_header_set_function(&n->header, GTA_MSG_G2_SEARCH); gnutella_header_set_ttl(&n->header, 1); gnutella_header_set_hops(&n->header, 0); if (!route_message(&n, &dest)) return; /* Already accounted as duplicated, and logged */ } /* * Setup request information so that we can call search_request() * to process our G2 query. */ ZERO(&sri); sri.magic = SEARCH_REQUEST_INFO_MAGIC; /* * Handle the children of /Q2. */ G2_TREE_CHILD_FOREACH(t, c) { enum g2_q2_child ct = TOKENIZE(g2_tree_name(c), g2_q2_children); const char *payload; switch (ct) { case G2_Q2_DN: payload = g2_tree_node_payload(c, &paylen); if (payload != NULL && NULL == dn) { uint off = 0; /* Not NUL-terminated, need to h_strndup() it */ dn = h_strndup(payload, paylen); if (!query_utf8_decode(dn, &off)) { gnet_stats_count_dropped(n, MSG_DROP_MALFORMED_UTF_8); goto done; /* Drop the query */ } sri.extended_query = dn + off; sri.search_len = paylen - off; /* In bytes */ } break; case G2_Q2_I: if (!has_interest) iflags = g2_node_extract_interest(c); has_interest = TRUE; break; case G2_Q2_MD: payload = g2_tree_node_payload(c, &paylen); if (payload != NULL && NULL == md) { /* Not NUL-terminated, need to h_strndup() it */ md = h_strndup(payload, paylen); } break; case G2_Q2_NAT: sri.flags |= QUERY_F_FIREWALLED; break; case G2_Q2_SZR: /* Size limits */ if (g2_node_extract_size_request(c, &sri.minsize, &sri.maxsize)) sri.size_restrictions = TRUE; break; case G2_Q2_UDP: if (!sri.oob) g2_node_extract_udp(c, &sri, n); break; case G2_Q2_URN: g2_node_extract_urn(c, &sri); break; } } /* * When there is no /Q2/I, return a default set of information. */ if (!has_interest) iflags = G2_Q2_F_DFLT; /* * If there are meta-data, try to intuit which media types there are * looking for. * * The payload is XML looking like "<audio/>" or "<video/>" but there * can be attributes and we don't want to do a full XML parsing there. * Hence we'll base our analysis on simple lexical parsing, which is * why we call a routine to "intuit", not to "extract". * * Also, this is poorer than Gnutella's GGEP "M" because apparently there * can be only one single type, since the XML payload must obey some * kind of schema and there is an audio schema, a video schema, etc... * XML was just a wrong design choice there. */ if (md != NULL) sri.media_types = g2_node_intuit_media_type(md); /* * Validate the return address if OOB hit delivery is configured. */ if (sri.oob && !search_oob_is_allowed(n, &sri)) goto done; /* * Update statistics, as done in search_request_preprocess() for Gnutella. */ if (sri.exv_sha1cnt) { gnet_stats_inc_general(GNR_QUERY_G2_SHA1); if (NULL == dn) { int i; for (i = 0; i < sri.exv_sha1cnt; i++) { search_request_listener_emit(QUERY_SHA1, sha1_base32(&sri.exv_sha1[i].sha1), n->addr, n->port); } } } if (dn != NULL && !is_ascii_string(dn)) gnet_stats_inc_general(GNR_QUERY_G2_UTF8); if (dn != NULL) search_request_listener_emit(QUERY_STRING, dn, n->addr, n->port); if (!search_is_valid(n, 0, &sri)) goto done; /* * Perform the query. */ sri.g2_query = TRUE; sri.partials = booleanize(iflags & G2_Q2_F_PFS); sri.g2_wants_url = booleanize(iflags & G2_Q2_F_URL); sri.g2_wants_alt = booleanize(iflags & G2_Q2_F_A); sri.g2_wants_dn = booleanize(iflags & G2_Q2_F_DN); search_request(n, &sri, NULL); done: HFREE_NULL(dn); HFREE_NULL(md); }
/** * Handle message coming from G2 node. */ void g2_node_handle(gnutella_node_t *n) { g2_tree_t *t; size_t plen; enum g2_msg type; node_check(n); g_assert(NODE_TALKS_G2(n)); t = g2_frame_deserialize(n->data, n->size, &plen, FALSE); if (NULL == t) { if (GNET_PROPERTY(g2_debug) > 0 || GNET_PROPERTY(log_bad_g2)) { g_warning("%s(): cannot deserialize /%s from %s", G_STRFUNC, g2_msg_raw_name(n->data, n->size), node_infostr(n)); } if (GNET_PROPERTY(log_bad_g2)) dump_hex(stderr, "G2 Packet", n->data, n->size); return; } else if (plen != n->size) { if (GNET_PROPERTY(g2_debug) > 0 || GNET_PROPERTY(log_bad_g2)) { g_warning("%s(): consumed %zu bytes but /%s from %s had %u", G_STRFUNC, plen, g2_msg_raw_name(n->data, n->size), node_infostr(n), n->size); } if (GNET_PROPERTY(log_bad_g2)) dump_hex(stderr, "G2 Packet", n->data, n->size); hostiles_dynamic_add(n->addr, "cannot parse incoming messages", HSTL_GIBBERISH); goto done; } else if (GNET_PROPERTY(g2_debug) > 19) { g_debug("%s(): received packet from %s", G_STRFUNC, node_infostr(n)); g2_tfmt_tree_dump(t, stderr, G2FMT_O_PAYLEN); } type = g2_msg_name_type(g2_tree_name(t)); switch (type) { case G2_MSG_PI: g2_node_handle_ping(n, t); break; case G2_MSG_PO: g2_node_handle_pong(n, t); break; case G2_MSG_LNI: g2_node_handle_lni(n, t); break; case G2_MSG_KHL: g2_node_handle_khl(t); break; case G2_MSG_PUSH: handle_push_request(n, t); break; case G2_MSG_Q2: g2_node_handle_q2(n, t); break; case G2_MSG_QA: case G2_MSG_QKA: g2_node_handle_rpc_answer(n, t, type); break; case G2_MSG_QH2: search_g2_results(n, t); break; default: g2_node_drop(G_STRFUNC, n, t, "default"); break; } done: g2_tree_free_null(&t); }
/** * 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; }