diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/wee-hook.c | 49 | ||||
-rw-r--r-- | src/core/wee-hook.h | 28 | ||||
-rw-r--r-- | src/core/wee-network.c | 664 |
3 files changed, 505 insertions, 236 deletions
diff --git a/src/core/wee-hook.c b/src/core/wee-hook.c index 5080e7f53..d2d77da31 100644 --- a/src/core/wee-hook.c +++ b/src/core/wee-hook.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2003-2012 Sebastien Helleu <flashcode@flashtux.org> + * Copyright (C) 2012 Simon Arlott * * This file is part of WeeChat, the extensible chat client. * @@ -1713,13 +1714,16 @@ hook_process_run (struct t_hook *hook_process) struct t_hook * hook_connect (struct t_weechat_plugin *plugin, const char *proxy, - const char *address, int port, int sock, int ipv6, + const char *address, int port, int ipv6, int retry, void *gnutls_sess, void *gnutls_cb, int gnutls_dhkey_size, const char *gnutls_priorities, const char *local_hostname, t_hook_callback_connect *callback, void *callback_data) { struct t_hook *new_hook; struct t_hook_connect *new_hook_connect; +#ifdef HOOK_CONNECT_MAX_SOCKETS + int i; +#endif #ifndef HAVE_GNUTLS /* make C compiler happy */ @@ -1729,7 +1733,7 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, (void) gnutls_priorities; #endif - if ((sock < 0) || !address || (port <= 0) || !callback) + if (!address || (port <= 0) || !callback) return NULL; new_hook = malloc (sizeof (*new_hook)); @@ -1750,8 +1754,9 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, new_hook_connect->proxy = (proxy) ? strdup (proxy) : NULL; new_hook_connect->address = strdup (address); new_hook_connect->port = port; - new_hook_connect->sock = sock; + new_hook_connect->sock = -1; new_hook_connect->ipv6 = ipv6; + new_hook_connect->retry = retry; #ifdef HAVE_GNUTLS new_hook_connect->gnutls_sess = gnutls_sess; new_hook_connect->gnutls_cb = gnutls_cb; @@ -1763,6 +1768,8 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, strdup (local_hostname) : NULL; new_hook_connect->child_read = -1; new_hook_connect->child_write = -1; + new_hook_connect->child_recv = -1; + new_hook_connect->child_send = -1; new_hook_connect->child_pid = 0; new_hook_connect->hook_child_timer = NULL; new_hook_connect->hook_fd = NULL; @@ -1770,6 +1777,13 @@ hook_connect (struct t_weechat_plugin *plugin, const char *proxy, new_hook_connect->handshake_hook_timer = NULL; new_hook_connect->handshake_fd_flags = 0; new_hook_connect->handshake_ip_address = NULL; +#ifdef HOOK_CONNECT_MAX_SOCKETS + for (i = 0; i < HOOK_CONNECT_MAX_SOCKETS; i++) + { + new_hook_connect->sock_v4[i] = -1; + new_hook_connect->sock_v6[i] = -1; + } +#endif hook_add_to_list (new_hook); @@ -3166,6 +3180,19 @@ unhook (struct t_hook *hook) close (HOOK_CONNECT(hook, child_read)); if (HOOK_CONNECT(hook, child_write) != -1) close (HOOK_CONNECT(hook, child_write)); + if (HOOK_CONNECT(hook, child_recv) != -1) + close (HOOK_CONNECT(hook, child_recv)); + if (HOOK_CONNECT(hook, child_send) != -1) + close (HOOK_CONNECT(hook, child_send)); +#ifdef HOOK_CONNECT_MAX_SOCKETS + for (i = 0; i < HOOK_CONNECT_MAX_SOCKETS; i++) + { + if (HOOK_CONNECT(hook, sock_v4[i]) != -1) + close (HOOK_CONNECT(hook, sock_v4[i])); + if (HOOK_CONNECT(hook, sock_v6[i]) != -1) + close (HOOK_CONNECT(hook, sock_v6[i])); + } +#endif break; case HOOK_TYPE_PRINT: if (HOOK_PRINT(hook, message)) @@ -3478,6 +3505,8 @@ hook_add_to_infolist_type (struct t_infolist *infolist, int type, return 0; if (!infolist_new_var_integer (ptr_item, "ipv6", HOOK_CONNECT(ptr_hook, ipv6))) return 0; + if (!infolist_new_var_integer (ptr_item, "retry", HOOK_CONNECT(ptr_hook, retry))) + return 0; #ifdef HAVE_GNUTLS if (!infolist_new_var_pointer (ptr_item, "gnutls_sess", HOOK_CONNECT(ptr_hook, gnutls_sess))) return 0; @@ -3492,6 +3521,10 @@ hook_add_to_infolist_type (struct t_infolist *infolist, int type, return 0; if (!infolist_new_var_integer (ptr_item, "child_write", HOOK_CONNECT(ptr_hook, child_write))) return 0; + if (!infolist_new_var_integer (ptr_item, "child_recv", HOOK_CONNECT(ptr_hook, child_recv))) + return 0; + if (!infolist_new_var_integer (ptr_item, "child_send", HOOK_CONNECT(ptr_hook, child_send))) + return 0; if (!infolist_new_var_integer (ptr_item, "child_pid", HOOK_CONNECT(ptr_hook, child_pid))) return 0; if (!infolist_new_var_pointer (ptr_item, "hook_child_timer", HOOK_CONNECT(ptr_hook, hook_child_timer))) @@ -3886,6 +3919,7 @@ hook_print_log () log_printf (" port. . . . . . . . . : %d", HOOK_CONNECT(ptr_hook, port)); log_printf (" sock. . . . . . . . . : %d", HOOK_CONNECT(ptr_hook, sock)); log_printf (" ipv6. . . . . . . . . : %d", HOOK_CONNECT(ptr_hook, ipv6)); + log_printf (" retry . . . . . . . . : %d", HOOK_CONNECT(ptr_hook, retry)); #ifdef HAVE_GNUTLS log_printf (" gnutls_sess . . . . . : 0x%lx", HOOK_CONNECT(ptr_hook, gnutls_sess)); log_printf (" gnutls_cb . . . . . . : 0x%lx", HOOK_CONNECT(ptr_hook, gnutls_cb)); @@ -3895,6 +3929,8 @@ hook_print_log () log_printf (" local_hostname. . . . : '%s'", HOOK_CONNECT(ptr_hook, local_hostname)); log_printf (" child_read. . . . . . : %d", HOOK_CONNECT(ptr_hook, child_read)); log_printf (" child_write . . . . . : %d", HOOK_CONNECT(ptr_hook, child_write)); + log_printf (" child_recv. . . . . . : %d", HOOK_CONNECT(ptr_hook, child_recv)); + log_printf (" child_send. . . . . . : %d", HOOK_CONNECT(ptr_hook, child_send)); log_printf (" child_pid . . . . . . : %d", HOOK_CONNECT(ptr_hook, child_pid)); log_printf (" hook_child_timer. . . : 0x%lx", HOOK_CONNECT(ptr_hook, hook_child_timer)); log_printf (" hook_fd . . . . . . . : 0x%lx", HOOK_CONNECT(ptr_hook, hook_fd)); @@ -3902,6 +3938,13 @@ hook_print_log () log_printf (" handshake_hook_timer. : 0x%lx", HOOK_CONNECT(ptr_hook, handshake_hook_timer)); log_printf (" handshake_fd_flags. . : %d", HOOK_CONNECT(ptr_hook, handshake_fd_flags)); log_printf (" handshake_ip_address. : '%s'", HOOK_CONNECT(ptr_hook, handshake_ip_address)); +#ifdef HOOK_CONNECT_MAX_SOCKETS + for (i = 0; i < HOOK_CONNECT_MAX_SOCKETS; i++) + { + log_printf (" sock_v4[%d]. . . . . . : '%d'", HOOK_CONNECT(ptr_hook, sock_v4[i])); + log_printf (" sock_v6[%d]. . . . . . : '%d'", HOOK_CONNECT(ptr_hook, sock_v6[i])); + } +#endif } break; case HOOK_TYPE_PRINT: diff --git a/src/core/wee-hook.h b/src/core/wee-hook.h index c1375d391..1ad0b6e1e 100644 --- a/src/core/wee-hook.h +++ b/src/core/wee-hook.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2003-2012 Sebastien Helleu <flashcode@flashtux.org> + * Copyright (C) 2012 Simon Arlott * * This file is part of WeeChat, the extensible chat client. * @@ -26,6 +27,18 @@ #include <gnutls/gnutls.h> #endif +#ifdef __CYGWIN__ +/* + * For the connect hook, when this is defined an array of sockets will + * be passed from the parent process to the child process instead of using + * SCM_RIGHTS to pass a socket back from the child process to parent process. + * + * This allows connections to work on Windows but it limits the number of + * IPs that can be attempted each time. + */ +#define HOOK_CONNECT_MAX_SOCKETS 4 +#endif + struct t_gui_bar; struct t_gui_buffer; struct t_gui_line; @@ -212,7 +225,7 @@ struct t_hook_process /* hook connect */ typedef int (t_hook_callback_connect)(void *data, int status, - int gnutls_rc, + int gnutls_rc, int sock, const char *error, const char *ip_address); @@ -235,8 +248,9 @@ struct t_hook_connect char *proxy; /* proxy (optional) */ char *address; /* peer address */ int port; /* peer port */ - int sock; /* socket (created by caller) */ - int ipv6; /* IPv6 connection ? */ + int ipv6; /* use IPv6 */ + int sock; /* socket (set when connected) */ + int retry; /* retry count */ #ifdef HAVE_GNUTLS gnutls_session_t *gnutls_sess; /* GnuTLS session (SSL connection) */ gnutls_callback_t *gnutls_cb; /* GnuTLS callback during handshake */ @@ -246,6 +260,8 @@ struct t_hook_connect char *local_hostname; /* force local hostname (optional) */ int child_read; /* to read data in pipe from child */ int child_write; /* to write data in pipe for child */ + int child_recv; /* to read data from child socket */ + int child_send; /* to write data to child socket */ pid_t child_pid; /* pid of child process (connecting) */ struct t_hook *hook_child_timer; /* timer for child process timeout */ struct t_hook *hook_fd; /* pointer to fd hook */ @@ -253,6 +269,10 @@ struct t_hook_connect struct t_hook *handshake_hook_timer; /* timer for handshake timeout */ int handshake_fd_flags; /* socket flags saved for handshake */ char *handshake_ip_address; /* ip address (used for handshake) */ +#ifdef HOOK_CONNECT_MAX_SOCKETS + int sock_v4[HOOK_CONNECT_MAX_SOCKETS]; /* IPv4 sockets for connecting */ + int sock_v6[HOOK_CONNECT_MAX_SOCKETS]; /* IPv6 sockets for connecting */ +#endif }; /* hook print */ @@ -457,7 +477,7 @@ extern struct t_hook *hook_process_hashtable (struct t_weechat_plugin *plugin, void *callback_data); extern struct t_hook *hook_connect (struct t_weechat_plugin *plugin, const char *proxy, const char *address, - int port, int sock, int ipv6, + int port, int ipv6, int retry, void *gnutls_session, void *gnutls_cb, int gnutls_dhkey_size, const char *gnutls_priorities, diff --git a/src/core/wee-network.c b/src/core/wee-network.c index a37be51d3..0f2a98739 100644 --- a/src/core/wee-network.c +++ b/src/core/wee-network.c @@ -2,6 +2,7 @@ * Copyright (C) 2003-2012 Sebastien Helleu <flashcode@flashtux.org> * Copyright (C) 2005-2010 Emmanuel Bouthenot <kolter@openics.org> * Copyright (C) 2010 Gu1ll4um3r0m41n <aeroxteam@gmail.com> + * Copyright (C) 2012 Simon Arlott * * This file is part of WeeChat, the extensible chat client. * @@ -38,6 +39,7 @@ #include <netdb.h> #include <errno.h> #include <gcrypt.h> +#include <sys/time.h> #ifdef HAVE_GNUTLS #include <gnutls/gnutls.h> @@ -645,20 +647,45 @@ void network_connect_child (struct t_hook *hook_connect) { struct t_proxy *ptr_proxy; - struct addrinfo hints, *res, *res_local, *ptr_res; + struct addrinfo hints, *res_local, *res_remote, *ptr_res, *ptr_loc; + char port[NI_MAXSERV + 1]; char status_str[2], *ptr_address, *status_with_string; - char ipv4_address[INET_ADDRSTRLEN + 1], ipv6_address[INET6_ADDRSTRLEN + 1]; + char remote_address[NI_MAXHOST + 1]; char status_without_string[1 + 5 + 1]; const char *error; int rc, length, num_written; + int sock, set, flags; +#ifdef HOOK_CONNECT_MAX_SOCKETS + int j; +#else + struct msghdr msg; + struct cmsghdr *cmsg; + char msg_buf[CMSG_SPACE(sizeof (sock))]; +#endif + /* + * indicates that something is wrong with whichever group of + * servers is being tried first after connecting, so start at + * a different offset to increase the chance of success + */ + int retry, rand_num, i; + int num_groups, tmp_num_groups, num_hosts, tmp_host; + struct addrinfo **res_reorder; + int last_af; + struct timeval tv_time; - res = NULL; res_local = NULL; + res_remote = NULL; + res_reorder = NULL; + port[0] = '\0'; status_str[1] = '\0'; + status_with_string = NULL; ptr_address = NULL; + gettimeofday (&tv_time, NULL); + srand ((tv_time.tv_sec * tv_time.tv_usec) ^ getpid ()); + ptr_proxy = NULL; if (HOOK_CONNECT(hook_connect, proxy) && HOOK_CONNECT(hook_connect, proxy)[0]) @@ -672,18 +699,86 @@ network_connect_child (struct t_hook *hook_connect) num_written = write (HOOK_CONNECT(hook_connect, child_write), status_without_string, strlen (status_without_string)); (void) num_written; - return; + goto end; } } + /* get info about peer */ + memset (&hints, 0, sizeof (hints)); + hints.ai_socktype = SOCK_STREAM; +#ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG; +#endif if (ptr_proxy) { - /* get info about peer */ - memset (&hints, 0, sizeof (hints)); hints.ai_family = (CONFIG_BOOLEAN(ptr_proxy->options[PROXY_OPTION_IPV6])) ? - AF_INET6 : AF_INET; + AF_UNSPEC : AF_INET; + snprintf (port, sizeof (port), "%d", CONFIG_INTEGER(ptr_proxy->options[PROXY_OPTION_PORT])); + rc = getaddrinfo (CONFIG_STRING(ptr_proxy->options[PROXY_OPTION_ADDRESS]), + port, &hints, &res_remote); + } + else + { + hints.ai_family = HOOK_CONNECT(hook_connect, ipv6) ? AF_UNSPEC : AF_INET; + snprintf (port, sizeof (port), "%d", HOOK_CONNECT(hook_connect, port)); + rc = getaddrinfo (HOOK_CONNECT(hook_connect, address), port, &hints, &res_remote); + } + + if (rc != 0) + { + /* address not found */ + status_with_string = NULL; + error = gai_strerror (rc); + if (error) + { + length = 1 + 5 + strlen (error) + 1; + status_with_string = malloc (length); + if (status_with_string) + { + snprintf (status_with_string, length, "%c%05d%s", + '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND, + (int)strlen (error), error); + } + } + if (status_with_string) + { + num_written = write (HOOK_CONNECT(hook_connect, child_write), + status_with_string, strlen (status_with_string)); + } + else + { + snprintf (status_without_string, sizeof (status_without_string), + "%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND); + num_written = write (HOOK_CONNECT(hook_connect, child_write), + status_without_string, strlen (status_without_string)); + } + (void) num_written; + goto end; + } + + if (!res_remote) + { + /* address not found */ + snprintf (status_without_string, sizeof (status_without_string), + "%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND); + num_written = write (HOOK_CONNECT(hook_connect, child_write), + status_without_string, strlen (status_without_string)); + (void) num_written; + goto end; + } + + /* set local hostname/IP if asked by user */ + if (HOOK_CONNECT(hook_connect, local_hostname) + && HOOK_CONNECT(hook_connect, local_hostname[0])) + { + memset (&hints, 0, sizeof (hints)); + hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - rc = getaddrinfo (CONFIG_STRING(ptr_proxy->options[PROXY_OPTION_ADDRESS]), NULL, &hints, &res); +#ifdef AI_ADDRCONFIG + hints.ai_flags = AI_ADDRCONFIG; +#endif + rc = getaddrinfo (HOOK_CONNECT(hook_connect, local_hostname), + NULL, &hints, &res_local); if (rc != 0) { /* address not found */ @@ -696,7 +791,7 @@ network_connect_child (struct t_hook *hook_connect) if (status_with_string) { snprintf (status_with_string, length, "%c%05d%s", - '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND, + '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR, (int)strlen (error), error); } } @@ -708,254 +803,256 @@ network_connect_child (struct t_hook *hook_connect) else { snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND); + "%c00000", '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR); num_written = write (HOOK_CONNECT(hook_connect, child_write), status_without_string, strlen (status_without_string)); } - if (status_with_string) - free (status_with_string); (void) num_written; - return; - } - if (!res) - { - /* adddress not found */ - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); - (void) num_written; - return; + goto end; } - if ((CONFIG_BOOLEAN(ptr_proxy->options[PROXY_OPTION_IPV6]) && (res->ai_family != AF_INET6)) - || ((!CONFIG_BOOLEAN(ptr_proxy->options[PROXY_OPTION_IPV6]) && (res->ai_family != AF_INET)))) + + if (!res_local) { - /* IP address not found */ + /* address not found */ snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_IP_ADDRESS_NOT_FOUND); + "%c00000", '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR); num_written = write (HOOK_CONNECT(hook_connect, child_write), status_without_string, strlen (status_without_string)); (void) num_written; - freeaddrinfo (res); - return; + goto end; } + } - if (CONFIG_BOOLEAN(ptr_proxy->options[PROXY_OPTION_IPV6])) - ((struct sockaddr_in6 *)(res->ai_addr))->sin6_port = htons (CONFIG_INTEGER(ptr_proxy->options[PROXY_OPTION_PORT])); - else - ((struct sockaddr_in *)(res->ai_addr))->sin_port = htons (CONFIG_INTEGER(ptr_proxy->options[PROXY_OPTION_PORT])); + /* res_local != NULL now indicates that bind() is required */ - /* connect to peer */ - if (!network_connect (HOOK_CONNECT(hook_connect, sock), - res->ai_addr, res->ai_addrlen)) - { - /* connection refused */ - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_CONNECTION_REFUSED); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); - (void) num_written; - freeaddrinfo (res); - return; - } + /* + * count all the groups of hosts by tracking family, e.g. + * 0 = [2001:db8::1, 2001:db8::2, + * 1 = 192.0.2.1, 192.0.2.2, + * 2 = 2002:c000:201::1, 2002:c000:201::2] + */ + last_af = AF_UNSPEC; + num_groups = 0; + num_hosts = 0; + for (ptr_res = res_remote; ptr_res; ptr_res = ptr_res->ai_next) + { + if (ptr_res->ai_family != last_af) + if (last_af != AF_UNSPEC) + num_groups++; - if (!network_pass_proxy (HOOK_CONNECT(hook_connect, proxy), - HOOK_CONNECT(hook_connect, sock), - HOOK_CONNECT(hook_connect, address), - HOOK_CONNECT(hook_connect, port))) - { - /* proxy fails to connect to peer */ - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_PROXY_ERROR); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); - (void) num_written; - freeaddrinfo (res); - return; - } + num_hosts++; + last_af = ptr_res->ai_family; + } + if (last_af != AF_UNSPEC) + num_groups++; - status_str[0] = '0' + WEECHAT_HOOK_CONNECT_OK; + res_reorder = malloc (sizeof (*res_reorder) * num_hosts); + if (!res_reorder) + { + snprintf (status_without_string, sizeof (status_without_string), + "%c00000", '0' + WEECHAT_HOOK_CONNECT_MEMORY_ERROR); + num_written = write (HOOK_CONNECT(hook_connect, child_write), + status_without_string, strlen (status_without_string)); + (void) num_written; + goto end; } - else + + /* reorder groups */ + retry = HOOK_CONNECT(hook_connect, retry); + if (num_groups > 0) { - /* set local hostname/IP if asked by user */ - if (HOOK_CONNECT(hook_connect, local_hostname) - && HOOK_CONNECT(hook_connect, local_hostname[0])) + retry %= num_groups; + i = 0; + + last_af = AF_UNSPEC; + tmp_num_groups = 0; + tmp_host = i; /* start of current group */ + + /* top of list */ + for (ptr_res = res_remote; ptr_res; ptr_res = ptr_res->ai_next) { - memset (&hints, 0, sizeof(hints)); - hints.ai_family = (HOOK_CONNECT(hook_connect, ipv6)) ? AF_INET6 : AF_INET; - hints.ai_socktype = SOCK_STREAM; - rc = getaddrinfo (HOOK_CONNECT(hook_connect, local_hostname), - NULL, &hints, &res_local); - if (rc != 0) + if (ptr_res->ai_family != last_af) { - /* fails to set local hostname/IP */ - status_with_string = NULL; - error = gai_strerror (rc); - if (error) - { - length = 1 + 5 + strlen (error) + 1; - status_with_string = malloc (length); - if (status_with_string) - { - snprintf (status_with_string, length, "%c%05d%s", - '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR, - (int)strlen (error), error); - } - } - if (status_with_string) - { - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_with_string, strlen (status_with_string)); - } + if (last_af != AF_UNSPEC) + tmp_num_groups++; + + tmp_host = i; + } + + if (tmp_num_groups >= retry) + { + /* shuffle while adding */ + rand_num = tmp_host + (rand() % ((i + 1) - tmp_host)); + if (rand_num == i) + res_reorder[i++] = ptr_res; else { - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); + res_reorder[i++] = res_reorder[rand_num]; + res_reorder[rand_num] = ptr_res; } - if (status_with_string) - free (status_with_string); - (void) num_written; - if (res_local) - freeaddrinfo (res_local); - return; } - else if (!res_local - || (HOOK_CONNECT(hook_connect, ipv6) - && (res_local->ai_family != AF_INET6)) - || ((!HOOK_CONNECT(hook_connect, ipv6) - && (res_local->ai_family != AF_INET)))) + + last_af = ptr_res->ai_family; + } + + last_af = AF_UNSPEC; + tmp_num_groups = 0; + tmp_host = i; /* start of current group */ + + /* remainder of list */ + for (ptr_res = res_remote; ptr_res; ptr_res = ptr_res->ai_next) + { + if (ptr_res->ai_family != last_af) { - /* fails to set local hostname/IP */ - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); - (void) num_written; - if (res_local) - freeaddrinfo (res_local); - return; + if (last_af != AF_UNSPEC) + tmp_num_groups++; + + tmp_host = i; } - if (bind (HOOK_CONNECT(hook_connect, sock), - res_local->ai_addr, res_local->ai_addrlen) < 0) + + if (tmp_num_groups < retry) { - /* fails to set local hostname/IP */ - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); - (void) num_written; - if (res_local) - freeaddrinfo (res_local); - return; + /* shuffle while adding */ + rand_num = tmp_host + (rand() % ((i + 1) - tmp_host)); + if (rand_num == i) + res_reorder[i++] = ptr_res; + else + { + res_reorder[i++] = res_reorder[rand_num]; + res_reorder[rand_num] = ptr_res; + } } + else + break; + + last_af = ptr_res->ai_family; } + } + else + { + /* no IP addresses found (all AF_UNSPEC) */ + snprintf (status_without_string, sizeof (status_without_string), + "%c00000", '0' + WEECHAT_HOOK_CONNECT_IP_ADDRESS_NOT_FOUND); + num_written = write (HOOK_CONNECT(hook_connect, child_write), + status_without_string, strlen (status_without_string)); + (void) num_written; + goto end; + } - /* get info about peer */ - memset (&hints, 0, sizeof(hints)); - hints.ai_family = (HOOK_CONNECT(hook_connect, ipv6)) ? AF_INET6 : AF_INET; - hints.ai_socktype = SOCK_STREAM; - rc = getaddrinfo (HOOK_CONNECT(hook_connect, address), - NULL, &hints, &res); - if (rc != 0) + status_str[0] = '0' + WEECHAT_HOOK_CONNECT_IP_ADDRESS_NOT_FOUND; + + /* try all IP addresses found, stop when connection is ok */ + sock = -1; + for (i = 0; i < num_hosts; i++) + { + ptr_res = res_reorder[i]; + +#ifdef HOOK_CONNECT_MAX_SOCKETS + /* use pre-created socket pool */ + sock = -1; + for (j = 0; j < HOOK_CONNECT_MAX_SOCKETS; j++) { - status_with_string = NULL; - error = gai_strerror (rc); - if (error) + if (ptr_res->ai_family == AF_INET) { - length = 1 + 5 + strlen (error) + 1; - status_with_string = malloc (length); - if (status_with_string) + sock = HOOK_CONNECT(hook_connect, sock_v4[j]); + if (sock != -1) { - snprintf (status_with_string, length, "%c%05d%s", - '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND, - (int)strlen (error), error); + HOOK_CONNECT(hook_connect, sock_v4[j]) = -1; + break; } } - if (status_with_string) - { - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_with_string, strlen (status_with_string)); - } - else + else if (ptr_res->ai_family == AF_INET6) { - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); + sock = HOOK_CONNECT(hook_connect, sock_v6[j]); + if (sock != -1) + { + HOOK_CONNECT(hook_connect, sock_v6[j]) = -1; + break; + } } - if (status_with_string) - free (status_with_string); - (void) num_written; - if (res) - freeaddrinfo (res); - if (res_local) - freeaddrinfo (res_local); - return; } - else if (!res) + if (sock < 0) + continue; +#else + /* create a socket */ + sock = socket (ptr_res->ai_family, + ptr_res->ai_socktype, + ptr_res->ai_protocol); +#endif + if (sock < 0) { - /* address not found */ - snprintf (status_without_string, sizeof (status_without_string), - "%c00000", '0' + WEECHAT_HOOK_CONNECT_ADDRESS_NOT_FOUND); - num_written = write (HOOK_CONNECT(hook_connect, child_write), - status_without_string, strlen (status_without_string)); - (void) num_written; - if (res) - freeaddrinfo (res); - if (res_local) - freeaddrinfo (res_local); - return; + status_str[0] = '0' + WEECHAT_HOOK_CONNECT_SOCKET_ERROR; + continue; } - status_str[0] = '0' + WEECHAT_HOOK_CONNECT_IP_ADDRESS_NOT_FOUND; + /* set SO_REUSEADDR option for socket */ + set = 1; + setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, (void *) &set, sizeof (set)); - /* try all IP addresses found, stop when connection is ok */ - for (ptr_res = res; ptr_res; ptr_res = ptr_res->ai_next) + /* set SO_KEEPALIVE option for socket */ + set = 1; + setsockopt (sock, SOL_SOCKET, SO_KEEPALIVE, (void *) &set, sizeof (set)); + + /* set flag O_NONBLOCK on socket */ + flags = fcntl (sock, F_GETFL); + if (flags == -1) + flags = 0; + fcntl (sock, F_SETFL, flags | O_NONBLOCK); + + if (res_local) { - /* skip IP address if it's not good family */ - if ((HOOK_CONNECT(hook_connect, ipv6) && (ptr_res->ai_family != AF_INET6)) - || ((!HOOK_CONNECT(hook_connect, ipv6) && (ptr_res->ai_family != AF_INET)))) - continue; + rc = -1; - /* connect to peer */ - if (HOOK_CONNECT(hook_connect, ipv6)) - ((struct sockaddr_in6 *)(ptr_res->ai_addr))->sin6_port = - htons (HOOK_CONNECT(hook_connect, port)); - else - ((struct sockaddr_in *)(ptr_res->ai_addr))->sin_port = - htons (HOOK_CONNECT(hook_connect, port)); + /* bind local hostname/IP if asked by user */ + for (ptr_loc = res_local; ptr_loc; ptr_loc = ptr_loc->ai_next) + { + if (ptr_loc->ai_family != ptr_res->ai_family) + continue; + + rc = bind (sock, ptr_loc->ai_addr, ptr_loc->ai_addrlen); + if (rc < 0) + continue; + } - if (network_connect (HOOK_CONNECT(hook_connect, sock), - ptr_res->ai_addr, ptr_res->ai_addrlen)) + if (rc < 0) { - status_str[0] = '0' + WEECHAT_HOOK_CONNECT_OK; - if (HOOK_CONNECT(hook_connect, ipv6)) - { - if (inet_ntop (AF_INET6, - &((struct sockaddr_in6 *)(ptr_res->ai_addr))->sin6_addr, - ipv6_address, - INET6_ADDRSTRLEN)) - { - ptr_address = ipv6_address; - } - } - else - { - if (inet_ntop (AF_INET, - &((struct sockaddr_in *)(ptr_res->ai_addr))->sin_addr, - ipv4_address, - INET_ADDRSTRLEN)) - { - ptr_address = ipv4_address; - } - } - break; + status_str[0] = '0' + WEECHAT_HOOK_CONNECT_LOCAL_HOSTNAME_ERROR; + close (sock); + sock = -1; + continue; } - else - status_str[0] = '0' + WEECHAT_HOOK_CONNECT_CONNECTION_REFUSED; + } + + /* connect to peer */ + if (network_connect (sock, ptr_res->ai_addr, ptr_res->ai_addrlen)) + { + status_str[0] = '0' + WEECHAT_HOOK_CONNECT_OK; + rc = getnameinfo (ptr_res->ai_addr, ptr_res->ai_addrlen, + remote_address, sizeof (remote_address), + NULL, 0, NI_NUMERICHOST); + if (rc == 0) + ptr_address = remote_address; + break; + } + else + { + status_str[0] = '0' + WEECHAT_HOOK_CONNECT_CONNECTION_REFUSED; + close (sock); + sock = -1; + } + } + + HOOK_CONNECT(hook_connect, sock) = sock; + + if (ptr_proxy && status_str[0] == '0' + WEECHAT_HOOK_CONNECT_OK) + { + if (!network_pass_proxy (HOOK_CONNECT(hook_connect, proxy), + HOOK_CONNECT(hook_connect, sock), + HOOK_CONNECT(hook_connect, address), + HOOK_CONNECT(hook_connect, port))) + { + /* proxy fails to connect to peer */ + status_str[0] = '0' + WEECHAT_HOOK_CONNECT_PROXY_ERROR; } } @@ -978,7 +1075,6 @@ network_connect_child (struct t_hook *hook_connect) num_written = write (HOOK_CONNECT(hook_connect, child_write), status_with_string, strlen (status_with_string)); (void) num_written; - free (status_with_string); } else { @@ -988,6 +1084,24 @@ network_connect_child (struct t_hook *hook_connect) status_without_string, strlen (status_without_string)); (void) num_written; } + + /* send the socket to the parent process */ +#ifndef HOOK_CONNECT_MAX_SOCKETS + memset (&msg, 0, sizeof (msg)); + msg.msg_control = msg_buf; + msg.msg_controllen = sizeof (msg_buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof (sock)); + memcpy(CMSG_DATA(cmsg), &sock, sizeof (sock)); + msg.msg_controllen = cmsg->cmsg_len; + num_written = sendmsg (HOOK_CONNECT(hook_connect, child_send), &msg, 0); + (void) num_written; +#else + num_written = write (HOOK_CONNECT(hook_connect, child_write), &sock, sizeof (sock)); + (void) num_written; +#endif } else { @@ -998,10 +1112,15 @@ network_connect_child (struct t_hook *hook_connect) (void) num_written; } - if (res) - freeaddrinfo (res); +end: + if (status_with_string) + free (status_with_string); + if (res_reorder) + free (res_reorder); if (res_local) freeaddrinfo (res_local); + if (res_remote) + freeaddrinfo (res_remote); } /* @@ -1023,7 +1142,7 @@ network_connect_child_timer_cb (void *arg_hook_connect, int remaining_calls) (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_TIMEOUT, - 0, NULL, NULL); + 0, -1, NULL, NULL); unhook (hook_connect); return WEECHAT_RC_OK; @@ -1066,8 +1185,8 @@ network_connect_gnutls_handshake_fd_cb (void *arg_hook_connect, int fd) { (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, - WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, - rc, + WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, rc, + HOOK_CONNECT(hook_connect, sock), gnutls_strerror (rc), HOOK_CONNECT(hook_connect, handshake_ip_address)); unhook (hook_connect); @@ -1086,8 +1205,8 @@ network_connect_gnutls_handshake_fd_cb (void *arg_hook_connect, int fd) { (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, - WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, - rc, + WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, rc, + HOOK_CONNECT(hook_connect, sock), "Error in the certificate.", HOOK_CONNECT(hook_connect, handshake_ip_address)); unhook (hook_connect); @@ -1096,8 +1215,10 @@ network_connect_gnutls_handshake_fd_cb (void *arg_hook_connect, int fd) #endif unhook (HOOK_CONNECT(hook_connect, handshake_hook_fd)); (void) (HOOK_CONNECT(hook_connect, callback)) - (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_OK, 0, NULL, - HOOK_CONNECT(hook_connect, handshake_ip_address)); + (hook_connect->callback_data, + WEECHAT_HOOK_CONNECT_OK, 0, + HOOK_CONNECT(hook_connect, sock), + NULL, HOOK_CONNECT(hook_connect, handshake_ip_address)); unhook (hook_connect); } @@ -1127,6 +1248,7 @@ network_connect_gnutls_handshake_timer_cb (void *arg_hook_connect, (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, GNUTLS_E_EXPIRED, + HOOK_CONNECT(hook_connect, sock), gnutls_strerror (GNUTLS_E_EXPIRED), HOOK_CONNECT(hook_connect, handshake_ip_address)); unhook (hook_connect); @@ -1149,6 +1271,14 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd) #ifdef HAVE_GNUTLS int rc, direction; #endif + int sock; +#ifdef HOOK_CONNECT_MAX_SOCKETS + int i; +#else + struct msghdr msg; + struct cmsghdr *cmsg; + char msg_buf[CMSG_SPACE(sizeof (sock))]; +#endif /* make C compiler happy */ (void) fd; @@ -1157,10 +1287,11 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd) cb_error = NULL; cb_ip_address = NULL; + sock = -1; num_read = read (HOOK_CONNECT(hook_connect, child_read), buffer, sizeof (buffer)); - if (num_read > 0) + if (num_read == sizeof (buffer)) { if (buffer[0] - '0' == WEECHAT_HOOK_CONNECT_OK) { @@ -1189,6 +1320,41 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd) } } } + +#ifndef HOOK_CONNECT_MAX_SOCKETS + /* receive the socket from the child process */ + memset (&msg, 0, sizeof (msg)); + msg.msg_control = msg_buf; + msg.msg_controllen = sizeof (msg_buf); + + if (recvmsg (HOOK_CONNECT(hook_connect, child_recv), &msg, 0) >= 0) + { + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg != NULL + && cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS + && cmsg->cmsg_len >= sizeof (sock)) + { + memcpy(&sock, CMSG_DATA(cmsg), sizeof (sock)); + } + } +#else + num_read = read (HOOK_CONNECT(hook_connect, child_read), &sock, sizeof (sock)); + (void) num_read; + + /* prevent unhook process from closing the socket */ + for (i = 0; i < HOOK_CONNECT_MAX_SOCKETS; i++) + { + if (HOOK_CONNECT(hook_connect, sock_v4[i]) == sock) + HOOK_CONNECT(hook_connect, sock_v4[i]) = -1; + + if (HOOK_CONNECT(hook_connect, sock_v6[i]) == sock) + HOOK_CONNECT(hook_connect, sock_v6[i]) = -1; + } +#endif + + HOOK_CONNECT(hook_connect, sock) = sock; + #ifdef HAVE_GNUTLS if (HOOK_CONNECT(hook_connect, gnutls_sess)) { @@ -1240,7 +1406,7 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd) (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, - rc, + rc, sock, gnutls_strerror (rc), cb_ip_address); unhook (hook_connect); @@ -1261,7 +1427,7 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd) (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_GNUTLS_HANDSHAKE_ERROR, - rc, + rc, sock, "Error in the certificate.", cb_ip_address); unhook (hook_connect); @@ -1303,7 +1469,14 @@ network_connect_child_read_cb (void *arg_hook_connect, int fd) } (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, buffer[0] - '0', 0, - cb_error, cb_ip_address); + sock, cb_error, cb_ip_address); + unhook (hook_connect); + } + else + { + (void) (HOOK_CONNECT(hook_connect, callback)) + (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_MEMORY_ERROR, + 0, sock, cb_error, cb_ip_address); unhook (hook_connect); } @@ -1323,6 +1496,11 @@ void network_connect_with_fork (struct t_hook *hook_connect) { int child_pipe[2]; +#ifdef HOOK_CONNECT_MAX_SOCKETS + int i; +#else + int child_socket[2]; +#endif #ifdef HAVE_GNUTLS int rc; const char *pos_error; @@ -1338,7 +1516,7 @@ network_connect_with_fork (struct t_hook *hook_connect) (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_GNUTLS_INIT_ERROR, - 0, NULL, NULL); + 0, -1, NULL, NULL); unhook (hook_connect); return; } @@ -1350,7 +1528,7 @@ network_connect_with_fork (struct t_hook *hook_connect) (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_GNUTLS_INIT_ERROR, - 0, _("invalid priorities"), NULL); + 0, -1, _("invalid priorities"), NULL); unhook (hook_connect); return; } @@ -1368,13 +1546,34 @@ network_connect_with_fork (struct t_hook *hook_connect) (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_MEMORY_ERROR, - 0, NULL, NULL); + 0, -1, NULL, NULL); unhook (hook_connect); return; } HOOK_CONNECT(hook_connect, child_read) = child_pipe[0]; HOOK_CONNECT(hook_connect, child_write) = child_pipe[1]; +#ifndef HOOK_CONNECT_MAX_SOCKETS + /* create socket for child process */ + if (socketpair (AF_LOCAL, SOCK_DGRAM, 0, child_socket) < 0) + { + (void) (HOOK_CONNECT(hook_connect, callback)) + (hook_connect->callback_data, + WEECHAT_HOOK_CONNECT_MEMORY_ERROR, + 0, -1, NULL, NULL); + unhook (hook_connect); + return; + } + HOOK_CONNECT(hook_connect, child_recv) = child_socket[0]; + HOOK_CONNECT(hook_connect, child_send) = child_socket[1]; +#else + for (i = 0; i < HOOK_CONNECT_MAX_SOCKETS; i++) + { + HOOK_CONNECT(hook_connect, sock_v4[i]) = socket (AF_INET, SOCK_STREAM, 0); + HOOK_CONNECT(hook_connect, sock_v6[i]) = socket (AF_INET6, SOCK_STREAM, 0); + } +#endif + switch (pid = fork ()) { /* fork failed */ @@ -1382,13 +1581,16 @@ network_connect_with_fork (struct t_hook *hook_connect) (void) (HOOK_CONNECT(hook_connect, callback)) (hook_connect->callback_data, WEECHAT_HOOK_CONNECT_MEMORY_ERROR, - 0, NULL, NULL); + 0, -1, NULL, NULL); unhook (hook_connect); return; /* child process */ case 0: setuid (getuid ()); close (HOOK_CONNECT(hook_connect, child_read)); +#ifndef HOOK_CONNECT_MAX_SOCKETS + close (HOOK_CONNECT(hook_connect, child_recv)); +#endif network_connect_child (hook_connect); _exit (EXIT_SUCCESS); } @@ -1396,6 +1598,10 @@ network_connect_with_fork (struct t_hook *hook_connect) HOOK_CONNECT(hook_connect, child_pid) = pid; close (HOOK_CONNECT(hook_connect, child_write)); HOOK_CONNECT(hook_connect, child_write) = -1; +#ifndef HOOK_CONNECT_MAX_SOCKETS + close (HOOK_CONNECT(hook_connect, child_send)); + HOOK_CONNECT(hook_connect, child_send) = -1; +#endif HOOK_CONNECT(hook_connect, hook_child_timer) = hook_timer (hook_connect->plugin, CONFIG_INTEGER(config_network_connection_timeout) * 1000, 0, 1, |