# HG changeset patch # User Paul Aurich # Date 1271467624 0 # Node ID 35a1cf247168bb7de56309b19639f87cad4654d0 # Parent 5be4137c4a68b2ab8c00d2090d45b46aa5a52206 Add network listen functions that accept a family argument (AF_INET(6?)). These allow code to portably support IPv6 for listeners (mostly file transfers and Bonjour). Callers should use the purple_socket_speaks_ipv4 to determine whether they need two sockets or just an IPv6 one. I used GIO's g_socket_speaks_ipv4 as the inspiration for that. diff -r 5be4137c4a68 -r 35a1cf247168 ChangeLog.API --- a/ChangeLog.API Fri Apr 16 03:44:18 2010 +0000 +++ b/ChangeLog.API Sat Apr 17 01:27:04 2010 +0000 @@ -19,7 +19,15 @@ IPs on the system. On systems with the getifaddrs() function, this will return both IPv4 and IPv6 addresses (excluding link-local and loopback addresses). On others, it returns just IPv4 addresses. + * purple_network_listen_family and + purple_network_listen_range_family. These will replace the + versions without _family in 3.0.0 and allow the caller to + specifically request either an IPv4 or IPv6 socket. IPv6 is + only supported if the getaddrinfo() function is available + at build-time (not the case on Windows, currently). * purple_prpl_got_media_caps + * purple_socket_get_family + * purple_socket_speaks_ipv4 * purple_unescape_text * purple_uuid_random * media_caps to the PurpleBuddy struct diff -r 5be4137c4a68 -r 35a1cf247168 configure.ac --- a/configure.ac Fri Apr 16 03:44:18 2010 +0000 +++ b/configure.ac Sat Apr 17 01:27:04 2010 +0000 @@ -184,6 +184,12 @@ [Define if struct sockaddr has an sa_len member])],[:], [#include ]) +dnl Check for v6-only sockets +AC_CHECK_DECL([IPV6_V6ONLY], + [AC_DEFINE([HAVE_IPV6_V6ONLY],[1], + [Define if the IPV6_V6ONLY setsockopt option exists])], + [], [#include ]) + dnl to prevent the g_stat()/g_unlink() crash, dnl (09:50:07) Robot101: LSchiere2: it's easy. +LC_SYS_LARGEFILE somewhere in configure.ac AC_SYS_LARGEFILE diff -r 5be4137c4a68 -r 35a1cf247168 libpurple/network.c --- a/libpurple/network.c Fri Apr 16 03:44:18 2010 +0000 +++ b/libpurple/network.c Sat Apr 17 01:27:04 2010 +0000 @@ -394,7 +394,7 @@ } static PurpleNetworkListenData * -purple_network_do_listen(unsigned short port, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data) +purple_network_do_listen(unsigned short port, int socket_family, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data) { int listenfd = -1; int flags; @@ -412,7 +412,7 @@ g_snprintf(serv, sizeof(serv), "%hu", port); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_flags = AI_PASSIVE; - hints.ai_family = AF_UNSPEC; + hints.ai_family = socket_family; hints.ai_socktype = socket_type; errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res); if (errnum != 0) { @@ -436,7 +436,7 @@ if (listenfd < 0) continue; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) - purple_debug_warning("network", "setsockopt: %s\n", g_strerror(errno)); + purple_debug_warning("network", "setsockopt(SO_REUSEADDR): %s\n", g_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 @@ -451,6 +451,13 @@ #else struct sockaddr_in sockin; + if (socket_family != AF_INET && socket_family != AF_UNSPEC) { + purple_debug_warning("network", "Address family %d only " + "supported when built with getaddrinfo() " + "support\n", socket_family); + return NULL; + } + if ((listenfd = socket(AF_INET, socket_type, 0)) < 0) { purple_debug_warning("network", "socket: %s\n", g_strerror(errno)); return NULL; @@ -492,7 +499,8 @@ listen_data->cb_data = cb_data; listen_data->socket_type = socket_type; - if (!listen_map_external || !purple_prefs_get_bool("/purple/network/map_ports")) + if (!purple_socket_speaks_ipv4(listenfd) || !listen_map_external || + !purple_prefs_get_bool("/purple/network/map_ports")) { purple_debug_info("network", "Skipping external port mapping.\n"); /* The pmp_map_cb does what we want to do */ @@ -519,17 +527,29 @@ } PurpleNetworkListenData * -purple_network_listen(unsigned short port, int socket_type, - PurpleNetworkListenCallback cb, gpointer cb_data) +purple_network_listen_family(unsigned short port, int socket_family, + int socket_type, PurpleNetworkListenCallback cb, + gpointer cb_data) { g_return_val_if_fail(port != 0, NULL); - return purple_network_do_listen(port, socket_type, cb, cb_data); + return purple_network_do_listen(port, socket_family, socket_type, + cb, cb_data); } PurpleNetworkListenData * -purple_network_listen_range(unsigned short start, unsigned short end, - int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data) +purple_network_listen(unsigned short port, int socket_type, + PurpleNetworkListenCallback cb, gpointer cb_data) +{ + return purple_network_listen_family(port, AF_UNSPEC, socket_type, + cb, cb_data); +} + +PurpleNetworkListenData * +purple_network_listen_range_family(unsigned short start, unsigned short end, + int socket_family, int socket_type, + PurpleNetworkListenCallback cb, + gpointer cb_data) { PurpleNetworkListenData *ret = NULL; @@ -542,7 +562,7 @@ } for (; start <= end; start++) { - ret = purple_network_do_listen(start, socket_type, cb, cb_data); + ret = purple_network_do_listen(start, AF_UNSPEC, socket_type, cb, cb_data); if (ret != NULL) break; } @@ -550,6 +570,15 @@ return ret; } +PurpleNetworkListenData * +purple_network_listen_range(unsigned short start, unsigned short end, + int socket_type, PurpleNetworkListenCallback cb, + gpointer cb_data) +{ + return purple_network_listen_range_family(start, end, AF_UNSPEC, + socket_type, cb, cb_data); +} + void purple_network_listen_cancel(PurpleNetworkListenData *listen_data) { if (listen_data->mapping_data != NULL) diff -r 5be4137c4a68 -r 35a1cf247168 libpurple/network.h --- a/libpurple/network.h Fri Apr 16 03:44:18 2010 +0000 +++ b/libpurple/network.h Sat Apr 17 01:27:04 2010 +0000 @@ -138,8 +138,8 @@ * * This opens a listening port. The caller will want to set up a watcher * of type PURPLE_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 + * 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. @@ -158,6 +158,27 @@ int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data); /** + * \copydoc purple_network_listen + * + * Libpurple does not currently do any port mapping (stateful firewall hole + * poking) for IPv6-only listeners (if an IPv6 socket supports v4-mapped + * addresses, a mapping is done). + * + * @param socket_family The protocol family of the socket. This should be + * AF_INET for IPv4 or AF_INET6 for IPv6. IPv6 sockets + * may or may not be able to accept IPv4 connections + * based on the system configuration (use + * purple_socket_speaks_ipv4 to check). If an IPv6 + * socket doesn't accept V4-mapped addresses, you will + * need a second listener to support both v4 and v6. + * @since 2.7.0 + * @deprecated This function will be renamed to purple_network_listen in 3.0.0. + */ +PurpleNetworkListenData *purple_network_listen_family(unsigned short port, + int socket_family, int socket_type, PurpleNetworkListenCallback cb, + gpointer cb_data); + +/** * Opens a listening port selected from a range of ports. The range of * ports used is chosen in the following manner: * If a range is specified in preferences, these values are used. @@ -192,6 +213,28 @@ PurpleNetworkListenCallback cb, gpointer cb_data); /** + * \copydoc purple_network_listen_range + * + * Libpurple does not currently do any port mapping (stateful firewall hole + * poking) for IPv6-only listeners (if an IPv6 socket supports v4-mapped + * addresses, a mapping is done). + * + * @param socket_family The protocol family of the socket. This should be + * AF_INET for IPv4 or AF_INET6 for IPv6. IPv6 sockets + * may or may not be able to accept IPv4 connections + * based on the system configuration (use + * purple_socket_speaks_ipv4 to check). If an IPv6 + * socket doesn't accept V4-mapped addresses, you will + * need a second listener to support both v4 and v6. + * @since 2.7.0 + * @deprecated This function will be renamed to purple_network_listen_range + * in 3.0.0. + */ +PurpleNetworkListenData *purple_network_listen_range_family( + unsigned short start, unsigned short end, int socket_family, + int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data); + +/** * This can be used to cancel any in-progress listener connection * by passing in the return value from either purple_network_listen() * or purple_network_listen_range(). diff -r 5be4137c4a68 -r 35a1cf247168 libpurple/util.c --- a/libpurple/util.c Fri Apr 16 03:44:18 2010 +0000 +++ b/libpurple/util.c Sat Apr 17 01:27:04 2010 +0000 @@ -3002,6 +3002,47 @@ return NULL; } +int +purple_socket_get_family(int fd) +{ + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + + g_return_val_if_fail(fd >= 0, -1); + + if (getsockname(fd, (struct sockaddr *)&addr, &len)) + return -1; + + return ((struct sockaddr *)&addr)->sa_family; +} + +gboolean +purple_socket_speaks_ipv4(int fd) +{ + int family; + + g_return_val_if_fail(fd >= 0, FALSE); + + family = purple_socket_get_family(fd); + + switch (family) { + case AF_INET: + return TRUE; +#if defined(IPV6_V6ONLY) + case AF_INET6: + { + int val = 0; + guint len = sizeof(val); + + if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) != 0) + return FALSE; + return !val; + } +#endif + default: + return FALSE; + } +} /************************************************************************** * String Functions diff -r 5be4137c4a68 -r 35a1cf247168 libpurple/util.h --- a/libpurple/util.h Fri Apr 16 03:44:18 2010 +0000 +++ b/libpurple/util.h Sat Apr 17 01:27:04 2010 +0000 @@ -819,6 +819,28 @@ */ char *purple_fd_get_ip(int fd); +/** + * Returns the address family of a socket. + * + * @param fd The socket file descriptor. + * + * @return The address family of the socket (AF_INET, AF_INET6, etc) + * @since 2.7.0 + */ +int purple_socket_get_family(int fd); + +/** + * Returns TRUE if a socket is capable of speaking IPv4. + * + * This is the case for IPv4 sockets and, on some systems, IPv6 sockets + * (due to the IPv4-mapped address functionality). + * + * @param fd The socket file descriptor + * @return TRUE if a socket can speak IPv4. + * @since 2.7.0 + */ +gboolean purple_socket_speaks_ipv4(int fd); + /*@}*/