Skip to content

Commit

Permalink
fix: fixes and improvements to global IP query (#5510)
Browse files Browse the repository at this point in the history
  • Loading branch information
tearfur committed Jun 11, 2023
1 parent c8e84f8 commit 802619e
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 180 deletions.
185 changes: 100 additions & 85 deletions libtransmission/global-ip-cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,37 @@ namespace

using namespace std::literals;

auto constexpr IPQueryServices = std::array{ "https://icanhazip.com"sv, "https://api64.ipify.org"sv };
static_assert(TR_AF_INET == 0);
static_assert(TR_AF_INET6 == 1);

auto constexpr protocol_str(tr_address_type type) noexcept
{
/* TODO: very slight performance nit
* - If upgrading to C++20, change Map to a consteval lambda:
* auto map = []() consteval { return std::array{ "IPv4"sv, "IPv6"sv }; };
* - If upgrading to C++23, change Map to static constexpr
*
* Ref: https://wg21.link/p2647r1
*/
auto constexpr Map = std::array{ "IPv4"sv, "IPv6"sv };
return Map[type];
}

auto constexpr IPQueryServices = std::array{ std::array{ "https://ip4.transmissionbt.com/"sv },
std::array{ "https://ip6.transmissionbt.com/"sv } };

auto constexpr UpkeepInterval = 30min;
auto constexpr RetryUpkeepInterval = 30s;

} // namespace

namespace
{
// Functions contained in external_source_ip_helpers are modified from code
// Functions contained in global_source_ip_helpers are modified from code
// by Juliusz Chroboczek and is covered under the same license as dht.cc.
// Please feel free to copy them into your software if it can help
// unbreaking the double-stack Internet.
namespace external_source_ip_helpers
namespace global_source_ip_helpers
{

// Get the source address used for a given destination address.
Expand Down Expand Up @@ -109,12 +127,12 @@ namespace external_source_ip_helpers
return {};
}

} // namespace external_source_ip_helpers
} // namespace global_source_ip_helpers
} // namespace

tr_global_ip_cache::tr_global_ip_cache(tr_web& web_in, libtransmission::TimerMaker& timer_maker_in)
: web_{ web_in }
, upkeep_timers_{ timer_maker_in.create(), timer_maker_in.create() }
tr_global_ip_cache::tr_global_ip_cache(Mediator& mediator_in)
: mediator_{ mediator_in }
, upkeep_timers_{ mediator_in.timer_maker().create(), mediator_in.timer_maker().create() }
{
static_assert(TR_AF_INET == 0);
static_assert(TR_AF_INET6 == 1);
Expand All @@ -132,6 +150,11 @@ tr_global_ip_cache::tr_global_ip_cache(tr_web& web_in, libtransmission::TimerMak
}
}

std::unique_ptr<tr_global_ip_cache> tr_global_ip_cache::create(tr_global_ip_cache::Mediator& mediator_in)
{
return std::unique_ptr<tr_global_ip_cache>(new tr_global_ip_cache(mediator_in));
}

tr_global_ip_cache::~tr_global_ip_cache()
{
// Destroying mutex while someone owns it is undefined behaviour, so we acquire it first
Expand Down Expand Up @@ -164,37 +187,21 @@ bool tr_global_ip_cache::try_shutdown() noexcept
return true;
}

void tr_global_ip_cache::set_settings_bind_addr(tr_address_type type, std::string_view bind_address) noexcept
{
settings_bind_addr_[type] = tr_address::from_string(bind_address);
if (settings_bind_addr_[type] && type != settings_bind_addr_[type]->type)
{
settings_bind_addr_[type].reset();
}
update_addr(type);
}

tr_address tr_global_ip_cache::bind_addr(tr_address_type type) const noexcept
{
if (type == TR_AF_INET || type == TR_AF_INET6)
{
return settings_bind_addr_[type].value_or(type == TR_AF_INET ? tr_address::any_ipv4() : tr_address::any_ipv6());
if (auto const addr = tr_address::from_string(mediator_.settings_bind_addr(type)); addr && type == addr->type)
{
return *addr;
}
return type == TR_AF_INET ? tr_address::any_ipv4() : tr_address::any_ipv6();
}

TR_ASSERT_MSG(false, "invalid type");
return {};
}

void tr_global_ip_cache::update_addr(tr_address_type type) noexcept
{
update_source_addr(type);
/* TODO: Temporarily disable because there is currently no way for this to work without using third party services */
// if (global_source_addr(type))
// {
// update_global_addr(type);
// }
}

bool tr_global_ip_cache::set_global_addr(tr_address_type type, tr_address const& addr) noexcept
{
if (type == addr.type && addr.is_global_unicast_address())
Expand All @@ -207,60 +214,19 @@ bool tr_global_ip_cache::set_global_addr(tr_address_type type, tr_address const&
return false;
}

void tr_global_ip_cache::unset_global_addr(tr_address_type type) noexcept
{
auto const lock = std::lock_guard{ global_addr_mutex_[type] };
global_addr_[type].reset();
tr_logAddTrace(fmt::format("Unset {} global address cache", type == TR_AF_INET ? "IPv4"sv : "IPv6"sv));
}

void tr_global_ip_cache::set_source_addr(tr_address const& addr) noexcept
{
auto const lock = std::lock_guard{ source_addr_mutex_[addr.type] };
source_addr_[addr.type] = addr;
tr_logAddTrace(fmt::format("Cached source address {}", addr.display_name()));
}

void tr_global_ip_cache::unset_addr(tr_address_type type) noexcept
{
auto const lock = std::lock_guard{ source_addr_mutex_[type] };
source_addr_[type].reset();
tr_logAddTrace(fmt::format("Unset {} source address cache", type == TR_AF_INET ? "IPv4"sv : "IPv6"sv));

// No public internet connectivity means no global IP address
unset_global_addr(type);
}

bool tr_global_ip_cache::set_is_updating(tr_address_type type) noexcept
void tr_global_ip_cache::update_addr(tr_address_type type) noexcept
{
auto lock = std::unique_lock{ is_updating_mutex_[type] };
is_updating_cv_[type].wait(
lock,
[this, type]() { return is_updating_[type] == is_updating_t::NO || is_updating_[type] == is_updating_t::ABORT; });
if (is_updating_[type] != is_updating_t::NO)
update_source_addr(type);
if (global_source_addr(type))
{
return false;
update_global_addr(type);
}
is_updating_[type] = is_updating_t::YES;
lock.unlock();
is_updating_cv_[type].notify_one();
return true;
}

void tr_global_ip_cache::unset_is_updating(tr_address_type type) noexcept
{
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
auto lock = std::unique_lock{ is_updating_mutex_[type] };
is_updating_[type] = is_updating_t::NO;
lock.unlock();
is_updating_cv_[type].notify_one();
}

void tr_global_ip_cache::update_global_addr(tr_address_type type) noexcept
{
TR_ASSERT(has_ip_protocol_[type]);
TR_ASSERT(global_source_addr(type));
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices));
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices[type]));

if (ix_service_[type] == 0U && !set_is_updating(type))
{
Expand All @@ -269,19 +235,19 @@ void tr_global_ip_cache::update_global_addr(tr_address_type type) noexcept
TR_ASSERT(is_updating_[type] == is_updating_t::YES);

// Update global address
auto options = tr_web::FetchOptions{ IPQueryServices[ix_service_[type]],
auto options = tr_web::FetchOptions{ IPQueryServices[type][ix_service_[type]],
[this, type](tr_web::FetchResponse const& response)
{ this->on_response_ip_query(type, response); },
nullptr };
options.ip_proto = type == TR_AF_INET ? tr_web::FetchOptions::IPProtocol::V4 : tr_web::FetchOptions::IPProtocol::V6;
options.sndbuf = 4096;
options.rcvbuf = 4096;
web_.fetch(std::move(options));
mediator_.fetch(std::move(options));
}

void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
{
using namespace external_source_ip_helpers;
using namespace global_source_ip_helpers;

TR_ASSERT(has_ip_protocol_[type]);

Expand All @@ -291,9 +257,9 @@ void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
}
TR_ASSERT(is_updating_[type] == is_updating_t::YES);

auto const protocol = type == TR_AF_INET ? "IPv4"sv : "IPv6"sv;
auto const protocol = protocol_str(type);

auto err = int{};
auto err = int{ 0 };
auto const& source_addr = get_global_source_address(bind_addr(type), err);
if (source_addr)
{
Expand All @@ -308,7 +274,7 @@ void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
// Stop the update process since we have no public internet connectivity
unset_addr(type);
upkeep_timers_[type]->set_interval(RetryUpkeepInterval);
tr_logAddDebug(fmt::format(_("Couldn't obtain source {protocol} address"), fmt::arg("protocol", protocol)));
tr_logAddDebug(fmt::format("Couldn't obtain source {} address", protocol));
if (err == EAFNOSUPPORT)
{
stop_timer(type); // No point in retrying
Expand All @@ -323,10 +289,10 @@ void tr_global_ip_cache::update_source_addr(tr_address_type type) noexcept
void tr_global_ip_cache::on_response_ip_query(tr_address_type type, tr_web::FetchResponse const& response) noexcept
{
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices));
TR_ASSERT(ix_service_[type] < std::size(IPQueryServices[type]));

auto const protocol = type == TR_AF_INET ? "IPv4"sv : "IPv6"sv;
auto success = bool{ false };
auto const protocol = protocol_str(type);
auto success = false;

if (response.status == 200 /* HTTP_OK */)
{
Expand All @@ -340,12 +306,12 @@ void tr_global_ip_cache::on_response_ip_query(tr_address_type type, tr_web::Fetc
_("Successfully updated global {type} address to {ip} using {url}"),
fmt::arg("type", protocol),
fmt::arg("ip", addr->display_name()),
fmt::arg("url", IPQueryServices[ix_service_[type]])));
fmt::arg("url", IPQueryServices[type][ix_service_[type]])));
}
}

// Try next IP query URL
if (!success && ++ix_service_[type] < std::size(IPQueryServices))
if (!success && ++ix_service_[type] < std::size(IPQueryServices[type]))
{
update_global_addr(type);
return;
Expand All @@ -361,3 +327,52 @@ void tr_global_ip_cache::on_response_ip_query(tr_address_type type, tr_web::Fetc
ix_service_[type] = 0U;
unset_is_updating(type);
}

void tr_global_ip_cache::unset_global_addr(tr_address_type type) noexcept
{
auto const lock = std::lock_guard{ global_addr_mutex_[type] };
global_addr_[type].reset();
tr_logAddTrace(fmt::format("Unset {} global address cache", protocol_str(type)));
}

void tr_global_ip_cache::set_source_addr(tr_address const& addr) noexcept
{
auto const lock = std::lock_guard{ source_addr_mutex_[addr.type] };
source_addr_[addr.type] = addr;
tr_logAddTrace(fmt::format("Cached source address {}", addr.display_name()));
}

void tr_global_ip_cache::unset_addr(tr_address_type type) noexcept
{
auto const lock = std::lock_guard{ source_addr_mutex_[type] };
source_addr_[type].reset();
tr_logAddTrace(fmt::format("Unset {} source address cache", protocol_str(type)));

// No public internet connectivity means no global IP address
unset_global_addr(type);
}

bool tr_global_ip_cache::set_is_updating(tr_address_type type) noexcept
{
auto lock = std::unique_lock{ is_updating_mutex_[type] };
is_updating_cv_[type].wait(
lock,
[this, type]() { return is_updating_[type] == is_updating_t::NO || is_updating_[type] == is_updating_t::ABORT; });
if (is_updating_[type] != is_updating_t::NO)
{
return false;
}
is_updating_[type] = is_updating_t::YES;
lock.unlock();
is_updating_cv_[type].notify_one();
return true;
}

void tr_global_ip_cache::unset_is_updating(tr_address_type type) noexcept
{
TR_ASSERT(is_updating_[type] == is_updating_t::YES);
auto lock = std::unique_lock{ is_updating_mutex_[type] };
is_updating_[type] = is_updating_t::NO;
lock.unlock();
is_updating_cv_[type].notify_one();
}
47 changes: 32 additions & 15 deletions libtransmission/global-ip-cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <string>
#include <string_view>

#include "libtransmission/net.h"
Expand All @@ -34,13 +33,34 @@
*
* The idea is, if this class successfully cached a source address, that means
* you have connectivity to the public internet. And if the global address is
* the same with the source address, then you are not behind an NAT.
* the same as the source address, then you are not behind a NAT.
*
*/
class tr_global_ip_cache
{
public:
tr_global_ip_cache(tr_web& web_in, libtransmission::TimerMaker& timer_maker_in);
struct Mediator
{
virtual ~Mediator() = default;

virtual void fetch(tr_web::FetchOptions&& /* options */)
{
}

[[nodiscard]] virtual std::string_view settings_bind_addr(tr_address_type /* type */)
{
return {};
}

[[nodiscard]] virtual libtransmission::TimerMaker& timer_maker() = 0;
};

private:
explicit tr_global_ip_cache(Mediator& mediator_in);

public:
[[nodiscard]] static std::unique_ptr<tr_global_ip_cache> create(Mediator& mediator_in);

tr_global_ip_cache() = delete;
~tr_global_ip_cache();
tr_global_ip_cache(tr_global_ip_cache const&) = delete;
Expand All @@ -62,12 +82,17 @@ class tr_global_ip_cache
return source_addr_[type];
}

void set_settings_bind_addr(tr_address_type type, std::string_view bind_address) noexcept;
[[nodiscard]] tr_address bind_addr(tr_address_type type) const noexcept;

void update_addr(tr_address_type type) noexcept;
bool set_global_addr(tr_address_type type, tr_address const& addr) noexcept;

void update_addr(tr_address_type type) noexcept;
void update_global_addr(tr_address_type type) noexcept;
void update_source_addr(tr_address_type type) noexcept;

// Only use as a callback for web_->fetch()
void on_response_ip_query(tr_address_type type, tr_web::FetchResponse const& response) noexcept;

[[nodiscard]] constexpr auto has_ip_protocol(tr_address_type type) const noexcept
{
return has_ip_protocol_[type];
Expand All @@ -94,19 +119,11 @@ class tr_global_ip_cache
[[nodiscard]] bool set_is_updating(tr_address_type type) noexcept;
void unset_is_updating(tr_address_type type) noexcept;

void update_global_addr(tr_address_type type) noexcept;
void update_source_addr(tr_address_type type) noexcept;

// Only use as a callback for web_->fetch()
void on_response_ip_query(tr_address_type type, tr_web::FetchResponse const& response) noexcept;

tr_web& web_;

array_ip_t<std::optional<tr_address>> settings_bind_addr_;
Mediator& mediator_;

enum class is_updating_t
{
NO,
NO = 0,
YES,
ABORT
};
Expand Down
Loading

0 comments on commit 802619e

Please sign in to comment.