blob: d16105372438f9873846dcbdb830b154548f7f98 [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 */
Adrien Béraud612b55b2023-05-29 10:42:04 -040017#pragma once
18
Adrien Bérauddbb06862023-07-08 09:18:39 -040019#include "../ip_utils.h"
Adrien Béraud612b55b2023-05-29 10:42:04 -040020
Adrien Bérauddbb06862023-07-08 09:18:39 -040021#include "mapping.h"
Morteza Namvar5f639522023-07-04 17:08:58 -040022
Adrien Béraud612b55b2023-05-29 10:42:04 -040023#include <opendht/rng.h>
Adrien Béraud25c30c42023-07-05 13:46:54 -040024#include <opendht/logger.h>
Adrien Béraud612b55b2023-05-29 10:42:04 -040025#include <asio/steady_timer.hpp>
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -040026#include <asio/system_timer.hpp>
Adrien Béraud612b55b2023-05-29 10:42:04 -040027
28#include <set>
29#include <map>
30#include <mutex>
31#include <memory>
32#include <string>
33#include <chrono>
34#include <random>
35#include <atomic>
Morteza Namvar5f639522023-07-04 17:08:58 -040036#include <condition_variable>
Adrien Béraud612b55b2023-05-29 10:42:04 -040037
Morteza Namvar5f639522023-07-04 17:08:58 -040038#include <cstdlib>
Adrien Béraud612b55b2023-05-29 10:42:04 -040039
Adrien Béraud612b55b2023-05-29 10:42:04 -040040using IgdFoundCallback = std::function<void()>;
41
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040042namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040043class IpAddr;
44}
45
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040046namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040047namespace upnp {
48
Morteza Namvar5f639522023-07-04 17:08:58 -040049class UPnPProtocol;
50class IGD;
51
François-Simon Fauteux-Chapleau826f0ba2024-05-29 15:22:21 -040052struct IGDInfo
53{
54 std::string uid;
55 IpAddr localIp;
56 IpAddr publicIp;
57 std::vector<MappingInfo> mappingInfoList;
58};
59
Morteza Namvar5f639522023-07-04 17:08:58 -040060enum class UpnpIgdEvent { ADDED, REMOVED, INVALID_STATE };
61
62// Interface used to report mapping event from the protocol implementations.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -040063// This interface is meant to be implemented only by UPnPContext class. Since
64// this class is a singleton, it's assumed that it outlives the protocol
Morteza Namvar5f639522023-07-04 17:08:58 -040065// implementations. In other words, the observer is always assumed to point to a
66// valid instance.
67class UpnpMappingObserver
68{
69public:
70 UpnpMappingObserver() {};
71 virtual ~UpnpMappingObserver() {};
72
73 virtual void onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event) = 0;
74 virtual void onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& map) = 0;
75 virtual void onMappingRequestFailed(const Mapping& map) = 0;
Morteza Namvar5f639522023-07-04 17:08:58 -040076 virtual void onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map) = 0;
Morteza Namvar5f639522023-07-04 17:08:58 -040077 virtual void onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& map) = 0;
Amna0d215232024-08-27 17:57:45 -040078 virtual void onIgdDiscoveryStarted() = 0;
Morteza Namvar5f639522023-07-04 17:08:58 -040079};
80
Adrien Béraud370257c2023-08-15 20:53:09 -040081class UPnPContext : public UpnpMappingObserver
Adrien Béraud612b55b2023-05-29 10:42:04 -040082{
Adrien Béraud612b55b2023-05-29 10:42:04 -040083public:
Sébastien Blin55abf072023-07-19 10:21:21 -040084 UPnPContext(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger);
Adrien Béraud612b55b2023-05-29 10:42:04 -040085 ~UPnPContext();
86
Adrien Béraudb04fbd72023-08-17 19:56:11 -040087 std::shared_ptr<asio::io_context> createIoContext(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger);
88
Adrien Béraud612b55b2023-05-29 10:42:04 -040089 // Terminate the instance.
90 void shutdown();
91
92 // Set the known public address
93 void setPublicAddress(const IpAddr& addr);
94
95 // Check if there is a valid IGD in the IGD list.
96 bool isReady() const;
97
98 // Get external Ip of a chosen IGD.
99 IpAddr getExternalIP() const;
100
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400101 // Inform the UPnP context that the network status has changed.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400102 void connectivityChanged();
103
104 // Returns a shared pointer of the mapping.
105 Mapping::sharedPtr_t reserveMapping(Mapping& requestedMap);
106
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400107 // Release a used mapping (make it available for future use).
108 // TODO: The current implementation doesn't seem to do the "make it available for future use" part... fix this.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400109 void releaseMapping(const Mapping& map);
110
111 // Register a controller
112 void registerController(void* controller);
113 // Unregister a controller
114 void unregisterController(void* controller);
115
116 // Generate random port numbers
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400117 static uint16_t generateRandomPort(PortType type);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400118
François-Simon Fauteux-Chapleau826f0ba2024-05-29 15:22:21 -0400119 // Return information about the UPnPContext's valid IGDs, including the list
120 // of all existing port mappings (for IGDs which support a protocol that allows
121 // querying that information -- UPnP does, but NAT-PMP doesn't for example)
122 std::vector<IGDInfo> getIgdsInfo() const;
123
Adrien Berauda8731ac2023-08-17 12:19:39 -0400124 template <typename T>
125 inline void dispatch(T&& f) {
126 ctx->dispatch(std::move(f));
127 }
128
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400129 void restart()
130 {
131 ctx->dispatch([this]{
132 stopUpnp();
133 startUpnp();
134 });
135 }
136
Amna0d215232024-08-27 17:57:45 -0400137 // Set the timeout for the IGD discovery process.
138 // If the timeout expires and no valid IGD has been discovered,
139 // then the state of all pending mappings is set to FAILED.
140 void setIgdDiscoveryTimeout(std::chrono::milliseconds timeout);
141
Adrien Béraud612b55b2023-05-29 10:42:04 -0400142private:
143 // Initialization
144 void init();
145
146 /**
147 * @brief start the search for IGDs activate the mapping
148 * list update.
149 *
150 */
151 void startUpnp();
152
153 /**
154 * @brief Clear all IGDs and release/delete current mappings
155 *
156 * @param forceRelease If true, also delete mappings with enabled
157 * auto-update feature.
158 *
159 */
160 void stopUpnp(bool forceRelease = false);
161
162 void shutdown(std::condition_variable& cv);
163
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400164 // Add a new mapping to the local list and
165 // send a request to the IGD to create it.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400166 Mapping::sharedPtr_t registerMapping(Mapping& map);
167
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400168 // Remove the given mapping from the local list.
169 //
170 // If the mapping has auto-update enabled, then a new mapping of the same
171 // type will be reserved unless ignoreAutoUpdate is true.
172 void unregisterMapping(const Mapping::sharedPtr_t& map, bool ignoreAutoUpdate = false);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400173
174 // Perform the request on the provided IGD.
175 void requestMapping(const Mapping::sharedPtr_t& map);
176
177 // Request a mapping remove from the IGD.
178 void requestRemoveMapping(const Mapping::sharedPtr_t& map);
179
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400180 // Update the state and notify the listener
181 void updateMappingState(const Mapping::sharedPtr_t& map,
182 MappingState newState,
183 bool notify = true);
184
Adrien Béraud612b55b2023-05-29 10:42:04 -0400185 // Provision ports.
186 uint16_t getAvailablePortNumber(PortType type);
187
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400188 // If the current IGD is still valid, do nothing.
189 // If not, then replace it with a valid one (if possible) or set it to null.
190 void updateCurrentIgd();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400191
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400192 // Get the current IGD
193 std::shared_ptr<IGD> getCurrentIgd() const;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400194
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400195 // Send a renewal request to the IGD for each mapping which is past its renewal time.
196 void renewMappings();
197
198 // Set a timer so that renewMappings is called when needed
199 void scheduleMappingsRenewal();
200 void _scheduleMappingsRenewal();
201
202 // Add or remove mappings to maintain the number of available mappings
203 // within the limits set by minAvailableMappings_ and maxAvailableMappings_.
204 void enforceAvailableMappingsLimits();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400205
206 // Provision (pre-allocate) the requested number of mappings.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400207 void provisionNewMappings(PortType type, int portCount);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400208
209 // Close unused mappings.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400210 void deleteUnneededMappings(PortType type, int portCount);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400211
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400212 // Get information from the current IGD about the mappings it currently has
213 // and update the local list accordingly. (Only called if the current IGD
214 // uses the UPnP protocol -- NAT-PMP doesn't support doing this.)
215 void syncLocalMappingListWithIgd();
216 void _syncLocalMappingListWithIgd();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400217
218 void pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd);
219
220 /**
221 * @brief Get the mapping list
222 *
223 * @param type transport type (TCP/UDP)
224 * @return a reference on the map
225 * @warning concurrency protection done by the caller
226 */
227 std::map<Mapping::key_t, Mapping::sharedPtr_t>& getMappingList(PortType type);
228
229 // Get the mapping from the key.
230 Mapping::sharedPtr_t getMappingWithKey(Mapping::key_t key);
231
Adrien Béraud612b55b2023-05-29 10:42:04 -0400232 // Process requests with pending status.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400233 void processPendingRequests();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400234
235 // Implementation of UpnpMappingObserver interface.
236
237 // Callback used to report changes in IGD status.
238 void onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event) override;
239 // Callback used to report add request status.
240 void onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& map) override;
241 // Callback invoked when a request fails. Reported on failures for both
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400242 // new requests and renewal requests.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400243 void onMappingRequestFailed(const Mapping& map) override;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400244
Adrien Béraud612b55b2023-05-29 10:42:04 -0400245 // Callback used to report renew request status.
246 void onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map) override;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400247
Adrien Béraud612b55b2023-05-29 10:42:04 -0400248 // Callback used to report remove request status.
249 void onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& map) override;
250
Amna0d215232024-08-27 17:57:45 -0400251 // Callback used to report the start of the discovery process: search for IGDs.
252 void onIgdDiscoveryStarted() override;
253
Adrien Béraud612b55b2023-05-29 10:42:04 -0400254private:
255 UPnPContext(const UPnPContext&) = delete;
256 UPnPContext(UPnPContext&&) = delete;
257 UPnPContext& operator=(UPnPContext&&) = delete;
258 UPnPContext& operator=(const UPnPContext&) = delete;
259
Adrien Béraudc36965c2023-08-17 21:50:27 -0400260 void _connectivityChanged(const asio::error_code& ec);
261
262 // Thread (io_context), destroyed last
263 std::unique_ptr<std::thread> ioContextRunner_ {};
264
Adrien Béraud612b55b2023-05-29 10:42:04 -0400265 bool started_ {false};
266
267 // The known public address. The external addresses returned by
268 // the IGDs will be checked against this address.
269 IpAddr knownPublicAddress_ {};
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400270 std::mutex publicAddressMutex_;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400271
272 // Map of available protocols.
273 std::map<NatProtocolType, std::shared_ptr<UPnPProtocol>> protocolList_;
274
275 // Port ranges for TCP and UDP (in that order).
276 std::map<PortType, std::pair<uint16_t, uint16_t>> portRange_ {};
277
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400278 // Minimum and maximum limits on the number of available
279 // mappings to keep in the list at any given time
280 static constexpr unsigned minAvailableMappings_[2] {4, 8};
281 static constexpr unsigned maxAvailableMappings_[2] {8, 12};
282 unsigned getMinAvailableMappings(PortType type) {
283 unsigned index = (type == PortType::TCP) ? 0 : 1;
284 return minAvailableMappings_[index];
285 }
286 unsigned getMaxAvailableMappings(PortType type) {
287 unsigned index = (type == PortType::TCP) ? 0 : 1;
288 return maxAvailableMappings_[index];
289 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400290
Adrien Béraud370257c2023-08-15 20:53:09 -0400291 std::shared_ptr<asio::io_context> ctx;
292 std::shared_ptr<dht::log::Logger> logger_;
Adrien Béraudc36965c2023-08-17 21:50:27 -0400293 asio::steady_timer connectivityChangedTimer_;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400294 asio::system_timer mappingRenewalTimer_;
295 asio::steady_timer renewalSchedulingTimer_;
296 asio::steady_timer syncTimer_;
297 std::mutex syncMutex_;
298 bool syncRequested_ {false};
Adrien Béraud612b55b2023-05-29 10:42:04 -0400299
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400300 // This mutex must lock only the members below. All other
301 // members must be accessed only from the UPnP context thread.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400302 std::mutex mutable mappingMutex_;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400303
Adrien Béraud612b55b2023-05-29 10:42:04 -0400304 // List of mappings.
305 std::map<Mapping::key_t, Mapping::sharedPtr_t> mappingList_[2] {};
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400306
307 // Current IGD. Can be null if there is no valid IGD.
308 std::shared_ptr<IGD> currentIgd_;
309
310 // Set of registered controllers
311 std::set<void*> controllerList_;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400312
313 // Shutdown synchronization
314 bool shutdownComplete_ {false};
François-Simon Fauteux-Chapleau808db4f2024-04-19 11:39:47 -0400315 bool shutdownTimedOut_ {false};
Amna0d215232024-08-27 17:57:45 -0400316
317 // IGD Discovery synchronization. This boolean indicates if the IGD discovery is in progress.
318 bool igdDiscoveryInProgress_ {true};
319 std::mutex igdDiscoveryMutex_;
320 std::chrono::milliseconds igdDiscoveryTimeout_ {std::chrono::milliseconds(500)};
321
322 // End of the discovery process.
323 void _endIgdDiscovery();
324
325 asio::steady_timer igdDiscoveryTimer_;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400326};
327
328} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -0400329} // namespace dhtnet