Skip to content

Commit 18515ae

Browse files
AmnaSneneFrançois-Simon Fauteux-Chapleau
authored and
François-Simon Fauteux-Chapleau
committed
upnp_context: fix handling of FAILED mappings
This patch fixes a bug where mappings in the FAILED state were sometimes left in the UPnPContext's mapping list. This is not necessary and can lead to problems if the list grows too large. GitLab: #45 Change-Id: I35641880160194be9c29867abb05161fbc7f9376
1 parent 7333b1f commit 18515ae

File tree

2 files changed

+68
-38
lines changed

2 files changed

+68
-38
lines changed

include/upnp/upnp_context.h

+9-4
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,7 @@ class UPnPContext : public UpnpMappingObserver
165165
Mapping::sharedPtr_t registerMapping(Mapping& map);
166166

167167
// Remove the given mapping from the local list.
168-
//
169-
// If the mapping has auto-update enabled, then a new mapping of the same
170-
// type will be reserved unless ignoreAutoUpdate is true.
171-
void unregisterMapping(const Mapping::sharedPtr_t& map, bool ignoreAutoUpdate = false);
168+
void unregisterMapping(const Mapping::sharedPtr_t& map);
172169

173170
// Perform the request on the provided IGD.
174171
void requestMapping(const Mapping::sharedPtr_t& map);
@@ -231,6 +228,14 @@ class UPnPContext : public UpnpMappingObserver
231228
// Process requests with pending status.
232229
void processPendingRequests();
233230

231+
// Handle mapping with FAILED state.
232+
//
233+
// There are two cases to consider: when auto-update is enabled and when it is not.
234+
// If auto-update is disabled, the mapping will be unregistered.
235+
// If auto-update is enabled, a new mapping of the same type will be requested if there is a valid IGD available.
236+
// Otherwise, the mapping request will be marked as pending and will be requested when an IGD becomes available.
237+
void handleFailedMapping(const Mapping::sharedPtr_t& map);
238+
234239
// Implementation of UpnpMappingObserver interface.
235240

236241
// Callback used to report changes in IGD status.

src/upnp/upnp_context.cpp

+59-34
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ UPnPContext::stopUpnp(bool forceRelease)
209209
// gets recreated if we restart UPnP later.
210210
map->setState(MappingState::PENDING);
211211
} else {
212-
unregisterMapping(map, true);
212+
unregisterMapping(map);
213213
}
214214
}
215215

@@ -814,7 +814,7 @@ UPnPContext::_syncLocalMappingListWithIgd()
814814
Mapping::UPNP_MAPPING_DESCRIPTION_PREFIX);
815815
bool requestsInProgress = false;
816816
// Use a temporary list to avoid processing mappings while holding the lock.
817-
std::list<Mapping::sharedPtr_t> toRemoveFromLocalList;
817+
std::list<Mapping::sharedPtr_t> failedMappings;
818818
for (auto type: {PortType::TCP, PortType::UDP}) {
819819
std::lock_guard lock(mappingMutex_);
820820
for (auto& [_, map] : getMappingList(type)) {
@@ -830,10 +830,10 @@ UPnPContext::_syncLocalMappingListWithIgd()
830830
auto it = remoteMapList.find(map->getMapKey());
831831
if (it == remoteMapList.end()) {
832832
if (logger_) logger_->warn("Mapping {} (IGD {}) marked as \"OPEN\" but not found in the "
833-
"remote list. Removing from local list.",
833+
"remote list. Setting state to \"FAILED\".",
834834
map->toString(),
835835
igd->toString());
836-
toRemoveFromLocalList.emplace_back(map);
836+
failedMappings.emplace_back(map);
837837
} else {
838838
auto oldExpiryTime = map->getExpiryTime();
839839
auto newExpiryTime = it->second.getExpiryTime();
@@ -860,11 +860,10 @@ UPnPContext::_syncLocalMappingListWithIgd()
860860
}
861861
scheduleMappingsRenewal();
862862

863-
for (auto const& map : toRemoveFromLocalList) {
863+
for (auto const& map : failedMappings) {
864864
updateMappingState(map, MappingState::FAILED);
865-
unregisterMapping(map);
866865
}
867-
if (!toRemoveFromLocalList.empty())
866+
if (!failedMappings.empty())
868867
enforceAvailableMappingsLimits();
869868

870869
if (requestsInProgress) {
@@ -924,7 +923,6 @@ UPnPContext::pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd)
924923
igd->toString(),
925924
igd->getProtocolName());
926925
updateMappingState(map, MappingState::FAILED);
927-
unregisterMapping(map);
928926
}
929927
}
930928

@@ -1130,19 +1128,27 @@ UPnPContext::_endIgdDiscovery()
11301128
if (isReady()) {
11311129
return;
11321130
}
1133-
// if there is no valid IGD, the pending mapping requests will be changed to failed
1134-
std::lock_guard lockMappings_(mappingMutex_);
1135-
PortType types[2] {PortType::TCP, PortType::UDP};
1136-
for (auto& type : types) {
1137-
const auto& mappingList = getMappingList(type);
1138-
for (auto const& [_, map] : mappingList) {
1139-
updateMappingState(map, MappingState::FAILED);
1140-
// Do not unregister the mapping, it's up to the controller to decide. It will be unregistered when the controller releases it.
1141-
// unregisterMapping(map) here will cause a deadlock because of the lock on mappingMutex_.
1142-
if (logger_) logger_->warn("Request for mapping {} failed, no IGD available",
1143-
map->toString());
1131+
1132+
// Use a temporary list to avoid holding the lock while processing the mapping list.
1133+
// This is necessary because updateMappingState calls a user-defined callback, which
1134+
// in turn could end up calling a function (such as reserveMapping) that would also
1135+
// attempt to lock mappingMutex_.
1136+
std::list<Mapping::sharedPtr_t> toRemoveList;
1137+
{
1138+
std::lock_guard lock(mappingMutex_);
1139+
PortType types[2] {PortType::TCP, PortType::UDP};
1140+
for (auto type : types) {
1141+
const auto& mappingList = getMappingList(type);
1142+
for (const auto& [_, map] : mappingList) {
1143+
toRemoveList.emplace_back(map);
1144+
}
11441145
}
11451146
}
1147+
// We reached the end of the IGD discovery period without finding a valid IGD,
1148+
// so all mapping requests are considered to have failed.
1149+
for (auto const& map : toRemoveList) {
1150+
updateMappingState(map, MappingState::FAILED);
1151+
}
11461152
}
11471153

11481154
void
@@ -1204,25 +1210,12 @@ UPnPContext::registerMapping(Mapping& map)
12041210
}
12051211

12061212
void
1207-
UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map, bool ignoreAutoUpdate)
1213+
UPnPContext::unregisterMapping(const Mapping::sharedPtr_t& map)
12081214
{
12091215
if (not map) {
12101216
return;
12111217
}
12121218

1213-
if (map->getAutoUpdate() && !ignoreAutoUpdate) {
1214-
if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
1215-
map->toString());
1216-
1217-
Mapping newMapping(map->getType());
1218-
newMapping.enableAutoUpdate(true);
1219-
newMapping.setNotifyCallback(map->getNotifyCallback());
1220-
reserveMapping(newMapping);
1221-
1222-
// TODO: figure out if this line is actually necessary
1223-
// (See https://review.jami.net/c/jami-daemon/+/16940)
1224-
map->setNotifyCallback(nullptr);
1225-
}
12261219
std::lock_guard lock(mappingMutex_);
12271220
auto& mappingList = getMappingList(map->getType());
12281221

@@ -1268,7 +1261,6 @@ UPnPContext::onMappingRequestFailed(const Mapping& mapRes)
12681261
}
12691262

12701263
updateMappingState(map, MappingState::FAILED);
1271-
unregisterMapping(map);
12721264

12731265
if (logger_) logger_->warn("Request for mapping {} on IGD {} failed",
12741266
map->toString(),
@@ -1293,6 +1285,39 @@ UPnPContext::updateMappingState(const Mapping::sharedPtr_t& map, MappingState ne
12931285
// Notify the listener if set.
12941286
if (notify and map->getNotifyCallback())
12951287
map->getNotifyCallback()(map);
1288+
1289+
if (newState == MappingState::FAILED)
1290+
handleFailedMapping(map);
1291+
1292+
}
1293+
1294+
void
1295+
UPnPContext::handleFailedMapping(const Mapping::sharedPtr_t& map)
1296+
{
1297+
if (!map->getAutoUpdate()) {
1298+
// If the mapping is not set to auto-update, it will be unregistered.
1299+
unregisterMapping(map);
1300+
return;
1301+
}
1302+
1303+
if (isReady()) {
1304+
Mapping newMapping(map->getType());
1305+
newMapping.enableAutoUpdate(true);
1306+
newMapping.setNotifyCallback(map->getNotifyCallback());
1307+
reserveMapping(newMapping);
1308+
if (logger_) logger_->debug("Mapping {} has auto-update enabled, a new mapping will be requested",
1309+
map->toString());
1310+
1311+
// TODO: figure out if this line is actually necessary
1312+
// (See https://review.jami.net/c/jami-daemon/+/16940)
1313+
map->setNotifyCallback(nullptr);
1314+
} else {
1315+
// If there is no valid IGD, the mapping is marked as pending
1316+
// and will be requested when an IGD becomes available.
1317+
updateMappingState(map, MappingState::PENDING, false);
1318+
if (logger_) logger_->debug("Mapping {} will be requested when an IGD becomes available",
1319+
map->toString());
1320+
}
12961321
}
12971322

12981323
} // namespace upnp

0 commit comments

Comments
 (0)