Example #1
0
// 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;
	}
}