Procházet zdrojové kódy

Merge pull request #99597 from Faless/upnp/custom_instance

[UPNP] Allow disabling UPNP implementation on the Web
Thaddeus Crews před 9 měsíci
rodič
revize
29e6ef4277

+ 1 - 1
modules/upnp/SCsub

@@ -10,7 +10,7 @@ env_upnp = env_modules.Clone()
 
 thirdparty_obj = []
 
-if env["builtin_miniupnpc"]:
+if env["builtin_miniupnpc"] and env["platform"] != "web":
     thirdparty_dir = "#thirdparty/miniupnpc/"
     thirdparty_sources = [
         "igd_desc_parse.c",

+ 12 - 2
modules/upnp/register_types.cpp

@@ -33,6 +33,11 @@
 #include "upnp.h"
 #include "upnp_device.h"
 
+#ifndef WEB_ENABLED
+#include "upnp_device_miniupnp.h"
+#include "upnp_miniupnp.h"
+#endif
+
 #include "core/error/error_macros.h"
 
 void initialize_upnp_module(ModuleInitializationLevel p_level) {
@@ -40,8 +45,13 @@ void initialize_upnp_module(ModuleInitializationLevel p_level) {
 		return;
 	}
 
-	GDREGISTER_CLASS(UPNP);
-	GDREGISTER_CLASS(UPNPDevice);
+	ClassDB::register_custom_instance_class<UPNP>();
+	ClassDB::register_custom_instance_class<UPNPDevice>();
+
+#ifndef WEB_ENABLED
+	UPNPMiniUPNP::make_default();
+	UPNPDeviceMiniUPNP::make_default();
+#endif
 }
 
 void uninitialize_upnp_module(ModuleInitializationLevel p_level) {

+ 1 - 298
modules/upnp/upnp.cpp

@@ -30,298 +30,7 @@
 
 #include "upnp.h"
 
-#include <miniwget.h>
-#include <upnpcommands.h>
-
-#include <stdlib.h>
-
-bool UPNP::is_common_device(const String &dev) const {
-	return dev.is_empty() ||
-			dev.contains("InternetGatewayDevice") ||
-			dev.contains("WANIPConnection") ||
-			dev.contains("WANPPPConnection") ||
-			dev.contains("rootdevice");
-}
-
-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 = {};
-	struct IGDdatas data;
-
-	parserootdesc(xml, size, &data);
-	free(xml);
-	xml = nullptr;
-
-	GetUPNPUrls(&urls, &data, dev->get_description_url().utf8().get_data(), 0);
-
-	char addr[16];
-#if MINIUPNPC_API_VERSION >= 18
-	int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16, nullptr, 0);
-#else
-	int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16);
-#endif
-
-	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.is_null());
-
-	devices.push_back(device);
-}
-
-void UPNP::set_device(int index, Ref<UPNPDevice> device) {
-	ERR_FAIL_INDEX(index, devices.size());
-	ERR_FAIL_COND(device.is_null());
-
-	devices.set(index, device);
-}
-
-void UPNP::remove_device(int index) {
-	ERR_FAIL_INDEX(index, devices.size());
-
-	devices.remove_at(index);
-}
-
-void UPNP::clear_devices() {
-	devices.clear();
-}
-
-Ref<UPNPDevice> UPNP::get_gateway() const {
-	ERR_FAIL_COND_V_MSG(devices.is_empty(), nullptr, "Couldn't find any UPNPDevices.");
-
-	for (int i = 0; i < devices.size(); i++) {
-		Ref<UPNPDevice> dev = get_device(i);
-
-		if (dev.is_valid() && 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.is_null()) {
-		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.is_null()) {
-		return UPNP_RESULT_NO_GATEWAY;
-	}
-
-	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.is_null()) {
-		return UPNP_RESULT_NO_GATEWAY;
-	}
-
-	return dev->delete_port_mapping(port, proto);
-}
+UPNP *(*UPNP::_create)(bool p_notify_postinitialize) = nullptr;
 
 void UPNP::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_device_count"), &UPNP::get_device_count);
@@ -382,9 +91,3 @@ void UPNP::_bind_methods() {
 	BIND_ENUM_CONSTANT(UPNP_RESULT_NO_DEVICES);
 	BIND_ENUM_CONSTANT(UPNP_RESULT_UNKNOWN_ERROR);
 }
-
-UPNP::UPNP() {
-}
-
-UPNP::~UPNP() {
-}

+ 27 - 34
modules/upnp/upnp.h

@@ -35,26 +35,14 @@
 
 #include "core/object/ref_counted.h"
 
-#include <miniupnpc.h>
-
 class UPNP : public RefCounted {
 	GDCLASS(UPNP, RefCounted);
 
-private:
-	String discover_multicast_if = "";
-	int discover_local_port = 0;
-	bool discover_ipv6 = false;
-
-	Vector<Ref<UPNPDevice>> devices;
-
-	bool is_common_device(const String &dev) const;
-	void add_device_to_list(UPNPDev *dev, UPNPDev *devlist);
-	void parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist);
-	char *load_description(const String &url, int *size, int *status_code) const;
-
 protected:
 	static void _bind_methods();
 
+	static UPNP *(*_create)(bool p_notify_postinitialize);
+
 public:
 	enum UPNPResult {
 		UPNP_RESULT_SUCCESS,
@@ -88,35 +76,40 @@ public:
 		UPNP_RESULT_UNKNOWN_ERROR,
 	};
 
-	static int upnp_result(int in);
+	static UPNP *create(bool p_notify_postinitialize = true) {
+		if (!_create) {
+			return nullptr;
+		}
+		return _create(p_notify_postinitialize);
+	}
 
-	int get_device_count() const;
-	Ref<UPNPDevice> get_device(int index) const;
-	void add_device(Ref<UPNPDevice> device);
-	void set_device(int index, Ref<UPNPDevice> device);
-	void remove_device(int index);
-	void clear_devices();
+	virtual int get_device_count() const = 0;
+	virtual Ref<UPNPDevice> get_device(int index) const = 0;
+	virtual void add_device(Ref<UPNPDevice> device) = 0;
+	virtual void set_device(int index, Ref<UPNPDevice> device) = 0;
+	virtual void remove_device(int index) = 0;
+	virtual void clear_devices() = 0;
 
-	Ref<UPNPDevice> get_gateway() const;
+	virtual Ref<UPNPDevice> get_gateway() const = 0;
 
-	int discover(int timeout = 2000, int ttl = 2, const String &device_filter = "InternetGatewayDevice");
+	virtual int discover(int timeout = 2000, int ttl = 2, const String &device_filter = "InternetGatewayDevice") = 0;
 
-	String query_external_address() const;
+	virtual String query_external_address() const = 0;
 
-	int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const;
-	int delete_port_mapping(int port, String proto = "UDP") const;
+	virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const = 0;
+	virtual int delete_port_mapping(int port, String proto = "UDP") const = 0;
 
-	void set_discover_multicast_if(const String &m_if);
-	String get_discover_multicast_if() const;
+	virtual void set_discover_multicast_if(const String &m_if) = 0;
+	virtual String get_discover_multicast_if() const = 0;
 
-	void set_discover_local_port(int port);
-	int get_discover_local_port() const;
+	virtual void set_discover_local_port(int port) = 0;
+	virtual int get_discover_local_port() const = 0;
 
-	void set_discover_ipv6(bool ipv6);
-	bool is_discover_ipv6() const;
+	virtual void set_discover_ipv6(bool ipv6) = 0;
+	virtual bool is_discover_ipv6() const = 0;
 
-	UPNP();
-	~UPNP();
+	UPNP() {}
+	virtual ~UPNP() {}
 };
 
 VARIANT_ENUM_CAST(UPNP::UPNPResult)

+ 1 - 125
modules/upnp/upnp_device.cpp

@@ -30,119 +30,7 @@
 
 #include "upnp_device.h"
 
-#include "upnp.h"
-
-#include <upnpcommands.h>
-
-String UPNPDevice::query_external_address() const {
-	ERR_FAIL_COND_V_MSG(!is_valid_gateway(), "", "The Internet Gateway Device must be valid.");
-
-	char addr[16];
-	int i = UPNP_GetExternalIPAddress(
-			igd_control_url.utf8().get_data(),
-			igd_service_type.utf8().get_data(),
-			(char *)&addr);
-
-	ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, "", "Couldn't get external IP address.");
-
-	return String(addr);
-}
-
-int UPNPDevice::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const {
-	ERR_FAIL_COND_V_MSG(!is_valid_gateway(), UPNP::UPNP_RESULT_INVALID_GATEWAY, "The Internet Gateway Device must be valid.");
-	ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive).");
-	ERR_FAIL_COND_V_MSG(port_internal < 0 || port_internal > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 0 and 65535 (inclusive)."); // Needs to allow 0 because 0 signifies "use external port as internal port"
-	ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP.");
-	ERR_FAIL_COND_V_MSG(duration < 0, UPNP::UPNP_RESULT_INVALID_DURATION, "The port mapping's lease duration can't be negative.");
-
-	if (port_internal < 1) {
-		port_internal = port;
-	}
-
-	int i = UPNP_AddPortMapping(
-			igd_control_url.utf8().get_data(),
-			igd_service_type.utf8().get_data(),
-			itos(port).utf8().get_data(),
-			itos(port_internal).utf8().get_data(),
-			igd_our_addr.utf8().get_data(),
-			desc.is_empty() ? nullptr : desc.utf8().get_data(),
-			proto.utf8().get_data(),
-			nullptr, // Remote host, always nullptr as IGDs don't support it
-			duration > 0 ? itos(duration).utf8().get_data() : nullptr);
-
-	ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNP::upnp_result(i), "Couldn't add port mapping.");
-
-	return UPNP::UPNP_RESULT_SUCCESS;
-}
-
-int UPNPDevice::delete_port_mapping(int port, String proto) const {
-	ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive).");
-	ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP.");
-
-	int i = UPNP_DeletePortMapping(
-			igd_control_url.utf8().get_data(),
-			igd_service_type.utf8().get_data(),
-			itos(port).utf8().get_data(),
-			proto.utf8().get_data(),
-			nullptr // Remote host, always nullptr as IGDs don't support it
-	);
-
-	ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNP::upnp_result(i), "Couldn't delete port mapping.");
-
-	return UPNP::UPNP_RESULT_SUCCESS;
-}
-
-void UPNPDevice::set_description_url(const String &url) {
-	description_url = url;
-}
-
-String UPNPDevice::get_description_url() const {
-	return description_url;
-}
-
-void UPNPDevice::set_service_type(const String &type) {
-	service_type = type;
-}
-
-String UPNPDevice::get_service_type() const {
-	return service_type;
-}
-
-void UPNPDevice::set_igd_control_url(const String &url) {
-	igd_control_url = url;
-}
-
-String UPNPDevice::get_igd_control_url() const {
-	return igd_control_url;
-}
-
-void UPNPDevice::set_igd_service_type(const String &type) {
-	igd_service_type = type;
-}
-
-String UPNPDevice::get_igd_service_type() const {
-	return igd_service_type;
-}
-
-void UPNPDevice::set_igd_our_addr(const String &addr) {
-	igd_our_addr = addr;
-}
-
-String UPNPDevice::get_igd_our_addr() const {
-	return igd_our_addr;
-}
-
-void UPNPDevice::set_igd_status(IGDStatus status) {
-	igd_status = status;
-}
-
-UPNPDevice::IGDStatus UPNPDevice::get_igd_status() const {
-	return igd_status;
-}
-
-bool UPNPDevice::is_valid_gateway() const {
-	return igd_status == IGD_STATUS_OK;
-}
+UPNPDevice *(*UPNPDevice::_create)(bool p_notify_postinitialize) = nullptr;
 
 void UPNPDevice::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("is_valid_gateway"), &UPNPDevice::is_valid_gateway);
@@ -185,15 +73,3 @@ void UPNPDevice::_bind_methods() {
 	BIND_ENUM_CONSTANT(IGD_STATUS_MALLOC_ERROR);
 	BIND_ENUM_CONSTANT(IGD_STATUS_UNKNOWN_ERROR);
 }
-
-UPNPDevice::UPNPDevice() {
-	description_url = "";
-	service_type = "";
-	igd_control_url = "";
-	igd_service_type = "";
-	igd_our_addr = "";
-	igd_status = IGD_STATUS_UNKNOWN_ERROR;
-}
-
-UPNPDevice::~UPNPDevice() {
-}

+ 29 - 28
modules/upnp/upnp_device.h

@@ -36,6 +36,11 @@
 class UPNPDevice : public RefCounted {
 	GDCLASS(UPNPDevice, RefCounted);
 
+protected:
+	static void _bind_methods();
+
+	static UPNPDevice *(*_create)(bool p_notify_postinitialize);
+
 public:
 	enum IGDStatus {
 		IGD_STATUS_OK,
@@ -50,42 +55,38 @@ public:
 		IGD_STATUS_UNKNOWN_ERROR,
 	};
 
-	void set_description_url(const String &url);
-	String get_description_url() const;
+	static UPNPDevice *create(bool p_notify_postinitialize = true) {
+		if (!_create) {
+			return nullptr;
+		}
+		return _create(p_notify_postinitialize);
+	}
 
-	void set_service_type(const String &type);
-	String get_service_type() const;
+	virtual void set_description_url(const String &url) = 0;
+	virtual String get_description_url() const = 0;
 
-	void set_igd_control_url(const String &url);
-	String get_igd_control_url() const;
+	virtual void set_service_type(const String &type) = 0;
+	virtual String get_service_type() const = 0;
 
-	void set_igd_service_type(const String &type);
-	String get_igd_service_type() const;
+	virtual void set_igd_control_url(const String &url) = 0;
+	virtual String get_igd_control_url() const = 0;
 
-	void set_igd_our_addr(const String &addr);
-	String get_igd_our_addr() const;
+	virtual void set_igd_service_type(const String &type) = 0;
+	virtual String get_igd_service_type() const = 0;
 
-	void set_igd_status(IGDStatus status);
-	IGDStatus get_igd_status() const;
+	virtual void set_igd_our_addr(const String &addr) = 0;
+	virtual String get_igd_our_addr() const = 0;
 
-	bool is_valid_gateway() const;
-	String query_external_address() const;
-	int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const;
-	int delete_port_mapping(int port, String proto = "UDP") const;
+	virtual void set_igd_status(IGDStatus status) = 0;
+	virtual IGDStatus get_igd_status() const = 0;
 
-	UPNPDevice();
-	~UPNPDevice();
-
-protected:
-	static void _bind_methods();
+	virtual bool is_valid_gateway() const = 0;
+	virtual String query_external_address() const = 0;
+	virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const = 0;
+	virtual int delete_port_mapping(int port, String proto = "UDP") const = 0;
 
-private:
-	String description_url;
-	String service_type;
-	String igd_control_url;
-	String igd_service_type;
-	String igd_our_addr;
-	IGDStatus igd_status;
+	UPNPDevice() {}
+	virtual ~UPNPDevice() {}
 };
 
 VARIANT_ENUM_CAST(UPNPDevice::IGDStatus)

+ 153 - 0
modules/upnp/upnp_device_miniupnp.cpp

@@ -0,0 +1,153 @@
+/**************************************************************************/
+/*  upnp_device_miniupnp.cpp                                              */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#ifndef WEB_ENABLED
+
+#include "upnp_device_miniupnp.h"
+
+#include "upnp_miniupnp.h"
+
+#include <upnpcommands.h>
+
+void UPNPDeviceMiniUPNP::make_default() {
+	UPNPDevice::_create = UPNPDeviceMiniUPNP::_create;
+}
+
+String UPNPDeviceMiniUPNP::query_external_address() const {
+	ERR_FAIL_COND_V_MSG(!is_valid_gateway(), "", "The Internet Gateway Device must be valid.");
+
+	char addr[16];
+	int i = UPNP_GetExternalIPAddress(
+			igd_control_url.utf8().get_data(),
+			igd_service_type.utf8().get_data(),
+			(char *)&addr);
+
+	ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, "", "Couldn't get external IP address.");
+
+	return String(addr);
+}
+
+int UPNPDeviceMiniUPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const {
+	ERR_FAIL_COND_V_MSG(!is_valid_gateway(), UPNP::UPNP_RESULT_INVALID_GATEWAY, "The Internet Gateway Device must be valid.");
+	ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive).");
+	ERR_FAIL_COND_V_MSG(port_internal < 0 || port_internal > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 0 and 65535 (inclusive)."); // Needs to allow 0 because 0 signifies "use external port as internal port"
+	ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP.");
+	ERR_FAIL_COND_V_MSG(duration < 0, UPNP::UPNP_RESULT_INVALID_DURATION, "The port mapping's lease duration can't be negative.");
+
+	if (port_internal < 1) {
+		port_internal = port;
+	}
+
+	int i = UPNP_AddPortMapping(
+			igd_control_url.utf8().get_data(),
+			igd_service_type.utf8().get_data(),
+			itos(port).utf8().get_data(),
+			itos(port_internal).utf8().get_data(),
+			igd_our_addr.utf8().get_data(),
+			desc.is_empty() ? nullptr : desc.utf8().get_data(),
+			proto.utf8().get_data(),
+			nullptr, // Remote host, always nullptr as IGDs don't support it
+			duration > 0 ? itos(duration).utf8().get_data() : nullptr);
+
+	ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNPMiniUPNP::upnp_result(i), "Couldn't add port mapping.");
+
+	return UPNP::UPNP_RESULT_SUCCESS;
+}
+
+int UPNPDeviceMiniUPNP::delete_port_mapping(int port, String proto) const {
+	ERR_FAIL_COND_V_MSG(port < 1 || port > 65535, UPNP::UPNP_RESULT_INVALID_PORT, "The port number must be set between 1 and 65535 (inclusive).");
+	ERR_FAIL_COND_V_MSG(proto != "UDP" && proto != "TCP", UPNP::UPNP_RESULT_INVALID_PROTOCOL, "The protocol must be either TCP or UDP.");
+
+	int i = UPNP_DeletePortMapping(
+			igd_control_url.utf8().get_data(),
+			igd_service_type.utf8().get_data(),
+			itos(port).utf8().get_data(),
+			proto.utf8().get_data(),
+			nullptr // Remote host, always nullptr as IGDs don't support it
+	);
+
+	ERR_FAIL_COND_V_MSG(i != UPNPCOMMAND_SUCCESS, UPNPMiniUPNP::upnp_result(i), "Couldn't delete port mapping.");
+
+	return UPNP::UPNP_RESULT_SUCCESS;
+}
+
+void UPNPDeviceMiniUPNP::set_description_url(const String &url) {
+	description_url = url;
+}
+
+String UPNPDeviceMiniUPNP::get_description_url() const {
+	return description_url;
+}
+
+void UPNPDeviceMiniUPNP::set_service_type(const String &type) {
+	service_type = type;
+}
+
+String UPNPDeviceMiniUPNP::get_service_type() const {
+	return service_type;
+}
+
+void UPNPDeviceMiniUPNP::set_igd_control_url(const String &url) {
+	igd_control_url = url;
+}
+
+String UPNPDeviceMiniUPNP::get_igd_control_url() const {
+	return igd_control_url;
+}
+
+void UPNPDeviceMiniUPNP::set_igd_service_type(const String &type) {
+	igd_service_type = type;
+}
+
+String UPNPDeviceMiniUPNP::get_igd_service_type() const {
+	return igd_service_type;
+}
+
+void UPNPDeviceMiniUPNP::set_igd_our_addr(const String &addr) {
+	igd_our_addr = addr;
+}
+
+String UPNPDeviceMiniUPNP::get_igd_our_addr() const {
+	return igd_our_addr;
+}
+
+void UPNPDeviceMiniUPNP::set_igd_status(IGDStatus status) {
+	igd_status = status;
+}
+
+UPNPDeviceMiniUPNP::IGDStatus UPNPDeviceMiniUPNP::get_igd_status() const {
+	return igd_status;
+}
+
+bool UPNPDeviceMiniUPNP::is_valid_gateway() const {
+	return igd_status == IGD_STATUS_OK;
+}
+
+#endif // WEB_ENABLED

+ 83 - 0
modules/upnp/upnp_device_miniupnp.h

@@ -0,0 +1,83 @@
+/**************************************************************************/
+/*  upnp_device_miniupnp.h                                                */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#ifndef UPNP_DEVICE_MINIUPNP_H
+#define UPNP_DEVICE_MINIUPNP_H
+
+#ifndef WEB_ENABLED
+
+#include "upnp_device.h"
+
+class UPNPDeviceMiniUPNP : public UPNPDevice {
+	GDCLASS(UPNPDeviceMiniUPNP, UPNPDevice);
+
+private:
+	static UPNPDevice *_create(bool p_notify_postinitialize) { return static_cast<UPNPDevice *>(ClassDB::creator<UPNPDeviceMiniUPNP>(p_notify_postinitialize)); }
+
+	String description_url;
+	String service_type;
+	String igd_control_url;
+	String igd_service_type;
+	String igd_our_addr;
+	IGDStatus igd_status = IGD_STATUS_UNKNOWN_ERROR;
+
+public:
+	static void make_default();
+
+	virtual void set_description_url(const String &url) override;
+	virtual String get_description_url() const override;
+
+	virtual void set_service_type(const String &type) override;
+	virtual String get_service_type() const override;
+
+	virtual void set_igd_control_url(const String &url) override;
+	virtual String get_igd_control_url() const override;
+
+	virtual void set_igd_service_type(const String &type) override;
+	virtual String get_igd_service_type() const override;
+
+	virtual void set_igd_our_addr(const String &addr) override;
+	virtual String get_igd_our_addr() const override;
+
+	virtual void set_igd_status(IGDStatus status) override;
+	virtual IGDStatus get_igd_status() const override;
+
+	virtual bool is_valid_gateway() const override;
+	virtual String query_external_address() const override;
+	virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const override;
+	virtual int delete_port_mapping(int port, String proto = "UDP") const override;
+
+	UPNPDeviceMiniUPNP() {}
+	virtual ~UPNPDeviceMiniUPNP() {}
+};
+
+#endif // WEB_ENABLED
+
+#endif // UPNP_DEVICE_MINIUPNP_H

+ 334 - 0
modules/upnp/upnp_miniupnp.cpp

@@ -0,0 +1,334 @@
+/**************************************************************************/
+/*  upnp_miniupnp.cpp                                                     */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#ifndef WEB_ENABLED
+
+#include "upnp_miniupnp.h"
+
+#include "upnp_device_miniupnp.h"
+
+#include <miniwget.h>
+#include <upnpcommands.h>
+
+#include <stdlib.h>
+
+void UPNPMiniUPNP::make_default() {
+	UPNP::_create = UPNPMiniUPNP::_create;
+}
+
+bool UPNPMiniUPNP::is_common_device(const String &dev) const {
+	return dev.is_empty() ||
+			dev.contains("InternetGatewayDevice") ||
+			dev.contains("WANIPConnection") ||
+			dev.contains("WANPPPConnection") ||
+			dev.contains("rootdevice");
+}
+
+int UPNPMiniUPNP::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 UPNPMiniUPNP::add_device_to_list(UPNPDev *dev, UPNPDev *devlist) {
+	Ref<UPNPDeviceMiniUPNP> 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 *UPNPMiniUPNP::load_description(const String &url, int *size, int *status_code) const {
+	return (char *)miniwget(url.utf8().get_data(), size, 0, status_code);
+}
+
+void UPNPMiniUPNP::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 = {};
+	struct IGDdatas data;
+
+	parserootdesc(xml, size, &data);
+	free(xml);
+	xml = nullptr;
+
+	GetUPNPUrls(&urls, &data, dev->get_description_url().utf8().get_data(), 0);
+
+	char addr[16];
+#if MINIUPNPC_API_VERSION >= 18
+	int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16, nullptr, 0);
+#else
+	int i = UPNP_GetValidIGD(devlist, &urls, &data, (char *)&addr, 16);
+#endif
+
+	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 UPNPMiniUPNP::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 UPNPMiniUPNP::get_device_count() const {
+	return devices.size();
+}
+
+Ref<UPNPDevice> UPNPMiniUPNP::get_device(int index) const {
+	ERR_FAIL_INDEX_V(index, devices.size(), nullptr);
+
+	return devices.get(index);
+}
+
+void UPNPMiniUPNP::add_device(Ref<UPNPDevice> device) {
+	ERR_FAIL_COND(device.is_null());
+
+	devices.push_back(device);
+}
+
+void UPNPMiniUPNP::set_device(int index, Ref<UPNPDevice> device) {
+	ERR_FAIL_INDEX(index, devices.size());
+	ERR_FAIL_COND(device.is_null());
+
+	devices.set(index, device);
+}
+
+void UPNPMiniUPNP::remove_device(int index) {
+	ERR_FAIL_INDEX(index, devices.size());
+
+	devices.remove_at(index);
+}
+
+void UPNPMiniUPNP::clear_devices() {
+	devices.clear();
+}
+
+Ref<UPNPDevice> UPNPMiniUPNP::get_gateway() const {
+	ERR_FAIL_COND_V_MSG(devices.is_empty(), nullptr, "Couldn't find any UPNPDevices.");
+
+	for (int i = 0; i < devices.size(); i++) {
+		Ref<UPNPDevice> dev = get_device(i);
+
+		if (dev.is_valid() && dev->is_valid_gateway()) {
+			return dev;
+		}
+	}
+
+	return nullptr;
+}
+
+void UPNPMiniUPNP::set_discover_multicast_if(const String &m_if) {
+	discover_multicast_if = m_if;
+}
+
+String UPNPMiniUPNP::get_discover_multicast_if() const {
+	return discover_multicast_if;
+}
+
+void UPNPMiniUPNP::set_discover_local_port(int port) {
+	discover_local_port = port;
+}
+
+int UPNPMiniUPNP::get_discover_local_port() const {
+	return discover_local_port;
+}
+
+void UPNPMiniUPNP::set_discover_ipv6(bool ipv6) {
+	discover_ipv6 = ipv6;
+}
+
+bool UPNPMiniUPNP::is_discover_ipv6() const {
+	return discover_ipv6;
+}
+
+String UPNPMiniUPNP::query_external_address() const {
+	Ref<UPNPDevice> dev = get_gateway();
+
+	if (dev.is_null()) {
+		return "";
+	}
+
+	return dev->query_external_address();
+}
+
+int UPNPMiniUPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const {
+	Ref<UPNPDevice> dev = get_gateway();
+
+	if (dev.is_null()) {
+		return UPNP_RESULT_NO_GATEWAY;
+	}
+
+	return dev->add_port_mapping(port, port_internal, desc, proto, duration);
+}
+
+int UPNPMiniUPNP::delete_port_mapping(int port, String proto) const {
+	Ref<UPNPDevice> dev = get_gateway();
+
+	if (dev.is_null()) {
+		return UPNP_RESULT_NO_GATEWAY;
+	}
+
+	return dev->delete_port_mapping(port, proto);
+}
+
+#endif // WEB_ENABLED

+ 93 - 0
modules/upnp/upnp_miniupnp.h

@@ -0,0 +1,93 @@
+/**************************************************************************/
+/*  upnp_miniupnp.h                                                       */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* 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.                 */
+/**************************************************************************/
+
+#ifndef UPNP_MINIUPNP_H
+#define UPNP_MINIUPNP_H
+
+#ifndef WEB_ENABLED
+
+#include "upnp.h"
+
+#include <miniupnpc.h>
+
+class UPNPMiniUPNP : public UPNP {
+	GDCLASS(UPNPMiniUPNP, UPNP);
+
+private:
+	static UPNP *_create(bool p_notify_postinitialize) { return static_cast<UPNP *>(ClassDB::creator<UPNPMiniUPNP>(p_notify_postinitialize)); }
+
+	String discover_multicast_if = "";
+	int discover_local_port = 0;
+	bool discover_ipv6 = false;
+
+	Vector<Ref<UPNPDevice>> devices;
+
+	bool is_common_device(const String &dev) const;
+	void add_device_to_list(UPNPDev *dev, UPNPDev *devlist);
+	void parse_igd(Ref<UPNPDevice> dev, UPNPDev *devlist);
+	char *load_description(const String &url, int *size, int *status_code) const;
+
+public:
+	static void make_default();
+
+	static int upnp_result(int in);
+
+	virtual int get_device_count() const override;
+	virtual Ref<UPNPDevice> get_device(int index) const override;
+	virtual void add_device(Ref<UPNPDevice> device) override;
+	virtual void set_device(int index, Ref<UPNPDevice> device) override;
+	virtual void remove_device(int index) override;
+	virtual void clear_devices() override;
+
+	virtual Ref<UPNPDevice> get_gateway() const override;
+
+	virtual int discover(int timeout = 2000, int ttl = 2, const String &device_filter = "InternetGatewayDevice") override;
+
+	virtual String query_external_address() const override;
+
+	virtual int add_port_mapping(int port, int port_internal = 0, String desc = "", String proto = "UDP", int duration = 0) const override;
+	virtual int delete_port_mapping(int port, String proto = "UDP") const override;
+
+	virtual void set_discover_multicast_if(const String &m_if) override;
+	virtual String get_discover_multicast_if() const override;
+
+	virtual void set_discover_local_port(int port) override;
+	virtual int get_discover_local_port() const override;
+
+	virtual void set_discover_ipv6(bool ipv6) override;
+	virtual bool is_discover_ipv6() const override;
+
+	UPNPMiniUPNP() {}
+	virtual ~UPNPMiniUPNP() {}
+};
+
+#endif // WEB_ENABLED
+
+#endif // UPNP_MINIUPNP_H