pidgin/pidgin
Clone
Summary
Browse
Changes
Graph
Use gUPnP to determine external IP address and control URL
21 months ago, Elliott Sales de Andrade
a5499f6be930
Use gUPnP to determine external IP address and control URL
This is based on the gUPnP example, but modified to use the async API, and its context manager to automatically check all interfaces.
Testing Done:
Opened prefs and checked that external IP was detected with a UPnP-enabled router.
Reviewed at https://reviews.imfreedom.org/r/1785/
/* purple
*
* Purple is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*/
#include
<gio/gio.h>
#include
<libgupnp/gupnp-context-manager.h>
#include
<libgupnp/gupnp-control-point.h>
#include
<libgupnp/gupnp-service-info.h>
#include
<libgupnp/gupnp-service-proxy.h>
#include
<libsoup/soup.h>
#include
"upnp.h"
#include
"glibcompat.h"
#include
"soupcompat.h"
#include
"debug.h"
#include
"eventloop.h"
#include
"network.h"
#include
"proxy.h"
#include
"purplegio.h"
#include
"signals.h"
#include
"util.h"
#include
"xmlnode.h"
/***************************************************************
** 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_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
,
PURPLE_UPNP_STATUS_DISCOVERING
,
PURPLE_UPNP_STATUS_DISCOVERED
}
PurpleUPnPStatus
;
typedef
struct
{
PurpleUPnPStatus
status
;
gchar
*
control_url
;
gchar
*
service_type
;
char
publicip
[
16
];
char
internalip
[
16
];
gint64
lookup_time
;
}
PurpleUPnPControlInfo
;
struct
_PurpleUPnPMappingAddRemove
{
unsigned
short
portmap
;
gchar
protocol
[
4
];
gboolean
add
;
PurpleUPnPCallback
cb
;
gpointer
cb_data
;
gboolean
success
;
guint
tima
;
/* g_timeout_add handle */
SoupMessage
*
msg
;
};
static
PurpleUPnPControlInfo
control_info
=
{
PURPLE_UPNP_STATUS_UNDISCOVERED
,
NULL
,
"
\0
"
,
"
\0
"
,
"
\0
"
,
0
};
static
GUPnPContextManager
*
manager
=
NULL
;
static
SoupSession
*
session
=
NULL
;
static
GSList
*
discovery_callbacks
=
NULL
;
static
void
lookup_internal_ip
(
void
);
static
gboolean
fire_ar_cb_async_and_free
(
gpointer
data
)
{
PurpleUPnPMappingAddRemove
*
ar
=
data
;
if
(
ar
)
{
if
(
ar
->
cb
)
ar
->
cb
(
ar
->
success
,
ar
->
cb_data
);
g_free
(
ar
);
}
return
FALSE
;
}
static
void
fire_discovery_callbacks
(
gboolean
success
)
{
while
(
discovery_callbacks
)
{
gpointer
data
;
PurpleUPnPCallback
cb
=
discovery_callbacks
->
data
;
discovery_callbacks
=
g_slist_delete_link
(
discovery_callbacks
,
discovery_callbacks
);
data
=
discovery_callbacks
->
data
;
discovery_callbacks
=
g_slist_delete_link
(
discovery_callbacks
,
discovery_callbacks
);
cb
(
success
,
data
);
}
}
static
void
upnp_service_proxy_call_action_cb
(
GObject
*
source
,
GAsyncResult
*
result
,
G_GNUC_UNUSED
gpointer
data
)
{
GError
*
error
=
NULL
;
char
*
ip
=
NULL
;
GUPnPServiceProxyAction
*
action
=
NULL
;
action
=
gupnp_service_proxy_call_action_finish
(
GUPNP_SERVICE_PROXY
(
source
),
result
,
&
error
);
if
(
error
!=
NULL
)
{
control_info
.
publicip
[
0
]
=
'\0'
;
purple_debug_error
(
"upnp"
,
"Failed to call GetExternalIPAddress action: %s"
,
error
->
message
);
g_error_free
(
error
);
return
;
}
gupnp_service_proxy_action_get_result
(
action
,
&
error
,
"NewExternalIPAddress"
,
G_TYPE_STRING
,
&
ip
,
NULL
);
if
(
error
==
NULL
)
{
g_strlcpy
(
control_info
.
publicip
,
ip
,
sizeof
(
control_info
.
publicip
));
purple_debug_info
(
"upnp"
,
"NAT Returned IP: %s"
,
control_info
.
publicip
);
g_free
(
ip
);
}
else
{
control_info
.
publicip
[
0
]
=
'\0'
;
purple_debug_error
(
"upnp"
,
"Failed to get result from GetExternalIPAddress: %s"
,
error
->
message
);
g_error_free
(
error
);
}
gupnp_service_proxy_action_unref
(
action
);
}
static
void
upnp_service_proxy_available_cb
(
G_GNUC_UNUSED
GUPnPControlPoint
*
cp
,
GUPnPServiceProxy
*
proxy
,
G_GNUC_UNUSED
gpointer
data
)
{
gchar
*
control_url
=
NULL
;
const
gchar
*
service_type
=
NULL
;
control_url
=
gupnp_service_info_get_control_url
(
GUPNP_SERVICE_INFO
(
proxy
));
service_type
=
gupnp_service_info_get_service_type
(
GUPNP_SERVICE_INFO
(
proxy
));
purple_debug_info
(
"upnp"
,
"Service proxy available for %s on control URL %s"
,
service_type
,
control_url
);
control_info
.
lookup_time
=
g_get_monotonic_time
();
control_info
.
control_url
=
control_url
;
control_info
.
service_type
=
g_strdup
(
service_type
);
if
(
control_url
)
{
GUPnPServiceProxyAction
*
action
=
NULL
;
control_info
.
status
=
PURPLE_UPNP_STATUS_DISCOVERED
;
fire_discovery_callbacks
(
TRUE
);
lookup_internal_ip
();
action
=
gupnp_service_proxy_action_new
(
"GetExternalIPAddress"
,
NULL
);
gupnp_service_proxy_call_action_async
(
proxy
,
action
,
NULL
,
upnp_service_proxy_call_action_cb
,
NULL
);
}
else
{
control_info
.
status
=
PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
;
fire_discovery_callbacks
(
FALSE
);
}
}
static
void
upnp_context_unavailable_cb
(
G_GNUC_UNUSED
GUPnPContextManager
*
manager
,
GUPnPContext
*
context
,
G_GNUC_UNUSED
gpointer
data
)
{
purple_debug_info
(
"upnp"
,
"UPnP context no longer available for interface %s"
,
gssdp_client_get_interface
(
GSSDP_CLIENT
(
context
)));
/* Delete these to prevent them owning refs back on the context. */
g_object_set_data
(
G_OBJECT
(
context
),
"WANIPConnection"
,
NULL
);
g_object_set_data
(
G_OBJECT
(
context
),
"WANPPPConnection"
,
NULL
);
}
static
void
upnp_context_available_cb
(
G_GNUC_UNUSED
GUPnPContextManager
*
manager
,
GUPnPContext
*
context
,
G_GNUC_UNUSED
gpointer
data
)
{
GUPnPControlPoint
*
cp
;
purple_debug_info
(
"upnp"
,
"UPnP context now available for interface %s"
,
gssdp_client_get_interface
(
GSSDP_CLIENT
(
context
)));
cp
=
gupnp_control_point_new
(
context
,
"urn:schemas-upnp-org:service:WANIPConnection:1"
);
g_signal_connect
(
cp
,
"service-proxy-available"
,
G_CALLBACK
(
upnp_service_proxy_available_cb
),
NULL
);
gssdp_resource_browser_set_active
(
GSSDP_RESOURCE_BROWSER
(
cp
),
TRUE
);
g_object_set_data_full
(
G_OBJECT
(
context
),
"WANIPConnection"
,
cp
,
g_object_unref
);
cp
=
gupnp_control_point_new
(
context
,
"urn:schemas-upnp-org:service:WANPPPConnection:1"
);
g_signal_connect
(
cp
,
"service-proxy-available"
,
G_CALLBACK
(
upnp_service_proxy_available_cb
),
NULL
);
gssdp_resource_browser_set_active
(
GSSDP_RESOURCE_BROWSER
(
cp
),
TRUE
);
g_object_set_data_full
(
G_OBJECT
(
context
),
"WANPPPConnection"
,
cp
,
g_object_unref
);
}
void
purple_upnp_discover
(
PurpleUPnPCallback
cb
,
gpointer
cb_data
)
{
if
(
cb
)
{
discovery_callbacks
=
g_slist_append
(
discovery_callbacks
,
cb
);
discovery_callbacks
=
g_slist_append
(
discovery_callbacks
,
cb_data
);
}
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_DISCOVERING
)
{
return
;
}
purple_debug_info
(
"upnp"
,
"Starting discovery on all available interfaces"
);
g_clear_object
(
&
manager
);
manager
=
gupnp_context_manager_create
(
0
);
g_signal_connect
(
manager
,
"context-available"
,
G_CALLBACK
(
upnp_context_available_cb
),
NULL
);
g_signal_connect
(
manager
,
"context-unavailable"
,
G_CALLBACK
(
upnp_context_unavailable_cb
),
NULL
);
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
()
{
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_DISCOVERED
&&
*
control_info
.
publicip
)
return
control_info
.
publicip
;
/* Trigger another UPnP discovery if 5 minutes have elapsed since the
* last one, and it wasn't successful */
if
(
control_info
.
status
<
PURPLE_UPNP_STATUS_DISCOVERING
&&
(
g_get_monotonic_time
()
-
control_info
.
lookup_time
)
>
300
*
G_USEC_PER_SEC
)
{
purple_upnp_discover
(
NULL
,
NULL
);
}
return
NULL
;
}
/* TODO: This could be exported */
static
const
gchar
*
purple_upnp_get_internal_ip
(
void
)
{
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_DISCOVERED
&&
*
control_info
.
internalip
)
return
control_info
.
internalip
;
/* Trigger another UPnP discovery if 5 minutes have elapsed since the
* last one, and it wasn't successful */
if
(
control_info
.
status
<
PURPLE_UPNP_STATUS_DISCOVERING
&&
(
g_get_monotonic_time
()
-
control_info
.
lookup_time
)
>
300
*
G_USEC_PER_SEC
)
{
purple_upnp_discover
(
NULL
,
NULL
);
}
return
NULL
;
}
static
void
looked_up_internal_ip_cb
(
GObject
*
source
,
GAsyncResult
*
result
,
G_GNUC_UNUSED
gpointer
user_data
)
{
GSocketConnection
*
conn
;
GSocketAddress
*
addr
;
GInetSocketAddress
*
inetsockaddr
;
GError
*
error
=
NULL
;
conn
=
g_socket_client_connect_to_host_finish
(
G_SOCKET_CLIENT
(
source
),
result
,
&
error
);
if
(
conn
==
NULL
)
{
purple_debug_error
(
"upnp"
,
"Unable to look up local IP: %s"
,
error
->
message
);
g_clear_error
(
&
error
);
return
;
}
g_strlcpy
(
control_info
.
internalip
,
"0.0.0.0"
,
sizeof
(
control_info
.
internalip
));
addr
=
g_socket_connection_get_local_address
(
conn
,
&
error
);
if
((
inetsockaddr
=
G_INET_SOCKET_ADDRESS
(
addr
))
!=
NULL
)
{
GInetAddress
*
inetaddr
=
g_inet_socket_address_get_address
(
inetsockaddr
);
if
(
g_inet_address_get_family
(
inetaddr
)
==
G_SOCKET_FAMILY_IPV4
&&
!
g_inet_address_get_is_loopback
(
inetaddr
))
{
gchar
*
ip
=
g_inet_address_to_string
(
inetaddr
);
g_strlcpy
(
control_info
.
internalip
,
ip
,
sizeof
(
control_info
.
internalip
));
g_free
(
ip
);
}
}
else
{
purple_debug_error
(
"upnp"
,
"Unable to get local address of connection: %s"
,
error
?
error
->
message
:
"unknown socket address type"
);
g_clear_error
(
&
error
);
}
g_object_unref
(
addr
);
purple_debug_info
(
"upnp"
,
"Local IP: %s"
,
control_info
.
internalip
);
g_object_unref
(
conn
);
}
static
void
lookup_internal_ip
(
void
)
{
gchar
*
host
;
gint
port
;
GSocketClient
*
client
;
GError
*
error
=
NULL
;
if
(
!
g_uri_split_network
(
control_info
.
control_url
,
G_URI_FLAGS_NONE
,
NULL
,
&
host
,
&
port
,
&
error
))
{
purple_debug_error
(
"upnp"
,
"lookup_internal_ip(): Failed In Parse URL: %s"
,
error
->
message
);
return
;
}
client
=
purple_gio_socket_client_new
(
NULL
,
&
error
);
if
(
client
==
NULL
)
{
purple_debug_error
(
"upnp"
,
"Get Local IP Connect to %s:%d Failed: %s"
,
host
,
port
,
error
->
message
);
g_clear_error
(
&
error
);
g_free
(
host
);
return
;
}
purple_debug_info
(
"upnp"
,
"Attempting connection to %s:%u
\n
"
,
host
,
port
);
g_socket_client_connect_to_host_async
(
client
,
host
,
port
,
NULL
,
looked_up_internal_ip_cb
,
NULL
);
g_object_unref
(
client
);
g_free
(
host
);
}
static
void
done_port_mapping_cb
(
G_GNUC_UNUSED
SoupSession
*
session
,
SoupMessage
*
msg
,
gpointer
user_data
)
{
PurpleUPnPMappingAddRemove
*
ar
=
user_data
;
gboolean
success
=
TRUE
;
/* 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"
);
}
ar
->
success
=
success
;
ar
->
tima
=
g_timeout_add
(
0
,
fire_ar_cb_async_and_free
,
ar
);
}
static
void
do_port_mapping_cb
(
gboolean
has_control_mapping
,
gpointer
data
)
{
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
);
return
;
}
ar
->
success
=
FALSE
;
ar
->
tima
=
g_timeout_add
(
0
,
fire_ar_cb_async_and_free
,
ar
);
}
static
gboolean
fire_port_mapping_failure_cb
(
gpointer
data
)
{
PurpleUPnPMappingAddRemove
*
ar
=
data
;
ar
->
tima
=
0
;
do_port_mapping_cb
(
FALSE
,
data
);
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
)
{
PurpleUPnPMappingAddRemove
*
ar
;
ar
=
g_new0
(
PurpleUPnPMappingAddRemove
,
1
);
ar
->
cb
=
cb
;
ar
->
cb_data
=
cb_data
;
ar
->
add
=
TRUE
;
ar
->
portmap
=
portmap
;
g_strlcpy
(
ar
->
protocol
,
protocol
,
sizeof
(
ar
->
protocol
));
/* If we're waiting for a discovery, add to the callbacks list */
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_DISCOVERING
)
{
/* TODO: This will fail because when this cb is triggered,
* the internal IP lookup won't be complete */
discovery_callbacks
=
g_slist_append
(
discovery_callbacks
,
do_port_mapping_cb
);
discovery_callbacks
=
g_slist_append
(
discovery_callbacks
,
ar
);
return
ar
;
}
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_UNDISCOVERED
)
{
purple_upnp_discover
(
do_port_mapping_cb
,
ar
);
return
ar
;
}
else
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
)
{
if
(
g_get_monotonic_time
()
-
control_info
.
lookup_time
>
300
*
G_USEC_PER_SEC
)
{
/* If we haven't had a successful UPnP discovery, check if 5 minutes
* has elapsed since the last try, try again */
purple_upnp_discover
(
do_port_mapping_cb
,
ar
);
}
else
if
(
cb
)
{
/* Asynchronously trigger a failed response */
ar
->
tima
=
g_timeout_add
(
10
,
fire_port_mapping_failure_cb
,
ar
);
}
else
{
/* No need to do anything if nobody expects a response*/
g_free
(
ar
);
ar
=
NULL
;
}
return
ar
;
}
do_port_mapping_cb
(
TRUE
,
ar
);
return
ar
;
}
PurpleUPnPMappingAddRemove
*
purple_upnp_remove_port_mapping
(
unsigned
short
portmap
,
const
char
*
protocol
,
PurpleUPnPCallback
cb
,
gpointer
cb_data
)
{
PurpleUPnPMappingAddRemove
*
ar
;
ar
=
g_new0
(
PurpleUPnPMappingAddRemove
,
1
);
ar
->
cb
=
cb
;
ar
->
cb_data
=
cb_data
;
ar
->
add
=
FALSE
;
ar
->
portmap
=
portmap
;
g_strlcpy
(
ar
->
protocol
,
protocol
,
sizeof
(
ar
->
protocol
));
/* If we're waiting for a discovery, add to the callbacks list */
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_DISCOVERING
)
{
discovery_callbacks
=
g_slist_append
(
discovery_callbacks
,
do_port_mapping_cb
);
discovery_callbacks
=
g_slist_append
(
discovery_callbacks
,
ar
);
return
ar
;
}
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_UNDISCOVERED
)
{
purple_upnp_discover
(
do_port_mapping_cb
,
ar
);
return
ar
;
}
else
if
(
control_info
.
status
==
PURPLE_UPNP_STATUS_UNABLE_TO_DISCOVER
)
{
if
(
g_get_monotonic_time
()
-
control_info
.
lookup_time
>
300
*
G_USEC_PER_SEC
)
{
/* If we haven't had a successful UPnP discovery, check if 5 minutes
* has elapsed since the last try, try again */
purple_upnp_discover
(
do_port_mapping_cb
,
ar
);
}
else
if
(
cb
)
{
/* Asynchronously trigger a failed response */
ar
->
tima
=
g_timeout_add
(
10
,
fire_port_mapping_failure_cb
,
ar
);
}
else
{
/* No need to do anything if nobody expects a response*/
g_free
(
ar
);
ar
=
NULL
;
}
return
ar
;
}
do_port_mapping_cb
(
TRUE
,
ar
);
return
ar
;
}
static
void
purple_upnp_network_config_changed_cb
(
GNetworkMonitor
*
monitor
,
gboolean
available
,
gpointer
data
)
{
/* Reset the control_info to default values */
control_info
.
status
=
PURPLE_UPNP_STATUS_UNDISCOVERED
;
g_clear_pointer
(
&
control_info
.
control_url
,
g_free
);
g_clear_pointer
(
&
control_info
.
service_type
,
g_free
);
control_info
.
publicip
[
0
]
=
'\0'
;
control_info
.
internalip
[
0
]
=
'\0'
;
control_info
.
lookup_time
=
0
;
}
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
),
NULL
);
}
void
purple_upnp_uninit
(
void
)
{
soup_session_abort
(
session
);
g_clear_object
(
&
session
);
g_clear_pointer
(
&
control_info
.
control_url
,
g_free
);
g_clear_pointer
(
&
control_info
.
service_type
,
g_free
);
g_clear_object
(
&
manager
);
}