blob: d07cc20768b431190385e2e56f0d9792a9950c31 [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
Adrien Béraudd78d1ac2023-08-25 10:43:33 -040081errorOnResponse(IXML_Document* doc, const std::shared_ptr<dht::log::Logger>& logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -040082{
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éraudd78d1ac2023-08-25 10:43:33 -040089 if (logger) logger->warn("PUPnP: Response contains error: {:s}: {:s}",
90 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_);
Adrien Bérauda61adb52023-08-23 09:31:02 -0400114 auto hostinfo = ip_utils::getHostName();
Adrien Bérauda61adb52023-08-23 09:31:02 -0400115 int upnp_err = UpnpInit2(hostinfo.interface.empty() ? nullptr : hostinfo.interface.c_str(), 0);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400116 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400117 if (logger_) logger_->error("PUPnP: Can't initialize libupnp: {}", UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400118 UpnpFinish();
119 initialized_ = false;
120 return;
121 }
122
123 // Disable embedded WebServer if any.
124 if (UpnpIsWebserverEnabled() == 1) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400125 if (logger_) logger_->warn("PUPnP: Web-server is enabled. Disabling");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400126 UpnpEnableWebserver(0);
127 if (UpnpIsWebserverEnabled() == 1) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400128 if (logger_) logger_->error("PUPnP: Could not disable Web-server!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400129 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400130 if (logger_) logger_->debug("PUPnP: Web-server successfully disabled");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400131 }
132 }
133
134 char* ip_address = UpnpGetServerIpAddress();
135 char* ip_address6 = nullptr;
136 unsigned short port = UpnpGetServerPort();
137 unsigned short port6 = 0;
138#if UPNP_ENABLE_IPV6
139 ip_address6 = UpnpGetServerIp6Address();
140 port6 = UpnpGetServerPort6();
141#endif
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400142 if (logger_) {
143 if (ip_address6 and port6)
144 logger_->debug("PUPnP: Initialized on {}:{:d} | {}:{:d}", ip_address, port, ip_address6, port6);
145 else
146 logger_->debug("PUPnP: Initialized on {}:{:d}", ip_address, port);
147 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400148
149 // Relax the parser to allow malformed XML text.
150 ixmlRelaxParser(1);
151
152 initialized_ = true;
153}
154
155bool
156PUPnP::isRunning() const
157{
158 std::unique_lock<std::mutex> lk(pupnpMutex_);
159 return not shutdownComplete_;
160}
161
162void
163PUPnP::registerClient()
164{
165 assert(not clientRegistered_);
166
Adrien Béraud612b55b2023-05-29 10:42:04 -0400167 // Register Upnp control point.
168 int upnp_err = UpnpRegisterClient(ctrlPtCallback, this, &ctrlptHandle_);
169 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400170 if (logger_) logger_->error("PUPnP: Can't register client: {}", UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400171 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400172 if (logger_) logger_->debug("PUPnP: Successfully registered client");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400173 clientRegistered_ = true;
174 }
175}
176
177void
178PUPnP::setObserver(UpnpMappingObserver* obs)
179{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400180 observer_ = obs;
181}
182
183const IpAddr
184PUPnP::getHostAddress() const
185{
186 std::lock_guard<std::mutex> lock(pupnpMutex_);
187 return hostAddress_;
188}
189
190void
191PUPnP::terminate(std::condition_variable& cv)
192{
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400193 if (logger_) logger_->debug("PUPnP: Terminate instance {}", fmt::ptr(this));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400194
195 clientRegistered_ = false;
196 observer_ = nullptr;
Sébastien Blind14fc352023-10-06 15:21:53 -0400197 std::unique_lock<std::mutex> lk(ongoingOpsMtx_);
198 destroying_ = true;
199 cvOngoing_.wait(lk, [&]() { return ongoingOps_ == 0; });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400200
201 UpnpUnRegisterClient(ctrlptHandle_);
202
203 if (initialized_) {
204 if (UpnpFinish() != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400205 if (logger_) logger_->error("PUPnP: Failed to properly close lib-upnp");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400206 }
207
208 initialized_ = false;
209 }
210
211 // Clear all the lists.
212 discoveredIgdList_.clear();
213
Adrien Béraud7a82bee2023-08-30 10:26:45 -0400214 std::lock_guard<std::mutex> lock(pupnpMutex_);
215 validIgdList_.clear();
216 shutdownComplete_ = true;
217 cv.notify_one();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400218}
219
220void
221PUPnP::terminate()
222{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400223 std::condition_variable cv {};
Adrien Béraud370257c2023-08-15 20:53:09 -0400224 ioContext->dispatch([&] {
225 terminate(cv);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400226 });
227
Adrien Béraud7a82bee2023-08-30 10:26:45 -0400228 std::unique_lock<std::mutex> lk(pupnpMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400229 if (cv.wait_for(lk, std::chrono::seconds(10), [this] { return shutdownComplete_; })) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400230 if (logger_) logger_->debug("PUPnP: Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400231 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400232 if (logger_) logger_->error("PUPnP: Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400233 // Force stop if the shutdown take too much time.
234 shutdownComplete_ = true;
235 }
236}
237
238void
239PUPnP::searchForDevices()
240{
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400241 if (logger_) logger_->debug("PUPnP: Send IGD search request");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400242
243 // Send out search for multiple types of devices, as some routers may possibly
244 // only reply to one.
245
246 auto err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, this);
247 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400248 if (logger_) logger_->warn("PUPnP: Send search for UPNP_ROOT_DEVICE failed. Error {:d}: {}",
249 err,
250 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400251 }
252
253 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this);
254 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400255 if (logger_) logger_->warn("PUPnP: Send search for UPNP_IGD_DEVICE failed. Error {:d}: {}",
256 err,
257 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400258 }
259
260 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this);
261 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400262 if (logger_) logger_->warn("PUPnP: Send search for UPNP_WANIP_SERVICE failed. Error {:d}: {}",
263 err,
264 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400265 }
266
267 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this);
268 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400269 if (logger_) logger_->warn("PUPnP: Send search for UPNP_WANPPP_SERVICE failed. Error {:d}: {}",
270 err,
271 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400272 }
273}
274
275void
276PUPnP::clearIgds()
277{
Morteza Namvar5f639522023-07-04 17:08:58 -0400278 // JAMI_DBG("PUPnP: clearing IGDs and devices lists");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400279
Adrien Béraud370257c2023-08-15 20:53:09 -0400280 searchForIgdTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400281
282 igdSearchCounter_ = 0;
283
284 {
285 std::lock_guard<std::mutex> lock(pupnpMutex_);
286 for (auto const& igd : validIgdList_) {
287 igd->setValid(false);
288 }
289 validIgdList_.clear();
290 hostAddress_ = {};
291 }
292
293 discoveredIgdList_.clear();
294}
295
296void
297PUPnP::searchForIgd()
298{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400299 // Update local address before searching.
300 updateHostAddress();
301
302 if (isReady()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400303 if (logger_) logger_->debug("PUPnP: Already have a valid IGD. Skip the search request");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400304 return;
305 }
306
307 if (igdSearchCounter_++ >= PUPNP_MAX_RESTART_SEARCH_RETRIES) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400308 if (logger_) logger_->warn("PUPnP: Setup failed after {:d} trials. PUPnP will be disabled!",
309 PUPNP_MAX_RESTART_SEARCH_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400310 return;
311 }
312
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400313 if (logger_) logger_->debug("PUPnP: Start search for IGD: attempt {:d}", igdSearchCounter_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400314
315 // Do not init if the host is not valid. Otherwise, the init will fail
316 // anyway and may put libupnp in an unstable state (mainly deadlocks)
317 // even if the UpnpFinish() method is called.
318 if (not hasValidHostAddress()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400319 if (logger_) logger_->warn("PUPnP: Host address is invalid. Skipping the IGD search");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400320 } else {
321 // Init and register if needed
322 if (not initialized_) {
323 initUpnpLib();
324 }
325 if (initialized_ and not clientRegistered_) {
326 registerClient();
327 }
328 // Start searching
329 if (clientRegistered_) {
330 assert(initialized_);
331 searchForDevices();
332 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400333 if (logger_) logger_->warn("PUPnP: PUPNP not fully setup. Skipping the IGD search");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400334 }
335 }
336
337 // Cancel the current timer (if any) and re-schedule.
338 // The connectivity change may be received while the the local
339 // interface is not fully setup. The rescheduling typically
340 // usefull to mitigate this race.
Adrien Béraud370257c2023-08-15 20:53:09 -0400341 searchForIgdTimer_.expires_after(PUPNP_SEARCH_RETRY_UNIT * igdSearchCounter_);
342 searchForIgdTimer_.async_wait([w = weak()] (const asio::error_code& ec) {
343 if (not ec) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400344 if (auto upnpThis = w.lock())
345 upnpThis->searchForIgd();
Adrien Béraud370257c2023-08-15 20:53:09 -0400346 }
347 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400348}
349
350std::list<std::shared_ptr<IGD>>
351PUPnP::getIgdList() const
352{
353 std::lock_guard<std::mutex> lock(pupnpMutex_);
354 std::list<std::shared_ptr<IGD>> igdList;
355 for (auto& it : validIgdList_) {
356 // Return only active IGDs.
357 if (it->isValid()) {
358 igdList.emplace_back(it);
359 }
360 }
361 return igdList;
362}
363
364bool
365PUPnP::isReady() const
366{
367 // Must at least have a valid local address.
368 if (not getHostAddress() or getHostAddress().isLoopback())
369 return false;
370
371 return hasValidIgd();
372}
373
374bool
375PUPnP::hasValidIgd() const
376{
377 std::lock_guard<std::mutex> lock(pupnpMutex_);
378 for (auto& it : validIgdList_) {
379 if (it->isValid()) {
380 return true;
381 }
382 }
383 return false;
384}
385
386void
387PUPnP::updateHostAddress()
388{
389 std::lock_guard<std::mutex> lock(pupnpMutex_);
390 hostAddress_ = ip_utils::getLocalAddr(AF_INET);
391}
392
393bool
394PUPnP::hasValidHostAddress()
395{
396 std::lock_guard<std::mutex> lock(pupnpMutex_);
397 return hostAddress_ and not hostAddress_.isLoopback();
398}
399
400void
401PUPnP::incrementErrorsCounter(const std::shared_ptr<IGD>& igd)
402{
403 if (not igd or not igd->isValid())
404 return;
405 if (not igd->incrementErrorsCounter()) {
406 // Disable this IGD.
407 igd->setValid(false);
408 // Notify the listener.
409 if (observer_)
410 observer_->onIgdUpdated(igd, UpnpIgdEvent::INVALID_STATE);
411 }
412}
413
414bool
415PUPnP::validateIgd(const std::string& location, IXML_Document* doc_container_ptr)
416{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400417 assert(doc_container_ptr != nullptr);
418
419 XMLDocument document(doc_container_ptr, ixmlDocument_free);
420 auto descDoc = document.get();
421 // Check device type.
422 auto deviceType = getFirstDocItem(descDoc, "deviceType");
423 if (deviceType != UPNP_IGD_DEVICE) {
424 // Device type not IGD.
425 return false;
426 }
427
428 std::shared_ptr<UPnPIGD> igd_candidate = parseIgd(descDoc, location);
429 if (not igd_candidate) {
430 // No valid IGD candidate.
431 return false;
432 }
433
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400434 if (logger_) logger_->debug("PUPnP: Validating the IGD candidate [UDN: {}]\n"
435 " Name : {}\n"
436 " Service Type : {}\n"
437 " Service ID : {}\n"
438 " Base URL : {}\n"
439 " Location URL : {}\n"
440 " control URL : {}\n"
441 " Event URL : {}",
442 igd_candidate->getUID(),
443 igd_candidate->getFriendlyName(),
444 igd_candidate->getServiceType(),
445 igd_candidate->getServiceId(),
446 igd_candidate->getBaseURL(),
447 igd_candidate->getLocationURL(),
448 igd_candidate->getControlURL(),
449 igd_candidate->getEventSubURL());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400450
451 // Check if IGD is connected.
452 if (not actionIsIgdConnected(*igd_candidate)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400453 if (logger_) logger_->warn("PUPnP: IGD candidate {} is not connected", igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400454 return false;
455 }
456
457 // Validate external Ip.
458 igd_candidate->setPublicIp(actionGetExternalIP(*igd_candidate));
459 if (igd_candidate->getPublicIp().toString().empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400460 if (logger_) logger_->warn("PUPnP: IGD candidate {} has no valid external Ip",
461 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400462 return false;
463 }
464
465 // Validate internal Ip.
466 if (igd_candidate->getBaseURL().empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400467 if (logger_) logger_->warn("PUPnP: IGD candidate {} has no valid internal Ip",
468 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400469 return false;
470 }
471
472 // Typically the IGD local address should be extracted from the XML
473 // document (e.g. parsing the base URL). For simplicity, we assume
474 // that it matches the gateway as seen by the local interface.
475 if (const auto& localGw = ip_utils::getLocalGateway()) {
476 igd_candidate->setLocalIp(localGw);
477 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400478 if (logger_) logger_->warn("PUPnP: Could not set internal address for IGD candidate {}",
479 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400480 return false;
481 }
482
483 // Store info for subscription.
484 std::string eventSub = igd_candidate->getEventSubURL();
485
486 {
487 // Add the IGD if not already present in the list.
488 std::lock_guard<std::mutex> lock(pupnpMutex_);
489 for (auto& igd : validIgdList_) {
490 // Must not be a null pointer
491 assert(igd.get() != nullptr);
492 if (*igd == *igd_candidate) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400493 if (logger_) logger_->debug("PUPnP: Device [{}] with int/ext addresses [{}:{}] is already in the list of valid IGDs",
494 igd_candidate->getUID(),
495 igd_candidate->toString(),
496 igd_candidate->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400497 return true;
498 }
499 }
500 }
501
502 // We have a valid IGD
503 igd_candidate->setValid(true);
504
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400505 if (logger_) logger_->debug("PUPnP: Added a new IGD [{}] to the list of valid IGDs",
506 igd_candidate->getUID());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400507
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400508 if (logger_) logger_->debug("PUPnP: New IGD addresses [int: {} - ext: {}]",
509 igd_candidate->toString(),
510 igd_candidate->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400511
512 // Subscribe to IGD events.
513 int upnp_err = UpnpSubscribeAsync(ctrlptHandle_,
514 eventSub.c_str(),
515 UPNP_INFINITE,
516 subEventCallback,
517 this);
518 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400519 if (logger_) logger_->warn("PUPnP: Failed to send subscribe request to {}: error %i - {}",
520 igd_candidate->getUID(),
521 upnp_err,
522 UpnpGetErrorMessage(upnp_err));
523 return false;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400524 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400525 if (logger_) logger_->debug("PUPnP: Successfully subscribed to IGD {}", igd_candidate->getUID());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400526 }
527
528 {
529 // This is a new (and hopefully valid) IGD.
530 std::lock_guard<std::mutex> lock(pupnpMutex_);
531 validIgdList_.emplace_back(igd_candidate);
532 }
533
534 // Report to the listener.
Adrien Béraud370257c2023-08-15 20:53:09 -0400535 ioContext->post([w = weak(), igd_candidate] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400536 if (auto upnpThis = w.lock()) {
537 if (upnpThis->observer_)
538 upnpThis->observer_->onIgdUpdated(igd_candidate, UpnpIgdEvent::ADDED);
539 }
540 });
541
542 return true;
543}
544
545void
546PUPnP::requestMappingAdd(const Mapping& mapping)
547{
Adrien Béraud370257c2023-08-15 20:53:09 -0400548 ioContext->post([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400549 if (auto upnpThis = w.lock()) {
550 if (not upnpThis->isRunning())
551 return;
552 Mapping mapRes(mapping);
553 if (upnpThis->actionAddPortMapping(mapRes)) {
554 mapRes.setState(MappingState::OPEN);
555 mapRes.setInternalAddress(upnpThis->getHostAddress().toString());
556 upnpThis->processAddMapAction(mapRes);
557 } else {
558 upnpThis->incrementErrorsCounter(mapRes.getIgd());
559 mapRes.setState(MappingState::FAILED);
560 upnpThis->processRequestMappingFailure(mapRes);
561 }
562 }
563 });
564}
565
566void
567PUPnP::requestMappingRemove(const Mapping& mapping)
568{
569 // Send remove request using the matching IGD
Adrien Béraud370257c2023-08-15 20:53:09 -0400570 ioContext->dispatch([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400571 if (auto upnpThis = w.lock()) {
572 // Abort if we are shutting down.
573 if (not upnpThis->isRunning())
574 return;
575 if (upnpThis->actionDeletePortMapping(mapping)) {
576 upnpThis->processRemoveMapAction(mapping);
577 } else {
578 assert(mapping.getIgd());
579 // Dont need to report in case of failure.
580 upnpThis->incrementErrorsCounter(mapping.getIgd());
581 }
582 }
583 });
584}
585
586std::shared_ptr<UPnPIGD>
587PUPnP::findMatchingIgd(const std::string& ctrlURL) const
588{
589 std::lock_guard<std::mutex> lock(pupnpMutex_);
590
591 auto iter = std::find_if(validIgdList_.begin(),
592 validIgdList_.end(),
593 [&ctrlURL](const std::shared_ptr<IGD>& igd) {
594 if (auto upnpIgd = std::dynamic_pointer_cast<UPnPIGD>(igd)) {
595 return upnpIgd->getControlURL() == ctrlURL;
596 }
597 return false;
598 });
599
600 if (iter == validIgdList_.end()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400601 if (logger_) logger_->warn("PUPnP: Did not find the IGD matching ctrl URL [{}]", ctrlURL);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400602 return {};
603 }
604
605 return std::dynamic_pointer_cast<UPnPIGD>(*iter);
606}
607
608void
609PUPnP::processAddMapAction(const Mapping& map)
610{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400611 if (observer_ == nullptr)
612 return;
613
Adrien Béraud370257c2023-08-15 20:53:09 -0400614 ioContext->post([w = weak(), map] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400615 if (auto upnpThis = w.lock()) {
616 if (upnpThis->observer_)
617 upnpThis->observer_->onMappingAdded(map.getIgd(), std::move(map));
618 }
619 });
620}
621
622void
623PUPnP::processRequestMappingFailure(const Mapping& map)
624{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400625 if (observer_ == nullptr)
626 return;
627
Adrien Béraud370257c2023-08-15 20:53:09 -0400628 ioContext->post([w = weak(), map] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400629 if (auto upnpThis = w.lock()) {
Adrien Beraud64bb00f2023-08-23 19:06:46 -0400630 if (upnpThis->logger_) upnpThis->logger_->debug("PUPnP: Closed mapping {}", map.toString());
Morteza Namvar5f639522023-07-04 17:08:58 -0400631 // JAMI_DBG("PUPnP: Failed to request mapping %s", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400632 if (upnpThis->observer_)
633 upnpThis->observer_->onMappingRequestFailed(map);
634 }
635 });
636}
637
638void
639PUPnP::processRemoveMapAction(const Mapping& map)
640{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400641 if (observer_ == nullptr)
642 return;
643
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400644 if (logger_) logger_->warn("PUPnP: Closed mapping {}", map.toString());
Adrien Béraud370257c2023-08-15 20:53:09 -0400645 ioContext->post([map, obs = observer_] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400646 obs->onMappingRemoved(map.getIgd(), std::move(map));
647 });
648}
649
650const char*
651PUPnP::eventTypeToString(Upnp_EventType eventType)
652{
653 switch (eventType) {
654 case UPNP_CONTROL_ACTION_REQUEST:
655 return "UPNP_CONTROL_ACTION_REQUEST";
656 case UPNP_CONTROL_ACTION_COMPLETE:
657 return "UPNP_CONTROL_ACTION_COMPLETE";
658 case UPNP_CONTROL_GET_VAR_REQUEST:
659 return "UPNP_CONTROL_GET_VAR_REQUEST";
660 case UPNP_CONTROL_GET_VAR_COMPLETE:
661 return "UPNP_CONTROL_GET_VAR_COMPLETE";
662 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
663 return "UPNP_DISCOVERY_ADVERTISEMENT_ALIVE";
664 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
665 return "UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE";
666 case UPNP_DISCOVERY_SEARCH_RESULT:
667 return "UPNP_DISCOVERY_SEARCH_RESULT";
668 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
669 return "UPNP_DISCOVERY_SEARCH_TIMEOUT";
670 case UPNP_EVENT_SUBSCRIPTION_REQUEST:
671 return "UPNP_EVENT_SUBSCRIPTION_REQUEST";
672 case UPNP_EVENT_RECEIVED:
673 return "UPNP_EVENT_RECEIVED";
674 case UPNP_EVENT_RENEWAL_COMPLETE:
675 return "UPNP_EVENT_RENEWAL_COMPLETE";
676 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
677 return "UPNP_EVENT_SUBSCRIBE_COMPLETE";
678 case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:
679 return "UPNP_EVENT_UNSUBSCRIBE_COMPLETE";
680 case UPNP_EVENT_AUTORENEWAL_FAILED:
681 return "UPNP_EVENT_AUTORENEWAL_FAILED";
682 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
683 return "UPNP_EVENT_SUBSCRIPTION_EXPIRED";
684 default:
685 return "Unknown UPNP Event";
686 }
687}
688
689int
690PUPnP::ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data)
691{
692 auto pupnp = static_cast<PUPnP*>(user_data);
693
694 if (pupnp == nullptr) {
Adrien Bérauda61adb52023-08-23 09:31:02 -0400695 fmt::print(stderr, "PUPnP: Control point callback without PUPnP");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400696 return UPNP_E_SUCCESS;
697 }
698
699 auto upnpThis = pupnp->weak().lock();
Adrien Bérauda61adb52023-08-23 09:31:02 -0400700 if (not upnpThis) {
701 fmt::print(stderr, "PUPnP: Control point callback without PUPnP");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400702 return UPNP_E_SUCCESS;
Adrien Bérauda61adb52023-08-23 09:31:02 -0400703 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400704
705 // Ignore if already unregistered.
706 if (not upnpThis->clientRegistered_)
707 return UPNP_E_SUCCESS;
708
709 // Process the callback.
710 return upnpThis->handleCtrlPtUPnPEvents(event_type, event);
711}
712
713PUPnP::CtrlAction
714PUPnP::getAction(const char* xmlNode)
715{
716 if (strstr(xmlNode, ACTION_ADD_PORT_MAPPING)) {
717 return CtrlAction::ADD_PORT_MAPPING;
718 } else if (strstr(xmlNode, ACTION_DELETE_PORT_MAPPING)) {
719 return CtrlAction::DELETE_PORT_MAPPING;
720 } else if (strstr(xmlNode, ACTION_GET_GENERIC_PORT_MAPPING_ENTRY)) {
721 return CtrlAction::GET_GENERIC_PORT_MAPPING_ENTRY;
722 } else if (strstr(xmlNode, ACTION_GET_STATUS_INFO)) {
723 return CtrlAction::GET_STATUS_INFO;
724 } else if (strstr(xmlNode, ACTION_GET_EXTERNAL_IP_ADDRESS)) {
725 return CtrlAction::GET_EXTERNAL_IP_ADDRESS;
726 } else {
727 return CtrlAction::UNKNOWN;
728 }
729}
730
731void
732PUPnP::processDiscoverySearchResult(const std::string& cpDeviceId,
733 const std::string& igdLocationUrl,
734 const IpAddr& dstAddr)
735{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400736 // Update host address if needed.
737 if (not hasValidHostAddress())
738 updateHostAddress();
739
740 // The host address must be valid to proceed.
741 if (not hasValidHostAddress()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400742 if (logger_) logger_->warn("PUPnP: Local address is invalid. Ignore search result for now!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400743 return;
744 }
745
746 // Use the device ID and the URL as ID. This is necessary as some
747 // IGDs may have the same device ID but different URLs.
748
749 auto igdId = cpDeviceId + " url: " + igdLocationUrl;
750
751 if (not discoveredIgdList_.emplace(igdId).second) {
Adrien Beraud64bb00f2023-08-23 19:06:46 -0400752 //if (logger_) logger_->debug("PUPnP: IGD [{}] already in the list", igdId);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400753 return;
754 }
755
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400756 if (logger_) logger_->debug("PUPnP: Discovered a new IGD [{}]", igdId);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400757
758 // NOTE: here, we check if the location given is related to the source address.
759 // If it's not the case, it's certainly a router plugged in the network, but not
760 // related to this network. So the given location will be unreachable and this
761 // will cause some timeout.
762
763 // Only check the IP address (ignore the port number).
764 dht::http::Url url(igdLocationUrl);
765 if (IpAddr(url.host).toString(false) != dstAddr.toString(false)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400766 if (logger_) logger_->debug("PUPnP: Returned location {} does not match the source address {}",
767 IpAddr(url.host).toString(true, true),
768 dstAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400769 return;
770 }
771
772 // Run a separate thread to prevent blocking this thread
773 // if the IGD HTTP server is not responsive.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400774 dht::ThreadPool::io().run([w = weak(), url=igdLocationUrl] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400775 if (auto upnpThis = w.lock()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400776 upnpThis->downLoadIgdDescription(url);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400777 }
778 });
779}
780
781void
782PUPnP::downLoadIgdDescription(const std::string& locationUrl)
783{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400784 if(logger_) logger_->debug("PUPnP: downLoadIgdDescription {}", locationUrl);
Sébastien Blind14fc352023-10-06 15:21:53 -0400785 {
786 std::lock_guard<std::mutex> lk(ongoingOpsMtx_);
787 if (destroying_)
788 return;
789 ongoingOps_++;
790 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400791 IXML_Document* doc_container_ptr = nullptr;
792 int upnp_err = UpnpDownloadXmlDoc(locationUrl.c_str(), &doc_container_ptr);
793
794 if (upnp_err != UPNP_E_SUCCESS or not doc_container_ptr) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400795 if(logger_) logger_->warn("PUPnP: Error downloading device XML document from {} -> {}",
796 locationUrl,
797 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400798 } else {
Adrien Béraud370257c2023-08-15 20:53:09 -0400799 if(logger_) logger_->debug("PUPnP: Succeeded to download device XML document from {}", locationUrl);
800 ioContext->post([w = weak(), url = locationUrl, doc_container_ptr] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400801 if (auto upnpThis = w.lock()) {
802 upnpThis->validateIgd(url, doc_container_ptr);
803 }
804 });
805 }
Sébastien Blind14fc352023-10-06 15:21:53 -0400806 std::lock_guard<std::mutex> lk(ongoingOpsMtx_);
807 ongoingOps_--;
808 cvOngoing_.notify_one();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400809}
810
811void
812PUPnP::processDiscoveryAdvertisementByebye(const std::string& cpDeviceId)
813{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400814 discoveredIgdList_.erase(cpDeviceId);
815
816 std::shared_ptr<IGD> igd;
817 {
818 std::lock_guard<std::mutex> lk(pupnpMutex_);
819 for (auto it = validIgdList_.begin(); it != validIgdList_.end();) {
820 if ((*it)->getUID() == cpDeviceId) {
821 igd = *it;
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400822 if (logger_) logger_->debug("PUPnP: Received [{}] for IGD [{}] {}. Will be removed.",
823 PUPnP::eventTypeToString(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE),
824 igd->getUID(),
825 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400826 igd->setValid(false);
827 // Remove the IGD.
828 it = validIgdList_.erase(it);
829 break;
830 } else {
831 it++;
832 }
833 }
834 }
835
836 // Notify the listener.
837 if (observer_ and igd) {
838 observer_->onIgdUpdated(igd, UpnpIgdEvent::REMOVED);
839 }
840}
841
842void
843PUPnP::processDiscoverySubscriptionExpired(Upnp_EventType event_type, const std::string& eventSubUrl)
844{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400845 std::lock_guard<std::mutex> lk(pupnpMutex_);
846 for (auto& it : validIgdList_) {
847 if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it)) {
848 if (igd->getEventSubURL() == eventSubUrl) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400849 if (logger_) logger_->debug("PUPnP: Received [{}] event for IGD [{}] {}. Request a new subscribe.",
850 PUPnP::eventTypeToString(event_type),
851 igd->getUID(),
852 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400853 UpnpSubscribeAsync(ctrlptHandle_,
854 eventSubUrl.c_str(),
855 UPNP_INFINITE,
856 subEventCallback,
857 this);
858 break;
859 }
860 }
861 }
862}
863
864int
865PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
866{
867 switch (event_type) {
868 // "ALIVE" events are processed as "SEARCH RESULT". It might be usefull
869 // if "SEARCH RESULT" was missed.
870 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
871 case UPNP_DISCOVERY_SEARCH_RESULT: {
872 const UpnpDiscovery* d_event = (const UpnpDiscovery*) event;
873
874 // First check the error code.
875 auto upnp_status = UpnpDiscovery_get_ErrCode(d_event);
876 if (upnp_status != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400877 if (logger_) logger_->error("PUPnP: UPNP discovery is in erroneous state: %s",
878 UpnpGetErrorMessage(upnp_status));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400879 break;
880 }
881
882 // Parse the event's data.
883 std::string deviceId {UpnpDiscovery_get_DeviceID_cstr(d_event)};
884 std::string location {UpnpDiscovery_get_Location_cstr(d_event)};
885 IpAddr dstAddr(*(const pj_sockaddr*) (UpnpDiscovery_get_DestAddr(d_event)));
Adrien Béraud370257c2023-08-15 20:53:09 -0400886 ioContext->post([w = weak(),
Adrien Béraud612b55b2023-05-29 10:42:04 -0400887 deviceId = std::move(deviceId),
888 location = std::move(location),
889 dstAddr = std::move(dstAddr)] {
890 if (auto upnpThis = w.lock()) {
891 upnpThis->processDiscoverySearchResult(deviceId, location, dstAddr);
892 }
893 });
894 break;
895 }
896 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: {
897 const UpnpDiscovery* d_event = (const UpnpDiscovery*) event;
898
899 std::string deviceId(UpnpDiscovery_get_DeviceID_cstr(d_event));
900
901 // Process the response on the main thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400902 ioContext->post([w = weak(), deviceId = std::move(deviceId)] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400903 if (auto upnpThis = w.lock()) {
904 upnpThis->processDiscoveryAdvertisementByebye(deviceId);
905 }
906 });
907 break;
908 }
909 case UPNP_DISCOVERY_SEARCH_TIMEOUT: {
910 // Even if the discovery search is successful, it's normal to receive
911 // time-out events. This because we send search requests using various
912 // device types, which some of them may not return a response.
913 break;
914 }
915 case UPNP_EVENT_RECEIVED: {
916 // Nothing to do.
917 break;
918 }
919 // Treat failed autorenewal like an expired subscription.
920 case UPNP_EVENT_AUTORENEWAL_FAILED:
921 case UPNP_EVENT_SUBSCRIPTION_EXPIRED: // This event will occur only if autorenewal is disabled.
922 {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400923 if (logger_) logger_->warn("PUPnP: Received Subscription Event {}", eventTypeToString(event_type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400924 const UpnpEventSubscribe* es_event = (const UpnpEventSubscribe*) event;
925 if (es_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400926 if (logger_) logger_->warn("PUPnP: Received Subscription Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400927 break;
928 }
929 std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
930
931 // Process the response on the main thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400932 ioContext->post([w = weak(), event_type, publisherUrl = std::move(publisherUrl)] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400933 if (auto upnpThis = w.lock()) {
934 upnpThis->processDiscoverySubscriptionExpired(event_type, publisherUrl);
935 }
936 });
937 break;
938 }
939 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
940 case UPNP_EVENT_UNSUBSCRIBE_COMPLETE: {
941 UpnpEventSubscribe* es_event = (UpnpEventSubscribe*) event;
942 if (es_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400943 if (logger_) logger_->warn("PUPnP: Received Subscription Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400944 } else {
945 UpnpEventSubscribe_delete(es_event);
946 }
947 break;
948 }
949 case UPNP_CONTROL_ACTION_COMPLETE: {
950 const UpnpActionComplete* a_event = (const UpnpActionComplete*) event;
951 if (a_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400952 if (logger_) logger_->warn("PUPnP: Received Action Complete Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400953 break;
954 }
955 auto res = UpnpActionComplete_get_ErrCode(a_event);
956 if (res != UPNP_E_SUCCESS and res != UPNP_E_TIMEDOUT) {
957 auto err = UpnpActionComplete_get_ErrCode(a_event);
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400958 if (logger_) logger_->warn("PUPnP: Received Action Complete error %i %s", err, UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400959 } else {
960 auto actionRequest = UpnpActionComplete_get_ActionRequest(a_event);
961 // Abort if there is no action to process.
962 if (actionRequest == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400963 if (logger_) logger_->warn("PUPnP: Can't get the Action Request data from the event");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400964 break;
965 }
966
967 auto actionResult = UpnpActionComplete_get_ActionResult(a_event);
968 if (actionResult != nullptr) {
969 ixmlDocument_free(actionResult);
970 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400971 if (logger_) logger_->warn("PUPnP: Action Result document not found");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400972 }
973 }
974 break;
975 }
976 default: {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400977 if (logger_) logger_->warn("PUPnP: Unhandled Control Point event");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400978 break;
979 }
980 }
981
982 return UPNP_E_SUCCESS;
983}
984
985int
986PUPnP::subEventCallback(Upnp_EventType event_type, const void* event, void* user_data)
987{
988 if (auto pupnp = static_cast<PUPnP*>(user_data))
989 return pupnp->handleSubscriptionUPnPEvent(event_type, event);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400990 return 0;
991}
992
993int
994PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType, const void* event)
995{
996 UpnpEventSubscribe* es_event = static_cast<UpnpEventSubscribe*>(const_cast<void*>(event));
997
998 if (es_event == nullptr) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400999 // JAMI_ERR("PUPnP: Unexpected null pointer!");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001000 return UPNP_E_INVALID_ARGUMENT;
1001 }
1002 std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
1003 int upnp_err = UpnpEventSubscribe_get_ErrCode(es_event);
1004 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001005 if (logger_) logger_->warn("PUPnP: Subscription error {} from {}",
1006 UpnpGetErrorMessage(upnp_err),
1007 publisherUrl);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001008 return upnp_err;
1009 }
1010
1011 return UPNP_E_SUCCESS;
1012}
1013
1014std::unique_ptr<UPnPIGD>
1015PUPnP::parseIgd(IXML_Document* doc, std::string locationUrl)
1016{
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001017 if (not(doc and !locationUrl.empty()))
Adrien Béraud612b55b2023-05-29 10:42:04 -04001018 return nullptr;
1019
1020 // Check the UDN to see if its already in our device list.
1021 std::string UDN(getFirstDocItem(doc, "UDN"));
1022 if (UDN.empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001023 if (logger_) logger_->warn("PUPnP: could not find UDN in description document of device");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001024 return nullptr;
1025 } else {
1026 std::lock_guard<std::mutex> lk(pupnpMutex_);
1027 for (auto& it : validIgdList_) {
1028 if (it->getUID() == UDN) {
1029 // We already have this device in our list.
1030 return nullptr;
1031 }
1032 }
1033 }
1034
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001035 if (logger_) logger_->debug("PUPnP: Found new device [{}]", UDN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001036
1037 std::unique_ptr<UPnPIGD> new_igd;
1038 int upnp_err;
1039
1040 // Get friendly name.
1041 std::string friendlyName(getFirstDocItem(doc, "friendlyName"));
1042
1043 // Get base URL.
1044 std::string baseURL(getFirstDocItem(doc, "URLBase"));
1045 if (baseURL.empty())
1046 baseURL = locationUrl;
1047
1048 // Get list of services defined by serviceType.
1049 std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&> serviceList(nullptr,
1050 ixmlNodeList_free);
1051 serviceList.reset(ixmlDocument_getElementsByTagName(doc, "serviceType"));
1052 unsigned long list_length = ixmlNodeList_length(serviceList.get());
1053
1054 // Go through the "serviceType" nodes until we find the the correct service type.
1055 for (unsigned long node_idx = 0; node_idx < list_length; node_idx++) {
1056 IXML_Node* serviceType_node = ixmlNodeList_item(serviceList.get(), node_idx);
1057 std::string serviceType(getElementText(serviceType_node));
1058
1059 // Only check serviceType of WANIPConnection or WANPPPConnection.
1060 if (serviceType != UPNP_WANIP_SERVICE
1061 && serviceType != UPNP_WANPPP_SERVICE) {
1062 // IGD is not WANIP or WANPPP service. Going to next node.
1063 continue;
1064 }
1065
1066 // Get parent node.
1067 IXML_Node* service_node = ixmlNode_getParentNode(serviceType_node);
1068 if (not service_node) {
1069 // IGD serviceType has no parent node. Going to next node.
1070 continue;
1071 }
1072
1073 // Perform sanity check. The parent node should be called "service".
1074 if (strcmp(ixmlNode_getNodeName(service_node), "service") != 0) {
1075 // IGD "serviceType" parent node is not called "service". Going to next node.
1076 continue;
1077 }
1078
1079 // Get serviceId.
1080 IXML_Element* service_element = (IXML_Element*) service_node;
1081 std::string serviceId(getFirstElementItem(service_element, "serviceId"));
1082 if (serviceId.empty()) {
1083 // IGD "serviceId" is empty. Going to next node.
1084 continue;
1085 }
1086
1087 // Get the relative controlURL and turn it into absolute address using the URLBase.
1088 std::string controlURL(getFirstElementItem(service_element, "controlURL"));
1089 if (controlURL.empty()) {
1090 // IGD control URL is empty. Going to next node.
1091 continue;
1092 }
1093
1094 char* absolute_control_url = nullptr;
1095 upnp_err = UpnpResolveURL2(baseURL.c_str(), controlURL.c_str(), &absolute_control_url);
1096 if (upnp_err == UPNP_E_SUCCESS)
1097 controlURL = absolute_control_url;
1098 else
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001099 if (logger_) logger_->warn("PUPnP: Error resolving absolute controlURL -> {}",
1100 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001101
1102 std::free(absolute_control_url);
1103
1104 // Get the relative eventSubURL and turn it into absolute address using the URLBase.
1105 std::string eventSubURL(getFirstElementItem(service_element, "eventSubURL"));
1106 if (eventSubURL.empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001107 if (logger_) logger_->warn("PUPnP: IGD event sub URL is empty. Going to next node");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001108 continue;
1109 }
1110
1111 char* absolute_event_sub_url = nullptr;
1112 upnp_err = UpnpResolveURL2(baseURL.c_str(), eventSubURL.c_str(), &absolute_event_sub_url);
1113 if (upnp_err == UPNP_E_SUCCESS)
1114 eventSubURL = absolute_event_sub_url;
1115 else
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001116 if (logger_) logger_->warn("PUPnP: Error resolving absolute eventSubURL -> {}",
1117 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001118
1119 std::free(absolute_event_sub_url);
1120
1121 new_igd.reset(new UPnPIGD(std::move(UDN),
1122 std::move(baseURL),
1123 std::move(friendlyName),
1124 std::move(serviceType),
1125 std::move(serviceId),
1126 std::move(locationUrl),
1127 std::move(controlURL),
1128 std::move(eventSubURL)));
1129
1130 return new_igd;
1131 }
1132
1133 return nullptr;
1134}
1135
1136bool
1137PUPnP::actionIsIgdConnected(const UPnPIGD& igd)
1138{
1139 if (not clientRegistered_)
1140 return false;
1141
1142 // Set action name.
1143 IXML_Document* action_container_ptr = UpnpMakeAction("GetStatusInfo",
1144 igd.getServiceType().c_str(),
1145 0,
1146 nullptr);
1147 if (not action_container_ptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001148 if (logger_) logger_->warn("PUPnP: Failed to make GetStatusInfo action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001149 return false;
1150 }
1151 XMLDocument action(action_container_ptr, ixmlDocument_free); // Action pointer.
1152
1153 IXML_Document* response_container_ptr = nullptr;
1154 int upnp_err = UpnpSendAction(ctrlptHandle_,
1155 igd.getControlURL().c_str(),
1156 igd.getServiceType().c_str(),
1157 nullptr,
1158 action.get(),
1159 &response_container_ptr);
1160 if (not response_container_ptr or upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001161 if (logger_) logger_->warn("PUPnP: Failed to send GetStatusInfo action -> {}", UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001162 return false;
1163 }
1164 XMLDocument response(response_container_ptr, ixmlDocument_free);
1165
Adrien Béraudd78d1ac2023-08-25 10:43:33 -04001166 if (errorOnResponse(response.get(), logger_)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001167 if (logger_) logger_->warn("PUPnP: Failed to get GetStatusInfo from {} -> {:d}: {}",
1168 igd.getServiceType().c_str(),
1169 upnp_err,
1170 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001171 return false;
1172 }
1173
1174 // Parse response.
1175 auto status = getFirstDocItem(response.get(), "NewConnectionStatus");
1176 return status == "Connected";
1177}
1178
1179IpAddr
1180PUPnP::actionGetExternalIP(const UPnPIGD& igd)
1181{
1182 if (not clientRegistered_)
1183 return {};
1184
1185 // Action and response pointers.
1186 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1187 action(nullptr, ixmlDocument_free); // Action pointer.
1188 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1189 response(nullptr, ixmlDocument_free); // Response pointer.
1190
1191 // Set action name.
1192 static constexpr const char* action_name {"GetExternalIPAddress"};
1193
1194 IXML_Document* action_container_ptr = nullptr;
1195 action_container_ptr = UpnpMakeAction(action_name, igd.getServiceType().c_str(), 0, nullptr);
1196 action.reset(action_container_ptr);
1197
1198 if (not action) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001199 if (logger_) logger_->warn("PUPnP: Failed to make GetExternalIPAddress action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001200 return {};
1201 }
1202
1203 IXML_Document* response_container_ptr = nullptr;
1204 int upnp_err = UpnpSendAction(ctrlptHandle_,
1205 igd.getControlURL().c_str(),
1206 igd.getServiceType().c_str(),
1207 nullptr,
1208 action.get(),
1209 &response_container_ptr);
1210 response.reset(response_container_ptr);
1211
1212 if (not response or upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001213 if (logger_) logger_->warn("PUPnP: Failed to send GetExternalIPAddress action -> {}",
1214 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001215 return {};
1216 }
1217
Adrien Béraudd78d1ac2023-08-25 10:43:33 -04001218 if (errorOnResponse(response.get(), logger_)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001219 if (logger_) logger_->warn("PUPnP: Failed to get GetExternalIPAddress from {} -> {:d}: {}",
1220 igd.getServiceType(),
1221 upnp_err,
1222 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001223 return {};
1224 }
1225
1226 return {getFirstDocItem(response.get(), "NewExternalIPAddress")};
1227}
1228
1229std::map<Mapping::key_t, Mapping>
1230PUPnP::getMappingsListByDescr(const std::shared_ptr<IGD>& igd, const std::string& description) const
1231{
1232 auto upnpIgd = std::dynamic_pointer_cast<UPnPIGD>(igd);
1233 assert(upnpIgd);
1234
1235 std::map<Mapping::key_t, Mapping> mapList;
1236
1237 if (not clientRegistered_ or not upnpIgd->isValid() or not upnpIgd->getLocalIp())
1238 return mapList;
1239
1240 // Set action name.
1241 static constexpr const char* action_name {"GetGenericPortMappingEntry"};
1242
1243 for (int entry_idx = 0;; entry_idx++) {
1244 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1245 action(nullptr, ixmlDocument_free); // Action pointer.
1246 IXML_Document* action_container_ptr = nullptr;
1247
1248 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1249 response(nullptr, ixmlDocument_free); // Response pointer.
1250 IXML_Document* response_container_ptr = nullptr;
1251
1252 UpnpAddToAction(&action_container_ptr,
1253 action_name,
1254 upnpIgd->getServiceType().c_str(),
1255 "NewPortMappingIndex",
1256 std::to_string(entry_idx).c_str());
1257 action.reset(action_container_ptr);
1258
1259 if (not action) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001260 // JAMI_WARN("PUPnP: Failed to add NewPortMappingIndex action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001261 break;
1262 }
1263
1264 int upnp_err = UpnpSendAction(ctrlptHandle_,
1265 upnpIgd->getControlURL().c_str(),
1266 upnpIgd->getServiceType().c_str(),
1267 nullptr,
1268 action.get(),
1269 &response_container_ptr);
1270 response.reset(response_container_ptr);
1271
1272 if (not response) {
1273 // No existing mapping. Abort silently.
1274 break;
1275 }
1276
1277 if (upnp_err != UPNP_E_SUCCESS) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001278 // JAMI_ERR("PUPnP: GetGenericPortMappingEntry returned with error: %i", upnp_err);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001279 break;
1280 }
1281
1282 // Check error code.
1283 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1284 if (not errorCode.empty()) {
1285 auto error = to_int<int>(errorCode);
1286 if (error == ARRAY_IDX_INVALID or error == CONFLICT_IN_MAPPING) {
1287 // No more port mapping entries in the response.
Morteza Namvar5f639522023-07-04 17:08:58 -04001288 // JAMI_DBG("PUPnP: No more mappings (found a total of %i mappings", entry_idx);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001289 break;
1290 } else {
1291 auto errorDescription = getFirstDocItem(response.get(), "errorDescription");
Adrien Béraud370257c2023-08-15 20:53:09 -04001292 if (logger_) logger_->error("PUPnP: GetGenericPortMappingEntry returned with error: {:s}: {:s}",
Adrien Béraud612b55b2023-05-29 10:42:04 -04001293 errorCode,
1294 errorDescription);
1295 break;
1296 }
1297 }
1298
1299 // Parse the response.
1300 auto desc_actual = getFirstDocItem(response.get(), "NewPortMappingDescription");
1301 auto client_ip = getFirstDocItem(response.get(), "NewInternalClient");
1302
1303 if (client_ip != getHostAddress().toString()) {
1304 // Silently ignore un-matching addresses.
1305 continue;
1306 }
1307
1308 if (desc_actual.find(description) == std::string::npos)
1309 continue;
1310
1311 auto port_internal = getFirstDocItem(response.get(), "NewInternalPort");
1312 auto port_external = getFirstDocItem(response.get(), "NewExternalPort");
1313 std::string transport(getFirstDocItem(response.get(), "NewProtocol"));
1314
1315 if (port_internal.empty() || port_external.empty() || transport.empty()) {
Adrien Béraud370257c2023-08-15 20:53:09 -04001316 // if (logger_) logger_->e("PUPnP: GetGenericPortMappingEntry returned an invalid entry at index %i",
Morteza Namvar5f639522023-07-04 17:08:58 -04001317 // entry_idx);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001318 continue;
1319 }
1320
1321 std::transform(transport.begin(), transport.end(), transport.begin(), ::toupper);
1322 PortType type = transport.find("TCP") != std::string::npos ? PortType::TCP : PortType::UDP;
1323 auto ePort = to_int<uint16_t>(port_external);
1324 auto iPort = to_int<uint16_t>(port_internal);
1325
1326 Mapping map(type, ePort, iPort);
1327 map.setIgd(igd);
1328
1329 mapList.emplace(map.getMapKey(), std::move(map));
1330 }
1331
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001332 if (logger_) logger_->debug("PUPnP: Found {:d} allocated mappings on IGD {:s}",
1333 mapList.size(),
1334 upnpIgd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001335
1336 return mapList;
1337}
1338
1339void
1340PUPnP::deleteMappingsByDescription(const std::shared_ptr<IGD>& igd, const std::string& description)
1341{
1342 if (not(clientRegistered_ and igd->getLocalIp()))
1343 return;
1344
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001345 if (logger_) logger_->debug("PUPnP: Remove all mappings (if any) on IGD {} matching descr prefix {}",
1346 igd->toString(),
1347 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001348
Adrien Béraud370257c2023-08-15 20:53:09 -04001349 ioContext->post([w=weak(), igd, description]{
1350 if (auto sthis = w.lock()) {
1351 auto mapList = sthis->getMappingsListByDescr(igd, description);
1352 for (auto const& [_, map] : mapList) {
1353 sthis->requestMappingRemove(map);
1354 }
1355 }
1356 });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001357}
1358
1359bool
1360PUPnP::actionAddPortMapping(const Mapping& mapping)
1361{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001362 if (not clientRegistered_)
1363 return false;
1364
1365 auto igdIn = std::dynamic_pointer_cast<UPnPIGD>(mapping.getIgd());
1366 if (not igdIn)
1367 return false;
1368
1369 // The requested IGD must be present in the list of local valid IGDs.
1370 auto igd = findMatchingIgd(igdIn->getControlURL());
1371
1372 if (not igd or not igd->isValid())
1373 return false;
1374
1375 // Action and response pointers.
1376 XMLDocument action(nullptr, ixmlDocument_free);
1377 IXML_Document* action_container_ptr = nullptr;
1378 XMLDocument response(nullptr, ixmlDocument_free);
1379 IXML_Document* response_container_ptr = nullptr;
1380
1381 // Set action sequence.
1382 UpnpAddToAction(&action_container_ptr,
1383 ACTION_ADD_PORT_MAPPING,
1384 igd->getServiceType().c_str(),
1385 "NewRemoteHost",
1386 "");
1387 UpnpAddToAction(&action_container_ptr,
1388 ACTION_ADD_PORT_MAPPING,
1389 igd->getServiceType().c_str(),
1390 "NewExternalPort",
1391 mapping.getExternalPortStr().c_str());
1392 UpnpAddToAction(&action_container_ptr,
1393 ACTION_ADD_PORT_MAPPING,
1394 igd->getServiceType().c_str(),
1395 "NewProtocol",
1396 mapping.getTypeStr());
1397 UpnpAddToAction(&action_container_ptr,
1398 ACTION_ADD_PORT_MAPPING,
1399 igd->getServiceType().c_str(),
1400 "NewInternalPort",
1401 mapping.getInternalPortStr().c_str());
1402 UpnpAddToAction(&action_container_ptr,
1403 ACTION_ADD_PORT_MAPPING,
1404 igd->getServiceType().c_str(),
1405 "NewInternalClient",
1406 getHostAddress().toString().c_str());
1407 UpnpAddToAction(&action_container_ptr,
1408 ACTION_ADD_PORT_MAPPING,
1409 igd->getServiceType().c_str(),
1410 "NewEnabled",
1411 "1");
1412 UpnpAddToAction(&action_container_ptr,
1413 ACTION_ADD_PORT_MAPPING,
1414 igd->getServiceType().c_str(),
1415 "NewPortMappingDescription",
1416 mapping.toString().c_str());
1417 UpnpAddToAction(&action_container_ptr,
1418 ACTION_ADD_PORT_MAPPING,
1419 igd->getServiceType().c_str(),
1420 "NewLeaseDuration",
1421 "0");
1422
1423 action.reset(action_container_ptr);
1424
1425 int upnp_err = UpnpSendAction(ctrlptHandle_,
1426 igd->getControlURL().c_str(),
1427 igd->getServiceType().c_str(),
1428 nullptr,
1429 action.get(),
1430 &response_container_ptr);
1431 response.reset(response_container_ptr);
1432
1433 bool success = true;
1434
1435 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001436 if (logger_) {
1437 logger_->warn("PUPnP: Failed to send action {} for mapping {}. {:d}: {}",
1438 ACTION_ADD_PORT_MAPPING,
1439 mapping.toString(),
1440 upnp_err,
1441 UpnpGetErrorMessage(upnp_err));
1442 logger_->warn("PUPnP: IGD ctrlUrl {}", igd->getControlURL());
1443 logger_->warn("PUPnP: IGD service type {}", igd->getServiceType());
1444 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001445
1446 success = false;
1447 }
1448
1449 // Check if an error has occurred.
1450 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1451 if (not errorCode.empty()) {
1452 success = false;
1453 // Try to get the error description.
1454 std::string errorDescription;
1455 if (response) {
1456 errorDescription = getFirstDocItem(response.get(), "errorDescription");
1457 }
1458
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001459 if (logger_) logger_->warn("PUPnP: {:s} returned with error: {:s} {:s}",
1460 ACTION_ADD_PORT_MAPPING,
1461 errorCode,
1462 errorDescription);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001463 }
1464 return success;
1465}
1466
1467bool
1468PUPnP::actionDeletePortMapping(const Mapping& mapping)
1469{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001470 if (not clientRegistered_)
1471 return false;
1472
1473 auto igdIn = std::dynamic_pointer_cast<UPnPIGD>(mapping.getIgd());
1474 if (not igdIn)
1475 return false;
1476
1477 // The requested IGD must be present in the list of local valid IGDs.
1478 auto igd = findMatchingIgd(igdIn->getControlURL());
1479
1480 if (not igd or not igd->isValid())
1481 return false;
1482
1483 // Action and response pointers.
1484 XMLDocument action(nullptr, ixmlDocument_free);
1485 IXML_Document* action_container_ptr = nullptr;
1486 XMLDocument response(nullptr, ixmlDocument_free);
1487 IXML_Document* response_container_ptr = nullptr;
1488
1489 // Set action sequence.
1490 UpnpAddToAction(&action_container_ptr,
1491 ACTION_DELETE_PORT_MAPPING,
1492 igd->getServiceType().c_str(),
1493 "NewRemoteHost",
1494 "");
1495 UpnpAddToAction(&action_container_ptr,
1496 ACTION_DELETE_PORT_MAPPING,
1497 igd->getServiceType().c_str(),
1498 "NewExternalPort",
1499 mapping.getExternalPortStr().c_str());
1500 UpnpAddToAction(&action_container_ptr,
1501 ACTION_DELETE_PORT_MAPPING,
1502 igd->getServiceType().c_str(),
1503 "NewProtocol",
1504 mapping.getTypeStr());
1505
1506 action.reset(action_container_ptr);
1507
1508 int upnp_err = UpnpSendAction(ctrlptHandle_,
1509 igd->getControlURL().c_str(),
1510 igd->getServiceType().c_str(),
1511 nullptr,
1512 action.get(),
1513 &response_container_ptr);
1514 response.reset(response_container_ptr);
1515
1516 bool success = true;
1517
1518 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001519 if (logger_) {
1520 logger_->warn("PUPnP: Failed to send action {} for mapping from {}. {:d}: {}",
1521 ACTION_DELETE_PORT_MAPPING,
1522 mapping.toString(),
1523 upnp_err,
1524 UpnpGetErrorMessage(upnp_err));
1525 logger_->warn("PUPnP: IGD ctrlUrl {}", igd->getControlURL());
1526 logger_->warn("PUPnP: IGD service type {}", igd->getServiceType());
1527 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001528 success = false;
1529 }
1530
1531 if (not response) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001532 if (logger_) logger_->warn("PUPnP: Failed to get response for {}", ACTION_DELETE_PORT_MAPPING);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001533 success = false;
1534 }
1535
1536 // Check if there is an error code.
1537 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1538 if (not errorCode.empty()) {
1539 auto errorDescription = getFirstDocItem(response.get(), "errorDescription");
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001540 if (logger_) logger_->warn("PUPnP: {:s} returned with error: {:s}: {:s}",
1541 ACTION_DELETE_PORT_MAPPING,
1542 errorCode,
1543 errorDescription);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001544 success = false;
1545 }
1546
1547 return success;
1548}
1549
1550} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001551} // namespace dhtnet