blob: a257c9236a95b1cc556bd0d6db0ac1345611e4d9 [file] [log] [blame]
Adrien Béraud612b55b2023-05-29 10:42:04 -04001/*
2 * Copyright (C) 2004-2023 Savoir-faire Linux Inc.
3 *
Adrien Béraudcb753622023-07-17 22:32:49 -04004 * This program is free software: you can redistribute it and/or modify
Adrien Béraud612b55b2023-05-29 10:42:04 -04005 * it under the terms of the GNU General Public License as published by
Adrien Béraudcb753622023-07-17 22:32:49 -04006 * the Free Software Foundation, either version 3 of the License, or
Adrien Béraud612b55b2023-05-29 10:42:04 -04007 * (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
Adrien Béraudcb753622023-07-17 22:32:49 -040011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Adrien Béraud612b55b2023-05-29 10:42:04 -040012 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
Adrien Béraudcb753622023-07-17 22:32:49 -040015 * along with this program. If not, see <https://www.gnu.org/licenses/>.
Adrien Béraud612b55b2023-05-29 10:42:04 -040016 */
Adrien Béraud612b55b2023-05-29 10:42:04 -040017#include "ice_transport.h"
Adrien Béraud6de3f882023-07-06 12:56:29 -040018#include "ice_transport_factory.h"
Adrien Béraud612b55b2023-05-29 10:42:04 -040019#include "ice_socket.h"
20#include "sip_utils.h"
21#include "string_utils.h"
22#include "upnp/upnp_control.h"
23#include "transport/peer_channel.h"
24#include "tracepoint/tracepoint.h"
25
26#include <opendht/logger.h>
27#include <opendht/utils.h>
28
29#include <pjlib.h>
30
31#include <map>
32#include <atomic>
33#include <queue>
34#include <mutex>
35#include <condition_variable>
36#include <thread>
37#include <utility>
38#include <tuple>
39#include <algorithm>
40#include <sstream>
41#include <chrono>
Adrien Béraud67ff0772023-07-12 11:22:29 -040042#include <memory>
Adrien Béraud612b55b2023-05-29 10:42:04 -040043#include <cerrno>
44
45#include "pj/limits.h"
46
47#define TRY(ret) \
48 do { \
49 if ((ret) != PJ_SUCCESS) \
50 throw std::runtime_error(#ret " failed"); \
51 } while (0)
52
53// Validate that the component ID is within the expected range
54#define ASSERT_COMP_ID(compId, compCount) \
55 do { \
56 if ((compId) == 0 or (compId) > (compCount)) \
57 throw std::runtime_error("Invalid component ID " + (std::to_string(compId))); \
58 } while (0)
59
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040060namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040061
62static constexpr unsigned STUN_MAX_PACKET_SIZE {8192};
63static constexpr uint16_t IPV6_HEADER_SIZE = 40; ///< Size in bytes of IPV6 packet header
64static constexpr uint16_t IPV4_HEADER_SIZE = 20; ///< Size in bytes of IPV4 packet header
65static constexpr int MAX_CANDIDATES {32};
66static constexpr int MAX_DESTRUCTION_TIMEOUT {3000};
67static constexpr int HANDLE_EVENT_DURATION {500};
68
69//==============================================================================
70
71using MutexGuard = std::lock_guard<std::mutex>;
72using MutexLock = std::unique_lock<std::mutex>;
73using namespace upnp;
74
75//==============================================================================
76
77class IceLock
78{
79 pj_grp_lock_t* lk_;
80
81public:
82 IceLock(pj_ice_strans* strans)
83 : lk_(pj_ice_strans_get_grp_lock(strans))
84 {
85 lock();
86 }
87
88 ~IceLock() { unlock(); }
89
90 void lock() { if (lk_) pj_grp_lock_acquire(lk_); }
91
92 void unlock() { if (lk_) pj_grp_lock_release(lk_); }
93};
94
95class IceTransport::Impl
96{
97public:
Adrien Béraud89933c12023-07-26 14:53:30 -040098 Impl(std::string_view name, const std::shared_ptr<Logger>& logger);
Adrien Béraud612b55b2023-05-29 10:42:04 -040099 ~Impl();
100
101 void initIceInstance(const IceTransportOptions& options);
102
103 void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status);
104
105 void onReceiveData(unsigned comp_id, void* pkt, pj_size_t size);
106
107 /**
108 * Set/change transport role as initiator.
109 * Should be called before start method.
110 */
111 bool setInitiatorSession();
112
113 /**
114 * Set/change transport role as slave.
115 * Should be called before start method.
116 */
117 bool setSlaveSession();
118 bool createIceSession(pj_ice_sess_role role);
119
120 void getUFragPwd();
121
122 std::string link() const;
123
124 bool _isInitialized() const;
125 bool _isStarted() const;
126 bool _isRunning() const;
127 bool _isFailed() const;
128 bool _waitForInitialization(std::chrono::milliseconds timeout);
129
130 const pj_ice_sess_cand* getSelectedCandidate(unsigned comp_id, bool remote) const;
131 IpAddr getLocalAddress(unsigned comp_id) const;
132 IpAddr getRemoteAddress(unsigned comp_id) const;
133 static const char* getCandidateType(const pj_ice_sess_cand* cand);
134 bool isTcpEnabled() const { return config_.protocol == PJ_ICE_TP_TCP; }
135 bool addStunConfig(int af);
136 void requestUpnpMappings();
137 bool hasUpnp() const;
138 // Take a list of address pairs (local/public) and add them as
139 // reflexive candidates using STUN config.
140 void addServerReflexiveCandidates(const std::vector<std::pair<IpAddr, IpAddr>>& addrList);
141 // Generate server reflexive candidates using the published (DHT/Account) address
142 std::vector<std::pair<IpAddr, IpAddr>> setupGenericReflexiveCandidates();
143 // Generate server reflexive candidates using UPNP mappings.
144 std::vector<std::pair<IpAddr, IpAddr>> setupUpnpReflexiveCandidates();
145 void setDefaultRemoteAddress(unsigned comp_id, const IpAddr& addr);
146 IpAddr getDefaultRemoteAddress(unsigned comp_id) const;
147 bool handleEvents(unsigned max_msec);
148 int flushTimerHeapAndIoQueue();
149 int checkEventQueue(int maxEventToPoll);
150
151 std::shared_ptr<dht::log::Logger> logger_ {};
152
153 std::condition_variable_any iceCV_ {};
154
155 std::string sessionName_ {};
156 std::unique_ptr<pj_pool_t, decltype(&pj_pool_release)> pool_ {nullptr, pj_pool_release};
157 bool isTcp_ {false};
158 bool upnpEnabled_ {false};
159 IceTransportCompleteCb on_initdone_cb_ {};
160 IceTransportCompleteCb on_negodone_cb_ {};
161 pj_ice_strans* icest_ {nullptr};
162 unsigned streamsCount_ {0};
163 unsigned compCountPerStream_ {0};
164 unsigned compCount_ {0};
165 std::string local_ufrag_ {};
166 std::string local_pwd_ {};
167 pj_sockaddr remoteAddr_ {};
168 pj_ice_strans_cfg config_ {};
169 //std::string last_errmsg_ {};
170
171 std::atomic_bool is_stopped_ {false};
172
173 struct Packet
174 {
175 Packet(void* pkt, pj_size_t size)
176 : data {reinterpret_cast<char*>(pkt), reinterpret_cast<char*>(pkt) + size}
177 {}
178 std::vector<char> data {};
179 };
180
181 struct ComponentIO
182 {
183 std::mutex mutex;
184 std::condition_variable cv;
185 std::deque<Packet> queue;
186 IceRecvCb recvCb;
187 };
188
189 // NOTE: Component IDs start from 1, while these three vectors
190 // are indexed from 0. Conversion from ID to vector index must
191 // be done properly.
192 std::vector<ComponentIO> compIO_ {};
193 std::vector<PeerChannel> peerChannels_ {};
194 std::vector<IpAddr> iceDefaultRemoteAddr_;
195
196 // ICE controlling role. True for controller agents and false for
197 // controlled agents
198 std::atomic_bool initiatorSession_ {true};
199
200 // Local/Public addresses used by the account owning the ICE instance.
201 IpAddr accountLocalAddr_ {};
202 IpAddr accountPublicAddr_ {};
203
204 // STUN and TURN servers
205 std::vector<StunServerInfo> stunServers_;
206 std::vector<TurnServerInfo> turnServers_;
207
208 /**
209 * Returns the IP of each candidate for a given component in the ICE session
210 */
211 struct LocalCandidate
212 {
213 IpAddr addr;
214 pj_ice_cand_transport transport;
215 };
216
217 std::shared_ptr<upnp::Controller> upnp_ {};
218 std::mutex upnpMutex_ {};
219 std::map<Mapping::key_t, Mapping> upnpMappings_;
220 std::mutex upnpMappingsMutex_ {};
221
222 bool onlyIPv4Private_ {true};
223
224 // IO/Timer events are handled by following thread
225 std::thread thread_ {};
226 std::atomic_bool threadTerminateFlags_ {false};
227
228 // Wait data on components
229 mutable std::mutex sendDataMutex_ {};
230 std::condition_variable waitDataCv_ = {};
231 pj_size_t lastSentLen_ {0};
232 bool destroying_ {false};
233 onShutdownCb scb {};
234
235 void cancelOperations()
236 {
237 for (auto& c : peerChannels_)
238 c.stop();
239 std::lock_guard<std::mutex> lk(sendDataMutex_);
240 destroying_ = true;
241 waitDataCv_.notify_all();
242 }
243};
244
245//==============================================================================
246
247/**
248 * Add stun/turn configuration or default host as candidates
249 */
250
251static void
Sébastien Blincf569402023-07-27 09:46:40 -0400252add_stun_server(pj_pool_t& pool, pj_ice_strans_cfg& cfg, const StunServerInfo& info, const std::shared_ptr<dht::log::Logger>& logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400253{
254 if (cfg.stun_tp_cnt >= PJ_ICE_MAX_STUN)
255 throw std::runtime_error("Too many STUN configurations");
256
257 IpAddr ip {info.uri};
258
259 // Given URI cannot be DNS resolved or not IPv4 or IPv6?
260 // This prevents a crash into PJSIP when ip.toString() is called.
261 if (ip.getFamily() == AF_UNSPEC) {
262 /*JAMI_DBG("[ice (%s)] STUN server '%s' not used, unresolvable address",
263 (cfg.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
264 info.uri.c_str());*/
265 return;
266 }
267
268 auto& stun = cfg.stun_tp[cfg.stun_tp_cnt++];
269 pj_ice_strans_stun_cfg_default(&stun);
270 pj_strdup2_with_null(&pool, &stun.server, ip.toString().c_str());
271 stun.af = ip.getFamily();
272 if (!(stun.port = ip.getPort()))
273 stun.port = PJ_STUN_PORT;
274 stun.cfg.max_pkt_size = STUN_MAX_PACKET_SIZE;
275 stun.conn_type = cfg.stun.conn_type;
Sébastien Blincf569402023-07-27 09:46:40 -0400276 if (logger)
277 logger->debug("added stun server '{}', port {}", pj_strbuf(&stun.server), stun.port);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400278}
279
280static void
Sébastien Blincf569402023-07-27 09:46:40 -0400281add_turn_server(pj_pool_t& pool, pj_ice_strans_cfg& cfg, const TurnServerInfo& info, const std::shared_ptr<dht::log::Logger>& logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400282{
283 if (cfg.turn_tp_cnt >= PJ_ICE_MAX_TURN)
284 throw std::runtime_error("Too many TURN servers");
285
286 IpAddr ip {info.uri};
287
288 // Same comment as add_stun_server()
289 if (ip.getFamily() == AF_UNSPEC) {
Sébastien Blincf569402023-07-27 09:46:40 -0400290 if (logger)
291 logger->debug("TURN server '{}' not used, unresolvable address", info.uri);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400292 return;
293 }
294
295 auto& turn = cfg.turn_tp[cfg.turn_tp_cnt++];
296 pj_ice_strans_turn_cfg_default(&turn);
297 pj_strdup2_with_null(&pool, &turn.server, ip.toString().c_str());
298 turn.af = ip.getFamily();
299 if (!(turn.port = ip.getPort()))
300 turn.port = PJ_STUN_PORT;
301 turn.cfg.max_pkt_size = STUN_MAX_PACKET_SIZE;
302 turn.conn_type = cfg.turn.conn_type;
303
304 // Authorization (only static plain password supported yet)
305 if (not info.password.empty()) {
306 turn.auth_cred.type = PJ_STUN_AUTH_CRED_STATIC;
307 turn.auth_cred.data.static_cred.data_type = PJ_STUN_PASSWD_PLAIN;
308 pj_strset(&turn.auth_cred.data.static_cred.realm,
309 (char*) info.realm.c_str(),
310 info.realm.size());
311 pj_strset(&turn.auth_cred.data.static_cred.username,
312 (char*) info.username.c_str(),
313 info.username.size());
314 pj_strset(&turn.auth_cred.data.static_cred.data,
315 (char*) info.password.c_str(),
316 info.password.size());
317 }
Sébastien Blincf569402023-07-27 09:46:40 -0400318 if (logger)
319 logger->debug("added turn server '{}', port {}", pj_strbuf(&turn.server), turn.port);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400320}
321
322//==============================================================================
323
Adrien Béraud89933c12023-07-26 14:53:30 -0400324IceTransport::Impl::Impl(std::string_view name, const std::shared_ptr<Logger>& logger)
325 : logger_(logger), sessionName_(name)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400326{
327 if (logger_)
Sébastien Blincf569402023-07-27 09:46:40 -0400328 logger_->debug("[ice:{}] Creating IceTransport session for \"{:s}\"", fmt::ptr(this), sessionName_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400329}
330
331IceTransport::Impl::~Impl()
332{
333 if (logger_)
334 logger_->debug("[ice:{}] destroying {}", fmt::ptr(this), fmt::ptr(icest_));
335
336 threadTerminateFlags_ = true;
337
338 if (thread_.joinable()) {
339 thread_.join();
340 }
341
342 if (icest_) {
343 pj_ice_strans* strans = nullptr;
344
345 std::swap(strans, icest_);
346
347 // must be done before ioqueue/timer destruction
348 if (logger_)
349 logger_->debug("[ice:{}] Destroying ice_strans {}", pj_ice_strans_get_user_data(strans), fmt::ptr(strans));
350
351 pj_ice_strans_stop_ice(strans);
352 pj_ice_strans_destroy(strans);
353
354 // NOTE: This last timer heap and IO queue polling is necessary to close
355 // TURN socket.
356 // Because when destroying the TURN session pjproject creates a pj_timer
357 // to postpone the TURN destruction. This timer is only called if we poll
358 // the event queue.
359
360 int ret = flushTimerHeapAndIoQueue();
361
362 if (ret < 0) {
363 if (logger_)
364 logger_->error("[ice:{}] IO queue polling failed", fmt::ptr(this));
365 } else if (ret > 0) {
366 if (logger_)
367 logger_->error("[ice:{}] Unexpected left timer in timer heap. "
368 "Please report the bug",
369 fmt::ptr(this));
370 }
371
372 if (checkEventQueue(1) > 0) {
373 if (logger_)
374 logger_->warn("[ice:{}] Unexpected left events in IO queue", fmt::ptr(this));
375 }
376
377 if (config_.stun_cfg.ioqueue)
378 pj_ioqueue_destroy(config_.stun_cfg.ioqueue);
379
380 if (config_.stun_cfg.timer_heap)
381 pj_timer_heap_destroy(config_.stun_cfg.timer_heap);
382 }
383
384 if (logger_)
Sébastien Blincf569402023-07-27 09:46:40 -0400385 logger_->debug("[ice:{:p}] done destroying", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400386 if (scb)
387 scb();
388}
389
390void
391IceTransport::Impl::initIceInstance(const IceTransportOptions& options)
392{
393 isTcp_ = options.tcpEnable;
394 upnpEnabled_ = options.upnpEnable;
395 on_initdone_cb_ = options.onInitDone;
396 on_negodone_cb_ = options.onNegoDone;
397 streamsCount_ = options.streamsCount;
398 compCountPerStream_ = options.compCountPerStream;
399 compCount_ = streamsCount_ * compCountPerStream_;
400 compIO_ = std::vector<ComponentIO>(compCount_);
401 peerChannels_ = std::vector<PeerChannel>(compCount_);
402 iceDefaultRemoteAddr_.resize(compCount_);
403 initiatorSession_ = options.master;
404 accountLocalAddr_ = std::move(options.accountLocalAddr);
405 accountPublicAddr_ = std::move(options.accountPublicAddr);
406 stunServers_ = std::move(options.stunServers);
407 turnServers_ = std::move(options.turnServers);
408
409 if (logger_)
410 logger_->debug("[ice:{}] Initializing the session - comp count {} - as a {}",
411 fmt::ptr(this),
412 compCount_,
413 initiatorSession_ ? "master" : "slave");
414
415 if (upnpEnabled_)
Adrien Béraud25c30c42023-07-05 13:46:54 -0400416 upnp_.reset(new upnp::Controller(options.upnpContext));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400417
418 config_ = options.factory->getIceCfg(); // config copy
419 if (isTcp_) {
420 config_.protocol = PJ_ICE_TP_TCP;
421 config_.stun.conn_type = PJ_STUN_TP_TCP;
422 config_.turn.conn_type = PJ_TURN_TP_TCP;
423 } else {
424 config_.protocol = PJ_ICE_TP_UDP;
425 config_.stun.conn_type = PJ_STUN_TP_UDP;
426 config_.turn.conn_type = PJ_TURN_TP_UDP;
427 }
428
429 pool_.reset(
430 pj_pool_create(options.factory->getPoolFactory(), "IceTransport.pool", 512, 512, NULL));
431 if (not pool_)
432 throw std::runtime_error("pj_pool_create() failed");
433
434 // Note: For server reflexive candidates, UPNP mappings will
435 // be used if available. Then, the public address learnt during
436 // the account registration process will be added only if it
437 // differs from the UPNP public address.
438 // Also note that UPNP candidates should be added first in order
439 // to have a higher priority when performing the connectivity
440 // checks.
441 // STUN configs layout:
442 // - index 0 : host IPv4
443 // - index 1 : host IPv6
444 // - index 2 : upnp/generic srflx IPv4.
445 // - index 3 : generic srflx (if upnp exists and different)
446
447 config_.stun_tp_cnt = 0;
448
449 if (logger_)
450 logger_->debug("[ice:{}] Add host candidates", fmt::ptr(this));
451 addStunConfig(pj_AF_INET());
452 addStunConfig(pj_AF_INET6());
453
454 std::vector<std::pair<IpAddr, IpAddr>> upnpSrflxCand;
455 if (upnp_) {
456 requestUpnpMappings();
457 upnpSrflxCand = setupUpnpReflexiveCandidates();
458 if (not upnpSrflxCand.empty()) {
459 addServerReflexiveCandidates(upnpSrflxCand);
460 if (logger_)
461 logger_->debug("[ice:{}] Added UPNP srflx candidates:", fmt::ptr(this));
462 }
463 }
464
465 auto genericSrflxCand = setupGenericReflexiveCandidates();
466
467 if (not genericSrflxCand.empty()) {
468 // Generic srflx candidates will be added only if different
469 // from upnp candidates.
470 if (upnpSrflxCand.empty()
471 or (upnpSrflxCand[0].second.toString() != genericSrflxCand[0].second.toString())) {
472 addServerReflexiveCandidates(genericSrflxCand);
473 if (logger_)
474 logger_->debug("[ice:{}] Added generic srflx candidates:", fmt::ptr(this));
475 }
476 }
477
478 if (upnpSrflxCand.empty() and genericSrflxCand.empty()) {
479 if (logger_)
480 logger_->warn("[ice:{}] No server reflexive candidates added", fmt::ptr(this));
481 }
482
483 pj_ice_strans_cb icecb;
484 pj_bzero(&icecb, sizeof(icecb));
485
486 icecb.on_rx_data = [](pj_ice_strans* ice_st,
487 unsigned comp_id,
488 void* pkt,
489 pj_size_t size,
490 const pj_sockaddr_t* /*src_addr*/,
491 unsigned /*src_addr_len*/) {
492 if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st)))
493 tr->onReceiveData(comp_id, pkt, size);
494 };
495
496 icecb.on_ice_complete = [](pj_ice_strans* ice_st, pj_ice_strans_op op, pj_status_t status) {
497 if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st)))
498 tr->onComplete(ice_st, op, status);
499 };
500
501 if (isTcp_) {
502 icecb.on_data_sent = [](pj_ice_strans* ice_st, pj_ssize_t size) {
503 if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st))) {
504 std::lock_guard lk(tr->sendDataMutex_);
505 tr->lastSentLen_ += size;
506 tr->waitDataCv_.notify_all();
507 }
508 };
509 }
510
511 icecb.on_destroy = [](pj_ice_strans* ice_st) {
512 if (auto* tr = static_cast<Impl*>(pj_ice_strans_get_user_data(ice_st)))
513 tr->cancelOperations(); // Avoid upper layer to manage this ; Stop read operations
514 };
515
516 // Add STUN servers
517 for (auto& server : stunServers_)
Sébastien Blincf569402023-07-27 09:46:40 -0400518 add_stun_server(*pool_, config_, server, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400519
520 // Add TURN servers
521 for (auto& server : turnServers_)
Sébastien Blincf569402023-07-27 09:46:40 -0400522 add_turn_server(*pool_, config_, server, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400523
524 static constexpr auto IOQUEUE_MAX_HANDLES = std::min(PJ_IOQUEUE_MAX_HANDLES, 64);
525 TRY(pj_timer_heap_create(pool_.get(), 100, &config_.stun_cfg.timer_heap));
526 TRY(pj_ioqueue_create(pool_.get(), IOQUEUE_MAX_HANDLES, &config_.stun_cfg.ioqueue));
527 std::ostringstream sessionName {};
528 // We use the instance pointer as the PJNATH session name in order
529 // to easily identify the logs reported by PJNATH.
530 sessionName << this;
531 pj_status_t status = pj_ice_strans_create(sessionName.str().c_str(),
532 &config_,
533 compCount_,
534 this,
535 &icecb,
536 &icest_);
537
538 if (status != PJ_SUCCESS || icest_ == nullptr) {
539 throw std::runtime_error("pj_ice_strans_create() failed");
540 }
541
542 // Must be created after any potential failure
543 thread_ = std::thread([this] {
544 while (not threadTerminateFlags_) {
545 // NOTE: handleEvents can return false in this case
546 // but here we don't care if there is event or not.
547 handleEvents(HANDLE_EVENT_DURATION);
548 }
549 });
550}
551
552bool
553IceTransport::Impl::_isInitialized() const
554{
555 if (auto *icest = icest_) {
556 auto state = pj_ice_strans_get_state(icest);
557 return state >= PJ_ICE_STRANS_STATE_SESS_READY and state != PJ_ICE_STRANS_STATE_FAILED;
558 }
559 return false;
560}
561
562bool
563IceTransport::Impl::_isStarted() const
564{
565 if (auto *icest = icest_) {
566 auto state = pj_ice_strans_get_state(icest);
567 return state >= PJ_ICE_STRANS_STATE_NEGO and state != PJ_ICE_STRANS_STATE_FAILED;
568 }
569 return false;
570}
571
572bool
573IceTransport::Impl::_isRunning() const
574{
575 if (auto *icest = icest_) {
576 auto state = pj_ice_strans_get_state(icest);
577 return state >= PJ_ICE_STRANS_STATE_RUNNING and state != PJ_ICE_STRANS_STATE_FAILED;
578 }
579 return false;
580}
581
582bool
583IceTransport::Impl::_isFailed() const
584{
585 if (auto *icest = icest_)
586 return pj_ice_strans_get_state(icest) == PJ_ICE_STRANS_STATE_FAILED;
587 return false;
588}
589
590bool
591IceTransport::Impl::handleEvents(unsigned max_msec)
592{
593 // By tests, never seen more than two events per 500ms
594 static constexpr auto MAX_NET_EVENTS = 2;
595
596 pj_time_val max_timeout = {0, static_cast<long>(max_msec)};
597 pj_time_val timeout = {0, 0};
598 unsigned net_event_count = 0;
599
600 pj_timer_heap_poll(config_.stun_cfg.timer_heap, &timeout);
601 auto hasActiveTimer = timeout.sec != PJ_MAXINT32 || timeout.msec != PJ_MAXINT32;
602
603 // timeout limitation
604 if (hasActiveTimer)
605 pj_time_val_normalize(&timeout);
606
607 if (PJ_TIME_VAL_GT(timeout, max_timeout)) {
608 timeout = max_timeout;
609 }
610
611 do {
612 auto n_events = pj_ioqueue_poll(config_.stun_cfg.ioqueue, &timeout);
613
614 // timeout
615 if (not n_events)
616 return hasActiveTimer;
617
618 // error
619 if (n_events < 0) {
620 const auto err = pj_get_os_error();
621 // Kept as debug as some errors are "normal" in regular context
622 if (logger_)
623 logger_->debug("[ice:{}] ioqueue error {:d}: {:s}", fmt::ptr(this), err, sip_utils::sip_strerror(err));
624 std::this_thread::sleep_for(std::chrono::milliseconds(PJ_TIME_VAL_MSEC(timeout)));
625 return hasActiveTimer;
626 }
627
628 net_event_count += n_events;
629 timeout.sec = timeout.msec = 0;
630 } while (net_event_count < MAX_NET_EVENTS);
631 return hasActiveTimer;
632}
633
634int
635IceTransport::Impl::flushTimerHeapAndIoQueue()
636{
637 pj_time_val timerTimeout = {0, 0};
638 pj_time_val defaultWaitTime = {0, HANDLE_EVENT_DURATION};
639 bool hasActiveTimer = false;
640 std::chrono::milliseconds totalWaitTime {0};
641 auto const start = std::chrono::steady_clock::now();
642 // We try to process pending events as fast as possible to
643 // speed-up the release.
644 int maxEventToProcess = 10;
645
646 do {
647 if (checkEventQueue(maxEventToProcess) < 0)
648 return -1;
649
650 pj_timer_heap_poll(config_.stun_cfg.timer_heap, &timerTimeout);
651 hasActiveTimer = !(timerTimeout.sec == PJ_MAXINT32 && timerTimeout.msec == PJ_MAXINT32);
652
653 if (hasActiveTimer) {
654 pj_time_val_normalize(&timerTimeout);
655 auto waitTime = std::chrono::milliseconds(
656 std::min(PJ_TIME_VAL_MSEC(timerTimeout), PJ_TIME_VAL_MSEC(defaultWaitTime)));
657 std::this_thread::sleep_for(waitTime);
658 totalWaitTime += waitTime;
659 }
660 } while (hasActiveTimer && totalWaitTime < std::chrono::milliseconds(MAX_DESTRUCTION_TIMEOUT));
661
662 auto duration = std::chrono::steady_clock::now() - start;
663 if (logger_)
664 logger_->debug("[ice:{}] Timer heap flushed after {}", fmt::ptr(this), dht::print_duration(duration));
665
666 return static_cast<int>(pj_timer_heap_count(config_.stun_cfg.timer_heap));
667}
668
669int
670IceTransport::Impl::checkEventQueue(int maxEventToPoll)
671{
672 pj_time_val timeout = {0, 0};
673 int eventCount = 0;
674 int events = 0;
675
676 do {
677 events = pj_ioqueue_poll(config_.stun_cfg.ioqueue, &timeout);
678 if (events < 0) {
679 const auto err = pj_get_os_error();
680 if (logger_)
681 logger_->error("[ice:{}] ioqueue error {:d}: {:s}", fmt::ptr(this), err, sip_utils::sip_strerror(err));
682 return events;
683 }
684
685 eventCount += events;
686
687 } while (events > 0 && eventCount < maxEventToPoll);
688
689 return eventCount;
690}
691
692void
693IceTransport::Impl::onComplete(pj_ice_strans*, pj_ice_strans_op op, pj_status_t status)
694{
695 const char* opname = op == PJ_ICE_STRANS_OP_INIT ? "initialization"
696 : op == PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation"
697 : "unknown_op";
698
699 const bool done = status == PJ_SUCCESS;
700 if (done) {
701 if (logger_)
702 logger_->debug("[ice:{}] {:s} {:s} success",
703 fmt::ptr(this),
704 (config_.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
705 opname);
706 } else {
707 if (logger_)
708 logger_->error("[ice:{}] {:s} {:s} failed: {:s}",
709 fmt::ptr(this),
710 (config_.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
711 opname,
712 sip_utils::sip_strerror(status));
713 }
714
715 if (done and op == PJ_ICE_STRANS_OP_INIT) {
716 if (initiatorSession_)
717 setInitiatorSession();
718 else
719 setSlaveSession();
720 }
721
722 if (op == PJ_ICE_STRANS_OP_INIT and on_initdone_cb_)
723 on_initdone_cb_(done);
724 else if (op == PJ_ICE_STRANS_OP_NEGOTIATION) {
725 if (done) {
726 // Dump of connection pairs
727 if (logger_)
728 logger_->debug("[ice:{}] {:s} connection pairs ([comp id] local [type] <-> remote [type]):\n{:s}",
729 fmt::ptr(this),
730 (config_.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP"),
731 link());
732 }
733 if (on_negodone_cb_)
734 on_negodone_cb_(done);
735 }
736
737 iceCV_.notify_all();
738}
739
740std::string
741IceTransport::Impl::link() const
742{
743 std::ostringstream out;
744 for (unsigned strm = 1; strm <= streamsCount_ * compCountPerStream_; strm++) {
745 auto absIdx = strm;
746 auto comp = (strm + 1) / compCountPerStream_;
747 auto laddr = getLocalAddress(absIdx);
748 auto raddr = getRemoteAddress(absIdx);
749
750 if (laddr and laddr.getPort() != 0 and raddr and raddr.getPort() != 0) {
751 out << " [" << comp << "] " << laddr.toString(true, true) << " ["
752 << getCandidateType(getSelectedCandidate(absIdx, false)) << "] "
753 << " <-> " << raddr.toString(true, true) << " ["
754 << getCandidateType(getSelectedCandidate(absIdx, true)) << "] " << '\n';
755 } else {
756 out << " [" << comp << "] disabled\n";
757 }
758 }
759 return out.str();
760}
761
762bool
763IceTransport::Impl::setInitiatorSession()
764{
765 if (logger_)
766 logger_->debug("[ice:{}] as master", fmt::ptr(this));
767 initiatorSession_ = true;
768 if (_isInitialized()) {
769 auto status = pj_ice_strans_change_role(icest_, PJ_ICE_SESS_ROLE_CONTROLLING);
770 if (status != PJ_SUCCESS) {
771 if (logger_)
772 logger_->error("[ice:{}] role change failed: {:s}", fmt::ptr(this), sip_utils::sip_strerror(status));
773 return false;
774 }
775 return true;
776 }
777 return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLING);
778}
779
780bool
781IceTransport::Impl::setSlaveSession()
782{
783 if (logger_)
784 logger_->debug("[ice:{}] as slave", fmt::ptr(this));
785 initiatorSession_ = false;
786 if (_isInitialized()) {
787 auto status = pj_ice_strans_change_role(icest_, PJ_ICE_SESS_ROLE_CONTROLLED);
788 if (status != PJ_SUCCESS) {
789 if (logger_)
790 logger_->error("[ice:{}] role change failed: {:s}", fmt::ptr(this), sip_utils::sip_strerror(status));
791 return false;
792 }
793 return true;
794 }
795 return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLED);
796}
797
798const pj_ice_sess_cand*
799IceTransport::Impl::getSelectedCandidate(unsigned comp_id, bool remote) const
800{
801 ASSERT_COMP_ID(comp_id, compCount_);
802
803 // Return the selected candidate pair. Might not be the nominated pair if
804 // ICE has not concluded yet, but should be the nominated pair afterwards.
805 if (not _isRunning()) {
806 if (logger_)
807 logger_->error("[ice:{}] ICE transport is not running", fmt::ptr(this));
808 return nullptr;
809 }
810
811 const auto* sess = pj_ice_strans_get_valid_pair(icest_, comp_id);
812 if (sess == nullptr) {
813 if (logger_)
814 logger_->warn("[ice:{}] Component {} has no valid pair (disabled)", fmt::ptr(this), comp_id);
815 return nullptr;
816 }
817
818 if (remote)
819 return sess->rcand;
820 else
821 return sess->lcand;
822}
823
824IpAddr
825IceTransport::Impl::getLocalAddress(unsigned comp_id) const
826{
827 ASSERT_COMP_ID(comp_id, compCount_);
828
829 if (auto cand = getSelectedCandidate(comp_id, false))
830 return cand->addr;
831
832 return {};
833}
834
835IpAddr
836IceTransport::Impl::getRemoteAddress(unsigned comp_id) const
837{
838 ASSERT_COMP_ID(comp_id, compCount_);
839
840 if (auto cand = getSelectedCandidate(comp_id, true))
841 return cand->addr;
842
843 return {};
844}
845
846const char*
847IceTransport::Impl::getCandidateType(const pj_ice_sess_cand* cand)
848{
849 auto name = cand ? pj_ice_get_cand_type_name(cand->type) : nullptr;
850 return name ? name : "?";
851}
852
853void
854IceTransport::Impl::getUFragPwd()
855{
856 if (icest_) {
857 pj_str_t local_ufrag, local_pwd;
858
859 pj_ice_strans_get_ufrag_pwd(icest_, &local_ufrag, &local_pwd, nullptr, nullptr);
860 local_ufrag_.assign(local_ufrag.ptr, local_ufrag.slen);
861 local_pwd_.assign(local_pwd.ptr, local_pwd.slen);
862 }
863}
864
865bool
866IceTransport::Impl::createIceSession(pj_ice_sess_role role)
867{
868 if (not icest_) {
869 return false;
870 }
871
872 if (pj_ice_strans_init_ice(icest_, role, nullptr, nullptr) != PJ_SUCCESS) {
873 if (logger_)
874 logger_->error("[ice:{}] pj_ice_strans_init_ice() failed", fmt::ptr(this));
875 return false;
876 }
877
878 // Fetch some information on local configuration
879 getUFragPwd();
880
881 if (logger_)
Sébastien Blincf569402023-07-27 09:46:40 -0400882 logger_->debug("[ice:{}] (local) ufrag={}, pwd={}", fmt::ptr(this), local_ufrag_, local_pwd_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400883
884 return true;
885}
886
887bool
888IceTransport::Impl::addStunConfig(int af)
889{
890 if (config_.stun_tp_cnt >= PJ_ICE_MAX_STUN) {
891 if (logger_)
Sébastien Blincf569402023-07-27 09:46:40 -0400892 logger_->error("Max number of STUN configurations reached ({})", PJ_ICE_MAX_STUN);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400893 return false;
894 }
895
896 if (af != pj_AF_INET() and af != pj_AF_INET6()) {
897 if (logger_)
Sébastien Blincf569402023-07-27 09:46:40 -0400898 logger_->error("Invalid address familly ({})", af);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400899 return false;
900 }
901
902 auto& stun = config_.stun_tp[config_.stun_tp_cnt++];
903
904 pj_ice_strans_stun_cfg_default(&stun);
905 stun.cfg.max_pkt_size = STUN_MAX_PACKET_SIZE;
906 stun.af = af;
907 stun.conn_type = config_.stun.conn_type;
908
909 if (logger_)
910 logger_->debug("[ice:{}] added host stun config for {:s} transport",
911 fmt::ptr(this),
912 config_.protocol == PJ_ICE_TP_TCP ? "TCP" : "UDP");
913
914 return true;
915}
916
917void
918IceTransport::Impl::requestUpnpMappings()
919{
920 // Must be called once !
921
922 std::lock_guard<std::mutex> lock(upnpMutex_);
923
924 if (not upnp_)
925 return;
926
927 auto transport = isTcpEnabled() ? PJ_CAND_TCP_PASSIVE : PJ_CAND_UDP;
928 auto portType = transport == PJ_CAND_UDP ? PortType::UDP : PortType::TCP;
929
930 // Request upnp mapping for each component.
931 for (unsigned id = 1; id <= compCount_; id++) {
932 // Set port number to 0 to get any available port.
933 Mapping requestedMap(portType);
934
935 // Request the mapping
936 Mapping::sharedPtr_t mapPtr = upnp_->reserveMapping(requestedMap);
937
938 // To use a mapping, it must be valid, open and has valid host address.
939 if (mapPtr and mapPtr->getMapKey() and (mapPtr->getState() == MappingState::OPEN)
940 and mapPtr->hasValidHostAddress()) {
941 std::lock_guard<std::mutex> lock(upnpMappingsMutex_);
942 auto ret = upnpMappings_.emplace(mapPtr->getMapKey(), *mapPtr);
943 if (ret.second) {
944 if (logger_)
945 logger_->debug("[ice:{}] UPNP mapping {:s} successfully allocated",
946 fmt::ptr(this),
947 mapPtr->toString(true));
948 } else {
949 if (logger_)
950 logger_->warn("[ice:{}] UPNP mapping {:s} already in the list!",
951 fmt::ptr(this),
952 mapPtr->toString());
953 }
954 } else {
955 if (logger_)
956 logger_->warn("[ice:{}] UPNP mapping request failed!", fmt::ptr(this));
957 upnp_->releaseMapping(requestedMap);
958 }
959 }
960}
961
962bool
963IceTransport::Impl::hasUpnp() const
964{
965 return upnp_ and upnpMappings_.size() == compCount_;
966}
967
968void
969IceTransport::Impl::addServerReflexiveCandidates(
970 const std::vector<std::pair<IpAddr, IpAddr>>& addrList)
971{
972 if (addrList.size() != compCount_) {
973 if (logger_)
974 logger_->warn("[ice:{}] Provided addr list size {} does not match component count {}",
975 fmt::ptr(this),
976 addrList.size(),
977 compCount_);
978 return;
979 }
980 if (compCount_ > PJ_ICE_MAX_COMP) {
981 if (logger_)
982 logger_->error("[ice:{}] Too many components", fmt::ptr(this));
983 return;
984 }
985
986 // Add config for server reflexive candidates (UPNP or from DHT).
987 if (not addStunConfig(pj_AF_INET()))
988 return;
989
990 assert(config_.stun_tp_cnt > 0 && config_.stun_tp_cnt < PJ_ICE_MAX_STUN);
991 auto& stun = config_.stun_tp[config_.stun_tp_cnt - 1];
992
993 for (unsigned id = 1; id <= compCount_; id++) {
994 auto idx = id - 1;
995 auto& localAddr = addrList[idx].first;
996 auto& publicAddr = addrList[idx].second;
997
998 if (logger_)
999 logger_->debug("[ice:{}] Add srflx reflexive candidates [{:s} : {:s}] for comp {:d}",
1000 fmt::ptr(this),
1001 localAddr.toString(true),
1002 publicAddr.toString(true),
1003 id);
1004
1005 pj_sockaddr_cp(&stun.cfg.user_mapping[idx].local_addr, localAddr.pjPtr());
1006 pj_sockaddr_cp(&stun.cfg.user_mapping[idx].mapped_addr, publicAddr.pjPtr());
1007
1008 if (isTcpEnabled()) {
1009 if (publicAddr.getPort() == 9) {
1010 stun.cfg.user_mapping[idx].tp_type = PJ_CAND_TCP_ACTIVE;
1011 } else {
1012 stun.cfg.user_mapping[idx].tp_type = PJ_CAND_TCP_PASSIVE;
1013 }
1014 } else {
1015 stun.cfg.user_mapping[idx].tp_type = PJ_CAND_UDP;
1016 }
1017 }
1018
1019 stun.cfg.user_mapping_cnt = compCount_;
1020}
1021
1022std::vector<std::pair<IpAddr, IpAddr>>
1023IceTransport::Impl::setupGenericReflexiveCandidates()
1024{
1025 if (not accountLocalAddr_) {
1026 if (logger_)
1027 logger_->warn("[ice:{}] Missing local address, generic srflx candidates wont be generated!",
1028 fmt::ptr(this));
1029 return {};
1030 }
1031
1032 if (not accountPublicAddr_) {
1033 if (logger_)
1034 logger_->warn("[ice:{}] Missing public address, generic srflx candidates wont be generated!",
1035 fmt::ptr(this));
1036 return {};
1037 }
1038
1039 std::vector<std::pair<IpAddr, IpAddr>> addrList;
1040 auto isTcp = isTcpEnabled();
1041
1042 addrList.reserve(compCount_);
1043 for (unsigned id = 1; id <= compCount_; id++) {
1044 // For TCP, the type is set to active, because most likely the incoming
1045 // connection will be blocked by the NAT.
1046 // For UDP use random port number.
1047 uint16_t port = isTcp ? 9
1048 : upnp::Controller::generateRandomPort(isTcp ? PortType::TCP
1049 : PortType::UDP);
1050
1051 accountLocalAddr_.setPort(port);
1052 accountPublicAddr_.setPort(port);
1053 addrList.emplace_back(accountLocalAddr_, accountPublicAddr_);
1054 }
1055
1056 return addrList;
1057}
1058
1059std::vector<std::pair<IpAddr, IpAddr>>
1060IceTransport::Impl::setupUpnpReflexiveCandidates()
1061{
1062 // Add UPNP server reflexive candidates if available.
1063 if (not hasUpnp())
1064 return {};
1065
1066 std::lock_guard<std::mutex> lock(upnpMappingsMutex_);
1067
1068 if (upnpMappings_.size() < (size_t)compCount_) {
1069 if (logger_)
1070 logger_->warn("[ice:{}] Not enough mappings {:d}. Expected {:d}",
1071 fmt::ptr(this),
1072 upnpMappings_.size(),
1073 compCount_);
1074 return {};
1075 }
1076
1077 std::vector<std::pair<IpAddr, IpAddr>> addrList;
1078
1079 addrList.reserve(upnpMappings_.size());
1080 for (auto const& [_, map] : upnpMappings_) {
1081 assert(map.getMapKey());
1082 IpAddr localAddr {map.getInternalAddress()};
1083 localAddr.setPort(map.getInternalPort());
1084 IpAddr publicAddr {map.getExternalAddress()};
1085 publicAddr.setPort(map.getExternalPort());
1086 addrList.emplace_back(localAddr, publicAddr);
1087 }
1088
1089 return addrList;
1090}
1091
1092void
1093IceTransport::Impl::setDefaultRemoteAddress(unsigned compId, const IpAddr& addr)
1094{
1095 ASSERT_COMP_ID(compId, compCount_);
1096
1097 iceDefaultRemoteAddr_[compId - 1] = addr;
1098 // The port does not matter. Set it 0 to avoid confusion.
1099 iceDefaultRemoteAddr_[compId - 1].setPort(0);
1100}
1101
1102IpAddr
1103IceTransport::Impl::getDefaultRemoteAddress(unsigned compId) const
1104{
1105 ASSERT_COMP_ID(compId, compCount_);
1106 return iceDefaultRemoteAddr_[compId - 1];
1107}
1108
1109void
1110IceTransport::Impl::onReceiveData(unsigned comp_id, void* pkt, pj_size_t size)
1111{
1112 ASSERT_COMP_ID(comp_id, compCount_);
1113
1114 jami_tracepoint_if_enabled(ice_transport_recv,
1115 reinterpret_cast<uint64_t>(this),
1116 comp_id,
1117 size,
1118 getRemoteAddress(comp_id).toString().c_str());
1119 if (size == 0)
1120 return;
1121
1122 {
1123 auto& io = compIO_[comp_id - 1];
1124 std::lock_guard<std::mutex> lk(io.mutex);
1125
1126 if (io.recvCb) {
1127 io.recvCb((uint8_t*) pkt, size);
1128 return;
1129 }
1130 }
1131
1132 std::error_code ec;
1133 auto err = peerChannels_.at(comp_id - 1).write((const char*) pkt, size, ec);
1134 if (err < 0) {
1135 if (logger_)
1136 logger_->error("[ice:{}] rx: channel is closed", fmt::ptr(this));
1137 }
1138}
1139
1140bool
1141IceTransport::Impl::_waitForInitialization(std::chrono::milliseconds timeout)
1142{
1143 IceLock lk(icest_);
1144
1145 if (not iceCV_.wait_for(lk, timeout, [this] {
1146 return threadTerminateFlags_ or _isInitialized() or _isFailed();
1147 })) {
1148 if (logger_)
1149 logger_->warn("[ice:{}] waitForInitialization: timeout", fmt::ptr(this));
1150 return false;
1151 }
1152
1153 return _isInitialized();
1154}
1155
1156//==============================================================================
1157
Adrien Béraud89933c12023-07-26 14:53:30 -04001158IceTransport::IceTransport(std::string_view name, const std::shared_ptr<dht::log::Logger>& logger)
1159 : pimpl_ {std::make_unique<Impl>(name, logger)}
Adrien Béraud612b55b2023-05-29 10:42:04 -04001160{}
1161
1162IceTransport::~IceTransport()
1163{
1164 cancelOperations();
1165}
1166
1167const std::shared_ptr<dht::log::Logger>&
1168IceTransport::logger() const
1169{
1170 return pimpl_->logger_;
1171}
1172
1173void
1174IceTransport::initIceInstance(const IceTransportOptions& options)
1175{
1176 pimpl_->initIceInstance(options);
1177 jami_tracepoint(ice_transport_context, reinterpret_cast<uint64_t>(this));
1178}
1179
1180bool
1181IceTransport::isInitialized() const
1182{
1183 IceLock lk(pimpl_->icest_);
1184 return pimpl_->_isInitialized();
1185}
1186
1187bool
1188IceTransport::isStarted() const
1189{
1190 IceLock lk(pimpl_->icest_);
1191 return pimpl_->_isStarted();
1192}
1193
1194bool
1195IceTransport::isRunning() const
1196{
1197 if (!pimpl_->icest_)
1198 return false;
1199 IceLock lk(pimpl_->icest_);
1200 return pimpl_->_isRunning();
1201}
1202
1203bool
1204IceTransport::isFailed() const
1205{
1206 return pimpl_->_isFailed();
1207}
1208
1209unsigned
1210IceTransport::getComponentCount() const
1211{
1212 return pimpl_->compCount_;
1213}
1214
1215bool
1216IceTransport::setSlaveSession()
1217{
1218 return pimpl_->setSlaveSession();
1219}
1220bool
1221IceTransport::setInitiatorSession()
1222{
1223 return pimpl_->setInitiatorSession();
1224}
1225
1226bool
1227IceTransport::isInitiator() const
1228{
1229 if (isInitialized()) {
1230 return pj_ice_strans_get_role(pimpl_->icest_) == PJ_ICE_SESS_ROLE_CONTROLLING;
1231 }
1232 return pimpl_->initiatorSession_;
1233}
1234
1235bool
1236IceTransport::startIce(const Attribute& rem_attrs, std::vector<IceCandidate>&& rem_candidates)
1237{
1238 if (not isInitialized()) {
1239 if (pimpl_->logger_)
1240 pimpl_->logger_->error("[ice:{}] not initialized transport", fmt::ptr(pimpl_.get()));
1241 pimpl_->is_stopped_ = true;
1242 return false;
1243 }
1244
1245 // pj_ice_strans_start_ice crashes if remote candidates array is empty
1246 if (rem_candidates.empty()) {
1247 if (pimpl_->logger_)
1248 pimpl_->logger_->error("[ice:{}] start failed: no remote candidates", fmt::ptr(pimpl_.get()));
1249 pimpl_->is_stopped_ = true;
1250 return false;
1251 }
1252
1253 auto comp_cnt = std::max(1u, getComponentCount());
1254 if (rem_candidates.size() / comp_cnt > PJ_ICE_ST_MAX_CAND - 1) {
1255 std::vector<IceCandidate> rcands;
1256 rcands.reserve(PJ_ICE_ST_MAX_CAND - 1);
1257 if (pimpl_->logger_)
1258 pimpl_->logger_->warn("[ice:{}] too much candidates detected, trim list.", fmt::ptr(pimpl_.get()));
1259 // Just trim some candidates. To avoid to only take host candidates, iterate
1260 // through the whole list and select some host, some turn and peer reflexives
1261 // It should give at least enough infos to negotiate.
1262 auto maxHosts = 8;
1263 auto maxRelays = PJ_ICE_MAX_TURN;
1264 for (auto& c : rem_candidates) {
1265 if (c.type == PJ_ICE_CAND_TYPE_HOST) {
1266 if (maxHosts == 0)
1267 continue;
1268 maxHosts -= 1;
1269 } else if (c.type == PJ_ICE_CAND_TYPE_RELAYED) {
1270 if (maxRelays == 0)
1271 continue;
1272 maxRelays -= 1;
1273 }
1274 if (rcands.size() == PJ_ICE_ST_MAX_CAND - 1)
1275 break;
1276 rcands.emplace_back(std::move(c));
1277 }
1278 rem_candidates = std::move(rcands);
1279 }
1280
1281 pj_str_t ufrag, pwd;
1282 if (pimpl_->logger_)
1283 pimpl_->logger_->debug("[ice:{}] negotiation starting ({:d} remote candidates)",
1284 fmt::ptr(pimpl_),
1285 rem_candidates.size());
1286
1287 auto status = pj_ice_strans_start_ice(pimpl_->icest_,
1288 pj_strset(&ufrag,
1289 (char*) rem_attrs.ufrag.c_str(),
1290 rem_attrs.ufrag.size()),
1291 pj_strset(&pwd,
1292 (char*) rem_attrs.pwd.c_str(),
1293 rem_attrs.pwd.size()),
1294 rem_candidates.size(),
1295 rem_candidates.data());
1296 if (status != PJ_SUCCESS) {
1297 if (pimpl_->logger_)
1298 pimpl_->logger_->error("[ice:{}] start failed: {:s}", fmt::ptr(pimpl_.get()), sip_utils::sip_strerror(status));
1299 pimpl_->is_stopped_ = true;
1300 return false;
1301 }
1302
1303 return true;
1304}
1305
1306bool
1307IceTransport::startIce(const SDP& sdp)
1308{
1309 if (pimpl_->streamsCount_ != 1) {
1310 if (pimpl_->logger_)
Adrien Béraud49b07192023-06-13 20:03:33 -04001311 pimpl_->logger_->error(FMT_STRING("Expected exactly one stream per SDP (found {:d} streams)"), pimpl_->streamsCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001312 return false;
1313 }
1314
1315 if (not isInitialized()) {
1316 if (pimpl_->logger_)
1317 pimpl_->logger_->error(FMT_STRING("[ice:{}] not initialized transport"), fmt::ptr(pimpl_));
1318 pimpl_->is_stopped_ = true;
1319 return false;
1320 }
1321
1322 for (unsigned id = 1; id <= getComponentCount(); id++) {
1323 auto candVec = getLocalCandidates(id);
1324 for (auto const& cand : candVec) {
1325 if (pimpl_->logger_)
1326 pimpl_->logger_->debug("[ice:{}] Using local candidate {:s} for comp {:d}",
1327 fmt::ptr(pimpl_), cand, id);
1328 }
1329 }
1330
1331 if (pimpl_->logger_)
1332 pimpl_->logger_->debug("[ice:{}] negotiation starting ({:u} remote candidates)",
1333 fmt::ptr(pimpl_), sdp.candidates.size());
1334 pj_str_t ufrag, pwd;
1335
1336 std::vector<IceCandidate> rem_candidates;
1337 rem_candidates.reserve(sdp.candidates.size());
1338 IceCandidate cand;
1339 for (const auto& line : sdp.candidates) {
1340 if (parseIceAttributeLine(0, line, cand))
1341 rem_candidates.emplace_back(cand);
1342 }
1343
1344 auto status = pj_ice_strans_start_ice(pimpl_->icest_,
1345 pj_strset(&ufrag,
1346 (char*) sdp.ufrag.c_str(),
1347 sdp.ufrag.size()),
1348 pj_strset(&pwd, (char*) sdp.pwd.c_str(), sdp.pwd.size()),
1349 rem_candidates.size(),
1350 rem_candidates.data());
1351 if (status != PJ_SUCCESS) {
1352 if (pimpl_->logger_)
1353 pimpl_->logger_->error("[ice:{}] start failed: {:s}", fmt::ptr(pimpl_), sip_utils::sip_strerror(status));
1354 pimpl_->is_stopped_ = true;
1355 return false;
1356 }
1357
1358 return true;
1359}
1360
1361void
1362IceTransport::cancelOperations()
1363{
1364 pimpl_->cancelOperations();
1365}
1366
1367IpAddr
1368IceTransport::getLocalAddress(unsigned comp_id) const
1369{
1370 return pimpl_->getLocalAddress(comp_id);
1371}
1372
1373IpAddr
1374IceTransport::getRemoteAddress(unsigned comp_id) const
1375{
1376 // Return the default remote address if set.
1377 // Note that the default remote addresses are the addresses
1378 // set in the 'c=' and 'a=rtcp' lines of the received SDP.
1379 // See pj_ice_strans_sendto2() for more details.
1380 if (auto defaultAddr = pimpl_->getDefaultRemoteAddress(comp_id)) {
1381 return defaultAddr;
1382 }
1383
1384 return pimpl_->getRemoteAddress(comp_id);
1385}
1386
1387const IceTransport::Attribute
1388IceTransport::getLocalAttributes() const
1389{
1390 return {pimpl_->local_ufrag_, pimpl_->local_pwd_};
1391}
1392
1393std::vector<std::string>
1394IceTransport::getLocalCandidates(unsigned comp_id) const
1395{
1396 ASSERT_COMP_ID(comp_id, getComponentCount());
1397 std::vector<std::string> res;
1398 pj_ice_sess_cand cand[MAX_CANDIDATES];
1399 unsigned cand_cnt = PJ_ARRAY_SIZE(cand);
1400
1401 if (!isInitialized()) {
1402 return res;
1403 }
1404
1405 if (pj_ice_strans_enum_cands(pimpl_->icest_, comp_id, &cand_cnt, cand) != PJ_SUCCESS) {
1406 if (pimpl_->logger_)
1407 pimpl_->logger_->error("[ice:{}] pj_ice_strans_enum_cands() failed", fmt::ptr(pimpl_));
1408 return res;
1409 }
1410
1411 res.reserve(cand_cnt);
1412 for (unsigned i = 0; i < cand_cnt; ++i) {
1413 /** Section 4.5, RFC 6544 (https://tools.ietf.org/html/rfc6544)
1414 * candidate-attribute = "candidate" ":" foundation SP component-id
1415 * SP "TCP" SP priority SP connection-address SP port SP cand-type [SP
1416 * rel-addr] [SP rel-port] SP tcp-type-ext
1417 * *(SP extension-att-name SP
1418 * extension-att-value)
1419 *
1420 * tcp-type-ext = "tcptype" SP tcp-type
1421 * tcp-type = "active" / "passive" / "so"
1422 */
1423 char ipaddr[PJ_INET6_ADDRSTRLEN];
1424 std::string tcp_type;
1425 if (cand[i].transport != PJ_CAND_UDP) {
1426 tcp_type += " tcptype";
1427 switch (cand[i].transport) {
1428 case PJ_CAND_TCP_ACTIVE:
1429 tcp_type += " active";
1430 break;
1431 case PJ_CAND_TCP_PASSIVE:
1432 tcp_type += " passive";
1433 break;
1434 case PJ_CAND_TCP_SO:
1435 default:
1436 tcp_type += " so";
1437 break;
1438 }
1439 }
1440 res.emplace_back(
1441 fmt::format("{} {} {} {} {} {} typ {}{}",
1442 sip_utils::as_view(cand[i].foundation),
1443 cand[i].comp_id,
1444 (cand[i].transport == PJ_CAND_UDP ? "UDP" : "TCP"),
1445 cand[i].prio,
1446 pj_sockaddr_print(&cand[i].addr, ipaddr, sizeof(ipaddr), 0),
1447 pj_sockaddr_get_port(&cand[i].addr),
1448 pj_ice_get_cand_type_name(cand[i].type),
1449 tcp_type));
1450 }
1451
1452 return res;
1453}
1454std::vector<std::string>
1455IceTransport::getLocalCandidates(unsigned streamIdx, unsigned compId) const
1456{
1457 ASSERT_COMP_ID(compId, getComponentCount());
1458
1459 std::vector<std::string> res;
1460 pj_ice_sess_cand cand[MAX_CANDIDATES];
1461 unsigned cand_cnt = MAX_CANDIDATES;
1462
1463 if (not isInitialized()) {
1464 return res;
1465 }
1466
1467 // In the implementation, the component IDs are enumerated globally
1468 // (per SDP: 1, 2, 3, 4, ...). This is simpler because we create
1469 // only one pj_ice_strans instance. However, the component IDs are
1470 // enumerated per stream in the generated SDP (1, 2, 1, 2, ...) in
1471 // order to be compliant with the spec.
1472
1473 auto globalCompId = streamIdx * 2 + compId;
1474 if (pj_ice_strans_enum_cands(pimpl_->icest_, globalCompId, &cand_cnt, cand) != PJ_SUCCESS) {
1475 if (pimpl_->logger_)
1476 pimpl_->logger_->error("[ice:{}] pj_ice_strans_enum_cands() failed", fmt::ptr(pimpl_));
1477 return res;
1478 }
1479
1480 res.reserve(cand_cnt);
1481 // Build ICE attributes according to RFC 6544, section 4.5.
1482 for (unsigned i = 0; i < cand_cnt; ++i) {
1483 char ipaddr[PJ_INET6_ADDRSTRLEN];
1484 std::string tcp_type;
1485 if (cand[i].transport != PJ_CAND_UDP) {
1486 tcp_type += " tcptype";
1487 switch (cand[i].transport) {
1488 case PJ_CAND_TCP_ACTIVE:
1489 tcp_type += " active";
1490 break;
1491 case PJ_CAND_TCP_PASSIVE:
1492 tcp_type += " passive";
1493 break;
1494 case PJ_CAND_TCP_SO:
1495 default:
1496 tcp_type += " so";
1497 break;
1498 }
1499 }
1500 res.emplace_back(
1501 fmt::format("{} {} {} {} {} {} typ {}{}",
1502 sip_utils::as_view(cand[i].foundation),
1503 compId,
1504 (cand[i].transport == PJ_CAND_UDP ? "UDP" : "TCP"),
1505 cand[i].prio,
1506 pj_sockaddr_print(&cand[i].addr, ipaddr, sizeof(ipaddr), 0),
1507 pj_sockaddr_get_port(&cand[i].addr),
1508 pj_ice_get_cand_type_name(cand[i].type),
1509 tcp_type));
1510 }
1511
1512 return res;
1513}
1514
1515bool
1516IceTransport::parseIceAttributeLine(unsigned streamIdx,
1517 const std::string& line,
1518 IceCandidate& cand) const
1519{
1520 // Silently ignore empty lines
1521 if (line.empty())
1522 return false;
1523
1524 if (streamIdx >= pimpl_->streamsCount_) {
1525 throw std::runtime_error(fmt::format("Stream index {:d} is invalid!", streamIdx));
1526 }
1527
1528 int af, cnt;
1529 char foundation[32], transport[12], ipaddr[80], type[32], tcp_type[32];
1530 pj_str_t tmpaddr;
1531 unsigned comp_id, prio, port;
1532 pj_status_t status;
1533 pj_bool_t is_tcp = PJ_FALSE;
1534
1535 // Parse ICE attribute line according to RFC-6544 section 4.5.
1536 // TODO/WARNING: There is no fail-safe in case of malformed attributes.
1537 cnt = sscanf(line.c_str(),
1538 "%31s %u %11s %u %79s %u typ %31s tcptype %31s\n",
1539 foundation,
1540 &comp_id,
1541 transport,
1542 &prio,
1543 ipaddr,
1544 &port,
1545 type,
1546 tcp_type);
1547 if (cnt != 7 && cnt != 8) {
1548 if (pimpl_->logger_)
1549 pimpl_->logger_->error("[ice:{}] Invalid ICE candidate line: {:s}", fmt::ptr(pimpl_), line);
1550 return false;
1551 }
1552
1553 if (strcmp(transport, "TCP") == 0) {
1554 is_tcp = PJ_TRUE;
1555 }
1556
1557 pj_bzero(&cand, sizeof(IceCandidate));
1558
1559 if (strcmp(type, "host") == 0)
1560 cand.type = PJ_ICE_CAND_TYPE_HOST;
1561 else if (strcmp(type, "srflx") == 0)
1562 cand.type = PJ_ICE_CAND_TYPE_SRFLX;
1563 else if (strcmp(type, "prflx") == 0)
1564 cand.type = PJ_ICE_CAND_TYPE_PRFLX;
1565 else if (strcmp(type, "relay") == 0)
1566 cand.type = PJ_ICE_CAND_TYPE_RELAYED;
1567 else {
1568 if (pimpl_->logger_)
1569 pimpl_->logger_->warn("[ice:{}] invalid remote candidate type '{:s}'", fmt::ptr(pimpl_), type);
1570 return false;
1571 }
1572
1573 if (is_tcp) {
1574 if (strcmp(tcp_type, "active") == 0)
1575 cand.transport = PJ_CAND_TCP_ACTIVE;
1576 else if (strcmp(tcp_type, "passive") == 0)
1577 cand.transport = PJ_CAND_TCP_PASSIVE;
1578 else if (strcmp(tcp_type, "so") == 0)
1579 cand.transport = PJ_CAND_TCP_SO;
1580 else {
1581 if (pimpl_->logger_)
1582 pimpl_->logger_->warn("[ice:{}] invalid transport type type '{:s}'", fmt::ptr(pimpl_), tcp_type);
1583 return false;
1584 }
1585 } else {
1586 cand.transport = PJ_CAND_UDP;
1587 }
1588
1589 // If the component Id is enumerated relative to media, convert
1590 // it to absolute enumeration.
1591 if (comp_id <= pimpl_->compCountPerStream_) {
1592 comp_id += pimpl_->compCountPerStream_ * streamIdx;
1593 }
1594 cand.comp_id = (pj_uint8_t) comp_id;
1595
1596 cand.prio = prio;
1597
1598 if (strchr(ipaddr, ':'))
1599 af = pj_AF_INET6();
1600 else {
1601 af = pj_AF_INET();
1602 pimpl_->onlyIPv4Private_ &= IpAddr(ipaddr).isPrivate();
1603 }
1604
1605 tmpaddr = pj_str(ipaddr);
1606 pj_sockaddr_init(af, &cand.addr, NULL, 0);
1607 status = pj_sockaddr_set_str_addr(af, &cand.addr, &tmpaddr);
1608 if (status != PJ_SUCCESS) {
1609 if (pimpl_->logger_)
1610 pimpl_->logger_->warn("[ice:{}] invalid IP address '{:s}'", fmt::ptr(pimpl_), ipaddr);
1611 return false;
1612 }
1613
1614 pj_sockaddr_set_port(&cand.addr, (pj_uint16_t) port);
1615 pj_strdup2(pimpl_->pool_.get(), &cand.foundation, foundation);
1616
1617 return true;
1618}
1619
1620ssize_t
1621IceTransport::recv(unsigned compId, unsigned char* buf, size_t len, std::error_code& ec)
1622{
1623 ASSERT_COMP_ID(compId, getComponentCount());
1624 auto& io = pimpl_->compIO_[compId - 1];
1625 std::lock_guard<std::mutex> lk(io.mutex);
1626
1627 if (io.queue.empty()) {
1628 ec = std::make_error_code(std::errc::resource_unavailable_try_again);
1629 return -1;
1630 }
1631
1632 auto& packet = io.queue.front();
1633 const auto count = std::min(len, packet.data.size());
1634 std::copy_n(packet.data.begin(), count, buf);
1635 if (count == packet.data.size()) {
1636 io.queue.pop_front();
1637 } else {
1638 packet.data.erase(packet.data.begin(), packet.data.begin() + count);
1639 }
1640
1641 ec.clear();
1642 return count;
1643}
1644
1645ssize_t
1646IceTransport::recvfrom(unsigned compId, char* buf, size_t len, std::error_code& ec)
1647{
1648 ASSERT_COMP_ID(compId, getComponentCount());
1649 return pimpl_->peerChannels_.at(compId - 1).read(buf, len, ec);
1650}
1651
1652void
1653IceTransport::setOnRecv(unsigned compId, IceRecvCb cb)
1654{
1655 ASSERT_COMP_ID(compId, getComponentCount());
1656
1657 auto& io = pimpl_->compIO_[compId - 1];
1658 std::lock_guard<std::mutex> lk(io.mutex);
1659 io.recvCb = std::move(cb);
1660
1661 if (io.recvCb) {
1662 // Flush existing queue using the callback
1663 for (const auto& packet : io.queue)
1664 io.recvCb((uint8_t*) packet.data.data(), packet.data.size());
1665 io.queue.clear();
1666 }
1667}
1668
1669void
1670IceTransport::setOnShutdown(onShutdownCb&& cb)
1671{
1672 pimpl_->scb = cb;
1673}
1674
1675ssize_t
1676IceTransport::send(unsigned compId, const unsigned char* buf, size_t len)
1677{
1678 ASSERT_COMP_ID(compId, getComponentCount());
1679
1680 auto remote = getRemoteAddress(compId);
1681
1682 if (!remote) {
1683 if (pimpl_->logger_)
1684 pimpl_->logger_->error("[ice:{}] can't find remote address for component {:d}", fmt::ptr(pimpl_), compId);
1685 errno = EINVAL;
1686 return -1;
1687 }
1688
1689 std::unique_lock dlk(pimpl_->sendDataMutex_, std::defer_lock);
1690 if (isTCPEnabled())
1691 dlk.lock();
1692
1693 jami_tracepoint(ice_transport_send,
1694 reinterpret_cast<uint64_t>(this),
1695 compId,
1696 len,
1697 remote.toString().c_str());
1698
1699 auto status = pj_ice_strans_sendto2(pimpl_->icest_,
1700 compId,
1701 buf,
1702 len,
1703 remote.pjPtr(),
1704 remote.getLength());
1705
1706 jami_tracepoint(ice_transport_send_status, status);
1707
1708 if (status == PJ_EPENDING && isTCPEnabled()) {
1709 // NOTE; because we are in TCP, the sent size will count the header (2
1710 // bytes length).
1711 pimpl_->waitDataCv_.wait(dlk, [&] {
1712 return pimpl_->lastSentLen_ >= static_cast<pj_size_t>(len) or pimpl_->destroying_;
1713 });
1714 pimpl_->lastSentLen_ = 0;
1715 } else if (status != PJ_SUCCESS && status != PJ_EPENDING) {
1716 if (status == PJ_EBUSY) {
1717 errno = EAGAIN;
1718 } else {
1719 if (pimpl_->logger_)
1720 pimpl_->logger_->error("[ice:{}] ice send failed: {:s}", fmt::ptr(pimpl_), sip_utils::sip_strerror(status));
1721 errno = EIO;
1722 }
1723 return -1;
1724 }
1725
1726 return len;
1727}
1728
1729bool
1730IceTransport::waitForInitialization(std::chrono::milliseconds timeout)
1731{
1732 return pimpl_->_waitForInitialization(timeout);
1733}
1734
1735ssize_t
1736IceTransport::waitForData(unsigned compId, std::chrono::milliseconds timeout, std::error_code& ec)
1737{
1738 ASSERT_COMP_ID(compId, getComponentCount());
1739 return pimpl_->peerChannels_.at(compId - 1).wait(timeout, ec);
1740}
1741
1742bool
1743IceTransport::isTCPEnabled()
1744{
1745 return pimpl_->isTcpEnabled();
1746}
1747
1748ICESDP
1749IceTransport::parseIceCandidates(std::string_view sdp_msg)
1750{
1751 if (pimpl_->streamsCount_ != 1) {
1752 if (pimpl_->logger_)
Sébastien Blincf569402023-07-27 09:46:40 -04001753 pimpl_->logger_->error("Expected exactly one stream per SDP (found {} streams)", pimpl_->streamsCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001754 return {};
1755 }
1756
1757 ICESDP res;
1758 int nr = 0;
Adrien Béraud1ae60aa2023-07-07 09:55:09 -04001759 for (std::string_view line; dhtnet::getline(sdp_msg, line); nr++) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001760 if (nr == 0) {
1761 res.rem_ufrag = line;
1762 } else if (nr == 1) {
1763 res.rem_pwd = line;
1764 } else {
1765 IceCandidate cand;
1766 if (parseIceAttributeLine(0, std::string(line), cand)) {
1767 if (pimpl_->logger_)
1768 pimpl_->logger_->debug("[ice:{}] Add remote candidate: {}",
1769 fmt::ptr(pimpl_),
1770 line);
1771 res.rem_candidates.emplace_back(cand);
1772 }
1773 }
1774 }
1775 return res;
1776}
1777
1778void
1779IceTransport::setDefaultRemoteAddress(unsigned comp_id, const IpAddr& addr)
1780{
1781 pimpl_->setDefaultRemoteAddress(comp_id, addr);
1782}
1783
1784std::string
1785IceTransport::link() const
1786{
1787 return pimpl_->link();
1788}
1789
1790//==============================================================================
1791
Adrien Béraud89933c12023-07-26 14:53:30 -04001792IceTransportFactory::IceTransportFactory(const std::shared_ptr<Logger>& logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -04001793 : cp_(new pj_caching_pool(),
1794 [](pj_caching_pool* p) {
1795 pj_caching_pool_destroy(p);
1796 delete p;
1797 })
1798 , ice_cfg_()
Adrien Béraud89933c12023-07-26 14:53:30 -04001799 , logger_(logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -04001800{
1801 pj_caching_pool_init(cp_.get(), NULL, 0);
1802
1803 pj_ice_strans_cfg_default(&ice_cfg_);
1804 ice_cfg_.stun_cfg.pf = &cp_->factory;
1805
1806 // v2.4.5 of PJNATH has a default of 100ms but RFC 5389 since version 14 requires
1807 // a minimum of 500ms on fixed-line links. Our usual case is wireless links.
1808 // This solves too long ICE exchange by DHT.
1809 // Using 500ms with default PJ_STUN_MAX_TRANSMIT_COUNT (7) gives around 33s before timeout.
1810 ice_cfg_.stun_cfg.rto_msec = 500;
1811
1812 // See https://tools.ietf.org/html/rfc5245#section-8.1.1.2
1813 // If enabled, it may help speed-up the connectivity, but may cause
1814 // the nomination of sub-optimal pairs.
1815 ice_cfg_.opt.aggressive = PJ_FALSE;
1816}
1817
1818IceTransportFactory::~IceTransportFactory() {}
1819
1820std::shared_ptr<IceTransport>
1821IceTransportFactory::createTransport(std::string_view name)
1822{
1823 try {
Adrien Béraud89933c12023-07-26 14:53:30 -04001824 return std::make_shared<IceTransport>(name, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001825 } catch (const std::exception& e) {
1826 //JAMI_ERR("%s", e.what());
1827 return nullptr;
1828 }
1829}
1830
1831std::unique_ptr<IceTransport>
1832IceTransportFactory::createUTransport(std::string_view name)
1833{
1834 try {
Adrien Béraud89933c12023-07-26 14:53:30 -04001835 return std::make_unique<IceTransport>(name, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001836 } catch (const std::exception& e) {
1837 //JAMI_ERR("%s", e.what());
1838 return nullptr;
1839 }
1840}
1841
1842//==============================================================================
1843
1844void
1845IceSocket::close()
1846{
1847 if (ice_transport_)
1848 ice_transport_->setOnRecv(compId_, {});
1849 ice_transport_.reset();
1850}
1851
1852ssize_t
1853IceSocket::send(const unsigned char* buf, size_t len)
1854{
1855 if (not ice_transport_)
1856 return -1;
1857 return ice_transport_->send(compId_, buf, len);
1858}
1859
1860ssize_t
1861IceSocket::waitForData(std::chrono::milliseconds timeout)
1862{
1863 if (not ice_transport_)
1864 return -1;
1865
1866 std::error_code ec;
1867 return ice_transport_->waitForData(compId_, timeout, ec);
1868}
1869
1870void
1871IceSocket::setOnRecv(IceRecvCb cb)
1872{
1873 if (ice_transport_)
1874 ice_transport_->setOnRecv(compId_, cb);
1875}
1876
1877uint16_t
1878IceSocket::getTransportOverhead()
1879{
1880 if (not ice_transport_)
1881 return 0;
1882
1883 return (ice_transport_->getRemoteAddress(compId_).getFamily() == AF_INET) ? IPV4_HEADER_SIZE
1884 : IPV6_HEADER_SIZE;
1885}
1886
1887void
1888IceSocket::setDefaultRemoteAddress(const IpAddr& addr)
1889{
1890 if (ice_transport_)
1891 ice_transport_->setDefaultRemoteAddress(compId_, addr);
1892}
1893
Sébastien Blin464bdff2023-07-19 08:02:53 -04001894} // namespace dhtnet