// This file Copyright © 2008-2022 Mnemosyne LLC. // It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), // or any future license endorsed by Mnemosyne LLC. // License text can be found in the licenses/ folder. #include #include #include #include #define LIBTRANSMISSION_PORT_FORWARDING_MODULE #include "transmission.h" #include "log.h" #include "net.h" #include "port-forwarding-natpmp.h" #include "port-forwarding-upnp.h" #include "port-forwarding.h" #include "session.h" #include "timer.h" #include "torrent.h" #include "tr-assert.h" #include "utils.h" // for _() using namespace std::literals; class tr_port_forwarding_impl final : public tr_port_forwarding { public: explicit tr_port_forwarding_impl(tr_session& session) : session_{ session } , timer_maker_{ session.timerMaker() } { } ~tr_port_forwarding_impl() override { is_shutting_down_ = true; stopForwarding(); } tr_port_forwarding_impl(tr_port_forwarding_impl&&) = delete; tr_port_forwarding_impl(tr_port_forwarding_impl const&) = delete; tr_port_forwarding_impl& operator=(tr_port_forwarding_impl&&) = delete; tr_port_forwarding_impl& operator=(tr_port_forwarding_impl const&) = delete; void portChanged() override { if (!is_enabled_) { return; } stopTimer(); natPulse(false); startTimer(); } void setEnabled(bool enabled) override { if (enabled) { is_enabled_ = true; startTimer(); } else { is_enabled_ = false; stopForwarding(); } } [[nodiscard]] bool isEnabled() const noexcept override { return is_enabled_; } [[nodiscard]] tr_port_forwarding_state state() const noexcept override { return std::max(natpmp_state_, upnp_state_); } private: void stopTimer() { timer_.reset(); } void stopForwarding() { tr_logAddTrace("stopped"); natPulse(false); natpmp_.reset(); natpmp_state_ = TR_PORT_UNMAPPED; tr_upnpClose(upnp_); upnp_ = nullptr; upnp_state_ = TR_PORT_UNMAPPED; stopTimer(); } void startTimer() { timer_ = timer_maker_.create([this]() { this->onTimer(); }); restartTimer(); } void restartTimer() { if (!timer_) { return; } // when to wake up again switch (state()) { case TR_PORT_MAPPED: // if we're mapped, everything is fine... check back at `renew_time` // to renew the port forwarding if it's expired do_port_check_ = true; if (auto const now = tr_time(); natpmp_->renewTime() > now) { timer_->startSingleShot(std::chrono::seconds{ natpmp_->renewTime() - now }); } else // ??? { timer_->startSingleShot(1min); } break; case TR_PORT_ERROR: // some kind of an error. wait a minute and retry timer_->startSingleShot(1min); break; default: // in progress. pulse frequently. timer_->startSingleShot(333ms); break; } } void onTimer() { TR_ASSERT(timer_); // do something natPulse(do_port_check_); do_port_check_ = false; // set up the timer for the next pulse restartTimer(); } static constexpr char const* getNatStateStr(int state) { switch (state) { case TR_PORT_MAPPING: return _("Starting"); case TR_PORT_MAPPED: return _("Forwarded"); case TR_PORT_UNMAPPING: return _("Stopping"); case TR_PORT_UNMAPPED: return _("Not forwarded"); default: return "???"; } } void natPulse(bool do_check) { auto& session = session_; auto const is_enabled = is_enabled_ && !is_shutting_down_; if (!natpmp_) { natpmp_ = std::make_unique(); } if (upnp_ == nullptr) { upnp_ = tr_upnpInit(); } auto const old_state = state(); auto const result = natpmp_->pulse(session.private_peer_port, is_enabled); natpmp_state_ = result.state; if (!std::empty(result.public_port) && !std::empty(result.private_port)) { session.public_peer_port = result.public_port; session.private_peer_port = result.private_port; tr_logAddInfo(fmt::format( _("Mapped private port {private_port} to public port {public_port}"), fmt::arg("public_port", session.public_peer_port.host()), fmt::arg("private_port", session.private_peer_port.host()))); } upnp_state_ = tr_upnpPulse(upnp_, session.private_peer_port, is_enabled, do_check, session.bind_ipv4.readable()); if (auto const new_state = state(); new_state != old_state) { tr_logAddInfo(fmt::format( _("State changed from '{old_state}' to '{state}'"), fmt::arg("old_state", getNatStateStr(old_state)), fmt::arg("state", getNatStateStr(new_state)))); } } tr_session& session_; libtransmission::TimerMaker& timer_maker_; bool is_enabled_ = false; bool is_shutting_down_ = false; bool do_port_check_ = false; tr_port_forwarding_state natpmp_state_ = TR_PORT_UNMAPPED; tr_port_forwarding_state upnp_state_ = TR_PORT_UNMAPPED; tr_upnp* upnp_ = nullptr; std::unique_ptr natpmp_; std::unique_ptr timer_; }; std::unique_ptr tr_port_forwarding::create(tr_session& session) { return std::make_unique(session); }