blob: 403a7046142e69bc23e87b7d852f1792bf612d6e [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 "pupnp.h"
Adrien Béraud370257c2023-08-15 20:53:09 -040018#include "string_utils.h"
Adrien Béraud612b55b2023-05-29 10:42:04 -040019
20#include <opendht/thread_pool.h>
21#include <opendht/http.h>
22
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040023namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040024namespace upnp {
25
26// Action identifiers.
27constexpr static const char* ACTION_ADD_PORT_MAPPING {"AddPortMapping"};
28constexpr static const char* ACTION_DELETE_PORT_MAPPING {"DeletePortMapping"};
29constexpr static const char* ACTION_GET_GENERIC_PORT_MAPPING_ENTRY {"GetGenericPortMappingEntry"};
30constexpr static const char* ACTION_GET_STATUS_INFO {"GetStatusInfo"};
31constexpr static const char* ACTION_GET_EXTERNAL_IP_ADDRESS {"GetExternalIPAddress"};
32
33// Error codes returned by router when trying to remove ports.
34constexpr static int ARRAY_IDX_INVALID = 713;
35constexpr static int CONFLICT_IN_MAPPING = 718;
36
37// Max number of IGD search attempts before failure.
38constexpr static unsigned int PUPNP_MAX_RESTART_SEARCH_RETRIES {3};
39// IGD search timeout (in seconds).
40constexpr static unsigned int SEARCH_TIMEOUT {60};
41// Base unit for the timeout between two successive IGD search.
42constexpr static auto PUPNP_SEARCH_RETRY_UNIT {std::chrono::seconds(10)};
43
44// Helper functions for xml parsing.
45static std::string_view
46getElementText(IXML_Node* node)
47{
48 if (node) {
49 if (IXML_Node* textNode = ixmlNode_getFirstChild(node))
50 if (const char* value = ixmlNode_getNodeValue(textNode))
51 return std::string_view(value);
52 }
53 return {};
54}
55
56static std::string_view
57getFirstDocItem(IXML_Document* doc, const char* item)
58{
59 std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&>
60 nodeList(ixmlDocument_getElementsByTagName(doc, item), ixmlNodeList_free);
61 if (nodeList) {
62 // If there are several nodes which match the tag, we only want the first one.
63 return getElementText(ixmlNodeList_item(nodeList.get(), 0));
64 }
65 return {};
66}
67
68static std::string_view
69getFirstElementItem(IXML_Element* element, const char* item)
70{
71 std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&>
72 nodeList(ixmlElement_getElementsByTagName(element, item), ixmlNodeList_free);
73 if (nodeList) {
74 // If there are several nodes which match the tag, we only want the first one.
75 return getElementText(ixmlNodeList_item(nodeList.get(), 0));
76 }
77 return {};
78}
79
80static bool
81errorOnResponse(IXML_Document* doc)
82{
83 if (not doc)
84 return true;
85
86 auto errorCode = getFirstDocItem(doc, "errorCode");
87 if (not errorCode.empty()) {
88 auto errorDescription = getFirstDocItem(doc, "errorDescription");
Adrien Béraud4f7e8012023-08-16 15:28:18 -040089 // if (logger_) logger_->warn("PUPnP: Response contains error: {:s}: {:s}",
Morteza Namvar5f639522023-07-04 17:08:58 -040090 // errorCode,
91 // errorDescription);
Adrien Béraud612b55b2023-05-29 10:42:04 -040092 return true;
93 }
94 return false;
95}
96
97// UPNP class implementation
98
Adrien Béraud370257c2023-08-15 20:53:09 -040099PUPnP::PUPnP(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger)
100 : UPnPProtocol(logger), ioContext(ctx), searchForIgdTimer_(*ctx)
Adrien Béraud612b55b2023-05-29 10:42:04 -0400101{
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400102 if (logger_) logger_->debug("PUPnP: Creating instance [{}] ...", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400103}
104
105PUPnP::~PUPnP()
106{
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400107 if (logger_) logger_->debug("PUPnP: Instance [{}] destroyed", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400108}
109
110void
111PUPnP::initUpnpLib()
112{
113 assert(not initialized_);
114
Adrien Bérauda61adb52023-08-23 09:31:02 -0400115 auto hostinfo = ip_utils::getHostName();
116
117 if (logger_) logger_->debug("PUPnP: Initializing libupnp {} {}", hostinfo.address, hostinfo.interface);
118
119 int upnp_err = UpnpInit2(hostinfo.interface.empty() ? nullptr : hostinfo.interface.c_str(), 0);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400120
121 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400122 if (logger_) logger_->error("PUPnP: Can't initialize libupnp: {}", UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400123 UpnpFinish();
124 initialized_ = false;
125 return;
126 }
127
128 // Disable embedded WebServer if any.
129 if (UpnpIsWebserverEnabled() == 1) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400130 if (logger_) logger_->warn("PUPnP: Web-server is enabled. Disabling");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400131 UpnpEnableWebserver(0);
132 if (UpnpIsWebserverEnabled() == 1) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400133 if (logger_) logger_->error("PUPnP: Could not disable Web-server!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400134 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400135 if (logger_) logger_->debug("PUPnP: Web-server successfully disabled");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400136 }
137 }
138
139 char* ip_address = UpnpGetServerIpAddress();
140 char* ip_address6 = nullptr;
141 unsigned short port = UpnpGetServerPort();
142 unsigned short port6 = 0;
143#if UPNP_ENABLE_IPV6
144 ip_address6 = UpnpGetServerIp6Address();
145 port6 = UpnpGetServerPort6();
146#endif
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400147 if (logger_) {
148 if (ip_address6 and port6)
149 logger_->debug("PUPnP: Initialized on {}:{:d} | {}:{:d}", ip_address, port, ip_address6, port6);
150 else
151 logger_->debug("PUPnP: Initialized on {}:{:d}", ip_address, port);
152 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400153
154 // Relax the parser to allow malformed XML text.
155 ixmlRelaxParser(1);
156
157 initialized_ = true;
158}
159
160bool
161PUPnP::isRunning() const
162{
163 std::unique_lock<std::mutex> lk(pupnpMutex_);
164 return not shutdownComplete_;
165}
166
167void
168PUPnP::registerClient()
169{
170 assert(not clientRegistered_);
171
Adrien Béraud612b55b2023-05-29 10:42:04 -0400172 // Register Upnp control point.
173 int upnp_err = UpnpRegisterClient(ctrlPtCallback, this, &ctrlptHandle_);
174 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400175 if (logger_) logger_->error("PUPnP: Can't register client: {}", UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400176 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400177 if (logger_) logger_->debug("PUPnP: Successfully registered client");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400178 clientRegistered_ = true;
179 }
180}
181
182void
183PUPnP::setObserver(UpnpMappingObserver* obs)
184{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400185 observer_ = obs;
186}
187
188const IpAddr
189PUPnP::getHostAddress() const
190{
191 std::lock_guard<std::mutex> lock(pupnpMutex_);
192 return hostAddress_;
193}
194
195void
196PUPnP::terminate(std::condition_variable& cv)
197{
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400198 if (logger_) logger_->debug("PUPnP: Terminate instance {}", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400199
200 clientRegistered_ = false;
201 observer_ = nullptr;
202
203 UpnpUnRegisterClient(ctrlptHandle_);
204
205 if (initialized_) {
206 if (UpnpFinish() != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400207 if (logger_) logger_->error("PUPnP: Failed to properly close lib-upnp");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400208 }
209
210 initialized_ = false;
211 }
212
213 // Clear all the lists.
214 discoveredIgdList_.clear();
215
216 {
217 std::lock_guard<std::mutex> lock(pupnpMutex_);
218 validIgdList_.clear();
219 shutdownComplete_ = true;
220 cv.notify_one();
221 }
222}
223
224void
225PUPnP::terminate()
226{
227 std::unique_lock<std::mutex> lk(pupnpMutex_);
228 std::condition_variable cv {};
229
Adrien Béraud370257c2023-08-15 20:53:09 -0400230 ioContext->dispatch([&] {
231 terminate(cv);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400232 });
233
234 if (cv.wait_for(lk, std::chrono::seconds(10), [this] { return shutdownComplete_; })) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400235 if (logger_) logger_->debug("PUPnP: Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400236 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400237 if (logger_) logger_->error("PUPnP: Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400238 // Force stop if the shutdown take too much time.
239 shutdownComplete_ = true;
240 }
241}
242
243void
244PUPnP::searchForDevices()
245{
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400246 if (logger_) logger_->debug("PUPnP: Send IGD search request");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400247
248 // Send out search for multiple types of devices, as some routers may possibly
249 // only reply to one.
250
251 auto err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, this);
252 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400253 if (logger_) logger_->warn("PUPnP: Send search for UPNP_ROOT_DEVICE failed. Error {:d}: {}",
254 err,
255 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400256 }
257
258 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this);
259 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400260 if (logger_) logger_->warn("PUPnP: Send search for UPNP_IGD_DEVICE failed. Error {:d}: {}",
261 err,
262 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400263 }
264
265 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this);
266 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400267 if (logger_) logger_->warn("PUPnP: Send search for UPNP_WANIP_SERVICE failed. Error {:d}: {}",
268 err,
269 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400270 }
271
272 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this);
273 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400274 if (logger_) logger_->warn("PUPnP: Send search for UPNP_WANPPP_SERVICE failed. Error {:d}: {}",
275 err,
276 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400277 }
278}
279
280void
281PUPnP::clearIgds()
282{
Morteza Namvar5f639522023-07-04 17:08:58 -0400283 // JAMI_DBG("PUPnP: clearing IGDs and devices lists");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400284
Adrien Béraud370257c2023-08-15 20:53:09 -0400285 searchForIgdTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400286
287 igdSearchCounter_ = 0;
288
289 {
290 std::lock_guard<std::mutex> lock(pupnpMutex_);
291 for (auto const& igd : validIgdList_) {
292 igd->setValid(false);
293 }
294 validIgdList_.clear();
295 hostAddress_ = {};
296 }
297
298 discoveredIgdList_.clear();
299}
300
301void
302PUPnP::searchForIgd()
303{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400304 // Update local address before searching.
305 updateHostAddress();
306
307 if (isReady()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400308 if (logger_) logger_->debug("PUPnP: Already have a valid IGD. Skip the search request");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400309 return;
310 }
311
312 if (igdSearchCounter_++ >= PUPNP_MAX_RESTART_SEARCH_RETRIES) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400313 if (logger_) logger_->warn("PUPnP: Setup failed after {:d} trials. PUPnP will be disabled!",
314 PUPNP_MAX_RESTART_SEARCH_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400315 return;
316 }
317
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400318 if (logger_) logger_->debug("PUPnP: Start search for IGD: attempt {:d}", igdSearchCounter_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400319
320 // Do not init if the host is not valid. Otherwise, the init will fail
321 // anyway and may put libupnp in an unstable state (mainly deadlocks)
322 // even if the UpnpFinish() method is called.
323 if (not hasValidHostAddress()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400324 if (logger_) logger_->warn("PUPnP: Host address is invalid. Skipping the IGD search");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400325 } else {
326 // Init and register if needed
327 if (not initialized_) {
328 initUpnpLib();
329 }
330 if (initialized_ and not clientRegistered_) {
331 registerClient();
332 }
333 // Start searching
334 if (clientRegistered_) {
335 assert(initialized_);
336 searchForDevices();
337 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400338 if (logger_) logger_->warn("PUPnP: PUPNP not fully setup. Skipping the IGD search");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400339 }
340 }
341
342 // Cancel the current timer (if any) and re-schedule.
343 // The connectivity change may be received while the the local
344 // interface is not fully setup. The rescheduling typically
345 // usefull to mitigate this race.
Adrien Béraud370257c2023-08-15 20:53:09 -0400346 searchForIgdTimer_.expires_after(PUPNP_SEARCH_RETRY_UNIT * igdSearchCounter_);
347 searchForIgdTimer_.async_wait([w = weak()] (const asio::error_code& ec) {
348 if (not ec) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400349 if (auto upnpThis = w.lock())
350 upnpThis->searchForIgd();
Adrien Béraud370257c2023-08-15 20:53:09 -0400351 }
352 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400353}
354
355std::list<std::shared_ptr<IGD>>
356PUPnP::getIgdList() const
357{
358 std::lock_guard<std::mutex> lock(pupnpMutex_);
359 std::list<std::shared_ptr<IGD>> igdList;
360 for (auto& it : validIgdList_) {
361 // Return only active IGDs.
362 if (it->isValid()) {
363 igdList.emplace_back(it);
364 }
365 }
366 return igdList;
367}
368
369bool
370PUPnP::isReady() const
371{
372 // Must at least have a valid local address.
373 if (not getHostAddress() or getHostAddress().isLoopback())
374 return false;
375
376 return hasValidIgd();
377}
378
379bool
380PUPnP::hasValidIgd() const
381{
382 std::lock_guard<std::mutex> lock(pupnpMutex_);
383 for (auto& it : validIgdList_) {
384 if (it->isValid()) {
385 return true;
386 }
387 }
388 return false;
389}
390
391void
392PUPnP::updateHostAddress()
393{
394 std::lock_guard<std::mutex> lock(pupnpMutex_);
395 hostAddress_ = ip_utils::getLocalAddr(AF_INET);
396}
397
398bool
399PUPnP::hasValidHostAddress()
400{
401 std::lock_guard<std::mutex> lock(pupnpMutex_);
402 return hostAddress_ and not hostAddress_.isLoopback();
403}
404
405void
406PUPnP::incrementErrorsCounter(const std::shared_ptr<IGD>& igd)
407{
408 if (not igd or not igd->isValid())
409 return;
410 if (not igd->incrementErrorsCounter()) {
411 // Disable this IGD.
412 igd->setValid(false);
413 // Notify the listener.
414 if (observer_)
415 observer_->onIgdUpdated(igd, UpnpIgdEvent::INVALID_STATE);
416 }
417}
418
419bool
420PUPnP::validateIgd(const std::string& location, IXML_Document* doc_container_ptr)
421{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400422 assert(doc_container_ptr != nullptr);
423
424 XMLDocument document(doc_container_ptr, ixmlDocument_free);
425 auto descDoc = document.get();
426 // Check device type.
427 auto deviceType = getFirstDocItem(descDoc, "deviceType");
428 if (deviceType != UPNP_IGD_DEVICE) {
429 // Device type not IGD.
430 return false;
431 }
432
433 std::shared_ptr<UPnPIGD> igd_candidate = parseIgd(descDoc, location);
434 if (not igd_candidate) {
435 // No valid IGD candidate.
436 return false;
437 }
438
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400439 if (logger_) logger_->debug("PUPnP: Validating the IGD candidate [UDN: {}]\n"
440 " Name : {}\n"
441 " Service Type : {}\n"
442 " Service ID : {}\n"
443 " Base URL : {}\n"
444 " Location URL : {}\n"
445 " control URL : {}\n"
446 " Event URL : {}",
447 igd_candidate->getUID(),
448 igd_candidate->getFriendlyName(),
449 igd_candidate->getServiceType(),
450 igd_candidate->getServiceId(),
451 igd_candidate->getBaseURL(),
452 igd_candidate->getLocationURL(),
453 igd_candidate->getControlURL(),
454 igd_candidate->getEventSubURL());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400455
456 // Check if IGD is connected.
457 if (not actionIsIgdConnected(*igd_candidate)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400458 if (logger_) logger_->warn("PUPnP: IGD candidate {} is not connected", igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400459 return false;
460 }
461
462 // Validate external Ip.
463 igd_candidate->setPublicIp(actionGetExternalIP(*igd_candidate));
464 if (igd_candidate->getPublicIp().toString().empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400465 if (logger_) logger_->warn("PUPnP: IGD candidate {} has no valid external Ip",
466 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400467 return false;
468 }
469
470 // Validate internal Ip.
471 if (igd_candidate->getBaseURL().empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400472 if (logger_) logger_->warn("PUPnP: IGD candidate {} has no valid internal Ip",
473 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400474 return false;
475 }
476
477 // Typically the IGD local address should be extracted from the XML
478 // document (e.g. parsing the base URL). For simplicity, we assume
479 // that it matches the gateway as seen by the local interface.
480 if (const auto& localGw = ip_utils::getLocalGateway()) {
481 igd_candidate->setLocalIp(localGw);
482 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400483 if (logger_) logger_->warn("PUPnP: Could not set internal address for IGD candidate {}",
484 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400485 return false;
486 }
487
488 // Store info for subscription.
489 std::string eventSub = igd_candidate->getEventSubURL();
490
491 {
492 // Add the IGD if not already present in the list.
493 std::lock_guard<std::mutex> lock(pupnpMutex_);
494 for (auto& igd : validIgdList_) {
495 // Must not be a null pointer
496 assert(igd.get() != nullptr);
497 if (*igd == *igd_candidate) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400498 if (logger_) logger_->debug("PUPnP: Device [{}] with int/ext addresses [{}:{}] is already in the list of valid IGDs",
499 igd_candidate->getUID(),
500 igd_candidate->toString(),
501 igd_candidate->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400502 return true;
503 }
504 }
505 }
506
507 // We have a valid IGD
508 igd_candidate->setValid(true);
509
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400510 if (logger_) logger_->debug("PUPnP: Added a new IGD [{}] to the list of valid IGDs",
511 igd_candidate->getUID());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400512
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400513 if (logger_) logger_->debug("PUPnP: New IGD addresses [int: {} - ext: {}]",
514 igd_candidate->toString(),
515 igd_candidate->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400516
517 // Subscribe to IGD events.
518 int upnp_err = UpnpSubscribeAsync(ctrlptHandle_,
519 eventSub.c_str(),
520 UPNP_INFINITE,
521 subEventCallback,
522 this);
523 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400524 if (logger_) logger_->warn("PUPnP: Failed to send subscribe request to {}: error %i - {}",
525 igd_candidate->getUID(),
526 upnp_err,
527 UpnpGetErrorMessage(upnp_err));
528 return false;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400529 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400530 if (logger_) logger_->debug("PUPnP: Successfully subscribed to IGD {}", igd_candidate->getUID());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400531 }
532
533 {
534 // This is a new (and hopefully valid) IGD.
535 std::lock_guard<std::mutex> lock(pupnpMutex_);
536 validIgdList_.emplace_back(igd_candidate);
537 }
538
539 // Report to the listener.
Adrien Béraud370257c2023-08-15 20:53:09 -0400540 ioContext->post([w = weak(), igd_candidate] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400541 if (auto upnpThis = w.lock()) {
542 if (upnpThis->observer_)
543 upnpThis->observer_->onIgdUpdated(igd_candidate, UpnpIgdEvent::ADDED);
544 }
545 });
546
547 return true;
548}
549
550void
551PUPnP::requestMappingAdd(const Mapping& mapping)
552{
Adrien Béraud370257c2023-08-15 20:53:09 -0400553 ioContext->post([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400554 if (auto upnpThis = w.lock()) {
555 if (not upnpThis->isRunning())
556 return;
557 Mapping mapRes(mapping);
558 if (upnpThis->actionAddPortMapping(mapRes)) {
559 mapRes.setState(MappingState::OPEN);
560 mapRes.setInternalAddress(upnpThis->getHostAddress().toString());
561 upnpThis->processAddMapAction(mapRes);
562 } else {
563 upnpThis->incrementErrorsCounter(mapRes.getIgd());
564 mapRes.setState(MappingState::FAILED);
565 upnpThis->processRequestMappingFailure(mapRes);
566 }
567 }
568 });
569}
570
571void
572PUPnP::requestMappingRemove(const Mapping& mapping)
573{
574 // Send remove request using the matching IGD
Adrien Béraud370257c2023-08-15 20:53:09 -0400575 ioContext->dispatch([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400576 if (auto upnpThis = w.lock()) {
577 // Abort if we are shutting down.
578 if (not upnpThis->isRunning())
579 return;
580 if (upnpThis->actionDeletePortMapping(mapping)) {
581 upnpThis->processRemoveMapAction(mapping);
582 } else {
583 assert(mapping.getIgd());
584 // Dont need to report in case of failure.
585 upnpThis->incrementErrorsCounter(mapping.getIgd());
586 }
587 }
588 });
589}
590
591std::shared_ptr<UPnPIGD>
592PUPnP::findMatchingIgd(const std::string& ctrlURL) const
593{
594 std::lock_guard<std::mutex> lock(pupnpMutex_);
595
596 auto iter = std::find_if(validIgdList_.begin(),
597 validIgdList_.end(),
598 [&ctrlURL](const std::shared_ptr<IGD>& igd) {
599 if (auto upnpIgd = std::dynamic_pointer_cast<UPnPIGD>(igd)) {
600 return upnpIgd->getControlURL() == ctrlURL;
601 }
602 return false;
603 });
604
605 if (iter == validIgdList_.end()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400606 if (logger_) logger_->warn("PUPnP: Did not find the IGD matching ctrl URL [{}]", ctrlURL);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400607 return {};
608 }
609
610 return std::dynamic_pointer_cast<UPnPIGD>(*iter);
611}
612
613void
614PUPnP::processAddMapAction(const Mapping& map)
615{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400616 if (observer_ == nullptr)
617 return;
618
Adrien Béraud370257c2023-08-15 20:53:09 -0400619 ioContext->post([w = weak(), map] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400620 if (auto upnpThis = w.lock()) {
621 if (upnpThis->observer_)
622 upnpThis->observer_->onMappingAdded(map.getIgd(), std::move(map));
623 }
624 });
625}
626
627void
628PUPnP::processRequestMappingFailure(const Mapping& map)
629{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400630 if (observer_ == nullptr)
631 return;
632
Adrien Béraud370257c2023-08-15 20:53:09 -0400633 ioContext->post([w = weak(), map] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400634 if (auto upnpThis = w.lock()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400635 if (upnpThis->logger_) upnpThis->logger_->warn("PUPnP: Closed mapping {}", map.toString());
Morteza Namvar5f639522023-07-04 17:08:58 -0400636 // JAMI_DBG("PUPnP: Failed to request mapping %s", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400637 if (upnpThis->observer_)
638 upnpThis->observer_->onMappingRequestFailed(map);
639 }
640 });
641}
642
643void
644PUPnP::processRemoveMapAction(const Mapping& map)
645{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400646 if (observer_ == nullptr)
647 return;
648
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400649 if (logger_) logger_->warn("PUPnP: Closed mapping {}", map.toString());
Adrien Béraud370257c2023-08-15 20:53:09 -0400650 ioContext->post([map, obs = observer_] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400651 obs->onMappingRemoved(map.getIgd(), std::move(map));
652 });
653}
654
655const char*
656PUPnP::eventTypeToString(Upnp_EventType eventType)
657{
658 switch (eventType) {
659 case UPNP_CONTROL_ACTION_REQUEST:
660 return "UPNP_CONTROL_ACTION_REQUEST";
661 case UPNP_CONTROL_ACTION_COMPLETE:
662 return "UPNP_CONTROL_ACTION_COMPLETE";
663 case UPNP_CONTROL_GET_VAR_REQUEST:
664 return "UPNP_CONTROL_GET_VAR_REQUEST";
665 case UPNP_CONTROL_GET_VAR_COMPLETE:
666 return "UPNP_CONTROL_GET_VAR_COMPLETE";
667 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
668 return "UPNP_DISCOVERY_ADVERTISEMENT_ALIVE";
669 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
670 return "UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE";
671 case UPNP_DISCOVERY_SEARCH_RESULT:
672 return "UPNP_DISCOVERY_SEARCH_RESULT";
673 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
674 return "UPNP_DISCOVERY_SEARCH_TIMEOUT";
675 case UPNP_EVENT_SUBSCRIPTION_REQUEST:
676 return "UPNP_EVENT_SUBSCRIPTION_REQUEST";
677 case UPNP_EVENT_RECEIVED:
678 return "UPNP_EVENT_RECEIVED";
679 case UPNP_EVENT_RENEWAL_COMPLETE:
680 return "UPNP_EVENT_RENEWAL_COMPLETE";
681 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
682 return "UPNP_EVENT_SUBSCRIBE_COMPLETE";
683 case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:
684 return "UPNP_EVENT_UNSUBSCRIBE_COMPLETE";
685 case UPNP_EVENT_AUTORENEWAL_FAILED:
686 return "UPNP_EVENT_AUTORENEWAL_FAILED";
687 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
688 return "UPNP_EVENT_SUBSCRIPTION_EXPIRED";
689 default:
690 return "Unknown UPNP Event";
691 }
692}
693
694int
695PUPnP::ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data)
696{
697 auto pupnp = static_cast<PUPnP*>(user_data);
698
699 if (pupnp == nullptr) {
Adrien Bérauda61adb52023-08-23 09:31:02 -0400700 fmt::print(stderr, "PUPnP: Control point callback without PUPnP");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400701 return UPNP_E_SUCCESS;
702 }
703
704 auto upnpThis = pupnp->weak().lock();
Adrien Bérauda61adb52023-08-23 09:31:02 -0400705 if (not upnpThis) {
706 fmt::print(stderr, "PUPnP: Control point callback without PUPnP");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400707 return UPNP_E_SUCCESS;
Adrien Bérauda61adb52023-08-23 09:31:02 -0400708 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400709
710 // Ignore if already unregistered.
711 if (not upnpThis->clientRegistered_)
712 return UPNP_E_SUCCESS;
713
714 // Process the callback.
715 return upnpThis->handleCtrlPtUPnPEvents(event_type, event);
716}
717
718PUPnP::CtrlAction
719PUPnP::getAction(const char* xmlNode)
720{
721 if (strstr(xmlNode, ACTION_ADD_PORT_MAPPING)) {
722 return CtrlAction::ADD_PORT_MAPPING;
723 } else if (strstr(xmlNode, ACTION_DELETE_PORT_MAPPING)) {
724 return CtrlAction::DELETE_PORT_MAPPING;
725 } else if (strstr(xmlNode, ACTION_GET_GENERIC_PORT_MAPPING_ENTRY)) {
726 return CtrlAction::GET_GENERIC_PORT_MAPPING_ENTRY;
727 } else if (strstr(xmlNode, ACTION_GET_STATUS_INFO)) {
728 return CtrlAction::GET_STATUS_INFO;
729 } else if (strstr(xmlNode, ACTION_GET_EXTERNAL_IP_ADDRESS)) {
730 return CtrlAction::GET_EXTERNAL_IP_ADDRESS;
731 } else {
732 return CtrlAction::UNKNOWN;
733 }
734}
735
736void
737PUPnP::processDiscoverySearchResult(const std::string& cpDeviceId,
738 const std::string& igdLocationUrl,
739 const IpAddr& dstAddr)
740{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400741 // Update host address if needed.
742 if (not hasValidHostAddress())
743 updateHostAddress();
744
745 // The host address must be valid to proceed.
746 if (not hasValidHostAddress()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400747 if (logger_) logger_->warn("PUPnP: Local address is invalid. Ignore search result for now!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400748 return;
749 }
750
751 // Use the device ID and the URL as ID. This is necessary as some
752 // IGDs may have the same device ID but different URLs.
753
754 auto igdId = cpDeviceId + " url: " + igdLocationUrl;
755
756 if (not discoveredIgdList_.emplace(igdId).second) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400757 if (logger_) logger_->warn("PUPnP: IGD [{}] already in the list", igdId);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400758 return;
759 }
760
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400761 if (logger_) logger_->debug("PUPnP: Discovered a new IGD [{}]", igdId);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400762
763 // NOTE: here, we check if the location given is related to the source address.
764 // If it's not the case, it's certainly a router plugged in the network, but not
765 // related to this network. So the given location will be unreachable and this
766 // will cause some timeout.
767
768 // Only check the IP address (ignore the port number).
769 dht::http::Url url(igdLocationUrl);
770 if (IpAddr(url.host).toString(false) != dstAddr.toString(false)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400771 if (logger_) logger_->debug("PUPnP: Returned location {} does not match the source address {}",
772 IpAddr(url.host).toString(true, true),
773 dstAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400774 return;
775 }
776
777 // Run a separate thread to prevent blocking this thread
778 // if the IGD HTTP server is not responsive.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400779 dht::ThreadPool::io().run([w = weak(), url=igdLocationUrl] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400780 if (auto upnpThis = w.lock()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400781 upnpThis->downLoadIgdDescription(url);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400782 }
783 });
784}
785
786void
787PUPnP::downLoadIgdDescription(const std::string& locationUrl)
788{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400789 if(logger_) logger_->debug("PUPnP: downLoadIgdDescription {}", locationUrl);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400790 IXML_Document* doc_container_ptr = nullptr;
791 int upnp_err = UpnpDownloadXmlDoc(locationUrl.c_str(), &doc_container_ptr);
792
793 if (upnp_err != UPNP_E_SUCCESS or not doc_container_ptr) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400794 if(logger_) logger_->warn("PUPnP: Error downloading device XML document from {} -> {}",
795 locationUrl,
796 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400797 } else {
Adrien Béraud370257c2023-08-15 20:53:09 -0400798 if(logger_) logger_->debug("PUPnP: Succeeded to download device XML document from {}", locationUrl);
799 ioContext->post([w = weak(), url = locationUrl, doc_container_ptr] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400800 if (auto upnpThis = w.lock()) {
801 upnpThis->validateIgd(url, doc_container_ptr);
802 }
803 });
804 }
805}
806
807void
808PUPnP::processDiscoveryAdvertisementByebye(const std::string& cpDeviceId)
809{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400810 discoveredIgdList_.erase(cpDeviceId);
811
812 std::shared_ptr<IGD> igd;
813 {
814 std::lock_guard<std::mutex> lk(pupnpMutex_);
815 for (auto it = validIgdList_.begin(); it != validIgdList_.end();) {
816 if ((*it)->getUID() == cpDeviceId) {
817 igd = *it;
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400818 if (logger_) logger_->debug("PUPnP: Received [{}] for IGD [{}] {}. Will be removed.",
819 PUPnP::eventTypeToString(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE),
820 igd->getUID(),
821 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400822 igd->setValid(false);
823 // Remove the IGD.
824 it = validIgdList_.erase(it);
825 break;
826 } else {
827 it++;
828 }
829 }
830 }
831
832 // Notify the listener.
833 if (observer_ and igd) {
834 observer_->onIgdUpdated(igd, UpnpIgdEvent::REMOVED);
835 }
836}
837
838void
839PUPnP::processDiscoverySubscriptionExpired(Upnp_EventType event_type, const std::string& eventSubUrl)
840{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400841 std::lock_guard<std::mutex> lk(pupnpMutex_);
842 for (auto& it : validIgdList_) {
843 if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it)) {
844 if (igd->getEventSubURL() == eventSubUrl) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400845 if (logger_) logger_->debug("PUPnP: Received [{}] event for IGD [{}] {}. Request a new subscribe.",
846 PUPnP::eventTypeToString(event_type),
847 igd->getUID(),
848 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400849 UpnpSubscribeAsync(ctrlptHandle_,
850 eventSubUrl.c_str(),
851 UPNP_INFINITE,
852 subEventCallback,
853 this);
854 break;
855 }
856 }
857 }
858}
859
860int
861PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
862{
863 switch (event_type) {
864 // "ALIVE" events are processed as "SEARCH RESULT". It might be usefull
865 // if "SEARCH RESULT" was missed.
866 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
867 case UPNP_DISCOVERY_SEARCH_RESULT: {
868 const UpnpDiscovery* d_event = (const UpnpDiscovery*) event;
869
870 // First check the error code.
871 auto upnp_status = UpnpDiscovery_get_ErrCode(d_event);
872 if (upnp_status != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400873 if (logger_) logger_->error("PUPnP: UPNP discovery is in erroneous state: %s",
874 UpnpGetErrorMessage(upnp_status));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400875 break;
876 }
877
878 // Parse the event's data.
879 std::string deviceId {UpnpDiscovery_get_DeviceID_cstr(d_event)};
880 std::string location {UpnpDiscovery_get_Location_cstr(d_event)};
881 IpAddr dstAddr(*(const pj_sockaddr*) (UpnpDiscovery_get_DestAddr(d_event)));
Adrien Béraud370257c2023-08-15 20:53:09 -0400882 ioContext->post([w = weak(),
Adrien Béraud612b55b2023-05-29 10:42:04 -0400883 deviceId = std::move(deviceId),
884 location = std::move(location),
885 dstAddr = std::move(dstAddr)] {
886 if (auto upnpThis = w.lock()) {
887 upnpThis->processDiscoverySearchResult(deviceId, location, dstAddr);
888 }
889 });
890 break;
891 }
892 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: {
893 const UpnpDiscovery* d_event = (const UpnpDiscovery*) event;
894
895 std::string deviceId(UpnpDiscovery_get_DeviceID_cstr(d_event));
896
897 // Process the response on the main thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400898 ioContext->post([w = weak(), deviceId = std::move(deviceId)] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400899 if (auto upnpThis = w.lock()) {
900 upnpThis->processDiscoveryAdvertisementByebye(deviceId);
901 }
902 });
903 break;
904 }
905 case UPNP_DISCOVERY_SEARCH_TIMEOUT: {
906 // Even if the discovery search is successful, it's normal to receive
907 // time-out events. This because we send search requests using various
908 // device types, which some of them may not return a response.
909 break;
910 }
911 case UPNP_EVENT_RECEIVED: {
912 // Nothing to do.
913 break;
914 }
915 // Treat failed autorenewal like an expired subscription.
916 case UPNP_EVENT_AUTORENEWAL_FAILED:
917 case UPNP_EVENT_SUBSCRIPTION_EXPIRED: // This event will occur only if autorenewal is disabled.
918 {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400919 if (logger_) logger_->warn("PUPnP: Received Subscription Event {}", eventTypeToString(event_type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400920 const UpnpEventSubscribe* es_event = (const UpnpEventSubscribe*) event;
921 if (es_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400922 if (logger_) logger_->warn("PUPnP: Received Subscription Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400923 break;
924 }
925 std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
926
927 // Process the response on the main thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400928 ioContext->post([w = weak(), event_type, publisherUrl = std::move(publisherUrl)] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400929 if (auto upnpThis = w.lock()) {
930 upnpThis->processDiscoverySubscriptionExpired(event_type, publisherUrl);
931 }
932 });
933 break;
934 }
935 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
936 case UPNP_EVENT_UNSUBSCRIBE_COMPLETE: {
937 UpnpEventSubscribe* es_event = (UpnpEventSubscribe*) event;
938 if (es_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400939 if (logger_) logger_->warn("PUPnP: Received Subscription Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400940 } else {
941 UpnpEventSubscribe_delete(es_event);
942 }
943 break;
944 }
945 case UPNP_CONTROL_ACTION_COMPLETE: {
946 const UpnpActionComplete* a_event = (const UpnpActionComplete*) event;
947 if (a_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400948 if (logger_) logger_->warn("PUPnP: Received Action Complete Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400949 break;
950 }
951 auto res = UpnpActionComplete_get_ErrCode(a_event);
952 if (res != UPNP_E_SUCCESS and res != UPNP_E_TIMEDOUT) {
953 auto err = UpnpActionComplete_get_ErrCode(a_event);
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400954 if (logger_) logger_->warn("PUPnP: Received Action Complete error %i %s", err, UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400955 } else {
956 auto actionRequest = UpnpActionComplete_get_ActionRequest(a_event);
957 // Abort if there is no action to process.
958 if (actionRequest == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400959 if (logger_) logger_->warn("PUPnP: Can't get the Action Request data from the event");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400960 break;
961 }
962
963 auto actionResult = UpnpActionComplete_get_ActionResult(a_event);
964 if (actionResult != nullptr) {
965 ixmlDocument_free(actionResult);
966 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400967 if (logger_) logger_->warn("PUPnP: Action Result document not found");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400968 }
969 }
970 break;
971 }
972 default: {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400973 if (logger_) logger_->warn("PUPnP: Unhandled Control Point event");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400974 break;
975 }
976 }
977
978 return UPNP_E_SUCCESS;
979}
980
981int
982PUPnP::subEventCallback(Upnp_EventType event_type, const void* event, void* user_data)
983{
984 if (auto pupnp = static_cast<PUPnP*>(user_data))
985 return pupnp->handleSubscriptionUPnPEvent(event_type, event);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400986 return 0;
987}
988
989int
990PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType, const void* event)
991{
992 UpnpEventSubscribe* es_event = static_cast<UpnpEventSubscribe*>(const_cast<void*>(event));
993
994 if (es_event == nullptr) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400995 // JAMI_ERR("PUPnP: Unexpected null pointer!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400996 return UPNP_E_INVALID_ARGUMENT;
997 }
998 std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
999 int upnp_err = UpnpEventSubscribe_get_ErrCode(es_event);
1000 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001001 if (logger_) logger_->warn("PUPnP: Subscription error {} from {}",
1002 UpnpGetErrorMessage(upnp_err),
1003 publisherUrl);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001004 return upnp_err;
1005 }
1006
1007 return UPNP_E_SUCCESS;
1008}
1009
1010std::unique_ptr<UPnPIGD>
1011PUPnP::parseIgd(IXML_Document* doc, std::string locationUrl)
1012{
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001013 if (not(doc and !locationUrl.empty()))
Adrien Béraud612b55b2023-05-29 10:42:04 -04001014 return nullptr;
1015
1016 // Check the UDN to see if its already in our device list.
1017 std::string UDN(getFirstDocItem(doc, "UDN"));
1018 if (UDN.empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001019 if (logger_) logger_->warn("PUPnP: could not find UDN in description document of device");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001020 return nullptr;
1021 } else {
1022 std::lock_guard<std::mutex> lk(pupnpMutex_);
1023 for (auto& it : validIgdList_) {
1024 if (it->getUID() == UDN) {
1025 // We already have this device in our list.
1026 return nullptr;
1027 }
1028 }
1029 }
1030
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001031 if (logger_) logger_->debug("PUPnP: Found new device [{}]", UDN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001032
1033 std::unique_ptr<UPnPIGD> new_igd;
1034 int upnp_err;
1035
1036 // Get friendly name.
1037 std::string friendlyName(getFirstDocItem(doc, "friendlyName"));
1038
1039 // Get base URL.
1040 std::string baseURL(getFirstDocItem(doc, "URLBase"));
1041 if (baseURL.empty())
1042 baseURL = locationUrl;
1043
1044 // Get list of services defined by serviceType.
1045 std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&> serviceList(nullptr,
1046 ixmlNodeList_free);
1047 serviceList.reset(ixmlDocument_getElementsByTagName(doc, "serviceType"));
1048 unsigned long list_length = ixmlNodeList_length(serviceList.get());
1049
1050 // Go through the "serviceType" nodes until we find the the correct service type.
1051 for (unsigned long node_idx = 0; node_idx < list_length; node_idx++) {
1052 IXML_Node* serviceType_node = ixmlNodeList_item(serviceList.get(), node_idx);
1053 std::string serviceType(getElementText(serviceType_node));
1054
1055 // Only check serviceType of WANIPConnection or WANPPPConnection.
1056 if (serviceType != UPNP_WANIP_SERVICE
1057 && serviceType != UPNP_WANPPP_SERVICE) {
1058 // IGD is not WANIP or WANPPP service. Going to next node.
1059 continue;
1060 }
1061
1062 // Get parent node.
1063 IXML_Node* service_node = ixmlNode_getParentNode(serviceType_node);
1064 if (not service_node) {
1065 // IGD serviceType has no parent node. Going to next node.
1066 continue;
1067 }
1068
1069 // Perform sanity check. The parent node should be called "service".
1070 if (strcmp(ixmlNode_getNodeName(service_node), "service") != 0) {
1071 // IGD "serviceType" parent node is not called "service". Going to next node.
1072 continue;
1073 }
1074
1075 // Get serviceId.
1076 IXML_Element* service_element = (IXML_Element*) service_node;
1077 std::string serviceId(getFirstElementItem(service_element, "serviceId"));
1078 if (serviceId.empty()) {
1079 // IGD "serviceId" is empty. Going to next node.
1080 continue;
1081 }
1082
1083 // Get the relative controlURL and turn it into absolute address using the URLBase.
1084 std::string controlURL(getFirstElementItem(service_element, "controlURL"));
1085 if (controlURL.empty()) {
1086 // IGD control URL is empty. Going to next node.
1087 continue;
1088 }
1089
1090 char* absolute_control_url = nullptr;
1091 upnp_err = UpnpResolveURL2(baseURL.c_str(), controlURL.c_str(), &absolute_control_url);
1092 if (upnp_err == UPNP_E_SUCCESS)
1093 controlURL = absolute_control_url;
1094 else
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001095 if (logger_) logger_->warn("PUPnP: Error resolving absolute controlURL -> {}",
1096 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001097
1098 std::free(absolute_control_url);
1099
1100 // Get the relative eventSubURL and turn it into absolute address using the URLBase.
1101 std::string eventSubURL(getFirstElementItem(service_element, "eventSubURL"));
1102 if (eventSubURL.empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001103 if (logger_) logger_->warn("PUPnP: IGD event sub URL is empty. Going to next node");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001104 continue;
1105 }
1106
1107 char* absolute_event_sub_url = nullptr;
1108 upnp_err = UpnpResolveURL2(baseURL.c_str(), eventSubURL.c_str(), &absolute_event_sub_url);
1109 if (upnp_err == UPNP_E_SUCCESS)
1110 eventSubURL = absolute_event_sub_url;
1111 else
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001112 if (logger_) logger_->warn("PUPnP: Error resolving absolute eventSubURL -> {}",
1113 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001114
1115 std::free(absolute_event_sub_url);
1116
1117 new_igd.reset(new UPnPIGD(std::move(UDN),
1118 std::move(baseURL),
1119 std::move(friendlyName),
1120 std::move(serviceType),
1121 std::move(serviceId),
1122 std::move(locationUrl),
1123 std::move(controlURL),
1124 std::move(eventSubURL)));
1125
1126 return new_igd;
1127 }
1128
1129 return nullptr;
1130}
1131
1132bool
1133PUPnP::actionIsIgdConnected(const UPnPIGD& igd)
1134{
1135 if (not clientRegistered_)
1136 return false;
1137
1138 // Set action name.
1139 IXML_Document* action_container_ptr = UpnpMakeAction("GetStatusInfo",
1140 igd.getServiceType().c_str(),
1141 0,
1142 nullptr);
1143 if (not action_container_ptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001144 if (logger_) logger_->warn("PUPnP: Failed to make GetStatusInfo action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001145 return false;
1146 }
1147 XMLDocument action(action_container_ptr, ixmlDocument_free); // Action pointer.
1148
1149 IXML_Document* response_container_ptr = nullptr;
1150 int upnp_err = UpnpSendAction(ctrlptHandle_,
1151 igd.getControlURL().c_str(),
1152 igd.getServiceType().c_str(),
1153 nullptr,
1154 action.get(),
1155 &response_container_ptr);
1156 if (not response_container_ptr or upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001157 if (logger_) logger_->warn("PUPnP: Failed to send GetStatusInfo action -> {}", UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001158 return false;
1159 }
1160 XMLDocument response(response_container_ptr, ixmlDocument_free);
1161
1162 if (errorOnResponse(response.get())) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001163 if (logger_) logger_->warn("PUPnP: Failed to get GetStatusInfo from {} -> {:d}: {}",
1164 igd.getServiceType().c_str(),
1165 upnp_err,
1166 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001167 return false;
1168 }
1169
1170 // Parse response.
1171 auto status = getFirstDocItem(response.get(), "NewConnectionStatus");
1172 return status == "Connected";
1173}
1174
1175IpAddr
1176PUPnP::actionGetExternalIP(const UPnPIGD& igd)
1177{
1178 if (not clientRegistered_)
1179 return {};
1180
1181 // Action and response pointers.
1182 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1183 action(nullptr, ixmlDocument_free); // Action pointer.
1184 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1185 response(nullptr, ixmlDocument_free); // Response pointer.
1186
1187 // Set action name.
1188 static constexpr const char* action_name {"GetExternalIPAddress"};
1189
1190 IXML_Document* action_container_ptr = nullptr;
1191 action_container_ptr = UpnpMakeAction(action_name, igd.getServiceType().c_str(), 0, nullptr);
1192 action.reset(action_container_ptr);
1193
1194 if (not action) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001195 if (logger_) logger_->warn("PUPnP: Failed to make GetExternalIPAddress action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001196 return {};
1197 }
1198
1199 IXML_Document* response_container_ptr = nullptr;
1200 int upnp_err = UpnpSendAction(ctrlptHandle_,
1201 igd.getControlURL().c_str(),
1202 igd.getServiceType().c_str(),
1203 nullptr,
1204 action.get(),
1205 &response_container_ptr);
1206 response.reset(response_container_ptr);
1207
1208 if (not response or upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001209 if (logger_) logger_->warn("PUPnP: Failed to send GetExternalIPAddress action -> {}",
1210 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001211 return {};
1212 }
1213
1214 if (errorOnResponse(response.get())) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001215 if (logger_) logger_->warn("PUPnP: Failed to get GetExternalIPAddress from {} -> {:d}: {}",
1216 igd.getServiceType(),
1217 upnp_err,
1218 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001219 return {};
1220 }
1221
1222 return {getFirstDocItem(response.get(), "NewExternalIPAddress")};
1223}
1224
1225std::map<Mapping::key_t, Mapping>
1226PUPnP::getMappingsListByDescr(const std::shared_ptr<IGD>& igd, const std::string& description) const
1227{
1228 auto upnpIgd = std::dynamic_pointer_cast<UPnPIGD>(igd);
1229 assert(upnpIgd);
1230
1231 std::map<Mapping::key_t, Mapping> mapList;
1232
1233 if (not clientRegistered_ or not upnpIgd->isValid() or not upnpIgd->getLocalIp())
1234 return mapList;
1235
1236 // Set action name.
1237 static constexpr const char* action_name {"GetGenericPortMappingEntry"};
1238
1239 for (int entry_idx = 0;; entry_idx++) {
1240 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1241 action(nullptr, ixmlDocument_free); // Action pointer.
1242 IXML_Document* action_container_ptr = nullptr;
1243
1244 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1245 response(nullptr, ixmlDocument_free); // Response pointer.
1246 IXML_Document* response_container_ptr = nullptr;
1247
1248 UpnpAddToAction(&action_container_ptr,
1249 action_name,
1250 upnpIgd->getServiceType().c_str(),
1251 "NewPortMappingIndex",
1252 std::to_string(entry_idx).c_str());
1253 action.reset(action_container_ptr);
1254
1255 if (not action) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001256 // JAMI_WARN("PUPnP: Failed to add NewPortMappingIndex action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001257 break;
1258 }
1259
1260 int upnp_err = UpnpSendAction(ctrlptHandle_,
1261 upnpIgd->getControlURL().c_str(),
1262 upnpIgd->getServiceType().c_str(),
1263 nullptr,
1264 action.get(),
1265 &response_container_ptr);
1266 response.reset(response_container_ptr);
1267
1268 if (not response) {
1269 // No existing mapping. Abort silently.
1270 break;
1271 }
1272
1273 if (upnp_err != UPNP_E_SUCCESS) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001274 // JAMI_ERR("PUPnP: GetGenericPortMappingEntry returned with error: %i", upnp_err);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001275 break;
1276 }
1277
1278 // Check error code.
1279 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1280 if (not errorCode.empty()) {
1281 auto error = to_int<int>(errorCode);
1282 if (error == ARRAY_IDX_INVALID or error == CONFLICT_IN_MAPPING) {
1283 // No more port mapping entries in the response.
Morteza Namvar5f639522023-07-04 17:08:58 -04001284 // JAMI_DBG("PUPnP: No more mappings (found a total of %i mappings", entry_idx);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001285 break;
1286 } else {
1287 auto errorDescription = getFirstDocItem(response.get(), "errorDescription");
Adrien Béraud370257c2023-08-15 20:53:09 -04001288 if (logger_) logger_->error("PUPnP: GetGenericPortMappingEntry returned with error: {:s}: {:s}",
Adrien Béraud612b55b2023-05-29 10:42:04 -04001289 errorCode,
1290 errorDescription);
1291 break;
1292 }
1293 }
1294
1295 // Parse the response.
1296 auto desc_actual = getFirstDocItem(response.get(), "NewPortMappingDescription");
1297 auto client_ip = getFirstDocItem(response.get(), "NewInternalClient");
1298
1299 if (client_ip != getHostAddress().toString()) {
1300 // Silently ignore un-matching addresses.
1301 continue;
1302 }
1303
1304 if (desc_actual.find(description) == std::string::npos)
1305 continue;
1306
1307 auto port_internal = getFirstDocItem(response.get(), "NewInternalPort");
1308 auto port_external = getFirstDocItem(response.get(), "NewExternalPort");
1309 std::string transport(getFirstDocItem(response.get(), "NewProtocol"));
1310
1311 if (port_internal.empty() || port_external.empty() || transport.empty()) {
Adrien Béraud370257c2023-08-15 20:53:09 -04001312 // if (logger_) logger_->e("PUPnP: GetGenericPortMappingEntry returned an invalid entry at index %i",
Morteza Namvar5f639522023-07-04 17:08:58 -04001313 // entry_idx);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001314 continue;
1315 }
1316
1317 std::transform(transport.begin(), transport.end(), transport.begin(), ::toupper);
1318 PortType type = transport.find("TCP") != std::string::npos ? PortType::TCP : PortType::UDP;
1319 auto ePort = to_int<uint16_t>(port_external);
1320 auto iPort = to_int<uint16_t>(port_internal);
1321
1322 Mapping map(type, ePort, iPort);
1323 map.setIgd(igd);
1324
1325 mapList.emplace(map.getMapKey(), std::move(map));
1326 }
1327
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001328 if (logger_) logger_->debug("PUPnP: Found {:d} allocated mappings on IGD {:s}",
1329 mapList.size(),
1330 upnpIgd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001331
1332 return mapList;
1333}
1334
1335void
1336PUPnP::deleteMappingsByDescription(const std::shared_ptr<IGD>& igd, const std::string& description)
1337{
1338 if (not(clientRegistered_ and igd->getLocalIp()))
1339 return;
1340
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001341 if (logger_) logger_->debug("PUPnP: Remove all mappings (if any) on IGD {} matching descr prefix {}",
1342 igd->toString(),
1343 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001344
Adrien Béraud370257c2023-08-15 20:53:09 -04001345 ioContext->post([w=weak(), igd, description]{
1346 if (auto sthis = w.lock()) {
1347 auto mapList = sthis->getMappingsListByDescr(igd, description);
1348 for (auto const& [_, map] : mapList) {
1349 sthis->requestMappingRemove(map);
1350 }
1351 }
1352 });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001353}
1354
1355bool
1356PUPnP::actionAddPortMapping(const Mapping& mapping)
1357{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001358 if (not clientRegistered_)
1359 return false;
1360
1361 auto igdIn = std::dynamic_pointer_cast<UPnPIGD>(mapping.getIgd());
1362 if (not igdIn)
1363 return false;
1364
1365 // The requested IGD must be present in the list of local valid IGDs.
1366 auto igd = findMatchingIgd(igdIn->getControlURL());
1367
1368 if (not igd or not igd->isValid())
1369 return false;
1370
1371 // Action and response pointers.
1372 XMLDocument action(nullptr, ixmlDocument_free);
1373 IXML_Document* action_container_ptr = nullptr;
1374 XMLDocument response(nullptr, ixmlDocument_free);
1375 IXML_Document* response_container_ptr = nullptr;
1376
1377 // Set action sequence.
1378 UpnpAddToAction(&action_container_ptr,
1379 ACTION_ADD_PORT_MAPPING,
1380 igd->getServiceType().c_str(),
1381 "NewRemoteHost",
1382 "");
1383 UpnpAddToAction(&action_container_ptr,
1384 ACTION_ADD_PORT_MAPPING,
1385 igd->getServiceType().c_str(),
1386 "NewExternalPort",
1387 mapping.getExternalPortStr().c_str());
1388 UpnpAddToAction(&action_container_ptr,
1389 ACTION_ADD_PORT_MAPPING,
1390 igd->getServiceType().c_str(),
1391 "NewProtocol",
1392 mapping.getTypeStr());
1393 UpnpAddToAction(&action_container_ptr,
1394 ACTION_ADD_PORT_MAPPING,
1395 igd->getServiceType().c_str(),
1396 "NewInternalPort",
1397 mapping.getInternalPortStr().c_str());
1398 UpnpAddToAction(&action_container_ptr,
1399 ACTION_ADD_PORT_MAPPING,
1400 igd->getServiceType().c_str(),
1401 "NewInternalClient",
1402 getHostAddress().toString().c_str());
1403 UpnpAddToAction(&action_container_ptr,
1404 ACTION_ADD_PORT_MAPPING,
1405 igd->getServiceType().c_str(),
1406 "NewEnabled",
1407 "1");
1408 UpnpAddToAction(&action_container_ptr,
1409 ACTION_ADD_PORT_MAPPING,
1410 igd->getServiceType().c_str(),
1411 "NewPortMappingDescription",
1412 mapping.toString().c_str());
1413 UpnpAddToAction(&action_container_ptr,
1414 ACTION_ADD_PORT_MAPPING,
1415 igd->getServiceType().c_str(),
1416 "NewLeaseDuration",
1417 "0");
1418
1419 action.reset(action_container_ptr);
1420
1421 int upnp_err = UpnpSendAction(ctrlptHandle_,
1422 igd->getControlURL().c_str(),
1423 igd->getServiceType().c_str(),
1424 nullptr,
1425 action.get(),
1426 &response_container_ptr);
1427 response.reset(response_container_ptr);
1428
1429 bool success = true;
1430
1431 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001432 if (logger_) {
1433 logger_->warn("PUPnP: Failed to send action {} for mapping {}. {:d}: {}",
1434 ACTION_ADD_PORT_MAPPING,
1435 mapping.toString(),
1436 upnp_err,
1437 UpnpGetErrorMessage(upnp_err));
1438 logger_->warn("PUPnP: IGD ctrlUrl {}", igd->getControlURL());
1439 logger_->warn("PUPnP: IGD service type {}", igd->getServiceType());
1440 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001441
1442 success = false;
1443 }
1444
1445 // Check if an error has occurred.
1446 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1447 if (not errorCode.empty()) {
1448 success = false;
1449 // Try to get the error description.
1450 std::string errorDescription;
1451 if (response) {
1452 errorDescription = getFirstDocItem(response.get(), "errorDescription");
1453 }
1454
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001455 if (logger_) logger_->warn("PUPnP: {:s} returned with error: {:s} {:s}",
1456 ACTION_ADD_PORT_MAPPING,
1457 errorCode,
1458 errorDescription);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001459 }
1460 return success;
1461}
1462
1463bool
1464PUPnP::actionDeletePortMapping(const Mapping& mapping)
1465{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001466 if (not clientRegistered_)
1467 return false;
1468
1469 auto igdIn = std::dynamic_pointer_cast<UPnPIGD>(mapping.getIgd());
1470 if (not igdIn)
1471 return false;
1472
1473 // The requested IGD must be present in the list of local valid IGDs.
1474 auto igd = findMatchingIgd(igdIn->getControlURL());
1475
1476 if (not igd or not igd->isValid())
1477 return false;
1478
1479 // Action and response pointers.
1480 XMLDocument action(nullptr, ixmlDocument_free);
1481 IXML_Document* action_container_ptr = nullptr;
1482 XMLDocument response(nullptr, ixmlDocument_free);
1483 IXML_Document* response_container_ptr = nullptr;
1484
1485 // Set action sequence.
1486 UpnpAddToAction(&action_container_ptr,
1487 ACTION_DELETE_PORT_MAPPING,
1488 igd->getServiceType().c_str(),
1489 "NewRemoteHost",
1490 "");
1491 UpnpAddToAction(&action_container_ptr,
1492 ACTION_DELETE_PORT_MAPPING,
1493 igd->getServiceType().c_str(),
1494 "NewExternalPort",
1495 mapping.getExternalPortStr().c_str());
1496 UpnpAddToAction(&action_container_ptr,
1497 ACTION_DELETE_PORT_MAPPING,
1498 igd->getServiceType().c_str(),
1499 "NewProtocol",
1500 mapping.getTypeStr());
1501
1502 action.reset(action_container_ptr);
1503
1504 int upnp_err = UpnpSendAction(ctrlptHandle_,
1505 igd->getControlURL().c_str(),
1506 igd->getServiceType().c_str(),
1507 nullptr,
1508 action.get(),
1509 &response_container_ptr);
1510 response.reset(response_container_ptr);
1511
1512 bool success = true;
1513
1514 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001515 if (logger_) {
1516 logger_->warn("PUPnP: Failed to send action {} for mapping from {}. {:d}: {}",
1517 ACTION_DELETE_PORT_MAPPING,
1518 mapping.toString(),
1519 upnp_err,
1520 UpnpGetErrorMessage(upnp_err));
1521 logger_->warn("PUPnP: IGD ctrlUrl {}", igd->getControlURL());
1522 logger_->warn("PUPnP: IGD service type {}", igd->getServiceType());
1523 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001524 success = false;
1525 }
1526
1527 if (not response) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001528 if (logger_) logger_->warn("PUPnP: Failed to get response for {}", ACTION_DELETE_PORT_MAPPING);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001529 success = false;
1530 }
1531
1532 // Check if there is an error code.
1533 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1534 if (not errorCode.empty()) {
1535 auto errorDescription = getFirstDocItem(response.get(), "errorDescription");
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001536 if (logger_) logger_->warn("PUPnP: {:s} returned with error: {:s}: {:s}",
1537 ACTION_DELETE_PORT_MAPPING,
1538 errorCode,
1539 errorDescription);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001540 success = false;
1541 }
1542
1543 return success;
1544}
1545
1546} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001547} // namespace dhtnet