Line data Source code
1 : /*
2 : * Copyright (C) 2004-2026 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 :
18 : #include "sip/sipvoiplink.h"
19 :
20 : #ifdef HAVE_CONFIG_H
21 : #include "config.h"
22 : #endif
23 :
24 : #include "sdp.h"
25 : #include "sip/sipcall.h"
26 : #include "sip/sipaccount.h"
27 :
28 : #include "jamidht/jamiaccount.h"
29 :
30 : #include "manager.h"
31 :
32 : #include "im/instant_messaging.h"
33 :
34 : #include "pres_sub_server.h"
35 :
36 : #include "connectivity/sip_utils.h"
37 : #include "string_utils.h"
38 : #include "logger.h"
39 :
40 : #include <dhtnet/ip_utils.h>
41 : #include <opendht/thread_pool.h>
42 :
43 : #include <pjsip/sip_endpoint.h>
44 : #include <pjsip/sip_uri.h>
45 :
46 : #include <pjsip-simple/presence.h>
47 : #include <pjsip-simple/publish.h>
48 :
49 : // Only PJSIP 2.10 is supported.
50 : #if PJ_VERSION_NUM < (2 << 24 | 10 << 16)
51 : #error "Unsupported PJSIP version (requires version 2.10+)"
52 : #endif
53 :
54 : #include <cstddef>
55 : #include <regex>
56 :
57 : namespace jami {
58 :
59 : using sip_utils::CONST_PJ_STR;
60 :
61 : /**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/
62 :
63 : static pjsip_endpoint* endpt_;
64 : static pjsip_module mod_ua_;
65 :
66 : static void invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* e);
67 : static void outgoing_request_forked_cb(pjsip_inv_session* inv, pjsip_event* e);
68 : static void transaction_state_changed_cb(pjsip_inv_session* inv, pjsip_transaction* tsx, pjsip_event* e);
69 : // Called when an SDP offer is found in answer. This will occur
70 : // when we send an empty invite (no SDP). In this case, we should
71 : // expect an offer in a the 200 OK message
72 : static void on_rx_offer2(pjsip_inv_session* inv, struct pjsip_inv_on_rx_offer_cb_param* param);
73 : // Called when a re-invite is received
74 : static pj_status_t reinvite_received_cb(pjsip_inv_session* inv, const pjmedia_sdp_session* offer, pjsip_rx_data* rdata);
75 : // Called to request an SDP offer if the peer sent an invite or
76 : // a re-invite with no SDP. In this, we must provide an offer in
77 : // the answer (200 OK) if we accept the call
78 : static void sdp_create_offer_cb(pjsip_inv_session* inv, pjmedia_sdp_session** p_offer);
79 : // Called to report media (SDP) negotiation result
80 : static void sdp_media_update_cb(pjsip_inv_session* inv, pj_status_t status);
81 :
82 : static std::shared_ptr<SIPCall> getCallFromInvite(pjsip_inv_session* inv);
83 : #ifdef DEBUG_SIP_REQUEST_MSG
84 : static void processInviteResponseHelper(pjsip_inv_session* inv, pjsip_event* e);
85 : #endif
86 :
87 : static pj_bool_t
88 5 : handleIncomingOptions(pjsip_rx_data* rdata)
89 : {
90 : pjsip_tx_data* tdata;
91 :
92 5 : auto* dlg = pjsip_rdata_get_dlg(rdata);
93 5 : if (dlg) {
94 0 : JAMI_INFO("Processing in-dialog option request");
95 0 : if (pjsip_dlg_create_response(dlg, rdata, PJSIP_SC_OK, NULL, &tdata) != PJ_SUCCESS) {
96 0 : JAMI_ERR("Failed to create in-dialog response for option request");
97 0 : return PJ_FALSE;
98 : }
99 : } else {
100 5 : JAMI_INFO("Processing out-of-dialog option request");
101 5 : if (pjsip_endpt_create_response(endpt_, rdata, PJSIP_SC_OK, NULL, &tdata) != PJ_SUCCESS) {
102 0 : JAMI_ERR("Failed to create out-of-dialog response for option request");
103 0 : return PJ_FALSE;
104 : }
105 : }
106 :
107 : #define ADD_HDR(hdr) \
108 : do { \
109 : const pjsip_hdr* cap_hdr = hdr; \
110 : if (cap_hdr) \
111 : pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, cap_hdr)); \
112 : } while (0)
113 : #define ADD_CAP(cap) ADD_HDR(pjsip_endpt_get_capability(endpt_, cap, NULL));
114 :
115 5 : ADD_CAP(PJSIP_H_ALLOW);
116 5 : ADD_CAP(PJSIP_H_ACCEPT);
117 5 : ADD_CAP(PJSIP_H_SUPPORTED);
118 5 : ADD_HDR(pjsip_evsub_get_allow_events_hdr(NULL));
119 :
120 5 : if (dlg) {
121 0 : if (pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata) != PJ_SUCCESS) {
122 0 : JAMI_ERR("Failed to send in-dialog response for option request");
123 0 : return PJ_FALSE;
124 : }
125 :
126 0 : JAMI_INFO("Sent in-dialog response for option request");
127 0 : return PJ_TRUE;
128 : }
129 :
130 : pjsip_response_addr res_addr;
131 5 : pjsip_get_response_addr(tdata->pool, rdata, &res_addr);
132 :
133 5 : if (pjsip_endpt_send_response(endpt_, &res_addr, tdata, NULL, NULL) != PJ_SUCCESS) {
134 0 : pjsip_tx_data_dec_ref(tdata);
135 0 : JAMI_ERR("Failed to send out-of-dialog response for option request");
136 0 : return PJ_FALSE;
137 : }
138 :
139 5 : JAMI_INFO("Sent out-of-dialog response for option request");
140 5 : return PJ_TRUE;
141 : }
142 :
143 : // return PJ_FALSE so that eventually other modules will handle these requests
144 : // TODO: move Voicemail to separate module
145 : static pj_bool_t
146 0 : transaction_response_cb(pjsip_rx_data* rdata)
147 : {
148 0 : pjsip_dialog* dlg = pjsip_rdata_get_dlg(rdata);
149 :
150 0 : if (!dlg)
151 0 : return PJ_FALSE;
152 :
153 0 : pjsip_transaction* tsx = pjsip_rdata_get_tsx(rdata);
154 :
155 0 : if (!tsx or tsx->method.id != PJSIP_INVITE_METHOD)
156 0 : return PJ_FALSE;
157 :
158 0 : if (tsx->status_code / 100 == 2) {
159 : /**
160 : * Send an ACK message inside a transaction. PJSIP send automatically, non-2xx ACK response.
161 : * ACK for a 2xx response must be send using this method.
162 : */
163 : pjsip_tx_data* tdata;
164 :
165 0 : if (rdata->msg_info.cseq) {
166 0 : pjsip_dlg_create_request(dlg, &pjsip_ack_method, rdata->msg_info.cseq->cseq, &tdata);
167 0 : pjsip_dlg_send_request(dlg, tdata, -1, NULL);
168 : }
169 : }
170 :
171 0 : return PJ_FALSE;
172 : }
173 :
174 : static pj_status_t
175 0 : try_respond_stateless(pjsip_endpoint* endpt,
176 : pjsip_rx_data* rdata,
177 : int st_code,
178 : const pj_str_t* st_text,
179 : const pjsip_hdr* hdr_list,
180 : const pjsip_msg_body* body)
181 : {
182 : /* Check that no UAS transaction has been created for this request.
183 : * If UAS transaction has been created for this request, application
184 : * MUST send the response statefully using that transaction.
185 : */
186 0 : if (!pjsip_rdata_get_tsx(rdata))
187 0 : return pjsip_endpt_respond_stateless(endpt, rdata, st_code, st_text, hdr_list, body);
188 : else
189 0 : JAMI_ERR("Transaction has been created for this request, send response "
190 : "statefully instead");
191 :
192 0 : return !PJ_SUCCESS;
193 : }
194 :
195 : template<typename T>
196 : static bool
197 105 : is_uninitialized(std::weak_ptr<T> const& weak)
198 : {
199 : using wt = std::weak_ptr<T>;
200 105 : return !weak.owner_before(wt {}) && !wt {}.owner_before(weak);
201 : }
202 :
203 : static pj_bool_t
204 105 : transaction_request_cb(pjsip_rx_data* rdata)
205 : {
206 105 : if (!rdata or !rdata->msg_info.msg) {
207 0 : JAMI_ERR("rx_data is NULL");
208 0 : return PJ_FALSE;
209 : }
210 :
211 105 : pjsip_method* method = &rdata->msg_info.msg->line.req.method;
212 :
213 105 : if (!method) {
214 0 : JAMI_ERR("method is NULL");
215 0 : return PJ_FALSE;
216 : }
217 :
218 105 : if (method->id == PJSIP_ACK_METHOD && pjsip_rdata_get_dlg(rdata))
219 0 : return PJ_FALSE;
220 :
221 105 : if (!rdata->msg_info.to or !rdata->msg_info.from or !rdata->msg_info.via) {
222 0 : JAMI_ERR("Missing From, To or Via fields");
223 0 : return PJ_FALSE;
224 : }
225 :
226 105 : auto* const sip_to_uri = reinterpret_cast<pjsip_sip_uri*>(pjsip_uri_get_uri(rdata->msg_info.to->uri));
227 105 : auto* const sip_from_uri = reinterpret_cast<pjsip_sip_uri*>(pjsip_uri_get_uri(rdata->msg_info.from->uri));
228 105 : const pjsip_host_port& sip_via = rdata->msg_info.via->sent_by;
229 :
230 105 : if (!sip_to_uri or !sip_from_uri or !sip_via.host.ptr) {
231 0 : JAMI_ERR("NULL URI");
232 0 : return PJ_FALSE;
233 : }
234 :
235 105 : std::string_view toUsername(sip_to_uri->user.ptr, sip_to_uri->user.slen);
236 105 : std::string_view toHost(sip_to_uri->host.ptr, sip_to_uri->host.slen);
237 105 : std::string_view viaHostname(sip_via.host.ptr, sip_via.host.slen);
238 105 : const std::string_view remote_user(sip_from_uri->user.ptr, sip_from_uri->user.slen);
239 105 : const std::string_view remote_hostname(sip_from_uri->host.ptr, sip_from_uri->host.slen);
240 105 : std::string peerNumber;
241 105 : if (not remote_user.empty() and not remote_hostname.empty())
242 105 : peerNumber = remote_user + "@" + remote_hostname;
243 : else {
244 : char tmp[PJSIP_MAX_URL_SIZE];
245 0 : size_t length = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_from_uri, tmp, PJSIP_MAX_URL_SIZE);
246 0 : peerNumber = sip_utils::stripSipUriPrefix(std::string_view(tmp, length));
247 : }
248 :
249 105 : auto transport = Manager::instance().sipVoIPLink().sipTransportBroker->addTransport(rdata->tp_info.transport);
250 :
251 105 : std::shared_ptr<SIPAccountBase> account;
252 : // If transport account is default-constructed, guessing account is allowed
253 105 : const auto& waccount = transport ? transport->getAccount() : std::weak_ptr<SIPAccountBase> {};
254 105 : if (is_uninitialized(waccount)) {
255 15 : account = Manager::instance().sipVoIPLink().guessAccount(toUsername, viaHostname, remote_hostname);
256 15 : if (not account)
257 0 : return PJ_FALSE;
258 15 : if (not transport and account->getAccountType() == SIPAccount::ACCOUNT_TYPE) {
259 0 : if (not(transport = std::static_pointer_cast<SIPAccount>(account)->getTransport())) {
260 0 : JAMI_ERR("No suitable transport to answer this call.");
261 0 : return PJ_FALSE;
262 : }
263 0 : JAMI_WARN("Using transport from account.");
264 : }
265 90 : } else if (!(account = waccount.lock())) {
266 0 : JAMI_ERR("Dropping SIP request: account is expired.");
267 0 : return PJ_FALSE;
268 : }
269 :
270 105 : pjsip_msg_body* body = rdata->msg_info.msg->body;
271 :
272 105 : if (method->id == PJSIP_OTHER_METHOD) {
273 0 : std::string_view request = sip_utils::as_view(method->name);
274 :
275 0 : if (request.find(sip_utils::SIP_METHODS::NOTIFY) != std::string_view::npos) {
276 0 : if (body and body->data) {
277 0 : std::string_view body_view(static_cast<char*>(body->data), body->len);
278 0 : auto pos = body_view.find("Voice-Message: ");
279 0 : if (pos != std::string_view::npos) {
280 0 : int newCount {0};
281 0 : int oldCount {0};
282 0 : int urgentCount {0};
283 0 : std::string sp(body_view.substr(pos));
284 0 : int ret = sscanf(sp.c_str(), "Voice-Message: %d/%d (%d/", &newCount, &oldCount, &urgentCount);
285 :
286 : // According to rfc3842
287 : // urgent messages are optional
288 0 : if (ret >= 2)
289 0 : emitSignal<libjami::CallSignal::VoiceMailNotify>(account->getAccountID(),
290 : newCount,
291 : oldCount,
292 : urgentCount);
293 0 : }
294 : }
295 0 : } else if (request.find(sip_utils::SIP_METHODS::MESSAGE) != std::string_view::npos) {
296 : // Reply 200 immediately (RFC 3428, ch. 7)
297 0 : try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, nullptr, nullptr, nullptr);
298 : // Process message content in case of multi-part body
299 0 : auto payloads = im::parseSipMessage(rdata->msg_info.msg);
300 0 : if (payloads.size() > 0) {
301 0 : constexpr pj_str_t STR_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("Message-ID");
302 0 : auto* msgId = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
303 : &STR_MESSAGE_ID,
304 : nullptr);
305 0 : std::string id = {};
306 0 : if (!msgId) {
307 : // Supports IMDN message format https://tools.ietf.org/html/rfc5438#section-7.1.1.3
308 0 : constexpr pj_str_t STR_IMDN_MESSAGE_ID = jami::sip_utils::CONST_PJ_STR("imdn.Message-ID");
309 0 : msgId = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
310 : &STR_IMDN_MESSAGE_ID,
311 : nullptr);
312 : }
313 0 : if (msgId)
314 0 : id = std::string(msgId->hvalue.ptr, msgId->hvalue.slen);
315 :
316 0 : if (not id.empty()) {
317 : try {
318 : // Mark message as treated
319 0 : auto intid = from_hex_string(id);
320 0 : auto acc = std::dynamic_pointer_cast<JamiAccount>(account);
321 0 : if (acc and acc->isMessageTreated(intid))
322 0 : return PJ_FALSE;
323 0 : } catch (const std::exception& e) {
324 0 : JAMI_WARNING("[Account {}] Couldn't treat message {}: {}",
325 : account->getAccountID(),
326 : from_hex_string(id),
327 : e.what());
328 0 : }
329 : }
330 0 : account->onTextMessage(id, peerNumber, transport->getTlsInfos().peerCert, payloads);
331 0 : }
332 0 : return PJ_FALSE;
333 0 : }
334 :
335 0 : try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, NULL, NULL, NULL);
336 :
337 0 : return PJ_FALSE;
338 105 : } else if (method->id == PJSIP_OPTIONS_METHOD) {
339 5 : return handleIncomingOptions(rdata);
340 100 : } else if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) {
341 0 : try_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL);
342 0 : return PJ_FALSE;
343 : }
344 :
345 100 : if (method->id == PJSIP_INVITE_METHOD) {
346 : // Log headers of received INVITE
347 100 : JAMI_INFO("Received a SIP INVITE request");
348 100 : sip_utils::logMessageHeaders(&rdata->msg_info.msg->hdr);
349 : }
350 :
351 100 : pjmedia_sdp_session* r_sdp {nullptr};
352 100 : if (body) {
353 100 : if (pjmedia_sdp_parse(rdata->tp_info.pool, (char*) body->data, body->len, &r_sdp) != PJ_SUCCESS) {
354 0 : JAMI_WARN("Failed to parse the SDP in offer");
355 0 : r_sdp = nullptr;
356 : }
357 : }
358 :
359 100 : if (not account->hasActiveCodec(MEDIA_AUDIO)) {
360 0 : try_respond_stateless(endpt_, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, NULL, NULL);
361 0 : return PJ_FALSE;
362 : }
363 :
364 : // Verify that we can handle the request
365 100 : unsigned options = 0;
366 :
367 100 : if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, endpt_, NULL) != PJ_SUCCESS) {
368 0 : JAMI_ERR("Unable to verify INVITE request in secure dialog.");
369 0 : try_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL);
370 0 : return PJ_FALSE;
371 : }
372 :
373 : // Build the initial media using the remote offer.
374 100 : auto localMediaList = Sdp::getMediaAttributeListFromSdp(r_sdp);
375 :
376 : // To enable video, it must be enabled in the remote and locally (i.e. in the account)
377 279 : for (auto& media : localMediaList) {
378 179 : if (media.type_ == MediaType::MEDIA_VIDEO) {
379 78 : media.enabled_ &= account->isVideoEnabled();
380 : }
381 : }
382 :
383 300 : auto call = account->newIncomingCall(std::string(remote_user),
384 0 : MediaAttribute::mediaAttributesToMediaMaps(localMediaList),
385 200 : transport);
386 100 : if (!call) {
387 0 : return PJ_FALSE;
388 : }
389 :
390 100 : call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
391 : // The username can be used to join specific calls in conversations
392 100 : call->toUsername(std::string(toUsername));
393 :
394 : // FIXME: for now, use the same address family as the SIP transport
395 100 : auto family = pjsip_transport_type_get_af(pjsip_transport_get_type_from_flag(transport->get()->flag));
396 :
397 100 : dhtnet::IpAddr addrSdp;
398 100 : if (account->getUPnPActive()) {
399 : /* use UPnP addr, or published addr if its set */
400 0 : addrSdp = account->getPublishedSameasLocal() ? account->getUPnPIpAddress() : account->getPublishedIpAddress();
401 : } else {
402 200 : addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal())
403 100 : ? account->getPublishedIpAddress()
404 100 : : dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
405 : }
406 :
407 : /* fallback on local address */
408 100 : if (not addrSdp)
409 0 : addrSdp = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
410 :
411 : // Try to obtain display name from From: header first, fallback on Contact:
412 100 : auto peerDisplayName = sip_utils::parseDisplayName(rdata->msg_info.from);
413 100 : if (peerDisplayName.empty()) {
414 0 : if (const auto* hdr = static_cast<const pjsip_contact_hdr*>(
415 0 : pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_CONTACT, nullptr))) {
416 0 : peerDisplayName = sip_utils::parseDisplayName(hdr);
417 : }
418 : }
419 :
420 100 : call->setPeerNumber(peerNumber);
421 100 : call->setPeerUri(account->getToUri(peerNumber));
422 100 : call->setPeerDisplayName(peerDisplayName);
423 100 : call->setState(Call::ConnectionState::PROGRESSING);
424 100 : call->getSDP().setPublishedIP(addrSdp);
425 100 : call->setPeerAllowMethods(sip_utils::getPeerAllowMethods(rdata));
426 :
427 : // Set the temporary media list. Might change when we receive
428 : // the accept from the client.
429 100 : if (r_sdp != nullptr) {
430 100 : call->getSDP().setReceivedOffer(r_sdp);
431 : }
432 :
433 100 : pjsip_dialog* dialog = nullptr;
434 100 : if (pjsip_dlg_create_uas_and_inc_lock(pjsip_ua_instance(), rdata, nullptr, &dialog) != PJ_SUCCESS) {
435 0 : JAMI_ERR("Unable to create UAS");
436 0 : call.reset();
437 0 : try_respond_stateless(endpt_, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, nullptr, nullptr, nullptr);
438 0 : return PJ_FALSE;
439 : }
440 :
441 100 : pjsip_tpselector tp_sel = SIPVoIPLink::getTransportSelector(transport->get());
442 100 : if (!dialog or pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) {
443 0 : JAMI_ERR("Unable to set transport for dialog");
444 0 : if (dialog)
445 0 : pjsip_dlg_dec_lock(dialog);
446 0 : return PJ_FALSE;
447 : }
448 :
449 100 : pjsip_inv_session* inv = nullptr;
450 : // Create UAS for the invite.
451 : // The SDP is not set here, it will be done when the call is
452 : // accepted and the media attributes of the answer are known.
453 100 : pjsip_inv_create_uas(dialog, rdata, NULL, PJSIP_INV_SUPPORT_ICE, &inv);
454 100 : if (!inv) {
455 0 : JAMI_ERR("Call invite is not initialized");
456 0 : pjsip_dlg_dec_lock(dialog);
457 0 : return PJ_FALSE;
458 : }
459 :
460 : // dialog is now owned by invite
461 100 : pjsip_dlg_dec_lock(dialog);
462 :
463 100 : inv->mod_data[mod_ua_.id] = call.get();
464 100 : call->setInviteSession(inv);
465 :
466 : // Check whether Replaces header is present in the request and process accordingly.
467 : pjsip_dialog* replaced_dlg;
468 : pjsip_tx_data* response;
469 :
470 100 : if (pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE, &response) != PJ_SUCCESS) {
471 0 : JAMI_ERR("Something wrong with Replaces request.");
472 0 : call.reset();
473 :
474 : // Something wrong with the Replaces header.
475 0 : if (response) {
476 : pjsip_response_addr res_addr;
477 0 : pjsip_get_response_addr(response->pool, rdata, &res_addr);
478 0 : pjsip_endpt_send_response(endpt_, &res_addr, response, NULL, NULL);
479 : } else {
480 0 : try_respond_stateless(endpt_, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL);
481 : }
482 :
483 0 : return PJ_FALSE;
484 : }
485 :
486 : // Check if call has been transferred
487 100 : pjsip_tx_data* tdata = nullptr;
488 :
489 100 : if (pjsip_inv_initial_answer(call->inviteSession_.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata) != PJ_SUCCESS) {
490 0 : JAMI_ERR("Unable to create answer TRYING");
491 0 : return PJ_FALSE;
492 : }
493 :
494 : // Add user-agent header
495 100 : sip_utils::addUserAgentHeader(account->getUserAgentName(), tdata);
496 :
497 100 : if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
498 0 : JAMI_ERR("Unable to send msg TRYING");
499 0 : return PJ_FALSE;
500 : }
501 :
502 100 : call->setState(Call::ConnectionState::TRYING);
503 :
504 100 : if (pjsip_inv_answer(call->inviteSession_.get(), PJSIP_SC_RINGING, NULL, NULL, &tdata) != PJ_SUCCESS) {
505 0 : JAMI_ERR("Unable to create answer RINGING");
506 0 : return PJ_FALSE;
507 : }
508 :
509 100 : sip_utils::addContactHeader(call->getContactHeader(), tdata);
510 100 : if (pjsip_inv_send_msg(call->inviteSession_.get(), tdata) != PJ_SUCCESS) {
511 0 : JAMI_ERR("Unable to send msg RINGING");
512 0 : return PJ_FALSE;
513 : }
514 :
515 100 : call->setState(Call::ConnectionState::RINGING);
516 :
517 100 : Manager::instance().incomingCall(account->getAccountID(), *call);
518 :
519 100 : if (replaced_dlg) {
520 : // Get the INVITE session associated with the replaced dialog.
521 0 : auto* replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg);
522 :
523 : // Disconnect the "replaced" INVITE session.
524 0 : if (pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, nullptr, &tdata) == PJ_SUCCESS && tdata) {
525 0 : pjsip_inv_send_msg(replaced_inv, tdata);
526 : }
527 :
528 : // Close call at application level
529 0 : if (auto replacedCall = getCallFromInvite(replaced_inv))
530 0 : replacedCall->hangup(PJSIP_SC_OK);
531 : }
532 :
533 100 : return PJ_FALSE;
534 105 : }
535 :
536 : static void
537 334 : tp_state_callback(pjsip_transport* tp, pjsip_transport_state state, const pjsip_transport_state_info* info)
538 : {
539 334 : if (auto& broker = Manager::instance().sipVoIPLink().sipTransportBroker)
540 334 : broker->transportStateChanged(tp, state, info);
541 : else
542 0 : JAMI_ERR("SIPVoIPLink with invalid SipTransportBroker");
543 334 : }
544 :
545 : /*************************************************************************************************/
546 :
547 : pjsip_endpoint*
548 5 : SIPVoIPLink::getEndpoint()
549 : {
550 5 : return endpt_;
551 : }
552 :
553 : pjsip_module*
554 0 : SIPVoIPLink::getMod()
555 : {
556 0 : return &mod_ua_;
557 : }
558 :
559 : pj_pool_t*
560 8 : SIPVoIPLink::getPool() noexcept
561 : {
562 8 : return pool_.get();
563 : }
564 :
565 : pj_caching_pool*
566 1654 : SIPVoIPLink::getCachingPool() noexcept
567 : {
568 1654 : return &cp_;
569 : }
570 :
571 32 : SIPVoIPLink::SIPVoIPLink()
572 32 : : pool_(nullptr)
573 : {
574 : #define TRY(ret) \
575 : do { \
576 : if ((ret) != PJ_SUCCESS) \
577 : throw VoipLinkException(#ret " failed"); \
578 : } while (0)
579 :
580 32 : pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0);
581 32 : pool_.reset(pj_pool_create(&cp_.factory, PACKAGE, static_cast<long>(64) * 1024, 4096, nullptr));
582 32 : if (!pool_)
583 0 : throw VoipLinkException("UserAgent: Unable to initialize memory pool");
584 :
585 32 : TRY(pjsip_endpt_create(&cp_.factory, pj_gethostname()->ptr, &endpt_));
586 :
587 32 : auto ns = dhtnet::ip_utils::getLocalNameservers();
588 32 : if (not ns.empty()) {
589 32 : std::vector<pj_str_t> dns_nameservers(ns.size());
590 32 : std::vector<pj_uint16_t> dns_ports(ns.size());
591 96 : for (unsigned i = 0, n = ns.size(); i < n; i++) {
592 : char hbuf[NI_MAXHOST];
593 64 : if (auto ret
594 64 : = getnameinfo((sockaddr*) &ns[i], ns[i].getLength(), hbuf, sizeof(hbuf), nullptr, 0, NI_NUMERICHOST)) {
595 0 : JAMI_WARN("Error printing SIP nameserver: %s", gai_strerror(ret));
596 : } else {
597 64 : JAMI_DBG("Using SIP nameserver: %s", hbuf);
598 64 : pj_strdup2(pool_.get(), &dns_nameservers[i], hbuf);
599 64 : dns_ports[i] = ns[i].getPort();
600 : }
601 : }
602 : pj_dns_resolver* resv;
603 32 : if (auto ret = pjsip_endpt_create_resolver(endpt_, &resv)) {
604 0 : JAMI_WARN("Error creating SIP DNS resolver: %s", sip_utils::sip_strerror(ret).c_str());
605 : } else {
606 32 : if (auto ret = pj_dns_resolver_set_ns(resv,
607 32 : dns_nameservers.size(),
608 32 : dns_nameservers.data(),
609 32 : dns_ports.data())) {
610 0 : JAMI_WARN("Error setting SIP DNS servers: %s", sip_utils::sip_strerror(ret).c_str());
611 : } else {
612 32 : if (auto ret = pjsip_endpt_set_resolver(endpt_, resv)) {
613 0 : JAMI_WARN("Error setting PJSIP DNS resolver: %s", sip_utils::sip_strerror(ret).c_str());
614 : }
615 : }
616 : }
617 32 : }
618 :
619 32 : sipTransportBroker.reset(new SipTransportBroker(endpt_));
620 :
621 32 : auto status = pjsip_tpmgr_set_state_cb(pjsip_endpt_get_tpmgr(endpt_), tp_state_callback);
622 32 : if (status != PJ_SUCCESS)
623 0 : JAMI_ERR("Unable to set transport callback: %s", sip_utils::sip_strerror(status).c_str());
624 :
625 32 : TRY(pjsip_tsx_layer_init_module(endpt_));
626 32 : TRY(pjsip_ua_init_module(endpt_, nullptr));
627 32 : TRY(pjsip_replaces_init_module(endpt_)); // See the Replaces specification in RFC 3891
628 32 : TRY(pjsip_100rel_init_module(endpt_));
629 :
630 : // Initialize and register ring module
631 32 : mod_ua_.name = sip_utils::CONST_PJ_STR(PACKAGE);
632 32 : mod_ua_.id = -1;
633 32 : mod_ua_.priority = PJSIP_MOD_PRIORITY_APPLICATION;
634 32 : mod_ua_.on_rx_request = &transaction_request_cb;
635 32 : mod_ua_.on_rx_response = &transaction_response_cb;
636 32 : TRY(pjsip_endpt_register_module(endpt_, &mod_ua_));
637 :
638 32 : TRY(pjsip_evsub_init_module(endpt_));
639 32 : TRY(pjsip_xfer_init_module(endpt_));
640 :
641 : // presence/publish management
642 32 : TRY(pjsip_pres_init_module(endpt_, pjsip_evsub_instance()));
643 32 : TRY(pjsip_endpt_register_module(endpt_, &PresSubServer::mod_presence_server));
644 :
645 : static const pjsip_inv_callback inv_cb = {
646 : invite_session_state_changed_cb,
647 : outgoing_request_forked_cb,
648 : transaction_state_changed_cb,
649 : nullptr /* on_rx_offer */,
650 : on_rx_offer2,
651 : reinvite_received_cb,
652 : sdp_create_offer_cb,
653 : sdp_media_update_cb,
654 : nullptr /* on_send_ack */,
655 : nullptr /* on_redirected */,
656 : };
657 32 : TRY(pjsip_inv_usage_init(endpt_, &inv_cb));
658 :
659 : static constexpr pj_str_t allowed[] = {
660 : CONST_PJ_STR(sip_utils::SIP_METHODS::INFO),
661 : CONST_PJ_STR(sip_utils::SIP_METHODS::OPTIONS),
662 : CONST_PJ_STR(sip_utils::SIP_METHODS::MESSAGE),
663 : CONST_PJ_STR(sip_utils::SIP_METHODS::PUBLISH),
664 : };
665 :
666 32 : pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ALLOW, nullptr, PJ_ARRAY_SIZE(allowed), allowed);
667 :
668 : static constexpr pj_str_t text_plain = CONST_PJ_STR("text/plain");
669 32 : pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &text_plain);
670 :
671 : static constexpr pj_str_t accepted = CONST_PJ_STR("application/sdp");
672 32 : pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &accepted);
673 :
674 : static constexpr pj_str_t iscomposing = CONST_PJ_STR("application/im-iscomposing+xml");
675 32 : pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &iscomposing);
676 :
677 32 : TRY(pjsip_replaces_init_module(endpt_));
678 : #undef TRY
679 :
680 64 : sipThread_ = std::thread([this] {
681 7514 : while (running_)
682 7482 : handleEvents();
683 32 : });
684 :
685 32 : JAMI_DBG("SIPVoIPLink@%p", this);
686 32 : }
687 :
688 32 : SIPVoIPLink::~SIPVoIPLink() {}
689 :
690 : void
691 32 : SIPVoIPLink::shutdown()
692 : {
693 32 : JAMI_DBG("Shutting down SIPVoIPLink@%p…", this);
694 : // Remaining calls should not happen as possible upper callbacks
695 : // may be called and another instance of SIPVoIPLink can be re-created!
696 :
697 32 : if (not Manager::instance().callFactory.empty(Call::LinkType::SIP))
698 0 : JAMI_ERR("%zu SIP calls remains!", Manager::instance().callFactory.callCount(Call::LinkType::SIP));
699 :
700 32 : sipTransportBroker->shutdown();
701 32 : pjsip_tpmgr_set_state_cb(pjsip_endpt_get_tpmgr(endpt_), nullptr);
702 :
703 32 : running_ = false;
704 32 : sipThread_.join();
705 32 : pjsip_endpt_destroy(endpt_);
706 32 : pool_.reset();
707 32 : pj_caching_pool_destroy(&cp_);
708 32 : sipTransportBroker.reset();
709 :
710 32 : JAMI_DBG("SIPVoIPLink@%p shutdown successfully completed", this);
711 32 : }
712 :
713 : std::shared_ptr<SIPAccountBase>
714 15 : SIPVoIPLink::guessAccount(std::string_view userName, std::string_view server, std::string_view fromUri) const
715 : {
716 60 : JAMI_LOG("username = {}, server = {}, from = {}", userName, server, fromUri);
717 : // Attempt to find the account id from username and server name by full match
718 :
719 15 : std::shared_ptr<SIPAccountBase> result;
720 15 : std::shared_ptr<SIPAccountBase> IP2IPAccount;
721 15 : MatchRank best = MatchRank::NONE;
722 :
723 : // SIP accounts
724 42 : for (const auto& account : Manager::instance().getAllAccounts<SIPAccount>()) {
725 33 : const MatchRank match(account->matches(userName, server));
726 :
727 : // return right away if this is a full match
728 33 : if (match == MatchRank::FULL) {
729 6 : return account;
730 27 : } else if (match > best) {
731 9 : best = match;
732 9 : result = account;
733 18 : } else if (!IP2IPAccount && account->isIP2IP()) {
734 : // Allow IP2IP calls if an account exists for this type of calls
735 9 : IP2IPAccount = account;
736 : }
737 15 : }
738 :
739 9 : return result ? result : IP2IPAccount;
740 15 : }
741 :
742 : // Called from EventThread::run (not main thread)
743 : void
744 7482 : SIPVoIPLink::handleEvents()
745 : {
746 7482 : const pj_time_val timeout = {1, 0};
747 7482 : if (auto ret = pjsip_endpt_handle_events(endpt_, &timeout))
748 0 : JAMI_ERR("pjsip_endpt_handle_events failed with error %s", sip_utils::sip_strerror(ret).c_str());
749 7482 : }
750 :
751 : void
752 0 : SIPVoIPLink::registerKeepAliveTimer(pj_timer_entry& timer, pj_time_val& delay)
753 : {
754 0 : JAMI_DEBUG("Register new keepalive timer {:d} with delay {:d}", timer.id, delay.sec);
755 :
756 0 : if (timer.id == -1)
757 0 : JAMI_WARN("Timer already scheduled");
758 :
759 0 : switch (pjsip_endpt_schedule_timer(endpt_, &timer, &delay)) {
760 0 : case PJ_SUCCESS:
761 0 : break;
762 :
763 0 : default:
764 0 : JAMI_ERR("Unable to schedule new timer in pjsip endpoint");
765 :
766 : /* fallthrough */
767 0 : case PJ_EINVAL:
768 0 : JAMI_ERR("Invalid timer or delay entry");
769 0 : break;
770 :
771 0 : case PJ_EINVALIDOP:
772 0 : JAMI_ERR("Invalid timer entry, maybe already scheduled");
773 0 : break;
774 : }
775 0 : }
776 :
777 : void
778 0 : SIPVoIPLink::cancelKeepAliveTimer(pj_timer_entry& timer)
779 : {
780 0 : pjsip_endpt_cancel_timer(endpt_, &timer);
781 0 : }
782 :
783 : ///////////////////////////////////////////////////////////////////////////////
784 : // Private functions
785 : ///////////////////////////////////////////////////////////////////////////////
786 :
787 : static std::shared_ptr<SIPCall>
788 5796 : getCallFromInvite(pjsip_inv_session* inv)
789 : {
790 5796 : if (auto* call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]))
791 4954 : return std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this());
792 842 : return nullptr;
793 : }
794 :
795 : static void
796 954 : invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* ev)
797 : {
798 954 : if (inv == nullptr or ev == nullptr) {
799 0 : throw VoipLinkException("Unexpected null pointer");
800 : }
801 :
802 954 : auto call = getCallFromInvite(inv);
803 954 : if (not call)
804 96 : return;
805 :
806 858 : if (ev->type != PJSIP_EVENT_TSX_STATE and ev->type != PJSIP_EVENT_TX_MSG and ev->type != PJSIP_EVENT_RX_MSG) {
807 0 : JAMI_WARN("[call:%s] INVITE@%p state changed to %d (%s): unexpected event type %d",
808 : call->getCallId().c_str(),
809 : inv,
810 : inv->state,
811 : pjsip_inv_state_name(inv->state),
812 : ev->type);
813 0 : return;
814 : }
815 :
816 858 : decltype(pjsip_transaction::status_code) status_code = 0;
817 :
818 858 : if (ev->type == PJSIP_EVENT_TSX_STATE) {
819 680 : auto* const tsx = ev->body.tsx_state.tsx;
820 680 : status_code = tsx ? tsx->status_code : PJSIP_SC_NOT_FOUND;
821 680 : const pj_str_t* description = pjsip_get_status_text(status_code);
822 :
823 2720 : JAMI_LOG("[call:{}] INVITE@{:p} state changed to {:d} ({}): cause={:d}, tsx@{:p} status {:d} ({:s})",
824 : call->getCallId(),
825 : fmt::ptr(inv),
826 : (int) inv->state,
827 : pjsip_inv_state_name(inv->state),
828 : (int) inv->cause,
829 : fmt::ptr(tsx),
830 : status_code,
831 : sip_utils::as_view(*description));
832 178 : } else if (ev->type == PJSIP_EVENT_TX_MSG) {
833 356 : JAMI_LOG("[call:{}] INVITE@{:p} state changed to {:d} ({:s}): cause={:d} (TX_MSG)",
834 : call->getCallId(),
835 : fmt::ptr(inv),
836 : (int) inv->state,
837 : pjsip_inv_state_name(inv->state),
838 : (int) inv->cause);
839 : }
840 858 : pjsip_rx_data* rdata {nullptr};
841 858 : if (ev->type == PJSIP_EVENT_RX_MSG) {
842 89 : rdata = ev->body.rx_msg.rdata;
843 769 : } else if (ev->type == PJSIP_EVENT_TSX_STATE and ev->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
844 280 : rdata = ev->body.tsx_state.src.rdata;
845 : }
846 858 : if (rdata != nullptr) {
847 369 : call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
848 369 : auto methods = sip_utils::getPeerAllowMethods(rdata);
849 369 : if (not methods.empty()) {
850 193 : call->setPeerAllowMethods(std::move(methods));
851 : }
852 369 : }
853 :
854 858 : switch (inv->state) {
855 199 : case PJSIP_INV_STATE_EARLY:
856 199 : if (status_code == PJSIP_SC_RINGING)
857 199 : call->onPeerRinging();
858 199 : break;
859 :
860 178 : case PJSIP_INV_STATE_CONFIRMED:
861 : // After we sent or received a ACK - The connection is established
862 178 : call->onAnswered();
863 178 : break;
864 :
865 101 : case PJSIP_INV_STATE_DISCONNECTED:
866 101 : switch (inv->cause) {
867 : // When a peer's device replies busy
868 2 : case PJSIP_SC_BUSY_HERE:
869 2 : call->onBusyHere();
870 2 : break;
871 : // When the peer manually refuse the call
872 4 : case PJSIP_SC_DECLINE:
873 : case PJSIP_SC_BUSY_EVERYWHERE:
874 4 : if (inv->role != PJSIP_ROLE_UAC)
875 2 : break;
876 : // close call
877 2 : call->onClosed();
878 2 : break;
879 : // The call terminates normally - BYE / CANCEL
880 93 : case PJSIP_SC_OK:
881 : case PJSIP_SC_REQUEST_TERMINATED:
882 93 : call->onClosed();
883 93 : break;
884 :
885 : // Error/unhandled conditions
886 2 : default:
887 2 : call->onFailure(inv->cause);
888 2 : break;
889 : }
890 101 : break;
891 :
892 380 : default:
893 380 : break;
894 : }
895 954 : }
896 :
897 : static void
898 27 : on_rx_offer2(pjsip_inv_session* inv, struct pjsip_inv_on_rx_offer_cb_param* param)
899 : {
900 27 : if (not param or not param->offer) {
901 0 : JAMI_ERR("Invalid offer");
902 27 : return;
903 : }
904 :
905 27 : auto call = getCallFromInvite(inv);
906 27 : if (not call)
907 0 : return;
908 :
909 : // This callback is called whenever a new media offer is found in a
910 : // SIP message, typically in a re-invite and in a '200 OK' (as a
911 : // response to an empty invite).
912 : // Here we only handle the second case. The first case is handled
913 : // in reinvite_received_cb.
914 27 : if (inv->cause != PJSIP_SC_OK) {
915 : // Silently ignore if it's not a '200 OK'
916 27 : return;
917 : }
918 :
919 0 : if (auto call = getCallFromInvite(inv)) {
920 0 : if (auto const& account = call->getAccount().lock()) {
921 0 : call->onReceiveOfferIn200OK(param->offer);
922 0 : }
923 0 : }
924 27 : }
925 :
926 : static pj_status_t
927 27 : reinvite_received_cb(pjsip_inv_session* inv, const pjmedia_sdp_session* offer, pjsip_rx_data* rdata)
928 : {
929 27 : if (!offer)
930 0 : return !PJ_SUCCESS;
931 27 : if (auto call = getCallFromInvite(inv)) {
932 27 : if (auto const& account = call->getAccount().lock()) {
933 27 : return call->onReceiveReinvite(offer, rdata);
934 27 : }
935 27 : }
936 :
937 : // Return success if there is no matching call. The re-invite
938 : // should be ignored.
939 0 : return PJ_SUCCESS;
940 : }
941 :
942 : static void
943 0 : sdp_create_offer_cb(pjsip_inv_session* inv, pjmedia_sdp_session** p_offer)
944 : {
945 0 : auto call = getCallFromInvite(inv);
946 0 : if (not call)
947 0 : return;
948 :
949 0 : auto account = call->getSIPAccount();
950 0 : if (not account) {
951 0 : JAMI_ERR("No account detected");
952 0 : return;
953 : }
954 :
955 0 : if (account->isEmptyOffersEnabled()) {
956 : // Skip if the client wants to send an empty offer.
957 0 : JAMI_DBG("Client requested to send an empty offer (no SDP)");
958 0 : return;
959 : }
960 :
961 0 : auto family = pj_AF_INET();
962 : // FIXME: for now, use the same address family as the SIP transport
963 0 : if (auto* dlg = inv->dlg) {
964 0 : if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
965 0 : if (auto* tr = dlg->tp_sel.u.transport)
966 0 : family = tr->local_addr.addr.sa_family;
967 0 : } else if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) {
968 0 : if (auto* tr = dlg->tp_sel.u.listener)
969 0 : family = tr->local_addr.addr.sa_family;
970 : }
971 : }
972 0 : auto ifaceAddr = dhtnet::ip_utils::getInterfaceAddr(account->getLocalInterface(), family);
973 :
974 0 : dhtnet::IpAddr address;
975 0 : if (account->getUPnPActive()) {
976 : /* use UPnP addr, or published addr if it's set */
977 0 : address = account->getPublishedSameasLocal() ? account->getUPnPIpAddress() : account->getPublishedIpAddress();
978 : } else {
979 0 : address = account->getPublishedSameasLocal() ? ifaceAddr : account->getPublishedIpAddress();
980 : }
981 :
982 : /* fallback on local address */
983 0 : if (not address)
984 0 : address = ifaceAddr;
985 :
986 0 : auto& sdp = call->getSDP();
987 0 : sdp.setPublishedIP(address);
988 :
989 : // This list should be provided by the client. Kept for backward compatibility.
990 0 : auto const& mediaList = call->getMediaAttributeList();
991 0 : if (mediaList.empty()) {
992 0 : throw VoipLinkException("Unexpected empty media attribute list");
993 : }
994 :
995 0 : JAMI_DBG("Creating a SDP offer using the following media:");
996 0 : for (auto const& media : mediaList) {
997 0 : JAMI_DBG("[call %s] Media %s", call->getCallId().c_str(), media.toString(true).c_str());
998 : }
999 :
1000 0 : const bool created = sdp.createOffer(mediaList);
1001 :
1002 0 : if (created and p_offer != nullptr)
1003 0 : *p_offer = sdp.getLocalSdpSession();
1004 0 : }
1005 :
1006 : static const pjmedia_sdp_session*
1007 231 : get_active_remote_sdp(pjsip_inv_session* inv)
1008 : {
1009 231 : const pjmedia_sdp_session* sdp_session {};
1010 :
1011 231 : if (pjmedia_sdp_neg_get_active_remote(inv->neg, &sdp_session) != PJ_SUCCESS) {
1012 0 : JAMI_ERR("Active remote not present");
1013 0 : return nullptr;
1014 : }
1015 :
1016 231 : if (pjmedia_sdp_validate(sdp_session) != PJ_SUCCESS) {
1017 0 : JAMI_ERR("Invalid remote SDP session");
1018 0 : return nullptr;
1019 : }
1020 :
1021 231 : return sdp_session;
1022 : }
1023 :
1024 : static const pjmedia_sdp_session*
1025 231 : get_active_local_sdp(pjsip_inv_session* inv)
1026 : {
1027 231 : const pjmedia_sdp_session* sdp_session {};
1028 :
1029 231 : if (pjmedia_sdp_neg_get_active_local(inv->neg, &sdp_session) != PJ_SUCCESS) {
1030 0 : JAMI_ERR("Active local not present");
1031 0 : return nullptr;
1032 : }
1033 :
1034 231 : if (pjmedia_sdp_validate(sdp_session) != PJ_SUCCESS) {
1035 0 : JAMI_ERR("Invalid local SDP session");
1036 0 : return nullptr;
1037 : }
1038 :
1039 231 : return sdp_session;
1040 : }
1041 :
1042 : // This callback is called after SDP offer/answer session has completed.
1043 : static void
1044 233 : sdp_media_update_cb(pjsip_inv_session* inv, pj_status_t status)
1045 : {
1046 233 : auto call = getCallFromInvite(inv);
1047 233 : if (not call)
1048 1 : return;
1049 :
1050 232 : JAMI_DBG("[call:%s] INVITE@%p media update: status %d", call->getCallId().c_str(), inv, status);
1051 :
1052 232 : if (status != PJ_SUCCESS) {
1053 1 : const int reason = inv->state != PJSIP_INV_STATE_NULL and inv->state != PJSIP_INV_STATE_CONFIRMED
1054 2 : ? PJSIP_SC_UNSUPPORTED_MEDIA_TYPE
1055 : : 0;
1056 :
1057 1 : JAMI_WARN("[call:%s] SDP offer failed, reason %d", call->getCallId().c_str(), reason);
1058 :
1059 1 : call->hangup(reason);
1060 1 : return;
1061 : }
1062 :
1063 : // Fetch SDP data from request
1064 231 : const auto* const localSDP = get_active_local_sdp(inv);
1065 231 : const auto* const remoteSDP = get_active_remote_sdp(inv);
1066 :
1067 : // Update our SDP manager
1068 231 : auto& sdp = call->getSDP();
1069 231 : sdp.setActiveLocalSdpSession(localSDP);
1070 231 : if (localSDP != nullptr) {
1071 231 : Sdp::printSession(localSDP, "Local active session:", sdp.getSdpDirection());
1072 : }
1073 :
1074 231 : sdp.setActiveRemoteSdpSession(remoteSDP);
1075 231 : if (remoteSDP != nullptr) {
1076 231 : Sdp::printSession(remoteSDP, "Remote active session:", sdp.getSdpDirection());
1077 : }
1078 :
1079 231 : call->onMediaNegotiationComplete();
1080 233 : }
1081 :
1082 : static void
1083 0 : outgoing_request_forked_cb(pjsip_inv_session* /*inv*/, pjsip_event* /*e*/)
1084 0 : {}
1085 :
1086 : static bool
1087 263 : handleMediaControl(SIPCall& call, pjsip_msg_body* body)
1088 : {
1089 : /*
1090 : * Incoming INFO request for media control.
1091 : */
1092 263 : constexpr pj_str_t STR_APPLICATION = CONST_PJ_STR("application");
1093 263 : constexpr pj_str_t STR_MEDIA_CONTROL_XML = CONST_PJ_STR("media_control+xml");
1094 :
1095 263 : if (body->len and pj_stricmp(&body->content_type.type, &STR_APPLICATION) == 0
1096 526 : and pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML) == 0) {
1097 263 : auto body_msg = std::string_view((char*) body->data, (size_t) body->len);
1098 :
1099 : /* Apply and answer the INFO request */
1100 : static constexpr auto PICT_FAST_UPDATE = "picture_fast_update"sv;
1101 : static constexpr auto STREAM_ID = "stream_id"sv;
1102 : static constexpr auto DEVICE_ORIENTATION = "device_orientation"sv;
1103 : static constexpr auto RECORDING_STATE = "recording_state"sv;
1104 : static constexpr auto MUTE_STATE = "mute_state"sv;
1105 : static constexpr auto VOICE_ACTIVITY = "voice_activity"sv;
1106 :
1107 263 : int streamIdx = -1;
1108 263 : if (body_msg.find(STREAM_ID) != std::string_view::npos) {
1109 : // Note: here we use the index of the RTP stream, not its label!
1110 : // Indeed, both sides will have different labels as they have different call IDs
1111 252 : static const std::regex STREAMID_REGEX("<stream_id>([0-9]+)</stream_id>");
1112 252 : std::svmatch matched_pattern;
1113 252 : std::regex_search(body_msg, matched_pattern, STREAMID_REGEX);
1114 252 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1115 : try {
1116 252 : streamIdx = std::stoi(matched_pattern[1]);
1117 0 : } catch (const std::exception& e) {
1118 0 : JAMI_WARN("Error parsing stream index: %s", e.what());
1119 0 : }
1120 : }
1121 252 : }
1122 :
1123 263 : if (body_msg.find(PICT_FAST_UPDATE) != std::string_view::npos) {
1124 205 : call.sendKeyframe(streamIdx);
1125 263 : return true;
1126 58 : } else if (body_msg.find(DEVICE_ORIENTATION) != std::string_view::npos) {
1127 47 : static const std::regex ORIENTATION_REGEX("device_orientation=([-+]?[0-9]+)");
1128 :
1129 47 : std::svmatch matched_pattern;
1130 47 : std::regex_search(body_msg, matched_pattern, ORIENTATION_REGEX);
1131 :
1132 47 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1133 : try {
1134 47 : int rotation = -std::stoi(matched_pattern[1]);
1135 47 : while (rotation <= -180)
1136 0 : rotation += 360;
1137 47 : while (rotation > 180)
1138 0 : rotation -= 360;
1139 47 : JAMI_WARN("Rotate video %d deg.", rotation);
1140 : #ifdef ENABLE_VIDEO
1141 47 : call.setRotation(streamIdx, rotation);
1142 : #endif
1143 0 : } catch (const std::exception& e) {
1144 0 : JAMI_WARN("Error parsing angle: %s", e.what());
1145 0 : }
1146 47 : return true;
1147 : }
1148 58 : } else if (body_msg.find(RECORDING_STATE) != std::string_view::npos) {
1149 6 : static const std::regex REC_REGEX("recording_state=([0-1])");
1150 6 : std::svmatch matched_pattern;
1151 6 : std::regex_search(body_msg, matched_pattern, REC_REGEX);
1152 :
1153 6 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1154 : try {
1155 6 : bool state = std::stoi(matched_pattern[1]);
1156 6 : call.peerRecording(state);
1157 0 : } catch (const std::exception& e) {
1158 0 : JAMI_WARN("Error parsing state remote recording: %s", e.what());
1159 0 : }
1160 6 : return true;
1161 : }
1162 11 : } else if (body_msg.find(MUTE_STATE) != std::string_view::npos) {
1163 5 : static const std::regex REC_REGEX("mute_state=([0-1])");
1164 5 : std::svmatch matched_pattern;
1165 5 : std::regex_search(body_msg, matched_pattern, REC_REGEX);
1166 :
1167 5 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1168 : try {
1169 5 : bool state = std::stoi(matched_pattern[1]);
1170 5 : call.peerMuted(state, streamIdx);
1171 0 : } catch (const std::exception& e) {
1172 0 : JAMI_WARN("Error parsing state remote mute: %s", e.what());
1173 0 : }
1174 5 : return true;
1175 : }
1176 5 : } else if (body_msg.find(VOICE_ACTIVITY) != std::string_view::npos) {
1177 0 : static const std::regex REC_REGEX("voice_activity=([0-1])");
1178 0 : std::svmatch matched_pattern;
1179 0 : std::regex_search(body_msg, matched_pattern, REC_REGEX);
1180 :
1181 0 : if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
1182 : try {
1183 0 : bool state = std::stoi(matched_pattern[1]);
1184 0 : call.peerVoice(state);
1185 0 : } catch (const std::exception& e) {
1186 0 : JAMI_WARN("Error parsing state remote voice: %s", e.what());
1187 0 : }
1188 0 : return true;
1189 : }
1190 0 : }
1191 : }
1192 :
1193 0 : return false;
1194 : }
1195 :
1196 : /**
1197 : * Helper function to process refer function on call transfer
1198 : */
1199 : static bool
1200 2 : transferCall(SIPCall& call, const std::string& refer_to)
1201 : {
1202 2 : const auto& callId = call.getCallId();
1203 2 : JAMI_WARN("[call:%s] Attempting to transfer to %s", callId.c_str(), refer_to.c_str());
1204 : try {
1205 4 : Manager::instance().newOutgoingCall(refer_to,
1206 4 : call.getAccountId(),
1207 4 : MediaAttribute::mediaAttributesToMediaMaps(call.getMediaAttributeList()));
1208 2 : Manager::instance().hangupCall(call.getAccountId(), callId);
1209 0 : } catch (const std::exception& e) {
1210 0 : JAMI_ERR("[call:%s] SIP transfer failed: %s", callId.c_str(), e.what());
1211 0 : return false;
1212 0 : }
1213 2 : return true;
1214 : }
1215 :
1216 : static void
1217 265 : replyToRequest(pjsip_inv_session* inv, pjsip_rx_data* rdata, int status_code)
1218 : {
1219 265 : const auto ret = pjsip_dlg_respond(inv->dlg, rdata, status_code, nullptr, nullptr, nullptr);
1220 265 : if (ret != PJ_SUCCESS)
1221 0 : JAMI_WARN("SIP: Failed to reply %d to request", status_code);
1222 265 : }
1223 :
1224 : static void
1225 2 : onRequestRefer(pjsip_inv_session* inv, pjsip_rx_data* rdata, pjsip_msg* msg, SIPCall& call)
1226 : {
1227 : static constexpr pj_str_t str_refer_to = CONST_PJ_STR("Refer-To");
1228 :
1229 2 : if (auto* refer_to = static_cast<pjsip_generic_string_hdr*>(
1230 2 : pjsip_msg_find_hdr_by_name(msg, &str_refer_to, nullptr))) {
1231 : // RFC 3515, 2.4.2: reply bad request if no or too many refer-to header.
1232 4 : if (static_cast<void*>(refer_to->next) == static_cast<void*>(&msg->hdr)
1233 2 : or !pjsip_msg_find_hdr_by_name(msg, &str_refer_to, refer_to->next)) {
1234 2 : replyToRequest(inv, rdata, PJSIP_SC_ACCEPTED);
1235 2 : transferCall(call, std::string(refer_to->hvalue.ptr, refer_to->hvalue.slen));
1236 :
1237 : // RFC 3515, 2.4.4: we MUST handle the processing using NOTIFY msgs
1238 : // But your current design doesn't permit that
1239 2 : return;
1240 : } else
1241 0 : JAMI_ERR("[call:%s] REFER: too many Refer-To headers", call.getCallId().c_str());
1242 : } else
1243 0 : JAMI_ERR("[call:%s] REFER: no Refer-To header", call.getCallId().c_str());
1244 :
1245 0 : replyToRequest(inv, rdata, PJSIP_SC_BAD_REQUEST);
1246 : }
1247 :
1248 : static void
1249 263 : onRequestInfo(pjsip_inv_session* inv, pjsip_rx_data* rdata, pjsip_msg* msg, SIPCall& call)
1250 : {
1251 263 : if (!msg->body or handleMediaControl(call, msg->body))
1252 263 : replyToRequest(inv, rdata, PJSIP_SC_OK);
1253 263 : }
1254 :
1255 : static void
1256 0 : onRequestNotify(pjsip_inv_session* /*inv*/, pjsip_rx_data* /*rdata*/, pjsip_msg* msg, SIPCall& call)
1257 : {
1258 0 : if (!msg->body)
1259 0 : return;
1260 :
1261 0 : const std::string bodyText {static_cast<char*>(msg->body->data), msg->body->len};
1262 0 : JAMI_DBG("[call:%s] NOTIFY body start - %p\n%s\n[call:%s] NOTIFY body end - %p",
1263 : call.getCallId().c_str(),
1264 : msg->body,
1265 : bodyText.c_str(),
1266 : call.getCallId().c_str(),
1267 : msg->body);
1268 :
1269 : // TODO
1270 0 : }
1271 :
1272 : static void
1273 4555 : transaction_state_changed_cb(pjsip_inv_session* inv, pjsip_transaction* tsx, pjsip_event* event)
1274 : {
1275 4555 : auto call = getCallFromInvite(inv);
1276 4554 : if (not call)
1277 746 : return;
1278 :
1279 : #ifdef DEBUG_SIP_REQUEST_MSG
1280 : processInviteResponseHelper(inv, event);
1281 : #endif
1282 :
1283 : // We process here only incoming request message
1284 3808 : if (tsx->role != PJSIP_ROLE_UAS or tsx->state != PJSIP_TSX_STATE_TRYING
1285 624 : or event->body.tsx_state.type != PJSIP_EVENT_RX_MSG) {
1286 3184 : return;
1287 : }
1288 :
1289 624 : auto* const rdata = event->body.tsx_state.src.rdata;
1290 624 : if (!rdata) {
1291 0 : JAMI_ERROR("[INVITE:{:p}] SIP RX request without rx data", fmt::ptr(inv));
1292 0 : return;
1293 : }
1294 :
1295 624 : auto* const msg = rdata->msg_info.msg;
1296 624 : if (msg->type != PJSIP_REQUEST_MSG) {
1297 0 : JAMI_ERROR("[INVITE:{:p}] SIP RX request without msg", fmt::ptr(inv));
1298 0 : return;
1299 : }
1300 :
1301 : // Using method name to dispatch
1302 624 : auto methodName = sip_utils::as_view(msg->line.req.method.name);
1303 2493 : JAMI_LOG("[INVITE:{:p}] RX SIP method {:d} ({:s})", fmt::ptr(inv), (int) msg->line.req.method.id, methodName);
1304 :
1305 : #ifdef DEBUG_SIP_REQUEST_MSG
1306 : char msgbuf[1000];
1307 : auto msgsize = pjsip_msg_print(msg, msgbuf, sizeof msgbuf);
1308 : if (msgsize > 0)
1309 : JAMI_LOG("{:s}", std::string_view(msgbuf, msgsize));
1310 : #endif // DEBUG_SIP_REQUEST_MSG
1311 :
1312 624 : if (methodName == sip_utils::SIP_METHODS::REFER)
1313 2 : onRequestRefer(inv, rdata, msg, *call);
1314 622 : else if (methodName == sip_utils::SIP_METHODS::INFO)
1315 263 : onRequestInfo(inv, rdata, msg, *call);
1316 359 : else if (methodName == sip_utils::SIP_METHODS::NOTIFY)
1317 0 : onRequestNotify(inv, rdata, msg, *call);
1318 359 : else if (methodName == sip_utils::SIP_METHODS::OPTIONS)
1319 0 : handleIncomingOptions(rdata);
1320 359 : else if (methodName == sip_utils::SIP_METHODS::MESSAGE) {
1321 238 : if (msg->body)
1322 476 : runOnMainThread([call, m = im::parseSipMessage(msg)]() mutable { call->onTextMessage(std::move(m)); });
1323 : }
1324 4554 : }
1325 :
1326 : #ifdef DEBUG_SIP_REQUEST_MSG
1327 : static void
1328 : processInviteResponseHelper(pjsip_inv_session* inv, pjsip_event* event)
1329 : {
1330 : if (event->body.tsx_state.type != PJSIP_EVENT_RX_MSG)
1331 : return;
1332 :
1333 : const auto rdata = event->body.tsx_state.src.rdata;
1334 : if (rdata == nullptr or rdata->msg_info.msg == nullptr)
1335 : return;
1336 :
1337 : const auto msg = rdata->msg_info.msg;
1338 : if (msg->type != PJSIP_RESPONSE_MSG)
1339 : return;
1340 :
1341 : // Only handle the following responses
1342 : switch (msg->line.status.code) {
1343 : case PJSIP_SC_TRYING:
1344 : case PJSIP_SC_RINGING:
1345 : case PJSIP_SC_OK:
1346 : break;
1347 : default:
1348 : return;
1349 : }
1350 :
1351 : JAMI_LOG("[INVITE:{:p}] SIP RX response: reason {:s}, status code {:d} {:s}",
1352 : fmt::ptr(inv),
1353 : sip_utils::as_view(msg->line.status.reason),
1354 : msg->line.status.code,
1355 : sip_utils::as_view(*pjsip_get_status_text(msg->line.status.code)));
1356 :
1357 : sip_utils::logMessageHeaders(&msg->hdr);
1358 : }
1359 : #endif
1360 :
1361 : int
1362 643 : SIPVoIPLink::getModId()
1363 : {
1364 643 : return mod_ua_.id;
1365 : }
1366 :
1367 : void
1368 0 : SIPVoIPLink::createSDPOffer(pjsip_inv_session* inv)
1369 : {
1370 0 : if (inv == nullptr) {
1371 0 : throw VoipLinkException("Invite session is unable to be null");
1372 : }
1373 0 : sdp_create_offer_cb(inv, nullptr);
1374 0 : }
1375 :
1376 : // Thread-safe DNS resolver callback mapping
1377 : class SafeResolveCallbackMap
1378 : {
1379 : public:
1380 : using ResolveCallback = std::function<void(pj_status_t, const pjsip_server_addresses*)>;
1381 :
1382 5 : void registerCallback(uintptr_t key, ResolveCallback&& cb)
1383 : {
1384 5 : std::lock_guard lk(mutex_);
1385 5 : cbMap_.emplace(key, std::move(cb));
1386 5 : }
1387 :
1388 5 : void process(uintptr_t key, pj_status_t status, const pjsip_server_addresses* addr)
1389 : {
1390 5 : std::lock_guard lk(mutex_);
1391 5 : auto it = cbMap_.find(key);
1392 5 : if (it != cbMap_.end()) {
1393 5 : it->second(status, addr);
1394 5 : cbMap_.erase(it);
1395 : }
1396 5 : }
1397 :
1398 : private:
1399 : std::mutex mutex_;
1400 : std::map<uintptr_t, ResolveCallback> cbMap_;
1401 : };
1402 :
1403 : static SafeResolveCallbackMap&
1404 10 : getResolveCallbackMap()
1405 : {
1406 10 : static SafeResolveCallbackMap map;
1407 10 : return map;
1408 : }
1409 :
1410 : static void
1411 5 : resolver_callback(pj_status_t status, void* token, const struct pjsip_server_addresses* addr)
1412 : {
1413 5 : getResolveCallbackMap().process((uintptr_t) token, status, addr);
1414 5 : }
1415 :
1416 : void
1417 5 : SIPVoIPLink::resolveSrvName(const std::string& name, pjsip_transport_type_e type, SrvResolveCallback&& cb)
1418 : {
1419 : // PJSIP limits hostname to be longer than PJ_MAX_HOSTNAME.
1420 : // But, resolver prefix the given name by a string like "_sip._udp."
1421 : // causing a check against PJ_MAX_HOSTNAME to be useless.
1422 : // It's not easy to pre-determinate as it's implementation dependent.
1423 : // So we just choose a security marge enough for most cases, preventing a crash later
1424 : // in the call of pjsip_endpt_resolve().
1425 5 : if (name.length() > (PJ_MAX_HOSTNAME - 12)) {
1426 0 : JAMI_ERR("Hostname is too long");
1427 0 : cb({});
1428 0 : return;
1429 : }
1430 :
1431 : // extract port if name is in form "server:port"
1432 : int port;
1433 : pj_ssize_t name_size;
1434 5 : const auto n = name.rfind(':');
1435 5 : if (n != std::string::npos) {
1436 0 : port = std::atoi(name.c_str() + n + 1);
1437 0 : name_size = static_cast<pj_ssize_t>(n);
1438 : } else {
1439 5 : port = 0;
1440 5 : name_size = static_cast<pj_ssize_t>(name.size());
1441 : }
1442 5 : JAMI_DBG("Attempt to resolve '%s' (port: %u)", name.c_str(), port);
1443 :
1444 5 : pjsip_host_info host_info {
1445 : /*.flag = */ 0,
1446 : /*.type = */ type,
1447 5 : /*.addr = */ {{(char*) name.c_str(), name_size}, port},
1448 5 : };
1449 :
1450 5 : const auto token = std::hash<std::string>()(name + std::to_string(type));
1451 5 : getResolveCallbackMap()
1452 5 : .registerCallback(token, [=, cb = std::move(cb)](pj_status_t s, const pjsip_server_addresses* r) {
1453 : try {
1454 5 : if (s != PJ_SUCCESS || !r) {
1455 0 : JAMI_WARN("Unable to resolve \"%s\" using pjsip_endpt_resolve, attempting getaddrinfo.",
1456 : name.c_str());
1457 0 : dht::ThreadPool::io().run([=, cb = std::move(cb)]() {
1458 0 : auto ips = dhtnet::ip_utils::getAddrList(name.c_str());
1459 0 : runOnMainThread(std::bind(cb, std::move(ips)));
1460 0 : });
1461 0 : } else {
1462 5 : std::vector<dhtnet::IpAddr> ips;
1463 5 : ips.reserve(r->count);
1464 10 : for (unsigned i = 0; i < r->count; i++)
1465 5 : ips.push_back(r->entry[i].addr);
1466 5 : cb(ips);
1467 5 : }
1468 0 : } catch (const std::exception& e) {
1469 0 : JAMI_ERR("Error resolving address: %s", e.what());
1470 0 : cb({});
1471 0 : }
1472 5 : });
1473 :
1474 5 : pjsip_endpt_resolve(endpt_, pool_.get(), &host_info, (void*) token, resolver_callback);
1475 : }
1476 :
1477 : #define RETURN_IF_NULL(A, ...) \
1478 : if ((A) == NULL) { \
1479 : JAMI_WARN(__VA_ARGS__); \
1480 : return; \
1481 : }
1482 :
1483 : #define RETURN_FALSE_IF_NULL(A, ...) \
1484 : if ((A) == NULL) { \
1485 : JAMI_WARN(__VA_ARGS__); \
1486 : return false; \
1487 : }
1488 :
1489 : void
1490 27 : SIPVoIPLink::findLocalAddressFromTransport(pjsip_transport* transport,
1491 : pjsip_transport_type_e transportType,
1492 : const std::string& host,
1493 : std::string& addr,
1494 : pj_uint16_t& port) const
1495 : {
1496 : // Initialize the SIP port with the default SIP port
1497 27 : port = pjsip_transport_get_default_port_for_type(transportType);
1498 :
1499 : // Initialize the SIP address with the hostname
1500 27 : addr = sip_utils::as_view(*pj_gethostname());
1501 :
1502 : // Update address and port with active transport
1503 27 : RETURN_IF_NULL(transport, "Transport is NULL in findLocalAddress, using local address %s :%d", addr.c_str(), port);
1504 :
1505 : // get the transport manager associated with the SIP enpoint
1506 27 : auto* tpmgr = pjsip_endpt_get_tpmgr(endpt_);
1507 27 : RETURN_IF_NULL(tpmgr,
1508 : "Transport manager is NULL in findLocalAddress, using local address %s :%d",
1509 : addr.c_str(),
1510 : port);
1511 :
1512 27 : const pj_str_t pjstring(CONST_PJ_STR(host));
1513 :
1514 27 : auto tp_sel = getTransportSelector(transport);
1515 27 : pjsip_tpmgr_fla2_param param = {transportType, &tp_sel, pjstring, PJ_FALSE, {nullptr, 0}, 0, nullptr};
1516 27 : if (pjsip_tpmgr_find_local_addr2(tpmgr, pool_.get(), ¶m) != PJ_SUCCESS) {
1517 0 : JAMI_WARN("Unable to retrieve local address and port from transport, using %s :%d", addr.c_str(), port);
1518 0 : return;
1519 : }
1520 :
1521 : // Update local address based on the transport type
1522 27 : addr = sip_utils::as_view(param.ret_addr);
1523 :
1524 : // Determine the local port based on transport information
1525 27 : port = param.ret_port;
1526 : }
1527 :
1528 : bool
1529 0 : SIPVoIPLink::findLocalAddressFromSTUN(
1530 : pjsip_transport* transport, pj_str_t* stunServerName, int stunPort, std::string& addr, pj_uint16_t& port) const
1531 : {
1532 : // WARN: this code use pjstun_get_mapped_addr2 that works
1533 : // in IPv4 only.
1534 : // WARN: this function is blocking (network request).
1535 :
1536 : // Initialize the sip port with the default SIP port
1537 0 : port = sip_utils::DEFAULT_SIP_PORT;
1538 :
1539 : // Get Local IP address
1540 0 : auto localIp = dhtnet::ip_utils::getLocalAddr(pj_AF_INET());
1541 0 : if (not localIp) {
1542 0 : JAMI_WARN("Failed to find local IP");
1543 0 : return false;
1544 : }
1545 :
1546 0 : addr = localIp.toString();
1547 :
1548 : // Update address and port with active transport
1549 0 : RETURN_FALSE_IF_NULL(transport,
1550 : "Transport is NULL in findLocalAddress, using local address %s:%u",
1551 : addr.c_str(),
1552 : port);
1553 :
1554 0 : JAMI_DBG("STUN mapping of '%s:%u'", addr.c_str(), port);
1555 :
1556 : pj_sockaddr_in mapped_addr;
1557 0 : pj_sock_t sipSocket = pjsip_udp_transport_get_socket(transport);
1558 0 : const pjstun_setting stunOpt = {PJ_TRUE, localIp.getFamily(), *stunServerName, stunPort, *stunServerName, stunPort};
1559 0 : pj_status_t stunStatus = pjstun_get_mapped_addr2(&cp_.factory, &stunOpt, 1, &sipSocket, &mapped_addr);
1560 :
1561 0 : switch (stunStatus) {
1562 0 : case PJLIB_UTIL_ESTUNNOTRESPOND:
1563 0 : JAMI_ERROR("No response from STUN server {:s}", sip_utils::as_view(*stunServerName));
1564 0 : return false;
1565 :
1566 0 : case PJLIB_UTIL_ESTUNSYMMETRIC:
1567 0 : JAMI_ERR("Different mapped addresses are returned by servers.");
1568 0 : return false;
1569 :
1570 0 : case PJ_SUCCESS:
1571 0 : port = pj_sockaddr_in_get_port(&mapped_addr);
1572 0 : addr = dhtnet::IpAddr((const sockaddr_in&) mapped_addr).toString(true);
1573 0 : JAMI_DEBUG("STUN server {:s} replied '{}'", sip_utils::as_view(*stunServerName), addr);
1574 0 : return true;
1575 :
1576 0 : default: // use given address, silent any not handled error
1577 0 : JAMI_WARNING("Error from STUN server {:s}, using source address", sip_utils::as_view(*stunServerName));
1578 0 : return false;
1579 : }
1580 : }
1581 : #undef RETURN_IF_NULL
1582 : #undef RETURN_FALSE_IF_NULL
1583 : } // namespace jami
|