From be7c4f5806a29298988601f9bac5a4ebab6c000c Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mon, 1 Feb 2010 21:23:59 +0100 Subject: Allow the use of IPv6 nameservers This patch allows the use of IPv6 addresses for nameserves in both /etc/resolv.conf and by using the ares_set_nameservers() API. --- Makefile.inc | 3 + ares.h | 14 ++++ ares_destroy.c | 10 +-- ares_init.c | 224 ++++++++++++++++++++++++++++++++++++++++--------- ares_private.h | 15 +--- ares_process.c | 95 ++++++++++++++++----- ares_set_nameservers.3 | 68 +++++++++++++++ 7 files changed, 346 insertions(+), 83 deletions(-) create mode 100644 ares_set_nameservers.3 diff --git a/Makefile.inc b/Makefile.inc index 3227858..8e575fd 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -92,6 +92,7 @@ MANPAGES = ares_cancel.3 \ ares_search.3 \ ares_send.3 \ ares_set_socket_callback.3 \ + ares_set_nameservers.3 \ ares_strerror.3 \ ares_timeout.3 \ ares_version.3 @@ -128,6 +129,7 @@ HTMLPAGES = ares_cancel.html \ ares_search.html \ ares_send.html \ ares_set_socket_callback.html \ + ares_set_nameservers.html \ ares_strerror.html \ ares_timeout.html \ ares_version.html @@ -164,6 +166,7 @@ PDFPAGES = ares_cancel.pdf \ ares_search.pdf \ ares_send.pdf \ ares_set_socket_callback.pdf \ + ares_set_nameservers.pdf \ ares_strerror.pdf \ ares_timeout.pdf \ ares_version.pdf diff --git a/ares.h b/ares.h index b1c2c22..a3fa6a8 100644 --- a/ares.h +++ b/ares.h @@ -268,6 +268,16 @@ struct ares_channeldata; typedef struct ares_channeldata *ares_channel; +struct ares_addr { + int family; + union { + struct in_addr addr4; + struct in6_addr addr6; + } addr; +}; +#define addrV4 addr.addr4 +#define addrV6 addr.addr6 + typedef void (*ares_callback)(void *arg, int status, int timeouts, @@ -318,6 +328,10 @@ CARES_EXTERN void ares_set_socket_callback(ares_channel channel, ares_sock_create_callback callback, void *user_data); +CARES_EXTERN int ares_set_nameservers(ares_channel channel, + struct ares_addr *servers, + int num_servers); + CARES_EXTERN void ares_send(ares_channel channel, const unsigned char *qbuf, int qlen, diff --git a/ares_destroy.c b/ares_destroy.c index 0044a71..4040971 100644 --- a/ares_destroy.c +++ b/ares_destroy.c @@ -67,15 +67,7 @@ void ares_destroy(ares_channel channel) } #endif - if (channel->servers) { - for (i = 0; i < channel->nservers; i++) - { - struct server_state *server = &channel->servers[i]; - ares__close_sockets(channel, server); - assert(ares__is_list_empty(&(server->queries_to_server))); - } - free(channel->servers); - } + ares__free_servers(channel); if (channel->domains) { for (i = 0; i < channel->ndomains; i++) diff --git a/ares_init.c b/ares_init.c index cb541af..051887d 100644 --- a/ares_init.c +++ b/ares_init.c @@ -65,6 +65,7 @@ #include #include #include +#include #include "ares.h" #include "inet_net_pton.h" #include "ares_library_init.h" @@ -88,6 +89,7 @@ static int set_search(ares_channel channel, const char *str); static int set_options(ares_channel channel, const char *str); static const char *try_option(const char *p, const char *q, const char *opt); static int init_id_key(rc4_key* key,int key_data_len); +static void init_servers(ares_channel channel); #if !defined(WIN32) && !defined(WATT32) static int sortlist_alloc(struct apattern **sortlist, int *nsort, struct apattern *pat); @@ -118,7 +120,6 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options, ares_channel channel; int i; int status = ARES_SUCCESS; - struct server_state *server; struct timeval now; #ifdef CURLDEBUG @@ -248,20 +249,7 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options, channel->nservers = 1; /* Initialize server states. */ - for (i = 0; i < channel->nservers; i++) - { - server = &channel->servers[i]; - server->udp_socket = ARES_SOCKET_BAD; - server->tcp_socket = ARES_SOCKET_BAD; - server->tcp_connection_generation = ++channel->tcp_connection_generation; - server->tcp_lenbuf_pos = 0; - server->tcp_buffer = NULL; - server->qhead = NULL; - server->qtail = NULL; - ares__init_list_head(&(server->queries_to_server)); - server->channel = channel; - server->is_broken = 0; - } + init_servers(channel); *channelptr = channel; return ARES_SUCCESS; @@ -296,6 +284,29 @@ int ares_dup(ares_channel *dest, ares_channel src) (*dest)->sock_create_cb = src->sock_create_cb; (*dest)->sock_create_cb_data = src->sock_create_cb_data; + /* IPv4 nameservers were copied in ares_save_options, we + * can only copy v6 servers */ + if (src->nservers > (*dest)->nservers) + { + int i; + (*dest)->servers = + realloc((*dest)->servers, + src->nservers * sizeof(struct server_state)); + if (!(*dest)->servers) + return ARES_ENOMEM; + + for (i = 0; i < src->nservers; i++) + { + if (src->servers[i].addr.family == AF_INET6) + { + memcpy(&((*dest)->servers[(*dest)->nservers].addr), + &(src->servers[i].addr), + sizeof(struct ares_addr)); + (*dest)->nservers++; + } + } + init_servers(*dest); + } return ARES_SUCCESS; /* everything went fine */ @@ -334,17 +345,33 @@ int ares_save_options(ares_channel channel, struct ares_options *options, options->tcp_port = (unsigned short)channel->tcp_port; options->sock_state_cb = channel->sock_state_cb; options->sock_state_cb_data = channel->sock_state_cb_data; + options->nservers = 0; /* Copy servers */ if (channel->nservers) { + int numv4 = 0; options->servers = malloc(channel->nservers * sizeof(struct server_state)); - if (!options->servers && channel->nservers != 0) + if (!options->servers) return ARES_ENOMEM; for (i = 0; i < channel->nservers; i++) - options->servers[i] = channel->servers[i].addr; + { + if (channel->servers[i].addr.family == AF_INET) + { + /* Because struct ares_options should stay the same + * only IPv4 nameservers are saved in this patch. + * ares_dup() works for both v4 and v6, though + */ + options->servers[numv4++] = + channel->servers[i].addr.addrV4; + } + } + options->servers = + realloc(options->servers, numv4 * sizeof(struct server_state)); + if (numv4 && !options->servers) + return ARES_ENOMEM; + options->nservers = numv4; } - options->nservers = channel->nservers; /* copy domains */ if (channel->ndomains) { @@ -431,7 +458,15 @@ static int init_by_options(ares_channel channel, if (!channel->servers) return ARES_ENOMEM; for (i = 0; i < options->nservers; i++) - channel->servers[i].addr = options->servers[i]; + { + /* This is a rather crude way to allow usage of + * protocol-independent struct addrinfo while not + * breaking the public struct ares_options which is forbidden + * in favor of providing ares_set_XXX() functions + */ + channel->servers[i].addr.family = AF_INET; + channel->servers[i].addr.addrV4 = options->servers[i]; + } } channel->nservers = options->nservers; } @@ -1001,7 +1036,9 @@ static int init_by_defaults(ares_channel channel) rc = ARES_ENOMEM; goto error; } - channel->servers[0].addr.s_addr = htonl(INADDR_LOOPBACK); + + channel->servers[0].addr.family = AF_INET; + channel->servers[0].addr.addrV4.s_addr = htonl(INADDR_LOOPBACK); channel->nservers = 1; } @@ -1146,11 +1183,64 @@ static int config_lookup(ares_channel channel, const char *str, #endif /* !WIN32 & !WATT32 */ #ifndef WATT32 +static int set_nameserver(struct server_state **servers, int *nservers, + char *str) +{ + struct server_state *newserv; + struct addrinfo *res = NULL; + struct addrinfo hints; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_flags = AI_NUMERICHOST; /* suppresses a lookup */ + hints.ai_family = AF_UNSPEC; + + newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state)); + if (!newserv) + { + freeaddrinfo(res); + return ARES_ENOMEM; + } + +#ifdef HAVE_GETADDRINFO + if (getaddrinfo(str, NULL, &hints, &res) != 0) + return ARES_SUCCESS; + + newserv[*nservers].addr.family = res->ai_family; + if (res->ai_family == AF_INET) + { + newserv[*nservers].addr.addrV4.s_addr = + ((struct sockaddr_in *) res->ai_addr)->sin_addr.s_addr; + } + else + { + memcpy(newserv[*nservers].addr.addrV6.s6_addr, + ((struct sockaddr_in6 *) res->ai_addr)->sin6_addr.s6_addr, + 16); + } + + freeaddrinfo(res); +#else + struct in_addr addr; + + /* Ignore addresses we cannot parse, like V6 */ + addr.s_addr = inet_addr(str); + if (addr.s_addr == INADDR_NONE) + return ARES_SUCCESS; + + newserv[*nservers].addr.family = AF_INET; + newserv[*nservers].addr.addrV4 = addr; +#endif + + *servers = newserv; + (*nservers)++; + return ARES_SUCCESS; +} + static int config_nameserver(struct server_state **servers, int *nservers, char *str) { - struct in_addr addr; - struct server_state *newserv; + int ret; + /* On Windows, there may be more than one nameserver specified in the same * registry key, so we parse it as a space or comma seperated list. */ @@ -1178,15 +1268,9 @@ static int config_nameserver(struct server_state **servers, int *nservers, } /* This is the part that actually sets the nameserver */ - addr.s_addr = inet_addr(begin); - if (addr.s_addr == INADDR_NONE) - continue; - newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state)); - if (!newserv) - return ARES_ENOMEM; - newserv[*nservers].addr = addr; - *servers = newserv; - (*nservers)++; + ret = set_nameserver(servers, nservers, begin); + if (ret) + return ret; if (!more) break; @@ -1194,15 +1278,9 @@ static int config_nameserver(struct server_state **servers, int *nservers, } #else /* Add a nameserver entry, if this is a valid address. */ - addr.s_addr = inet_addr(str); - if (addr.s_addr == INADDR_NONE) - return ARES_SUCCESS; - newserv = realloc(*servers, (*nservers + 1) * sizeof(struct server_state)); - if (!newserv) - return ARES_ENOMEM; - newserv[*nservers].addr = addr; - *servers = newserv; - (*nservers)++; + ret = set_nameserver(servers, nservers, str); + if (ret) + return ret; #endif return ARES_SUCCESS; } @@ -1580,3 +1658,69 @@ void ares_set_socket_callback(ares_channel channel, channel->sock_create_cb = cb; channel->sock_create_cb_data = data; } + +static void init_servers(ares_channel channel) +{ + struct server_state *server; + int i; + + for (i = 0; i < channel->nservers; i++) + { + server = &channel->servers[i]; + server->udp_socket = ARES_SOCKET_BAD; + server->tcp_socket = ARES_SOCKET_BAD; + server->tcp_connection_generation = ++channel->tcp_connection_generation; + server->tcp_lenbuf_pos = 0; + server->tcp_buffer = NULL; + server->qhead = NULL; + server->qtail = NULL; + ares__init_list_head(&(server->queries_to_server)); + server->channel = channel; + server->is_broken = 0; + } +} + +void ares__free_servers(ares_channel channel) +{ + int i; + + if (channel->servers) { + for (i = 0; i < channel->nservers; i++) + { + struct server_state *server = &channel->servers[i]; + ares__close_sockets(channel, server); + assert(ares__is_list_empty(&(server->queries_to_server))); + } + free(channel->servers); + } + + channel->servers = NULL; + channel->nservers = -1; +} + +int ares_set_nameservers(ares_channel channel, + struct ares_addr *servers, + int num_servers) +{ + int i; + + ares__free_servers(channel); + + channel->nservers = num_servers; + channel->servers = + malloc(channel->nservers * sizeof(struct server_state)); + if (!channel->servers) + return ARES_ENOMEM; + + for (i = 0; i < num_servers; i++) + { + struct server_state *ss = channel->servers + i; + memset(ss, 0, sizeof(struct server_state)); + + ss->channel = channel; + memcpy(&ss->addr, &servers[i], sizeof(struct ares_addr)); + } + + init_servers(channel); + return ARES_SUCCESS; +} diff --git a/ares_private.h b/ares_private.h index 4726d7a..083de5a 100644 --- a/ares_private.h +++ b/ares_private.h @@ -110,16 +110,6 @@ # define writev(s,ptr,cnt) ares_writev(s,ptr,cnt) #endif -struct ares_addr { - int family; - union { - struct in_addr addr4; - struct in6_addr addr6; - } addr; -}; -#define addrV4 addr.addr4 -#define addrV6 addr.addr6 - struct query; struct send_request { @@ -137,7 +127,8 @@ struct send_request { }; struct server_state { - struct in_addr addr; + struct ares_addr addr; + ares_socket_t udp_socket; ares_socket_t tcp_socket; @@ -319,6 +310,8 @@ struct timeval ares__tvnow(void); int ares__expand_name_for_response(const unsigned char *encoded, const unsigned char *abuf, int alen, char **s, long *enclen); +void ares__free_servers(ares_channel channel); + #if 0 /* Not used */ long ares__tvdiff(struct timeval t1, struct timeval t2); #endif diff --git a/ares_process.c b/ares_process.c index 71f9394..65b95f4 100644 --- a/ares_process.c +++ b/ares_process.c @@ -434,7 +434,7 @@ static void read_udp_packets(ares_channel channel, fd_set *read_fds, ssize_t count; unsigned char buf[PACKETSZ + 1]; #ifdef HAVE_RECVFROM - struct sockaddr_in from; + struct sockaddr_storage from; ares_socklen_t fromlen; #endif @@ -480,16 +480,29 @@ static void read_udp_packets(ares_channel channel, fd_set *read_fds, if (count == -1 && try_again(SOCKERRNO)) continue; else if (count <= 0) - handle_error(channel, i, now); + { + handle_error(channel, i, now); + break; + } #ifdef HAVE_RECVFROM - else if (from.sin_addr.s_addr != server->addr.s_addr) - /* Address response came from did not match the address - * we sent the request to. Someone may be attempting - * to perform a cache poisoning attack */ - break; + /* Check if address response came from did match the address + * we sent the request to. Someone may be attempting + * to perform a cache poisoning attack */ + else if (from.ss_family == AF_INET) + { + if (((struct sockaddr_in *) &from)->sin_addr.s_addr != + server->addr.addrV4.s_addr) + break; + } + else if (from.ss_family == AF_INET6) + { + if (memcmp(((struct sockaddr_in6 *) &from)->sin6_addr.s6_addr, + server->addr.addrV6.s6_addr, + 16)) + break; + } #endif - else - process_answer(channel, buf, (int)count, i, 0, now); + process_answer(channel, buf, (int)count, i, 0, now); } while (count > 0); } } @@ -889,14 +902,44 @@ static int configure_socket(ares_socket_t s, ares_channel channel) return 0; } +static int prepare_addrinfo(struct server_state *server, int port, + struct sockaddr_storage *addr) +{ + const unsigned short sh_port = (unsigned short)(port & 0xffff); + int rc = ARES_SUCCESS; + + switch (server->addr.family) + { + case AF_INET: + ((struct sockaddr_in *) addr)->sin_family = AF_INET; + ((struct sockaddr_in *) addr)->sin_port = sh_port; + ((struct sockaddr_in *) addr)->sin_addr.s_addr = server->addr.addrV4.s_addr; + break; + + case AF_INET6: + ((struct sockaddr_in6 *) addr)->sin6_family = AF_INET6; + ((struct sockaddr_in6 *) addr)->sin6_port = sh_port; + memcpy(((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr, + server->addr.addrV6.s6_addr, + 16); + break; + + default: + rc = ARES_EBADFAMILY; + break; + } + + return rc; +} + static int open_tcp_socket(ares_channel channel, struct server_state *server) { ares_socket_t s; int opt; - struct sockaddr_in sockin; + struct sockaddr_storage addr; /* Acquire a socket. */ - s = socket(AF_INET, SOCK_STREAM, 0); + s = socket(server->addr.family, SOCK_STREAM, 0); if (s == ARES_SOCKET_BAD) return -1; @@ -923,12 +966,15 @@ static int open_tcp_socket(ares_channel channel, struct server_state *server) } #endif + if (prepare_addrinfo(server, channel->tcp_port, &addr) != ARES_SUCCESS) + { + sclose(s); + return -1; + } + /* Connect to the server. */ - memset(&sockin, 0, sizeof(sockin)); - sockin.sin_family = AF_INET; - sockin.sin_addr = server->addr; - sockin.sin_port = (unsigned short)(channel->tcp_port & 0xffff); - if (connect(s, (struct sockaddr *) &sockin, sizeof(sockin)) == -1) + if (connect(s, (struct sockaddr *) &addr, + sizeof(struct sockaddr_storage)) == -1) { int err = SOCKERRNO; @@ -960,10 +1006,10 @@ static int open_tcp_socket(ares_channel channel, struct server_state *server) static int open_udp_socket(ares_channel channel, struct server_state *server) { ares_socket_t s; - struct sockaddr_in sockin; + struct sockaddr_storage addr; /* Acquire a socket. */ - s = socket(AF_INET, SOCK_DGRAM, 0); + s = socket(server->addr.family, SOCK_DGRAM, 0); if (s == ARES_SOCKET_BAD) return -1; @@ -974,12 +1020,15 @@ static int open_udp_socket(ares_channel channel, struct server_state *server) return -1; } + if (prepare_addrinfo(server, channel->udp_port, &addr) != ARES_SUCCESS) + { + sclose(s); + return -1; + } + /* Connect to the server. */ - memset(&sockin, 0, sizeof(sockin)); - sockin.sin_family = AF_INET; - sockin.sin_addr = server->addr; - sockin.sin_port = (unsigned short)(channel->udp_port & 0xffff); - if (connect(s, (struct sockaddr *) &sockin, sizeof(sockin)) == -1) + if (connect(s, (struct sockaddr *) &addr, + sizeof(struct sockaddr_storage)) == -1) { int err = SOCKERRNO; diff --git a/ares_set_nameservers.3 b/ares_set_nameservers.3 new file mode 100644 index 0000000..9795c98 --- /dev/null +++ b/ares_set_nameservers.3 @@ -0,0 +1,68 @@ +.TH ARES_SET_NAMESERVERS 3 "12 Feb 2010" +.SH NAME +ares_set_nameservers - Set nameservers +.SH SYNOPSIS +.nf +.B #include +.PP +.B int ares_set_nameservers(ares_channel \fIchannel\fP, + struct ares_addr *\fIservers\fP, + int \fInum_servers\fP) +.PP +.B cc file.c -lcares +.fi +.SH DESCRIPTION +.PP +This function sets nameservers for the given ares channel handle. +The array +.I servers +contains the addresses of nameservers, the length of the array +is stored in the +.I num_servers +parameter. +Contrary to initializing nameservers with +.B ares_init_options +this function can be used to set IPv6 nameservers. + +The structure +.I ares_addr +contains the following fields: +.sp +.in +4n +.nf +struct ares_addr { + int family; + union { + struct in_addr addr4; + struct in6_addr addr6; + } addr; +}; +.fi +.in + +Two shortcuts for accessing members of the union +.I addr +are defined: +.sp +.in +4n +.nf +#define addrV4 addr.addr4 +#define addrV6 addr.addr6 +.fi +.in +.PP +.SH RETURN VALUES +.B ares_set_nameservers +can return any of the following values: +.TP 15 +.B ARES_SUCCESS +The response was successfully parsed. +.TP 15 +.B ARES_ENOMEM +Memory was exhausted. +.SH SEE ALSO +.BR ares_init_options (3) +.SH AUTHOR +Written by Jakub Hrozek , +on behalf of Red Hat, Inc http://www.redhat.com + -- cgit