Sun, 18 Sep 2022 22:30:19 -0500
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)