blob: 7deb4fba43788627cc474271f96944ef3229eea7 [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
19#ifdef _WIN32
20#define UPNP_USE_MSVCPP
21#define UPNP_STATIC_LIB
22#endif
23
24#include "../upnp_protocol.h"
25#include "../igd.h"
26#include "upnp_igd.h"
Adrien Béraud370257c2023-08-15 20:53:09 -040027#include "ip_utils.h"
Adrien Béraud612b55b2023-05-29 10:42:04 -040028
29#include <upnp/upnp.h>
30#include <upnp/upnptools.h>
31
32#ifdef _WIN32
33#include <windows.h>
34#include <wincrypt.h>
35#endif
36
37#include <atomic>
38#include <thread>
39#include <list>
40#include <map>
41#include <set>
42#include <string>
43#include <memory>
44#include <future>
45
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040046namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040047class IpAddr;
48}
49
Adrien Béraud1ae60aa2023-07-07 09:55:09 -040050namespace dhtnet {
Adrien Béraud612b55b2023-05-29 10:42:04 -040051namespace upnp {
52
53class PUPnP : public UPnPProtocol
54{
55public:
56 using XMLDocument = std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&>;
57
58 enum class CtrlAction {
59 UNKNOWN,
60 ADD_PORT_MAPPING,
61 DELETE_PORT_MAPPING,
62 GET_GENERIC_PORT_MAPPING_ENTRY,
63 GET_STATUS_INFO,
64 GET_EXTERNAL_IP_ADDRESS
65 };
66
Adrien Béraud370257c2023-08-15 20:53:09 -040067 PUPnP(const std::shared_ptr<asio::io_context>& ctx, const std::shared_ptr<dht::log::Logger>& logger);
Adrien Béraud612b55b2023-05-29 10:42:04 -040068 ~PUPnP();
69
70 // Set the observer
71 void setObserver(UpnpMappingObserver* obs) override;
72
73 // Returns the protocol type.
74 NatProtocolType getProtocol() const override { return NatProtocolType::PUPNP; }
75
76 // Get protocol type as string.
77 char const* getProtocolName() const override { return "PUPNP"; }
78
79 // Notifies a change in network.
80 void clearIgds() override;
81
82 // Sends out async search for IGD.
83 void searchForIgd() override;
84
85 // Get the IGD list.
86 std::list<std::shared_ptr<IGD>> getIgdList() const override;
87
88 // Return true if the it's fully setup.
89 bool isReady() const override;
90
91 // Get from the IGD the list of already allocated mappings if any.
92 std::map<Mapping::key_t, Mapping> getMappingsListByDescr(
93 const std::shared_ptr<IGD>& igd, const std::string& descr) const override;
94
95 // Request a new mapping.
96 void requestMappingAdd(const Mapping& mapping) override;
97
98 // Renew an allocated mapping.
99 // Not implemented. Currently, UPNP allocations do not have expiration time.
100 void requestMappingRenew([[maybe_unused]] const Mapping& mapping) override { assert(false); };
101
102 // Removes a mapping.
103 void requestMappingRemove(const Mapping& igdMapping) override;
104
105 // Get the host (local) address.
106 const IpAddr getHostAddress() const override;
107
108 // Terminate the instance.
109 void terminate() override;
110
111private:
Adrien Béraud370257c2023-08-15 20:53:09 -0400112 PUPnP& operator=(const PUPnP&) = delete;
113 PUPnP(const PUPnP&) = delete;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400114
115 void terminate(std::condition_variable& cv);
116
117 // Init lib-upnp
118 void initUpnpLib();
119
120 // Return true if running.
121 bool isRunning() const;
122
123 // Register the client
124 void registerClient();
125
126 // Start search for UPNP devices
127 void searchForDevices();
128
129 // Return true if it has at least one valid IGD.
130 bool hasValidIgd() const;
131
132 // Update the host (local) address.
133 void updateHostAddress();
134
135 // Check the host (local) address.
136 // Returns true if the address is valid.
137 bool hasValidHostAddress();
138
139 // Delete mappings matching the description
140 void deleteMappingsByDescription(const std::shared_ptr<IGD>& igd,
141 const std::string& description);
142
143 // Search for the IGD in the local list of known IGDs.
144 std::shared_ptr<UPnPIGD> findMatchingIgd(const std::string& ctrlURL) const;
145
146 // Process the reception of an add mapping action answer.
147 void processAddMapAction(const Mapping& map);
148
149 // Process the a mapping request failure.
150 void processRequestMappingFailure(const Mapping& map);
151
152 // Process the reception of a remove mapping action answer.
153 void processRemoveMapAction(const Mapping& map);
154
155 // Increment IGD errors counter.
156 void incrementErrorsCounter(const std::shared_ptr<IGD>& igd);
157
158 // Download XML document.
159 void downLoadIgdDescription(const std::string& url);
160
161 // Validate IGD from the xml document received from the router.
162 bool validateIgd(const std::string& location, IXML_Document* doc_container_ptr);
163
164 // Returns control point action callback based on xml node.
165 static CtrlAction getAction(const char* xmlNode);
166
167 // Control point callback.
168 static int ctrlPtCallback(Upnp_EventType event_type, const void* event, void* user_data);
169#if UPNP_VERSION < 10800
170 static inline int ctrlPtCallback(Upnp_EventType event_type, void* event, void* user_data)
171 {
172 return ctrlPtCallback(event_type, (const void*) event, user_data);
173 };
174#endif
175 // Process IGD responses.
176 void processDiscoverySearchResult(const std::string& deviceId,
177 const std::string& igdUrl,
178 const IpAddr& dstAddr);
179 void processDiscoveryAdvertisementByebye(const std::string& deviceId);
180 void processDiscoverySubscriptionExpired(Upnp_EventType event_type,
181 const std::string& eventSubUrl);
182
183 // Callback event handler function for the UPnP client (control point).
184 int handleCtrlPtUPnPEvents(Upnp_EventType event_type, const void* event);
185
186 // Subscription event callback.
187 static int subEventCallback(Upnp_EventType event_type, const void* event, void* user_data);
188#if UPNP_VERSION < 10800
189 static inline int subEventCallback(Upnp_EventType event_type, void* event, void* user_data)
190 {
191 return subEventCallback(event_type, (const void*) event, user_data);
192 };
193#endif
194
195 // Callback subscription event function for handling subscription request.
196 int handleSubscriptionUPnPEvent(Upnp_EventType event_type, const void* event);
197
198 // Parses the IGD candidate.
199 std::unique_ptr<UPnPIGD> parseIgd(IXML_Document* doc, std::string locationUrl);
200
201 // These functions directly create UPnP actions and make synchronous UPnP
202 // control point calls. Must be run on the PUPNP internal execution queue.
203 bool actionIsIgdConnected(const UPnPIGD& igd);
204 IpAddr actionGetExternalIP(const UPnPIGD& igd);
205 bool actionAddPortMapping(const Mapping& mapping);
206 bool actionDeletePortMapping(const Mapping& mapping);
207
208 // Event type to string
209 static const char* eventTypeToString(Upnp_EventType eventType);
210
211 std::weak_ptr<PUPnP> weak() { return std::static_pointer_cast<PUPnP>(shared_from_this()); }
212
Adrien Béraud612b55b2023-05-29 10:42:04 -0400213 // Initialization status.
214 std::atomic_bool initialized_ {false};
215 // Client registration status.
216 std::atomic_bool clientRegistered_ {false};
217
Adrien Béraud370257c2023-08-15 20:53:09 -0400218 std::shared_ptr<asio::io_context> ioContext;
219 asio::steady_timer searchForIgdTimer_;
Adrien Béraud612b55b2023-05-29 10:42:04 -0400220 unsigned int igdSearchCounter_ {0};
221
222 // List of discovered IGDs.
223 std::set<std::string> discoveredIgdList_;
224
225 // Control point handle.
226 UpnpClient_Handle ctrlptHandle_ {-1};
227
228 // Observer to report the results.
229 UpnpMappingObserver* observer_ {nullptr};
230
231 // List of valid IGDs.
232 std::list<std::shared_ptr<IGD>> validIgdList_;
233
234 // Current host address.
235 IpAddr hostAddress_ {};
236
237 // Calls from other threads that does not need synchronous access are
238 // rescheduled on the UPNP private queue. This will avoid the need to
239 // protect most of the data members of this class.
240 // For some internal members (namely the validIgdList and the hostAddress)
241 // that need to be synchronously accessed, are protected by this mutex.
242 mutable std::mutex pupnpMutex_;
243
244 // Shutdown synchronization
245 bool shutdownComplete_ {false};
Sébastien Blind14fc352023-10-06 15:21:53 -0400246
247 // Count ongoing operations
248 std::mutex ongoingOpsMtx_;
249 std::condition_variable cvOngoing_;
250 int ongoingOps_ {0};
251 bool destroying_ {false};
Adrien Béraud612b55b2023-05-29 10:42:04 -0400252};
253
254} // namespace upnp
Sébastien Blin464bdff2023-07-19 08:02:53 -0400255} // namespace dhtnet