Mercurial > pidgin
diff libgaim/network.c @ 14192:60b1bc8dbf37
[gaim-migrate @ 16863]
Renamed 'core' to 'libgaim'
committer: Tailor Script <tailor@pidgin.im>
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Sat, 19 Aug 2006 01:50:10 +0000 |
parents | |
children | 645598a4ec04 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libgaim/network.c Sat Aug 19 01:50:10 2006 +0000 @@ -0,0 +1,362 @@ +/** + * @file network.c Network Implementation + * @ingroup core + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "internal.h" + +#ifndef _WIN32 +#include <net/if.h> +#include <sys/ioctl.h> +#endif + +/* Solaris */ +#if defined (__SVR4) && defined (__sun) +#include <sys/sockio.h> +#endif + +#include "debug.h" +#include "account.h" +#include "network.h" +#include "prefs.h" +#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) +{ + static unsigned char ret[4]; + gchar *delimiter = "."; + gchar **split; + int i; + + g_return_val_if_fail(ip != NULL, NULL); + + split = g_strsplit(ip, delimiter, 4); + for (i = 0; split[i] != NULL; i++) + ret[i] = atoi(split[i]); + g_strfreev(split); + + /* i should always be 4 */ + if (i != 4) + return NULL; + + return ret; +} + +void +gaim_network_set_public_ip(const char *ip) +{ + g_return_if_fail(ip != NULL); + + /* XXX - Ensure the IP address is valid */ + + gaim_prefs_set_string("/core/network/public_ip", ip); +} + +const char * +gaim_network_get_public_ip(void) +{ + return gaim_prefs_get_string("/core/network/public_ip"); +} + +const char * +gaim_network_get_local_system_ip(int fd) +{ + char buffer[1024]; + static char ip[16]; + char *tmp; + struct ifconf ifc; + struct ifreq *ifr; + struct sockaddr_in *sinptr; + guint32 lhost = htonl(127 * 256 * 256 * 256 + 1); + long unsigned int add; + int source = fd; + + if (fd < 0) + source = socket(PF_INET,SOCK_STREAM, 0); + + ifc.ifc_len = sizeof(buffer); + ifc.ifc_req = (struct ifreq *)buffer; + ioctl(source, SIOCGIFCONF, &ifc); + + if (fd < 0) + close(source); + + tmp = buffer; + while (tmp < buffer + ifc.ifc_len) + { + ifr = (struct ifreq *)tmp; + tmp += sizeof(struct ifreq); + + if (ifr->ifr_addr.sa_family == AF_INET) + { + sinptr = (struct sockaddr_in *)&ifr->ifr_addr; + if (sinptr->sin_addr.s_addr != lhost) + { + add = ntohl(sinptr->sin_addr.s_addr); + g_snprintf(ip, 16, "%lu.%lu.%lu.%lu", + ((add >> 24) & 255), + ((add >> 16) & 255), + ((add >> 8) & 255), + add & 255); + + return ip; + } + } + } + + return "0.0.0.0"; +} + +const char * +gaim_network_get_my_ip(int fd) +{ + const char *ip = NULL; + GaimStunNatDiscovery *stun; + + /* Check if the user specified an IP manually */ + if (!gaim_prefs_get_bool("/core/network/auto_ip")) { + ip = gaim_network_get_public_ip(); + if ((ip != NULL) && (*ip != '\0')) + return ip; + } + + /* Check if STUN discovery was already done */ + 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 using UPnP */ + ip = gaim_upnp_get_public_ip(); + if (ip != NULL) + return ip; + + /* Just fetch the IP of the local system */ + return gaim_network_get_local_system_ip(fd); +} + + +static void +gaim_network_set_upnp_port_mapping_cb(gboolean success, gpointer data) +{ + ListenUPnPData *ldata = data; + + if (!success) { + gaim_debug_info("network", "Couldn't create UPnP mapping\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; + ListenUPnPData *ld; +#ifdef HAVE_GETADDRINFO + int errnum; + struct addrinfo hints, *res, *next; + char serv[6]; + + /* + * Get a list of addresses on this machine. + */ + snprintf(serv, sizeof(serv), "%hu", port); + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = socket_type; + errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res); + if (errnum != 0) { +#ifndef _WIN32 + gaim_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum)); + 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); +#endif + return FALSE; + } + + /* + * Go through the list of addresses and attempt to listen on + * one of them. + * XXX - Try IPv6 addresses first? + */ + for (next = res; next != NULL; next = next->ai_next) { + listenfd = socket(next->ai_family, next->ai_socktype, next->ai_protocol); + if (listenfd < 0) + continue; + if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) + gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno)); + if (bind(listenfd, next->ai_addr, next->ai_addrlen) == 0) + break; /* success */ + /* XXX - It is unclear to me (datallah) whether we need to be + using a new socket each time */ + close(listenfd); + } + + freeaddrinfo(res); + + if (next == NULL) + 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 FALSE; + } + + if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) + gaim_debug_warning("network", "setsockopt: %s\n", strerror(errno)); + + memset(&sockin, 0, sizeof(struct sockaddr_in)); + sockin.sin_family = PF_INET; + sockin.sin_port = htons(port); + + if (bind(listenfd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) { + gaim_debug_warning("network", "bind: %s\n", strerror(errno)); + close(listenfd); + return FALSE; + } +#endif + + if (socket_type == SOCK_STREAM && listen(listenfd, 4) != 0) { + gaim_debug_warning("network", "listen: %s\n", strerror(errno)); + close(listenfd); + return FALSE; + } + fcntl(listenfd, F_SETFL, O_NONBLOCK); + + gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd)); + + ld = g_new0(ListenUPnPData, 1); + ld->listenfd = listenfd; + ld->adding = TRUE; + ld->retry = TRUE; + ld->cb = cb; + ld->cb_data = cb_data; + + 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; +} + +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, cb, cb_data); +} + +gboolean +gaim_network_listen_range(unsigned short start, unsigned short end, + int socket_type, GaimNetworkListenCallback cb, gpointer cb_data) +{ + gboolean ret = FALSE; + + if (gaim_prefs_get_bool("/core/network/ports_range_use")) { + start = gaim_prefs_get_int("/core/network/ports_range_start"); + end = gaim_prefs_get_int("/core/network/ports_range_end"); + } else { + if (end < start) + end = start; + } + + for (; start <= end; start++) { + ret = gaim_network_do_listen(start, socket_type, cb, cb_data); + if (ret) + break; + } + + return ret; +} + +unsigned short +gaim_network_get_port_from_fd(int fd) +{ + struct sockaddr_in addr; + socklen_t len; + + g_return_val_if_fail(fd >= 0, 0); + + len = sizeof(addr); + if (getsockname(fd, (struct sockaddr *) &addr, &len) == -1) { + gaim_debug_warning("network", "getsockname: %s\n", strerror(errno)); + return 0; + } + + return ntohs(addr.sin_port); +} + +void +gaim_network_init(void) +{ + gaim_prefs_add_none ("/core/network"); + gaim_prefs_add_bool ("/core/network/auto_ip", TRUE); + gaim_prefs_add_string("/core/network/public_ip", ""); + 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); +}