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
38bool 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
46int 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
93void 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
105char *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
109void 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
182int 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
234int UPNP::get_device_count() const {
235 return devices.size();
236}
237
238Ref<UPNPDevice> UPNP::get_device(int index) const {
239 ERR_FAIL_INDEX_V(index, devices.size(), nullptr);
240
241 return devices.get(index);
242}
243
244void UPNP::add_device(Ref<UPNPDevice> device) {
245 ERR_FAIL_COND(device == nullptr);
246
247 devices.push_back(device);
248}
249
250void 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
257void UPNP::remove_device(int index) {
258 ERR_FAIL_INDEX(index, devices.size());
259
260 devices.remove_at(index);
261}
262
263void UPNP::clear_devices() {
264 devices.clear();
265}
266
267Ref<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
281void UPNP::set_discover_multicast_if(const String &m_if) {
282 discover_multicast_if = m_if;
283}
284
285String UPNP::get_discover_multicast_if() const {
286 return discover_multicast_if;
287}
288
289void UPNP::set_discover_local_port(int port) {
290 discover_local_port = port;
291}
292
293int UPNP::get_discover_local_port() const {
294 return discover_local_port;
295}
296
297void UPNP::set_discover_ipv6(bool ipv6) {
298 discover_ipv6 = ipv6;
299}
300
301bool UPNP::is_discover_ipv6() const {
302 return discover_ipv6;
303}
304
305String 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
315int 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
325int 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
335void 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
395UPNP::UPNP() {
396}
397
398UPNP::~UPNP() {
399}
400