changeset 12909:8e3b85fe4a55

[gaim-migrate @ 15262] Make UPnP truly asynchronous. There are probably still a couple socket calls that should be made nonblocking, but I wanted to commit this before it became even bigger. This contains a number of probable leak fixes in the upnp stuff. The UPnP stuff has been updated to use gaim_url_fetch_request() instead of the specific implementation. To make this all work, I had to make gaim_network_listen() and gaim_network_listen_range() also asynchronous - seems to work just fine apart from the UPnP calls seeming to take longer than they should (I'm planning to look into this). I also triggered a STUN and UPnP discovery on startup so that we hopefully have the information when we need it. committer: Tailor Script <tailor@pidgin.im>
author Daniel Atallah <daniel.atallah@gmail.com>
date Tue, 17 Jan 2006 05:48:51 +0000
parents 4f2b96f23700
children 3097275dbbdd
files plugins/ChangeLog.API src/network.c src/network.h src/protocols/irc/dcc_send.c src/protocols/jabber/si.c src/protocols/oscar/oscar.c src/protocols/simple/simple.c src/stun.c src/upnp.c src/upnp.h
diffstat 10 files changed, 980 insertions(+), 798 deletions(-) [+]
line wrap: on
line diff
--- a/plugins/ChangeLog.API	Tue Jan 17 05:20:38 2006 +0000
+++ b/plugins/ChangeLog.API	Tue Jan 17 05:48:51 2006 +0000
@@ -86,7 +86,8 @@
 	* GaimPluginProtocolInfo: Added media_prpl_ops
 	* gaim_pounce_new(): Added option argument for pounce options
 	* gaim_network_listen() and gaim_network_listen_range(): Added
-	  socket_type parameter to allow creation of UDP listening.
+	  socket_type parameter to allow creation of UDP listening. Modified
+	  to be asynchronous with a Callback to allow for UPnP operation.
 	* GaimPrefCallback: val is now a gconstpointer instead of a gpointer
 	* gtk_imhtml_get_current_format(): the arguments are now set to TRUE or
 	  FALSE.  Previously they were set to TRUE or left alone.  Also, you
--- a/src/network.c	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/network.c	Tue Jan 17 05:48:51 2006 +0000
@@ -32,6 +32,14 @@
 #include "stun.h"
 #include "upnp.h"
 
+typedef struct {
+	int listenfd;
+	int socket_type;
+	gboolean retry;
+	gboolean adding;
+	GaimNetworkListenCallback cb;
+	gpointer cb_data;
+} ListenUPnPData;
 
 const unsigned char *
 gaim_network_ip_atoi(const char *ip)
@@ -143,7 +151,6 @@
 gaim_network_get_my_ip(int fd)
 {
 	const char *ip = NULL;
-	GaimUPnPControlInfo* controlInfo = NULL;
 	GaimStunNatDiscovery *stun;
 
 	/* Check if the user specified an IP manually */
@@ -158,16 +165,9 @@
 		stun = gaim_stun_discover(NULL);
 		if (stun != NULL && stun->status == GAIM_STUN_STATUS_DISCOVERED)
 			return stun->publicip;
-	}	
 
-
-	/* attempt to get the ip from a NAT device */
-	if ((controlInfo = gaim_upnp_discover()) != NULL) {
-		ip = gaim_upnp_get_public_ip(controlInfo);
-
-		g_free(controlInfo->controlURL);
-		g_free(controlInfo->serviceType);
-		g_free(controlInfo);
+		/* attempt to get the ip from a NAT device */
+		ip = gaim_upnp_get_public_ip();
 
 		if (ip != NULL)
 		  return ip;
@@ -178,16 +178,50 @@
 }
 
 
-static int
-gaim_network_do_listen(unsigned short port, int socket_type)
+static void
+gaim_network_set_upnp_port_mapping_cb(gboolean success, gpointer data)
+{
+	ListenUPnPData *ldata = data;
+
+	if (!success) {
+		gaim_debug_warning("network", "Couldn't create UPnP mapping for...\n");
+		if (ldata->retry) {
+			ldata->retry = FALSE;
+			ldata->adding = FALSE;
+			gaim_upnp_remove_port_mapping(
+				gaim_network_get_port_from_fd(ldata->listenfd),
+				(ldata->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
+				gaim_network_set_upnp_port_mapping_cb, ldata);
+			return;
+		}
+	} else if (!ldata->adding) {
+		/* We've tried successfully to remove the port mapping.
+		 * Try to add it again */
+		ldata->adding = TRUE;
+		gaim_upnp_set_port_mapping(
+			gaim_network_get_port_from_fd(ldata->listenfd),
+			(ldata->socket_type == SOCK_STREAM) ? "TCP" : "UDP",
+			gaim_network_set_upnp_port_mapping_cb, ldata);
+		return;
+	}
+
+	if (ldata->cb)
+		ldata->cb(ldata->listenfd, ldata->cb_data);
+
+	g_free(ldata);
+}
+
+
+static gboolean
+gaim_network_do_listen(unsigned short port, int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
 {
 	int listenfd = -1;
 	const int on = 1;
-	GaimUPnPControlInfo* controlInfo = NULL;
 #if HAVE_GETADDRINFO
 	int errnum;
 	struct addrinfo hints, *res, *next;
 	char serv[6];
+	ListenUPnPData *ld;
 
 	/*
 	 * Get a list of addresses on this machine.
@@ -204,9 +238,9 @@
 		if (errnum == EAI_SYSTEM)
 			gaim_debug_warning("network", "getaddrinfo: system error: %s\n", strerror(errno));
 #else
-	gaim_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum);
+		gaim_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum);
 #endif
-		return -1;
+		return FALSE;
 	}
 
 	/*
@@ -230,13 +264,13 @@
 	freeaddrinfo(res);
 
 	if (next == NULL)
-		return -1;
+		return FALSE;
 #else
 	struct sockaddr_in sockin;
 
 	if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) {
 		gaim_debug_warning("network", "socket: %s\n", strerror(errno));
-		return -1;
+		return FALSE;
 	}
 
 	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0)
@@ -249,52 +283,48 @@
 	if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) {
 		gaim_debug_warning("network", "bind: %s\n", strerror(errno));
 		close(listenfd);
-		return -1;
+		return FALSE;
 	}
 #endif
 
 	if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) {
 		gaim_debug_warning("network", "listen: %s\n", strerror(errno));
 		close(listenfd);
-		return -1;
+		return FALSE;
 	}
 	fcntl(listenfd, F_SETFL, O_NONBLOCK);
 
-	if ((controlInfo = gaim_upnp_discover()) != NULL) {
-		char *type_desc = (socket_type == SOCK_STREAM) ? "TCP" : "UDP";
-		if (!gaim_upnp_set_port_mapping(controlInfo,
-				gaim_network_get_port_from_fd(listenfd),
-				type_desc)) {
-			gaim_upnp_remove_port_mapping(controlInfo,
-				gaim_network_get_port_from_fd(listenfd),
-				type_desc);
-			gaim_upnp_set_port_mapping(controlInfo,
-				gaim_network_get_port_from_fd(listenfd),
-				type_desc);
+	gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd));
 
-		}
-		g_free(controlInfo->serviceType);
-		g_free(controlInfo->controlURL);
-		g_free(controlInfo);
-	}
+	ld = g_new0(ListenUPnPData, 1);
+	ld->listenfd = listenfd;
+	ld->adding = TRUE;
+	ld->retry = TRUE;
+	ld->cb = cb;
+	ld->cb_data = cb_data;
 
-	gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd));
-	return listenfd;
+	gaim_upnp_set_port_mapping(
+			gaim_network_get_port_from_fd(listenfd),
+			(socket_type == SOCK_STREAM) ? "TCP" : "UDP",
+			gaim_network_set_upnp_port_mapping_cb, ld);
+
+	return TRUE;
 }
 
-int
-gaim_network_listen(unsigned short port, int socket_type)
+gboolean
+gaim_network_listen(unsigned short port, int socket_type,
+		GaimNetworkListenCallback cb, gpointer cb_data)
 {
 	g_return_val_if_fail(port != 0, -1);
 
-	return gaim_network_do_listen(port, socket_type);
+	return gaim_network_do_listen(port, socket_type, cb, cb_data);
 }
 
-int
+gboolean
 gaim_network_listen_range(unsigned short start, unsigned short end,
-		int socket_type)
+		int socket_type, GaimNetworkListenCallback cb, gpointer cb_data)
 {
-	int ret = -1;
+	gboolean ret = FALSE;
 
 	if (gaim_prefs_get_bool("/core/network/ports_range_use")) {
 		start = gaim_prefs_get_int("/core/network/ports_range_start");
@@ -305,8 +335,8 @@
 	}
 
 	for (; start <= end; start++) {
-		ret = gaim_network_do_listen(start, socket_type);
-		if (ret >= 0)
+		ret = gaim_network_do_listen(start, socket_type, cb, cb_data);
+		if (ret)
 			break;
 	}
 
@@ -339,4 +369,6 @@
 	gaim_prefs_add_bool  ("/core/network/ports_range_use", FALSE);
 	gaim_prefs_add_int   ("/core/network/ports_range_start", 1024);
 	gaim_prefs_add_int   ("/core/network/ports_range_end", 2048);
+
+	gaim_upnp_discover(NULL, NULL);
 }
--- a/src/network.h	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/network.h	Tue Jan 17 05:48:51 2006 +0000
@@ -34,6 +34,8 @@
 /**************************************************************************/
 /*@{*/
 
+typedef void (*GaimNetworkListenCallback) (int listenfd, gpointer data);
+
 /**
  * Converts a dot-decimal IP address to an array of unsigned
  * chars.  For example, converts 192.168.0.1 to a 4 byte
@@ -109,19 +111,24 @@
  * would want to do that is beyond me.
  *
  * This opens a listening port. The caller will want to set up a watcher
- * of type GAIM_INPUT_READ on the returned fd. It will probably call
- * accept in the callback, and then possibly remove the watcher and close
+ * of type GAIM_INPUT_READ on the fd returned in cb. It will probably call
+ * accept in the watcher callback, and then possibly remove the watcher and close
  * the listening socket, and add a new watcher on the new socket accept
  * returned.
  *
  * @param port The port number to bind to.  Must be greater than 0.
  * @param socket_type The type of socket to open for listening.
  *   This will be either SOCK_STREAM for TCP or SOCK_DGRAM for UDP.
+ * @param cb The callback to be invoked when the port to listen on is available.
+ *           The file descriptor of the listening socket will be specified in
+ *           this callback, or -1 if no socket could be established.
+ * @param cb_data extra data to be returned when cb is called
  *
- * @return The file descriptor of the listening socket, or -1 if
- *         no socket could be established.
+ * @return TRUE if the callback will be invoked, or FALSE if unable to obtain
+ *              a local socket to listen on.
  */
-int gaim_network_listen(unsigned short port, int socket_type);
+gboolean gaim_network_listen(unsigned short port, int socket_type,
+	GaimNetworkListenCallback cb, gpointer cb_data);
 
 /**
  * Opens a listening port selected from a range of ports.  The range of
@@ -132,8 +139,8 @@
  * Otherwise a port is chosen at random by the kernel.
  *
  * This opens a listening port. The caller will want to set up a watcher
- * of type GAIM_INPUT_READ on the returned fd. It will probably call
- * accept in the callback, and then possibly remove the watcher and close
+ * of type GAIM_INPUT_READ on the fd returned in cb. It will probably call
+ * accept in the watcher callback, and then possibly remove the watcher and close
  * the listening socket, and add a new watcher on the new socket accept
  * returned.
  *
@@ -144,12 +151,16 @@
  *            arg in prefs.
  * @param socket_type The type of socket to open for listening.
  *   This will be either SOCK_STREAM for TCP or SOCK_DGRAM for UDP.
+ * @param cb The callback to be invoked when the port to listen on is available.
+ *           The file descriptor of the listening socket will be specified in
+ *           this callback, or -1 if no socket could be established.
+ * @param cb_data extra data to be returned when cb is called
  *
- * @return The file descriptor of the listening socket, or -1 if
- *         no socket could be established.
+ * @return TRUE if the callback will be invoked, or FALSE if unable to obtain
+ *              a local socket to listen on.
  */
-int gaim_network_listen_range(unsigned short start, unsigned short end,
-	int socket_type);
+gboolean gaim_network_listen_range(unsigned short start, unsigned short end,
+	int socket_type, GaimNetworkListenCallback cb, gpointer cb_data);
 
 /**
  * Gets a port number from a file descriptor.
--- a/src/protocols/irc/dcc_send.c	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/protocols/irc/dcc_send.c	Tue Jan 17 05:48:51 2006 +0000
@@ -250,23 +250,29 @@
 	gaim_xfer_start(xfer, conn, NULL, 0);
 }
 
-/*
- * This function is called after the user has selected a file to send.
- */
-static void irc_dccsend_send_init(GaimXfer *xfer) {
-	struct irc_xfer_send_data *xd = xfer->data;
-	GaimConnection *gc = gaim_account_get_connection(gaim_xfer_get_account(xfer));
-	struct irc_conn *irc = gc->proto_data;
-	int sock;
+static void
+irc_dccsend_network_listen_cb(int sock, gpointer data)
+{
+	GaimXfer *xfer = data;
+	struct irc_xfer_send_data *xd;
+	GaimConnection *gc;
+	struct irc_conn *irc;
 	const char *arg[2];
 	char *tmp;
 	struct in_addr addr;
 	unsigned short int port;
 
-	xfer->filename = g_path_get_basename(xfer->local_filename);
+	if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL
+			|| gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_REMOTE) {
+		gaim_xfer_unref(xfer);
+		return;
+	}
 
-	/* Create a listening socket */
-	sock = gaim_network_listen_range(0, 0, SOCK_STREAM);
+	xd = xfer->data;
+	gc = gaim_account_get_connection(gaim_xfer_get_account(xfer));
+	irc = gc->proto_data;
+
+	gaim_xfer_unref(xfer);
 
 	if (sock < 0) {
 		gaim_notify_error(gc, NULL, _("File Transfer Failed"),
@@ -294,6 +300,27 @@
 	g_free(tmp);
 }
 
+/*
+ * This function is called after the user has selected a file to send.
+ */
+static void irc_dccsend_send_init(GaimXfer *xfer) {
+	GaimConnection *gc = gaim_account_get_connection(gaim_xfer_get_account(xfer));
+
+	xfer->filename = g_path_get_basename(xfer->local_filename);
+
+	gaim_xfer_ref(xfer);
+
+	/* Create a listening socket */
+	if (!gaim_network_listen_range(0, 0, SOCK_STREAM,
+			irc_dccsend_network_listen_cb, xfer)) {
+		gaim_xfer_unref(xfer);
+		gaim_notify_error(gc, NULL, _("File Transfer Failed"),
+		                  _("Gaim could not open a listening port."));
+		gaim_xfer_cancel_local(xfer);
+	}
+
+}
+
 GaimXfer *irc_dccsend_new_xfer(GaimConnection *gc, const char *who) {
 	GaimXfer *xfer;
 	struct irc_xfer_send_data *xd;
--- a/src/protocols/jabber/si.c	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/protocols/jabber/si.c	Tue Jan 17 05:48:51 2006 +0000
@@ -11,6 +11,7 @@
  * 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
@@ -414,15 +415,28 @@
 			jabber_si_xfer_bytestreams_send_read_cb, xfer);
 }
 
-
 static void
-jabber_si_xfer_bytestreams_send_init(GaimXfer *xfer)
+jabber_si_xfer_bytestreams_listen_cb(int sock, gpointer data)
 {
-	JabberSIXfer *jsx = xfer->data;
+	GaimXfer *xfer = data;
+	JabberSIXfer *jsx;
 	JabberIq *iq;
 	xmlnode *query, *streamhost;
 	char *jid, *port;
-	int fd;
+
+	if (gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) {
+		gaim_xfer_unref(xfer);
+		return;
+	}
+
+	jsx = xfer->data;
+
+	gaim_xfer_unref(xfer);
+
+	if (sock < 0) {
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
 
 	iq = jabber_iq_new_query(jsx->js, JABBER_IQ_SET,
 			"http://jabber.org/protocol/bytestreams");
@@ -432,22 +446,20 @@
 	xmlnode_set_attrib(query, "sid", jsx->stream_id);
 
 	streamhost = xmlnode_new_child(query, "streamhost");
-	jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node, jsx->js->user->domain, jsx->js->user->resource);
+	jid = g_strdup_printf("%s@%s/%s", jsx->js->user->node,
+			jsx->js->user->domain, jsx->js->user->resource);
 	xmlnode_set_attrib(streamhost, "jid", jid);
 	g_free(jid);
 
-	if((fd = gaim_network_listen_range(0, 0, SOCK_STREAM)) < 0) {
-		/* XXX: couldn't open a port, we're fscked */
-		return;
-	}
-
-	xmlnode_set_attrib(streamhost, "host",  gaim_network_get_my_ip(jsx->js->fd));
-	xfer->local_port = gaim_network_get_port_from_fd(fd);
+	/* XXX: shouldn't we use the public IP or something? here */
+	xmlnode_set_attrib(streamhost, "host",
+			gaim_network_get_my_ip(jsx->js->fd));
+	xfer->local_port = gaim_network_get_port_from_fd(sock);
 	port = g_strdup_printf("%hu", xfer->local_port);
 	xmlnode_set_attrib(streamhost, "port", port);
 	g_free(port);
 
-	xfer->watcher = gaim_input_add(fd, GAIM_INPUT_READ,
+	xfer->watcher = gaim_input_add(sock, GAIM_INPUT_READ,
 			jabber_si_xfer_bytestreams_send_connected_cb, xfer);
 
 	/* XXX: insert proxies here */
@@ -455,6 +467,22 @@
 	/* XXX: callback to find out which streamhost they used, or see if they
 	 * screwed it up */
 	jabber_iq_send(iq);
+
+}
+
+static void
+jabber_si_xfer_bytestreams_send_init(GaimXfer *xfer)
+{
+	gaim_xfer_ref(xfer);
+
+	if(!gaim_network_listen_range(0, 0, SOCK_STREAM,
+				jabber_si_xfer_bytestreams_listen_cb, xfer)) {
+		gaim_xfer_unref(xfer);
+		/* XXX: couldn't open a port, we're fscked */
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+
 }
 
 static void jabber_si_xfer_send_method_cb(JabberStream *js, xmlnode *packet,
--- a/src/protocols/oscar/oscar.c	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/protocols/oscar/oscar.c	Tue Jan 17 05:48:51 2006 +0000
@@ -1507,6 +1507,51 @@
 	g_free(data);
 }
 
+struct dir_im_listen {
+	struct oscar_direct_im *dim;
+	const guchar *cookie;
+};
+
+static void
+oscar_direct_im_listen_cb(int listenfd, gpointer data) {
+	struct dir_im_listen *dim_l = data;
+	const char *ip;
+	OscarData *od;
+	struct oscar_direct_im *dim = dim_l->dim;
+
+	od = (OscarData *)dim->gc->proto_data;
+
+	/* XXX: shouldn't this be your public IP or something? */
+	ip = gaim_network_get_my_ip(od->conn ? od->conn->fd : -1);
+
+	if (listenfd >= 0)
+		dim->conn = aim_odc_initiate(od->sess, dim->name, listenfd,
+				gaim_network_ip_atoi(ip),
+				gaim_network_get_port_from_fd(listenfd), dim_l->cookie);
+
+	if (dim->conn != NULL) {
+		char *tmp;
+		GaimConversation *conv;
+
+		od->direct_ims = g_slist_append(od->direct_ims, dim);
+		dim->watcher = gaim_input_add(dim->conn->fd, GAIM_INPUT_READ,
+						oscar_callback, dim->conn);
+		aim_conn_addhandler(od->sess, dim->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIM_ESTABLISHED,
+					gaim_odc_initiate, 0);
+
+		conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, dim->gc->account, dim->name);
+		tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for Direct IM."), dim->name, ip,
+		                      gaim_network_get_port_from_fd(listenfd));
+		gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL));
+		g_free(tmp);
+	} else {
+		gaim_notify_error(dim->gc, NULL, _("Unable to open Direct IM"), NULL);
+		oscar_direct_im_destroy(od, dim);
+	}
+
+	g_free(dim_l);
+}
+
 /* this function is used to initiate a direct im session with someone.
  * we start listening on a port and send a request. they either connect
  * or send some kind of reply. If they can't connect, they ask us to
@@ -1520,8 +1565,7 @@
 static void oscar_direct_im_initiate(GaimConnection *gc, const char *who, const guchar *cookie) {
 	OscarData *od;
 	struct oscar_direct_im *dim;
-	int listenfd;
-	const char *ip;
+	struct dir_im_listen *dim_l;
 
 	od = (OscarData *)gc->proto_data;
 
@@ -1540,28 +1584,14 @@
 	dim->gc = gc;
 	g_snprintf(dim->name, sizeof dim->name, "%s", who);
 
-	listenfd = gaim_network_listen_range(5190, 5199, SOCK_STREAM);
-	ip = gaim_network_get_my_ip(od->conn ? od->conn->fd : -1);
-	if (listenfd >= 0)
-		dim->conn = aim_odc_initiate(od->sess, who, listenfd, gaim_network_ip_atoi(ip), gaim_network_get_port_from_fd(listenfd), cookie);
-	if (dim->conn != NULL) {
-		char *tmp;
-		GaimConversation *conv;
-
-		od->direct_ims = g_slist_append(od->direct_ims, dim);
-		dim->watcher = gaim_input_add(dim->conn->fd, GAIM_INPUT_READ,
-						oscar_callback, dim->conn);
-		aim_conn_addhandler(od->sess, dim->conn, AIM_CB_FAM_OFT, AIM_CB_OFT_DIRECTIM_ESTABLISHED,
-					gaim_odc_initiate, 0);
-
-		conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, dim->gc->account, who);
-		tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for Direct IM."), who, ip,
-		                      gaim_network_get_port_from_fd(listenfd));
-		gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL));
-		g_free(tmp);
-	} else {
+	dim_l = g_new0(struct dir_im_listen, 1);
+	dim_l->dim = dim;
+	dim_l->cookie = cookie;
+
+	if(!gaim_network_listen_range(5190, 5199, SOCK_STREAM, oscar_direct_im_listen_cb, dim)) {
 		gaim_notify_error(gc, NULL, _("Unable to open Direct IM"), NULL);
 		oscar_direct_im_destroy(od, dim);
+		g_free(dim_l);
 	}
 }
 
@@ -2545,25 +2575,28 @@
 	}
 }
 
-
-/*
- * Opens a listener socket in preparation for sending a file
- * This is not called if we are using a rendezvous proxy server
- */
-static void oscar_xfer_init_send(GaimXfer *xfer)
-{
-	struct aim_oft_info *oft_info = xfer->data;
-	GaimConnection *gc = oft_info->sess->aux_data;
-	OscarData *od = gc->proto_data;
-	int listenfd;
-
-	gaim_debug_info("oscar", "AAA - in oscar_xfer_init_send\n");
-
-	/* Create a listening socket and an associated libfaim conn */
-	if ((listenfd = gaim_network_listen_range(5190, 5199, SOCK_STREAM)) < 0) {
+static void
+oscar_xfer_init_listen_cb(int listenfd, gpointer data) {
+	GaimXfer *xfer = data;
+	struct aim_oft_info *oft_info;
+	GaimConnection *gc;
+	OscarData *od;
+
+	/* If the ft was canceled before we get here, don't continue */
+	if(gaim_xfer_get_status(xfer) == GAIM_XFER_STATUS_CANCEL_LOCAL) {
+		gaim_xfer_unref(xfer);
+		return;
+	}
+
+	oft_info = xfer->data;
+	gc = oft_info->sess->aux_data;
+	od = gc->proto_data;
+
+	if (listenfd < 0) {
 		gaim_xfer_cancel_local(xfer);
 		return;
 	}
+
 	xfer->local_port = gaim_network_get_port_from_fd(listenfd);
 	oft_info->port = xfer->local_port;
 	if (aim_sendfile_listen(od->sess, oft_info, listenfd) != 0) {
@@ -2583,6 +2616,26 @@
 	oscar_send_file_request(xfer);
 }
 
+
+/*
+ * Opens a listener socket in preparation for sending a file
+ * This is not called if we are using a rendezvous proxy server
+ */
+static void oscar_xfer_init_send(GaimXfer *xfer)
+{
+	gaim_debug_info("oscar", "AAA - in oscar_xfer_init_send\n");
+
+	gaim_xfer_ref(xfer);
+
+	/* Create a listening socket and an associated libfaim conn */
+	if (!gaim_network_listen_range(5190, 5199, SOCK_STREAM,
+				oscar_xfer_init_listen_cb, xfer)) {
+		gaim_xfer_unref(xfer);
+		gaim_xfer_cancel_local(xfer);
+		return;
+	}
+}
+
 /*
  * "On second thought, you don't deserve this file."
  */
--- a/src/protocols/simple/simple.c	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/protocols/simple/simple.c	Tue Jan 17 05:48:51 2006 +0000
@@ -1200,6 +1200,26 @@
 	return (gaim_utf8_strcasecmp(nick1, nick2) == 0);
 }
 
+static void simple_udp_host_resolved_listen_cb(int listenfd, gpointer data) {
+	struct simple_account_data *sip = (struct simple_account_data*) data;
+
+	if(listenfd == -1) {
+		gaim_connection_error(sip->gc, _("Could not create listen socket"));
+		return;
+	}
+
+	sip->fd = listenfd;
+
+	sip->listenport = gaim_network_get_port_from_fd(sip->fd);
+	sip->listenfd = sip->fd;
+
+	sip->listenpa = gaim_input_add(sip->fd, GAIM_INPUT_READ, simple_udp_process, sip->gc);
+
+	sip->resendtimeout = gaim_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
+	sip->registertimeout = gaim_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
+	do_register(sip);
+}
+
 static void simple_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message) {
 	struct simple_account_data *sip = (struct simple_account_data*) data;
 	int addr_size;
@@ -1221,21 +1241,36 @@
 	}
 
 	/* create socket for incoming connections */
-	sip->fd = gaim_network_listen_range(5060, 5160, SOCK_DGRAM);
+	if(!gaim_network_listen_range(5060, 5160, SOCK_DGRAM,
+				simple_udp_host_resolved_listen_cb, sip)) {
+		gaim_connection_error(sip->gc, _("Could not create listen socket"));
+		return;
+	}
+}
 
-	if(sip->fd == -1) {
+static void
+simple_tcp_connect_listen_cb(int listenfd, gpointer data) {
+	struct simple_account_data *sip = (struct simple_account_data*) data;
+	int error = 0;
+
+	sip->listenfd = listenfd;
+	if(sip->listenfd == -1) {
 		gaim_connection_error(sip->gc, _("Could not create listen socket"));
 		return;
 	}
 
-	sip->listenport = gaim_network_get_port_from_fd(sip->fd);
-	sip->listenfd = sip->fd;
-
-	sip->listenpa = gaim_input_add(sip->fd, GAIM_INPUT_READ, simple_udp_process, sip->gc);
-
-	sip->resendtimeout = gaim_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
-	sip->registertimeout = gaim_timeout_add((rand()%100)+10*1000, (GSourceFunc)subscribe_timeout, sip);
-	do_register(sip);
+	gaim_debug_info("simple", "listenfd: %d\n", sip->listenfd);
+	sip->listenport = gaim_network_get_port_from_fd(sip->listenfd);
+	sip->listenpa = gaim_input_add(sip->listenfd, GAIM_INPUT_READ,
+			simple_newconn_cb, sip->gc);
+	gaim_debug_info("simple","connecting to %s port %d\n",
+			sip->realhostname, sip->realport);
+	/* open tcp connection to the server */
+	error = gaim_proxy_connect(sip->account, sip->realhostname,
+			sip->realport, login_cb, sip->gc);
+	if(error) {
+		gaim_connection_error(sip->gc, _("Couldn't create socket"));
+	}
 }
 
 static void srvresolved(GaimSrvResponse *resp, int results, gpointer data) {
@@ -1244,7 +1279,6 @@
 	gchar *hostname;
 	int port = gaim_account_get_int(sip->account, "port", 0);
 
-	int error = 0;
 
 	/* find the host to connect to */
 	if(results) {
@@ -1266,21 +1300,11 @@
 	/* TCP case */
 	if(! sip->udp) {
 		/* create socket for incoming connections */
-		sip->listenfd = gaim_network_listen_range(5060, 5160, SOCK_STREAM);
-		if(sip->listenfd == -1) {
+		if(!gaim_network_listen_range(5060, 5160, SOCK_STREAM,
+					simple_tcp_connect_listen_cb, sip)) {
 			gaim_connection_error(sip->gc, _("Could not create listen socket"));
 			return;
 		}
-		gaim_debug_info("simple", "listenfd: %d\n", sip->listenfd);
-		sip->listenport = gaim_network_get_port_from_fd(sip->listenfd);
-		sip->listenpa = gaim_input_add(sip->listenfd, GAIM_INPUT_READ, simple_newconn_cb, sip->gc);
-		gaim_debug_info("simple","connecting to %s port %d\n", hostname, port);
-		/* open tcp connection to the server */
-		error = gaim_proxy_connect(sip->account, hostname, port, login_cb, sip->gc);
-		if(error) {
-			gaim_connection_error(sip->gc, _("Couldn't create socket"));
-		}
-
 	} else { /* UDP */
 		gaim_debug_info("simple", "using udp with server %s and port %d\n", hostname, port);
 
--- a/src/stun.c	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/stun.c	Tue Jan 17 05:48:51 2006 +0000
@@ -273,22 +273,14 @@
 	}
 }
 
-static void hbn_cb(GSList *hosts, gpointer data, const char *error_message) {
+
+static void hbn_listen_cb(int fd, gpointer data) {
+	GSList *hosts = data;
 	struct stun_conn *sc;
 	static struct stun_header hdr_data;
-	int ret, fd;
+	int ret;
 
-	if(!hosts || !hosts->data) {
-		nattype.status = GAIM_STUN_STATUS_UNDISCOVERED;
-		nattype.lookup_time = time(NULL);
-		do_callbacks();
-		return;
-	}
-
-
-	fd = gaim_network_listen_range(12108, 12208, SOCK_DGRAM);
-
-	if(!fd) {
+	if(fd < 0) {
 		nattype.status = GAIM_STUN_STATUS_UNKNOWN;
 		nattype.lookup_time = time(NULL);
 		do_callbacks();
@@ -337,6 +329,25 @@
 	sc->timeout = gaim_timeout_add(500, (GSourceFunc) timeoutfunc, sc);
 }
 
+static void hbn_cb(GSList *hosts, gpointer data, const char *error_message) {
+
+	if(!hosts || !hosts->data) {
+		nattype.status = GAIM_STUN_STATUS_UNDISCOVERED;
+		nattype.lookup_time = time(NULL);
+		do_callbacks();
+		return;
+	}
+
+	if (!gaim_network_listen_range(12108, 12208, SOCK_DGRAM, hbn_listen_cb, hosts)) {
+		nattype.status = GAIM_STUN_STATUS_UNKNOWN;
+		nattype.lookup_time = time(NULL);
+		do_callbacks();
+		return;
+	}
+
+
+}
+
 static void do_test1(GaimSrvResponse *resp, int results, gpointer sdata) {
 	const char *servername = sdata;
 	int port = 3478;
@@ -417,4 +428,5 @@
 
 void gaim_stun_init() {
 	gaim_prefs_add_string("/core/network/stun_server", "");
+	gaim_stun_discover(NULL);
 }
--- a/src/upnp.c	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/upnp.c	Tue Jan 17 05:48:51 2006 +0000
@@ -34,33 +34,14 @@
 #include "upnp.h"
 
 
-/**
- * Information on the httpResponse callback
- */
-typedef struct
-{
-	guint inpa;		/* gaim_input_add handle */
-	guint tima;		/* gaim_timout_add handle */
-	gchar* sendBuffer;	/* send data */
-	gchar* recvBuffer;	/* response data */
-	guint totalSizeRecv;
-	gboolean done;
-
-} NetResponseData;
-
-
 /***************************************************************
 ** General Defines                                             *
 ****************************************************************/
 #define HTTP_OK "200 OK"
 #define DEFAULT_HTTP_PORT 80
-#define MAX_PORT_SIZE 6
 #define SIZEOF_HTTP 7         /* size of "http://" string */
-#define RECEIVE_TIMEOUT 10000
-#define CONSECUTIVE_RECEIVE_TIMEOUT 500
 #define DISCOVERY_TIMEOUT 1000
 
-
 /***************************************************************
 ** Discovery/Description Defines                               *
 ****************************************************************/
@@ -70,196 +51,118 @@
 #define HTTPMU_HOST_ADDRESS "239.255.255.250"
 #define HTTPMU_HOST_PORT 1900
 
-#define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:"      \
-                              "%s"
+#define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s"
 
-#define SEARCH_REQUEST_STRING "M-SEARCH * HTTP/1.1\r\n"            \
-                              "MX: 2\r\n"                          \
-                              "HOST: 239.255.255.250:1900\r\n"     \
-                              "MAN: \"ssdp:discover\"\r\n"         \
-                              "ST: urn:schemas-upnp-org:service:"  \
-                              "%s\r\n"                             \
-                              "\r\n"
+#define SEARCH_REQUEST_STRING \
+	"M-SEARCH * HTTP/1.1\r\n" \
+	"MX: 2\r\n" \
+	"HOST: 239.255.255.250:1900\r\n" \
+	"MAN: \"ssdp:discover\"\r\n" \
+	"ST: urn:schemas-upnp-org:service:%s\r\n" \
+	"\r\n"
 
-#define MAX_DISCOVERY_RECEIVE_SIZE 400
-#define MAX_DESCRIPTION_RECEIVE_SIZE 7000
-#define MAX_DESCRIPTION_HTTP_HEADER_SIZE 100
-
+#define WAN_IP_CONN_SERVICE "WANIPConnection:1"
+#define WAN_PPP_CONN_SERVICE "WANPPPConnection:1"
 
 /******************************************************************
 ** Action Defines                                                 *
 *******************************************************************/
-#define HTTP_HEADER_ACTION "POST /%s HTTP/1.1\r\n"                         \
-                           "HOST: %s\r\n"                                  \
-                           "SOAPACTION: "                                  \
-                           "\"urn:schemas-upnp-org:"                       \
-                           "service:%s#%s\"\r\n"                           \
-                           "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n"\
-                           "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n"
+#define HTTP_HEADER_ACTION \
+	"POST /%s HTTP/1.1\r\n" \
+	"HOST: %s:%d\r\n" \
+	"SOAPACTION: \"urn:schemas-upnp-org:service:%s#%s\"\r\n" \
+	"CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n" \
+	"CONTENT-LENGTH: %" G_GSIZE_FORMAT "\r\n\r\n"
 
-#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="                                     \
-                     "\"urn:schemas-upnp-org:service:%s\">\r\n%s"         \
-                     "</u:%s>\r\n"                                        \
-                     "</s:Body>\r\n"                                      \
-                     "</s:Envelope>\r\n"
+#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=\"urn:schemas-upnp-org:service:%s\">\r\n" \
+	      "%s" \
+	    "</u:%s>\r\n" \
+	  "</s:Body>\r\n" \
+	"</s:Envelope>"
 
 #define PORT_MAPPING_LEASE_TIME "0"
 #define PORT_MAPPING_DESCRIPTION "GAIM_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 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"
+#define DELETE_PORT_MAPPING_PARAMS \
+	"<NewRemoteHost></NewRemoteHost>\r\n" \
+	"<NewExternalPort>%i</NewExternalPort>\r\n" \
+	"<NewProtocol>%s</NewProtocol>\r\n"
+
+typedef enum {
+	GAIM_UPNP_STATUS_UNDISCOVERED = -1,
+	GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER,
+	GAIM_UPNP_STATUS_DISCOVERING,
+	GAIM_UPNP_STATUS_DISCOVERED
+} GaimUPnPStatus;
 
-
-static void
-gaim_upnp_timeout(gpointer data, gint source, GaimInputCondition cond)
-{
-	NetResponseData* nrd = data;
-
-	gaim_input_remove(nrd->inpa);
-	gaim_timeout_remove(nrd->tima);
+typedef struct {
+	GaimUPnPStatus status;
+	gchar* control_url;
+	gchar service_type[20];
+	char publicip[16];
+	char internalip[16];
+	time_t lookup_time;
+} GaimUPnPControlInfo;
 
-	if(nrd->totalSizeRecv == 0 && nrd->recvBuffer != NULL) {
-		g_free(nrd->recvBuffer);
-		nrd->recvBuffer = NULL;
-	} else if(nrd->recvBuffer != NULL) {
-		nrd->recvBuffer[nrd->totalSizeRecv] = '\0';
-	}
+typedef struct {
+	guint inpa;	/* gaim_input_add handle */
+	guint tima;	/* gaim_timeout_add handle */
+	int fd;
+	struct sockaddr_in server;
+	gchar service_type[25];
+	int retry_count;
+	gchar *full_url;
+} UPnPDiscoveryData;
 
-	nrd->done = TRUE;
-}
+typedef struct {
+	unsigned short portmap;
+	gchar protocol[4];
+	gboolean add;
+	GaimUPnPCallback cb;
+	gpointer cb_data;
+} UPnPMappingAddRemove;
+
+static GaimUPnPControlInfo control_info = {
+	GAIM_UPNP_STATUS_UNDISCOVERED,
+	NULL, "\0", "\0", "\0", 0};
+
+static GSList *discovery_callbacks = NULL;
+
+static void gaim_upnp_discover_send_broadcast(UPnPDiscoveryData *dd);
+static void lookup_public_ip(void);
+static void lookup_internal_ip(void);
 
 
 static void
-gaim_upnp_http_read(gpointer data, gint sock, GaimInputCondition cond)
-{
-	int sizeRecv;
-	NetResponseData* nrd = data;
-
-	sizeRecv = recv(sock, &(nrd->recvBuffer[nrd->totalSizeRecv]),
-		MAX_DESCRIPTION_RECEIVE_SIZE-nrd->totalSizeRecv, 0);
-	if(sizeRecv < 0 && errno != EINTR) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_http_read(): recv < 0: %i!\n\n", errno);
-		g_free(nrd->recvBuffer);
-		nrd->recvBuffer = NULL;
-		gaim_timeout_remove(nrd->tima);
-		gaim_input_remove(nrd->inpa);
-		nrd->done = TRUE;
-		return;
-	}else if(errno == EINTR) {
-		sizeRecv = 0;
-	}
-	nrd->totalSizeRecv += sizeRecv;
-
-	if(sizeRecv == 0) {
-		gaim_timeout_remove(nrd->tima);
-		gaim_input_remove(nrd->inpa);
-		if(nrd->totalSizeRecv == 0) {
-			gaim_debug_error("upnp",
-				"gaim_upnp_http_read(): totalSizeRecv == 0\n\n");
-			g_free(nrd->recvBuffer);
-			nrd->recvBuffer = NULL;
-		} else {
-			nrd->recvBuffer[nrd->totalSizeRecv] = '\0';
-		}
-		nrd->done = TRUE;
-	} else {
-		gaim_timeout_remove(nrd->tima);
-		gaim_input_remove(nrd->inpa);
-		nrd->tima = gaim_timeout_add(CONSECUTIVE_RECEIVE_TIMEOUT,
-			(GSourceFunc)gaim_upnp_timeout, nrd);
-		nrd->inpa = gaim_input_add(sock, GAIM_INPUT_READ,
-			gaim_upnp_http_read, nrd);
-	}
-}
-
-
-static void
-gaim_upnp_http_send(gpointer data, gint sock, GaimInputCondition cond)
+fire_discovery_callbacks(gboolean success)
 {
-	gsize sizeSent, totalSizeSent = 0;
-	NetResponseData* nrd = data;
-
-	gaim_timeout_remove(nrd->tima);
-	while(totalSizeSent < strlen(nrd->sendBuffer)) {
-		sizeSent = send(sock,(gchar*)(nrd->sendBuffer+totalSizeSent),
-			strlen(nrd->sendBuffer)-totalSizeSent,0);
-		if(sizeSent <= 0 && errno != EINTR) {
-			gaim_debug_error("upnp",
-				"gaim_upnp_http_request(): Failed In send\n\n");
-			nrd->done = TRUE;
-			g_free(nrd->recvBuffer);
-			nrd->recvBuffer = NULL;
-			close(sock);
-			return;
-		}else if(errno == EINTR) {
-			sizeSent = 0;
-		}
-		totalSizeSent += sizeSent;
-	}
-
-	nrd->tima = gaim_timeout_add(RECEIVE_TIMEOUT,
-		(GSourceFunc)gaim_upnp_timeout, nrd);
-	nrd->inpa = gaim_input_add(sock, GAIM_INPUT_READ,
-		gaim_upnp_http_read, nrd);
-	while (!nrd->done) {
-		g_main_context_iteration(NULL, TRUE);
+	while(discovery_callbacks) {
+		gpointer data;
+		GaimUPnPCallback cb = discovery_callbacks->data;
+		discovery_callbacks = g_slist_remove(discovery_callbacks, cb);
+		data = discovery_callbacks->data;
+		discovery_callbacks = g_slist_remove(discovery_callbacks, data);
+		cb(success, data);
 	}
-	close(sock);
-}
-
-
-static gchar*
-gaim_upnp_http_request(const gchar* address, int port, gchar* httpRequest)
-{
-	gchar* recvBuffer;
-	NetResponseData* nrd = (NetResponseData*)g_malloc0(sizeof(NetResponseData));
-	nrd->sendBuffer = httpRequest;
-	nrd->recvBuffer = (gchar*)g_malloc(MAX_DESCRIPTION_RECEIVE_SIZE);
-
-	nrd->tima = gaim_timeout_add(RECEIVE_TIMEOUT,
-		(GSourceFunc)gaim_upnp_timeout, nrd);
-
-	if(gaim_proxy_connect(NULL, address, port, gaim_upnp_http_send, nrd)) {
-
-		gaim_debug_error("upnp", "Connect Failed: Address: %s @@@ Port %d @@@ Request %s\n\n",
-			address, port, nrd->sendBuffer);
-
-		gaim_timeout_remove(nrd->tima);
-		g_free(nrd->recvBuffer);
-		nrd->recvBuffer = NULL;
-	} else {
-		while (!nrd->done) {
-			g_main_context_iteration(NULL, TRUE);
-		}
-	}
-
-	recvBuffer = nrd->recvBuffer;
-	g_free(nrd);
-
-	return recvBuffer;
 }
 
 
@@ -270,7 +173,8 @@
 	if(deviceTypeNode == NULL) {
 		return FALSE;
 	}
-	return !g_ascii_strcasecmp(xmlnode_get_data(deviceTypeNode), deviceType);
+	return !g_ascii_strcasecmp(xmlnode_get_data(deviceTypeNode),
+			deviceType);
 }
 
 
@@ -282,12 +186,12 @@
 		return FALSE;
 	}
 	return !g_ascii_strcasecmp(xmlnode_get_data(serviceTypeNode),
-		serviceType);
+			serviceType);
 }
 
 
 static gchar*
-gaim_upnp_parse_description_response(const gchar* httpResponse,
+gaim_upnp_parse_description_response(const gchar* httpResponse, gsize len,
 	const gchar* httpURL, const gchar* serviceType)
 {
 	gchar* xmlRoot;
@@ -300,22 +204,22 @@
 	xmlnode* baseURLNode;
 
 	/* make sure we have a valid http response */
-	if(g_strstr_len(httpResponse, strlen(httpResponse), HTTP_OK) == NULL) {
+	if(g_strstr_len(httpResponse, len, HTTP_OK) == NULL) {
 		gaim_debug_error("upnp",
 			"parse_description_response(): Failed In HTTP_OK\n\n");
 		return NULL;
 	}
 
 	/* find the root of the xml document */
-	if((xmlRoot = g_strstr_len(httpResponse, strlen(httpResponse),
-			"<root")) == NULL) {
+	if((xmlRoot = g_strstr_len(httpResponse, len, "<root")) == NULL) {
 		gaim_debug_error("upnp",
 			"parse_description_response(): Failed finding root\n\n");
 		return NULL;
 	}
 
 	/* create the xml root node */
-	if((xmlRootNode = xmlnode_from_str(xmlRoot, -1)) == NULL) {
+	if((xmlRootNode = xmlnode_from_str(xmlRoot,
+			len - (xmlRoot - httpResponse))) == NULL) {
 		gaim_debug_error("upnp",
 			"parse_description_response(): Could not parse xml root node\n\n");
 		return NULL;
@@ -340,9 +244,9 @@
 	if(serviceTypeNode == NULL) {
 		gaim_debug_error("upnp",
 			"parse_description_response(): could not get serviceTypeNode 1\n\n");
-		return NULL;
 		g_free(baseURL);
 		xmlnode_free(xmlRootNode);
+		return NULL;
 	}
 	serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList");
 	if(serviceTypeNode == NULL) {
@@ -378,9 +282,8 @@
 
 	/* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its servicelist */
 	serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device");
-	while(!gaim_upnp_compare_device(serviceTypeNode,
-			"urn:schemas-upnp-org:device:WANConnectionDevice:1") &&
-			serviceTypeNode != NULL) {
+	while(serviceTypeNode && !gaim_upnp_compare_device(serviceTypeNode,
+			"urn:schemas-upnp-org:device:WANConnectionDevice:1")) {
 		serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
 	}
 	if(serviceTypeNode == NULL) {
@@ -431,7 +334,7 @@
 			g_strstr_len(xmlnode_get_data(controlURLNode),
 				SIZEOF_HTTP, "HTTP://") == NULL) {
 		controlURL = g_strdup_printf("%s%s", baseURL,
-		xmlnode_get_data(controlURLNode));
+			xmlnode_get_data(controlURLNode));
 	}else{
 		controlURL = g_strdup(xmlnode_get_data(controlURLNode));
 	}
@@ -441,19 +344,47 @@
 	return controlURL;
 }
 
-
-static gchar*
-gaim_upnp_parse_description(const gchar* descriptionURL, const gchar* serviceType)
+static void
+upnp_parse_description_cb(void *data, const char *httpResponse, gsize len)
 {
-	gchar* fullURL;
-	gchar* controlURL;
-	gchar* httpResponse;
-	gchar* httpRequest;
+	UPnPDiscoveryData *dd = data;
+	gchar *control_url = NULL;
+
+	if (len > 0)
+		control_url = gaim_upnp_parse_description_response(
+			httpResponse, len, dd->full_url, dd->service_type);
+
+	g_free(dd->full_url);
+
+	if(control_url == NULL) {
+		gaim_debug_error("upnp",
+			"gaim_upnp_parse_description(): control URL is NULL\n\n");
+	}
 
+	control_info.status = control_url ? GAIM_UPNP_STATUS_DISCOVERED
+		: GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER;
+	control_info.lookup_time = time(NULL);
+	control_info.control_url = control_url;
+	strncpy(control_info.service_type, dd->service_type,
+		sizeof(control_info.service_type));
+
+	fire_discovery_callbacks(control_url != NULL);
+
+	/* Look up the public and internal IPs */
+	if(control_url != NULL) {
+		lookup_public_ip();
+		lookup_internal_ip();
+	}
+
+	g_free(dd);
+}
+
+static void
+gaim_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd)
+{
+	gchar* httpRequest;
 	gchar* descriptionXMLAddress;
-	gchar* descriptionAddressPort;
 	gchar* descriptionAddress;
-	gchar descriptionPort[MAX_PORT_SIZE];
 	int port = 0;
 
 	/* parse the 4 above variables out of the descriptionURL
@@ -462,519 +393,578 @@
 	/* parse the url into address, port, path variables */
 	if(!gaim_url_parse(descriptionURL, &descriptionAddress,
 			&port, &descriptionXMLAddress, NULL, NULL)) {
-		return NULL;
+		return;
 	}
 	if(port == 0 || port == -1) {
 		port = DEFAULT_HTTP_PORT;
 	}
-	g_ascii_dtostr(descriptionPort, MAX_PORT_SIZE, port);
-	descriptionAddressPort = g_strdup_printf("%s:%s", descriptionAddress,
-		descriptionPort);
-
-	fullURL = g_strdup_printf("http://%s", descriptionAddressPort);
 
 	/* for example...
 	   GET /rootDesc.xml HTTP/1.1\r\nHost: 192.168.1.1:5678\r\n\r\n */
-	httpRequest = g_strdup_printf("GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n",
-		descriptionXMLAddress, descriptionAddressPort);
-
-	httpResponse = gaim_upnp_http_request(descriptionAddress,
-		port, httpRequest);
-	if(httpResponse == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_parse_description(): httpResponse is NULL\n\n");
-		g_free(descriptionXMLAddress);
-		g_free(descriptionAddress);
-		g_free(descriptionAddressPort);
-		g_free(httpRequest);
-		g_free(fullURL);
-		return NULL;
-	}
-
-	controlURL = gaim_upnp_parse_description_response(httpResponse,
-		fullURL, serviceType);
+	httpRequest = g_strdup_printf(
+		"GET /%s HTTP/1.1\r\n"
+		"Connection: close\r\n"
+		"Host: %s:%d\r\n\r\n",
+		descriptionXMLAddress, descriptionAddress, port);
 
 	g_free(descriptionXMLAddress);
-	g_free(descriptionAddress);
-	g_free(descriptionAddressPort);
-	g_free(fullURL);
-	g_free(httpRequest);
-	g_free(httpResponse);
+
+	dd->full_url = g_strdup_printf("http://%s:%d",
+			descriptionAddress, port);
 
-	if(controlURL == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_parse_description(): controlURL is NULL\n\n");
-	}
+	/* Remove the timeout because everything it is waiting for has
+	 * successfully completed */
+	gaim_timeout_remove(dd->tima);
+	dd->tima = 0;
 
-	return controlURL;
+	gaim_url_fetch_request(descriptionURL, TRUE, NULL, TRUE, httpRequest,
+			TRUE, upnp_parse_description_cb, dd);
+
+	g_free(descriptionAddress);
+	g_free(httpRequest);
+
 }
 
-
-static gchar*
-gaim_upnp_parse_discover_response(const gchar* buf, unsigned int bufSize,
-	const gchar* serviceType)
+static void
+gaim_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len,
+	UPnPDiscoveryData *dd)
 {
 	gchar* startDescURL;
 	gchar* endDescURL;
 	gchar* descURL;
-	gchar* retVal;
 
-	if(g_strstr_len(buf, strlen(buf), HTTP_OK) == NULL) {
+	if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) {
 		gaim_debug_error("upnp",
 			"parse_discover_response(): Failed In HTTP_OK\n\n");
-		return NULL;
+		return;
 	}
 
-	if((startDescURL = g_strstr_len(buf, strlen(buf), "http://")) == NULL) {
+	if((startDescURL = g_strstr_len(buf, buf_len, "http://")) == NULL) {
 		gaim_debug_error("upnp",
 			"parse_discover_response(): Failed In finding http://\n\n");
-		return NULL;
+		return;
 	}
 
-	endDescURL = g_strstr_len(startDescURL, strlen(startDescURL), "\r");
+	endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf),
+			"\r");
 	if(endDescURL == NULL) {
-		endDescURL = g_strstr_len(startDescURL, strlen(startDescURL), "\n");
+		endDescURL = g_strstr_len(startDescURL,
+				buf_len - (startDescURL - buf), "\n");
 		if(endDescURL == NULL) {
 			gaim_debug_error("upnp",
 				"parse_discover_response(): Failed In endDescURL\n\n");
-			return NULL;
-		}else if(endDescURL == startDescURL) {
-			gaim_debug_error("upnp",
-				"parse_discover_response(): endDescURL == startDescURL\n\n");
-			return NULL;
+			return;
 		}
-	}else if(endDescURL == startDescURL) {
+	}
+
+	/* XXX: I'm not sure how this could ever happen */
+	if(endDescURL == startDescURL) {
 		gaim_debug_error("upnp",
-			"parse_discover_response(): 2nd endDescURL == startDescURL\n\n");
-		return NULL;
+			"parse_discover_response(): endDescURL == startDescURL\n\n");
+		return;
 	}
-	descURL = g_strndup(startDescURL, endDescURL-startDescURL);
+
+	descURL = g_strndup(startDescURL, endDescURL - startDescURL);
 
-	retVal = gaim_upnp_parse_description(descURL, serviceType);
+	gaim_upnp_parse_description(descURL, dd);
+
 	g_free(descURL);
-	return retVal;
+
 }
 
+static gboolean
+gaim_upnp_discover_timeout(gpointer data)
+{
+	UPnPDiscoveryData* dd = data;
+
+	if (dd->inpa)
+		gaim_input_remove(dd->inpa);
+	dd->inpa = 0;
+	dd->tima = 0;
+
+	if (dd->retry_count < NUM_UDP_ATTEMPTS) {
+		dd->retry_count++;
+		gaim_upnp_discover_send_broadcast(dd);
+	} else {
+		if (dd->fd)
+			close(dd->fd);
+
+		control_info.status = GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER;
+		control_info.lookup_time = time(NULL);
+		control_info.service_type[0] = '\0';
+		g_free(control_info.control_url);
+		control_info.control_url = NULL;
+
+		fire_discovery_callbacks(FALSE);
+
+		g_free(dd);
+	}
+
+	return FALSE;
+}
 
 static void
 gaim_upnp_discover_udp_read(gpointer data, gint sock, GaimInputCondition cond)
 {
-	unsigned int length;
-	struct sockaddr_in from;
-	int sizeRecv;
-	NetResponseData* nrd = data;
-
-	gaim_timeout_remove(nrd->tima);
-	length = sizeof(struct sockaddr_in);
+	int len;
+	UPnPDiscoveryData *dd = data;
+	gchar buf[65536];
 
 	do {
-		sizeRecv = recvfrom(sock, nrd->recvBuffer,
-			MAX_DISCOVERY_RECEIVE_SIZE, 0,
-			(struct sockaddr*)&from, &length);
+		len = recv(dd->fd, buf,
+			sizeof(buf) - 1, 0);
 
-		if(sizeRecv > 0) {
-			nrd->recvBuffer[sizeRecv] = '\0';
-		}else if(errno != EINTR) {
-			g_free(nrd->recvBuffer);
-			nrd->recvBuffer = NULL;
+		if(len > 0) {
+			buf[len] = '\0';
+			break;
+		} else if(errno != EINTR) {
+			/* We'll either get called again, or time out */
+			return;
 		}
-	}while(errno == EINTR);
+	} while (errno == EINTR);
+
+	gaim_input_remove(dd->inpa);
+	dd->inpa = 0;
 
-	gaim_input_remove(nrd->inpa);
-	nrd->done = TRUE;
-	return;
+	close(dd->fd);
+	dd->fd = 0;
+
+	/* parse the response, and see if it was a success */
+	gaim_upnp_parse_discover_response(buf, len, dd);
+
+	/* We'll either time out or continue successfully */
 }
 
-
-GaimUPnPControlInfo*
-gaim_upnp_discover(void)
+void
+gaim_upnp_discover_send_broadcast(UPnPDiscoveryData *dd)
 {
-	/* Socket Setup Variables */
-	int sock, i;
-	struct sockaddr_in server;
-	struct hostent* hp;
-
-	/* UDP SEND VARIABLES */
-	gboolean sentSuccess, recvSuccess;
-	ssize_t sizeSent;
-	ssize_t totalSizeSent;
-	gchar wanIP[] = "WANIPConnection:1";
-	gchar wanPPP[] = "WANPPPConnection:1";
-	gchar* serviceToUse;
-	gchar* sendMessage = NULL;
-
-	/* UDP RECEIVE VARIABLES */
-	GaimUPnPControlInfo* controlInfo = g_malloc(sizeof(GaimUPnPControlInfo));
-	NetResponseData* nrd = g_malloc(sizeof(NetResponseData));
-
-	/* Set up the sockets */
-	sock = socket(AF_INET, SOCK_DGRAM, 0);
-	if (sock == -1) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_discover(): Failed In sock creation\n\n");
-		g_free(nrd);
-		g_free(controlInfo);
-		return NULL;
-	}
-	memset(&server, 0, sizeof(struct sockaddr));
-	server.sin_family = AF_INET;
-	if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
-		close(sock);
-		gaim_debug_error("upnp",
-			"gaim_upnp_discover(): Failed In gethostbyname\n\n");
-		g_free(nrd);
-		g_free(controlInfo);
-		return NULL;
-	}
-	memcpy(&server.sin_addr,
-	hp->h_addr_list[0],
-	hp->h_length);
-	server.sin_port = htons(HTTPMU_HOST_PORT);
+	gchar *sendMessage = NULL;
+	gsize totalSize;
+	gboolean sentSuccess;
 
 	/* because we are sending over UDP, if there is a failure
 	   we should retry the send NUM_UDP_ATTEMPTS times. Also,
 	   try different requests for WANIPConnection and WANPPPConnection*/
-	for(i = 0; i < NUM_UDP_ATTEMPTS; i++) {
+	for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) {
 		sentSuccess = TRUE;
-		recvSuccess = TRUE;
-		totalSizeSent = 0;
 
-		nrd->recvBuffer = NULL;
-		nrd->totalSizeRecv = 0;
-		nrd->done = FALSE;
-
-		if(sendMessage != NULL) {
-			g_free(sendMessage);
+		if((dd->retry_count % 2) == 0) {
+			strncpy(dd->service_type, WAN_IP_CONN_SERVICE, sizeof(dd->service_type));
+		} else {
+			strncpy(dd->service_type, WAN_PPP_CONN_SERVICE, sizeof(dd->service_type));
 		}
 
-		if(i%2 == 0) {
-			serviceToUse = wanIP;
-		} else {
-			serviceToUse = wanPPP;
-		}
-		sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, serviceToUse);
+		sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, dd->service_type);
 
-		nrd->recvBuffer = (char*)g_malloc(MAX_DISCOVERY_RECEIVE_SIZE);
+		totalSize = strlen(sendMessage);
 
-		while(totalSizeSent < (ssize_t)strlen(sendMessage)) {
-			sizeSent = sendto(sock,(void*)&sendMessage[totalSizeSent],
-				strlen(&sendMessage[totalSizeSent]),0,
-				(struct sockaddr*)&server,
-				sizeof(struct sockaddr_in));
-			if(sizeSent <= 0 && errno != EINTR) {
-				sentSuccess = FALSE;
+		do {
+			if(sendto(dd->fd, sendMessage, totalSize, 0,
+					(struct sockaddr*) &(dd->server),
+					sizeof(struct sockaddr_in)
+					) == totalSize) {
+				sentSuccess = TRUE;
 				break;
-			}else if(errno == EINTR) {
-				sizeSent = 0;
 			}
-			totalSizeSent += sizeSent;
-		}
+		} while (errno == EINTR);
+
+		g_free(sendMessage);
 
 		if(sentSuccess) {
-			nrd->tima = gaim_timeout_add(DISCOVERY_TIMEOUT,
-				(GSourceFunc)gaim_upnp_timeout, nrd);
-			nrd->inpa = gaim_input_add(sock, GAIM_INPUT_READ,
-				gaim_upnp_discover_udp_read, nrd);
-			while (!nrd->done) {
-				g_main_context_iteration(NULL, TRUE);
-			}
-			if(nrd->recvBuffer == NULL) {
-				recvSuccess = FALSE;
-			} else {
-				/* parse the response, and see if it was a success */
-				close(sock);
-				if((controlInfo->controlURL=
-						gaim_upnp_parse_discover_response(nrd->recvBuffer,
-							strlen(nrd->recvBuffer),
-							serviceToUse))==NULL) {
-					gaim_debug_error("upnp",
-						"gaim_upnp_discover(): Failed In parse response\n\n");
-					g_free(nrd->recvBuffer);
-					g_free(nrd);
-					g_free(controlInfo);
-					return NULL;
-				}
+			dd->tima = gaim_timeout_add(DISCOVERY_TIMEOUT,
+				gaim_upnp_discover_timeout, dd);
+			dd->inpa = gaim_input_add(dd->fd, GAIM_INPUT_READ,
+				gaim_upnp_discover_udp_read, dd);
 
-				controlInfo->serviceType = g_strdup(serviceToUse);
-			}
-		}
-
-		/* if sent success and recv successful, then break */
-		if(sentSuccess && recvSuccess) {
-			i = NUM_UDP_ATTEMPTS;
+			return;
 		}
 	}
 
-	g_free(nrd->recvBuffer);
-	g_free(sendMessage);
-	g_free(nrd);
-
-	if(!sentSuccess || !recvSuccess) {
-		close(sock);
-		gaim_debug_error("upnp",
-			"gaim_upnp_discover(): Failed In sent/recv success\n\n");
-		g_free(controlInfo);
-		return NULL;
-	}
-
-	return controlInfo;
+	/* We have already done all our retries. Make sure that the callback
+	 * doesn't get called before the original function returns */
+	gaim_timeout_add(10, gaim_upnp_discover_timeout, dd);
 }
 
 
-static char*
-gaim_upnp_generate_action_message_and_send(const GaimUPnPControlInfo* controlInfo,
-	const gchar* actionName, const gchar* actionParams)
+void
+gaim_upnp_discover(GaimUPnPCallback cb, gpointer cb_data)
 {
-	gchar* actionMessage;
+	/* Socket Setup Variables */
+	int sock;
+	struct hostent* hp;
+
+	/* UDP RECEIVE VARIABLES */
+	UPnPDiscoveryData *dd;
+
+	if (control_info.status == GAIM_UPNP_STATUS_DISCOVERING) {
+		if (cb) {
+			discovery_callbacks = g_slist_append(
+					discovery_callbacks, cb);
+			discovery_callbacks = g_slist_append(
+					discovery_callbacks, cb_data);
+		}
+		return;
+	}
+
+	dd = g_new0(UPnPDiscoveryData, 1);
+	if (cb) {
+		discovery_callbacks = g_slist_append(discovery_callbacks, cb);
+		discovery_callbacks = g_slist_append(discovery_callbacks,
+				cb_data);
+	}
+
+	/* Set up the sockets */
+	sock = socket(AF_INET, SOCK_DGRAM, 0);
+	if(sock == -1) {
+		gaim_debug_error("upnp",
+			"gaim_upnp_discover(): Failed In sock creation\n\n");
+		/* Short circuit the retry attempts */
+		dd->retry_count = NUM_UDP_ATTEMPTS;
+		gaim_timeout_add(10, gaim_upnp_discover_timeout, dd);
+		return;
+	}
+
+	dd->fd = sock;
+
+	/* This shouldn't block */
+	if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
+		gaim_debug_error("upnp",
+			"gaim_upnp_discover(): Failed In gethostbyname\n\n");
+		/* Short circuit the retry attempts */
+		dd->retry_count = NUM_UDP_ATTEMPTS;
+		gaim_timeout_add(10, gaim_upnp_discover_timeout, dd);
+		return;
+	}
+
+	memset(&(dd->server), 0, sizeof(struct sockaddr));
+	dd->server.sin_family = AF_INET;
+	memcpy(&(dd->server.sin_addr), hp->h_addr_list[0], hp->h_length);
+	dd->server.sin_port = htons(HTTPMU_HOST_PORT);
+
+	control_info.status = GAIM_UPNP_STATUS_DISCOVERING;
+
+	gaim_upnp_discover_send_broadcast(dd);
+}
+
+static void
+gaim_upnp_generate_action_message_and_send(const gchar* actionName,
+		const gchar* actionParams, GaimURLFetchCallback cb,
+		gpointer cb_data)
+{
+
 	gchar* soapMessage;
 	gchar* totalSendMessage;
-	gchar* httpResponse;
-
 	gchar* pathOfControl;
 	gchar* addressOfControl;
-	gchar* addressPortOfControl;
-	gchar portOfControl[MAX_PORT_SIZE];
-	int port=0;
-
-	/* set the soap message */
-	soapMessage = g_strdup_printf(SOAP_ACTION, actionName,
-		controlInfo->serviceType, actionParams, actionName);
+	int port = 0;
 
 	/* parse the url into address, port, path variables */
-	if(!gaim_url_parse(controlInfo->controlURL, &addressOfControl,
+	if(!gaim_url_parse(control_info.control_url, &addressOfControl,
 			&port, &pathOfControl, NULL, NULL)) {
 		gaim_debug_error("upnp",
 			"generate_action_message_and_send(): Failed In Parse URL\n\n");
-		g_free(soapMessage);
-		return NULL;
-	}
-	if(port == 0 || port == -1) {
-		port = DEFAULT_HTTP_PORT;
-	}
-	g_ascii_dtostr(portOfControl, MAX_PORT_SIZE, port);
-
-	/* set the addressPortOfControl variable which should have a
-	   form like the following: 192.168.1.1:8000 */
-	addressPortOfControl = g_strdup_printf("%s:%s",
-		addressOfControl, portOfControl);
-
-	/* set the HTTP Header */
-	actionMessage = g_strdup_printf(HTTP_HEADER_ACTION,
-		pathOfControl, addressPortOfControl,
-		controlInfo->serviceType, actionName,
-	strlen(soapMessage));
-
-	/* append to the header the body */
-	totalSendMessage = g_strdup_printf("%s%s", actionMessage, soapMessage);
-
-	/* get the return of the http response */
-	httpResponse = gaim_upnp_http_request(addressOfControl,
-		port, totalSendMessage);
-	if(httpResponse == NULL) {
-		gaim_debug_error("upnp",
-			"generate_action_message_and_send(): Failed In httpResponse\n\n");
-	}
-
-	g_free(actionMessage);
-	g_free(soapMessage);
-	g_free(totalSendMessage);
-	g_free(pathOfControl);
-	g_free(addressOfControl);
-	g_free(addressPortOfControl);
-
-	return httpResponse;
-}
-
-
-gchar*
-gaim_upnp_get_public_ip(const GaimUPnPControlInfo* controlInfo)
-{
-	gchar* extIPAddress;
-	gchar* httpResponse;
-	gchar actionName[] = "GetExternalIPAddress";
-	gchar actionParams[] = "";
-	gchar* temp, *temp2;
-
-	httpResponse = gaim_upnp_generate_action_message_and_send(controlInfo,
-		actionName, actionParams);
-	if(httpResponse == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_get_public_ip(): Failed In httpResponse\n\n");
-		return NULL;
-	}
-
-	/* extract the ip, or see if there is an error */
-	if((temp = g_strstr_len(httpResponse, strlen(httpResponse),
-			"<NewExternalIPAddress")) == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_get_public_ip(): Failed Finding <NewExternalIPAddress\n\n");
-		g_free(httpResponse);
-		return NULL;
-	}
-	if((temp = g_strstr_len(temp, strlen(temp), ">")) == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_get_public_ip(): Failed In Finding >\n\n");
-		g_free(httpResponse);
-		return NULL;
-	}
-	if((temp2 = g_strstr_len(temp, strlen(temp), "<")) == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_get_public_ip(): Failed In Finding <\n\n");
-		g_free(httpResponse);
-		return NULL;
-	}
-
-	extIPAddress = g_strndup(&temp[1], (temp2-1)-temp);
-
-	g_free(httpResponse);
-
-	gaim_debug_info("upnp", "NAT Returned IP: %s\n", extIPAddress);
-	return extIPAddress;
-}
-
-
-static void
-gaim_upnp_get_local_system_ip(gpointer data, gint sock, GaimInputCondition cond)
-{
-	NetResponseData* nrd = data;
-	nrd->recvBuffer = g_strdup(gaim_network_get_local_system_ip(sock));
-
-	gaim_timeout_remove(nrd->tima);
-	nrd->done = TRUE;
-
-	close(sock);
-}
-
-
-static gchar*
-gaim_upnp_get_local_ip_address(const gchar* address)
-{
-	gchar* ip;
-	gchar* pathOfControl;
-	gchar* addressOfControl;
-	int port = 0;
-	NetResponseData* nrd = (NetResponseData*)g_malloc0(sizeof(NetResponseData));
-
-	if(!gaim_url_parse(address, &addressOfControl, &port, &pathOfControl,
-			NULL, NULL)) {
-		gaim_debug_error("upnp",
-			"get_local_ip_address(): Failed In Parse URL\n\n");
-		return NULL;
+		/* XXX: This should probably be async */
+		if(cb)
+			cb(cb_data, NULL, 0);
 	}
 	if(port == 0 || port == -1) {
 		port = DEFAULT_HTTP_PORT;
 	}
 
-	nrd->tima = gaim_timeout_add(RECEIVE_TIMEOUT,
-		(GSourceFunc)gaim_upnp_timeout, nrd);
-
-	if(gaim_proxy_connect(NULL, addressOfControl, port,
-			gaim_upnp_get_local_system_ip, nrd)) {
-
-		gaim_debug_error("upnp", "Get Local IP Connect Failed: Address: %s @@@ Port %d @@@ Request %s\n\n",
-			address, port, nrd->sendBuffer);
+	/* set the soap message */
+	soapMessage = g_strdup_printf(SOAP_ACTION, actionName,
+		control_info.service_type, actionParams, actionName);
 
-		gaim_timeout_remove(nrd->tima);
-	} else {
-		while (!nrd->done) {
-			g_main_context_iteration(NULL, TRUE);
-		}
-	}
+	/* set the HTTP Header, and append the body to it */
+	totalSendMessage = g_strdup_printf(HTTP_HEADER_ACTION "%s",
+		pathOfControl, addressOfControl, port,
+		control_info.service_type, actionName,
+		strlen(soapMessage), soapMessage);
+	g_free(pathOfControl);
+	g_free(soapMessage);
 
-	ip = nrd->recvBuffer;
-	g_free(nrd);
+	gaim_url_fetch_request(control_info.control_url, FALSE, NULL, TRUE,
+			totalSendMessage, TRUE, cb, cb_data);
 
-	gaim_debug_info("upnp", "local ip: %s\n", ip);
-
-	return ip;
+	g_free(totalSendMessage);
+	g_free(addressOfControl);
 }
 
 
-gboolean
-gaim_upnp_set_port_mapping(const GaimUPnPControlInfo* controlInfo,
-	unsigned short portMap, const gchar* protocol)
+const gchar *
+gaim_upnp_get_public_ip()
+{
+	if (control_info.status == GAIM_UPNP_STATUS_DISCOVERED
+			&& control_info.publicip
+			&& strlen(control_info.publicip) > 0)
+		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 < GAIM_UPNP_STATUS_DISCOVERING
+			&& (time(NULL) - control_info.lookup_time) > 300)
+		gaim_upnp_discover(NULL, NULL);
+
+	return NULL;
+}
+
+static void
+looked_up_public_ip_cb(gpointer data, const char *httpResponse, gsize len)
 {
-	gchar* httpResponse;
-	gchar actionName[] = "AddPortMapping";
-	gchar* actionParams;
-	gchar* internalIP;
+	gchar* temp, *temp2;
+
+	if(!httpResponse)
+		return;
+
+	/* extract the ip, or see if there is an error */
+	if((temp = g_strstr_len(httpResponse, len,
+			"<NewExternalIPAddress")) == NULL) {
+		gaim_debug_error("upnp",
+			"looked_up_public_ip_cb(): Failed Finding <NewExternalIPAddress\n\n");
+		return;
+	}
+	if(!(temp = g_strstr_len(temp, len - (temp - httpResponse), ">"))) {
+		gaim_debug_error("upnp",
+			"looked_up_public_ip_cb(): Failed In Finding >\n\n");
+		return;
+	}
+	if(!(temp2 = g_strstr_len(temp, len - (temp - httpResponse), "<"))) {
+		gaim_debug_error("upnp",
+			"looked_up_public_ip_cb(): Failed In Finding <\n\n");
+		return;
+	}
+	*temp2 = '\0';
+
+	strncpy(control_info.publicip, temp + 1,
+			sizeof(control_info.publicip));
+
+	gaim_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip);
+}
+
+void
+lookup_public_ip()
+{
+	gaim_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
+			looked_up_public_ip_cb, NULL);
+}
 
-	/* get the internal IP */
-	if((internalIP = gaim_upnp_get_local_ip_address(controlInfo->controlURL)) == NULL) {
+/* TODO: This could be exported */
+static const gchar *
+gaim_upnp_get_internal_ip()
+{
+	if (control_info.status == GAIM_UPNP_STATUS_DISCOVERED
+			&& control_info.internalip
+			&& strlen(control_info.internalip) > 0)
+		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 < GAIM_UPNP_STATUS_DISCOVERING
+			&& (time(NULL) - control_info.lookup_time) > 300)
+		gaim_upnp_discover(NULL, NULL);
+
+	return NULL;
+}
+
+static void
+looked_up_internal_ip_cb(gpointer data, gint sock, GaimInputCondition cond)
+{
+	if (sock) {
+		strncpy(control_info.internalip,
+			gaim_network_get_local_system_ip(sock),
+			sizeof(control_info.internalip));
+		gaim_debug_info("upnp", "Local IP: %s\n",
+				control_info.internalip);
+		close(sock);
+	} else
+		gaim_debug_info("upnp", "Unable to look up local IP\n");
+
+}
+
+void
+lookup_internal_ip()
+{
+	gchar* addressOfControl;
+	int port = 0;
+
+	if(!gaim_url_parse(control_info.control_url, &addressOfControl, &port,
+			NULL, NULL, NULL)) {
 		gaim_debug_error("upnp",
-			"gaim_upnp_set_port_mapping(): couldn't get local ip\n\n");
-		return FALSE;
+			"lookup_internal_ip(): Failed In Parse URL\n\n");
+		return;
+	}
+	if(port == 0 || port == -1) {
+		port = DEFAULT_HTTP_PORT;
 	}
 
-	/* make the portMappingParams variable */
-	actionParams = g_strdup_printf(ADD_PORT_MAPPING_PARAMS, portMap,
-		protocol, portMap, internalIP);
+	if(gaim_proxy_connect(NULL, addressOfControl, port,
+			looked_up_internal_ip_cb, NULL) != 0) {
 
-	httpResponse = gaim_upnp_generate_action_message_and_send(controlInfo,
-		actionName, actionParams);
-	if(httpResponse == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_set_port_mapping(): Failed In httpResponse\n\n");
-		g_free(actionParams);
-		g_free(internalIP);
-		return FALSE;
-	}
-
-	/* determine if port mapping was a success */
-	if(strstr(httpResponse, HTTP_OK) == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_set_port_mapping(): Failed HTTP_OK\n\n%s\n\n", httpResponse);
-		g_free(actionParams);
-		g_free(httpResponse);
-		g_free(internalIP);
-		return FALSE;
+		gaim_debug_error("upnp", "Get Local IP Connect Failed: Address: %s @@@ Port %d\n",
+			addressOfControl, port);
 	}
 
-	g_free(actionParams);
-	g_free(httpResponse);
+	g_free(addressOfControl);
+}
+
+static void
+done_port_mapping_cb(gpointer data, const gchar *httpResponse, gsize len)
+{
+	UPnPMappingAddRemove *ar = data;
+
+	gboolean success = TRUE;
+
+	/* determine if port mapping was a success */
+	if(!httpResponse || g_strstr_len(httpResponse, len, HTTP_OK) == NULL) {
+		gaim_debug_error("upnp",
+			"gaim_upnp_set_port_mapping(): Failed HTTP_OK\n\n%s\n\n",
+			httpResponse ? httpResponse : "(null)");
+		success =  FALSE;
+	} else
+		gaim_debug_info("upnp", "Successfully completed port mapping operation\n");
+
+	if (ar->cb)
+		ar->cb(success, ar->cb_data);
+	g_free(ar);
+}
+
+static void
+do_port_mapping_cb(gboolean has_control_mapping, gpointer data)
+{
+	UPnPMappingAddRemove *ar = data;
 
-	gaim_debug_info("upnp", "NAT Added Port Forward On Port: %d: To IP: %s\n",
-		portMap, internalIP);
-	g_free(internalIP);
-	return TRUE;
-}
+	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 = gaim_upnp_get_internal_ip())) {
+				gaim_debug_error("upnp",
+					"gaim_upnp_set_port_mapping(): couldn't get local ip\n\n");
+				/* UGLY */
+				if (ar->cb)
+					ar->cb(FALSE, ar->cb_data);
+				g_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);
+		}
+
+		gaim_upnp_generate_action_message_and_send(action_name,
+				action_params, done_port_mapping_cb, ar);
+
+		g_free(action_params);
+		return;
+	}
 
 
-gboolean
-gaim_upnp_remove_port_mapping(const GaimUPnPControlInfo* controlInfo,
-	unsigned short portMap, const char* protocol)
+	if (ar->cb)
+		ar->cb(FALSE, ar->cb_data);
+	g_free(ar);
+}
+
+static gboolean
+fire_port_mapping_failure_cb(gpointer data)
 {
-	gchar* httpResponse;
-	gchar actionName[] = "DeletePortMapping";
-	gchar* actionParams;
+	do_port_mapping_cb(FALSE, data);
+	return FALSE;
+}
 
-	/* make the portMappingParams variable */
-	actionParams = g_strdup_printf(DELETE_PORT_MAPPING_PARAMS, portMap,
-		 protocol);
+void
+gaim_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
+		GaimUPnPCallback cb, gpointer cb_data)
+{
+	UPnPMappingAddRemove *ar;
 
-	httpResponse = gaim_upnp_generate_action_message_and_send(controlInfo,
-		actionName, actionParams);
+	ar = g_new0(UPnPMappingAddRemove, 1);
+	ar->cb = cb;
+	ar->cb_data = cb_data;
+	ar->add = TRUE;
+	ar->portmap = portmap;
+	strncpy(ar->protocol, protocol, sizeof(ar->protocol));
 
-	if(httpResponse == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_remove_port_mapping(): Failed In httpResponse\n\n");
-		g_free(actionParams);
-		return FALSE;
+	/* If we're waiting for a discovery, add to the callbacks list */
+	if(control_info.status == GAIM_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;
 	}
 
-	/* determine if port mapping was a success */
-	if(strstr(httpResponse, HTTP_OK) == NULL) {
-		gaim_debug_error("upnp",
-			"gaim_upnp_set_port_mapping(): Failed HTTP_OK\n\n%s\n\n",
-			httpResponse);
-		g_free(actionParams);
-		g_free(httpResponse);
-		return FALSE;
+	/* If we haven't had a successful UPnP discovery, check if 5 minutes has
+	 * elapsed since the last try, try again */
+	if(control_info.status == GAIM_UPNP_STATUS_UNDISCOVERED ||
+			(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER
+			 && (time(NULL) - control_info.lookup_time) > 300)) {
+		gaim_upnp_discover(do_port_mapping_cb, ar);
+		return;
+	} else if(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER) {
+		if (cb) {
+			/* Asynchronously trigger a failed response */
+			gaim_timeout_add(10, fire_port_mapping_failure_cb, ar);
+		} else {
+			/* No need to do anything if nobody expects a response*/
+			g_free(ar);
+		}
+		return;
 	}
 
-	g_free(actionParams);
-	g_free(httpResponse);
+	do_port_mapping_cb(TRUE, ar);
+}
+
+void
+gaim_upnp_remove_port_mapping(unsigned short portmap, const char* protocol,
+		GaimUPnPCallback cb, gpointer cb_data)
+{
+	UPnPMappingAddRemove *ar;
+
+	ar = g_new0(UPnPMappingAddRemove, 1);
+	ar->cb = cb;
+	ar->cb_data = cb_data;
+	ar->add = FALSE;
+	ar->portmap = portmap;
+	strncpy(ar->protocol, protocol, sizeof(ar->protocol));
 
-	gaim_debug_info("upnp", "NAT Removed Port Forward On Port: %d\n", portMap);
-	return TRUE;
+	/* If we're waiting for a discovery, add to the callbacks list */
+	if(control_info.status == GAIM_UPNP_STATUS_DISCOVERING) {
+		discovery_callbacks = g_slist_append(
+				discovery_callbacks, do_port_mapping_cb);
+		discovery_callbacks = g_slist_append(
+				discovery_callbacks, ar);
+		return;
+	}
+
+	/* If we haven't had a successful UPnP discovery, check if 5 minutes has
+	 * elapsed since the last try, try again */
+	if(control_info.status == GAIM_UPNP_STATUS_UNDISCOVERED ||
+			(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER
+			 && (time(NULL) - control_info.lookup_time) > 300)) {
+		gaim_upnp_discover(do_port_mapping_cb, ar);
+		return;
+	} else if(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER) {
+		if (cb) {
+			/* Asynchronously trigger a failed response */
+			gaim_timeout_add(10, fire_port_mapping_failure_cb, ar);
+		} else {
+			/* No need to do anything if nobody expects a response*/
+			g_free(ar);
+		}
+		return;
+	}
+
+	do_port_mapping_cb(TRUE, ar);
 }
--- a/src/upnp.h	Tue Jan 17 05:20:38 2006 +0000
+++ b/src/upnp.h	Tue Jan 17 05:48:51 2006 +0000
@@ -27,13 +27,6 @@
 #define _GAIM_UPNP_H_
 
 
-typedef struct
-{
-  gchar* controlURL;
-  gchar* serviceType;
-} GaimUPnPControlInfo;
-
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -43,27 +36,42 @@
 /**************************************************************************/
 /*@{*/
 
+
+typedef void (*GaimUPnPCallback) (gboolean success, gpointer data);
+
 /**
  * Sends a discovery request to search for a UPnP enabled IGD that
  * contains the WANIPConnection service that will allow us to recieve the
  * public IP address of the IGD, and control it for forwarding ports.
+ * The result will be cached for further use.
  *
+ * @param cb an optional callback function to be notified when the UPnP
+ *           discovery is complete
+ * @param cb_data Extra data to be passed to the callback
+ */
+void gaim_upnp_discover(GaimUPnPCallback cb, gpointer cb_data);
+
+#if 0
+/**
+ * Retrieve the current UPnP control info, if there is any available.
+ * This will only be filled in if gaim_upnp_discover() had been called,
+ * and finished discovering.
+ * 
  * @return The control URL for the IGD we'll use to use the IGD services
  */
-GaimUPnPControlInfo* gaim_upnp_discover(void);
-
+const GaimUPnPControlInfo* gaim_upnp_get_control_info(void);
+#endif
 
 
 /**
  * Gets the IP address from a UPnP enabled IGD that sits on the local
  * network, so when getting the network IP, instead of returning the
- * local network IP, the public IP is retrieved.
- *
- * @param controlInfo The control URL retrieved from gaim_upnp_discover.
+ * local network IP, the public IP is retrieved.  This is a cached value from
+ * the time of the UPnP discovery.
  *
  * @return The IP address of the network, or NULL if something went wrong
  */
-gchar* gaim_upnp_get_public_ip(const GaimUPnPControlInfo* controlInfo);
+const gchar* gaim_upnp_get_public_ip(void);
 
 
 /**
@@ -71,15 +79,14 @@
  * this gaim client. Essentially, this function takes care of the port
  * forwarding so things like file transfers can work behind NAT firewalls
  *
- * @param controlInfo The control URL retrieved from gaim_upnp_discover.
- * @param portMap The port to map to this client
+ * @param portmap The port to map to this client
  * @param protocol The protocol to map, either "TCP" or "UDP"
- *
- * @return TRUE if success, FALSE if something went wrong.
+ * @param cb an optional callback function to be notified when the mapping
+ *           addition is complete
+ * @param cb_data Extra data to be passed to the callback
  */
-gboolean gaim_upnp_set_port_mapping(const GaimUPnPControlInfo* controlInfo, 
-                                    unsigned short portMap,
-                                    const gchar* protocol);
+void gaim_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
+		GaimUPnPCallback cb, gpointer cb_data);
 
 /**
  * Deletes a port mapping in a UPnP enabled IGD that sits on the local network
@@ -87,19 +94,16 @@
  * port forwarding after they have completed a connection so another client on
  * the local network can take advantage of the port forwarding
  *
- * @param controlInfo The control URL retrieved from gaim_upnp_discover.
- * @param portMap The port to delete the mapping for
+ * @param portmap The port to delete the mapping for
  * @param protocol The protocol to map to. Either "TCP" or "UDP"
- *
- * @return TRUE if success, FALSE if something went wrong.
+ * @param cb an optional callback function to be notified when the mapping
+ *           removal is complete
+ * @param cb_data Extra data to be passed to the callback
  */
-gboolean 
-gaim_upnp_remove_port_mapping(const GaimUPnPControlInfo* controlInfo,
-                              unsigned short portMap,
-                              const gchar* protocol);
+void gaim_upnp_remove_port_mapping(unsigned short portmap,
+		const gchar* protocol, GaimUPnPCallback cb, gpointer cb_data);
 /*@}*/
 
-
 #ifdef __cplusplus
 }
 #endif