/** * Send a message to target node. * * @param n the G2 node to which message should be sent * @param mb the message to sent (ownership taken, will be freed later) */ void g2_node_send(const gnutella_node_t *n, pmsg_t *mb) { node_check(n); g_assert(NODE_TALKS_G2(n)); if (NODE_IS_UDP(n)) mq_udp_node_putq(n->outq, mb, n); else if (NODE_IS_WRITABLE(n)) mq_tcp_putq(n->outq, mb, NULL); else goto drop; if (GNET_PROPERTY(log_sending_g2)) { g_debug("%s(): sending %s to %s", G_STRFUNC, g2_msg_infostr_mb(mb), node_infostr(n)); } return; drop: if (GNET_PROPERTY(log_sending_g2)) { g_debug("%s(): aborting sending %s to %s", G_STRFUNC, g2_msg_infostr_mb(mb), node_infostr(n)); } pmsg_free(mb); /* Cannot send it, free it now */ }
void gnet_stats_count_dropped(gnutella_node_t *n, msg_drop_reason_t reason) { uint32 size; uint type; gnet_stats_t *stats; g_assert(UNSIGNED(reason) < MSG_DROP_REASON_COUNT); g_assert(thread_is_main()); stats = NODE_USES_UDP(n) ? &gnet_udp_stats : &gnet_tcp_stats; if (NODE_TALKS_G2(n)) { int f = g2_msg_type(n->data, n->size); if (f != G2_MSG_MAX) { f += MSG_G2_BASE; } else { f = G_N_ELEMENTS(stats_lut) - 1; /* Last, holds MSG_UNKNOWN */ } type = stats_lut[f]; size = n->size; } else { type = stats_lut[gnutella_header_get_function(&n->header)]; size = n->size + sizeof(n->header); } entropy_harvest_small( VARLEN(n->addr), VARLEN(n->port), VARLEN(reason), VARLEN(type), VARLEN(size), NULL); DROP_STATS(stats, type, size); node_inc_rxdrop(n); switch (reason) { case MSG_DROP_HOSTILE_IP: n->n_hostile++; break; case MSG_DROP_SPAM: n->n_spam++; break; case MSG_DROP_EVIL: n->n_evil++; break; default: ; } if (NODE_TALKS_G2(n)) { if (GNET_PROPERTY(log_dropped_g2)) { g2_msg_log_dropped_data(n->data, n->size, "from %s: %s", node_infostr(n), gnet_stats_drop_reason_to_string(reason)); } } else { if (GNET_PROPERTY(log_dropped_gnutella)) { gmsg_log_split_dropped(&n->header, n->data, n->size, "from %s: %s", node_infostr(n), gnet_stats_drop_reason_to_string(reason)); } } }
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); } }
/** * 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); }
/** * Free routine for the extended message blocks we send to the UDP layer. */ static void g2_qh2_pmsg_free(pmsg_t *mb, void *arg) { struct g2_qh2_pmsg_info *pmi = arg; gnutella_node_t *n; g2_qh2_pmsg_info_check(pmi); g_assert(pmsg_is_extended(mb)); if (pmsg_was_sent(mb)) goto done; /* * Message was unsent, probably because the UDP address in the /Q2 was * wrong for some reason. * * If we're still connected to the hub which passed us this /Q2, then * we can relay back the /QH2 to the hub and it will hopefully be able * to deliver it back to the querying node. */ n = node_by_id(pmi->hub_id); if (NULL == n) { if (GNET_PROPERTY(g2_debug) > 1) { g_debug("%s(): could not send %s, relaying hub is gone, dropping.", G_STRFUNC, g2_msg_infostr_mb(mb)); } gnet_stats_inc_general(GNR_UDP_G2_HITS_UNDELIVERED); goto done; } else { pmsg_t *nmb; if (GNET_PROPERTY(g2_debug) > 1) { g_debug("%s(): could not send %s, giving back to %s for relaying", G_STRFUNC, g2_msg_infostr_mb(mb), node_infostr(n)); } nmb = pmsg_clone_plain(mb); pmsg_clear_reliable(nmb); g2_node_send(n, nmb); gnet_stats_inc_general(GNR_UDP_G2_HITS_REROUTED_TO_HUB); } done: nid_unref(pmi->hub_id); pmi->magic = 0; WFREE(pmi); }
void gnet_stats_count_dropped_nosize( const gnutella_node_t *n, msg_drop_reason_t reason) { uint type; gnet_stats_t *stats; g_assert(UNSIGNED(reason) < MSG_DROP_REASON_COUNT); g_assert(thread_is_main()); g_assert(!NODE_TALKS_G2(n)); type = stats_lut[gnutella_header_get_function(&n->header)]; stats = NODE_USES_UDP(n) ? &gnet_udp_stats : &gnet_tcp_stats; entropy_harvest_small(VARLEN(n->addr), VARLEN(n->port), NULL); /* Data part of message not read */ DROP_STATS(stats, type, sizeof(n->header)); if (GNET_PROPERTY(log_dropped_gnutella)) gmsg_log_split_dropped(&n->header, n->data, 0, "from %s: %s", node_infostr(n), gnet_stats_drop_reason_to_string(reason)); }
/** * Look whether the datagram we received is a valid Gnutella packet. * * The routine also handles traffic statistics (reception and dropping). * * If ``n'' is not NULL, then ``s'' may be NULL. If ``n'' is NULL, then * ``s'' must not be NULL. * * @param n the pseudo UDP reception node (NULL if invalid IP:port) * @param s the socket on which we got the UDP datagram * @param truncated whether datagram was truncated during reception * @param header header of message * @param payload payload of message (maybe not contiguous with header) * @param len total length of message (header + payload) * * @return TRUE if valid, FALSE otherwise. */ bool udp_is_valid_gnet_split(gnutella_node_t *n, const gnutella_socket_t *s, bool truncated, const void *header, const void *payload, size_t len) { const char *msg; uint16 size; /**< Payload size, from the Gnutella message */ g_assert(s != NULL || n != NULL); /* * If we can't get a proper UDP node for this address/port combination, * ignore the message. */ if (NULL == n) { msg = "Invalid address/port combination"; goto not; } if (len < GTA_HEADER_SIZE) { msg = "Too short"; goto not; } /* * We have enough to account for packet reception. * Note that packet could be garbage at this point. */ memcpy(n->header, header, sizeof n->header); n->size = len - GTA_HEADER_SIZE; /* Payload size if Gnutella msg */ gnet_stats_count_received_header(n); gnet_stats_count_received_payload(n, payload); /* * If the message was truncated, then there is also going to be a * size mismatch, but we want to flag truncated messages as being * "too large" because this is mainly why we reject them. They may * be legitimate Gnutella packets, too bad. */ if (truncated) { msg = "Truncated (too large?)"; goto too_large; } /* * Message sizes are architecturally limited to 64K bytes. * * We don't ensure the leading bits are zero in the size field because * this constraint we put allows us to use those bits for flags in * future extensions. * * The downside is that we have only 3 bytes (2 bytes for the size and * 1 byte for the function type) to identify a valid Gnutella packet. */ switch (gmsg_size_valid(header, &size)) { case GMSG_VALID: case GMSG_VALID_MARKED: break; case GMSG_VALID_NO_PROCESS: msg = "Header flags undefined for now"; goto drop; case GMSG_INVALID: msg = "Invalid size (greater than 64 KiB without flags)"; goto not; /* Probably just garbage */ } if ((size_t) size + GTA_HEADER_SIZE != len) { msg = "Size mismatch"; goto not; } /* * We only support a subset of Gnutella message from UDP. In particular, * messages like HSEP data, BYE or QRP are not expected! */ switch (gnutella_header_get_function(header)) { case GTA_MSG_INIT: case GTA_MSG_INIT_RESPONSE: case GTA_MSG_VENDOR: case GTA_MSG_STANDARD: case GTA_MSG_PUSH_REQUEST: case GTA_MSG_SEARCH_RESULTS: case GTA_MSG_RUDP: case GTA_MSG_DHT: return TRUE; case GTA_MSG_SEARCH: if (settings_is_ultra() && GNET_PROPERTY(enable_guess)) { return TRUE; /* GUESS query accepted */ } msg = "Query from UDP refused"; goto drop; } msg = "Gnutella message not processed from UDP"; drop: gnet_stats_count_dropped(n, MSG_DROP_UNEXPECTED); gnet_stats_inc_general(GNR_UDP_UNPROCESSED_MESSAGE); goto log; too_large: gnet_stats_count_dropped(n, MSG_DROP_TOO_LARGE); gnet_stats_inc_general(GNR_UDP_UNPROCESSED_MESSAGE); goto log; not: gnet_stats_inc_general(GNR_UDP_ALIEN_MESSAGE); /* FALL THROUGH */ log: if (GNET_PROPERTY(udp_debug)) { g_warning("UDP got invalid %sGnutella packet (%zu byte%s) " "\"%s\" %sfrom %s: %s", socket_udp_is_old(s) ? "OLD " : "", len, 1 == len ? "" : "s", len >= GTA_HEADER_SIZE ? gmsg_infostr_full_split(header, payload, len - GTA_HEADER_SIZE) : "<incomplete Gnutella header>", truncated ? "(truncated) " : "", NULL == n ? host_addr_port_to_string(s->addr, s->port) : node_infostr(n), msg); if (len != 0) { iovec_t iov[2]; iovec_set(&iov[0], header, GTA_HEADER_SIZE); iovec_set(&iov[1], payload, len - GTA_HEADER_SIZE); dump_hex_vec(stderr, "UDP datagram", iov, G_N_ELEMENTS(iov)); } } return FALSE; /* Dropped */ }
/** * 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; }
/** * 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; }
/** * Based on the information we have on the query hits we already * seen or enqueued, determine whether we're going to drop this * message on the floor or forward it. */ static enum dh_drop dh_can_forward(dqhit_t *dh, mqueue_t *mq, bool test) { const char *teststr = test ? "[test] " : ""; g_assert(mq != NULL); /* * The heart of the "dynamic hit routing" algorithm is here. */ /* * If the queue already has more bytes queued than its high-watermark, * meaning it is in the dangerous zone, drop this hit if we sent more * than DH_THRESH_HITS already or have enough in the queue to reach the * DH_MIN_HITS level. */ if ( mq_size(mq) > mq_hiwat(mq) && /* Implies we're flow-controlled */ (dh->hits_sent >= DH_THRESH_HITS || dh->hits_queued >= DH_MIN_HITS) ) { if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %squeue size > hiwat, dropping", teststr); return DH_DROP_FC; } /* * In SWIFT mode, we're aggressively dropping messages from the queue. * We're in flow control, but we're probably lower than hiwat, the * heaviest condition. Be more tolerant before dropping, meaning * a strongest dropping rule than the above. */ if ( mq_is_swift_controlled(mq) && (dh->hits_sent >= DH_MIN_HITS || dh->hits_queued >= DH_MIN_HITS) ) { if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %squeue in SWIFT mode, dropping", teststr); return DH_DROP_FC; } /* * Queue is flow-controlled, don't add to its burden if we * already have hits enqueued for this query with results sent. */ if ( mq_is_flow_controlled(mq) && ( (dh->hits_sent >= DH_MIN_HITS && dh->hits_queued >= 2 * DH_THRESH_HITS) || (dh->hits_sent < DH_MIN_HITS && (dh->hits_sent + dh->hits_queued) >= DH_MIN_HITS + DH_THRESH_HITS) ) ) { if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %squeue in FLOWC mode, dropping", teststr); return DH_DROP_FC; } /* * If the queue has more bytes than its low-watermark, meaning * it is in the warning zone, drop if we sent more then DH_POPULAR_HITS * already, and we have quite a few queued. */ if ( mq_size(mq) > mq_lowat(mq) && dh->hits_sent >= DH_POPULAR_HITS && dh->hits_queued >= (DH_MIN_HITS / 2) ) { if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %squeue size > lowat, dropping", teststr); return DH_DROP_FC; } /* * If we sent more than DH_POPULAR_HITS and have DH_MIN_HITS queued, * don't add more and throttle. */ if ( dh->hits_sent >= DH_POPULAR_HITS && dh->hits_queued >= DH_MIN_HITS ) { if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %senough hits queued, throttling", teststr); return DH_DROP_THROTTLE; } /* * If what we sent plus what we hold will top the maximum number of hits, * yet we did not reach the maximum, drop: we need to leave room for * other hits for less popular results. */ if ( dh->hits_sent < DH_MAX_HITS && dh->hits_queued > (DH_MIN_HITS / 2) && (dh->hits_queued + dh->hits_sent) >= DH_MAX_HITS) { if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %senough queued, nearing max, throttling", teststr); return DH_DROP_THROTTLE; } /* * Finally, if what we have sent makes up for more than DH_MAX_HITS and * we have anything queued for that query, drop. */ if (dh->hits_sent >= DH_MAX_HITS && dh->hits_queued) { if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %smax sendable hits reached, throttling", teststr); return DH_DROP_THROTTLE; } /* * Transient nodes are going to go away soon, results should not be * forwarded to them since they may not be relayed in time anyway or * could be just a waste of bandwidth. * * Avoid them if they already have enough in their TX queue. */ { gnutella_node_t *n = mq_node(mq); if (NODE_IS_TRANSIENT(n) && mq_size(mq) > mq_lowat(mq)) { if (GNET_PROPERTY(dh_debug) > 19) { g_debug("DH %stransient target %s with %d bytes in queue", teststr, node_infostr(n), mq_size(mq)); } return DH_DROP_TRANSIENT; } } if (GNET_PROPERTY(dh_debug) > 19) g_debug("DH %sforwarding", teststr); return DH_FORWARD; }