1 | /**************************************************************************/ |
2 | /* upnp.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "upnp.h" |
32 | |
33 | #include <miniwget.h> |
34 | #include <upnpcommands.h> |
35 | |
36 | #include <stdlib.h> |
37 | |
38 | bool UPNP::is_common_device(const String &dev) const { |
39 | return dev.is_empty() || |
40 | dev.find("InternetGatewayDevice" ) >= 0 || |
41 | dev.find("WANIPConnection" ) >= 0 || |
42 | dev.find("WANPPPConnection" ) >= 0 || |
43 | dev.find("rootdevice" ) >= 0; |
44 | } |
45 | |
46 | int UPNP::discover(int timeout, int ttl, const String &device_filter) { |
47 | ERR_FAIL_COND_V_MSG(timeout < 0, UPNP_RESULT_INVALID_PARAM, "The response's wait time can't be negative." ); |
48 | ERR_FAIL_COND_V_MSG(ttl < 0 || ttl > 255, UPNP_RESULT_INVALID_PARAM, "The time-to-live must be set between 0 and 255 (inclusive)." ); |
49 | |
50 | devices.clear(); |
51 | |
52 | int error = 0; |
53 | struct UPNPDev *devlist; |
54 | |
55 | CharString cs = discover_multicast_if.utf8(); |
56 | const char *m_if = cs.length() ? cs.get_data() : nullptr; |
57 | if (is_common_device(device_filter)) { |
58 | devlist = upnpDiscover(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error); |
59 | } else { |
60 | devlist = upnpDiscoverAll(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error); |
61 | } |
62 | |
63 | if (error != UPNPDISCOVER_SUCCESS) { |
64 | switch (error) { |
65 | case UPNPDISCOVER_SOCKET_ERROR: |
66 | return UPNP_RESULT_SOCKET_ERROR; |
67 | case UPNPDISCOVER_MEMORY_ERROR: |
68 | return UPNP_RESULT_MEM_ALLOC_ERROR; |
69 | default: |
70 | return UPNP_RESULT_UNKNOWN_ERROR; |
71 | } |
72 | } |
73 | |
74 | if (!devlist) { |
75 | return UPNP_RESULT_NO_DEVICES; |
76 | } |
77 | |
78 | struct UPNPDev *dev = devlist; |
79 | |
80 | while (dev) { |
81 | if (device_filter.is_empty() || strstr(dev->st, device_filter.utf8().get_data())) { |
82 | add_device_to_list(dev, devlist); |
83 | } |
84 | |
85 | dev = dev->pNext; |
86 | } |
87 | |
88 | freeUPNPDevlist(devlist); |
89 | |
90 | return UPNP_RESULT_SUCCESS; |
91 | } |
92 | |
93 | void UPNP::add_device_to_list(UPNPDev *dev, UPNPDev *devlist) { |
94 | Ref<UPNPDevice> new_device; |
95 | new_device.instantiate(); |
96 | |
97 | new_device->set_description_url(dev->descURL); |
98 | new_device->set_service_type(dev->st); |
99 | |
100 | parse_igd(new_device, devlist); |
101 | |
102 | devices.push_back(new_device); |
103 | } |
104 | |
105 | char *UPNP::load_description(const String &url, int *size, int *status_code) const { |
106 | return (char *)miniwget(url.utf8().get_data(), size, 0, status_code); |
107 | } |
108 | |
109 | void UPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) { |
110 | int size = 0; |
111 | int status_code = -1; |
112 | char *xml = load_description(dev->get_description_url(), &size, &status_code); |
113 | |
114 | if (status_code != 200) { |
115 | dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_ERROR); |
116 | return; |
117 | } |
118 | |
119 | if (!xml || size < 1) { |
120 | dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_EMPTY); |
121 | return; |
122 | } |
123 | |
124 | struct UPNPUrls *urls = (UPNPUrls *)malloc(sizeof(struct UPNPUrls)); |
125 | |
126 | if (!urls) { |
127 | dev->set_igd_status(UPNPDevice::IGD_STATUS_MALLOC_ERROR); |
128 | return; |
129 | } |
130 | |
131 | struct IGDdatas data; |
132 | |
133 | memset(urls, 0, sizeof(struct UPNPUrls)); |
134 | |
135 | parserootdesc(xml, size, &data); |
136 | free(xml); |
137 | xml = nullptr; |
138 | |
139 | GetUPNPUrls(urls, &data, dev->get_description_url().utf8().get_data(), 0); |
140 | |
141 | if (!urls) { |
142 | dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_URLS); |
143 | return; |
144 | } |
145 | |
146 | char addr[16]; |
147 | int i = UPNP_GetValidIGD(devlist, urls, &data, (char *)&addr, 16); |
148 | |
149 | if (i != 1) { |
150 | FreeUPNPUrls(urls); |
151 | |
152 | switch (i) { |
153 | case 0: |
154 | dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_IGD); |
155 | return; |
156 | case 2: |
157 | dev->set_igd_status(UPNPDevice::IGD_STATUS_DISCONNECTED); |
158 | return; |
159 | case 3: |
160 | dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_DEVICE); |
161 | return; |
162 | default: |
163 | dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_ERROR); |
164 | return; |
165 | } |
166 | } |
167 | |
168 | if (urls->controlURL[0] == '\0') { |
169 | FreeUPNPUrls(urls); |
170 | dev->set_igd_status(UPNPDevice::IGD_STATUS_INVALID_CONTROL); |
171 | return; |
172 | } |
173 | |
174 | dev->set_igd_control_url(urls->controlURL); |
175 | dev->set_igd_service_type(data.first.servicetype); |
176 | dev->set_igd_our_addr(addr); |
177 | dev->set_igd_status(UPNPDevice::IGD_STATUS_OK); |
178 | |
179 | FreeUPNPUrls(urls); |
180 | } |
181 | |
182 | int UPNP::upnp_result(int in) { |
183 | switch (in) { |
184 | case UPNPCOMMAND_SUCCESS: |
185 | return UPNP_RESULT_SUCCESS; |
186 | case UPNPCOMMAND_UNKNOWN_ERROR: |
187 | return UPNP_RESULT_UNKNOWN_ERROR; |
188 | case UPNPCOMMAND_INVALID_ARGS: |
189 | return UPNP_RESULT_INVALID_ARGS; |
190 | case UPNPCOMMAND_HTTP_ERROR: |
191 | return UPNP_RESULT_HTTP_ERROR; |
192 | case UPNPCOMMAND_INVALID_RESPONSE: |
193 | return UPNP_RESULT_INVALID_RESPONSE; |
194 | case UPNPCOMMAND_MEM_ALLOC_ERROR: |
195 | return UPNP_RESULT_MEM_ALLOC_ERROR; |
196 | |
197 | case 402: |
198 | return UPNP_RESULT_INVALID_ARGS; |
199 | case 403: |
200 | return UPNP_RESULT_NOT_AUTHORIZED; |
201 | case 501: |
202 | return UPNP_RESULT_ACTION_FAILED; |
203 | case 606: |
204 | return UPNP_RESULT_NOT_AUTHORIZED; |
205 | case 714: |
206 | return UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY; |
207 | case 715: |
208 | return UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED; |
209 | case 716: |
210 | return UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED; |
211 | case 718: |
212 | return UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING; |
213 | case 724: |
214 | return UPNP_RESULT_SAME_PORT_VALUES_REQUIRED; |
215 | case 725: |
216 | return UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED; |
217 | case 726: |
218 | return UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD; |
219 | case 727: |
220 | return UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD; |
221 | case 728: |
222 | return UPNP_RESULT_NO_PORT_MAPS_AVAILABLE; |
223 | case 729: |
224 | return UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM; |
225 | case 732: |
226 | return UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED; |
227 | case 733: |
228 | return UPNP_RESULT_INCONSISTENT_PARAMETERS; |
229 | } |
230 | |
231 | return UPNP_RESULT_UNKNOWN_ERROR; |
232 | } |
233 | |
234 | int UPNP::get_device_count() const { |
235 | return devices.size(); |
236 | } |
237 | |
238 | Ref<UPNPDevice> UPNP::get_device(int index) const { |
239 | ERR_FAIL_INDEX_V(index, devices.size(), nullptr); |
240 | |
241 | return devices.get(index); |
242 | } |
243 | |
244 | void UPNP::add_device(Ref<UPNPDevice> device) { |
245 | ERR_FAIL_COND(device == nullptr); |
246 | |
247 | devices.push_back(device); |
248 | } |
249 | |
250 | void UPNP::set_device(int index, Ref<UPNPDevice> device) { |
251 | ERR_FAIL_INDEX(index, devices.size()); |
252 | ERR_FAIL_COND(device == nullptr); |
253 | |
254 | devices.set(index, device); |
255 | } |
256 | |
257 | void UPNP::remove_device(int index) { |
258 | ERR_FAIL_INDEX(index, devices.size()); |
259 | |
260 | devices.remove_at(index); |
261 | } |
262 | |
263 | void UPNP::clear_devices() { |
264 | devices.clear(); |
265 | } |
266 | |
267 | Ref<UPNPDevice> UPNP::get_gateway() const { |
268 | ERR_FAIL_COND_V_MSG(devices.size() < 1, nullptr, "Couldn't find any UPNPDevices." ); |
269 | |
270 | for (int i = 0; i < devices.size(); i++) { |
271 | Ref<UPNPDevice> dev = get_device(i); |
272 | |
273 | if (dev != nullptr && dev->is_valid_gateway()) { |
274 | return dev; |
275 | } |
276 | } |
277 | |
278 | return nullptr; |
279 | } |
280 | |
281 | void UPNP::set_discover_multicast_if(const String &m_if) { |
282 | discover_multicast_if = m_if; |
283 | } |
284 | |
285 | String UPNP::get_discover_multicast_if() const { |
286 | return discover_multicast_if; |
287 | } |
288 | |
289 | void UPNP::set_discover_local_port(int port) { |
290 | discover_local_port = port; |
291 | } |
292 | |
293 | int UPNP::get_discover_local_port() const { |
294 | return discover_local_port; |
295 | } |
296 | |
297 | void UPNP::set_discover_ipv6(bool ipv6) { |
298 | discover_ipv6 = ipv6; |
299 | } |
300 | |
301 | bool UPNP::is_discover_ipv6() const { |
302 | return discover_ipv6; |
303 | } |
304 | |
305 | String UPNP::query_external_address() const { |
306 | Ref<UPNPDevice> dev = get_gateway(); |
307 | |
308 | if (dev == nullptr) { |
309 | return "" ; |
310 | } |
311 | |
312 | return dev->query_external_address(); |
313 | } |
314 | |
315 | int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { |
316 | Ref<UPNPDevice> dev = get_gateway(); |
317 | |
318 | if (dev == nullptr) { |
319 | return UPNP_RESULT_NO_GATEWAY; |
320 | } |
321 | |
322 | return dev->add_port_mapping(port, port_internal, desc, proto, duration); |
323 | } |
324 | |
325 | int UPNP::delete_port_mapping(int port, String proto) const { |
326 | Ref<UPNPDevice> dev = get_gateway(); |
327 | |
328 | if (dev == nullptr) { |
329 | return UPNP_RESULT_NO_GATEWAY; |
330 | } |
331 | |
332 | return dev->delete_port_mapping(port, proto); |
333 | } |
334 | |
335 | void UPNP::_bind_methods() { |
336 | ClassDB::bind_method(D_METHOD("get_device_count" ), &UPNP::get_device_count); |
337 | ClassDB::bind_method(D_METHOD("get_device" , "index" ), &UPNP::get_device); |
338 | ClassDB::bind_method(D_METHOD("add_device" , "device" ), &UPNP::add_device); |
339 | ClassDB::bind_method(D_METHOD("set_device" , "index" , "device" ), &UPNP::set_device); |
340 | ClassDB::bind_method(D_METHOD("remove_device" , "index" ), &UPNP::remove_device); |
341 | ClassDB::bind_method(D_METHOD("clear_devices" ), &UPNP::clear_devices); |
342 | |
343 | ClassDB::bind_method(D_METHOD("get_gateway" ), &UPNP::get_gateway); |
344 | |
345 | ClassDB::bind_method(D_METHOD("discover" , "timeout" , "ttl" , "device_filter" ), &UPNP::discover, DEFVAL(2000), DEFVAL(2), DEFVAL("InternetGatewayDevice" )); |
346 | |
347 | ClassDB::bind_method(D_METHOD("query_external_address" ), &UPNP::query_external_address); |
348 | |
349 | ClassDB::bind_method(D_METHOD("add_port_mapping" , "port" , "port_internal" , "desc" , "proto" , "duration" ), &UPNP::add_port_mapping, DEFVAL(0), DEFVAL("" ), DEFVAL("UDP" ), DEFVAL(0)); |
350 | ClassDB::bind_method(D_METHOD("delete_port_mapping" , "port" , "proto" ), &UPNP::delete_port_mapping, DEFVAL("UDP" )); |
351 | |
352 | ClassDB::bind_method(D_METHOD("set_discover_multicast_if" , "m_if" ), &UPNP::set_discover_multicast_if); |
353 | ClassDB::bind_method(D_METHOD("get_discover_multicast_if" ), &UPNP::get_discover_multicast_if); |
354 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "discover_multicast_if" ), "set_discover_multicast_if" , "get_discover_multicast_if" ); |
355 | |
356 | ClassDB::bind_method(D_METHOD("set_discover_local_port" , "port" ), &UPNP::set_discover_local_port); |
357 | ClassDB::bind_method(D_METHOD("get_discover_local_port" ), &UPNP::get_discover_local_port); |
358 | ADD_PROPERTY(PropertyInfo(Variant::INT, "discover_local_port" , PROPERTY_HINT_RANGE, "0,65535" ), "set_discover_local_port" , "get_discover_local_port" ); |
359 | |
360 | ClassDB::bind_method(D_METHOD("set_discover_ipv6" , "ipv6" ), &UPNP::set_discover_ipv6); |
361 | ClassDB::bind_method(D_METHOD("is_discover_ipv6" ), &UPNP::is_discover_ipv6); |
362 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "discover_ipv6" ), "set_discover_ipv6" , "is_discover_ipv6" ); |
363 | |
364 | BIND_ENUM_CONSTANT(UPNP_RESULT_SUCCESS); |
365 | BIND_ENUM_CONSTANT(UPNP_RESULT_NOT_AUTHORIZED); |
366 | BIND_ENUM_CONSTANT(UPNP_RESULT_PORT_MAPPING_NOT_FOUND); |
367 | BIND_ENUM_CONSTANT(UPNP_RESULT_INCONSISTENT_PARAMETERS); |
368 | BIND_ENUM_CONSTANT(UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY); |
369 | BIND_ENUM_CONSTANT(UPNP_RESULT_ACTION_FAILED); |
370 | BIND_ENUM_CONSTANT(UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED); |
371 | BIND_ENUM_CONSTANT(UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED); |
372 | BIND_ENUM_CONSTANT(UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED); |
373 | BIND_ENUM_CONSTANT(UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD); |
374 | BIND_ENUM_CONSTANT(UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD); |
375 | BIND_ENUM_CONSTANT(UPNP_RESULT_NO_PORT_MAPS_AVAILABLE); |
376 | BIND_ENUM_CONSTANT(UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM); |
377 | BIND_ENUM_CONSTANT(UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING); |
378 | BIND_ENUM_CONSTANT(UPNP_RESULT_SAME_PORT_VALUES_REQUIRED); |
379 | BIND_ENUM_CONSTANT(UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED); |
380 | BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_GATEWAY); |
381 | BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PORT); |
382 | BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PROTOCOL); |
383 | BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_DURATION); |
384 | BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_ARGS); |
385 | BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_RESPONSE); |
386 | BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PARAM); |
387 | BIND_ENUM_CONSTANT(UPNP_RESULT_HTTP_ERROR); |
388 | BIND_ENUM_CONSTANT(UPNP_RESULT_SOCKET_ERROR); |
389 | BIND_ENUM_CONSTANT(UPNP_RESULT_MEM_ALLOC_ERROR); |
390 | BIND_ENUM_CONSTANT(UPNP_RESULT_NO_GATEWAY); |
391 | BIND_ENUM_CONSTANT(UPNP_RESULT_NO_DEVICES); |
392 | BIND_ENUM_CONSTANT(UPNP_RESULT_UNKNOWN_ERROR); |
393 | } |
394 | |
395 | UPNP::UPNP() { |
396 | } |
397 | |
398 | UPNP::~UPNP() { |
399 | } |
400 | |