# HG changeset patch # User Daniel Atallah # Date 1266987609 0 # Node ID 755219afed9f7daeeaf652ceac9c025f98bf7e86 # Parent 59099ccd141eab8169d0e0efd2d34b9e17b8678f Implementation of IPv6 support for Bonjour * IPv6 buddy presences are preferred to IPv4 presences * File Transfers currently don't support IPv6 (due to limitations in network.c) Fixes #11290 diff -r 59099ccd141e -r 755219afed9f ChangeLog --- a/ChangeLog Wed Feb 24 04:39:15 2010 +0000 +++ b/ChangeLog Wed Feb 24 05:00:09 2010 +0000 @@ -18,6 +18,9 @@ * Make the search dialog unobtrusive in the conversation window (by making it look and behave like the search dialog in Firefox) + Bonjour: + * Added support for IPv6. (Thanks to T_X for testing) + version 2.6.6 (02/18/2010): libpurple: * Fix 'make check' on OS X. (David Fang) diff -r 59099ccd141e -r 755219afed9f libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Wed Feb 24 04:39:15 2010 +0000 +++ b/libpurple/protocols/bonjour/bonjour.c Wed Feb 24 05:00:09 2010 +0000 @@ -101,6 +101,8 @@ /* Start waiting for jabber connections (iChat style) */ bd->jabber_data = g_new0(BonjourJabber, 1); + bd->jabber_data->socket = -1; + bd->jabber_data->socket6 = -1; bd->jabber_data->port = purple_account_get_int(account, "port", BONJOUR_DEFAULT_PORT); bd->jabber_data->account = account; diff -r 59099ccd141e -r 755219afed9f libpurple/protocols/bonjour/bonjour_ft.c --- a/libpurple/protocols/bonjour/bonjour_ft.c Wed Feb 24 04:39:15 2010 +0000 +++ b/libpurple/protocols/bonjour/bonjour_ft.c Wed Feb 24 05:00:09 2010 +0000 @@ -747,8 +747,7 @@ XepIq *iq; xmlnode *query, *streamhost; gchar *port; - const char *next_ip, *local_ip; - const char token [] = ";"; + GSList *local_ips; BonjourData *bd; purple_debug_info("bonjour", "Bonjour-bytestreams-listen. sock=%d.\n", sock); @@ -773,17 +772,16 @@ xfer->local_port = purple_network_get_port_from_fd(sock); - local_ip = purple_network_get_my_ip_ext2(sock); - /* cheat a little here - the intent of the "const" attribute is to make it clear that the string doesn't need to be freed */ - next_ip = strtok((char *)local_ip, token); + local_ips = bonjour_jabber_get_local_ips(sock); port = g_strdup_printf("%hu", xfer->local_port); - while(next_ip != NULL) { + while(local_ips) { streamhost = xmlnode_new_child(query, "streamhost"); xmlnode_set_attrib(streamhost, "jid", xf->sid); - xmlnode_set_attrib(streamhost, "host", next_ip); + xmlnode_set_attrib(streamhost, "host", local_ips->data); xmlnode_set_attrib(streamhost, "port", port); - next_ip = strtok(NULL, token); + g_free(local_ips->data); + local_ips = g_slist_delete_link(local_ips, local_ips); } g_free(port); @@ -796,15 +794,17 @@ XepXfer *xf; if(xfer == NULL) return; + purple_debug_info("bonjour", "Bonjour-bytestreams-init.\n"); xf = xfer->data; + purple_network_listen_map_external(FALSE); xf->listen_data = purple_network_listen_range(0, 0, SOCK_STREAM, bonjour_bytestreams_listen, xfer); purple_network_listen_map_external(TRUE); - if (xf->listen_data == NULL) { + if (xf->listen_data == NULL) purple_xfer_cancel_local(xfer); - } + return; } diff -r 59099ccd141e -r 755219afed9f libpurple/protocols/bonjour/jabber.c --- a/libpurple/protocols/bonjour/jabber.c Wed Feb 24 04:39:15 2010 +0000 +++ b/libpurple/protocols/bonjour/jabber.c Wed Feb 24 05:00:09 2010 +0000 @@ -44,6 +44,11 @@ #endif #include +#ifdef HAVE_GETIFADDRS +#include +#endif + + #include "network.h" #include "eventloop.h" #include "connection.h" @@ -623,15 +628,22 @@ } +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + static void _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition) { BonjourJabber *jdata = data; - struct sockaddr_in their_addr; /* connector's address information */ - socklen_t sin_size = sizeof(struct sockaddr); + struct sockaddr_storage their_addr; /* connector's address information */ + socklen_t sin_size = sizeof(struct sockaddr_storage); int client_socket; int flags; - char *address_text = NULL; +#ifdef HAVE_INET_NTOP + char addrstr[INET6_ADDRSTRLEN]; +#endif + const char *address_text; struct _match_buddies_by_address_t *mbba; BonjourJabberConversation *bconv; GSList *buddies; @@ -640,7 +652,9 @@ if (condition != PURPLE_INPUT_READ) return; - if ((client_socket = accept(server_socket, (struct sockaddr *)&their_addr, &sin_size)) == -1) + memset(&their_addr, 0, sin_size); + + if ((client_socket = accept(server_socket, (struct sockaddr*)&their_addr, &sin_size)) == -1) return; flags = fcntl(client_socket, F_GETFL); @@ -650,7 +664,16 @@ #endif /* Look for the buddy that has opened the conversation and fill information */ - address_text = inet_ntoa(their_addr.sin_addr); +#ifdef HAVE_INET_NTOP + if (their_addr.ss_family == AF_INET6) + address_text = inet_ntop(their_addr.ss_family, &((struct sockaddr_in6 *)&their_addr)->sin6_addr, + addrstr, sizeof(addrstr)); + else + address_text = inet_ntop(their_addr.ss_family, &((struct sockaddr_in *)&their_addr)->sin_addr, + addrstr, sizeof(addrstr)); +#else + address_text = inet_ntoa(((struct sockaddr_in *)&their_addr)->sin_addr); +#endif purple_debug_info("bonjour", "Received incoming connection from %s.\n", address_text); mbba = g_new0(struct _match_buddies_by_address_t, 1); mbba->address = address_text; @@ -680,52 +703,42 @@ } -gint -bonjour_jabber_start(BonjourJabber *jdata) +static int +start_serversocket_listening(int port, int socket, struct sockaddr *addr, size_t addr_size, gboolean ip6, gboolean allow_port_fallback) { - struct sockaddr_in my_addr; + int ret_port = port; - /* Open a listening socket for incoming conversations */ - jdata->socket = socket(PF_INET, SOCK_STREAM, 0); - if (jdata->socket < 0) { - gchar *buf = g_strdup_printf(_("Unable to create socket: %s"), - g_strerror(errno)); - purple_connection_error_reason(jdata->account->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, buf); - g_free(buf); - return -1; - } - - memset(&my_addr, 0, sizeof(struct sockaddr_in)); - my_addr.sin_family = AF_INET; + purple_debug_info("bonjour", "Attempting to bind IPv%d socket to port %d.\n", ip6 ? 6 : 4, port); /* Try to use the specified port - if it isn't available, use a random port */ - my_addr.sin_port = htons(jdata->port); - if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) != 0) - { + if (bind(socket, addr, addr_size) != 0) { + purple_debug_info("bonjour", "Unable to bind to specified " - "port %i: %s\n", jdata->port, g_strerror(errno)); - my_addr.sin_port = 0; - if (bind(jdata->socket, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) != 0) - { - gchar *buf = g_strdup_printf(_("Unable to bind socket " - "to port: %s"), g_strerror(errno)); - purple_connection_error_reason(jdata->account->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, buf); - g_free(buf); + "port %i: %s\n", port, g_strerror(errno)); + + if (!allow_port_fallback) { + purple_debug_warning("bonjour", "Not attempting random port assignment.\n"); return -1; } - jdata->port = purple_network_get_port_from_fd(jdata->socket); +#ifdef PF_INET6 + if (ip6) + ((struct sockaddr_in6 *) addr)->sin6_port = 0; + else +#endif + ((struct sockaddr_in *) addr)->sin_port = 0; + + if (bind(socket, addr, addr_size) != 0) { + purple_debug_error("bonjour", "Unable to bind IPv%d socket to port: %s\n", ip6 ? 6 : 4, g_strerror(errno)); + return -1; + } + ret_port = purple_network_get_port_from_fd(socket); } + purple_debug_info("bonjour", "Bound IPv%d socket to port %d.\n", ip6 ? 6 : 4, ret_port); + /* Attempt to listen on the bound socket */ - if (listen(jdata->socket, 10) != 0) - { - gchar *buf = g_strdup_printf(_("Unable to listen on socket: %s"), - g_strerror(errno)); - purple_connection_error_reason(jdata->account->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, buf); - g_free(buf); + if (listen(socket, 10) != 0) { + purple_debug_error("bonjour", "Unable to listen on IPv%d socket: %s\n", ip6 ? 6 : 4, g_strerror(errno)); return -1; } @@ -739,8 +752,66 @@ } #endif - /* Open a watcher in the socket we have just opened */ - jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata); + return ret_port; +} + +gint +bonjour_jabber_start(BonjourJabber *jdata) +{ + int ipv6_port = -1, ipv4_port = -1; + + /* Open a listening socket for incoming conversations */ +#ifdef PF_INET6 + jdata->socket6 = socket(PF_INET6, SOCK_STREAM, 0); +#endif + jdata->socket = socket(PF_INET, SOCK_STREAM, 0); + if (jdata->socket == -1 && jdata->socket6 == -1) { + purple_debug_error("bonjour", "Unable to create socket: %s", + g_strerror(errno)); + return -1; + } + +#ifdef PF_INET6 + if (jdata->socket6 != -1) { + struct sockaddr_in6 addr6; + memset(&addr6, 0, sizeof(addr6)); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(jdata->port); + addr6.sin6_addr = in6addr_any; + ipv6_port = start_serversocket_listening(jdata->port, jdata->socket6, (struct sockaddr *) &addr6, sizeof(addr6), TRUE, TRUE); + /* Open a watcher in the socket we have just opened */ + if (ipv6_port > 0) { + jdata->watcher_id6 = purple_input_add(jdata->socket6, PURPLE_INPUT_READ, _server_socket_handler, jdata); + jdata->port = ipv6_port; + } else { + purple_debug_error("bonjour", "Failed to start listening on IPv6 socket.\n"); + close(jdata->socket6); + jdata->socket6 = -1; + } + } +#endif + if (jdata->socket != -1) { + struct sockaddr_in addr4; + memset(&addr4, 0, sizeof(addr4)); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(jdata->port); + ipv4_port = start_serversocket_listening(jdata->port, jdata->socket, (struct sockaddr *) &addr4, sizeof(addr4), FALSE, ipv6_port != -1); + /* Open a watcher in the socket we have just opened */ + if (ipv4_port > 0) { + jdata->watcher_id = purple_input_add(jdata->socket, PURPLE_INPUT_READ, _server_socket_handler, jdata); + jdata->port = ipv4_port; + } else { + purple_debug_error("bonjour", "Failed to start listening on IPv4 socket.\n"); + close(jdata->socket); + jdata->socket = -1; + } + } + + if (!(ipv6_port > 0 || ipv4_port > 0)) { + purple_debug_error("bonjour", "Unable to listen on socket: %s", + g_strerror(errno)); + return -1; + } return jdata->port; } @@ -1101,6 +1172,10 @@ close(jdata->socket); if (jdata->watcher_id > 0) purple_input_remove(jdata->watcher_id); + if (jdata->socket6 >= 0) + close(jdata->socket6); + if (jdata->watcher_id6 > 0) + purple_input_remove(jdata->watcher_id6); /* Close all the conversation sockets and remove all the watchers after sending end streams */ if (jdata->account->gc != NULL) { @@ -1234,58 +1309,97 @@ return (ret >= 0) ? 0 : -1; } -/* This returns a ';' delimited string containing all non-localhost IPs */ -const char * -purple_network_get_my_ip_ext2(int fd) +/* This returns a list containing all non-localhost IPs */ +GSList * +bonjour_jabber_get_local_ips(int fd) { - char buffer[1024]; - static char ip_ext[17 * 10]; + GSList *ips = NULL; + const char *address_text; + int ret; + +#ifdef HAVE_GETIFADDRS /* This is required for IPv6 */ + { + struct ifaddrs *ifap, *ifa; + struct sockaddr *addr; + char addrstr[INET6_ADDRSTRLEN]; + + ret = getifaddrs(&ifap); + if (ret != 0) { + const char *error = g_strerror(errno); + purple_debug_error("bonjour", "getifaddrs() error: %s\n", error ? error : "(null)"); + return NULL; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) { + if (!(ifa->ifa_flags & IFF_RUNNING) || (ifa->ifa_flags & IFF_LOOPBACK) || ifa->ifa_addr == NULL) + continue; + + addr = ifa->ifa_addr; + address_text = NULL; + switch (addr->sa_family) { + case AF_INET: + address_text = inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr, + addrstr, sizeof(addrstr)); + break; +#ifdef PF_INET6 + case AF_INET6: + address_text = inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr, + addrstr, sizeof(addrstr)); + break; +#endif + } + + if (address_text != NULL) { + if (addr->sa_family == AF_INET) + ips = g_slist_append(ips, g_strdup(address_text)); + else + ips = g_slist_prepend(ips, g_strdup(address_text)); + } + } + + freeifaddrs(ifap); + + } +#else + { char *tmp; - char *tip; struct ifconf ifc; struct ifreq *ifr; + char buffer[1024]; struct sockaddr_in *sinptr; - guint32 lhost = htonl(127 * 256 * 256 * 256 + 1); - long unsigned int add; int source = fd; - int len, count = 0; 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); + ret = ioctl(source, SIOCGIFCONF, &ifc); if (fd < 0) close(source); - memset(ip_ext, 0, sizeof(ip_ext)); - memcpy(ip_ext, "0.0.0.0", 7); + if (ret < 0) { + const char *error = g_strerror(errno); + purple_debug_error("bonjour", "ioctl(SIOCGIFCONF) error: %s\n", error ? error : "(null)"); + return NULL; + } + tmp = buffer; - tip = ip_ext; - while (tmp < buffer + ifc.ifc_len && count < 10) - { + while (tmp < buffer + ifc.ifc_len) { ifr = (struct ifreq *)tmp; tmp += HX_SIZE_OF_IFREQ(*ifr); - if (ifr->ifr_addr.sa_family == AF_INET) - { + 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); - len = g_snprintf(tip, 17, "%lu.%lu.%lu.%lu;", - ((add >> 24) & 255), - ((add >> 16) & 255), - ((add >> 8) & 255), - add & 255); - tip = &tip[len]; - count++; - continue; + if ((ntohl(sinptr->sin_addr.s_addr) >> 24) != 127) { + address_text = inet_ntoa(sinptr->sin_addr); + ips = g_slist_prepend(ips, g_strdup(address_text)); } - } + } } + } +#endif - return ip_ext; + return ips; } diff -r 59099ccd141e -r 755219afed9f libpurple/protocols/bonjour/jabber.h --- a/libpurple/protocols/bonjour/jabber.h Wed Feb 24 04:39:15 2010 +0000 +++ b/libpurple/protocols/bonjour/jabber.h Wed Feb 24 05:00:09 2010 +0000 @@ -37,7 +37,9 @@ { gint port; gint socket; + gint socket6; gint watcher_id; + gint watcher_id6; PurpleAccount *account; GSList *pending_conversations; } BonjourJabber; @@ -105,6 +107,6 @@ XepIq *xep_iq_new(void *data, XepIqType type, const char *to, const char *from, const char *id); int xep_iq_send_and_free(XepIq *iq); -const char *purple_network_get_my_ip_ext2(int fd); +GSList * bonjour_jabber_get_local_ips(int fd); #endif /* _BONJOUR_JABBER_H_ */ diff -r 59099ccd141e -r 755219afed9f libpurple/protocols/bonjour/mdns_avahi.c --- a/libpurple/protocols/bonjour/mdns_avahi.c Wed Feb 24 04:39:15 2010 +0000 +++ b/libpurple/protocols/bonjour/mdns_avahi.c Wed Feb 24 05:00:09 2010 +0000 @@ -189,8 +189,12 @@ bb->ips = g_slist_remove(bb->ips, rd->ip); g_free((gchar *) rd->ip); } - bb->ips = g_slist_prepend(bb->ips, g_strdup(ip)); - rd->ip = bb->ips->data; + rd->ip = g_strdup(ip); + /* IPv6 goes at the front of the list and IPv4 at the end so that we "prefer" IPv6, if present */ + if (protocol == AVAHI_PROTO_INET6) + bb->ips = g_slist_prepend(bb->ips, (gchar *) rd->ip); + else + bb->ips = g_slist_append(bb->ips, (gchar *) rd->ip); } bb->port_p2pj = port; @@ -249,7 +253,7 @@ /* Make sure it isn't us */ if (purple_utf8_strcasecmp(name, account->username) != 0) { if (!avahi_service_resolver_new(avahi_service_browser_get_client(b), - interface, protocol, name, type, domain, AVAHI_PROTO_INET, + interface, protocol, name, type, domain, protocol, 0, _resolver_callback, account)) { purple_debug_warning("bonjour", "_browser_callback -- Error initiating resolver: %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b)))); @@ -448,14 +452,14 @@ case PUBLISH_START: publish_result = avahi_entry_group_add_service_strlst( idata->group, AVAHI_IF_UNSPEC, - AVAHI_PROTO_INET, 0, + AVAHI_PROTO_UNSPEC, 0, purple_account_get_username(data->account), LINK_LOCAL_RECORD_NAME, NULL, NULL, data->port_p2pj, lst); break; case PUBLISH_UPDATE: publish_result = avahi_entry_group_update_service_txt_strlst( idata->group, AVAHI_IF_UNSPEC, - AVAHI_PROTO_INET, 0, + AVAHI_PROTO_UNSPEC, 0, purple_account_get_username(data->account), LINK_LOCAL_RECORD_NAME, NULL, lst); break; @@ -487,7 +491,7 @@ g_return_val_if_fail(idata != NULL, FALSE); - idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, LINK_LOCAL_RECORD_NAME, NULL, 0, _browser_callback, data->account); + idata->sb = avahi_service_browser_new(idata->client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, LINK_LOCAL_RECORD_NAME, NULL, 0, _browser_callback, data->account); if (!idata->sb) { purple_debug_error("bonjour", @@ -533,7 +537,7 @@ purple_account_get_username(data->account)); ret = avahi_entry_group_add_record(idata->buddy_icon_group, AVAHI_IF_UNSPEC, - AVAHI_PROTO_INET, flags, svc_name, + AVAHI_PROTO_UNSPEC, flags, svc_name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 120, avatar_data, avatar_len); g_free(svc_name); @@ -622,7 +626,7 @@ name = g_strdup_printf("%s." LINK_LOCAL_RECORD_NAME "local", buddy->name); idata->buddy_icon_rec_browser = avahi_record_browser_new(session_idata->client, AVAHI_IF_UNSPEC, - AVAHI_PROTO_INET, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0, + AVAHI_PROTO_UNSPEC, name, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_NULL, 0, _buddy_icon_record_cb, buddy); g_free(name);