blob: 0e36f46496137177bf85b67cba3808293468c811 [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_transport.h"
18#include "../sip_utils.h"
19
20#include <atomic>
21#include <thread>
Adrien Béraud1b7081a2023-07-18 10:31:45 -040022#include <mutex>
23#include <functional>
24#include <stdexcept>
Adrien Bérauda1d294f2023-07-17 22:42:13 -040025
Adrien Béraud9e0f84f2023-07-27 16:02:21 -040026extern "C" {
Adrien Bérauda1d294f2023-07-17 22:42:13 -040027#include <pjnath.h>
28#include <pjlib-util.h>
29#include <pjlib.h>
Adrien Béraud9e0f84f2023-07-27 16:02:21 -040030}
Adrien Bérauda1d294f2023-07-17 22:42:13 -040031
32#define TRY(ret) \
33 do { \
34 if ((ret) != PJ_SUCCESS) \
35 throw std::runtime_error(#ret " failed"); \
36 } while (0)
37
38namespace dhtnet {
39
40class TurnLock
41{
42 pj_grp_lock_t* lk_;
43
44public:
45 TurnLock(pj_turn_sock* sock)
46 : lk_(pj_turn_sock_get_grp_lock(sock))
47 {
48 lock();
49 }
50
51 ~TurnLock() { unlock(); }
52
53 void lock() { pj_grp_lock_add_ref(lk_); }
54
55 void unlock() { pj_grp_lock_dec_ref(lk_); }
56};
57
58class TurnTransport::Impl
59{
60public:
61 Impl(std::function<void(bool)>&& cb, const std::shared_ptr<Logger>& logger)
62 : cb_(std::move(cb)), logger_(logger) {}
63 ~Impl();
64
65 /**
66 * Detect new TURN state
67 */
68 void onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state);
69
70 /**
71 * Pool events from pjsip
72 */
73 void ioJob();
74
75 void start()
76 {
77 ioWorker = std::thread([this] { ioJob(); });
78 }
79
François-Simon Fauteux-Chapleau25693412024-04-10 11:49:18 -040080 void shutdown()
81 {
Adrien Béraud024c46f2024-03-02 23:53:18 -050082 std::lock_guard lock(shutdownMtx_);
François-Simon Fauteux-Chapleau25693412024-04-10 11:49:18 -040083 // The ioWorker thread must be stopped before caling pj_turn_sock_destroy,
84 // otherwise there's a potential race condition where pj_turn_sock_destroy
85 // sets the state of the TURN session to PJ_TURN_STATE_DESTROYING, and then
86 // ioWorker tries to execute a callback which expects the session to be in
François-Simon Fauteux-Chapleau72704fc2024-05-15 16:00:20 -040087 // an earlier state. See https://git.jami.net/savoirfairelinux/dhtnet/-/issues/27
François-Simon Fauteux-Chapleau25693412024-04-10 11:49:18 -040088 if (ioWorker.joinable()) {
89 stopped_ = true;
90 ioWorker.join();
91 }
Adrien Bérauda1d294f2023-07-17 22:42:13 -040092 if (relay) {
93 pj_turn_sock_destroy(relay);
François-Simon Fauteux-Chapleau72704fc2024-05-15 16:00:20 -040094 // Calling pj_turn_sock_destroy doesn't (necessarily) immediately close the
95 // socket; as mentioned in PJSIP's documentation, the operation may be performed
96 // asynchronously, which is why we need to call the two polling functions below.
97 // https://docs.pjsip.org/en/latest/api/generated/pjnath/group/group__PJNATH__TURN__SOCK.html
98 const pj_time_val delay = {0, 20};
99 pj_ioqueue_poll(stunConfig.ioqueue, &delay);
100 pj_timer_heap_poll(stunConfig.timer_heap, nullptr);
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400101 relay = nullptr;
102 }
103 turnLock.reset();
François-Simon Fauteux-Chapleau72704fc2024-05-15 16:00:20 -0400104 if (stunConfig.timer_heap) {
105 pj_timer_heap_destroy(stunConfig.timer_heap);
106 stunConfig.timer_heap = nullptr;
107 }
108 if (stunConfig.ioqueue) {
109 pj_ioqueue_destroy(stunConfig.ioqueue);
110 stunConfig.ioqueue = nullptr;
111 }
112 if (pool) {
113 pj_pool_release(pool);
114 pool = nullptr;
115 }
116 pj_pool_factory_dump(&poolCache.factory, PJ_TRUE);
117 pj_caching_pool_destroy(&poolCache);
François-Simon Fauteux-Chapleau25693412024-04-10 11:49:18 -0400118 }
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400119
120 TurnTransportParams settings;
121
122 pj_caching_pool poolCache {};
123 pj_pool_t* pool {nullptr};
124 pj_stun_config stunConfig {};
125 pj_turn_sock* relay {nullptr};
126 std::unique_ptr<TurnLock> turnLock;
127 pj_str_t relayAddr {};
128 IpAddr peerRelayAddr; // address where peers should connect to
129 IpAddr mappedAddr;
130 std::function<void(bool)> cb_;
131
132 std::thread ioWorker;
133 std::atomic_bool stopped_ {false};
134 std::atomic_bool cbCalled_ {false};
135 std::mutex shutdownMtx_;
136 std::shared_ptr<Logger> logger_;
137};
138
139TurnTransport::Impl::~Impl()
140{
141 shutdown();
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400142}
143
144void
145TurnTransport::Impl::onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state)
146{
147 if (new_state == PJ_TURN_STATE_DESTROYING) {
148 stopped_ = true;
149 return;
150 }
151
152 if (new_state == PJ_TURN_STATE_READY) {
153 pj_turn_session_info info;
154 pj_turn_sock_get_info(relay, &info);
155 peerRelayAddr = IpAddr {info.relay_addr};
156 mappedAddr = IpAddr {info.mapped_addr};
157 if(logger_) logger_->debug("TURN server ready, peer relay address: {:s}",
158 peerRelayAddr.toString(true, true).c_str());
159 cbCalled_ = true;
160 cb_(true);
161 } else if (old_state <= PJ_TURN_STATE_READY and new_state > PJ_TURN_STATE_READY and not cbCalled_) {
162 if(logger_) logger_->debug("TURN server disconnected ({:s})", pj_turn_state_name(new_state));
163 cb_(false);
164 }
165}
166
167void
168TurnTransport::Impl::ioJob()
169{
170 const pj_time_val delay = {0, 10};
171 while (!stopped_) {
172 pj_ioqueue_poll(stunConfig.ioqueue, &delay);
173 pj_timer_heap_poll(stunConfig.timer_heap, nullptr);
174 }
175}
176
177TurnTransport::TurnTransport(const TurnTransportParams& params, std::function<void(bool)>&& cb, const std::shared_ptr<Logger>& logger)
François-Simon Fauteux-Chapleau2ef5b662024-03-27 12:30:26 -0400178 : pjInitLock_()
179 , pimpl_ {new Impl(std::move(cb), logger)}
Adrien Bérauda1d294f2023-07-17 22:42:13 -0400180{
181 auto server = params.server;
182 if (!server.getPort())
183 server.setPort(PJ_STUN_PORT);
184 if (server.isUnspecified())
185 throw std::invalid_argument("invalid turn server address");
186 pimpl_->settings = params;
187 // PJSIP memory pool
188 pj_caching_pool_init(&pimpl_->poolCache, &pj_pool_factory_default_policy, 0);
189 pimpl_->pool = pj_pool_create(&pimpl_->poolCache.factory, "TurnTransport", 512, 512, nullptr);
190 if (not pimpl_->pool)
191 throw std::runtime_error("pj_pool_create() failed");
192 // STUN config
193 pj_stun_config_init(&pimpl_->stunConfig, &pimpl_->poolCache.factory, 0, nullptr, nullptr);
194 // create global timer heap
195 TRY(pj_timer_heap_create(pimpl_->pool, 1000, &pimpl_->stunConfig.timer_heap));
196 // create global ioqueue
197 TRY(pj_ioqueue_create(pimpl_->pool, 16, &pimpl_->stunConfig.ioqueue));
198 // TURN callbacks
199 pj_turn_sock_cb relay_cb;
200 pj_bzero(&relay_cb, sizeof(relay_cb));
201 relay_cb.on_state =
202 [](pj_turn_sock* relay, pj_turn_state_t old_state, pj_turn_state_t new_state) {
203 auto pimpl = static_cast<Impl*>(pj_turn_sock_get_user_data(relay));
204 pimpl->onTurnState(old_state, new_state);
205 };
206 // TURN socket config
207 pj_turn_sock_cfg turn_sock_cfg;
208 pj_turn_sock_cfg_default(&turn_sock_cfg);
209 turn_sock_cfg.max_pkt_size = 4096;
210 // TURN socket creation
211 TRY(pj_turn_sock_create(&pimpl_->stunConfig,
212 server.getFamily(),
213 PJ_TURN_TP_TCP,
214 &relay_cb,
215 &turn_sock_cfg,
216 &*this->pimpl_,
217 &pimpl_->relay));
218 // TURN allocation setup
219 pj_turn_alloc_param turn_alloc_param;
220 pj_turn_alloc_param_default(&turn_alloc_param);
221 turn_alloc_param.peer_conn_type = PJ_TURN_TP_TCP;
222 pj_stun_auth_cred cred;
223 pj_bzero(&cred, sizeof(cred));
224 cred.type = PJ_STUN_AUTH_CRED_STATIC;
225 pj_cstr(&cred.data.static_cred.realm, pimpl_->settings.realm.c_str());
226 pj_cstr(&cred.data.static_cred.username, pimpl_->settings.username.c_str());
227 cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
228 pj_cstr(&cred.data.static_cred.data, pimpl_->settings.password.c_str());
229 pimpl_->relayAddr = pj_strdup3(pimpl_->pool, server.toString().c_str());
230 // TURN connection/allocation
231 if (logger) logger->debug("Connecting to TURN {:s}", server.toString(true, true));
232 TRY(pj_turn_sock_alloc(pimpl_->relay,
233 &pimpl_->relayAddr,
234 server.getPort(),
235 nullptr,
236 &cred,
237 &turn_alloc_param));
238 pimpl_->turnLock = std::make_unique<TurnLock>(pimpl_->relay);
239 pimpl_->start();
240}
241
242TurnTransport::~TurnTransport() {}
243
244void
245TurnTransport::shutdown()
246{
247 pimpl_->shutdown();
248}
249
Sébastien Blin464bdff2023-07-19 08:02:53 -0400250} // namespace dhtnet