blob: da22e25c6c832941a4c82010efc64fdc4b35d1eb [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).
Adrien Béraud612b55b2023-05-29 10:42:04 -0400108 void releaseMapping(const Mapping& map);
109
110 // Register a controller
111 void registerController(void* controller);
112 // Unregister a controller
113 void unregisterController(void* controller);
114
115 // Generate random port numbers
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400116 static uint16_t generateRandomPort(PortType type);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400117
François-Simon Fauteux-Chapleau826f0ba2024-05-29 15:22:21 -0400118 // Return information about the UPnPContext's valid IGDs, including the list
119 // of all existing port mappings (for IGDs which support a protocol that allows
120 // querying that information -- UPnP does, but NAT-PMP doesn't for example)
121 std::vector<IGDInfo> getIgdsInfo() const;
122
Adrien Berauda8731ac2023-08-17 12:19:39 -0400123 template <typename T>
124 inline void dispatch(T&& f) {
125 ctx->dispatch(std::move(f));
126 }
127
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400128 void restart()
129 {
130 ctx->dispatch([this]{
131 stopUpnp();
132 startUpnp();
133 });
134 }
135
Amna0d215232024-08-27 17:57:45 -0400136 // Set the timeout for the IGD discovery process.
137 // If the timeout expires and no valid IGD has been discovered,
138 // then the state of all pending mappings is set to FAILED.
139 void setIgdDiscoveryTimeout(std::chrono::milliseconds timeout);
140
Adrien Béraud612b55b2023-05-29 10:42:04 -0400141private:
142 // Initialization
143 void init();
144
145 /**
146 * @brief start the search for IGDs activate the mapping
147 * list update.
148 *
149 */
150 void startUpnp();
151
152 /**
153 * @brief Clear all IGDs and release/delete current mappings
154 *
155 * @param forceRelease If true, also delete mappings with enabled
156 * auto-update feature.
157 *
158 */
159 void stopUpnp(bool forceRelease = false);
160
161 void shutdown(std::condition_variable& cv);
162
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400163 // Add a new mapping to the local list and
164 // send a request to the IGD to create it.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400165 Mapping::sharedPtr_t registerMapping(Mapping& map);
166
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400167 // Remove the given mapping from the local list.
Amna18515ae2024-09-11 16:39:11 -0400168 void unregisterMapping(const Mapping::sharedPtr_t& map);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400169
170 // Perform the request on the provided IGD.
171 void requestMapping(const Mapping::sharedPtr_t& map);
172
173 // Request a mapping remove from the IGD.
174 void requestRemoveMapping(const Mapping::sharedPtr_t& map);
175
Adrien Beraud3bd61c92023-08-17 16:57:37 -0400176 // Update the state and notify the listener
177 void updateMappingState(const Mapping::sharedPtr_t& map,
178 MappingState newState,
179 bool notify = true);
180
Adrien Béraud612b55b2023-05-29 10:42:04 -0400181 // Provision ports.
182 uint16_t getAvailablePortNumber(PortType type);
183
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400184 // If the current IGD is still valid, do nothing.
185 // If not, then replace it with a valid one (if possible) or set it to null.
186 void updateCurrentIgd();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400187
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400188 // Get the current IGD
189 std::shared_ptr<IGD> getCurrentIgd() const;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400190
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400191 // Send a renewal request to the IGD for each mapping which is past its renewal time.
192 void renewMappings();
193
194 // Set a timer so that renewMappings is called when needed
195 void scheduleMappingsRenewal();
196 void _scheduleMappingsRenewal();
197
198 // Add or remove mappings to maintain the number of available mappings
199 // within the limits set by minAvailableMappings_ and maxAvailableMappings_.
200 void enforceAvailableMappingsLimits();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400201
202 // Provision (pre-allocate) the requested number of mappings.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400203 void provisionNewMappings(PortType type, int portCount);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400204
205 // Close unused mappings.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400206 void deleteUnneededMappings(PortType type, int portCount);
Adrien Béraud612b55b2023-05-29 10:42:04 -0400207
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400208 // Get information from the current IGD about the mappings it currently has
209 // and update the local list accordingly. (Only called if the current IGD
210 // uses the UPnP protocol -- NAT-PMP doesn't support doing this.)
211 void syncLocalMappingListWithIgd();
212 void _syncLocalMappingListWithIgd();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400213
214 void pruneMappingsWithInvalidIgds(const std::shared_ptr<IGD>& igd);
215
216 /**
217 * @brief Get the mapping list
218 *
219 * @param type transport type (TCP/UDP)
220 * @return a reference on the map
221 * @warning concurrency protection done by the caller
222 */
223 std::map<Mapping::key_t, Mapping::sharedPtr_t>& getMappingList(PortType type);
224
225 // Get the mapping from the key.
226 Mapping::sharedPtr_t getMappingWithKey(Mapping::key_t key);
227
Adrien Béraud612b55b2023-05-29 10:42:04 -0400228 // Process requests with pending status.
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400229 void processPendingRequests();
Adrien Béraud612b55b2023-05-29 10:42:04 -0400230
Amna18515ae2024-09-11 16:39:11 -0400231 // 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
Adrien Béraud612b55b2023-05-29 10:42:04 -0400239 // Implementation of UpnpMappingObserver interface.
240
241 // Callback used to report changes in IGD status.
242 void onIgdUpdated(const std::shared_ptr<IGD>& igd, UpnpIgdEvent event) override;
243 // Callback used to report add request status.
244 void onMappingAdded(const std::shared_ptr<IGD>& igd, const Mapping& map) override;
245 // Callback invoked when a request fails. Reported on failures for both
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400246 // new requests and renewal requests.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400247 void onMappingRequestFailed(const Mapping& map) override;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400248
Adrien Béraud612b55b2023-05-29 10:42:04 -0400249 // Callback used to report renew request status.
250 void onMappingRenewed(const std::shared_ptr<IGD>& igd, const Mapping& map) override;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400251
Adrien Béraud612b55b2023-05-29 10:42:04 -0400252 // Callback used to report remove request status.
253 void onMappingRemoved(const std::shared_ptr<IGD>& igd, const Mapping& map) override;
254
Amna0d215232024-08-27 17:57:45 -0400255 // Callback used to report the start of the discovery process: search for IGDs.
256 void onIgdDiscoveryStarted() override;
257
Adrien Béraud612b55b2023-05-29 10:42:04 -0400258private:
259 UPnPContext(const UPnPContext&) = delete;
260 UPnPContext(UPnPContext&&) = delete;
261 UPnPContext& operator=(UPnPContext&&) = delete;
262 UPnPContext& operator=(const UPnPContext&) = delete;
263
Adrien Béraudc36965c2023-08-17 21:50:27 -0400264 void _connectivityChanged(const asio::error_code& ec);
265
266 // Thread (io_context), destroyed last
267 std::unique_ptr<std::thread> ioContextRunner_ {};
268
Adrien Béraud612b55b2023-05-29 10:42:04 -0400269 bool started_ {false};
270
271 // The known public address. The external addresses returned by
272 // the IGDs will be checked against this address.
273 IpAddr knownPublicAddress_ {};
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400274 std::mutex publicAddressMutex_;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400275
276 // Map of available protocols.
277 std::map<NatProtocolType, std::shared_ptr<UPnPProtocol>> protocolList_;
278
279 // Port ranges for TCP and UDP (in that order).
280 std::map<PortType, std::pair<uint16_t, uint16_t>> portRange_ {};
281
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400282 // Minimum and maximum limits on the number of available
283 // mappings to keep in the list at any given time
284 static constexpr unsigned minAvailableMappings_[2] {4, 8};
285 static constexpr unsigned maxAvailableMappings_[2] {8, 12};
286 unsigned getMinAvailableMappings(PortType type) {
287 unsigned index = (type == PortType::TCP) ? 0 : 1;
288 return minAvailableMappings_[index];
289 }
290 unsigned getMaxAvailableMappings(PortType type) {
291 unsigned index = (type == PortType::TCP) ? 0 : 1;
292 return maxAvailableMappings_[index];
293 }
Adrien Béraud612b55b2023-05-29 10:42:04 -0400294
Adrien Béraud370257c2023-08-15 20:53:09 -0400295 std::shared_ptr<asio::io_context> ctx;
296 std::shared_ptr<dht::log::Logger> logger_;
Adrien Béraudc36965c2023-08-17 21:50:27 -0400297 asio::steady_timer connectivityChangedTimer_;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400298 asio::system_timer mappingRenewalTimer_;
299 asio::steady_timer renewalSchedulingTimer_;
300 asio::steady_timer syncTimer_;
301 std::mutex syncMutex_;
302 bool syncRequested_ {false};
Adrien Béraud612b55b2023-05-29 10:42:04 -0400303
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400304 // This mutex must lock only the members below. All other
305 // members must be accessed only from the UPnP context thread.
Adrien Béraud612b55b2023-05-29 10:42:04 -0400306 std::mutex mutable mappingMutex_;
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400307
Adrien Béraud612b55b2023-05-29 10:42:04 -0400308 // List of mappings.
309 std::map<Mapping::key_t, Mapping::sharedPtr_t> mappingList_[2] {};
François-Simon Fauteux-Chapleaufd29c1d2024-05-30 16:48:26 -0400310
311 // Current IGD. Can be null if there is no valid IGD.
312 std::shared_ptr<IGD> currentIgd_;
313
314 // Set of registered controllers
315 std::set<void*> controllerList_;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400316
317 // Shutdown synchronization
318 bool shutdownComplete_ {false};
François-Simon Fauteux-Chapleau808db4f2024-04-19 11:39:47 -0400319 bool shutdownTimedOut_ {false};
Amna0d215232024-08-27 17:57:45 -0400320
321 // IGD Discovery synchronization. This boolean indicates if the IGD discovery is in progress.
322 bool igdDiscoveryInProgress_ {true};
323 std::mutex igdDiscoveryMutex_;
324 std::chrono::milliseconds igdDiscoveryTimeout_ {std::chrono::milliseconds(500)};
325
326 // End of the discovery process.
327 void _endIgdDiscovery();
328
329 asio::steady_timer igdDiscoveryTimer_;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400330};
331
332} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -0400333} // namespace dhtnet