upnpctrl: add command to show all existing port mappings
This can be useful for debugging purposes.
Change-Id: I9da11d20a7a8cd9f7d1eae9d4aee45281c5cd4ad
diff --git a/include/upnp/mapping.h b/include/upnp/mapping.h
index e92a654..b9c2078 100644
--- a/include/upnp/mapping.h
+++ b/include/upnp/mapping.h
@@ -137,5 +137,17 @@
#endif
};
+struct MappingInfo
+{
+ std::string remoteHost;
+ std::string protocol;
+ std::string internalClient;
+ std::string enabled;
+ std::string description;
+ uint16_t externalPort;
+ uint16_t internalPort;
+ uint32_t leaseDuration;
+};
+
} // namespace upnp
} // namespace dhtnet
diff --git a/include/upnp/upnp_context.h b/include/upnp/upnp_context.h
index 9941e3b..0958864 100644
--- a/include/upnp/upnp_context.h
+++ b/include/upnp/upnp_context.h
@@ -57,6 +57,14 @@
class UPnPProtocol;
class IGD;
+struct IGDInfo
+{
+ std::string uid;
+ IpAddr localIp;
+ IpAddr publicIp;
+ std::vector<MappingInfo> mappingInfoList;
+};
+
enum class UpnpIgdEvent { ADDED, REMOVED, INVALID_STATE };
// Interface used to report mapping event from the protocol implementations.
@@ -136,6 +144,11 @@
// Generate random port numbers
static uint16_t generateRandomPort(PortType type, bool mustBeEven = false);
+ // Return information about the UPnPContext's valid IGDs, including the list
+ // of all existing port mappings (for IGDs which support a protocol that allows
+ // querying that information -- UPnP does, but NAT-PMP doesn't for example)
+ std::vector<IGDInfo> getIgdsInfo() const;
+
template <typename T>
inline void dispatch(T&& f) {
ctx->dispatch(std::move(f));
diff --git a/src/upnp/protocol/pupnp/pupnp.cpp b/src/upnp/protocol/pupnp/pupnp.cpp
index 432ea60..dbb05f1 100644
--- a/src/upnp/protocol/pupnp/pupnp.cpp
+++ b/src/upnp/protocol/pupnp/pupnp.cpp
@@ -1353,6 +1353,85 @@
return mapList;
}
+std::vector<MappingInfo>
+PUPnP::getMappingsInfo(const std::shared_ptr<IGD>& igd) const
+{
+ auto upnpIgd = std::dynamic_pointer_cast<UPnPIGD>(igd);
+ assert(upnpIgd);
+
+ std::vector<MappingInfo> mappingInfoList;
+
+ if (not clientRegistered_ or not upnpIgd->isValid() or not upnpIgd->getLocalIp())
+ return mappingInfoList;
+
+ static constexpr const char* action_name {"GetGenericPortMappingEntry"};
+
+ for (int entry_idx = 0;; entry_idx++) {
+ std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
+ action(nullptr, ixmlDocument_free); // Action pointer.
+ IXML_Document* action_container_ptr = nullptr;
+
+ std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>
+ response(nullptr, ixmlDocument_free); // Response pointer.
+ IXML_Document* response_container_ptr = nullptr;
+
+ UpnpAddToAction(&action_container_ptr,
+ action_name,
+ upnpIgd->getServiceType().c_str(),
+ "NewPortMappingIndex",
+ std::to_string(entry_idx).c_str());
+ action.reset(action_container_ptr);
+
+ int upnp_err = UpnpSendAction(ctrlptHandle_,
+ upnpIgd->getControlURL().c_str(),
+ upnpIgd->getServiceType().c_str(),
+ nullptr,
+ action.get(),
+ &response_container_ptr);
+ response.reset(response_container_ptr);
+
+ if (!response || upnp_err != UPNP_E_SUCCESS) {
+ break;
+ }
+
+ auto errorCode = getFirstDocItem(response.get(), "errorCode");
+ if (not errorCode.empty()) {
+ auto error = to_int<int>(errorCode);
+ if (error == ARRAY_IDX_INVALID or error == CONFLICT_IN_MAPPING) {
+ // No more port mapping entries in the response.
+ break;
+ } else {
+ auto errorDescription = getFirstDocItem(response.get(), "errorDescription");
+ if (logger_) logger_->error("PUPnP: GetGenericPortMappingEntry returned with error: {:s}: {:s}",
+ errorCode,
+ errorDescription);
+ break;
+ }
+ }
+
+ // Parse the response.
+ MappingInfo info;
+ info.remoteHost = getFirstDocItem(response.get(), "NewRemoteHost");
+ info.protocol = getFirstDocItem(response.get(), "NewProtocol");
+ info.internalClient = getFirstDocItem(response.get(), "NewInternalClient");
+ info.enabled = getFirstDocItem(response.get(), "NewEnabled");
+ info.description = getFirstDocItem(response.get(), "NewPortMappingDescription");
+
+ auto externalPort = getFirstDocItem(response.get(), "NewExternalPort");
+ info.externalPort = to_int<uint16_t>(externalPort);
+
+ auto internalPort = getFirstDocItem(response.get(), "NewInternalPort");
+ info.internalPort = to_int<uint16_t>(internalPort);
+
+ auto leaseDuration = getFirstDocItem(response.get(), "NewLeaseDuration");
+ info.leaseDuration = to_int<uint32_t>(leaseDuration);
+
+ mappingInfoList.push_back(std::move(info));
+ }
+
+ return mappingInfoList;
+}
+
void
PUPnP::deleteMappingsByDescription(const std::shared_ptr<IGD>& igd, const std::string& description)
{
diff --git a/src/upnp/protocol/pupnp/pupnp.h b/src/upnp/protocol/pupnp/pupnp.h
index 5bba6dc..e29e01d 100644
--- a/src/upnp/protocol/pupnp/pupnp.h
+++ b/src/upnp/protocol/pupnp/pupnp.h
@@ -93,6 +93,9 @@
std::map<Mapping::key_t, Mapping> getMappingsListByDescr(
const std::shared_ptr<IGD>& igd, const std::string& descr) const override;
+ // Get information about all existing port mappings on the given IGD
+ std::vector<MappingInfo> getMappingsInfo(const std::shared_ptr<IGD>& igd) const override;
+
// Request a new mapping.
void requestMappingAdd(const Mapping& mapping) override;
diff --git a/src/upnp/protocol/upnp_protocol.h b/src/upnp/protocol/upnp_protocol.h
index 3dde4ab..1ec05ac 100644
--- a/src/upnp/protocol/upnp_protocol.h
+++ b/src/upnp/protocol/upnp_protocol.h
@@ -77,6 +77,12 @@
return {};
}
+ // Get information about all existing port mappings on the given IGD
+ virtual std::vector<MappingInfo> getMappingsInfo(const std::shared_ptr<IGD>& igd) const
+ {
+ return {};
+ }
+
// Sends a request to add a mapping.
virtual void requestMappingAdd(const Mapping& map) = 0;
diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp
index 78488dc..7ea3d83 100644
--- a/src/upnp/upnp_context.cpp
+++ b/src/upnp/upnp_context.cpp
@@ -459,6 +459,27 @@
}
}
+std::vector<IGDInfo>
+UPnPContext::getIgdsInfo() const
+{
+ std::vector<IGDInfo> igdInfoList;
+
+ std::lock_guard lk(mappingMutex_);
+ for (auto& igd : validIgdList_) {
+ auto protocol = protocolList_.at(igd->getProtocol());
+
+ IGDInfo info;
+ info.uid = igd->getUID();
+ info.localIp = igd->getLocalIp();
+ info.publicIp = igd->getPublicIp();
+ info.mappingInfoList = protocol->getMappingsInfo(igd);
+
+ igdInfoList.push_back(std::move(info));
+ }
+
+ return igdInfoList;
+}
+
uint16_t
UPnPContext::getAvailablePortNumber(PortType type)
{
diff --git a/tools/upnp/upnpctrl.cpp b/tools/upnp/upnpctrl.cpp
index 586898b..a5034a6 100644
--- a/tools/upnp/upnpctrl.cpp
+++ b/tools/upnp/upnpctrl.cpp
@@ -14,7 +14,38 @@
" help, h, ?\n"
" quit, exit, q, x\n"
" ip\n"
- " open <port> <protocol>\n");
+ " open <port> <protocol>\n"
+ " close <port>\n"
+ " mappings\n");
+}
+
+void
+print_mappings(std::shared_ptr<dhtnet::upnp::UPnPContext> upnpContext)
+{
+ for (auto const& igdInfo : upnpContext->getIgdsInfo()) {
+ fmt::print("\nIGD: \"{}\" [local IP: {} - public IP: {}]\n",
+ igdInfo.uid,
+ igdInfo.localIp.toString(),
+ igdInfo.publicIp.toString());
+
+ if (igdInfo.mappingInfoList.empty())
+ continue;
+
+ static const char *format = "{:>8} {:>12} {:>12} {:>8} {:>8} {:>16} {:>16} {}\n";
+ fmt::print(format, "Protocol", "ExternalPort", "InternalPort", "Duration",
+ "Enabled?", "InternalClient", "RemoteHost", "Description");
+ for (auto const& mappingInfo : igdInfo.mappingInfoList) {
+ fmt::print(format,
+ mappingInfo.protocol,
+ mappingInfo.externalPort,
+ mappingInfo.internalPort,
+ mappingInfo.leaseDuration,
+ mappingInfo.enabled,
+ mappingInfo.internalClient,
+ mappingInfo.remoteHost.empty() ? "any" : mappingInfo.remoteHost,
+ mappingInfo.description);
+ }
+ }
}
std::string to_lower(std::string_view str_v) {
@@ -82,11 +113,13 @@
++it;
}
}
+ } else if (command == "mappings") {
+ print_mappings(upnpContext);
} else {
fmt::print("Unknown command: {}\n", command);
}
}
- fmt::print("Stopping...");
+ fmt::print("Stopping...\n");
for (auto c: mappings)
controller->releaseMapping(*c);
mappings.clear();