improve the external IP discovery to work with multiple external IPs (specifically IPv4 and IPv6)

This commit is contained in:
Arvid Norberg 2013-01-01 23:12:16 +00:00
parent 3d0e5484ee
commit aab59d04c7
13 changed files with 285 additions and 149 deletions

View File

@ -30,6 +30,7 @@ set(sources
i2p_stream
identify_client
ip_filter
ip_voter
peer_connection
bt_peer_connection
web_peer_connection

View File

@ -490,6 +490,7 @@ SOURCES =
http_parser
identify_client
ip_filter
ip_voter
peer_connection
bt_peer_connection
web_connection_base

View File

@ -61,6 +61,7 @@ nobase_include_HEADERS = \
io_service.hpp \
io_service_fwd.hpp \
ip_filter.hpp \
ip_voter.hpp \
lazy_entry.hpp \
lsd.hpp \
magnet_uri.hpp \

View File

@ -57,6 +57,7 @@ POSSIBILITY OF SUCH DAMAGE.
#pragma warning(pop)
#endif
#include "libtorrent/ip_voter.hpp"
#include "libtorrent/torrent_handle.hpp"
#include "libtorrent/entry.hpp"
#include "libtorrent/socket.hpp"
@ -510,7 +511,8 @@ namespace libtorrent
// implements dht_observer
virtual void set_external_address(address const& ip
, int source_type, address const& source);
address const& external_address() const { return m_external_address; }
external_ip const& external_address() const;
bool can_write_to_disk() const
{ return m_disk_thread.can_write(); }
@ -1151,36 +1153,9 @@ namespace libtorrent
#ifdef TORRENT_UPNP_LOGGING
std::ofstream m_upnp_log;
#endif
// TODO: factor the IP voting out to its own type, maybe templated by the address type. Have one IPv4 vote and a separate IPv6 vote. Maybe even better, have one per local interface sockets can be bound to
struct external_ip_t
{
external_ip_t(): sources(0), num_votes(0) {}
bool add_vote(sha1_hash const& k, int type);
bool operator<(external_ip_t const& rhs) const
{
if (num_votes < rhs.num_votes) return true;
if (num_votes > rhs.num_votes) return false;
return sources < rhs.sources;
}
// this is a bloom filter of the IPs that have
// reported this address
bloom_filter<16> voters;
// this is the actual external address
address addr;
// a bitmask of sources the reporters have come from
boost::uint16_t sources;
// the total number of votes for this IP
boost::uint16_t num_votes;
};
// this is a bloom filter of all the IPs that have
// been the first to report an external address. Each
// IP only gets to add a new item once.
bloom_filter<32> m_external_address_voters;
std::vector<external_ip_t> m_external_addresses;
address m_external_address;
// state for keeping track of external IPs
external_ip m_external_ip;
#ifndef TORRENT_DISABLE_EXTENSIONS
typedef std::list<boost::function<boost::shared_ptr<

View File

@ -0,0 +1,107 @@
/*
Copyright (c) 2013, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TORRENT_IP_VOTER_HPP_INCLUDED
#define TORRENT_IP_VOTER_HPP_INCLUDED
#include <vector>
#include "libtorrent/address.hpp"
#include "libtorrent/bloom_filter.hpp"
namespace libtorrent
{
// this is an object that keeps the state for a single external IP
// based on peoples votes
struct ip_voter
{
// returns true if a different IP is the top vote now
// i.e. we changed our idea of what our external IP is
bool cast_vote(address const& ip, int source_type, address const& sorce);
address external_address() const { return m_external_address; }
private:
struct external_ip_t
{
external_ip_t(): sources(0), num_votes(0) {}
bool add_vote(sha1_hash const& k, int type);
bool operator<(external_ip_t const& rhs) const
{
if (num_votes < rhs.num_votes) return true;
if (num_votes > rhs.num_votes) return false;
return sources < rhs.sources;
}
// this is a bloom filter of the IPs that have
// reported this address
bloom_filter<16> voters;
// this is the actual external address
address addr;
// a bitmask of sources the reporters have come from
boost::uint16_t sources;
// the total number of votes for this IP
boost::uint16_t num_votes;
};
// this is a bloom filter of all the IPs that have
// been the first to report an external address. Each
// IP only gets to add a new item once.
bloom_filter<32> m_external_address_voters;
std::vector<external_ip_t> m_external_addresses;
address m_external_address;
};
// this keeps track of multiple external IPs (for now, just IPv6 and IPv4, but
// it could be extended to deal with loopback and local network addresses as well)
struct external_ip
{
// returns true if a different IP is the top vote now
// i.e. we changed our idea of what our external IP is
bool cast_vote(address const& ip, int source_type, address const& source);
// the external IP as it would be observed from `ip`
address external_address(address const& ip) const;
private:
// for now, assume one external IPv4 and one external IPv6 address
// 0 = IPv4 1 = IPv6
// TODO: instead, have one instance per possible subnet, global IPv4, global IPv6, loopback, 192.168.x.x, 10.x.x.x, etc.
ip_voter m_vote_group[2];
};
}
#endif

View File

@ -50,6 +50,7 @@ namespace libtorrent
class torrent;
class peer_connection;
struct external_ip;
// this is compressed as an unsigned floating point value
// the top 13 bits are the mantissa and the low
@ -178,7 +179,7 @@ namespace libtorrent
size_type total_download() const;
size_type total_upload() const;
boost::uint32_t rank(tcp::endpoint const& external) const;
boost::uint32_t rank(external_ip const& external, int external_port) const;
libtorrent::address address() const;
char const* dest() const;
@ -442,7 +443,7 @@ namespace libtorrent
bool compare_peer_erase(policy::peer const& lhs, policy::peer const& rhs) const;
bool compare_peer(policy::peer const& lhs, policy::peer const& rhs
, tcp::endpoint const& external_ip) const;
, external_ip const& external, int source_port) const;
iterator find_connect_candidate(int session_time);

View File

@ -56,6 +56,7 @@ libtorrent_rasterbar_la_SOURCES = \
identify_client.cpp \
instantiate_connection.cpp \
ip_filter.cpp \
ip_voter.cpp \
lazy_bdecode.cpp \
logger.cpp \
lsd.cpp \

123
src/ip_voter.cpp Normal file
View File

@ -0,0 +1,123 @@
/*
Copyright (c) 2013, Arvid Norberg
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
#include "libtorrent/ip_voter.hpp"
#include "libtorrent/broadcast_socket.hpp" // for is_any() etc.
#include "libtorrent/socket_io.hpp" // for hash_address
#include <boost/bind.hpp>
namespace libtorrent
{
bool ip_voter::cast_vote(address const& ip
, int source_type, address const& source)
{
if (is_any(ip)) return false;
if (is_local(ip)) return false;
if (is_loopback(ip)) return false;
// don't trust source that aren't connected to us
// on a different address family than the external
// IP they claim we have
if (ip.is_v4() != source.is_v4()) return false;
// this is the key to use for the bloom filters
// it represents the identity of the voter
sha1_hash k;
hash_address(source, k);
// do we already have an entry for this external IP?
std::vector<external_ip_t>::iterator i = std::find_if(m_external_addresses.begin()
, m_external_addresses.end(), boost::bind(&external_ip_t::addr, _1) == ip);
if (i == m_external_addresses.end())
{
// each IP only gets to add a new IP once
if (m_external_address_voters.find(k)) return false;
if (m_external_addresses.size() > 40)
{
if (random() % 100 < 50)
return false;
// use stable sort here to maintain the fifo-order
// of the entries with the same number of votes
// this will sort in ascending order, i.e. the lowest
// votes first. Also, the oldest are first, so this
// is a sort of weighted LRU.
std::stable_sort(m_external_addresses.begin(), m_external_addresses.end());
// erase the first element, since this is the
// oldest entry and the one with lowst number
// of votes. This makes sense because the oldest
// entry has had the longest time to receive more
// votes to be bumped up
m_external_addresses.erase(m_external_addresses.begin());
}
m_external_addresses.push_back(external_ip_t());
i = m_external_addresses.end() - 1;
i->addr = ip;
}
// add one more vote to this external IP
if (!i->add_vote(k, source_type)) return false;
i = std::max_element(m_external_addresses.begin(), m_external_addresses.end());
TORRENT_ASSERT(i != m_external_addresses.end());
if (i->addr == m_external_address) return false;
m_external_address = i->addr;
m_external_address_voters.clear();
return true;
}
bool ip_voter::external_ip_t::add_vote(sha1_hash const& k, int type)
{
sources |= type;
if (voters.find(k)) return false;
voters.set(k);
++num_votes;
return true;
}
bool external_ip::cast_vote(address const& ip, int source_type, address const& source)
{
return m_vote_group[ip.is_v6()].cast_vote(ip, source_type, source);
}
address external_ip::external_address(address const& ip) const
{
return m_vote_group[ip.is_v6()].external_address();
}
}

View File

@ -203,7 +203,7 @@ namespace libtorrent { namespace dht
dht_tracker::dht_tracker(libtorrent::aux::session_impl& ses, rate_limited_udp_socket& sock
, dht_settings const& settings, entry const* state)
: m_dht(&ses, this, settings, extract_node_id(state)
, ses.external_address(), &ses)
, ses.external_address().external_address(address_v4()), &ses)
, m_sock(sock)
, m_last_new_key(time_now() - minutes(key_refresh))
, m_timer(sock.get_io_service())

View File

@ -1114,7 +1114,8 @@ namespace libtorrent
boost::uint32_t peer_connection::peer_rank() const
{
return m_peer_info->rank(tcp::endpoint(m_ses.external_address(), m_ses.listen_port()));
return m_peer_info->rank(m_ses.external_address()
, m_ses.listen_port());
}
// message handlers

View File

@ -742,7 +742,8 @@ namespace libtorrent
TORRENT_ASSERT(m_finished == m_torrent->is_finished());
int min_reconnect_time = m_torrent->settings().min_reconnect_time;
tcp::endpoint external_ip(m_torrent->session().external_address(), m_torrent->session().listen_port());
external_ip const& external = m_torrent->session().external_address();
int external_port = m_torrent->session().listen_port();
if (m_round_robin >= int(m_peers.size())) m_round_robin = 0;
@ -809,7 +810,7 @@ namespace libtorrent
// pe, which is the peer m_round_robin points to. If it is, just
// keep looking.
if (candidate != -1
&& compare_peer(*m_peers[candidate], pe, external_ip)) continue;
&& compare_peer(*m_peers[candidate], pe, external, external_port)) continue;
if (pe.last_connected
&& session_time - pe.last_connected <
@ -831,9 +832,9 @@ namespace libtorrent
(*m_torrent->session().m_logger) << time_now_string()
<< " *** FOUND CONNECTION CANDIDATE ["
" ip: " << m_peers[candidate]->ip() <<
" d: " << cidr_distance(external_ip.address(), m_peers[candidate]->address()) <<
" rank: " << m_peers[candidate]->rank(external_ip) <<
" external: " << external_ip.address() <<
" d: " << cidr_distance(external.address(m_peers[candidate]->address()), m_peers[candidate]->address()) <<
" rank: " << m_peers[candidate]->rank(external, external_port) <<
" external: " << external.external_address(m_peers[candidate]->address()) <<
" t: " << (session_time - m_peers[candidate]->last_connected) <<
" ]\n";
}
@ -1903,12 +1904,13 @@ namespace libtorrent
}
// TOOD: pass in both an IPv6 and IPv4 address here
boost::uint32_t policy::peer::rank(tcp::endpoint const& external) const
boost::uint32_t policy::peer::rank(external_ip const& external, int external_port) const
{
//TODO: really, keep track of one external IP per address family
//TODO: how do we deal with our external address changing?
//TODO: how do we deal with our external address changing? Pass in a force-update maybe? and keep a version number in policy
if (peer_rank == 0)
peer_rank = peer_priority(external, tcp::endpoint(this->address(), this->port));
peer_rank = peer_priority(
tcp::endpoint(external.external_address(this->address()), external_port)
, tcp::endpoint(this->address(), this->port));
return peer_rank;
}
@ -1963,7 +1965,7 @@ namespace libtorrent
// this returns true if lhs is a better connect candidate than rhs
bool policy::compare_peer(policy::peer const& lhs, policy::peer const& rhs
, tcp::endpoint const& external_ip) const
, external_ip const& external, int external_port) const
{
// prefer peers with lower failcount
if (lhs.failcount != rhs.failcount)
@ -1990,8 +1992,8 @@ namespace libtorrent
if (lhs_as != rhs_as) return lhs_as > rhs_as;
}
#endif
boost::uint32_t lhs_peer_rank = lhs.rank(external_ip);
boost::uint32_t rhs_peer_rank = rhs.rank(external_ip);
boost::uint32_t lhs_peer_rank = lhs.rank(external, external_port);
boost::uint32_t rhs_peer_rank = rhs.rank(external, external_port);
if (lhs_peer_rank > rhs_peer_rank) return true;
return false;
}

View File

@ -6084,93 +6084,23 @@ retry:
}
m_upnp = 0;
}
bool session_impl::external_ip_t::add_vote(sha1_hash const& k, int type)
{
sources |= type;
if (voters.find(k)) return false;
voters.set(k);
++num_votes;
return true;
}
external_ip const& session_impl::external_address() const
{ return m_external_ip; }
void session_impl::set_external_address(address const& ip
, int source_type, address const& source)
{
if (is_any(ip)) return;
if (is_local(ip)) return;
if (is_loopback(ip)) return;
#if defined TORRENT_VERBOSE_LOGGING
session_log(": set_external_address(%s, %d, %s)", print_address(ip).c_str()
, source_type, print_address(source).c_str());
#endif
// this is the key to use for the bloom filters
// it represents the identity of the voter
sha1_hash k;
hash_address(source, k);
// do we already have an entry for this external IP?
std::vector<external_ip_t>::iterator i = std::find_if(m_external_addresses.begin()
, m_external_addresses.end(), boost::bind(&external_ip_t::addr, _1) == ip);
if (i == m_external_addresses.end())
{
// each IP only gets to add a new IP once
if (m_external_address_voters.find(k)) return;
if (m_external_addresses.size() > 20)
{
if (random() < UINT_MAX / 2)
{
#if defined TORRENT_VERBOSE_LOGGING
session_log(": More than 20 slots, dopped");
#endif
return;
}
// use stable sort here to maintain the fifo-order
// of the entries with the same number of votes
// this will sort in ascending order, i.e. the lowest
// votes first. Also, the oldest are first, so this
// is a sort of weighted LRU.
std::stable_sort(m_external_addresses.begin(), m_external_addresses.end());
// erase the first element, since this is the
// oldest entry and the one with lowst number
// of votes. This makes sense because the oldest
// entry has had the longest time to receive more
// votes to be bumped up
#if defined TORRENT_VERBOSE_LOGGING
session_log(" More than 20 slots, dopping %s (%d)"
, print_address(m_external_addresses.front().addr).c_str()
, m_external_addresses.front().num_votes);
#endif
m_external_addresses.erase(m_external_addresses.begin());
}
m_external_addresses.push_back(external_ip_t());
i = m_external_addresses.end() - 1;
i->addr = ip;
}
// add one more vote to this external IP
if (!i->add_vote(k, source_type)) return;
i = std::max_element(m_external_addresses.begin(), m_external_addresses.end());
TORRENT_ASSERT(i != m_external_addresses.end());
#if defined TORRENT_VERBOSE_LOGGING
for (std::vector<external_ip_t>::iterator j = m_external_addresses.begin()
, end(m_external_addresses.end()); j != end; ++j)
{
session_log("%s %s votes: %d", (j == i)?"-->":" "
, print_address(j->addr).c_str(), j->num_votes);
}
#endif
if (i->addr == m_external_address) return;
if (!m_external_ip.cast_vote(ip, source_type, source));
#if defined TORRENT_VERBOSE_LOGGING
session_log(" external IP updated");
#endif
m_external_address = i->addr;
m_external_address_voters.clear();
if (m_alerts.should_post<external_ip_alert>())
m_alerts.post_alert(external_ip_alert(ip));
@ -6178,6 +6108,7 @@ retry:
// since we have a new external IP now, we need to
// restart the DHT with a new node ID
#ifndef TORRENT_DISABLE_DHT
// TODO: we only need to do this if our global IPv4 address has changed
if (m_dht)
{
entry s = m_dht->state();

View File

@ -51,6 +51,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/bloom_filter.hpp"
#include "libtorrent/aux_/session_impl.hpp"
#include "libtorrent/rsa.hpp"
#include "libtorrent/ip_voter.hpp"
#ifndef TORRENT_DISABLE_DHT
#include "libtorrent/kademlia/node_id.hpp"
#include "libtorrent/kademlia/routing_table.hpp"
@ -394,6 +395,13 @@ address rand_v4()
return address_v4((rand() << 16 | rand()) & 0xffffffff);
}
address rand_v6()
{
address_v6::bytes_type bytes;
for (int i = 0; i < bytes.size(); ++i) bytes[i] = rand();
return address_v6(bytes);
}
int test_main()
{
using namespace libtorrent;
@ -537,12 +545,7 @@ int test_main()
}
// test external ip voting
aux::session_impl* ses = new aux::session_impl(std::pair<int, int>(0,0)
, fingerprint("LT", 0, 0, 0, 0), "0.0.0.0", 0
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
, ""
#endif
);
external_ip ipv1;
// test a single malicious node
// adds 50 legitimate responses from different peers
@ -551,39 +554,28 @@ int test_main()
address malicious = address_v4::from_string("4.4.4.4");
for (int i = 0; i < 50; ++i)
{
ses->set_external_address(real_external, aux::session_impl::source_dht, rand_v4());
ses->set_external_address(rand_v4(), aux::session_impl::source_dht, malicious);
ipv1.cast_vote(real_external, aux::session_impl::source_dht, rand_v4());
ipv1.cast_vote(rand_v4(), aux::session_impl::source_dht, malicious);
}
TEST_CHECK(ses->external_address() == real_external);
ses->abort();
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
ses->m_logger.reset();
#endif
delete ses;
ses = new aux::session_impl(std::pair<int, int>(0,0)
, fingerprint("LT", 0, 0, 0, 0), "0.0.0.0", 0
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
, ""
#endif
);
TEST_CHECK(ipv1.external_address(rand_v4()) == real_external);
external_ip ipv2;
// test a single malicious node
// adds 50 legitimate responses from different peers
// and 50 consistent malicious responses from the same peer
real_external = address_v4::from_string("5.5.5.5");
address real_external1 = address_v4::from_string("5.5.5.5");
address real_external2 = address_v6::from_string("2f80::1");
malicious = address_v4::from_string("4.4.4.4");
address malicious_external = address_v4::from_string("3.3.3.3");
for (int i = 0; i < 50; ++i)
{
ses->set_external_address(real_external, aux::session_impl::source_dht, rand_v4());
ses->set_external_address(malicious_external, aux::session_impl::source_dht, malicious);
ipv2.cast_vote(real_external1, aux::session_impl::source_dht, rand_v4());
ipv2.cast_vote(real_external2, aux::session_impl::source_dht, rand_v6());
ipv2.cast_vote(malicious_external, aux::session_impl::source_dht, malicious);
}
TEST_CHECK(ses->external_address() == real_external);
ses->abort();
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
ses->m_logger.reset();
#endif
delete ses;
TEST_CHECK(ipv2.external_address(rand_v4()) == real_external1);
TEST_CHECK(ipv2.external_address(rand_v6()) == real_external2);
// test bloom_filter
bloom_filter<32> filter;