| 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 | |