mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-31 05:31:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			401 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*************************************************************************/
 | |
| /*  upnp.cpp                                                             */
 | |
| /*************************************************************************/
 | |
| /*                       This file is part of:                           */
 | |
| /*                           GODOT ENGINE                                */
 | |
| /*                      https://godotengine.org                          */
 | |
| /*************************************************************************/
 | |
| /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur.                 */
 | |
| /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md).   */
 | |
| /*                                                                       */
 | |
| /* Permission is hereby granted, free of charge, to any person obtaining */
 | |
| /* a copy of this software and associated documentation files (the       */
 | |
| /* "Software"), to deal in the Software without restriction, including   */
 | |
| /* without limitation the rights to use, copy, modify, merge, publish,   */
 | |
| /* distribute, sublicense, and/or sell copies of the Software, and to    */
 | |
| /* permit persons to whom the Software is furnished to do so, subject to */
 | |
| /* the following conditions:                                             */
 | |
| /*                                                                       */
 | |
| /* The above copyright notice and this permission notice shall be        */
 | |
| /* included in all copies or substantial portions of the Software.       */
 | |
| /*                                                                       */
 | |
| /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
 | |
| /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
 | |
| /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
 | |
| /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
 | |
| /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
 | |
| /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
 | |
| /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
 | |
| /*************************************************************************/
 | |
| 
 | |
| #include "upnp.h"
 | |
| 
 | |
| #include <miniupnpc/miniwget.h>
 | |
| #include <miniupnpc/upnpcommands.h>
 | |
| 
 | |
| #include <stdlib.h>
 | |
| 
 | |
| bool UPNP::is_common_device(const String &dev) const {
 | |
| 	return dev.is_empty() ||
 | |
| 		   dev.find("InternetGatewayDevice") >= 0 ||
 | |
| 		   dev.find("WANIPConnection") >= 0 ||
 | |
| 		   dev.find("WANPPPConnection") >= 0 ||
 | |
| 		   dev.find("rootdevice") >= 0;
 | |
| }
 | |
| 
 | |
| int UPNP::discover(int timeout, int ttl, const String &device_filter) {
 | |
| 	ERR_FAIL_COND_V_MSG(timeout < 0, UPNP_RESULT_INVALID_PARAM, "The response's wait time can't be negative.");
 | |
| 	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).");
 | |
| 
 | |
| 	devices.clear();
 | |
| 
 | |
| 	int error = 0;
 | |
| 	struct UPNPDev *devlist;
 | |
| 
 | |
| 	CharString cs = discover_multicast_if.utf8();
 | |
| 	const char *m_if = cs.length() ? cs.get_data() : nullptr;
 | |
| 	if (is_common_device(device_filter)) {
 | |
| 		devlist = upnpDiscover(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error);
 | |
| 	} else {
 | |
| 		devlist = upnpDiscoverAll(timeout, m_if, nullptr, discover_local_port, discover_ipv6, ttl, &error);
 | |
| 	}
 | |
| 
 | |
| 	if (error != UPNPDISCOVER_SUCCESS) {
 | |
| 		switch (error) {
 | |
| 			case UPNPDISCOVER_SOCKET_ERROR:
 | |
| 				return UPNP_RESULT_SOCKET_ERROR;
 | |
| 			case UPNPDISCOVER_MEMORY_ERROR:
 | |
| 				return UPNP_RESULT_MEM_ALLOC_ERROR;
 | |
| 			default:
 | |
| 				return UPNP_RESULT_UNKNOWN_ERROR;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (!devlist) {
 | |
| 		return UPNP_RESULT_NO_DEVICES;
 | |
| 	}
 | |
| 
 | |
| 	struct UPNPDev *dev = devlist;
 | |
| 
 | |
| 	while (dev) {
 | |
| 		if (device_filter.is_empty() || strstr(dev->st, device_filter.utf8().get_data())) {
 | |
| 			add_device_to_list(dev, devlist);
 | |
| 		}
 | |
| 
 | |
| 		dev = dev->pNext;
 | |
| 	}
 | |
| 
 | |
| 	freeUPNPDevlist(devlist);
 | |
| 
 | |
| 	return UPNP_RESULT_SUCCESS;
 | |
| }
 | |
| 
 | |
| void UPNP::add_device_to_list(UPNPDev *dev, UPNPDev *devlist) {
 | |
| 	Ref<UPNPDevice> new_device;
 | |
| 	new_device.instantiate();
 | |
| 
 | |
| 	new_device->set_description_url(dev->descURL);
 | |
| 	new_device->set_service_type(dev->st);
 | |
| 
 | |
| 	parse_igd(new_device, devlist);
 | |
| 
 | |
| 	devices.push_back(new_device);
 | |
| }
 | |
| 
 | |
| char *UPNP::load_description(const String &url, int *size, int *status_code) const {
 | |
| 	return (char *)miniwget(url.utf8().get_data(), size, 0, status_code);
 | |
| }
 | |
| 
 | |
| void UPNP::parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist) {
 | |
| 	int size = 0;
 | |
| 	int status_code = -1;
 | |
| 	char *xml = load_description(dev->get_description_url(), &size, &status_code);
 | |
| 
 | |
| 	if (status_code != 200) {
 | |
| 		dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_ERROR);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	if (!xml || size < 1) {
 | |
| 		dev->set_igd_status(UPNPDevice::IGD_STATUS_HTTP_EMPTY);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	struct UPNPUrls *urls = (UPNPUrls *)malloc(sizeof(struct UPNPUrls));
 | |
| 
 | |
| 	if (!urls) {
 | |
| 		dev->set_igd_status(UPNPDevice::IGD_STATUS_MALLOC_ERROR);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	struct IGDdatas data;
 | |
| 
 | |
| 	memset(urls, 0, sizeof(struct UPNPUrls));
 | |
| 
 | |
| 	parserootdesc(xml, size, &data);
 | |
| 	free(xml);
 | |
| 	xml = nullptr;
 | |
| 
 | |
| 	GetUPNPUrls(urls, &data, dev->get_description_url().utf8().get_data(), 0);
 | |
| 
 | |
| 	if (!urls) {
 | |
| 		dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_URLS);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	char addr[16];
 | |
| 	int i = UPNP_GetValidIGD(devlist, urls, &data, (char *)&addr, 16);
 | |
| 
 | |
| 	if (i != 1) {
 | |
| 		FreeUPNPUrls(urls);
 | |
| 
 | |
| 		switch (i) {
 | |
| 			case 0:
 | |
| 				dev->set_igd_status(UPNPDevice::IGD_STATUS_NO_IGD);
 | |
| 				return;
 | |
| 			case 2:
 | |
| 				dev->set_igd_status(UPNPDevice::IGD_STATUS_DISCONNECTED);
 | |
| 				return;
 | |
| 			case 3:
 | |
| 				dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_DEVICE);
 | |
| 				return;
 | |
| 			default:
 | |
| 				dev->set_igd_status(UPNPDevice::IGD_STATUS_UNKNOWN_ERROR);
 | |
| 				return;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (urls->controlURL[0] == '\0') {
 | |
| 		FreeUPNPUrls(urls);
 | |
| 		dev->set_igd_status(UPNPDevice::IGD_STATUS_INVALID_CONTROL);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	dev->set_igd_control_url(urls->controlURL);
 | |
| 	dev->set_igd_service_type(data.first.servicetype);
 | |
| 	dev->set_igd_our_addr(addr);
 | |
| 	dev->set_igd_status(UPNPDevice::IGD_STATUS_OK);
 | |
| 
 | |
| 	FreeUPNPUrls(urls);
 | |
| }
 | |
| 
 | |
| int UPNP::upnp_result(int in) {
 | |
| 	switch (in) {
 | |
| 		case UPNPCOMMAND_SUCCESS:
 | |
| 			return UPNP_RESULT_SUCCESS;
 | |
| 		case UPNPCOMMAND_UNKNOWN_ERROR:
 | |
| 			return UPNP_RESULT_UNKNOWN_ERROR;
 | |
| 		case UPNPCOMMAND_INVALID_ARGS:
 | |
| 			return UPNP_RESULT_INVALID_ARGS;
 | |
| 		case UPNPCOMMAND_HTTP_ERROR:
 | |
| 			return UPNP_RESULT_HTTP_ERROR;
 | |
| 		case UPNPCOMMAND_INVALID_RESPONSE:
 | |
| 			return UPNP_RESULT_INVALID_RESPONSE;
 | |
| 		case UPNPCOMMAND_MEM_ALLOC_ERROR:
 | |
| 			return UPNP_RESULT_MEM_ALLOC_ERROR;
 | |
| 
 | |
| 		case 402:
 | |
| 			return UPNP_RESULT_INVALID_ARGS;
 | |
| 		case 403:
 | |
| 			return UPNP_RESULT_NOT_AUTHORIZED;
 | |
| 		case 501:
 | |
| 			return UPNP_RESULT_ACTION_FAILED;
 | |
| 		case 606:
 | |
| 			return UPNP_RESULT_NOT_AUTHORIZED;
 | |
| 		case 714:
 | |
| 			return UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY;
 | |
| 		case 715:
 | |
| 			return UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED;
 | |
| 		case 716:
 | |
| 			return UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED;
 | |
| 		case 718:
 | |
| 			return UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING;
 | |
| 		case 724:
 | |
| 			return UPNP_RESULT_SAME_PORT_VALUES_REQUIRED;
 | |
| 		case 725:
 | |
| 			return UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED;
 | |
| 		case 726:
 | |
| 			return UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD;
 | |
| 		case 727:
 | |
| 			return UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD;
 | |
| 		case 728:
 | |
| 			return UPNP_RESULT_NO_PORT_MAPS_AVAILABLE;
 | |
| 		case 729:
 | |
| 			return UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM;
 | |
| 		case 732:
 | |
| 			return UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED;
 | |
| 		case 733:
 | |
| 			return UPNP_RESULT_INCONSISTENT_PARAMETERS;
 | |
| 	}
 | |
| 
 | |
| 	return UPNP_RESULT_UNKNOWN_ERROR;
 | |
| }
 | |
| 
 | |
| int UPNP::get_device_count() const {
 | |
| 	return devices.size();
 | |
| }
 | |
| 
 | |
| Ref<UPNPDevice> UPNP::get_device(int index) const {
 | |
| 	ERR_FAIL_INDEX_V(index, devices.size(), nullptr);
 | |
| 
 | |
| 	return devices.get(index);
 | |
| }
 | |
| 
 | |
| void UPNP::add_device(Ref<UPNPDevice> device) {
 | |
| 	ERR_FAIL_COND(device == nullptr);
 | |
| 
 | |
| 	devices.push_back(device);
 | |
| }
 | |
| 
 | |
| void UPNP::set_device(int index, Ref<UPNPDevice> device) {
 | |
| 	ERR_FAIL_INDEX(index, devices.size());
 | |
| 	ERR_FAIL_COND(device == nullptr);
 | |
| 
 | |
| 	devices.set(index, device);
 | |
| }
 | |
| 
 | |
| void UPNP::remove_device(int index) {
 | |
| 	ERR_FAIL_INDEX(index, devices.size());
 | |
| 
 | |
| 	devices.remove(index);
 | |
| }
 | |
| 
 | |
| void UPNP::clear_devices() {
 | |
| 	devices.clear();
 | |
| }
 | |
| 
 | |
| Ref<UPNPDevice> UPNP::get_gateway() const {
 | |
| 	ERR_FAIL_COND_V_MSG(devices.size() < 1, nullptr, "Couldn't find any UPNPDevices.");
 | |
| 
 | |
| 	for (int i = 0; i < devices.size(); i++) {
 | |
| 		Ref<UPNPDevice> dev = get_device(i);
 | |
| 
 | |
| 		if (dev != nullptr && dev->is_valid_gateway()) {
 | |
| 			return dev;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nullptr;
 | |
| }
 | |
| 
 | |
| void UPNP::set_discover_multicast_if(const String &m_if) {
 | |
| 	discover_multicast_if = m_if;
 | |
| }
 | |
| 
 | |
| String UPNP::get_discover_multicast_if() const {
 | |
| 	return discover_multicast_if;
 | |
| }
 | |
| 
 | |
| void UPNP::set_discover_local_port(int port) {
 | |
| 	discover_local_port = port;
 | |
| }
 | |
| 
 | |
| int UPNP::get_discover_local_port() const {
 | |
| 	return discover_local_port;
 | |
| }
 | |
| 
 | |
| void UPNP::set_discover_ipv6(bool ipv6) {
 | |
| 	discover_ipv6 = ipv6;
 | |
| }
 | |
| 
 | |
| bool UPNP::is_discover_ipv6() const {
 | |
| 	return discover_ipv6;
 | |
| }
 | |
| 
 | |
| String UPNP::query_external_address() const {
 | |
| 	Ref<UPNPDevice> dev = get_gateway();
 | |
| 
 | |
| 	if (dev == nullptr) {
 | |
| 		return "";
 | |
| 	}
 | |
| 
 | |
| 	return dev->query_external_address();
 | |
| }
 | |
| 
 | |
| int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const {
 | |
| 	Ref<UPNPDevice> dev = get_gateway();
 | |
| 
 | |
| 	if (dev == nullptr) {
 | |
| 		return UPNP_RESULT_NO_GATEWAY;
 | |
| 	}
 | |
| 
 | |
| 	dev->delete_port_mapping(port, proto);
 | |
| 
 | |
| 	return dev->add_port_mapping(port, port_internal, desc, proto, duration);
 | |
| }
 | |
| 
 | |
| int UPNP::delete_port_mapping(int port, String proto) const {
 | |
| 	Ref<UPNPDevice> dev = get_gateway();
 | |
| 
 | |
| 	if (dev == nullptr) {
 | |
| 		return UPNP_RESULT_NO_GATEWAY;
 | |
| 	}
 | |
| 
 | |
| 	return dev->delete_port_mapping(port, proto);
 | |
| }
 | |
| 
 | |
| void UPNP::_bind_methods() {
 | |
| 	ClassDB::bind_method(D_METHOD("get_device_count"), &UPNP::get_device_count);
 | |
| 	ClassDB::bind_method(D_METHOD("get_device", "index"), &UPNP::get_device);
 | |
| 	ClassDB::bind_method(D_METHOD("add_device", "device"), &UPNP::add_device);
 | |
| 	ClassDB::bind_method(D_METHOD("set_device", "index", "device"), &UPNP::set_device);
 | |
| 	ClassDB::bind_method(D_METHOD("remove_device", "index"), &UPNP::remove_device);
 | |
| 	ClassDB::bind_method(D_METHOD("clear_devices"), &UPNP::clear_devices);
 | |
| 
 | |
| 	ClassDB::bind_method(D_METHOD("get_gateway"), &UPNP::get_gateway);
 | |
| 
 | |
| 	ClassDB::bind_method(D_METHOD("discover", "timeout", "ttl", "device_filter"), &UPNP::discover, DEFVAL(2000), DEFVAL(2), DEFVAL("InternetGatewayDevice"));
 | |
| 
 | |
| 	ClassDB::bind_method(D_METHOD("query_external_address"), &UPNP::query_external_address);
 | |
| 
 | |
| 	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));
 | |
| 	ClassDB::bind_method(D_METHOD("delete_port_mapping", "port", "proto"), &UPNP::delete_port_mapping, DEFVAL("UDP"));
 | |
| 
 | |
| 	ClassDB::bind_method(D_METHOD("set_discover_multicast_if", "m_if"), &UPNP::set_discover_multicast_if);
 | |
| 	ClassDB::bind_method(D_METHOD("get_discover_multicast_if"), &UPNP::get_discover_multicast_if);
 | |
| 	ADD_PROPERTY(PropertyInfo(Variant::STRING, "discover_multicast_if"), "set_discover_multicast_if", "get_discover_multicast_if");
 | |
| 
 | |
| 	ClassDB::bind_method(D_METHOD("set_discover_local_port", "port"), &UPNP::set_discover_local_port);
 | |
| 	ClassDB::bind_method(D_METHOD("get_discover_local_port"), &UPNP::get_discover_local_port);
 | |
| 	ADD_PROPERTY(PropertyInfo(Variant::INT, "discover_local_port", PROPERTY_HINT_RANGE, "0,65535"), "set_discover_local_port", "get_discover_local_port");
 | |
| 
 | |
| 	ClassDB::bind_method(D_METHOD("set_discover_ipv6", "ipv6"), &UPNP::set_discover_ipv6);
 | |
| 	ClassDB::bind_method(D_METHOD("is_discover_ipv6"), &UPNP::is_discover_ipv6);
 | |
| 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "discover_ipv6"), "set_discover_ipv6", "is_discover_ipv6");
 | |
| 
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_SUCCESS);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_NOT_AUTHORIZED);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_PORT_MAPPING_NOT_FOUND);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INCONSISTENT_PARAMETERS);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_NO_SUCH_ENTRY_IN_ARRAY);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_ACTION_FAILED);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_SRC_IP_WILDCARD_NOT_PERMITTED);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_EXT_PORT_WILDCARD_NOT_PERMITTED);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INT_PORT_WILDCARD_NOT_PERMITTED);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_REMOTE_HOST_MUST_BE_WILDCARD);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_EXT_PORT_MUST_BE_WILDCARD);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_NO_PORT_MAPS_AVAILABLE);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_CONFLICT_WITH_OTHER_MECHANISM);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_CONFLICT_WITH_OTHER_MAPPING);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_SAME_PORT_VALUES_REQUIRED);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_ONLY_PERMANENT_LEASE_SUPPORTED);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_GATEWAY);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PORT);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PROTOCOL);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_DURATION);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_ARGS);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_RESPONSE);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_INVALID_PARAM);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_HTTP_ERROR);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_SOCKET_ERROR);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_MEM_ALLOC_ERROR);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_NO_GATEWAY);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_NO_DEVICES);
 | |
| 	BIND_ENUM_CONSTANT(UPNP_RESULT_UNKNOWN_ERROR);
 | |
| }
 | |
| 
 | |
| UPNP::UPNP() {
 | |
| }
 | |
| 
 | |
| UPNP::~UPNP() {
 | |
| }
 | 
