| /* |
| * Copyright (C) 2004-2023 Savoir-faire Linux Inc. |
| * |
| * This program is free software: you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation, either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| */ |
| #pragma once |
| |
| /*#include "upnp_protocol.h" |
| #if HAVE_LIBNATPMP |
| #include "protocol/natpmp/nat_pmp.h" |
| #endif |
| #if HAVE_LIBUPNP |
| #include "protocol/pupnp/pupnp.h" |
| #endif |
| #include "igd.h"*/ |
| |
| #include "../ip_utils.h" |
| |
| #include "mapping.h" |
| |
| #include <opendht/rng.h> |
| #include <opendht/logger.h> |
| #include <asio/steady_timer.hpp> |
| |
| #include <set> |
| #include <map> |
| #include <mutex> |
| #include <memory> |
| #include <string> |
| #include <chrono> |
| #include <random> |
| #include <atomic> |
| #include <condition_variable> |
| |
| #include <cstdlib> |
| |
| using IgdFoundCallback = std::function<void()>; |
| |
| namespace dhtnet { |
| class IpAddr; |
| } |
| |
| namespace dhtnet { |
| namespace upnp { |
| |
| class UPnPProtocol; |
| class IGD; |
| |
| enum class UpnpIgdEvent { ADDED, REMOVED, INVALID_STATE }; |
| |
| // Interface used to report mapping event from the protocol implementations. |
| // This interface is meant to be implemented only by UPnPConext class. Sincce |
| // this class is a singleton, it's assumed that it out-lives the protocol |
| // implementations. In other words, the observer is always assumed to point to a |
| // valid instance. |
| class UpnpMappingObserver |
| { |
| public: |
| UpnpMappingObserver() {}; |
| virtual ~UpnpMappingObserver() {}; |
| |
| virtual void onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event) = 0; |
| virtual void onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& map) = 0; |
| virtual void onMappingRequestFailed(const Mapping& map) = 0; |
| #if HAVE_LIBNATPMP |
| virtual void onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map) = 0; |
| #endif |
| virtual void onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& map) = 0; |
| }; |
| |
| class UPnPContext : public UpnpMappingObserver |
| { |
| private: |
| struct MappingStatus |
| { |
| int openCount_ {0}; |
| int readyCount_ {0}; |
| int pendingCount_ {0}; |
| int inProgressCount_ {0}; |
| int failedCount_ {0}; |
| |
| void reset() |
| { |
| openCount_ = 0; |
| readyCount_ = 0; |
| pendingCount_ = 0; |
| inProgressCount_ = 0; |
| failedCount_ = 0; |
| }; |
| int sum() { return openCount_ + pendingCount_ + inProgressCount_ + failedCount_; } |
| }; |
| |
| public: |
| UPnPContext(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger); |
| ~UPnPContext(); |
| |
| std::shared_ptr<asio::io_context> createIoContext(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger); |
| |
| // Terminate the instance. |
| void shutdown(); |
| |
| // Set the known public address |
| void setPublicAddress(const IpAddr& addr); |
| |
| // Check if there is a valid IGD in the IGD list. |
| bool isReady() const; |
| |
| // Get external Ip of a chosen IGD. |
| IpAddr getExternalIP() const; |
| |
| // Inform the UPnP context that the network status has changed. This clears the list of known |
| void connectivityChanged(); |
| |
| // Returns a shared pointer of the mapping. |
| Mapping::sharedPtr_t reserveMapping(Mapping& requestedMap); |
| |
| // Release an used mapping (make it available for future use). |
| void releaseMapping(const Mapping& map); |
| |
| // Register a controller |
| void registerController(void* controller); |
| // Unregister a controller |
| void unregisterController(void* controller); |
| |
| // Generate random port numbers |
| static uint16_t generateRandomPort(PortType type, bool mustBeEven = false); |
| |
| template <typename T> |
| inline void dispatch(T&& f) { |
| ctx->dispatch(std::move(f)); |
| } |
| |
| private: |
| // Initialization |
| void init(); |
| |
| /** |
| * @brief start the search for IGDs activate the mapping |
| * list update. |
| * |
| */ |
| void startUpnp(); |
| |
| /** |
| * @brief Clear all IGDs and release/delete current mappings |
| * |
| * @param forceRelease If true, also delete mappings with enabled |
| * auto-update feature. |
| * |
| */ |
| void stopUpnp(bool forceRelease = false); |
| |
| void shutdown(std::condition_variable& cv); |
| |
| // Create and register a new mapping. |
| Mapping::sharedPtr_t registerMapping(Mapping& map); |
| |
| // Removes the mapping from the list. |
| void unregisterMapping(const Mapping::sharedPtr_t& map); |
| |
| // Perform the request on the provided IGD. |
| void requestMapping(const Mapping::sharedPtr_t& map); |
| |
| // Request a mapping remove from the IGD. |
| void requestRemoveMapping(const Mapping::sharedPtr_t& map); |
| |
| // Remove all mappings of the given type. |
| void deleteAllMappings(PortType type); |
| |
| // Update the state and notify the listener |
| void updateMappingState(const Mapping::sharedPtr_t& map, |
| MappingState newState, |
| bool notify = true); |
| |
| // Provision ports. |
| uint16_t getAvailablePortNumber(PortType type); |
| |
| // Update preferred IGD |
| void updatePreferredIgd(); |
| |
| // Get preferred IGD |
| std::shared_ptr<IGD> getPreferredIgd() const; |
| |
| // Check and prune the mapping list. Called periodically. |
| void updateMappingList(bool async); |
| |
| // Provision (pre-allocate) the requested number of mappings. |
| bool provisionNewMappings(PortType type, int portCount); |
| |
| // Close unused mappings. |
| bool deleteUnneededMappings(PortType type, int portCount); |
| |
| /** |
| * Prune the mapping list.To avoid competing with allocation |
| * requests, the pruning is performed only if there are no |
| * requests in progress. |
| */ |
| void pruneMappingList(); |
| |
| /** |
| * Check if there are allocated mappings from previous instances, |
| * and try to close them. |
| * Only done for UPNP protocol. NAT-PMP allocations will expire |
| * anyway if not renewed. |
| */ |
| void pruneUnMatchedMappings(const std::shared_ptr<IGD>& igd, |
| const std::map<Mapping::key_t, Mapping>& remoteMapList); |
| |
| /** |
| * Check the local mapping list against the list returned by the |
| * IGD and remove all mappings which do not have a match. |
| * Only done for UPNP protocol. |
| */ |
| void pruneUnTrackedMappings(const std::shared_ptr<IGD>& igd, |
| const std::map<Mapping::key_t, Mapping>& remoteMapList); |
| |
| void pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd); |
| |
| /** |
| * @brief Get the mapping list |
| * |
| * @param type transport type (TCP/UDP) |
| * @return a reference on the map |
| * @warning concurrency protection done by the caller |
| */ |
| std::map<Mapping::key_t, Mapping::sharedPtr_t>& getMappingList(PortType type); |
| |
| // Get the mapping from the key. |
| Mapping::sharedPtr_t getMappingWithKey(Mapping::key_t key); |
| |
| // Get the number of mappings per state. |
| void getMappingStatus(PortType type, MappingStatus& status); |
| void getMappingStatus(MappingStatus& status); |
| |
| #if HAVE_LIBNATPMP |
| void renewAllocations(); |
| #endif |
| |
| // Process requests with pending status. |
| void processPendingRequests(const std::shared_ptr<IGD>& igd); |
| |
| // Process mapping with auto-update flag enabled. |
| void processMappingWithAutoUpdate(); |
| |
| // Implementation of UpnpMappingObserver interface. |
| |
| // Callback used to report changes in IGD status. |
| void onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event) override; |
| // Callback used to report add request status. |
| void onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& map) override; |
| // Callback invoked when a request fails. Reported on failures for both |
| // new requests and renewal requests (if supported by the the protocol). |
| void onMappingRequestFailed(const Mapping& map) override; |
| #if HAVE_LIBNATPMP |
| // Callback used to report renew request status. |
| void onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map) override; |
| #endif |
| // Callback used to report remove request status. |
| void onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& map) override; |
| |
| private: |
| UPnPContext(const UPnPContext&) = delete; |
| UPnPContext(UPnPContext&&) = delete; |
| UPnPContext& operator=(UPnPContext&&) = delete; |
| UPnPContext& operator=(const UPnPContext&) = delete; |
| |
| void _connectivityChanged(const asio::error_code& ec); |
| |
| // Thread (io_context), destroyed last |
| std::unique_ptr<std::thread> ioContextRunner_ {}; |
| |
| bool started_ {false}; |
| |
| // The known public address. The external addresses returned by |
| // the IGDs will be checked against this address. |
| IpAddr knownPublicAddress_ {}; |
| |
| // Set of registered controllers |
| std::mutex mutable controllerMutex_; |
| std::set<void*> controllerList_; |
| |
| // Map of available protocols. |
| std::map<NatProtocolType, std::shared_ptr<UPnPProtocol>> protocolList_; |
| |
| // Port ranges for TCP and UDP (in that order). |
| std::map<PortType, std::pair<uint16_t, uint16_t>> portRange_ {}; |
| |
| // Min open ports limit |
| int minOpenPortLimit_[2] {4, 8}; |
| // Max open ports limit |
| int maxOpenPortLimit_[2] {8, 12}; |
| |
| std::shared_ptr<asio::io_context> ctx; |
| std::shared_ptr<dht::log::Logger> logger_; |
| asio::steady_timer mappingListUpdateTimer_; |
| asio::steady_timer connectivityChangedTimer_; |
| |
| // Current preferred IGD. Can be null if there is no valid IGD. |
| std::shared_ptr<IGD> preferredIgd_; |
| |
| // This mutex must lock only these two members. All other |
| // members must be accessed only from the UPNP context thread. |
| std::mutex mutable mappingMutex_; |
| // List of mappings. |
| std::map<Mapping::key_t, Mapping::sharedPtr_t> mappingList_[2] {}; |
| std::set<std::shared_ptr<IGD>> validIgdList_ {}; |
| |
| // Shutdown synchronization |
| bool shutdownComplete_ {false}; |
| }; |
| |
| } // namespace upnp |
| } // namespace dhtnet |