revamped part of the port mapping code (UPnP and NAT-PMP). Added documentation for start_{lsd,natpmp,upnp} and stop_{lsd,natpmp,upnp}

This commit is contained in:
Arvid Norberg
2008-04-06 19:17:58 +00:00
parent 35fd9aec61
commit 2e6b9c2dce
13 changed files with 720 additions and 333 deletions

View File

@@ -43,8 +43,6 @@ POSSIBILITY OF SUCH DAMAGE.
using boost::bind;
using namespace libtorrent;
enum { num_mappings = 2 };
natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callback_t const& cb)
: m_callback(cb)
, m_currently_mapping(-1)
@@ -52,11 +50,9 @@ natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callbac
, m_socket(ios)
, m_send_timer(ios)
, m_refresh_timer(ios)
, m_next_refresh(-1)
, m_disabled(false)
{
m_mappings[0].protocol = 2; // tcp
m_mappings[1].protocol = 1; // udp
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log.open("natpmp.log", std::ios::in | std::ios::out | std::ios::trunc);
#endif
@@ -65,6 +61,8 @@ natpmp::natpmp(io_service& ios, address const& listen_interface, portmap_callbac
void natpmp::rebind(address const& listen_interface)
{
mutex_t::scoped_lock l(m_mutex);
asio::error_code ec;
address gateway = get_default_gateway(m_socket.get_io_service(), listen_interface, ec);
if (ec)
@@ -101,42 +99,131 @@ void natpmp::rebind(address const& listen_interface)
return;
}
for (int i = 0; i < num_mappings; ++i)
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
if (m_mappings[i].local_port == 0)
if (i->protocol != none
|| i->action != mapping_t::action_none)
continue;
refresh_mapping(i);
i->action = mapping_t::action_add;
update_mapping(i - m_mappings.begin());
}
}
void natpmp::disable(char const* message)
{
m_disabled = true;
std::stringstream msg;
msg << "NAT-PMP disabled: " << message;
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
if (i->protocol == none) continue;
i->protocol = none;
m_callback(i - m_mappings.begin(), 0, message);
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << msg.str() << std::endl;
m_log << time_now_string() << " NAT-PMP disabled: " << message << std::endl;
#endif
m_callback(0, 0, msg.str());
close();
}
void natpmp::delete_mapping(int index)
{
TORRENT_ASSERT(index < int(m_mappings.size()) && index >= 0);
if (index >= int(m_mappings.size()) || index < 0) return;
mapping_t& m = m_mappings[index];
if (m.protocol == none) return;
m.action = mapping_t::action_delete;
update_mapping(index);
}
void natpmp::set_mappings(int tcp, int udp)
int natpmp::add_mapping(protocol_type p, int external_port, int local_port)
{
if (m_disabled) return;
update_mapping(0, tcp);
update_mapping(1, udp);
mutex_t::scoped_lock l(m_mutex);
if (m_disabled) return -1;
std::vector<mapping_t>::iterator i = std::find_if(m_mappings.begin()
, m_mappings.end(), boost::bind(&mapping_t::protocol, _1) == int(none));
if (i == m_mappings.end())
{
m_mappings.push_back(mapping_t());
i = m_mappings.end() - 1;
}
i->protocol = p;
i->external_port = external_port;
i->local_port = local_port;
i->action = mapping_t::action_add;
int mapping_index = i - m_mappings.begin();
update_mapping(mapping_index);
return mapping_index;
}
void natpmp::update_mapping(int i, int port)
void natpmp::try_next_mapping(int i)
{
natpmp::mapping& m = m_mappings[i];
if (port <= 0) return;
if (m.local_port != port)
m.need_update = true;
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() << " try_next_mapping [ " << i << " ]" << std::endl;
#endif
m.local_port = port;
// prefer the same external port as the local port
if (m.external_port == 0) m.external_port = port;
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
ptime now = time_now();
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
m_log << " " << (i - m_mappings.begin()) << " [ "
"proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
<< " port: " << i->external_port
<< " local-port: " << i->local_port
<< " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
<< " ttl: " << total_seconds(i->expires - now)
<< " ]" << std::endl;
}
#endif
if (i < int(m_mappings.size()) - 1)
{
update_mapping(i + 1);
return;
}
std::vector<mapping_t>::iterator m = std::find_if(
m_mappings.begin(), m_mappings.end()
, boost::bind(&mapping_t::action, _1) != int(mapping_t::action_none));
if (m == m_mappings.end())
{
if (m_abort)
{
asio::error_code ec;
m_send_timer.cancel(ec);
m_socket.close(ec);
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << " done" << (m_abort?" shutting down":"") << std::endl;
#endif
return;
}
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << " updating " << (m - m_mappings.begin()) << std::endl;
#endif
update_mapping(m - m_mappings.begin());
}
void natpmp::update_mapping(int i)
{
natpmp::mapping_t& m = m_mappings[i];
if (m.action == mapping_t::action_none
|| m.protocol == none)
{
try_next_mapping(i);
return;
}
if (m_currently_mapping == -1)
{
@@ -156,7 +243,8 @@ void natpmp::send_map_request(int i)
TORRENT_ASSERT(m_currently_mapping == -1
|| m_currently_mapping == i);
m_currently_mapping = i;
mapping& m = m_mappings[i];
mapping_t& m = m_mappings[i];
TORRENT_ASSERT(m.action != mapping_t::action_none);
char buf[12];
char* out = buf;
write_uint8(0, out); // NAT-PMP version
@@ -164,14 +252,16 @@ void natpmp::send_map_request(int i)
write_uint16(0, out); // reserved
write_uint16(m.local_port, out); // private port
write_uint16(m.external_port, out); // requested public port
int ttl = m.external_port == 0 ? 0 : 3600;
int ttl = m.action == mapping_t::action_add ? 3600 : 0;
write_uint32(ttl, out); // port mapping lifetime
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string()
<< " ==> port map request: " << (m.protocol == 1 ? "udp" : "tcp")
<< " ==> port map ["
<< " action: " << (m.action == mapping_t::action_add ? "add" : "delete") << " "
<< " proto: " << (m.protocol == udp ? "udp" : "tcp")
<< " local: " << m.local_port << " external: " << m.external_port
<< " ttl: " << ttl << std::endl;
<< " ttl: " << ttl << " ]" << std::endl;
#endif
asio::error_code ec;
@@ -185,12 +275,16 @@ void natpmp::send_map_request(int i)
void natpmp::resend_request(int i, asio::error_code const& e)
{
if (e) return;
mutex_t::scoped_lock l(m_mutex);
if (m_currently_mapping != i) return;
if (m_retry_count >= 9)
{
m_mappings[i].need_update = false;
m_currently_mapping = -1;
m_mappings[i].action = mapping_t::action_none;
// try again in two hours
m_mappings[i].expires = time_now() + hours(2);
try_next_mapping(i);
return;
}
send_map_request(i);
@@ -209,12 +303,14 @@ void natpmp::on_reply(asio::error_code const& e
return;
}
mutex_t::scoped_lock l(m_mutex);
asio::error_code ec;
m_send_timer.cancel(ec);
TORRENT_ASSERT(m_currently_mapping >= 0);
int i = m_currently_mapping;
mapping& m = m_mappings[i];
mapping_t& m = m_mappings[i];
char* in = m_response_buffer;
int version = read_uint8(in);
@@ -229,9 +325,10 @@ void natpmp::on_reply(asio::error_code const& e
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string()
<< " <== port map response: " << (cmd - 128 == 1 ? "udp" : "tcp")
<< " <== port map ["
<< " protocol: " << (cmd - 128 == 1 ? "udp" : "tcp")
<< " local: " << private_port << " external: " << public_port
<< " ttl: " << lifetime << std::endl;
<< " ttl: " << lifetime << " ]" << std::endl;
#endif
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
@@ -259,7 +356,7 @@ void natpmp::on_reply(asio::error_code const& e
{
// this means the mapping was
// successfully closed
m.local_port = 0;
m.protocol = none;
}
else
{
@@ -282,21 +379,16 @@ void natpmp::on_reply(asio::error_code const& e
case 4: errmsg << "Out of resources"; break;
case 5: errmsg << "Unsupported opcode"; break;
}
m_mappings[i].expires = time_now() + hours(2);
m_callback(0, 0, errmsg.str());
m.expires = time_now() + hours(2);
m_callback(i, 0, errmsg.str());
}
else if (m.local_port != 0)
else if (m.action == mapping_t::action_add)
{
// don't report when we remove mappings
int tcp_port = 0;
int udp_port = 0;
if (m.protocol == 1) udp_port = m.external_port;
else tcp_port = public_port;
m_callback(tcp_port, udp_port, "");
m_callback(i, m.external_port, "");
}
m_currently_mapping = -1;
m_mappings[i].need_update = false;
m.action = mapping_t::action_none;
m_send_timer.cancel(ec);
update_expiration_timer();
try_next_mapping(i);
@@ -304,69 +396,95 @@ void natpmp::on_reply(asio::error_code const& e
void natpmp::update_expiration_timer()
{
if (m_abort) return;
ptime now = time_now();
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() << " update_expiration_timer " << std::endl;
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
m_log << " " << (i - m_mappings.begin()) << " [ "
"proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
<< " port: " << i->external_port
<< " local-port: " << i->local_port
<< " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
<< " ttl: " << total_seconds(i->expires - now)
<< " ]" << std::endl;
}
#endif
ptime min_expire = now + seconds(3600);
int min_index = -1;
for (int i = 0; i < num_mappings; ++i)
if (m_mappings[i].expires < min_expire
&& m_mappings[i].local_port != 0)
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
if (i->protocol == none
|| i->action != mapping_t::action_none) continue;
if (i->expires < min_expire)
{
min_expire = m_mappings[i].expires;
min_index = i;
min_expire = i->expires;
min_index = i - m_mappings.begin();
}
}
// this is already the mapping we're waiting for
if (m_next_refresh == min_index) return;
if (min_index >= 0)
{
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() << " next expiration ["
" i: " << min_index
<< " ttl: " << total_seconds(min_expire - time_now())
<< " ]" << std::endl;
#endif
asio::error_code ec;
if (m_next_refresh >= 0) m_refresh_timer.cancel(ec);
m_refresh_timer.expires_from_now(min_expire - now, ec);
m_refresh_timer.async_wait(bind(&natpmp::mapping_expired, self(), _1, min_index));
m_next_refresh = min_index;
}
}
void natpmp::mapping_expired(asio::error_code const& e, int i)
{
if (e) return;
mutex_t::scoped_lock l(m_mutex);
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << "*** mapping " << i << " expired, updating" << std::endl;
m_log << time_now_string() << " mapping expired [ i: " << i << " ]" << std::endl;
#endif
refresh_mapping(i);
}
void natpmp::refresh_mapping(int i)
{
m_mappings[i].need_update = true;
if (m_currently_mapping == -1)
{
// the socket is not currently in use
// send out a mapping request
m_retry_count = 0;
send_map_request(i);
m_socket.async_receive_from(asio::buffer(&m_response_buffer, 16)
, m_remote, bind(&natpmp::on_reply, self(), _1, _2));
}
}
void natpmp::try_next_mapping(int i)
{
++i;
if (i >= num_mappings) i = 0;
if (m_mappings[i].need_update)
refresh_mapping(i);
m_mappings[i].action = mapping_t::action_add;
if (m_next_refresh == i) m_next_refresh = -1;
update_mapping(i);
}
void natpmp::close()
{
mutex_t::scoped_lock l(m_mutex);
m_abort = true;
asio::error_code ec;
m_socket.close(ec);
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << time_now_string() << " close" << std::endl;
#endif
if (m_disabled) return;
for (int i = 0; i < num_mappings; ++i)
ptime now = time_now();
for (std::vector<mapping_t>::iterator i = m_mappings.begin()
, end(m_mappings.end()); i != end; ++i)
{
if (m_mappings[i].local_port == 0)
continue;
m_mappings[i].external_port = 0;
refresh_mapping(i);
#if defined(TORRENT_LOGGING) || defined(TORRENT_VERBOSE_LOGGING)
m_log << " " << (i - m_mappings.begin()) << " [ "
"proto: " << (i->protocol == none ? "none" : i->protocol == tcp ? "tcp" : "udp")
<< " port: " << i->external_port
<< " local-port: " << i->local_port
<< " action: " << (i->action == mapping_t::action_none ? "none" : i->action == mapping_t::action_add ? "add" : "delete")
<< " ttl: " << total_seconds(i->expires - now)
<< " ]" << std::endl;
#endif
if (i->protocol == none) continue;
i->action = mapping_t::action_delete;
}
m_refresh_timer.cancel(ec);
m_send_timer.cancel(ec);
update_mapping(0);
}