blob: bf114c72b48204c987f70dad1902d633d3c38574 [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
26#include <pjnath.h>
27#include <pjlib-util.h>
28#include <pjlib.h>
29
30#define TRY(ret) \
31 do { \
32 if ((ret) != PJ_SUCCESS) \
33 throw std::runtime_error(#ret " failed"); \
34 } while (0)
35
36namespace dhtnet {
37
38class TurnLock
39{
40 pj_grp_lock_t* lk_;
41
42public:
43 TurnLock(pj_turn_sock* sock)
44 : lk_(pj_turn_sock_get_grp_lock(sock))
45 {
46 lock();
47 }
48
49 ~TurnLock() { unlock(); }
50
51 void lock() { pj_grp_lock_add_ref(lk_); }
52
53 void unlock() { pj_grp_lock_dec_ref(lk_); }
54};
55
56class TurnTransport::Impl
57{
58public:
59 Impl(std::function<void(bool)>&& cb, const std::shared_ptr<Logger>& logger)
60 : cb_(std::move(cb)), logger_(logger) {}
61 ~Impl();
62
63 /**
64 * Detect new TURN state
65 */
66 void onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state);
67
68 /**
69 * Pool events from pjsip
70 */
71 void ioJob();
72
73 void start()
74 {
75 ioWorker = std::thread([this] { ioJob(); });
76 }
77
78 void shutdown() {
79 std::lock_guard<std::mutex> lock(shutdownMtx_);
80 if (relay) {
81 pj_turn_sock_destroy(relay);
82 relay = nullptr;
83 }
84 turnLock.reset();
85 if (ioWorker.joinable())
86 ioWorker.join();
87 }
88
89 TurnTransportParams settings;
90
91 pj_caching_pool poolCache {};
92 pj_pool_t* pool {nullptr};
93 pj_stun_config stunConfig {};
94 pj_turn_sock* relay {nullptr};
95 std::unique_ptr<TurnLock> turnLock;
96 pj_str_t relayAddr {};
97 IpAddr peerRelayAddr; // address where peers should connect to
98 IpAddr mappedAddr;
99 std::function<void(bool)> cb_;
100
101 std::thread ioWorker;
102 std::atomic_bool stopped_ {false};
103 std::atomic_bool cbCalled_ {false};
104 std::mutex shutdownMtx_;
105 std::shared_ptr<Logger> logger_;
106};
107
108TurnTransport::Impl::~Impl()
109{
110 shutdown();
111 pj_caching_pool_destroy(&poolCache);
112}
113
114void
115TurnTransport::Impl::onTurnState(pj_turn_state_t old_state, pj_turn_state_t new_state)
116{
117 if (new_state == PJ_TURN_STATE_DESTROYING) {
118 stopped_ = true;
119 return;
120 }
121
122 if (new_state == PJ_TURN_STATE_READY) {
123 pj_turn_session_info info;
124 pj_turn_sock_get_info(relay, &info);
125 peerRelayAddr = IpAddr {info.relay_addr};
126 mappedAddr = IpAddr {info.mapped_addr};
127 if(logger_) logger_->debug("TURN server ready, peer relay address: {:s}",
128 peerRelayAddr.toString(true, true).c_str());
129 cbCalled_ = true;
130 cb_(true);
131 } else if (old_state <= PJ_TURN_STATE_READY and new_state > PJ_TURN_STATE_READY and not cbCalled_) {
132 if(logger_) logger_->debug("TURN server disconnected ({:s})", pj_turn_state_name(new_state));
133 cb_(false);
134 }
135}
136
137void
138TurnTransport::Impl::ioJob()
139{
140 const pj_time_val delay = {0, 10};
141 while (!stopped_) {
142 pj_ioqueue_poll(stunConfig.ioqueue, &delay);
143 pj_timer_heap_poll(stunConfig.timer_heap, nullptr);
144 }
145}
146
147TurnTransport::TurnTransport(const TurnTransportParams& params, std::function<void(bool)>&& cb, const std::shared_ptr<Logger>& logger)
148 : pimpl_ {new Impl(std::move(cb), logger)}
149{
150 auto server = params.server;
151 if (!server.getPort())
152 server.setPort(PJ_STUN_PORT);
153 if (server.isUnspecified())
154 throw std::invalid_argument("invalid turn server address");
155 pimpl_->settings = params;
156 // PJSIP memory pool
157 pj_caching_pool_init(&pimpl_->poolCache, &pj_pool_factory_default_policy, 0);
158 pimpl_->pool = pj_pool_create(&pimpl_->poolCache.factory, "TurnTransport", 512, 512, nullptr);
159 if (not pimpl_->pool)
160 throw std::runtime_error("pj_pool_create() failed");
161 // STUN config
162 pj_stun_config_init(&pimpl_->stunConfig, &pimpl_->poolCache.factory, 0, nullptr, nullptr);
163 // create global timer heap
164 TRY(pj_timer_heap_create(pimpl_->pool, 1000, &pimpl_->stunConfig.timer_heap));
165 // create global ioqueue
166 TRY(pj_ioqueue_create(pimpl_->pool, 16, &pimpl_->stunConfig.ioqueue));
167 // TURN callbacks
168 pj_turn_sock_cb relay_cb;
169 pj_bzero(&relay_cb, sizeof(relay_cb));
170 relay_cb.on_state =
171 [](pj_turn_sock* relay, pj_turn_state_t old_state, pj_turn_state_t new_state) {
172 auto pimpl = static_cast<Impl*>(pj_turn_sock_get_user_data(relay));
173 pimpl->onTurnState(old_state, new_state);
174 };
175 // TURN socket config
176 pj_turn_sock_cfg turn_sock_cfg;
177 pj_turn_sock_cfg_default(&turn_sock_cfg);
178 turn_sock_cfg.max_pkt_size = 4096;
179 // TURN socket creation
180 TRY(pj_turn_sock_create(&pimpl_->stunConfig,
181 server.getFamily(),
182 PJ_TURN_TP_TCP,
183 &relay_cb,
184 &turn_sock_cfg,
185 &*this->pimpl_,
186 &pimpl_->relay));
187 // TURN allocation setup
188 pj_turn_alloc_param turn_alloc_param;
189 pj_turn_alloc_param_default(&turn_alloc_param);
190 turn_alloc_param.peer_conn_type = PJ_TURN_TP_TCP;
191 pj_stun_auth_cred cred;
192 pj_bzero(&cred, sizeof(cred));
193 cred.type = PJ_STUN_AUTH_CRED_STATIC;
194 pj_cstr(&cred.data.static_cred.realm, pimpl_->settings.realm.c_str());
195 pj_cstr(&cred.data.static_cred.username, pimpl_->settings.username.c_str());
196 cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
197 pj_cstr(&cred.data.static_cred.data, pimpl_->settings.password.c_str());
198 pimpl_->relayAddr = pj_strdup3(pimpl_->pool, server.toString().c_str());
199 // TURN connection/allocation
200 if (logger) logger->debug("Connecting to TURN {:s}", server.toString(true, true));
201 TRY(pj_turn_sock_alloc(pimpl_->relay,
202 &pimpl_->relayAddr,
203 server.getPort(),
204 nullptr,
205 &cred,
206 &turn_alloc_param));
207 pimpl_->turnLock = std::make_unique<TurnLock>(pimpl_->relay);
208 pimpl_->start();
209}
210
211TurnTransport::~TurnTransport() {}
212
213void
214TurnTransport::shutdown()
215{
216 pimpl_->shutdown();
217}
218
219} // namespace jami