blob: a390df1a4f2c4123cfebb5285ba80ecb0ee39ebb [file] [log] [blame]
Adrien Bérauda1d294f2023-07-17 22:42:13 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "turn_cache.h"
18#include "fileutils.h"
19#include "turn_transport.h"
20
21#include <opendht/thread_pool.h>
22#include <opendht/logger.h>
23#include <fstream>
24
25namespace dhtnet {
26
27TurnCache::TurnCache(const std::string& accountId,
28 const std::string& cachePath,
29 const std::shared_ptr<asio::io_context>& io_ctx,
30 const std::shared_ptr<Logger>& logger,
31 const TurnTransportParams& params,
32 bool enabled)
33 : accountId_(accountId)
34 , cachePath_(cachePath)
François-Simon Fauteux-Chapleau25693412024-04-10 11:49:18 -040035 , params_(params)
36 , enabled_(enabled)
Adrien Bérauda1d294f2023-07-17 22:42:13 -040037 , io_context(io_ctx)
38 , logger_(logger)
39{
40 refreshTimer_ = std::make_unique<asio::steady_timer>(*io_context,
41 std::chrono::steady_clock::now());
42 onConnectedTimer_ = std::make_unique<asio::steady_timer>(*io_context,
43 std::chrono::steady_clock::now());
44}
45
46TurnCache::~TurnCache() {
47 {
Adrien Béraud024c46f2024-03-02 23:53:18 -050048 std::lock_guard lock(shutdownMtx_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -040049 if (refreshTimer_) {
50 refreshTimer_->cancel();
51 refreshTimer_.reset();
52 }
53 if (onConnectedTimer_) {
54 onConnectedTimer_->cancel();
55 onConnectedTimer_.reset();
56 }
57 }
58 {
Adrien Béraud024c46f2024-03-02 23:53:18 -050059 std::lock_guard lock(cachedTurnMutex_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -040060 testTurnV4_.reset();
61 testTurnV6_.reset();
62 cacheTurnV4_.reset();
63 cacheTurnV6_.reset();
64 }
65}
66
67std::optional<IpAddr>
68TurnCache::getResolvedTurn(uint16_t family) const
69{
François-Simon Fauteux-Chapleau25693412024-04-10 11:49:18 -040070 std::lock_guard lk(cachedTurnMutex_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -040071 if (family == AF_INET && cacheTurnV4_) {
72 return *cacheTurnV4_;
73 } else if (family == AF_INET6 && cacheTurnV6_) {
74 return *cacheTurnV6_;
75 }
76 return std::nullopt;
77}
78
79void
80TurnCache::reconfigure(const TurnTransportParams& params, bool enabled)
81{
82 params_ = params;
83 enabled_ = enabled;
84 {
Adrien Béraud024c46f2024-03-02 23:53:18 -050085 std::lock_guard lk(cachedTurnMutex_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -040086 // Force re-resolution
87 isRefreshing_ = false;
88 cacheTurnV4_.reset();
89 cacheTurnV6_.reset();
90 testTurnV4_.reset();
91 testTurnV6_.reset();
92 }
Adrien Béraud024c46f2024-03-02 23:53:18 -050093 std::lock_guard lock(shutdownMtx_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -040094 if (refreshTimer_) {
95 refreshTimer_->expires_at(std::chrono::steady_clock::now());
96 refreshTimer_->async_wait(std::bind(&TurnCache::refresh, shared_from_this(), std::placeholders::_1));
97 }
98}
99
100void
101TurnCache::refresh(const asio::error_code& ec)
102{
103 if (ec == asio::error::operation_aborted)
104 return;
105 // The resolution of the TURN server can take quite some time (if timeout).
106 // So, run this in its own io thread to avoid to block the main thread.
107 // Avoid multiple refresh
108 if (isRefreshing_.exchange(true))
109 return;
110 if (!enabled_) {
111 // In this case, we do not use any TURN server
Adrien Béraud024c46f2024-03-02 23:53:18 -0500112 std::lock_guard lk(cachedTurnMutex_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400113 cacheTurnV4_.reset();
114 cacheTurnV6_.reset();
115 isRefreshing_ = false;
116 return;
117 }
118
119 if(logger_) logger_->debug("[Account {}] Refresh cache for TURN server resolution", accountId_);
120 // Retrieve old cached value if available.
121 // This means that we directly get the correct value when launching the application on the
122 // same network
123 // No need to resolve, it's already a valid address
124 auto server = params_.domain;
125 if (IpAddr::isValid(server, AF_INET)) {
126 testTurn(IpAddr(server, AF_INET));
127 return;
128 } else if (IpAddr::isValid(server, AF_INET6)) {
129 testTurn(IpAddr(server, AF_INET6));
130 return;
131 }
132 // Else cache resolution result
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400133 fileutils::recursive_mkdir(cachePath_ / "domains", 0700);
134 auto pathV4 = cachePath_ / "domains" / ("v4." + server);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400135 IpAddr testV4, testV6;
136 if (auto turnV4File = std::ifstream(pathV4)) {
137 std::string content((std::istreambuf_iterator<char>(turnV4File)),
138 std::istreambuf_iterator<char>());
139 testV4 = IpAddr(content, AF_INET);
140 }
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400141 auto pathV6 = cachePath_ / "domains" / ("v6." + server);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400142 if (auto turnV6File = std::ifstream(pathV6)) {
143 std::string content((std::istreambuf_iterator<char>(turnV6File)),
144 std::istreambuf_iterator<char>());
145 testV6 = IpAddr(content, AF_INET6);
146 }
147 // Resolve just in case. The user can have a different connectivity
148 auto turnV4 = IpAddr {server, AF_INET};
149 {
150 if (turnV4) {
151 // Cache value to avoid a delay when starting up Jami
152 std::ofstream turnV4File(pathV4);
153 turnV4File << turnV4.toString();
154 } else
155 fileutils::remove(pathV4, true);
156 // Update TURN
157 testV4 = IpAddr(std::move(turnV4));
158 }
159 auto turnV6 = IpAddr {server, AF_INET6};
160 {
161 if (turnV6) {
162 // Cache value to avoid a delay when starting up Jami
163 std::ofstream turnV6File(pathV6);
164 turnV6File << turnV6.toString();
165 } else
166 fileutils::remove(pathV6, true);
167 // Update TURN
168 testV6 = IpAddr(std::move(turnV6));
169 }
170 if (testV4)
171 testTurn(testV4);
172 if (testV6)
173 testTurn(testV6);
174
175 refreshTurnDelay(!testV4 && !testV6);
176}
177
178void
179TurnCache::testTurn(IpAddr server)
180{
181 TurnTransportParams params = params_;
182 params.server = server;
Adrien Béraud024c46f2024-03-02 23:53:18 -0500183 std::lock_guard lk(cachedTurnMutex_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400184 auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_;
185 turn.reset(); // Stop previous TURN
186 try {
187 turn = std::make_unique<TurnTransport>(
188 params, [this, server](bool ok) {
189 // Stop server in an async job, because this callback can be called
190 // immediately and cachedTurnMutex_ must not be locked.
Adrien Béraud024c46f2024-03-02 23:53:18 -0500191 std::lock_guard lock(shutdownMtx_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400192 if (onConnectedTimer_) {
193 onConnectedTimer_->expires_at(std::chrono::steady_clock::now());
194 onConnectedTimer_->async_wait(std::bind(&TurnCache::onConnected, shared_from_this(), std::placeholders::_1, ok, server));
195 }
196 });
197 } catch (const std::exception& e) {
198 if(logger_) logger_->error("TurnTransport creation error: {}", e.what());
199 }
200}
201
202void
203TurnCache::onConnected(const asio::error_code& ec, bool ok, IpAddr server)
204{
205 if (ec == asio::error::operation_aborted)
206 return;
207
Adrien Béraud024c46f2024-03-02 23:53:18 -0500208 std::lock_guard lk(cachedTurnMutex_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400209 auto& cacheTurn = server.isIpv4() ? cacheTurnV4_ : cacheTurnV6_;
210 if (!ok) {
211 if(logger_) logger_->error("Connection to {:s} failed - reset", server.toString());
212 cacheTurn.reset();
213 } else {
214 if(logger_) logger_->debug("Connection to {:s} ready", server.toString());
215 cacheTurn = std::make_unique<IpAddr>(server);
216 }
217 refreshTurnDelay(!cacheTurnV6_ && !cacheTurnV4_);
218 if (auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_)
219 turn->shutdown();
220}
221
222
223void
224TurnCache::refreshTurnDelay(bool scheduleNext)
225{
226 isRefreshing_ = false;
227 if (scheduleNext) {
Adrien Béraud024c46f2024-03-02 23:53:18 -0500228 std::lock_guard lock(shutdownMtx_);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400229 if(logger_) logger_->warn("[Account {:s}] Cache for TURN resolution failed.", accountId_);
230 if (refreshTimer_) {
231 refreshTimer_->expires_at(std::chrono::steady_clock::now() + turnRefreshDelay_);
232 refreshTimer_->async_wait(std::bind(&TurnCache::refresh, shared_from_this(), std::placeholders::_1));
233 }
234 if (turnRefreshDelay_ < std::chrono::minutes(30))
235 turnRefreshDelay_ *= 2;
236 } else {
237 if(logger_) logger_->debug("[Account {:s}] Cache refreshed for TURN resolution", accountId_);
238 turnRefreshDelay_ = std::chrono::seconds(10);
239 }
240}
241
Sébastien Blin464bdff2023-07-19 08:02:53 -0400242} // namespace dhtnet