// build response void node_impl::incoming_request(msg const& m, entry& e) { e = entry(entry::dictionary_t); e["y"] = "r"; e["t"] = m.message.dict_find_string_value("t"); key_desc_t top_desc[] = { {"q", lazy_entry::string_t, 0, 0}, {"ro", lazy_entry::int_t, 0, key_desc_t::optional}, {"a", lazy_entry::dict_t, 0, key_desc_t::parse_children}, {"id", lazy_entry::string_t, 20, key_desc_t::last_child}, }; lazy_entry const* top_level[4]; char error_string[200]; if (!verify_message(&m.message, top_desc, top_level, 4, error_string, sizeof(error_string))) { incoming_error(e, error_string); return; } e["ip"] = endpoint_to_bytes(m.addr); char const* query = top_level[0]->string_cstr(); lazy_entry const* arg_ent = top_level[2]; bool read_only = top_level[1] && top_level[1]->int_value() != 0; node_id id(top_level[3]->string_ptr()); // if this nodes ID doesn't match its IP, tell it what // its IP is with an error // don't enforce this yet if (m_settings.enforce_node_id && !verify_id(id, m.addr.address())) { incoming_error(e, "invalid node ID"); return; } if (!read_only) m_table.heard_about(id, m.addr); entry& reply = e["r"]; m_rpc.add_our_id(reply); // mirror back the other node's external port reply["p"] = m.addr.port(); if (strcmp(query, "ping") == 0) { // we already have 't' and 'id' in the response // no more left to add } else if (strcmp(query, "get_peers") == 0) { key_desc_t msg_desc[] = { {"info_hash", lazy_entry::string_t, 20, 0}, {"noseed", lazy_entry::int_t, 0, key_desc_t::optional}, {"scrape", lazy_entry::int_t, 0, key_desc_t::optional}, }; lazy_entry const* msg_keys[3]; if (!verify_message(arg_ent, msg_desc, msg_keys, 3, error_string, sizeof(error_string))) { incoming_error(e, error_string); return; } reply["token"] = generate_token(m.addr, msg_keys[0]->string_ptr()); sha1_hash info_hash(msg_keys[0]->string_ptr()); nodes_t n; // always return nodes as well as peers m_table.find_node(info_hash, n, 0); write_nodes_entry(reply, n); bool noseed = false; bool scrape = false; if (msg_keys[1] && msg_keys[1]->int_value() != 0) noseed = true; if (msg_keys[2] && msg_keys[2]->int_value() != 0) scrape = true; lookup_peers(info_hash, reply, noseed, scrape); #ifdef TORRENT_DHT_VERBOSE_LOGGING if (reply.find_key("values")) { TORRENT_LOG(node) << " values: " << reply["values"].list().size(); } #endif } else if (strcmp(query, "find_node") == 0) { key_desc_t msg_desc[] = { {"target", lazy_entry::string_t, 20, 0}, }; lazy_entry const* msg_keys[1]; if (!verify_message(arg_ent, msg_desc, msg_keys, 1, error_string, sizeof(error_string))) { incoming_error(e, error_string); return; } sha1_hash target(msg_keys[0]->string_ptr()); // TODO: 2 find_node should write directly to the response entry nodes_t n; m_table.find_node(target, n, 0); write_nodes_entry(reply, n); } else if (strcmp(query, "announce_peer") == 0) { key_desc_t msg_desc[] = { {"info_hash", lazy_entry::string_t, 20, 0}, {"port", lazy_entry::int_t, 0, 0}, {"token", lazy_entry::string_t, 0, 0}, {"n", lazy_entry::string_t, 0, key_desc_t::optional}, {"seed", lazy_entry::int_t, 0, key_desc_t::optional}, {"implied_port", lazy_entry::int_t, 0, key_desc_t::optional}, }; lazy_entry const* msg_keys[6]; if (!verify_message(arg_ent, msg_desc, msg_keys, 6, error_string, sizeof(error_string))) { #ifdef TORRENT_DHT_VERBOSE_LOGGING ++g_failed_announces; #endif incoming_error(e, error_string); return; } int port = int(msg_keys[1]->int_value()); // is the announcer asking to ignore the explicit // listen port and instead use the source port of the packet? if (msg_keys[5] && msg_keys[5]->int_value() != 0) port = m.addr.port(); if (port < 0 || port >= 65536) { #ifdef TORRENT_DHT_VERBOSE_LOGGING ++g_failed_announces; #endif incoming_error(e, "invalid port"); return; } sha1_hash info_hash(msg_keys[0]->string_ptr()); if (m_post_alert) { alert* a = new dht_announce_alert(m.addr.address(), port, info_hash); if (!m_post_alert->post_alert(a)) delete a; } if (!verify_token(msg_keys[2]->string_value(), msg_keys[0]->string_ptr(), m.addr)) { #ifdef TORRENT_DHT_VERBOSE_LOGGING ++g_failed_announces; #endif incoming_error(e, "invalid token"); return; } // the token was correct. That means this // node is not spoofing its address. So, let // the table get a chance to add it. m_table.node_seen(id, m.addr, 0xffff); if (!m_map.empty() && int(m_map.size()) >= m_settings.max_torrents) { // we need to remove some. Remove the ones with the // fewest peers int num_peers = m_map.begin()->second.peers.size(); table_t::iterator candidate = m_map.begin(); for (table_t::iterator i = m_map.begin() , end(m_map.end()); i != end; ++i) { if (int(i->second.peers.size()) > num_peers) continue; if (i->first == info_hash) continue; num_peers = i->second.peers.size(); candidate = i; } m_map.erase(candidate); } torrent_entry& v = m_map[info_hash]; // the peer announces a torrent name, and we don't have a name // for this torrent. Store it. if (msg_keys[3] && v.name.empty()) { std::string name = msg_keys[3]->string_value(); if (name.size() > 50) name.resize(50); v.name = name; } peer_entry peer; peer.addr = tcp::endpoint(m.addr.address(), port); peer.added = time_now(); peer.seed = msg_keys[4] && msg_keys[4]->int_value(); std::set<peer_entry>::iterator i = v.peers.find(peer); if (i != v.peers.end()) v.peers.erase(i++); v.peers.insert(i, peer); #ifdef TORRENT_DHT_VERBOSE_LOGGING ++g_announces; #endif } else if (strcmp(query, "put") == 0) { // the first 2 entries are for both mutable and // immutable puts const static key_desc_t msg_desc[] = { {"token", lazy_entry::string_t, 0, 0}, {"v", lazy_entry::none_t, 0, 0}, {"seq", lazy_entry::int_t, 0, key_desc_t::optional}, // public key {"k", lazy_entry::string_t, item_pk_len, key_desc_t::optional}, {"sig", lazy_entry::string_t, item_sig_len, key_desc_t::optional}, {"cas", lazy_entry::int_t, 0, key_desc_t::optional}, {"salt", lazy_entry::string_t, 0, key_desc_t::optional}, }; // attempt to parse the message lazy_entry const* msg_keys[7]; if (!verify_message(arg_ent, msg_desc, msg_keys, 7, error_string, sizeof(error_string))) { incoming_error(e, error_string); return; } // is this a mutable put? bool mutable_put = (msg_keys[2] && msg_keys[3] && msg_keys[4]); // public key (only set if it's a mutable put) char const* pk = NULL; if (msg_keys[3]) pk = msg_keys[3]->string_ptr(); // signature (only set if it's a mutable put) char const* sig = NULL; if (msg_keys[4]) sig = msg_keys[4]->string_ptr(); // pointer and length to the whole entry std::pair<char const*, int> buf = msg_keys[1]->data_section(); if (buf.second > 1000 || buf.second <= 0) { incoming_error(e, "message too big", 205); return; } std::pair<char const*, int> salt(static_cast<char const*>(NULL), 0); if (msg_keys[6]) salt = std::pair<char const*, int>( msg_keys[6]->string_ptr(), msg_keys[6]->string_length()); if (salt.second > 64) { incoming_error(e, "salt too big", 207); return; } sha1_hash target; if (pk) target = item_target_id(salt, pk); else target = item_target_id(buf); // fprintf(stderr, "%s PUT target: %s salt: %s key: %s\n" // , mutable_put ? "mutable":"immutable" // , to_hex(target.to_string()).c_str() // , salt.second > 0 ? std::string(salt.first, salt.second).c_str() : "" // , pk ? to_hex(std::string(pk, 32)).c_str() : ""); // verify the write-token. tokens are only valid to write to // specific target hashes. it must match the one we got a "get" for if (!verify_token(msg_keys[0]->string_value(), (char const*)&target[0], m.addr)) { incoming_error(e, "invalid token"); return; } dht_immutable_item* f = 0; if (!mutable_put) { dht_immutable_table_t::iterator i = m_immutable_table.find(target); if (i == m_immutable_table.end()) { // make sure we don't add too many items if (int(m_immutable_table.size()) >= m_settings.max_dht_items) { // delete the least important one (i.e. the one // the fewest peers are announcing, and farthest // from our node ID) dht_immutable_table_t::iterator j = std::min_element(m_immutable_table.begin() , m_immutable_table.end() , immutable_item_comparator(m_id)); TORRENT_ASSERT(j != m_immutable_table.end()); free(j->second.value); m_immutable_table.erase(j); } dht_immutable_item to_add; to_add.value = (char*)malloc(buf.second); to_add.size = buf.second; memcpy(to_add.value, buf.first, buf.second); boost::tie(i, boost::tuples::ignore) = m_immutable_table.insert( std::make_pair(target, to_add)); } // fprintf(stderr, "added immutable item (%d)\n", int(m_immutable_table.size())); f = &i->second; } else { // mutable put, we must verify the signature #ifdef TORRENT_USE_VALGRIND VALGRIND_CHECK_MEM_IS_DEFINED(msg_keys[4]->string_ptr(), item_sig_len); VALGRIND_CHECK_MEM_IS_DEFINED(pk, item_pk_len); #endif // msg_keys[4] is the signature, msg_keys[3] is the public key if (!verify_mutable_item(buf, salt , msg_keys[2]->int_value(), pk, sig)) { incoming_error(e, "invalid signature", 206); return; } dht_mutable_table_t::iterator i = m_mutable_table.find(target); if (i == m_mutable_table.end()) { // this is the case where we don't have an item in this slot // make sure we don't add too many items if (int(m_mutable_table.size()) >= m_settings.max_dht_items) { // delete the least important one (i.e. the one // the fewest peers are announcing) dht_mutable_table_t::iterator j = std::min_element(m_mutable_table.begin() , m_mutable_table.end() , boost::bind(&dht_immutable_item::num_announcers , boost::bind(&dht_mutable_table_t::value_type::second, _1)) < boost::bind(&dht_immutable_item::num_announcers , boost::bind(&dht_mutable_table_t::value_type::second, _2)) ); TORRENT_ASSERT(j != m_mutable_table.end()); free(j->second.value); free(j->second.salt); m_mutable_table.erase(j); } dht_mutable_item to_add; to_add.value = (char*)malloc(buf.second); to_add.size = buf.second; to_add.seq = msg_keys[2]->int_value(); to_add.salt = NULL; to_add.salt_size = 0; if (salt.second > 0) { to_add.salt = (char*)malloc(salt.second); to_add.salt_size = salt.second; memcpy(to_add.salt, salt.first, salt.second); } memcpy(to_add.sig, sig, sizeof(to_add.sig)); TORRENT_ASSERT(sizeof(to_add.sig) == msg_keys[4]->string_length()); memcpy(to_add.value, buf.first, buf.second); memcpy(&to_add.key, pk, sizeof(to_add.key)); boost::tie(i, boost::tuples::ignore) = m_mutable_table.insert( std::make_pair(target, to_add)); // fprintf(stderr, "added mutable item (%d)\n", int(m_mutable_table.size())); } else { // this is the case where we already dht_mutable_item* item = &i->second; // this is the "cas" field in the put message // if it was specified, we MUST make sure the current sequence // number matches the expected value before replacing it // this is critical for avoiding race conditions when multiple // writers are accessing the same slot if (msg_keys[5] && item->seq != msg_keys[5]->int_value()) { incoming_error(e, "CAS mismatch", 301); return; } if (item->seq > boost::uint64_t(msg_keys[2]->int_value())) { incoming_error(e, "old sequence number", 302); return; } if (item->seq < boost::uint64_t(msg_keys[2]->int_value())) { if (item->size != buf.second) { free(item->value); item->value = (char*)malloc(buf.second); item->size = buf.second; } item->seq = msg_keys[2]->int_value(); memcpy(item->sig, msg_keys[4]->string_ptr(), sizeof(item->sig)); TORRENT_ASSERT(sizeof(item->sig) == msg_keys[4]->string_length()); memcpy(item->value, buf.first, buf.second); } } f = &i->second; } m_table.node_seen(id, m.addr, 0xffff); f->last_seen = time_now(); // maybe increase num_announcers if we haven't seen this IP before sha1_hash iphash; hash_address(m.addr.address(), iphash); if (!f->ips.find(iphash)) { f->ips.set(iphash); ++f->num_announcers; } } else if (strcmp(query, "get") == 0) { key_desc_t msg_desc[] = { {"seq", lazy_entry::int_t, 0, key_desc_t::optional}, {"target", lazy_entry::string_t, 20, 0}, }; // k is not used for now // attempt to parse the message lazy_entry const* msg_keys[2]; if (!verify_message(arg_ent, msg_desc, msg_keys, 2, error_string, sizeof(error_string))) { incoming_error(e, error_string); return; } sha1_hash target(msg_keys[1]->string_ptr()); // fprintf(stderr, "%s GET target: %s\n" // , msg_keys[1] ? "mutable":"immutable" // , to_hex(target.to_string()).c_str()); reply["token"] = generate_token(m.addr, msg_keys[1]->string_ptr()); nodes_t n; // always return nodes as well as peers m_table.find_node(target, n, 0); write_nodes_entry(reply, n); dht_immutable_table_t::iterator i = m_immutable_table.end(); // if the get has a sequence number it must be for a mutable item // so don't bother searching the immutable table if (!msg_keys[0]) i = m_immutable_table.find(target); if (i != m_immutable_table.end()) { dht_immutable_item const& f = i->second; reply["v"] = bdecode(f.value, f.value + f.size); } else { dht_mutable_table_t::iterator i = m_mutable_table.find(target); if (i != m_mutable_table.end()) { dht_mutable_item const& f = i->second; reply["seq"] = f.seq; if (!msg_keys[0] || boost::uint64_t(msg_keys[0]->int_value()) < f.seq) { reply["v"] = bdecode(f.value, f.value + f.size); reply["sig"] = std::string(f.sig, f.sig + sizeof(f.sig)); reply["k"] = std::string(f.key.bytes, f.key.bytes + sizeof(f.key.bytes)); } } } } else { // if we don't recognize the message but there's a // 'target' or 'info_hash' in the arguments, treat it // as find_node to be future compatible lazy_entry const* target_ent = arg_ent->dict_find_string("target"); if (target_ent == 0 || target_ent->string_length() != 20) { target_ent = arg_ent->dict_find_string("info_hash"); if (target_ent == 0 || target_ent->string_length() != 20) { incoming_error(e, "unknown message"); return; } } sha1_hash target(target_ent->string_ptr()); nodes_t n; // always return nodes as well as peers m_table.find_node(target, n, 0); write_nodes_entry(reply, n); return; } }