upnpctrl: add command to show all existing port mappings

This can be useful for debugging purposes.

Change-Id: I9da11d20a7a8cd9f7d1eae9d4aee45281c5cd4ad
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)
 {