Mercurial > pidgin
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