blob: 65862cbf4da58d51be9c3da00cd0f1063b74aa4c [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)
35 , io_context(io_ctx)
36 , logger_(logger)
37{
38 refreshTimer_ = std::make_unique<asio::steady_timer>(*io_context,
39 std::chrono::steady_clock::now());
40 onConnectedTimer_ = std::make_unique<asio::steady_timer>(*io_context,
41 std::chrono::steady_clock::now());
42}
43
44TurnCache::~TurnCache() {
45 {
46 std::lock_guard<std::mutex> lock(shutdownMtx_);
47 if (refreshTimer_) {
48 refreshTimer_->cancel();
49 refreshTimer_.reset();
50 }
51 if (onConnectedTimer_) {
52 onConnectedTimer_->cancel();
53 onConnectedTimer_.reset();
54 }
55 }
56 {
57 std::lock_guard<std::mutex> lock(cachedTurnMutex_);
58 testTurnV4_.reset();
59 testTurnV6_.reset();
60 cacheTurnV4_.reset();
61 cacheTurnV6_.reset();
62 }
63}
64
65std::optional<IpAddr>
66TurnCache::getResolvedTurn(uint16_t family) const
67{
68 if (family == AF_INET && cacheTurnV4_) {
69 return *cacheTurnV4_;
70 } else if (family == AF_INET6 && cacheTurnV6_) {
71 return *cacheTurnV6_;
72 }
73 return std::nullopt;
74}
75
76void
77TurnCache::reconfigure(const TurnTransportParams& params, bool enabled)
78{
79 params_ = params;
80 enabled_ = enabled;
81 {
82 std::lock_guard<std::mutex> lk(cachedTurnMutex_);
83 // Force re-resolution
84 isRefreshing_ = false;
85 cacheTurnV4_.reset();
86 cacheTurnV6_.reset();
87 testTurnV4_.reset();
88 testTurnV6_.reset();
89 }
90 std::lock_guard<std::mutex> lock(shutdownMtx_);
91 if (refreshTimer_) {
92 refreshTimer_->expires_at(std::chrono::steady_clock::now());
93 refreshTimer_->async_wait(std::bind(&TurnCache::refresh, shared_from_this(), std::placeholders::_1));
94 }
95}
96
97void
98TurnCache::refresh(const asio::error_code& ec)
99{
100 if (ec == asio::error::operation_aborted)
101 return;
102 // The resolution of the TURN server can take quite some time (if timeout).
103 // So, run this in its own io thread to avoid to block the main thread.
104 // Avoid multiple refresh
105 if (isRefreshing_.exchange(true))
106 return;
107 if (!enabled_) {
108 // In this case, we do not use any TURN server
109 std::lock_guard<std::mutex> lk(cachedTurnMutex_);
110 cacheTurnV4_.reset();
111 cacheTurnV6_.reset();
112 isRefreshing_ = false;
113 return;
114 }
115
116 if(logger_) logger_->debug("[Account {}] Refresh cache for TURN server resolution", accountId_);
117 // Retrieve old cached value if available.
118 // This means that we directly get the correct value when launching the application on the
119 // same network
120 // No need to resolve, it's already a valid address
121 auto server = params_.domain;
122 if (IpAddr::isValid(server, AF_INET)) {
123 testTurn(IpAddr(server, AF_INET));
124 return;
125 } else if (IpAddr::isValid(server, AF_INET6)) {
126 testTurn(IpAddr(server, AF_INET6));
127 return;
128 }
129 // Else cache resolution result
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400130 fileutils::recursive_mkdir(cachePath_ / "domains", 0700);
131 auto pathV4 = cachePath_ / "domains" / ("v4." + server);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400132 IpAddr testV4, testV6;
133 if (auto turnV4File = std::ifstream(pathV4)) {
134 std::string content((std::istreambuf_iterator<char>(turnV4File)),
135 std::istreambuf_iterator<char>());
136 testV4 = IpAddr(content, AF_INET);
137 }
Adrien Béraud2a4e73d2023-08-27 12:53:55 -0400138 auto pathV6 = cachePath_ / "domains" / ("v6." + server);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400139 if (auto turnV6File = std::ifstream(pathV6)) {
140 std::string content((std::istreambuf_iterator<char>(turnV6File)),
141 std::istreambuf_iterator<char>());
142 testV6 = IpAddr(content, AF_INET6);
143 }
144 // Resolve just in case. The user can have a different connectivity
145 auto turnV4 = IpAddr {server, AF_INET};
146 {
147 if (turnV4) {
148 // Cache value to avoid a delay when starting up Jami
149 std::ofstream turnV4File(pathV4);
150 turnV4File << turnV4.toString();
151 } else
152 fileutils::remove(pathV4, true);
153 // Update TURN
154 testV4 = IpAddr(std::move(turnV4));
155 }
156 auto turnV6 = IpAddr {server, AF_INET6};
157 {
158 if (turnV6) {
159 // Cache value to avoid a delay when starting up Jami
160 std::ofstream turnV6File(pathV6);
161 turnV6File << turnV6.toString();
162 } else
163 fileutils::remove(pathV6, true);
164 // Update TURN
165 testV6 = IpAddr(std::move(turnV6));
166 }
167 if (testV4)
168 testTurn(testV4);
169 if (testV6)
170 testTurn(testV6);
171
172 refreshTurnDelay(!testV4 && !testV6);
173}
174
175void
176TurnCache::testTurn(IpAddr server)
177{
178 TurnTransportParams params = params_;
179 params.server = server;
180 std::lock_guard<std::mutex> lk(cachedTurnMutex_);
181 auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_;
182 turn.reset(); // Stop previous TURN
183 try {
184 turn = std::make_unique<TurnTransport>(
185 params, [this, server](bool ok) {
186 // Stop server in an async job, because this callback can be called
187 // immediately and cachedTurnMutex_ must not be locked.
188 std::lock_guard<std::mutex> lock(shutdownMtx_);
189 if (onConnectedTimer_) {
190 onConnectedTimer_->expires_at(std::chrono::steady_clock::now());
191 onConnectedTimer_->async_wait(std::bind(&TurnCache::onConnected, shared_from_this(), std::placeholders::_1, ok, server));
192 }
193 });
194 } catch (const std::exception& e) {
195 if(logger_) logger_->error("TurnTransport creation error: {}", e.what());
196 }
197}
198
199void
200TurnCache::onConnected(const asio::error_code& ec, bool ok, IpAddr server)
201{
202 if (ec == asio::error::operation_aborted)
203 return;
204
205 std::lock_guard<std::mutex> lk(cachedTurnMutex_);
206 auto& cacheTurn = server.isIpv4() ? cacheTurnV4_ : cacheTurnV6_;
207 if (!ok) {
208 if(logger_) logger_->error("Connection to {:s} failed - reset", server.toString());
209 cacheTurn.reset();
210 } else {
211 if(logger_) logger_->debug("Connection to {:s} ready", server.toString());
212 cacheTurn = std::make_unique<IpAddr>(server);
213 }
214 refreshTurnDelay(!cacheTurnV6_ && !cacheTurnV4_);
215 if (auto& turn = server.isIpv4() ? testTurnV4_ : testTurnV6_)
216 turn->shutdown();
217}
218
219
220void
221TurnCache::refreshTurnDelay(bool scheduleNext)
222{
223 isRefreshing_ = false;
224 if (scheduleNext) {
225 std::lock_guard<std::mutex> lock(shutdownMtx_);
226 if(logger_) logger_->warn("[Account {:s}] Cache for TURN resolution failed.", accountId_);
227 if (refreshTimer_) {
228 refreshTimer_->expires_at(std::chrono::steady_clock::now() + turnRefreshDelay_);
229 refreshTimer_->async_wait(std::bind(&TurnCache::refresh, shared_from_this(), std::placeholders::_1));
230 }
231 if (turnRefreshDelay_ < std::chrono::minutes(30))
232 turnRefreshDelay_ *= 2;
233 } else {
234 if(logger_) logger_->debug("[Account {:s}] Cache refreshed for TURN resolution", accountId_);
235 turnRefreshDelay_ = std::chrono::seconds(10);
236 }
237}
238
Sébastien Blin464bdff2023-07-19 08:02:53 -0400239} // namespace dhtnet