Use gUPnP-igd to implement add/remove port mappings

Sun, 18 Sep 2022 22:30:19 -0500

author
Elliott Sales de Andrade <quantum.analyst@gmail.com>
date
Sun, 18 Sep 2022 22:30:19 -0500
changeset 41699
13fdc1beaf35
parent 41698
e6b5a931b741
child 41700
9f6a2c90800e

Use gUPnP-igd to implement add/remove port mappings

Unfortunately, there appears to be no way to [cancel a mapping request](https://gitlab.gnome.org/GNOME/gupnp-igd/-/issues/7), so I've removed that function, and removed the return values used for cancelling. If cancelling comes back, it'll probably be in the form of a `GCancellable` anyway.

Testing Done:
Compile only.

We removed implicit automatic port mapping when we replaced `purple_proxy_connect` with direct `gio` code. Protocols should probably be explicit about when they request a port mapping, but they haven't been made to do that yet.

Reviewed at https://reviews.imfreedom.org/r/1789/

ChangeLog.API file | annotate | diff | comparison | revisions
libpurple/meson.build file | annotate | diff | comparison | revisions
libpurple/upnp.c file | annotate | diff | comparison | revisions
libpurple/upnp.h file | annotate | diff | comparison | revisions
meson.build file | annotate | diff | comparison | revisions
--- a/ChangeLog.API	Sun Sep 18 04:06:33 2022 -0500
+++ b/ChangeLog.API	Sun Sep 18 22:30:19 2022 -0500
@@ -264,7 +264,8 @@
 		* purple_str_size_to_units now takes a goffset as the size parameter
 		* PTFunc renamed to PurpleThemeFunc
 		* purple_txt_resolve now takes a PurpleAccount as the first parameter
-		* UPnPMappingAddRemove renamed to PurpleUPnPMappingAddRemove
+		* purple_upnp_remove_port_mapping no longer returns anything
+		* purple_upnp_set_port_mapping no longer returns anything
 		* purple_util_fetch_url_request now takes a PurpleAccount as
 		  the first parameter
 		* purple_util_fetch_url_request now takes a length as the eighth
@@ -707,6 +708,8 @@
 		* purple_txt_resolve_account
 		* PurpleType, use GType instead.
 		* purple_unescape_filename
+		* PurpleUPnPMappingAddRemove
+		* purple_upnp_cancel_port_mapping
 		* purple_url_decode
 		* purple_user_dir
 		* purple_utf8_salvage.  Use g_utf8_make_valid instead.
--- a/libpurple/meson.build	Sun Sep 18 04:06:33 2022 -0500
+++ b/libpurple/meson.build	Sun Sep 18 22:30:19 2022 -0500
@@ -310,8 +310,8 @@
                     install : true,
                     version : PURPLE_LIB_VERSION,
                     dependencies : # static_link_libs
-                        [dnsapi, ws2_32, glib, gio, gplugin_dep, gupnp, libsoup,
-                         libxml, gdk_pixbuf, gstreamer,
+                        [dnsapi, ws2_32, glib, gio, gplugin_dep, gupnp,
+                         gupnp_igd, libsoup, libxml, gdk_pixbuf, gstreamer,
                          gstreamer_app, json, sqlite3, math])
 
 install_headers(purple_coreheaders,
--- a/libpurple/upnp.c	Sun Sep 18 04:06:33 2022 -0500
+++ b/libpurple/upnp.c	Sun Sep 18 22:30:19 2022 -0500
@@ -24,8 +24,7 @@
 #include <libgupnp/gupnp-control-point.h>
 #include <libgupnp/gupnp-service-info.h>
 #include <libgupnp/gupnp-service-proxy.h>
-
-#include <libsoup/soup.h>
+#include <libgupnp-igd/gupnp-simple-igd.h>
 
 #include "upnp.h"
 
@@ -44,45 +43,9 @@
 /***************************************************************
 ** General Defines                                             *
 ****************************************************************/
-/* limit UPnP-triggered http downloads to 128k */
-#define MAX_UPNP_DOWNLOAD (128 * 1024)
-
-/******************************************************************
-** Action Defines                                                 *
-*******************************************************************/
-#define SOAP_ACTION \
-	"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \
-	"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
-		"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \
-	  "<s:Body>\r\n" \
-	    "<u:%s xmlns:u=\"%s\">\r\n" \
-	      "%s" \
-	    "</u:%s>\r\n" \
-	  "</s:Body>\r\n" \
-	"</s:Envelope>"
-
-#define PORT_MAPPING_LEASE_TIME "0"
+#define PORT_MAPPING_LEASE_TIME 0
 #define PORT_MAPPING_DESCRIPTION "PURPLE_UPNP_PORT_FORWARD"
 
-#define ADD_PORT_MAPPING_PARAMS \
-	"<NewRemoteHost></NewRemoteHost>\r\n" \
-	"<NewExternalPort>%i</NewExternalPort>\r\n" \
-	"<NewProtocol>%s</NewProtocol>\r\n" \
-	"<NewInternalPort>%i</NewInternalPort>\r\n" \
-	"<NewInternalClient>%s</NewInternalClient>\r\n" \
-	"<NewEnabled>1</NewEnabled>\r\n" \
-	"<NewPortMappingDescription>" \
-	PORT_MAPPING_DESCRIPTION \
-	"</NewPortMappingDescription>\r\n" \
-	"<NewLeaseDuration>" \
-	PORT_MAPPING_LEASE_TIME \
-	"</NewLeaseDuration>\r\n"
-
-#define DELETE_PORT_MAPPING_PARAMS \
-	"<NewRemoteHost></NewRemoteHost>\r\n" \
-	"<NewExternalPort>%i</NewExternalPort>\r\n" \
-	"<NewProtocol>%s</NewProtocol>\r\n"
-
 typedef enum {
 	PURPLE_UPNP_STATUS_UNDISCOVERED = -1,
 	PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER,
@@ -99,8 +62,7 @@
 	gint64 lookup_time;
 } PurpleUPnPControlInfo;
 
-struct _PurpleUPnPMappingAddRemove
-{
+typedef struct {
 	unsigned short portmap;
 	gchar protocol[4];
 	gboolean add;
@@ -108,15 +70,14 @@
 	gpointer cb_data;
 	gboolean success;
 	guint tima; /* g_timeout_add handle */
-	SoupMessage *msg;
-};
+} PurpleUPnPMappingAddRemove;
 
 static PurpleUPnPControlInfo control_info = {
 	PURPLE_UPNP_STATUS_UNDISCOVERED,
 	NULL, "\0", "\0", "\0", 0};
 
 static GUPnPContextManager *manager = NULL;
-static SoupSession *session = NULL;
+static GUPnPSimpleIgd *simple_igd = NULL;
 static GSList *discovery_callbacks = NULL;
 
 static void lookup_internal_ip(void);
@@ -287,33 +248,6 @@
 	control_info.status = PURPLE_UPNP_STATUS_DISCOVERING;
 }
 
-static SoupMessage *
-purple_upnp_generate_action_message_and_send(const gchar *actionName,
-                                             const gchar *actionParams,
-                                             SoupSessionCallback cb,
-                                             gpointer cb_data)
-{
-	SoupMessage *msg;
-	gchar *action;
-	gchar* soapMessage;
-
-	/* set the soap message */
-	soapMessage = g_strdup_printf(SOAP_ACTION, actionName,
-		control_info.service_type, actionParams, actionName);
-
-	msg = soup_message_new("POST", control_info.control_url);
-	// purple_http_request_set_max_len(msg, MAX_UPNP_DOWNLOAD);
-	action = g_strdup_printf("\"%s#%s\"", control_info.service_type, actionName);
-	soup_message_headers_replace(soup_message_get_request_headers(msg),
-	                             "SOAPAction", action);
-	g_free(action);
-	soup_message_set_request(msg, "text/xml; charset=utf-8", SOUP_MEMORY_TAKE,
-	                         soapMessage, strlen(soapMessage));
-	soup_session_queue_message(session, msg, cb, cb_data);
-
-	return msg;
-}
-
 const gchar *
 purple_upnp_get_public_ip()
 {
@@ -431,26 +365,57 @@
 }
 
 static void
-done_port_mapping_cb(G_GNUC_UNUSED SoupSession *session, SoupMessage *msg,
-                     gpointer user_data)
+upnp_mapped_external_port_cb(GUPnPSimpleIgd *igd, const gchar *protocol,
+                             G_GNUC_UNUSED const gchar *external_ip,
+                             G_GNUC_UNUSED const gchar *replaces_external_ip,
+                             guint16 external_port,
+                             G_GNUC_UNUSED const gchar *local_ip,
+                             guint16 local_port,
+                             G_GNUC_UNUSED const gchar *description,
+                             gpointer data)
 {
-	PurpleUPnPMappingAddRemove *ar = user_data;
-
-	gboolean success = TRUE;
+	PurpleUPnPMappingAddRemove *ar = data;
 
-	/* determine if port mapping was a success */
-	if (!SOUP_STATUS_IS_SUCCESSFUL(soup_message_get_status(msg))) {
-		purple_debug_error("upnp",
-		                   "purple_upnp_set_port_mapping(): Failed HTTP_OK: %s",
-		                   soup_message_get_reason_phrase(msg));
-		success =  FALSE;
-	} else {
-		purple_debug_info("upnp",
-		                  "Successfully completed port mapping operation");
+	if(!purple_strequal(ar->protocol, protocol)) {
+		return;
+	}
+	if(external_port != ar->portmap) {
+		return;
 	}
 
-	ar->success = success;
+	purple_debug_info("upnp", "Successfully completed port mapping operation");
+
+	ar->success = TRUE;
 	ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
+
+	g_signal_handlers_disconnect_by_data(igd, ar);
+}
+
+static void
+upnp_error_mapping_port_cb(GUPnPSimpleIgd *igd, GError *error,
+                           const gchar *protocol,
+                           guint16 external_port,
+                           G_GNUC_UNUSED const gchar *local_ip,
+                           G_GNUC_UNUSED guint16 local_port,
+                           G_GNUC_UNUSED const gchar *description,
+                           gpointer data)
+{
+	PurpleUPnPMappingAddRemove *ar = data;
+
+	if(!purple_strequal(ar->protocol, protocol)) {
+		return;
+	}
+	if(external_port != ar->portmap) {
+		return;
+	}
+
+	purple_debug_error("upnp", "purple_upnp_set_port_mapping(): Failed: %s",
+	                   error->message);
+
+	ar->success = FALSE;
+	ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
+
+	g_signal_handlers_disconnect_by_data(igd, ar);
 }
 
 static void
@@ -458,41 +423,43 @@
 {
 	PurpleUPnPMappingAddRemove *ar = data;
 
-	if (has_control_mapping) {
-		gchar action_name[25];
-		gchar *action_params;
-		if(ar->add) {
-			const gchar *internal_ip;
-			/* get the internal IP */
-			if(!(internal_ip = purple_upnp_get_internal_ip())) {
-				purple_debug_error("upnp",
-					"purple_upnp_set_port_mapping(): couldn't get local ip\n");
-				ar->success = FALSE;
-				ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
-				return;
-			}
-			strncpy(action_name, "AddPortMapping",
-					sizeof(action_name));
-			action_params = g_strdup_printf(
-					ADD_PORT_MAPPING_PARAMS,
-					ar->portmap, ar->protocol, ar->portmap,
-					internal_ip);
-		} else {
-			strncpy(action_name, "DeletePortMapping", sizeof(action_name));
-			action_params = g_strdup_printf(
-				DELETE_PORT_MAPPING_PARAMS,
-				ar->portmap, ar->protocol);
-		}
-
-		ar->msg = purple_upnp_generate_action_message_and_send(
-		        action_name, action_params, done_port_mapping_cb, ar);
-
-		g_free(action_params);
+	if (!has_control_mapping) {
+		ar->success = FALSE;
+		ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
 		return;
 	}
 
-	ar->success = FALSE;
-	ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
+	if(ar->add) {
+		const gchar *internal_ip;
+		/* get the internal IP */
+		if(!(internal_ip = purple_upnp_get_internal_ip())) {
+			purple_debug_error("upnp",
+				"purple_upnp_set_port_mapping(): couldn't get local ip\n");
+			ar->success = FALSE;
+			ar->tima = g_timeout_add(0, fire_ar_cb_async_and_free, ar);
+			return;
+		}
+
+		if(simple_igd == NULL) {
+			simple_igd = gupnp_simple_igd_new();
+		}
+
+		gupnp_simple_igd_add_port(simple_igd, ar->protocol, ar->portmap,
+		                          internal_ip, ar->portmap,
+		                          PORT_MAPPING_LEASE_TIME,
+		                          PORT_MAPPING_DESCRIPTION);
+	} else {
+		if(simple_igd == NULL) {
+			simple_igd = gupnp_simple_igd_new();
+		}
+
+		gupnp_simple_igd_remove_port(simple_igd, ar->protocol, ar->portmap);
+	}
+
+	g_signal_connect(simple_igd, "mapped-external-port",
+	                 G_CALLBACK(upnp_mapped_external_port_cb), ar);
+	g_signal_connect(simple_igd, "error-mapping-port",
+	                 G_CALLBACK(upnp_error_mapping_port_cb), ar);
 }
 
 static gboolean
@@ -505,38 +472,9 @@
 	return FALSE;
 }
 
-void purple_upnp_cancel_port_mapping(PurpleUPnPMappingAddRemove *ar)
-{
-	GSList *l;
-
-	/* Remove ar from discovery_callbacks if present; it was inserted after a cb.
-	 * The same cb may be in the list multiple times, so be careful to remove
-	 * the one associated with ar. */
-	l = discovery_callbacks;
-	while (l)
-	{
-		GSList *next = l->next;
-
-		if (next && (next->data == ar)) {
-			discovery_callbacks = g_slist_delete_link(discovery_callbacks, next);
-			next = l->next;
-			discovery_callbacks = g_slist_delete_link(discovery_callbacks, l);
-		}
-
-		l = next;
-	}
-
-	if (ar->tima > 0)
-		g_source_remove(ar->tima);
-
-	soup_session_cancel_message(session, ar->msg, SOUP_STATUS_CANCELLED);
-
-	g_free(ar);
-}
-
-PurpleUPnPMappingAddRemove *
-purple_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
-		PurpleUPnPCallback cb, gpointer cb_data)
+void
+purple_upnp_set_port_mapping(unsigned short portmap, const gchar *protocol,
+                             PurpleUPnPCallback cb, gpointer cb_data)
 {
 	PurpleUPnPMappingAddRemove *ar;
 
@@ -574,13 +512,11 @@
 			do_port_mapping_cb(TRUE, ar);
 			break;
 	}
-
-	return ar;
 }
 
-PurpleUPnPMappingAddRemove *
-purple_upnp_remove_port_mapping(unsigned short portmap, const char* protocol,
-		PurpleUPnPCallback cb, gpointer cb_data)
+void
+purple_upnp_remove_port_mapping(unsigned short portmap, const char *protocol,
+                                PurpleUPnPCallback cb, gpointer cb_data)
 {
 	PurpleUPnPMappingAddRemove *ar;
 
@@ -618,8 +554,6 @@
 			do_port_mapping_cb(TRUE, ar);
 			break;
 	}
-
-	return ar;
 }
 
 static void
@@ -637,8 +571,6 @@
 void
 purple_upnp_init()
 {
-	session = soup_session_new();
-
 	g_signal_connect(g_network_monitor_get_default(),
 	                 "network-changed",
 	                 G_CALLBACK(purple_upnp_network_config_changed_cb),
@@ -648,9 +580,7 @@
 void
 purple_upnp_uninit(void)
 {
-	soup_session_abort(session);
-	g_clear_object(&session);
-
+	g_clear_object(&simple_igd);
 	g_clear_pointer(&control_info.control_url, g_free);
 	g_clear_pointer(&control_info.service_type, g_free);
 	g_clear_object(&manager);
--- a/libpurple/upnp.h	Sun Sep 18 04:06:33 2022 -0500
+++ b/libpurple/upnp.h	Sun Sep 18 22:30:19 2022 -0500
@@ -27,16 +27,12 @@
 #ifndef PURPLE_UPNP_H
 #define PURPLE_UPNP_H
 
-typedef struct _PurpleUPnPMappingAddRemove PurpleUPnPMappingAddRemove;
-
 G_BEGIN_DECLS
 
 /**************************************************************************/
 /* UPnP API                                                               */
 /**************************************************************************/
 
-/* typedef struct _PurpleUPnPRequestData PurpleUPnPRequestData; */
-
 typedef void (*PurpleUPnPCallback) (gboolean success, gpointer data);
 
 
@@ -80,15 +76,6 @@
 const gchar* purple_upnp_get_public_ip(void);
 
 /**
- * purple_upnp_cancel_port_mapping:
- * @mapping_data: The data returned when you initiated the UPnP mapping request.
- *
- * Cancel a pending port mapping request initiated with either
- * purple_upnp_set_port_mapping() or purple_upnp_remove_port_mapping().
- */
-void purple_upnp_cancel_port_mapping(PurpleUPnPMappingAddRemove *mapping_data);
-
-/**
  * purple_upnp_set_port_mapping:
  * @portmap: The port to map to this client
  * @protocol: The protocol to map, either "TCP" or "UDP"
@@ -99,12 +86,8 @@
  * Maps Ports in a UPnP enabled IGD that sits on the local network to
  * this purple client. Essentially, this function takes care of the port
  * forwarding so things like file transfers can work behind NAT firewalls
- *
- * Returns: (transfer full): Data which can be passed to purple_upnp_cancel_port_mapping() to
- *          cancel
  */
-PurpleUPnPMappingAddRemove *purple_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
-		PurpleUPnPCallback cb, gpointer cb_data);
+void purple_upnp_set_port_mapping(unsigned short portmap, const gchar *protocol, PurpleUPnPCallback cb, gpointer cb_data);
 
 /**
  * purple_upnp_remove_port_mapping:
@@ -118,12 +101,8 @@
  * to this purple client. Essentially, this function takes care of deleting the
  * port forwarding after they have completed a connection so another client on
  * the local network can take advantage of the port forwarding
- *
- * Returns: (transfer full): Data which can be passed to purple_upnp_cancel_port_mapping() to
- *          cancel
  */
-PurpleUPnPMappingAddRemove *purple_upnp_remove_port_mapping(unsigned short portmap,
-		const gchar* protocol, PurpleUPnPCallback cb, gpointer cb_data);
+void purple_upnp_remove_port_mapping(unsigned short portmap, const gchar *protocol, PurpleUPnPCallback cb, gpointer cb_data);
 
 G_END_DECLS
 
--- a/meson.build	Sun Sep 18 04:06:33 2022 -0500
+++ b/meson.build	Sun Sep 18 22:30:19 2022 -0500
@@ -294,6 +294,7 @@
 #######################################################################
 
 gupnp = dependency('gupnp-1.2', version : '>= 1.2.0')
+gupnp_igd = dependency('gupnp-igd-1.0', version : '>= 1.0.0')
 
 #######################################################################
 # Check for libsoup (required)

mercurial