This allows users to specify which interface outbound
requests will originate from on multi-homed machines.
Also support setting list of servers as a comma-separated
string.
Signed-off-by: Ben Greear <greearb_at_candelatech.com>
--- :100644 100644 368c73a... 3b4ff13... M ares.h :100644 100644 1f561aa... d5b768c... M ares_init.c :100644 100644 d00368a... b43fdff... M ares_options.c :100644 100644 0df5cb7... 6f86a3a... M ares_private.h :100644 100644 3ef1ddb... e416132... M ares_process.c ares.h | 19 +++++++++ ares_init.c | 24 +++++++++++ ares_options.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ares_private.h | 7 +++ ares_process.c | 38 +++++++++++++++-- 5 files changed, 208 insertions(+), 4 deletions(-) diff --git a/ares.h b/ares.h index 368c73a..3b4ff13 100644 --- a/ares.h +++ b/ares.h @@ -313,6 +313,20 @@ CARES_EXTERN void ares_destroy(ares_channel channel); CARES_EXTERN void ares_cancel(ares_channel channel); +/* These next 3 configure local binding for the out-going socket + * connection. Use these to specify source IP and/or network device + * on multi-homed systems. + */ +CARES_EXTERN void ares_set_local_ip4(ares_channel channel, uint32_t local_ip); + +/* local_ip6 should be 16 bytes in length */ +CARES_EXTERN void ares_set_local_ip6(ares_channel channel, + const unsigned char* local_ip6); + +/* local_dev_name should be null terminated. */ +CARES_EXTERN void ares_set_local_dev(ares_channel channel, + const char* local_dev_name); + CARES_EXTERN void ares_set_socket_callback(ares_channel channel, ares_sock_create_callback callback, void *user_data); @@ -496,6 +510,7 @@ CARES_EXTERN void ares_free_data(void *dataptr); CARES_EXTERN const char *ares_strerror(int code); +/* TODO: Hold port here as well. */ struct ares_addr_node { struct ares_addr_node *next; int family; @@ -508,6 +523,10 @@ struct ares_addr_node { CARES_EXTERN int ares_set_servers(ares_channel channel, struct ares_addr_node *servers); +/* Incomming string format: host[:port][,host[:port]]... */ +CARES_EXTERN int ares_set_servers_csv(ares_channel channel, + const char* servers); + CARES_EXTERN int ares_get_servers(ares_channel channel, struct ares_addr_node **servers); diff --git a/ares_init.c b/ares_init.c index 1f561aa..d5b768c 100644 --- a/ares_init.c +++ b/ares_init.c @@ -168,6 +168,10 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options, channel->last_server = 0; channel->last_timeout_processed = (time_t)now.tv_sec; + memset(&channel->local_dev_name, 0, sizeof(channel->local_dev_name)); + channel->local_ip4 = 0; + memset(&channel->local_ip6, 0, sizeof(channel->local_ip6)); + /* Initialize our lists of queries */ ares__init_list_head(&(channel->all_queries)); for (i = 0; i < ARES_QID_TABLE_SIZE; i++) @@ -1594,6 +1598,26 @@ unsigned short ares__generate_new_id(rc4_key* key) return r; } +void ares_set_local_ip4(ares_channel channel, uint32_t local_ip) +{ + channel->local_ip4 = local_ip; +} + +/* local_ip6 should be 16 bytes in length */ +void ares_set_local_ip6(ares_channel channel, + const unsigned char* local_ip6) { + memcpy(&channel->local_ip6, local_ip6, sizeof(channel->local_ip6)); +} + +/* local_dev_name should be null terminated. */ +void ares_set_local_dev(ares_channel channel, + const char* local_dev_name) { + strncpy(channel->local_dev_name, local_dev_name, + sizeof(channel->local_dev_name)); + channel->local_dev_name[sizeof(channel->local_dev_name) - 1] = 0; +} + + void ares_set_socket_callback(ares_channel channel, ares_sock_create_callback cb, void *data) diff --git a/ares_options.c b/ares_options.c index d00368a..b43fdff 100644 --- a/ares_options.c +++ b/ares_options.c @@ -125,3 +125,127 @@ int ares_set_servers(ares_channel channel, return ARES_SUCCESS; } + +/* Incomming string format: host[:port][,host[:port]]... */ +int ares_set_servers_csv(ares_channel channel, + const char* _csv) +{ + struct ares_addr_node *srvr; + int num_srvrs = 0; + int i; + char* csv = NULL; + char* ptr; + char* start_host; + int port; + bool found_port; + int rv = ARES_SUCCESS; + struct ares_addr_node *servers = NULL; + struct ares_addr_node *last = NULL; + + if (ares_library_initialized() != ARES_SUCCESS) + return ARES_ENOTINITIALIZED; + + if (!channel) + return ARES_ENODATA; + + ares__destroy_servers_state(channel); + + i = strlen(_csv); + if (i == 0) + return ARES_SUCCESS; /* blank all servers */ + + csv = malloc(i + 2); + strcpy(csv, _csv); + if (csv[i-1] != ',') { /* make parsing easier by ensuring ending ',' */ + csv[i] = ','; + csv[i+1] = 0; + } + + ptr = csv; + start_host = csv; + found_port = false; + for (ptr; *ptr; ptr++) { + if (*ptr == ',') { + char* pp = ptr - 1; + struct in_addr in4; + struct in6_addr in6; + struct ares_addr_node *s = NULL; + + *ptr = 0; /* null terminate host:port string */ + /* Got an entry..see if port was specified. */ + while (pp > start_host) { + if (*pp == ':') + break; /* yes */ + if (!isdigit(*pp)) { + /* Found end of digits before we found :, so wasn't a port */ + pp = ptr; + break; + } + pp--; + } + if ((pp != start_host) && ((pp + 1) < ptr)) { + /* Found it. */ + found_port = true; + port = atoi(pp + 1); + *pp = 0; /* null terminate host */ + } + /* resolve host, try ipv4 first, rslt is in network byte order */ + rv = inet_pton(AF_INET, start_host, &in4); + if (!rv) { + /* Ok, try IPv6 then */ + rv = inet_pton(AF_INET6, start_host, &in6); + if (!rv) { + rv = ARES_EBADSTR; + goto out; + } + /* was ipv6, add new server */ + s = malloc(sizeof(*s)); + if (!s) { + rv = ARES_ENOMEM; + goto out; + } + s->family = AF_INET6; + memcpy(&s->addr, &in6, sizeof(struct ares_in6_addr)); + } + else { + /* was ipv4, add new server */ + s = malloc(sizeof(*s)); + if (!s) { + rv = ARES_ENOMEM; + goto out; + } + s->family = AF_INET; + memcpy(&s->addr, &in4, sizeof(struct in_addr)); + } + if (s) { + /* TODO: Add port to ares_addr_node and assign it here. */ + + s->next = NULL; + if (last) { + last->next = s; + } + else { + servers = s; + last = s; + } + } + + /* Set up for next one */ + found_port = false; + start_host = ptr + 1; + } + } + + rv = ares_set_servers(channel, servers); + + out: + if (csv) + free(csv); + while (servers) { + struct ares_addr_node *s = servers; + servers = servers->next; + free(s); + } + + return rv; +} diff --git a/ares_private.h b/ares_private.h index 0df5cb7..6f86a3a 100644 --- a/ares_private.h +++ b/ares_private.h @@ -257,6 +257,13 @@ struct ares_channeldata { int nsort; char *lookups; + /* For binding to local devices and/or IP addresses. Leave + * them null/zero for no binding. + */ + char local_dev_name[32]; + uint32_t local_ip4; + unsigned char local_ip6[16]; + int optmask; /* the option bitfield passed in at init time */ /* Server addresses and communications state */ diff --git a/ares_process.c b/ares_process.c index 3ef1ddb..e416132 100644 --- a/ares_process.c +++ b/ares_process.c @@ -91,7 +91,6 @@ static void skip_server(ares_channel channel, struct query *query, int whichserver); static void next_server(ares_channel channel, struct query *query, struct timeval *now); -static int configure_socket(ares_socket_t s, ares_channel channel); static int open_tcp_socket(ares_channel channel, struct server_state *server); static int open_udp_socket(ares_channel channel, struct server_state *server); static int same_questions(const unsigned char *qbuf, int qlen, @@ -869,7 +868,7 @@ static int setsocknonblock(ares_socket_t sockfd, /* operate on this */ #endif } -static int configure_socket(ares_socket_t s, ares_channel channel) +static int configure_socket(ares_socket_t s, int family, ares_channel channel) { setsocknonblock(s, TRUE); @@ -892,6 +891,37 @@ static int configure_socket(ares_socket_t s, ares_channel channel) sizeof(channel->socket_receive_buffer_size)) == -1) return -1; +#ifdef SO_BINDTODEVICE + if (channel->local_dev_name[0]) { + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, + channel->local_dev_name, sizeof(channel->local_dev_name))) { + /* Only root can do this, and usually not fatal if it doesn't work, so */ + /* just continue on. */ + } + } +#endif + + if (family == AF_INET) { + if (channel->local_ip4) { + struct sockaddr_in sa; + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_addr.s_addr = htonl(channel->local_ip4); + if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) < 0) + return -1; + } + } + else if (family == AF_INET6) { + if (memcmp(channel->local_ip6, &in6addr_any, sizeof(channel->local_ip6)) != 0) { + struct sockaddr_in6 sa; + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + memcpy(&sa.sin6_addr, channel->local_ip6, sizeof(channel->local_ip6)); + if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) < 0) + return -1; + } + } + return 0; } @@ -936,7 +966,7 @@ static int open_tcp_socket(ares_channel channel, struct server_state *server) return -1; /* Configure it. */ - if (configure_socket(s, channel) < 0) + if (configure_socket(s, server->addr.family, channel) < 0) { sclose(s); return -1; @@ -1028,7 +1058,7 @@ static int open_udp_socket(ares_channel channel, struct server_state *server) return -1; /* Set the socket non-blocking. */ - if (configure_socket(s, channel) < 0) + if (configure_socket(s, server->addr.family, channel) < 0) { sclose(s); return -1; -- 1.6.2.5Received on 2010-05-25