blob: 8db05170fb16201a73dbcc851d61c9026becf5cb [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;
197
198 UpnpUnRegisterClient(ctrlptHandle_);
199
200 if (initialized_) {
201 if (UpnpFinish() != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400202 if (logger_) logger_->error("PUPnP: Failed to properly close lib-upnp");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400203 }
204
205 initialized_ = false;
206 }
207
208 // Clear all the lists.
209 discoveredIgdList_.clear();
210
Adrien Béraud7a82bee2023-08-30 10:26:45 -0400211 std::lock_guard<std::mutex> lock(pupnpMutex_);
212 validIgdList_.clear();
213 shutdownComplete_ = true;
214 cv.notify_one();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400215}
216
217void
218PUPnP::terminate()
219{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400220 std::condition_variable cv {};
Adrien Béraud370257c2023-08-15 20:53:09 -0400221 ioContext->dispatch([&] {
222 terminate(cv);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400223 });
224
Adrien Béraud7a82bee2023-08-30 10:26:45 -0400225 std::unique_lock<std::mutex> lk(pupnpMutex_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400226 if (cv.wait_for(lk, std::chrono::seconds(10), [this] { return shutdownComplete_; })) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400227 if (logger_) logger_->debug("PUPnP: Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400228 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400229 if (logger_) logger_->error("PUPnP: Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400230 // Force stop if the shutdown take too much time.
231 shutdownComplete_ = true;
232 }
233}
234
235void
236PUPnP::searchForDevices()
237{
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400238 if (logger_) logger_->debug("PUPnP: Send IGD search request");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400239
240 // Send out search for multiple types of devices, as some routers may possibly
241 // only reply to one.
242
243 auto err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, this);
244 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400245 if (logger_) logger_->warn("PUPnP: Send search for UPNP_ROOT_DEVICE failed. Error {:d}: {}",
246 err,
247 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400248 }
249
250 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this);
251 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400252 if (logger_) logger_->warn("PUPnP: Send search for UPNP_IGD_DEVICE failed. Error {:d}: {}",
253 err,
254 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400255 }
256
257 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this);
258 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400259 if (logger_) logger_->warn("PUPnP: Send search for UPNP_WANIP_SERVICE failed. Error {:d}: {}",
260 err,
261 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400262 }
263
264 err = UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this);
265 if (err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400266 if (logger_) logger_->warn("PUPnP: Send search for UPNP_WANPPP_SERVICE failed. Error {:d}: {}",
267 err,
268 UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400269 }
270}
271
272void
273PUPnP::clearIgds()
274{
Morteza Namvar5f639522023-07-04 17:08:58 -0400275 // JAMI_DBG("PUPnP: clearing IGDs and devices lists");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400276
Adrien Béraud370257c2023-08-15 20:53:09 -0400277 searchForIgdTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400278
279 igdSearchCounter_ = 0;
280
281 {
282 std::lock_guard<std::mutex> lock(pupnpMutex_);
283 for (auto const& igd : validIgdList_) {
284 igd->setValid(false);
285 }
286 validIgdList_.clear();
287 hostAddress_ = {};
288 }
289
290 discoveredIgdList_.clear();
291}
292
293void
294PUPnP::searchForIgd()
295{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400296 // Update local address before searching.
297 updateHostAddress();
298
299 if (isReady()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400300 if (logger_) logger_->debug("PUPnP: Already have a valid IGD. Skip the search request");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400301 return;
302 }
303
304 if (igdSearchCounter_++ >= PUPNP_MAX_RESTART_SEARCH_RETRIES) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400305 if (logger_) logger_->warn("PUPnP: Setup failed after {:d} trials. PUPnP will be disabled!",
306 PUPNP_MAX_RESTART_SEARCH_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400307 return;
308 }
309
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400310 if (logger_) logger_->debug("PUPnP: Start search for IGD: attempt {:d}", igdSearchCounter_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400311
312 // Do not init if the host is not valid. Otherwise, the init will fail
313 // anyway and may put libupnp in an unstable state (mainly deadlocks)
314 // even if the UpnpFinish() method is called.
315 if (not hasValidHostAddress()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400316 if (logger_) logger_->warn("PUPnP: Host address is invalid. Skipping the IGD search");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400317 } else {
318 // Init and register if needed
319 if (not initialized_) {
320 initUpnpLib();
321 }
322 if (initialized_ and not clientRegistered_) {
323 registerClient();
324 }
325 // Start searching
326 if (clientRegistered_) {
327 assert(initialized_);
328 searchForDevices();
329 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400330 if (logger_) logger_->warn("PUPnP: PUPNP not fully setup. Skipping the IGD search");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400331 }
332 }
333
334 // Cancel the current timer (if any) and re-schedule.
335 // The connectivity change may be received while the the local
336 // interface is not fully setup. The rescheduling typically
337 // usefull to mitigate this race.
Adrien Béraud370257c2023-08-15 20:53:09 -0400338 searchForIgdTimer_.expires_after(PUPNP_SEARCH_RETRY_UNIT * igdSearchCounter_);
339 searchForIgdTimer_.async_wait([w = weak()] (const asio::error_code& ec) {
340 if (not ec) {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400341 if (auto upnpThis = w.lock())
342 upnpThis->searchForIgd();
Adrien Béraud370257c2023-08-15 20:53:09 -0400343 }
344 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400345}
346
347std::list<std::shared_ptr<IGD>>
348PUPnP::getIgdList() const
349{
350 std::lock_guard<std::mutex> lock(pupnpMutex_);
351 std::list<std::shared_ptr<IGD>> igdList;
352 for (auto& it : validIgdList_) {
353 // Return only active IGDs.
354 if (it->isValid()) {
355 igdList.emplace_back(it);
356 }
357 }
358 return igdList;
359}
360
361bool
362PUPnP::isReady() const
363{
364 // Must at least have a valid local address.
365 if (not getHostAddress() or getHostAddress().isLoopback())
366 return false;
367
368 return hasValidIgd();
369}
370
371bool
372PUPnP::hasValidIgd() const
373{
374 std::lock_guard<std::mutex> lock(pupnpMutex_);
375 for (auto& it : validIgdList_) {
376 if (it->isValid()) {
377 return true;
378 }
379 }
380 return false;
381}
382
383void
384PUPnP::updateHostAddress()
385{
386 std::lock_guard<std::mutex> lock(pupnpMutex_);
387 hostAddress_ = ip_utils::getLocalAddr(AF_INET);
388}
389
390bool
391PUPnP::hasValidHostAddress()
392{
393 std::lock_guard<std::mutex> lock(pupnpMutex_);
394 return hostAddress_ and not hostAddress_.isLoopback();
395}
396
397void
398PUPnP::incrementErrorsCounter(const std::shared_ptr<IGD>& igd)
399{
400 if (not igd or not igd->isValid())
401 return;
402 if (not igd->incrementErrorsCounter()) {
403 // Disable this IGD.
404 igd->setValid(false);
405 // Notify the listener.
406 if (observer_)
407 observer_->onIgdUpdated(igd, UpnpIgdEvent::INVALID_STATE);
408 }
409}
410
411bool
412PUPnP::validateIgd(const std::string& location, IXML_Document* doc_container_ptr)
413{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400414 assert(doc_container_ptr != nullptr);
415
416 XMLDocument document(doc_container_ptr, ixmlDocument_free);
417 auto descDoc = document.get();
418 // Check device type.
419 auto deviceType = getFirstDocItem(descDoc, "deviceType");
420 if (deviceType != UPNP_IGD_DEVICE) {
421 // Device type not IGD.
422 return false;
423 }
424
425 std::shared_ptr<UPnPIGD> igd_candidate = parseIgd(descDoc, location);
426 if (not igd_candidate) {
427 // No valid IGD candidate.
428 return false;
429 }
430
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400431 if (logger_) logger_->debug("PUPnP: Validating the IGD candidate [UDN: {}]\n"
432 " Name : {}\n"
433 " Service Type : {}\n"
434 " Service ID : {}\n"
435 " Base URL : {}\n"
436 " Location URL : {}\n"
437 " control URL : {}\n"
438 " Event URL : {}",
439 igd_candidate->getUID(),
440 igd_candidate->getFriendlyName(),
441 igd_candidate->getServiceType(),
442 igd_candidate->getServiceId(),
443 igd_candidate->getBaseURL(),
444 igd_candidate->getLocationURL(),
445 igd_candidate->getControlURL(),
446 igd_candidate->getEventSubURL());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400447
448 // Check if IGD is connected.
449 if (not actionIsIgdConnected(*igd_candidate)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400450 if (logger_) logger_->warn("PUPnP: IGD candidate {} is not connected", igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400451 return false;
452 }
453
454 // Validate external Ip.
455 igd_candidate->setPublicIp(actionGetExternalIP(*igd_candidate));
456 if (igd_candidate->getPublicIp().toString().empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400457 if (logger_) logger_->warn("PUPnP: IGD candidate {} has no valid external Ip",
458 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400459 return false;
460 }
461
462 // Validate internal Ip.
463 if (igd_candidate->getBaseURL().empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400464 if (logger_) logger_->warn("PUPnP: IGD candidate {} has no valid internal Ip",
465 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400466 return false;
467 }
468
469 // Typically the IGD local address should be extracted from the XML
470 // document (e.g. parsing the base URL). For simplicity, we assume
471 // that it matches the gateway as seen by the local interface.
472 if (const auto& localGw = ip_utils::getLocalGateway()) {
473 igd_candidate->setLocalIp(localGw);
474 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400475 if (logger_) logger_->warn("PUPnP: Could not set internal address for IGD candidate {}",
476 igd_candidate->getUID().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400477 return false;
478 }
479
480 // Store info for subscription.
481 std::string eventSub = igd_candidate->getEventSubURL();
482
483 {
484 // Add the IGD if not already present in the list.
485 std::lock_guard<std::mutex> lock(pupnpMutex_);
486 for (auto& igd : validIgdList_) {
487 // Must not be a null pointer
488 assert(igd.get() != nullptr);
489 if (*igd == *igd_candidate) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400490 if (logger_) logger_->debug("PUPnP: Device [{}] with int/ext addresses [{}:{}] is already in the list of valid IGDs",
491 igd_candidate->getUID(),
492 igd_candidate->toString(),
493 igd_candidate->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400494 return true;
495 }
496 }
497 }
498
499 // We have a valid IGD
500 igd_candidate->setValid(true);
501
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400502 if (logger_) logger_->debug("PUPnP: Added a new IGD [{}] to the list of valid IGDs",
503 igd_candidate->getUID());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400504
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400505 if (logger_) logger_->debug("PUPnP: New IGD addresses [int: {} - ext: {}]",
506 igd_candidate->toString(),
507 igd_candidate->getPublicIp().toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400508
509 // Subscribe to IGD events.
510 int upnp_err = UpnpSubscribeAsync(ctrlptHandle_,
511 eventSub.c_str(),
512 UPNP_INFINITE,
513 subEventCallback,
514 this);
515 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400516 if (logger_) logger_->warn("PUPnP: Failed to send subscribe request to {}: error %i - {}",
517 igd_candidate->getUID(),
518 upnp_err,
519 UpnpGetErrorMessage(upnp_err));
520 return false;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400521 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400522 if (logger_) logger_->debug("PUPnP: Successfully subscribed to IGD {}", igd_candidate->getUID());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400523 }
524
525 {
526 // This is a new (and hopefully valid) IGD.
527 std::lock_guard<std::mutex> lock(pupnpMutex_);
528 validIgdList_.emplace_back(igd_candidate);
529 }
530
531 // Report to the listener.
Adrien Béraud370257c2023-08-15 20:53:09 -0400532 ioContext->post([w = weak(), igd_candidate] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400533 if (auto upnpThis = w.lock()) {
534 if (upnpThis->observer_)
535 upnpThis->observer_->onIgdUpdated(igd_candidate, UpnpIgdEvent::ADDED);
536 }
537 });
538
539 return true;
540}
541
542void
543PUPnP::requestMappingAdd(const Mapping& mapping)
544{
Adrien Béraud370257c2023-08-15 20:53:09 -0400545 ioContext->post([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400546 if (auto upnpThis = w.lock()) {
547 if (not upnpThis->isRunning())
548 return;
549 Mapping mapRes(mapping);
550 if (upnpThis->actionAddPortMapping(mapRes)) {
551 mapRes.setState(MappingState::OPEN);
552 mapRes.setInternalAddress(upnpThis->getHostAddress().toString());
553 upnpThis->processAddMapAction(mapRes);
554 } else {
555 upnpThis->incrementErrorsCounter(mapRes.getIgd());
556 mapRes.setState(MappingState::FAILED);
557 upnpThis->processRequestMappingFailure(mapRes);
558 }
559 }
560 });
561}
562
563void
564PUPnP::requestMappingRemove(const Mapping& mapping)
565{
566 // Send remove request using the matching IGD
Adrien Béraud370257c2023-08-15 20:53:09 -0400567 ioContext->dispatch([w = weak(), mapping] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400568 if (auto upnpThis = w.lock()) {
569 // Abort if we are shutting down.
570 if (not upnpThis->isRunning())
571 return;
572 if (upnpThis->actionDeletePortMapping(mapping)) {
573 upnpThis->processRemoveMapAction(mapping);
574 } else {
575 assert(mapping.getIgd());
576 // Dont need to report in case of failure.
577 upnpThis->incrementErrorsCounter(mapping.getIgd());
578 }
579 }
580 });
581}
582
583std::shared_ptr<UPnPIGD>
584PUPnP::findMatchingIgd(const std::string& ctrlURL) const
585{
586 std::lock_guard<std::mutex> lock(pupnpMutex_);
587
588 auto iter = std::find_if(validIgdList_.begin(),
589 validIgdList_.end(),
590 [&ctrlURL](const std::shared_ptr<IGD>& igd) {
591 if (auto upnpIgd = std::dynamic_pointer_cast<UPnPIGD>(igd)) {
592 return upnpIgd->getControlURL() == ctrlURL;
593 }
594 return false;
595 });
596
597 if (iter == validIgdList_.end()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400598 if (logger_) logger_->warn("PUPnP: Did not find the IGD matching ctrl URL [{}]", ctrlURL);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400599 return {};
600 }
601
602 return std::dynamic_pointer_cast<UPnPIGD>(*iter);
603}
604
605void
606PUPnP::processAddMapAction(const Mapping& map)
607{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400608 if (observer_ == nullptr)
609 return;
610
Adrien Béraud370257c2023-08-15 20:53:09 -0400611 ioContext->post([w = weak(), map] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400612 if (auto upnpThis = w.lock()) {
613 if (upnpThis->observer_)
614 upnpThis->observer_->onMappingAdded(map.getIgd(), std::move(map));
615 }
616 });
617}
618
619void
620PUPnP::processRequestMappingFailure(const Mapping& map)
621{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400622 if (observer_ == nullptr)
623 return;
624
Adrien Béraud370257c2023-08-15 20:53:09 -0400625 ioContext->post([w = weak(), map] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400626 if (auto upnpThis = w.lock()) {
Adrien Beraud64bb00f2023-08-23 19:06:46 -0400627 if (upnpThis->logger_) upnpThis->logger_->debug("PUPnP: Closed mapping {}", map.toString());
Morteza Namvar5f639522023-07-04 17:08:58 -0400628 // JAMI_DBG("PUPnP: Failed to request mapping %s", map.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400629 if (upnpThis->observer_)
630 upnpThis->observer_->onMappingRequestFailed(map);
631 }
632 });
633}
634
635void
636PUPnP::processRemoveMapAction(const Mapping& map)
637{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400638 if (observer_ == nullptr)
639 return;
640
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400641 if (logger_) logger_->warn("PUPnP: Closed mapping {}", map.toString());
Adrien Béraud370257c2023-08-15 20:53:09 -0400642 ioContext->post([map, obs = observer_] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400643 obs->onMappingRemoved(map.getIgd(), std::move(map));
644 });
645}
646
647const char*
648PUPnP::eventTypeToString(Upnp_EventType eventType)
649{
650 switch (eventType) {
651 case UPNP_CONTROL_ACTION_REQUEST:
652 return "UPNP_CONTROL_ACTION_REQUEST";
653 case UPNP_CONTROL_ACTION_COMPLETE:
654 return "UPNP_CONTROL_ACTION_COMPLETE";
655 case UPNP_CONTROL_GET_VAR_REQUEST:
656 return "UPNP_CONTROL_GET_VAR_REQUEST";
657 case UPNP_CONTROL_GET_VAR_COMPLETE:
658 return "UPNP_CONTROL_GET_VAR_COMPLETE";
659 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
660 return "UPNP_DISCOVERY_ADVERTISEMENT_ALIVE";
661 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
662 return "UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE";
663 case UPNP_DISCOVERY_SEARCH_RESULT:
664 return "UPNP_DISCOVERY_SEARCH_RESULT";
665 case UPNP_DISCOVERY_SEARCH_TIMEOUT:
666 return "UPNP_DISCOVERY_SEARCH_TIMEOUT";
667 case UPNP_EVENT_SUBSCRIPTION_REQUEST:
668 return "UPNP_EVENT_SUBSCRIPTION_REQUEST";
669 case UPNP_EVENT_RECEIVED:
670 return "UPNP_EVENT_RECEIVED";
671 case UPNP_EVENT_RENEWAL_COMPLETE:
672 return "UPNP_EVENT_RENEWAL_COMPLETE";
673 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
674 return "UPNP_EVENT_SUBSCRIBE_COMPLETE";
675 case UPNP_EVENT_UNSUBSCRIBE_COMPLETE:
676 return "UPNP_EVENT_UNSUBSCRIBE_COMPLETE";
677 case UPNP_EVENT_AUTORENEWAL_FAILED:
678 return "UPNP_EVENT_AUTORENEWAL_FAILED";
679 case UPNP_EVENT_SUBSCRIPTION_EXPIRED:
680 return "UPNP_EVENT_SUBSCRIPTION_EXPIRED";
681 default:
682 return "Unknown UPNP Event";
683 }
684}
685
686int
687PUPnP::ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data)
688{
689 auto pupnp = static_cast<PUPnP*>(user_data);
690
691 if (pupnp == nullptr) {
Adrien Bérauda61adb52023-08-23 09:31:02 -0400692 fmt::print(stderr, "PUPnP: Control point callback without PUPnP");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400693 return UPNP_E_SUCCESS;
694 }
695
696 auto upnpThis = pupnp->weak().lock();
Adrien Bérauda61adb52023-08-23 09:31:02 -0400697 if (not upnpThis) {
698 fmt::print(stderr, "PUPnP: Control point callback without PUPnP");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400699 return UPNP_E_SUCCESS;
Adrien Bérauda61adb52023-08-23 09:31:02 -0400700 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400701
702 // Ignore if already unregistered.
703 if (not upnpThis->clientRegistered_)
704 return UPNP_E_SUCCESS;
705
706 // Process the callback.
707 return upnpThis->handleCtrlPtUPnPEvents(event_type, event);
708}
709
710PUPnP::CtrlAction
711PUPnP::getAction(const char* xmlNode)
712{
713 if (strstr(xmlNode, ACTION_ADD_PORT_MAPPING)) {
714 return CtrlAction::ADD_PORT_MAPPING;
715 } else if (strstr(xmlNode, ACTION_DELETE_PORT_MAPPING)) {
716 return CtrlAction::DELETE_PORT_MAPPING;
717 } else if (strstr(xmlNode, ACTION_GET_GENERIC_PORT_MAPPING_ENTRY)) {
718 return CtrlAction::GET_GENERIC_PORT_MAPPING_ENTRY;
719 } else if (strstr(xmlNode, ACTION_GET_STATUS_INFO)) {
720 return CtrlAction::GET_STATUS_INFO;
721 } else if (strstr(xmlNode, ACTION_GET_EXTERNAL_IP_ADDRESS)) {
722 return CtrlAction::GET_EXTERNAL_IP_ADDRESS;
723 } else {
724 return CtrlAction::UNKNOWN;
725 }
726}
727
728void
729PUPnP::processDiscoverySearchResult(const std::string& cpDeviceId,
730 const std::string& igdLocationUrl,
731 const IpAddr& dstAddr)
732{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400733 // Update host address if needed.
734 if (not hasValidHostAddress())
735 updateHostAddress();
736
737 // The host address must be valid to proceed.
738 if (not hasValidHostAddress()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400739 if (logger_) logger_->warn("PUPnP: Local address is invalid. Ignore search result for now!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400740 return;
741 }
742
743 // Use the device ID and the URL as ID. This is necessary as some
744 // IGDs may have the same device ID but different URLs.
745
746 auto igdId = cpDeviceId + " url: " + igdLocationUrl;
747
748 if (not discoveredIgdList_.emplace(igdId).second) {
Adrien Beraud64bb00f2023-08-23 19:06:46 -0400749 //if (logger_) logger_->debug("PUPnP: IGD [{}] already in the list", igdId);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400750 return;
751 }
752
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400753 if (logger_) logger_->debug("PUPnP: Discovered a new IGD [{}]", igdId);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400754
755 // NOTE: here, we check if the location given is related to the source address.
756 // If it's not the case, it's certainly a router plugged in the network, but not
757 // related to this network. So the given location will be unreachable and this
758 // will cause some timeout.
759
760 // Only check the IP address (ignore the port number).
761 dht::http::Url url(igdLocationUrl);
762 if (IpAddr(url.host).toString(false) != dstAddr.toString(false)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400763 if (logger_) logger_->debug("PUPnP: Returned location {} does not match the source address {}",
764 IpAddr(url.host).toString(true, true),
765 dstAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400766 return;
767 }
768
769 // Run a separate thread to prevent blocking this thread
770 // if the IGD HTTP server is not responsive.
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400771 dht::ThreadPool::io().run([w = weak(), url=igdLocationUrl] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400772 if (auto upnpThis = w.lock()) {
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400773 upnpThis->downLoadIgdDescription(url);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400774 }
775 });
776}
777
778void
779PUPnP::downLoadIgdDescription(const std::string& locationUrl)
780{
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400781 if(logger_) logger_->debug("PUPnP: downLoadIgdDescription {}", locationUrl);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400782 IXML_Document* doc_container_ptr = nullptr;
783 int upnp_err = UpnpDownloadXmlDoc(locationUrl.c_str(), &doc_container_ptr);
784
785 if (upnp_err != UPNP_E_SUCCESS or not doc_container_ptr) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400786 if(logger_) logger_->warn("PUPnP: Error downloading device XML document from {} -> {}",
787 locationUrl,
788 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400789 } else {
Adrien Béraud370257c2023-08-15 20:53:09 -0400790 if(logger_) logger_->debug("PUPnP: Succeeded to download device XML document from {}", locationUrl);
791 ioContext->post([w = weak(), url = locationUrl, doc_container_ptr] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400792 if (auto upnpThis = w.lock()) {
793 upnpThis->validateIgd(url, doc_container_ptr);
794 }
795 });
796 }
797}
798
799void
800PUPnP::processDiscoveryAdvertisementByebye(const std::string& cpDeviceId)
801{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400802 discoveredIgdList_.erase(cpDeviceId);
803
804 std::shared_ptr<IGD> igd;
805 {
806 std::lock_guard<std::mutex> lk(pupnpMutex_);
807 for (auto it = validIgdList_.begin(); it != validIgdList_.end();) {
808 if ((*it)->getUID() == cpDeviceId) {
809 igd = *it;
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400810 if (logger_) logger_->debug("PUPnP: Received [{}] for IGD [{}] {}. Will be removed.",
811 PUPnP::eventTypeToString(UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE),
812 igd->getUID(),
813 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400814 igd->setValid(false);
815 // Remove the IGD.
816 it = validIgdList_.erase(it);
817 break;
818 } else {
819 it++;
820 }
821 }
822 }
823
824 // Notify the listener.
825 if (observer_ and igd) {
826 observer_->onIgdUpdated(igd, UpnpIgdEvent::REMOVED);
827 }
828}
829
830void
831PUPnP::processDiscoverySubscriptionExpired(Upnp_EventType event_type, const std::string& eventSubUrl)
832{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400833 std::lock_guard<std::mutex> lk(pupnpMutex_);
834 for (auto& it : validIgdList_) {
835 if (auto igd = std::dynamic_pointer_cast<UPnPIGD>(it)) {
836 if (igd->getEventSubURL() == eventSubUrl) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400837 if (logger_) logger_->debug("PUPnP: Received [{}] event for IGD [{}] {}. Request a new subscribe.",
838 PUPnP::eventTypeToString(event_type),
839 igd->getUID(),
840 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400841 UpnpSubscribeAsync(ctrlptHandle_,
842 eventSubUrl.c_str(),
843 UPNP_INFINITE,
844 subEventCallback,
845 this);
846 break;
847 }
848 }
849 }
850}
851
852int
853PUPnP::handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event)
854{
855 switch (event_type) {
856 // "ALIVE" events are processed as "SEARCH RESULT". It might be usefull
857 // if "SEARCH RESULT" was missed.
858 case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
859 case UPNP_DISCOVERY_SEARCH_RESULT: {
860 const UpnpDiscovery* d_event = (const UpnpDiscovery*) event;
861
862 // First check the error code.
863 auto upnp_status = UpnpDiscovery_get_ErrCode(d_event);
864 if (upnp_status != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400865 if (logger_) logger_->error("PUPnP: UPNP discovery is in erroneous state: %s",
866 UpnpGetErrorMessage(upnp_status));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400867 break;
868 }
869
870 // Parse the event's data.
871 std::string deviceId {UpnpDiscovery_get_DeviceID_cstr(d_event)};
872 std::string location {UpnpDiscovery_get_Location_cstr(d_event)};
873 IpAddr dstAddr(*(const pj_sockaddr*) (UpnpDiscovery_get_DestAddr(d_event)));
Adrien Béraud370257c2023-08-15 20:53:09 -0400874 ioContext->post([w = weak(),
Adrien Béraud612b55b2023-05-29 10:42:04 -0400875 deviceId = std::move(deviceId),
876 location = std::move(location),
877 dstAddr = std::move(dstAddr)] {
878 if (auto upnpThis = w.lock()) {
879 upnpThis->processDiscoverySearchResult(deviceId, location, dstAddr);
880 }
881 });
882 break;
883 }
884 case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: {
885 const UpnpDiscovery* d_event = (const UpnpDiscovery*) event;
886
887 std::string deviceId(UpnpDiscovery_get_DeviceID_cstr(d_event));
888
889 // Process the response on the main thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400890 ioContext->post([w = weak(), deviceId = std::move(deviceId)] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400891 if (auto upnpThis = w.lock()) {
892 upnpThis->processDiscoveryAdvertisementByebye(deviceId);
893 }
894 });
895 break;
896 }
897 case UPNP_DISCOVERY_SEARCH_TIMEOUT: {
898 // Even if the discovery search is successful, it's normal to receive
899 // time-out events. This because we send search requests using various
900 // device types, which some of them may not return a response.
901 break;
902 }
903 case UPNP_EVENT_RECEIVED: {
904 // Nothing to do.
905 break;
906 }
907 // Treat failed autorenewal like an expired subscription.
908 case UPNP_EVENT_AUTORENEWAL_FAILED:
909 case UPNP_EVENT_SUBSCRIPTION_EXPIRED: // This event will occur only if autorenewal is disabled.
910 {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400911 if (logger_) logger_->warn("PUPnP: Received Subscription Event {}", eventTypeToString(event_type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400912 const UpnpEventSubscribe* es_event = (const UpnpEventSubscribe*) event;
913 if (es_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400914 if (logger_) logger_->warn("PUPnP: Received Subscription Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400915 break;
916 }
917 std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
918
919 // Process the response on the main thread.
Adrien Béraud370257c2023-08-15 20:53:09 -0400920 ioContext->post([w = weak(), event_type, publisherUrl = std::move(publisherUrl)] {
Adrien Béraud612b55b2023-05-29 10:42:04 -0400921 if (auto upnpThis = w.lock()) {
922 upnpThis->processDiscoverySubscriptionExpired(event_type, publisherUrl);
923 }
924 });
925 break;
926 }
927 case UPNP_EVENT_SUBSCRIBE_COMPLETE:
928 case UPNP_EVENT_UNSUBSCRIBE_COMPLETE: {
929 UpnpEventSubscribe* es_event = (UpnpEventSubscribe*) event;
930 if (es_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400931 if (logger_) logger_->warn("PUPnP: Received Subscription Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400932 } else {
933 UpnpEventSubscribe_delete(es_event);
934 }
935 break;
936 }
937 case UPNP_CONTROL_ACTION_COMPLETE: {
938 const UpnpActionComplete* a_event = (const UpnpActionComplete*) event;
939 if (a_event == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400940 if (logger_) logger_->warn("PUPnP: Received Action Complete Event with null pointer");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400941 break;
942 }
943 auto res = UpnpActionComplete_get_ErrCode(a_event);
944 if (res != UPNP_E_SUCCESS and res != UPNP_E_TIMEDOUT) {
945 auto err = UpnpActionComplete_get_ErrCode(a_event);
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400946 if (logger_) logger_->warn("PUPnP: Received Action Complete error %i %s", err, UpnpGetErrorMessage(err));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400947 } else {
948 auto actionRequest = UpnpActionComplete_get_ActionRequest(a_event);
949 // Abort if there is no action to process.
950 if (actionRequest == nullptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400951 if (logger_) logger_->warn("PUPnP: Can't get the Action Request data from the event");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400952 break;
953 }
954
955 auto actionResult = UpnpActionComplete_get_ActionResult(a_event);
956 if (actionResult != nullptr) {
957 ixmlDocument_free(actionResult);
958 } else {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400959 if (logger_) logger_->warn("PUPnP: Action Result document not found");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400960 }
961 }
962 break;
963 }
964 default: {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400965 if (logger_) logger_->warn("PUPnP: Unhandled Control Point event");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400966 break;
967 }
968 }
969
970 return UPNP_E_SUCCESS;
971}
972
973int
974PUPnP::subEventCallback(Upnp_EventType event_type, const void* event, void* user_data)
975{
976 if (auto pupnp = static_cast<PUPnP*>(user_data))
977 return pupnp->handleSubscriptionUPnPEvent(event_type, event);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400978 return 0;
979}
980
981int
982PUPnP::handleSubscriptionUPnPEvent(Upnp_EventType, const void* event)
983{
984 UpnpEventSubscribe* es_event = static_cast<UpnpEventSubscribe*>(const_cast<void*>(event));
985
986 if (es_event == nullptr) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400987 // JAMI_ERR("PUPnP: Unexpected null pointer!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400988 return UPNP_E_INVALID_ARGUMENT;
989 }
990 std::string publisherUrl(UpnpEventSubscribe_get_PublisherUrl_cstr(es_event));
991 int upnp_err = UpnpEventSubscribe_get_ErrCode(es_event);
992 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -0400993 if (logger_) logger_->warn("PUPnP: Subscription error {} from {}",
994 UpnpGetErrorMessage(upnp_err),
995 publisherUrl);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400996 return upnp_err;
997 }
998
999 return UPNP_E_SUCCESS;
1000}
1001
1002std::unique_ptr<UPnPIGD>
1003PUPnP::parseIgd(IXML_Document* doc, std::string locationUrl)
1004{
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001005 if (not(doc and !locationUrl.empty()))
Adrien Béraud612b55b2023-05-29 10:42:04 -04001006 return nullptr;
1007
1008 // Check the UDN to see if its already in our device list.
1009 std::string UDN(getFirstDocItem(doc, "UDN"));
1010 if (UDN.empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001011 if (logger_) logger_->warn("PUPnP: could not find UDN in description document of device");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001012 return nullptr;
1013 } else {
1014 std::lock_guard<std::mutex> lk(pupnpMutex_);
1015 for (auto& it : validIgdList_) {
1016 if (it->getUID() == UDN) {
1017 // We already have this device in our list.
1018 return nullptr;
1019 }
1020 }
1021 }
1022
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001023 if (logger_) logger_->debug("PUPnP: Found new device [{}]", UDN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001024
1025 std::unique_ptr<UPnPIGD> new_igd;
1026 int upnp_err;
1027
1028 // Get friendly name.
1029 std::string friendlyName(getFirstDocItem(doc, "friendlyName"));
1030
1031 // Get base URL.
1032 std::string baseURL(getFirstDocItem(doc, "URLBase"));
1033 if (baseURL.empty())
1034 baseURL = locationUrl;
1035
1036 // Get list of services defined by serviceType.
1037 std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&> serviceList(nullptr,
1038 ixmlNodeList_free);
1039 serviceList.reset(ixmlDocument_getElementsByTagName(doc, "serviceType"));
1040 unsigned long list_length = ixmlNodeList_length(serviceList.get());
1041
1042 // Go through the "serviceType" nodes until we find the the correct service type.
1043 for (unsigned long node_idx = 0; node_idx < list_length; node_idx++) {
1044 IXML_Node* serviceType_node = ixmlNodeList_item(serviceList.get(), node_idx);
1045 std::string serviceType(getElementText(serviceType_node));
1046
1047 // Only check serviceType of WANIPConnection or WANPPPConnection.
1048 if (serviceType != UPNP_WANIP_SERVICE
1049 && serviceType != UPNP_WANPPP_SERVICE) {
1050 // IGD is not WANIP or WANPPP service. Going to next node.
1051 continue;
1052 }
1053
1054 // Get parent node.
1055 IXML_Node* service_node = ixmlNode_getParentNode(serviceType_node);
1056 if (not service_node) {
1057 // IGD serviceType has no parent node. Going to next node.
1058 continue;
1059 }
1060
1061 // Perform sanity check. The parent node should be called "service".
1062 if (strcmp(ixmlNode_getNodeName(service_node), "service") != 0) {
1063 // IGD "serviceType" parent node is not called "service". Going to next node.
1064 continue;
1065 }
1066
1067 // Get serviceId.
1068 IXML_Element* service_element = (IXML_Element*) service_node;
1069 std::string serviceId(getFirstElementItem(service_element, "serviceId"));
1070 if (serviceId.empty()) {
1071 // IGD "serviceId" is empty. Going to next node.
1072 continue;
1073 }
1074
1075 // Get the relative controlURL and turn it into absolute address using the URLBase.
1076 std::string controlURL(getFirstElementItem(service_element, "controlURL"));
1077 if (controlURL.empty()) {
1078 // IGD control URL is empty. Going to next node.
1079 continue;
1080 }
1081
1082 char* absolute_control_url = nullptr;
1083 upnp_err = UpnpResolveURL2(baseURL.c_str(), controlURL.c_str(), &absolute_control_url);
1084 if (upnp_err == UPNP_E_SUCCESS)
1085 controlURL = absolute_control_url;
1086 else
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001087 if (logger_) logger_->warn("PUPnP: Error resolving absolute controlURL -> {}",
1088 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001089
1090 std::free(absolute_control_url);
1091
1092 // Get the relative eventSubURL and turn it into absolute address using the URLBase.
1093 std::string eventSubURL(getFirstElementItem(service_element, "eventSubURL"));
1094 if (eventSubURL.empty()) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001095 if (logger_) logger_->warn("PUPnP: IGD event sub URL is empty. Going to next node");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001096 continue;
1097 }
1098
1099 char* absolute_event_sub_url = nullptr;
1100 upnp_err = UpnpResolveURL2(baseURL.c_str(), eventSubURL.c_str(), &absolute_event_sub_url);
1101 if (upnp_err == UPNP_E_SUCCESS)
1102 eventSubURL = absolute_event_sub_url;
1103 else
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001104 if (logger_) logger_->warn("PUPnP: Error resolving absolute eventSubURL -> {}",
1105 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001106
1107 std::free(absolute_event_sub_url);
1108
1109 new_igd.reset(new UPnPIGD(std::move(UDN),
1110 std::move(baseURL),
1111 std::move(friendlyName),
1112 std::move(serviceType),
1113 std::move(serviceId),
1114 std::move(locationUrl),
1115 std::move(controlURL),
1116 std::move(eventSubURL)));
1117
1118 return new_igd;
1119 }
1120
1121 return nullptr;
1122}
1123
1124bool
1125PUPnP::actionIsIgdConnected(const UPnPIGD& igd)
1126{
1127 if (not clientRegistered_)
1128 return false;
1129
1130 // Set action name.
1131 IXML_Document* action_container_ptr = UpnpMakeAction("GetStatusInfo",
1132 igd.getServiceType().c_str(),
1133 0,
1134 nullptr);
1135 if (not action_container_ptr) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001136 if (logger_) logger_->warn("PUPnP: Failed to make GetStatusInfo action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001137 return false;
1138 }
1139 XMLDocument action(action_container_ptr, ixmlDocument_free); // Action pointer.
1140
1141 IXML_Document* response_container_ptr = nullptr;
1142 int upnp_err = UpnpSendAction(ctrlptHandle_,
1143 igd.getControlURL().c_str(),
1144 igd.getServiceType().c_str(),
1145 nullptr,
1146 action.get(),
1147 &response_container_ptr);
1148 if (not response_container_ptr or upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001149 if (logger_) logger_->warn("PUPnP: Failed to send GetStatusInfo action -> {}", UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001150 return false;
1151 }
1152 XMLDocument response(response_container_ptr, ixmlDocument_free);
1153
Adrien Béraudd78d1ac2023-08-25 10:43:33 -04001154 if (errorOnResponse(response.get(), logger_)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001155 if (logger_) logger_->warn("PUPnP: Failed to get GetStatusInfo from {} -> {:d}: {}",
1156 igd.getServiceType().c_str(),
1157 upnp_err,
1158 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001159 return false;
1160 }
1161
1162 // Parse response.
1163 auto status = getFirstDocItem(response.get(), "NewConnectionStatus");
1164 return status == "Connected";
1165}
1166
1167IpAddr
1168PUPnP::actionGetExternalIP(const UPnPIGD& igd)
1169{
1170 if (not clientRegistered_)
1171 return {};
1172
1173 // Action and response pointers.
1174 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1175 action(nullptr, ixmlDocument_free); // Action pointer.
1176 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1177 response(nullptr, ixmlDocument_free); // Response pointer.
1178
1179 // Set action name.
1180 static constexpr const char* action_name {"GetExternalIPAddress"};
1181
1182 IXML_Document* action_container_ptr = nullptr;
1183 action_container_ptr = UpnpMakeAction(action_name, igd.getServiceType().c_str(), 0, nullptr);
1184 action.reset(action_container_ptr);
1185
1186 if (not action) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001187 if (logger_) logger_->warn("PUPnP: Failed to make GetExternalIPAddress action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001188 return {};
1189 }
1190
1191 IXML_Document* response_container_ptr = nullptr;
1192 int upnp_err = UpnpSendAction(ctrlptHandle_,
1193 igd.getControlURL().c_str(),
1194 igd.getServiceType().c_str(),
1195 nullptr,
1196 action.get(),
1197 &response_container_ptr);
1198 response.reset(response_container_ptr);
1199
1200 if (not response or upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001201 if (logger_) logger_->warn("PUPnP: Failed to send GetExternalIPAddress action -> {}",
1202 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001203 return {};
1204 }
1205
Adrien Béraudd78d1ac2023-08-25 10:43:33 -04001206 if (errorOnResponse(response.get(), logger_)) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001207 if (logger_) logger_->warn("PUPnP: Failed to get GetExternalIPAddress from {} -> {:d}: {}",
1208 igd.getServiceType(),
1209 upnp_err,
1210 UpnpGetErrorMessage(upnp_err));
Adrien Béraud612b55b2023-05-29 10:42:04 -04001211 return {};
1212 }
1213
1214 return {getFirstDocItem(response.get(), "NewExternalIPAddress")};
1215}
1216
1217std::map<Mapping::key_t, Mapping>
1218PUPnP::getMappingsListByDescr(const std::shared_ptr<IGD>& igd, const std::string& description) const
1219{
1220 auto upnpIgd = std::dynamic_pointer_cast<UPnPIGD>(igd);
1221 assert(upnpIgd);
1222
1223 std::map<Mapping::key_t, Mapping> mapList;
1224
1225 if (not clientRegistered_ or not upnpIgd->isValid() or not upnpIgd->getLocalIp())
1226 return mapList;
1227
1228 // Set action name.
1229 static constexpr const char* action_name {"GetGenericPortMappingEntry"};
1230
1231 for (int entry_idx = 0;; entry_idx++) {
1232 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1233 action(nullptr, ixmlDocument_free); // Action pointer.
1234 IXML_Document* action_container_ptr = nullptr;
1235
1236 std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
1237 response(nullptr, ixmlDocument_free); // Response pointer.
1238 IXML_Document* response_container_ptr = nullptr;
1239
1240 UpnpAddToAction(&action_container_ptr,
1241 action_name,
1242 upnpIgd->getServiceType().c_str(),
1243 "NewPortMappingIndex",
1244 std::to_string(entry_idx).c_str());
1245 action.reset(action_container_ptr);
1246
1247 if (not action) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001248 // JAMI_WARN("PUPnP: Failed to add NewPortMappingIndex action");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001249 break;
1250 }
1251
1252 int upnp_err = UpnpSendAction(ctrlptHandle_,
1253 upnpIgd->getControlURL().c_str(),
1254 upnpIgd->getServiceType().c_str(),
1255 nullptr,
1256 action.get(),
1257 &response_container_ptr);
1258 response.reset(response_container_ptr);
1259
1260 if (not response) {
1261 // No existing mapping. Abort silently.
1262 break;
1263 }
1264
1265 if (upnp_err != UPNP_E_SUCCESS) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001266 // JAMI_ERR("PUPnP: GetGenericPortMappingEntry returned with error: %i", upnp_err);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001267 break;
1268 }
1269
1270 // Check error code.
1271 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1272 if (not errorCode.empty()) {
1273 auto error = to_int<int>(errorCode);
1274 if (error == ARRAY_IDX_INVALID or error == CONFLICT_IN_MAPPING) {
1275 // No more port mapping entries in the response.
Morteza Namvar5f639522023-07-04 17:08:58 -04001276 // JAMI_DBG("PUPnP: No more mappings (found a total of %i mappings", entry_idx);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001277 break;
1278 } else {
1279 auto errorDescription = getFirstDocItem(response.get(), "errorDescription");
Adrien Béraud370257c2023-08-15 20:53:09 -04001280 if (logger_) logger_->error("PUPnP: GetGenericPortMappingEntry returned with error: {:s}: {:s}",
Adrien Béraud612b55b2023-05-29 10:42:04 -04001281 errorCode,
1282 errorDescription);
1283 break;
1284 }
1285 }
1286
1287 // Parse the response.
1288 auto desc_actual = getFirstDocItem(response.get(), "NewPortMappingDescription");
1289 auto client_ip = getFirstDocItem(response.get(), "NewInternalClient");
1290
1291 if (client_ip != getHostAddress().toString()) {
1292 // Silently ignore un-matching addresses.
1293 continue;
1294 }
1295
1296 if (desc_actual.find(description) == std::string::npos)
1297 continue;
1298
1299 auto port_internal = getFirstDocItem(response.get(), "NewInternalPort");
1300 auto port_external = getFirstDocItem(response.get(), "NewExternalPort");
1301 std::string transport(getFirstDocItem(response.get(), "NewProtocol"));
1302
1303 if (port_internal.empty() || port_external.empty() || transport.empty()) {
Adrien Béraud370257c2023-08-15 20:53:09 -04001304 // if (logger_) logger_->e("PUPnP: GetGenericPortMappingEntry returned an invalid entry at index %i",
Morteza Namvar5f639522023-07-04 17:08:58 -04001305 // entry_idx);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001306 continue;
1307 }
1308
1309 std::transform(transport.begin(), transport.end(), transport.begin(), ::toupper);
1310 PortType type = transport.find("TCP") != std::string::npos ? PortType::TCP : PortType::UDP;
1311 auto ePort = to_int<uint16_t>(port_external);
1312 auto iPort = to_int<uint16_t>(port_internal);
1313
1314 Mapping map(type, ePort, iPort);
1315 map.setIgd(igd);
1316
1317 mapList.emplace(map.getMapKey(), std::move(map));
1318 }
1319
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001320 if (logger_) logger_->debug("PUPnP: Found {:d} allocated mappings on IGD {:s}",
1321 mapList.size(),
1322 upnpIgd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001323
1324 return mapList;
1325}
1326
1327void
1328PUPnP::deleteMappingsByDescription(const std::shared_ptr<IGD>& igd, const std::string& description)
1329{
1330 if (not(clientRegistered_ and igd->getLocalIp()))
1331 return;
1332
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001333 if (logger_) logger_->debug("PUPnP: Remove all mappings (if any) on IGD {} matching descr prefix {}",
1334 igd->toString(),
1335 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001336
Adrien Béraud370257c2023-08-15 20:53:09 -04001337 ioContext->post([w=weak(), igd, description]{
1338 if (auto sthis = w.lock()) {
1339 auto mapList = sthis->getMappingsListByDescr(igd, description);
1340 for (auto const& [_, map] : mapList) {
1341 sthis->requestMappingRemove(map);
1342 }
1343 }
1344 });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001345}
1346
1347bool
1348PUPnP::actionAddPortMapping(const Mapping& mapping)
1349{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001350 if (not clientRegistered_)
1351 return false;
1352
1353 auto igdIn = std::dynamic_pointer_cast<UPnPIGD>(mapping.getIgd());
1354 if (not igdIn)
1355 return false;
1356
1357 // The requested IGD must be present in the list of local valid IGDs.
1358 auto igd = findMatchingIgd(igdIn->getControlURL());
1359
1360 if (not igd or not igd->isValid())
1361 return false;
1362
1363 // Action and response pointers.
1364 XMLDocument action(nullptr, ixmlDocument_free);
1365 IXML_Document* action_container_ptr = nullptr;
1366 XMLDocument response(nullptr, ixmlDocument_free);
1367 IXML_Document* response_container_ptr = nullptr;
1368
1369 // Set action sequence.
1370 UpnpAddToAction(&action_container_ptr,
1371 ACTION_ADD_PORT_MAPPING,
1372 igd->getServiceType().c_str(),
1373 "NewRemoteHost",
1374 "");
1375 UpnpAddToAction(&action_container_ptr,
1376 ACTION_ADD_PORT_MAPPING,
1377 igd->getServiceType().c_str(),
1378 "NewExternalPort",
1379 mapping.getExternalPortStr().c_str());
1380 UpnpAddToAction(&action_container_ptr,
1381 ACTION_ADD_PORT_MAPPING,
1382 igd->getServiceType().c_str(),
1383 "NewProtocol",
1384 mapping.getTypeStr());
1385 UpnpAddToAction(&action_container_ptr,
1386 ACTION_ADD_PORT_MAPPING,
1387 igd->getServiceType().c_str(),
1388 "NewInternalPort",
1389 mapping.getInternalPortStr().c_str());
1390 UpnpAddToAction(&action_container_ptr,
1391 ACTION_ADD_PORT_MAPPING,
1392 igd->getServiceType().c_str(),
1393 "NewInternalClient",
1394 getHostAddress().toString().c_str());
1395 UpnpAddToAction(&action_container_ptr,
1396 ACTION_ADD_PORT_MAPPING,
1397 igd->getServiceType().c_str(),
1398 "NewEnabled",
1399 "1");
1400 UpnpAddToAction(&action_container_ptr,
1401 ACTION_ADD_PORT_MAPPING,
1402 igd->getServiceType().c_str(),
1403 "NewPortMappingDescription",
1404 mapping.toString().c_str());
1405 UpnpAddToAction(&action_container_ptr,
1406 ACTION_ADD_PORT_MAPPING,
1407 igd->getServiceType().c_str(),
1408 "NewLeaseDuration",
1409 "0");
1410
1411 action.reset(action_container_ptr);
1412
1413 int upnp_err = UpnpSendAction(ctrlptHandle_,
1414 igd->getControlURL().c_str(),
1415 igd->getServiceType().c_str(),
1416 nullptr,
1417 action.get(),
1418 &response_container_ptr);
1419 response.reset(response_container_ptr);
1420
1421 bool success = true;
1422
1423 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001424 if (logger_) {
1425 logger_->warn("PUPnP: Failed to send action {} for mapping {}. {:d}: {}",
1426 ACTION_ADD_PORT_MAPPING,
1427 mapping.toString(),
1428 upnp_err,
1429 UpnpGetErrorMessage(upnp_err));
1430 logger_->warn("PUPnP: IGD ctrlUrl {}", igd->getControlURL());
1431 logger_->warn("PUPnP: IGD service type {}", igd->getServiceType());
1432 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001433
1434 success = false;
1435 }
1436
1437 // Check if an error has occurred.
1438 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1439 if (not errorCode.empty()) {
1440 success = false;
1441 // Try to get the error description.
1442 std::string errorDescription;
1443 if (response) {
1444 errorDescription = getFirstDocItem(response.get(), "errorDescription");
1445 }
1446
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001447 if (logger_) logger_->warn("PUPnP: {:s} returned with error: {:s} {:s}",
1448 ACTION_ADD_PORT_MAPPING,
1449 errorCode,
1450 errorDescription);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001451 }
1452 return success;
1453}
1454
1455bool
1456PUPnP::actionDeletePortMapping(const Mapping& mapping)
1457{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001458 if (not clientRegistered_)
1459 return false;
1460
1461 auto igdIn = std::dynamic_pointer_cast<UPnPIGD>(mapping.getIgd());
1462 if (not igdIn)
1463 return false;
1464
1465 // The requested IGD must be present in the list of local valid IGDs.
1466 auto igd = findMatchingIgd(igdIn->getControlURL());
1467
1468 if (not igd or not igd->isValid())
1469 return false;
1470
1471 // Action and response pointers.
1472 XMLDocument action(nullptr, ixmlDocument_free);
1473 IXML_Document* action_container_ptr = nullptr;
1474 XMLDocument response(nullptr, ixmlDocument_free);
1475 IXML_Document* response_container_ptr = nullptr;
1476
1477 // Set action sequence.
1478 UpnpAddToAction(&action_container_ptr,
1479 ACTION_DELETE_PORT_MAPPING,
1480 igd->getServiceType().c_str(),
1481 "NewRemoteHost",
1482 "");
1483 UpnpAddToAction(&action_container_ptr,
1484 ACTION_DELETE_PORT_MAPPING,
1485 igd->getServiceType().c_str(),
1486 "NewExternalPort",
1487 mapping.getExternalPortStr().c_str());
1488 UpnpAddToAction(&action_container_ptr,
1489 ACTION_DELETE_PORT_MAPPING,
1490 igd->getServiceType().c_str(),
1491 "NewProtocol",
1492 mapping.getTypeStr());
1493
1494 action.reset(action_container_ptr);
1495
1496 int upnp_err = UpnpSendAction(ctrlptHandle_,
1497 igd->getControlURL().c_str(),
1498 igd->getServiceType().c_str(),
1499 nullptr,
1500 action.get(),
1501 &response_container_ptr);
1502 response.reset(response_container_ptr);
1503
1504 bool success = true;
1505
1506 if (upnp_err != UPNP_E_SUCCESS) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001507 if (logger_) {
1508 logger_->warn("PUPnP: Failed to send action {} for mapping from {}. {:d}: {}",
1509 ACTION_DELETE_PORT_MAPPING,
1510 mapping.toString(),
1511 upnp_err,
1512 UpnpGetErrorMessage(upnp_err));
1513 logger_->warn("PUPnP: IGD ctrlUrl {}", igd->getControlURL());
1514 logger_->warn("PUPnP: IGD service type {}", igd->getServiceType());
1515 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001516 success = false;
1517 }
1518
1519 if (not response) {
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001520 if (logger_) logger_->warn("PUPnP: Failed to get response for {}", ACTION_DELETE_PORT_MAPPING);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001521 success = false;
1522 }
1523
1524 // Check if there is an error code.
1525 auto errorCode = getFirstDocItem(response.get(), "errorCode");
1526 if (not errorCode.empty()) {
1527 auto errorDescription = getFirstDocItem(response.get(), "errorDescription");
Adrien Béraud4f7e8012023-08-16 15:28:18 -04001528 if (logger_) logger_->warn("PUPnP: {:s} returned with error: {:s}: {:s}",
1529 ACTION_DELETE_PORT_MAPPING,
1530 errorCode,
1531 errorDescription);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001532 success = false;
1533 }
1534
1535 return success;
1536}
1537
1538} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001539} // namespace dhtnet