blob: 7233eafe4c1fa813a6c2536b909cb60dd8b23620 [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 */
Morteza Namvar5f639522023-07-04 17:08:58 -040017#include "upnp/upnp_context.h"
Adrien Béraud25c30c42023-07-05 13:46:54 -040018#include "protocol/upnp_protocol.h"
19
Adrien Béraud370257c2023-08-15 20:53:09 -040020#if HAVE_LIBNATPMP
21#include "protocol/natpmp/nat_pmp.h"
22#endif
23#if HAVE_LIBUPNP
24#include "protocol/pupnp/pupnp.h"
25#endif
26
Morteza Namvar5f639522023-07-04 17:08:58 -040027#include <asio/steady_timer.hpp>
Adrien Béraud9d350962023-07-13 15:36:32 -040028#if __has_include(<fmt/std.h>)
Adrien Béraud25c30c42023-07-05 13:46:54 -040029#include <fmt/std.h>
Adrien Béraud9d350962023-07-13 15:36:32 -040030#else
31#include <fmt/ostream.h>
32#endif
Adrien Béraud612b55b2023-05-29 10:42:04 -040033
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040034namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040035namespace upnp {
36
37constexpr static auto MAP_UPDATE_INTERVAL = std::chrono::seconds(30);
38constexpr static int MAX_REQUEST_RETRIES = 20;
39constexpr static int MAX_REQUEST_REMOVE_COUNT = 5;
40
41constexpr static uint16_t UPNP_TCP_PORT_MIN {10000};
42constexpr static uint16_t UPNP_TCP_PORT_MAX {UPNP_TCP_PORT_MIN + 5000};
43constexpr static uint16_t UPNP_UDP_PORT_MIN {20000};
44constexpr static uint16_t UPNP_UDP_PORT_MAX {UPNP_UDP_PORT_MIN + 5000};
45
Sébastien Blin55abf072023-07-19 10:21:21 -040046UPnPContext::UPnPContext(const std::shared_ptr<asio::io_context>& ioContext, const std::shared_ptr<dht::log::Logger>& logger)
Adrien Béraud370257c2023-08-15 20:53:09 -040047 : mappingListUpdateTimer_(*ioContext), ctx(ioContext), logger_(logger)
Adrien Béraud612b55b2023-05-29 10:42:04 -040048{
Morteza Namvar5f639522023-07-04 17:08:58 -040049 // JAMI_DBG("Creating UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040050
51 // Set port ranges
52 portRange_.emplace(PortType::TCP, std::make_pair(UPNP_TCP_PORT_MIN, UPNP_TCP_PORT_MAX));
53 portRange_.emplace(PortType::UDP, std::make_pair(UPNP_UDP_PORT_MIN, UPNP_UDP_PORT_MAX));
54
Adrien Béraud370257c2023-08-15 20:53:09 -040055 ctx->post([this] { init(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -040056}
57
Adrien Béraud25c30c42023-07-05 13:46:54 -040058/*std::shared_ptr<UPnPContext>
Adrien Béraud612b55b2023-05-29 10:42:04 -040059UPnPContext::getUPnPContext()
60{
61 // This is the unique shared instance (singleton) of UPnPContext class.
62 static auto context = std::make_shared<UPnPContext>();
63 return context;
Adrien Béraud25c30c42023-07-05 13:46:54 -040064}*/
Adrien Béraud612b55b2023-05-29 10:42:04 -040065
66void
67UPnPContext::shutdown(std::condition_variable& cv)
68{
Morteza Namvar5f639522023-07-04 17:08:58 -040069 // JAMI_DBG("Shutdown UPnPContext instance [%p]", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -040070
71 stopUpnp(true);
72
73 for (auto const& [_, proto] : protocolList_) {
74 proto->terminate();
75 }
76
77 {
78 std::lock_guard<std::mutex> lock(mappingMutex_);
79 mappingList_->clear();
Adrien Béraud25c30c42023-07-05 13:46:54 -040080 //if (mappingListUpdateTimer_)
81 // mappingListUpdateTimer_->cancel();
82 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -040083 controllerList_.clear();
84 protocolList_.clear();
85 shutdownComplete_ = true;
86 cv.notify_one();
87 }
88}
89
90void
91UPnPContext::shutdown()
92{
93 std::unique_lock<std::mutex> lk(mappingMutex_);
94 std::condition_variable cv;
95
Adrien Béraud370257c2023-08-15 20:53:09 -040096 ctx->post([&, this] { shutdown(cv); });
97
Morteza Namvar5f639522023-07-04 17:08:58 -040098 // JAMI_DBG("Waiting for shutdown ...");
Adrien Béraud612b55b2023-05-29 10:42:04 -040099
100 if (cv.wait_for(lk, std::chrono::seconds(30), [this] { return shutdownComplete_; })) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400101 // JAMI_DBG("Shutdown completed");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400102 } else {
Morteza Namvar5f639522023-07-04 17:08:58 -0400103 // JAMI_ERR("Shutdown timed-out");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400104 }
105}
106
107UPnPContext::~UPnPContext()
108{
Morteza Namvar5f639522023-07-04 17:08:58 -0400109 // JAMI_DBG("UPnPContext instance [%p] destroyed", this);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400110}
111
112void
113UPnPContext::init()
114{
Adrien Béraud612b55b2023-05-29 10:42:04 -0400115#if HAVE_LIBNATPMP
Adrien Béraud370257c2023-08-15 20:53:09 -0400116 auto natPmp = std::make_shared<NatPmp>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400117 natPmp->setObserver(this);
118 protocolList_.emplace(NatProtocolType::NAT_PMP, std::move(natPmp));
119#endif
120
121#if HAVE_LIBUPNP
Adrien Béraud370257c2023-08-15 20:53:09 -0400122 auto pupnp = std::make_shared<PUPnP>(ctx, logger_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400123 pupnp->setObserver(this);
124 protocolList_.emplace(NatProtocolType::PUPNP, std::move(pupnp));
125#endif
126}
127
128void
129UPnPContext::startUpnp()
130{
131 assert(not controllerList_.empty());
132
Morteza Namvar5f639522023-07-04 17:08:58 -0400133 // JAMI_DBG("Starting UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400134
135 // Request a new IGD search.
136 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400137 ctx->dispatch([p=protocol] { p->searchForIgd(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400138 }
139
140 started_ = true;
141}
142
143void
144UPnPContext::stopUpnp(bool forceRelease)
145{
Adrien Béraud370257c2023-08-15 20:53:09 -0400146 /*if (not isValidThread()) {
147 ctx->post([this, forceRelease] { stopUpnp(forceRelease); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400148 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400149 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400150
Morteza Namvar5f639522023-07-04 17:08:58 -0400151 // JAMI_DBG("Stopping UPNP context");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400152
153 // Clear all current mappings if any.
154
155 // Use a temporary list to avoid processing the mapping
156 // list while holding the lock.
157 std::list<Mapping::sharedPtr_t> toRemoveList;
158 {
159 std::lock_guard<std::mutex> lock(mappingMutex_);
160
161 PortType types[2] {PortType::TCP, PortType::UDP};
162 for (auto& type : types) {
163 auto& mappingList = getMappingList(type);
164 for (auto const& [_, map] : mappingList) {
165 toRemoveList.emplace_back(map);
166 }
167 }
168 // Invalidate the current IGDs.
169 preferredIgd_.reset();
170 validIgdList_.clear();
171 }
172 for (auto const& map : toRemoveList) {
173 requestRemoveMapping(map);
174
Adrien Béraud370257c2023-08-15 20:53:09 -0400175 // Notify is not needed in updateState when
Adrien Béraud612b55b2023-05-29 10:42:04 -0400176 // shutting down (hence set it to false). NotifyCallback
177 // would trigger a new SIP registration and create a
178 // false registered state upon program close.
179 // It's handled by upper layers.
Adrien Béraud370257c2023-08-15 20:53:09 -0400180 map->updateState(MappingState::FAILED, false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400181 // We dont remove mappings with auto-update enabled,
182 // unless forceRelease is true.
183 if (not map->getAutoUpdate() or forceRelease) {
184 map->enableAutoUpdate(false);
185 unregisterMapping(map);
186 }
187 }
188
189 // Clear all current IGDs.
190 for (auto const& [_, protocol] : protocolList_) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400191 ctx->dispatch([p=protocol]{ p->clearIgds(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400192 }
193
194 started_ = false;
195}
196
197uint16_t
198UPnPContext::generateRandomPort(PortType type, bool mustBeEven)
199{
200 auto minPort = type == PortType::TCP ? UPNP_TCP_PORT_MIN : UPNP_UDP_PORT_MIN;
201 auto maxPort = type == PortType::TCP ? UPNP_TCP_PORT_MAX : UPNP_UDP_PORT_MAX;
202
203 if (minPort >= maxPort) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400204 // JAMI_ERR("Max port number (%i) must be greater than min port number (%i)", maxPort, minPort);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400205 // Must be called with valid range.
206 assert(false);
207 }
208
209 int fact = mustBeEven ? 2 : 1;
210 if (mustBeEven) {
211 minPort /= fact;
212 maxPort /= fact;
213 }
214
215 // Seed the generator.
216 static std::mt19937 gen(dht::crypto::getSeededRandomEngine());
217 // Define the range.
218 std::uniform_int_distribution<uint16_t> dist(minPort, maxPort);
219 return dist(gen) * fact;
220}
221
222void
223UPnPContext::connectivityChanged()
224{
Adrien Béraud370257c2023-08-15 20:53:09 -0400225 /*if (not isValidThread()) {
226 ctx->post([this] { connectivityChanged(); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400227 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400228 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400229
230 auto hostAddr = ip_utils::getLocalAddr(AF_INET);
231
Morteza Namvar5f639522023-07-04 17:08:58 -0400232 // JAMI_DBG("Connectivity change check: host address %s", hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400233
234 auto restartUpnp = false;
235
236 // On reception of "connectivity change" notification, the UPNP search
237 // will be restarted if either there is no valid IGD, or the IGD address
238 // changed.
239
240 if (not isReady()) {
241 restartUpnp = true;
242 } else {
243 // Check if the host address changed.
244 for (auto const& [_, protocol] : protocolList_) {
245 if (protocol->isReady() and hostAddr != protocol->getHostAddress()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400246 // JAMI_WARN("Host address changed from %s to %s",
247 // protocol->getHostAddress().toString().c_str(),
248 // hostAddr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400249 protocol->clearIgds();
250 restartUpnp = true;
251 break;
252 }
253 }
254 }
255
256 // We have at least one valid IGD and the host address did
257 // not change, so no need to restart.
258 if (not restartUpnp) {
259 return;
260 }
261
262 // No registered controller. A new search will be performed when
263 // a controller is registered.
264 if (controllerList_.empty())
265 return;
266
Morteza Namvar5f639522023-07-04 17:08:58 -0400267 // JAMI_DBG("Connectivity changed. Clear the IGDs and restart");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400268
269 stopUpnp();
270 startUpnp();
271
272 // Mapping with auto update enabled must be processed first.
273 processMappingWithAutoUpdate();
274}
275
276void
277UPnPContext::setPublicAddress(const IpAddr& addr)
278{
279 if (not addr)
280 return;
281
282 std::lock_guard<std::mutex> lock(mappingMutex_);
283 if (knownPublicAddress_ != addr) {
284 knownPublicAddress_ = std::move(addr);
Morteza Namvar5f639522023-07-04 17:08:58 -0400285 // JAMI_DBG("Setting the known public address to %s", addr.toString().c_str());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400286 }
287}
288
289bool
290UPnPContext::isReady() const
291{
292 std::lock_guard<std::mutex> lock(mappingMutex_);
293 return not validIgdList_.empty();
294}
295
296IpAddr
297UPnPContext::getExternalIP() const
298{
299 std::lock_guard<std::mutex> lock(mappingMutex_);
300 // Return the first IGD Ip available.
301 if (not validIgdList_.empty()) {
302 return (*validIgdList_.begin())->getPublicIp();
303 }
304 return {};
305}
306
307Mapping::sharedPtr_t
308UPnPContext::reserveMapping(Mapping& requestedMap)
309{
310 auto desiredPort = requestedMap.getExternalPort();
311
312 if (desiredPort == 0) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400313 if (logger_) logger_->debug("Desired port is not set, will provide the first available port for [{}]",
314 requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400315 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400316 if (logger_) logger_->debug("Try to find mapping for port {:d} [{}]", desiredPort, requestedMap.getTypeStr());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400317 }
318
319 Mapping::sharedPtr_t mapRes;
320
321 {
322 std::lock_guard<std::mutex> lock(mappingMutex_);
323 auto& mappingList = getMappingList(requestedMap.getType());
324
325 // We try to provide a mapping in "OPEN" state. If not found,
326 // we provide any available mapping. In this case, it's up to
327 // the caller to use it or not.
328 for (auto const& [_, map] : mappingList) {
329 // If the desired port is null, we pick the first available port.
330 if (map->isValid() and (desiredPort == 0 or map->getExternalPort() == desiredPort)
331 and map->isAvailable()) {
332 // Considere the first available mapping regardless of its
333 // state. A mapping with OPEN state will be used if found.
334 if (not mapRes)
335 mapRes = map;
336
337 if (map->getState() == MappingState::OPEN) {
338 // Found an "OPEN" mapping. We are done.
339 mapRes = map;
340 break;
341 }
342 }
343 }
344 }
345
346 // Create a mapping if none was available.
347 if (not mapRes) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400348 // JAMI_WARN("Did not find any available mapping. Will request one now");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400349 mapRes = registerMapping(requestedMap);
350 }
351
352 if (mapRes) {
353 // Make the mapping unavailable
354 mapRes->setAvailable(false);
355 // Copy attributes.
356 mapRes->setNotifyCallback(requestedMap.getNotifyCallback());
357 mapRes->enableAutoUpdate(requestedMap.getAutoUpdate());
358 // Notify the listener.
359 if (auto cb = mapRes->getNotifyCallback())
360 cb(mapRes);
361 }
362
363 updateMappingList(true);
364
365 return mapRes;
366}
367
368void
369UPnPContext::releaseMapping(const Mapping& map)
370{
Adrien Béraud370257c2023-08-15 20:53:09 -0400371 /*if (not isValidThread()) {
372 ctx->post([this, map] { releaseMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400373 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400374 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400375
376 auto mapPtr = getMappingWithKey(map.getMapKey());
377
378 if (not mapPtr) {
379 // Might happen if the mapping failed or was never granted.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400380 if (logger_) logger_->debug("Mapping {} does not exist or was already removed", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400381 return;
382 }
383
384 if (mapPtr->isAvailable()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400385 if (logger_) logger_->warn("Trying to release an unused mapping {}", mapPtr->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400386 return;
387 }
388
389 // Remove it.
390 requestRemoveMapping(mapPtr);
391 unregisterMapping(mapPtr);
392}
393
394void
395UPnPContext::registerController(void* controller)
396{
397 {
398 std::lock_guard<std::mutex> lock(mappingMutex_);
399 if (shutdownComplete_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400400 if (logger_) logger_->warn("UPnPContext already shut down");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400401 return;
402 }
403 }
404
Adrien Béraud370257c2023-08-15 20:53:09 -0400405 /*if (not isValidThread()) {
406 ctx->post([this, controller] { registerController(controller); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400407 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400408 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400409
410 auto ret = controllerList_.emplace(controller);
411 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400412 if (logger_) logger_->warn("Controller {} is already registered", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400413 return;
414 }
415
Adrien Berauda8731ac2023-08-17 12:19:39 -0400416 if (logger_) logger_->debug("Successfully registered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400417 if (not started_)
418 startUpnp();
419}
420
421void
422UPnPContext::unregisterController(void* controller)
423{
Adrien Béraud370257c2023-08-15 20:53:09 -0400424 /*if (not isValidThread()) {
425 ctx->post([this, controller] { unregisterController(controller); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400426 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400427 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400428
429 if (controllerList_.erase(controller) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400430 if (logger_) logger_->debug("Successfully unregistered controller {}", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400431 } else {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400432 if (logger_) logger_->debug("Controller {} was already removed", fmt::ptr(controller));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400433 }
434
435 if (controllerList_.empty()) {
436 stopUpnp();
437 }
438}
439
440uint16_t
441UPnPContext::getAvailablePortNumber(PortType type)
442{
443 // Only return an availalable random port. No actual
444 // reservation is made here.
445
446 std::lock_guard<std::mutex> lock(mappingMutex_);
447 auto& mappingList = getMappingList(type);
448 int tryCount = 0;
449 while (tryCount++ < MAX_REQUEST_RETRIES) {
450 uint16_t port = generateRandomPort(type);
451 Mapping map(type, port, port);
452 if (mappingList.find(map.getMapKey()) == mappingList.end())
453 return port;
454 }
455
456 // Very unlikely to get here.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400457 if (logger_) logger_->error("Could not find an available port after %i trials", MAX_REQUEST_RETRIES);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400458 return 0;
459}
460
461void
462UPnPContext::requestMapping(const Mapping::sharedPtr_t& map)
463{
464 assert(map);
465
Adrien Béraud370257c2023-08-15 20:53:09 -0400466 /*if (not isValidThread()) {
467 ctx->post([this, map] { requestMapping(map); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400468 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400469 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400470
471 auto const& igd = getPreferredIgd();
472 // We must have at least a valid IGD pointer if we get here.
473 // Not this method is called only if there were a valid IGD, however,
474 // because the processing is asynchronous, it's possible that the IGD
475 // was invalidated when the this code executed.
476 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400477 if (logger_) logger_->debug("No valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400478 return;
479 }
480
481 map->setIgd(igd);
482
Adrien Berauda8731ac2023-08-17 12:19:39 -0400483 if (logger_) logger_->debug("Request mapping {} using protocol [{}] IGD [{}]",
484 map->toString(),
485 igd->getProtocolName(),
486 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400487
Adrien Béraud370257c2023-08-15 20:53:09 -0400488 map->updateState(MappingState::IN_PROGRESS);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400489
490 auto const& protocol = protocolList_.at(igd->getProtocol());
491 protocol->requestMappingAdd(*map);
492}
493
494bool
495UPnPContext::provisionNewMappings(PortType type, int portCount)
496{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400497 if (logger_) logger_->debug("Provision {:d} new mappings of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400498
499 assert(portCount > 0);
500
501 while (portCount > 0) {
502 auto port = getAvailablePortNumber(type);
503 if (port > 0) {
504 // Found an available port number
505 portCount--;
506 Mapping map(type, port, port, true);
507 registerMapping(map);
508 } else {
509 // Very unlikely to get here!
Adrien Berauda8731ac2023-08-17 12:19:39 -0400510 if (logger_) logger_->error("Can not find any available port to provision!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400511 return false;
512 }
513 }
514
515 return true;
516}
517
518bool
519UPnPContext::deleteUnneededMappings(PortType type, int portCount)
520{
Adrien Berauda8731ac2023-08-17 12:19:39 -0400521 if (logger_) logger_->debug("Remove {:d} unneeded mapping of type [{}]", portCount, Mapping::getTypeStr(type));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400522
523 assert(portCount > 0);
524
Adrien Béraud370257c2023-08-15 20:53:09 -0400525 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400526
527 std::lock_guard<std::mutex> lock(mappingMutex_);
528 auto& mappingList = getMappingList(type);
529
530 for (auto it = mappingList.begin(); it != mappingList.end();) {
531 auto map = it->second;
532 assert(map);
533
534 if (not map->isAvailable()) {
535 it++;
536 continue;
537 }
538
539 if (map->getState() == MappingState::OPEN and portCount > 0) {
540 // Close portCount mappings in "OPEN" state.
541 requestRemoveMapping(map);
Adrien Béraud370257c2023-08-15 20:53:09 -0400542 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400543 portCount--;
544 } else if (map->getState() != MappingState::OPEN) {
545 // If this methods is called, it means there are more open
546 // mappings than required. So, all mappings in a state other
547 // than "OPEN" state (typically in in-progress state) will
548 // be deleted as well.
Adrien Béraud370257c2023-08-15 20:53:09 -0400549 it = mappingList.erase(it);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400550 } else {
551 it++;
552 }
553 }
554
555 return true;
556}
557
558void
559UPnPContext::updatePreferredIgd()
560{
Adrien Béraud370257c2023-08-15 20:53:09 -0400561 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400562
563 if (preferredIgd_ and preferredIgd_->isValid())
564 return;
565
566 // Reset and search for the best IGD.
567 preferredIgd_.reset();
568
569 for (auto const& [_, protocol] : protocolList_) {
570 if (protocol->isReady()) {
571 auto igdList = protocol->getIgdList();
572 assert(not igdList.empty());
573 auto const& igd = igdList.front();
574 if (not igd->isValid())
575 continue;
576
577 // Prefer NAT-PMP over PUPNP.
578 if (preferredIgd_ and igd->getProtocol() != NatProtocolType::NAT_PMP)
579 continue;
580
581 // Update.
582 preferredIgd_ = igd;
583 }
584 }
585
586 if (preferredIgd_ and preferredIgd_->isValid()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400587 if (logger_) logger_->debug("Preferred IGD updated to [{}] IGD [{} {}] ",
588 preferredIgd_->getProtocolName(),
589 preferredIgd_->getUID(),
590 preferredIgd_->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400591 }
592}
593
594std::shared_ptr<IGD>
595UPnPContext::getPreferredIgd() const
596{
Adrien Béraud370257c2023-08-15 20:53:09 -0400597 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400598
599 return preferredIgd_;
600}
601
602void
603UPnPContext::updateMappingList(bool async)
604{
605 // Run async if requested.
606 if (async) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400607 ctx->post([this] { updateMappingList(false); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400608 return;
609 }
610
Adrien Béraud370257c2023-08-15 20:53:09 -0400611 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400612
613 // Update the preferred IGD.
614 updatePreferredIgd();
615
Adrien Béraud25c30c42023-07-05 13:46:54 -0400616 mappingListUpdateTimer_.cancel();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400617
618 // Skip if no controller registered.
619 if (controllerList_.empty())
620 return;
621
622 // Cancel the current timer (if any) and re-schedule.
623 std::shared_ptr<IGD> prefIgd = getPreferredIgd();
624 if (not prefIgd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400625 if (logger_) logger_->debug("UPNP/NAT-PMP enabled, but no valid IGDs available");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400626 // No valid IGD. Nothing to do.
627 return;
628 }
629
Adrien Berauda8731ac2023-08-17 12:19:39 -0400630 mappingListUpdateTimer_.expires_after(MAP_UPDATE_INTERVAL);
Adrien Béraud25c30c42023-07-05 13:46:54 -0400631 mappingListUpdateTimer_.async_wait([this](asio::error_code const& ec) {
632 if (ec != asio::error::operation_aborted)
633 updateMappingList(false);
634 });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400635
636 // Process pending requests if any.
637 processPendingRequests(prefIgd);
638
639 // Make new requests for mappings that failed and have
640 // the auto-update option enabled.
641 processMappingWithAutoUpdate();
642
643 PortType typeArray[2] = {PortType::TCP, PortType::UDP};
644
645 for (auto idx : {0, 1}) {
646 auto type = typeArray[idx];
647
648 MappingStatus status;
649 getMappingStatus(type, status);
650
Adrien Berauda8731ac2023-08-17 12:19:39 -0400651 if (logger_) logger_->debug("Mapping status [{}] - overall {:d}: {:d} open ({:d} ready + {:d} in use), {:d} pending, {:d} "
652 "in-progress, {:d} failed",
653 Mapping::getTypeStr(type),
654 status.sum(),
655 status.openCount_,
656 status.readyCount_,
657 status.openCount_ - status.readyCount_,
658 status.pendingCount_,
659 status.inProgressCount_,
660 status.failedCount_);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400661
662 if (status.failedCount_ > 0) {
663 std::lock_guard<std::mutex> lock(mappingMutex_);
664 auto const& mappingList = getMappingList(type);
665 for (auto const& [_, map] : mappingList) {
666 if (map->getState() == MappingState::FAILED) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400667 if (logger_) logger_->debug("Mapping status [{}] - Available [{}]",
668 map->toString(true),
669 map->isAvailable() ? "YES" : "NO");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400670 }
671 }
672 }
673
674 int toRequestCount = (int) minOpenPortLimit_[idx]
675 - (int) (status.readyCount_ + status.inProgressCount_
676 + status.pendingCount_);
677
678 // Provision/release mappings accordingly.
679 if (toRequestCount > 0) {
680 // Take into account the request in-progress when making
681 // requests for new mappings.
682 provisionNewMappings(type, toRequestCount);
683 } else if (status.readyCount_ > maxOpenPortLimit_[idx]) {
684 deleteUnneededMappings(type, status.readyCount_ - maxOpenPortLimit_[idx]);
685 }
686 }
687
688 // Prune the mapping list if needed
689 if (protocolList_.at(NatProtocolType::PUPNP)->isReady()) {
690#if HAVE_LIBNATPMP
691 // Dont perform if NAT-PMP is valid.
692 if (not protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
693#endif
694 {
695 pruneMappingList();
696 }
697 }
698
699#if HAVE_LIBNATPMP
700 // Renew nat-pmp allocations
701 if (protocolList_.at(NatProtocolType::NAT_PMP)->isReady())
702 renewAllocations();
703#endif
704}
705
706void
707UPnPContext::pruneMappingList()
708{
Adrien Béraud370257c2023-08-15 20:53:09 -0400709 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400710
711 MappingStatus status;
712 getMappingStatus(status);
713
714 // Do not prune the list if there are pending/in-progress requests.
715 if (status.inProgressCount_ != 0 or status.pendingCount_ != 0) {
716 return;
717 }
718
719 auto const& igd = getPreferredIgd();
720 if (not igd or igd->getProtocol() != NatProtocolType::PUPNP) {
721 return;
722 }
723 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
724
725 auto remoteMapList = protocol->getMappingsListByDescr(igd,
726 Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
727 if (remoteMapList.empty()) {
728 std::lock_guard<std::mutex> lock(mappingMutex_);
729 if (not getMappingList(PortType::TCP).empty() or getMappingList(PortType::TCP).empty()) {
Morteza Namvar5f639522023-07-04 17:08:58 -0400730 // JAMI_WARN("We have provisionned mappings but the PUPNP IGD returned an empty list!");
Adrien Béraud612b55b2023-05-29 10:42:04 -0400731 }
732 }
733
734 pruneUnMatchedMappings(igd, remoteMapList);
735 pruneUnTrackedMappings(igd, remoteMapList);
736}
737
738void
739UPnPContext::pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd,
740 const std::map<Mapping::key_t, Mapping>& remoteMapList)
741{
742 // Check/synchronize local mapping list with the list
743 // returned by the IGD.
744
745 PortType types[2] {PortType::TCP, PortType::UDP};
746
747 for (auto& type : types) {
748 // Use a temporary list to avoid processing mappings while holding the lock.
749 std::list<Mapping::sharedPtr_t> toRemoveList;
750 {
751 std::lock_guard<std::mutex> lock(mappingMutex_);
752 auto& mappingList = getMappingList(type);
753 for (auto const& [_, map] : mappingList) {
754 // Only check mappings allocated by UPNP protocol.
755 if (map->getProtocol() != NatProtocolType::PUPNP) {
756 continue;
757 }
758 // Set mapping as failed if not found in the list
759 // returned by the IGD.
760 if (map->getState() == MappingState::OPEN
761 and remoteMapList.find(map->getMapKey()) == remoteMapList.end()) {
762 toRemoveList.emplace_back(map);
763
Adrien Berauda8731ac2023-08-17 12:19:39 -0400764 if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
765 "remote list. Mark as failed!",
766 map->toString(),
767 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400768 }
769 }
770 }
771
772 for (auto const& map : toRemoveList) {
Adrien Béraud370257c2023-08-15 20:53:09 -0400773 map->updateState(MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400774 unregisterMapping(map);
775 }
776 }
777}
778
779void
780UPnPContext::pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd,
781 const std::map<Mapping::key_t, Mapping>& remoteMapList)
782{
783 // Use a temporary list to avoid processing mappings while holding the lock.
784 std::list<Mapping> toRemoveList;
785 {
786 std::lock_guard<std::mutex> lock(mappingMutex_);
787
788 for (auto const& [_, map] : remoteMapList) {
789 // Must has valid IGD pointer and use UPNP protocol.
790 assert(map.getIgd());
791 assert(map.getIgd()->getProtocol() == NatProtocolType::PUPNP);
792 auto& mappingList = getMappingList(map.getType());
793 auto it = mappingList.find(map.getMapKey());
794 if (it == mappingList.end()) {
795 // Not present, request mapping remove.
796 toRemoveList.emplace_back(std::move(map));
797 // Make only few remove requests at once.
798 if (toRemoveList.size() >= MAX_REQUEST_REMOVE_COUNT)
799 break;
800 }
801 }
802 }
803
804 // Remove un-tracked mappings.
805 auto protocol = protocolList_.at(NatProtocolType::PUPNP);
806 for (auto const& map : toRemoveList) {
807 protocol->requestMappingRemove(map);
808 }
809}
810
811void
812UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
813{
Adrien Béraud370257c2023-08-15 20:53:09 -0400814 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400815
816 // Use temporary list to avoid holding the lock while
817 // processing the mapping list.
818 std::list<Mapping::sharedPtr_t> toRemoveList;
819 {
820 std::lock_guard<std::mutex> lock(mappingMutex_);
821
822 PortType types[2] {PortType::TCP, PortType::UDP};
823 for (auto& type : types) {
824 auto& mappingList = getMappingList(type);
825 for (auto const& [_, map] : mappingList) {
826 if (map->getIgd() == igd)
827 toRemoveList.emplace_back(map);
828 }
829 }
830 }
831
832 for (auto const& map : toRemoveList) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400833 if (logger_) logger_->debug("Remove mapping {} (has an invalid IGD {} [{}])",
834 map->toString(),
835 igd->toString(),
836 igd->getProtocolName());
Adrien Béraud370257c2023-08-15 20:53:09 -0400837 map->updateState(MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400838 unregisterMapping(map);
839 }
840}
841
842void
843UPnPContext::processPendingRequests(const std::shared_ptr<IGD>& igd)
844{
845 // This list holds the mappings to be requested. This is
846 // needed to avoid performing the requests while holding
847 // the lock.
848 std::list<Mapping::sharedPtr_t> requestsList;
849
850 // Populate the list of requests to perform.
851 {
852 std::lock_guard<std::mutex> lock(mappingMutex_);
853 PortType typeArray[2] {PortType::TCP, PortType::UDP};
854
855 for (auto type : typeArray) {
856 auto& mappingList = getMappingList(type);
857 for (auto& [_, map] : mappingList) {
858 if (map->getState() == MappingState::PENDING) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400859 if (logger_) logger_->debug("Send pending request for mapping {} to IGD {}",
860 map->toString(),
861 igd->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400862 requestsList.emplace_back(map);
863 }
864 }
865 }
866 }
867
868 // Process the pending requests.
869 for (auto const& map : requestsList) {
870 requestMapping(map);
871 }
872}
873
874void
875UPnPContext::processMappingWithAutoUpdate()
876{
877 // This list holds the mappings to be requested. This is
878 // needed to avoid performing the requests while holding
879 // the lock.
880 std::list<Mapping::sharedPtr_t> requestsList;
881
882 // Populate the list of requests for mappings with auto-update enabled.
883 {
884 std::lock_guard<std::mutex> lock(mappingMutex_);
885 PortType typeArray[2] {PortType::TCP, PortType::UDP};
886
887 for (auto type : typeArray) {
888 auto& mappingList = getMappingList(type);
889 for (auto const& [_, map] : mappingList) {
890 if (map->getState() == MappingState::FAILED and map->getAutoUpdate()) {
891 requestsList.emplace_back(map);
892 }
893 }
894 }
895 }
896
897 for (auto const& oldMap : requestsList) {
898 // Request a new mapping if auto-update is enabled.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400899 if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
900 oldMap->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400901
902 // Reserve a new mapping.
903 Mapping newMapping(oldMap->getType());
904 newMapping.enableAutoUpdate(true);
905 newMapping.setNotifyCallback(oldMap->getNotifyCallback());
906
907 auto const& mapPtr = reserveMapping(newMapping);
908 assert(mapPtr);
909
910 // Release the old one.
911 oldMap->setAvailable(true);
912 oldMap->enableAutoUpdate(false);
913 oldMap->setNotifyCallback(nullptr);
914 unregisterMapping(oldMap);
915 }
916}
917
918void
919UPnPContext::onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event)
920{
921 assert(igd);
922
Adrien Béraud370257c2023-08-15 20:53:09 -0400923 /*if (not isValidThread()) {
924 ctx->post([this, igd, event] { onIgdUpdated(igd, event); });
Adrien Béraud612b55b2023-05-29 10:42:04 -0400925 return;
Adrien Béraud370257c2023-08-15 20:53:09 -0400926 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -0400927
928 // Reset to start search for a new best IGD.
929 preferredIgd_.reset();
930
931 char const* IgdState = event == UpnpIgdEvent::ADDED ? "ADDED"
932 : event == UpnpIgdEvent::REMOVED ? "REMOVED"
933 : "INVALID";
934
935 auto const& igdLocalAddr = igd->getLocalIp();
936 auto protocolName = igd->getProtocolName();
937
Adrien Berauda8731ac2023-08-17 12:19:39 -0400938 if (logger_) logger_->debug("New event for IGD [{} {}] [{}]: [{}]",
939 igd->getUID(),
940 igd->toString(),
941 protocolName,
942 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400943
944 // Check if the IGD has valid addresses.
945 if (not igdLocalAddr) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400946 if (logger_) logger_->warn("[{}] IGD has an invalid local address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400947 return;
948 }
949
950 if (not igd->getPublicIp()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400951 if (logger_) logger_->warn("[{}] IGD has an invalid public address", protocolName);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400952 return;
953 }
954
955 if (knownPublicAddress_ and igd->getPublicIp() != knownPublicAddress_) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400956 if (logger_) logger_->warn("[{}] IGD external address [{}] does not match known public address [{}]."
957 " The mapped addresses might not be reachable",
958 protocolName,
959 igd->getPublicIp().toString(),
960 knownPublicAddress_.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -0400961 }
962
963 // The IGD was removed or is invalid.
964 if (event == UpnpIgdEvent::REMOVED or event == UpnpIgdEvent::INVALID_STATE) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400965 if (logger_) logger_->warn("State of IGD [{} {}] [{}] changed to [{}]. Pruning the mapping list",
966 igd->getUID(),
967 igd->toString(),
968 protocolName,
969 IgdState);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400970
971 pruneMappingsWithInvalidIgds(igd);
972
973 std::lock_guard<std::mutex> lock(mappingMutex_);
974 validIgdList_.erase(igd);
975 return;
976 }
977
978 // Update the IGD list.
979 {
980 std::lock_guard<std::mutex> lock(mappingMutex_);
981 auto ret = validIgdList_.emplace(igd);
982 if (ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -0400983 if (logger_) logger_->debug("IGD [{}] on address {} was added. Will process any pending requests",
984 protocolName,
985 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400986 } else {
987 // Already in the list.
Adrien Berauda8731ac2023-08-17 12:19:39 -0400988 if (logger_) logger_->error("IGD [{}] on address {} already in the list",
989 protocolName,
990 igdLocalAddr.toString(true, true));
Adrien Béraud612b55b2023-05-29 10:42:04 -0400991 return;
992 }
993 }
994
995 // Update the provisionned mappings.
996 updateMappingList(false);
997}
998
999void
1000UPnPContext::onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1001{
Adrien Béraud370257c2023-08-15 20:53:09 -04001002 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001003
1004 // Check if we have a pending request for this response.
1005 auto map = getMappingWithKey(mapRes.getMapKey());
1006 if (not map) {
1007 // We may receive a response for a canceled request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001008 if (logger_) logger_->debug("Response for mapping {} [IGD {}] [{}] does not have a local match",
1009 mapRes.toString(),
1010 igd->toString(),
1011 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001012 return;
1013 }
1014
1015 // The mapping request is new and successful. Update.
1016 map->setIgd(igd);
1017 map->setInternalAddress(mapRes.getInternalAddress());
1018 map->setExternalPort(mapRes.getExternalPort());
1019
1020 // Update the state and report to the owner.
Adrien Béraud370257c2023-08-15 20:53:09 -04001021 map->updateState(MappingState::OPEN);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001022
Adrien Berauda8731ac2023-08-17 12:19:39 -04001023 if (logger_) logger_->debug("Mapping {} (on IGD {} [{}]) successfully performed",
1024 map->toString(),
1025 igd->toString(),
1026 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001027
1028 // Call setValid() to reset the errors counter. We need
1029 // to reset the counter on each successful response.
1030 igd->setValid(true);
1031}
1032
1033#if HAVE_LIBNATPMP
1034void
1035UPnPContext::onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map)
1036{
1037 auto mapPtr = getMappingWithKey(map.getMapKey());
1038
1039 if (not mapPtr) {
1040 // We may receive a notification for a canceled request. Ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001041 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] does not have a match in local list",
1042 map.toString(),
1043 igd->toString(),
1044 map.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001045 return;
1046 }
1047 if (mapPtr->getProtocol() != NatProtocolType::NAT_PMP or not mapPtr->isValid()
1048 or mapPtr->getState() != MappingState::OPEN) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001049 if (logger_) logger_->warn("Renewed mapping {} from IGD {} [{}] is in unexpected state",
1050 mapPtr->toString(),
1051 igd->toString(),
1052 mapPtr->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001053 return;
1054 }
1055
1056 mapPtr->setRenewalTime(map.getRenewalTime());
1057}
1058#endif
1059
1060void
1061UPnPContext::requestRemoveMapping(const Mapping::sharedPtr_t& map)
1062{
Adrien Béraud370257c2023-08-15 20:53:09 -04001063 if (not map or not map->isValid()) {
Adrien Béraud612b55b2023-05-29 10:42:04 -04001064 // Silently ignore if the mapping is invalid
1065 return;
1066 }
Adrien Béraud612b55b2023-05-29 10:42:04 -04001067 auto protocol = protocolList_.at(map->getIgd()->getProtocol());
1068 protocol->requestMappingRemove(*map);
1069}
1070
1071void
1072UPnPContext::deleteAllMappings(PortType type)
1073{
Adrien Béraud370257c2023-08-15 20:53:09 -04001074 /*if (not isValidThread()) {
1075 ctx->post([this, type] { deleteAllMappings(type); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001076 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001077 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001078
1079 std::lock_guard<std::mutex> lock(mappingMutex_);
1080 auto& mappingList = getMappingList(type);
1081
1082 for (auto const& [_, map] : mappingList) {
1083 requestRemoveMapping(map);
1084 }
1085}
1086
1087void
1088UPnPContext::onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& mapRes)
1089{
1090 if (not mapRes.isValid())
1091 return;
1092
Adrien Béraud370257c2023-08-15 20:53:09 -04001093 /*if (not isValidThread()) {
1094 ctx->post([this, igd, mapRes] { onMappingRemoved(igd, mapRes); });
Adrien Béraud612b55b2023-05-29 10:42:04 -04001095 return;
Adrien Béraud370257c2023-08-15 20:53:09 -04001096 }*/
Adrien Béraud612b55b2023-05-29 10:42:04 -04001097
1098 auto map = getMappingWithKey(mapRes.getMapKey());
1099 // Notify the listener.
1100 if (map and map->getNotifyCallback())
1101 map->getNotifyCallback()(map);
1102}
1103
1104Mapping::sharedPtr_t
1105UPnPContext::registerMapping(Mapping& map)
1106{
1107 if (map.getExternalPort() == 0) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001108 // JAMI_DBG("Port number not set. Will set a random port number");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001109 auto port = getAvailablePortNumber(map.getType());
1110 map.setExternalPort(port);
1111 map.setInternalPort(port);
1112 }
1113
1114 // Newly added mapping must be in pending state by default.
1115 map.setState(MappingState::PENDING);
1116
1117 Mapping::sharedPtr_t mapPtr;
1118
1119 {
1120 std::lock_guard<std::mutex> lock(mappingMutex_);
1121 auto& mappingList = getMappingList(map.getType());
1122
1123 auto ret = mappingList.emplace(map.getMapKey(), std::make_shared<Mapping>(map));
1124 if (not ret.second) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001125 if (logger_) logger_->warn("Mapping request for {} already added!", map.toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001126 return {};
1127 }
1128 mapPtr = ret.first->second;
1129 assert(mapPtr);
1130 }
1131
1132 // No available IGD. The pending mapping requests will be processed
1133 // when a IGD becomes available (in onIgdAdded() method).
1134 if (not isReady()) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001135 if (logger_) logger_->warn("No IGD available. Mapping will be requested when an IGD becomes available");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001136 } else {
1137 requestMapping(mapPtr);
1138 }
1139
1140 return mapPtr;
1141}
1142
Adrien Béraud612b55b2023-05-29 10:42:04 -04001143void
1144UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
1145{
Adrien Béraud370257c2023-08-15 20:53:09 -04001146 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001147
1148 if (not map) {
Morteza Namvar5f639522023-07-04 17:08:58 -04001149 // JAMI_ERR("Mapping pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001150 return;
1151 }
1152
1153 if (map->getAutoUpdate()) {
1154 // Dont unregister mappings with auto-update enabled.
1155 return;
1156 }
1157 auto& mappingList = getMappingList(map->getType());
1158
1159 if (mappingList.erase(map->getMapKey()) == 1) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001160 if (logger_) logger_->debug("Unregistered mapping {}", map->toString());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001161 } else {
1162 // The mapping may already be un-registered. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001163 if (logger_) logger_->debug("Mapping {} [{}] does not have a local match",
1164 map->toString(),
1165 map->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001166 }
1167}
1168
1169std::map<Mapping::key_t, Mapping::sharedPtr_t>&
1170UPnPContext::getMappingList(PortType type)
1171{
1172 unsigned typeIdx = type == PortType::TCP ? 0 : 1;
1173 return mappingList_[typeIdx];
1174}
1175
1176Mapping::sharedPtr_t
1177UPnPContext::getMappingWithKey(Mapping::key_t key)
1178{
1179 std::lock_guard<std::mutex> lock(mappingMutex_);
1180 auto const& mappingList = getMappingList(Mapping::getTypeFromMapKey(key));
1181 auto it = mappingList.find(key);
1182 if (it == mappingList.end())
1183 return nullptr;
1184 return it->second;
1185}
1186
1187void
1188UPnPContext::getMappingStatus(PortType type, MappingStatus& status)
1189{
1190 std::lock_guard<std::mutex> lock(mappingMutex_);
1191 auto& mappingList = getMappingList(type);
1192
1193 for (auto const& [_, map] : mappingList) {
1194 switch (map->getState()) {
1195 case MappingState::PENDING: {
1196 status.pendingCount_++;
1197 break;
1198 }
1199 case MappingState::IN_PROGRESS: {
1200 status.inProgressCount_++;
1201 break;
1202 }
1203 case MappingState::FAILED: {
1204 status.failedCount_++;
1205 break;
1206 }
1207 case MappingState::OPEN: {
1208 status.openCount_++;
1209 if (map->isAvailable())
1210 status.readyCount_++;
1211 break;
1212 }
1213
1214 default:
1215 // Must not get here.
1216 assert(false);
1217 break;
1218 }
1219 }
1220}
1221
1222void
1223UPnPContext::getMappingStatus(MappingStatus& status)
1224{
1225 getMappingStatus(PortType::TCP, status);
1226 getMappingStatus(PortType::UDP, status);
1227}
1228
1229void
1230UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
1231{
Adrien Béraud612b55b2023-05-29 10:42:04 -04001232 auto const& map = getMappingWithKey(mapRes.getMapKey());
1233 if (not map) {
1234 // We may receive a response for a removed request. Just ignore it.
Adrien Berauda8731ac2023-08-17 12:19:39 -04001235 if (logger_) logger_->debug("Mapping {} [IGD {}] does not have a local match",
1236 mapRes.toString(),
1237 mapRes.getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001238 return;
1239 }
1240
1241 auto igd = map->getIgd();
1242 if (not igd) {
Adrien Berauda8731ac2023-08-17 12:19:39 -04001243 if (logger_) logger_->error("IGD pointer is null");
Adrien Béraud612b55b2023-05-29 10:42:04 -04001244 return;
1245 }
1246
Adrien Béraud370257c2023-08-15 20:53:09 -04001247 map->updateState(MappingState::FAILED);
Adrien Béraud612b55b2023-05-29 10:42:04 -04001248 unregisterMapping(map);
1249
Adrien Berauda8731ac2023-08-17 12:19:39 -04001250 if (logger_) logger_->warn("Mapping request for {} failed on IGD {} [{}]",
1251 map->toString(),
1252 igd->toString(),
1253 igd->getProtocolName());
Adrien Béraud612b55b2023-05-29 10:42:04 -04001254}
1255
Adrien Béraud612b55b2023-05-29 10:42:04 -04001256#if HAVE_LIBNATPMP
1257void
1258UPnPContext::renewAllocations()
1259{
Adrien Béraud370257c2023-08-15 20:53:09 -04001260 //CHECK_VALID_THREAD();
Adrien Béraud612b55b2023-05-29 10:42:04 -04001261
1262 // Check if the we have valid PMP IGD.
1263 auto pmpProto = protocolList_.at(NatProtocolType::NAT_PMP);
1264
1265 auto now = sys_clock::now();
1266 std::vector<Mapping::sharedPtr_t> toRenew;
1267
1268 for (auto type : {PortType::TCP, PortType::UDP}) {
1269 std::lock_guard<std::mutex> lock(mappingMutex_);
1270 auto mappingList = getMappingList(type);
1271 for (auto const& [_, map] : mappingList) {
1272 if (not map->isValid())
1273 continue;
1274 if (map->getProtocol() != NatProtocolType::NAT_PMP)
1275 continue;
1276 if (map->getState() != MappingState::OPEN)
1277 continue;
1278 if (now < map->getRenewalTime())
1279 continue;
1280
1281 toRenew.emplace_back(map);
1282 }
1283 }
1284
1285 // Quit if there are no mapping to renew
1286 if (toRenew.empty())
1287 return;
1288
1289 for (auto const& map : toRenew) {
1290 pmpProto->requestMappingRenew(*map);
1291 }
1292}
1293#endif
1294
1295} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -04001296} // namespace dhtnet