For the last few days I've been learning more about UPnP and testing it
with the few devices I have around the house. One of these is a cheap
ADSL router, which apparently has the lamest UPnP stack on in existence.
It does however support the WAN IP Connection interface, so
you can use UPnP to get the external IP address and manipulate the port
mapping. I'll skip over the horrific security violations this involves,
because it's a useful demonstration that the majority of people will be
able to test.
Today we'll start simple and get our external IP address using GUPnP. The
first thing to be done is to create a Control Point, which in
the UPnP model handles discovery of resources, be them devices or services
(a device can have multiple services). When creating a control point you
can specify the URN of the resource you want to target. In this case we
want all services providing WANIPConnection so we'd
use urn:schemas-upnp-org:service:WANIPConnection:1. If you want
to browse for all services then use ssdp:all (SSDP being the
Simple Service Discovery Protocol).
static GMainLoop *main_loop;
int
main (int argc, char **argv)
{
GError *error = NULL;
GUPnPContext *context;
GUPnPControlPoint *cp;
/* libsoup requires threading, so we have to initialise it */
g_thread_init (NULL);
g_type_init ();
/* Default GLib context, default host IP, default port */
context = gupnp_context_new (NULL, NULL, 0, &error);
if (error) g_error (error->message);
/* Create a control point targeting WAN IP Connection services */
cp = gupnp_control_point_new
(context, "urn:schemas-upnp-org:service:WANIPConnection:1");
/* The service-proxy-available signal is emitted when any services which match
our target are found */
g_signal_connect (cp,
"service-proxy-available",
G_CALLBACK (service_proxy_available_cb),
NULL);
/* Tell the control point to start searching */
gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
/* Enter the main loop */
main_loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (main_loop);
/* Clean up */
g_main_loop_unref (main_loop);
g_object_unref (cp);
g_object_unref (context);
return 0;
}
static void
service_proxy_available_cb (GUPnPControlPoint *cp,
GUPnPServiceProxy *proxy)
{
/* ... */
}
Now we have an application which searches for the service we specified and
calls service_proxy_available_cb for each one it found. Now, to
get the external IP address we need to invoke
the GetExternalIPAddress action. This action takes no in
arguments, and has a single out argument called "NewExternalIPAddress".
Yes, the naming scheme is stupid. GUPnP has a set of methods to
invoke actions -- which will be very familiar to anyone who has
used dbus-glib -- where you pass a NULL-terminated varargs list
of (name, type, value) tuples for the in arguments, then
a NULL-terminated varargs list of (name, value, return location) tuples
for the out arguments. A simple implementation would be as follows.
static void
service_proxy_available_cb (GUPnPControlPoint *cp,
GUPnPServiceProxy *proxy)
{
GError *error = NULL;
char *ip = NULL;
gupnp_service_proxy_send_action (proxy,
/* Action name and error location */
"GetExternalIPAddress", &error,
/* IN args */
NULL,
/* OUT args */
"NewExternalIPAddress",
G_TYPE_STRING, &ip,
NULL);
if (error == NULL) {
g_print ("External IP address is %s\n", ip);
g_free (ip);
} else {
g_printerr ("Error: %s\n", error->message);
g_error_free (error);
}
g_main_loop_quit (main_loop);
}
Note that _send_action blocks until the service has replied. If you
need to make non-blocking calls then
use gupnp_service_proxy_begin_action which takes a callback.
So, that is searching for services and invoking actions in GUPnP. Next
time I'll cover subscribing to state variables, and routers which can't
count.
NP: Folk But Not Folk, Various