1
0
mirror of https://frontier.innolan.net/rainlance/c-ares.git synced 2025-11-20 17:40:52 +00:00

local-bind: Support binding to local interface/IPs

Add 3 new functions to set the local binding for the out-going
socket connection, and add ares_set_servers_csv() to set a
list of servers at once as a comma-separated string.

Signed-off-by: Ben Greear <greearb@candelatech.com>
This commit is contained in:
Ben Greear
2010-07-18 23:58:39 +02:00
committed by Daniel Stenberg
parent 45a09b7efb
commit e3b04e5a47
10 changed files with 368 additions and 7 deletions

19
ares.h
View File

@ -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);

View File

@ -172,6 +172,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++)
@ -286,6 +290,10 @@ 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;
strncpy((*dest)->local_dev_name, src->local_dev_name, sizeof(src->local_dev_name));
(*dest)->local_ip4 = src->local_ip4;
memcpy((*dest)->local_ip6, src->local_ip6, sizeof(src->local_ip6));
/* Full name server cloning required when not all are IPv4 */
for (i = 0; i < src->nservers; i++)
{
@ -1604,6 +1612,28 @@ 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)

View File

@ -21,6 +21,7 @@
#include "ares.h"
#include "ares_data.h"
#include "ares_private.h"
#include "inet_net_pton.h"
int ares_get_servers(ares_channel channel,
@ -125,3 +126,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 ares_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 = ares_inet_pton(AF_INET, start_host, &in4);
if (!rv) {
/* Ok, try IPv6 then */
rv = ares_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;
}

View File

@ -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 */

View File

@ -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,8 +891,39 @@ 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;
}
}
static int open_tcp_socket(ares_channel channel, struct server_state *server)
{
@ -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;

39
ares_set_local_dev.3 Normal file
View File

@ -0,0 +1,39 @@
.\"
.\" Copyright 2010 by Ben Greear <greearb@candelatech.com>
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.TH ARES_SET_LOCAL_DEV 3 "30 June 2010"
.SH NAME
ares_set_local_dev \- Bind to a specific network device when creating sockets.
.SH SYNOPSIS
.nf
.B #include <ares.h>
.PP
.B void ares_set_local_dev(ares_channel \fIchannel\fP, const char* \fIlocal_dev_name\fP)
.fi
.SH DESCRIPTION
The \fBares_set_local_dev\fP function causes all future sockets
to be bound to this device with SO_BINDTODEVICE. This forces communications
to go over a certain interface, which can be useful on multi-homed machines.
This option is only supported on Linux, and root priviledges are required
for the option to work. If SO_BINDTODEVICE is not supported or the
setsocktop call fails (probably because of permissions), the error is
silently ignored.
.SH SEE ALSO
.BR ares_set_local_ipv4 (3)
.BR ares_set_local_ipv6 (3)
.SH NOTES
This function was added in c-ares 1.7.2
.SH AUTHOR
Ben Greear

34
ares_set_local_ip4.3 Normal file
View File

@ -0,0 +1,34 @@
.\"
.\" Copyright 2010 by Ben Greear <greearb@candelatech.com>
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.TH ARES_SET_LOCAL_IP4 3 "30 June 2010"
.SH NAME
ares_set_local_ip4 \- Set local IPv4 address outgoing requests.
.SH SYNOPSIS
.nf
.B #include <ares.h>
.PP
.B void ares_set_local_ip4(ares_channel \fIchannel\fP, uint32_t \fIlocal_ip\fP)
.fi
.SH DESCRIPTION
The \fBares_set_local_ip4\fP function sets the IP address for outbound
requests. This allows users to specify outbound interfaces when used
on multi-homed systems.
.SH SEE ALSO
.BR ares_set_local_ip6 (3)
.SH NOTES
This function was added in c-ares 1.7.2
.SH AUTHOR
Ben Greear

34
ares_set_local_ip6.3 Normal file
View File

@ -0,0 +1,34 @@
.\"
.\" Copyright 2010 by Ben Greear <greearb@candelatech.com>
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.TH ARES_SET_LOCAL_IP6 3 "30 June 2010"
.SH NAME
ares_set_local_ip6 \- Set local IPv6 address outgoing requests.
.SH SYNOPSIS
.nf
.B #include <ares.h>
.PP
.B void ares_set_local_ip6(ares_channel \fIchannel\fP, const unsigned char* \fIlocal_ip6\fP)
.fi
.SH DESCRIPTION
The \fBares_set_local_ip6\fP function sets the IPv6 address for outbound
IPv6 requests. This allows users to specify outbound interfaces when used
on multi-homed systems. The local_ip6 argument must be 16 bytes in length.
.SH SEE ALSO
.BR ares_set_local_ip4 (3)
.SH NOTES
This function was added in c-ares 1.7.2
.SH AUTHOR
Ben Greear

View File

@ -1,6 +1,5 @@
.\"
.\" Copyright 1998 by the Massachusetts Institute of Technology.
.\" Copyright (C) 2008-2010 by Daniel Stenberg
.\" Copyright 2010 by Ben Greear <greearb@candelatech.com>
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
@ -67,6 +66,7 @@ was invalid.
.B ARES_ENOTINITIALIZED
c-ares library initialization not yet performed.
.SH SEE ALSO
.BR ares_set_servers_csv (3),
.BR ares_get_servers (3),
.BR ares_init_options (3),
.BR ares_dup(3)

43
ares_set_servers_csv.3 Normal file
View File

@ -0,0 +1,43 @@
.\"
.\" Copyright 2010 by Ben Greear <greearb@candelatech.com>
.\"
.\" Permission to use, copy, modify, and distribute this
.\" software and its documentation for any purpose and without
.\" fee is hereby granted, provided that the above copyright
.\" notice appear in all copies and that both that copyright
.\" notice and this permission notice appear in supporting
.\" documentation, and that the name of M.I.T. not be used in
.\" advertising or publicity pertaining to distribution of the
.\" software without specific, written prior permission.
.\" M.I.T. makes no representations about the suitability of
.\" this software for any purpose. It is provided "as is"
.\" without express or implied warranty.
.\"
.TH ARES_SET_SERVERS_CSV 3 "30 June 2010"
.SH NAME
ares_set_servers_csv \- Set list of DNS servers to be used.
.SH SYNOPSIS
.nf
.B #include <ares.h>
.PP
.B void ares_set_servers_csv(ares_channel \fIchannel\fP, const char* \fIservers\fP)
.fi
.SH DESCRIPTION
The \fBares_set_servers_csv\fP function sets the list of DNS servers
that ARES will query. The format of the servers option is:
host[:port][,host[:port]]...
For example:
192.168.1.100,192.168.1.101,3.4.5.6
.SH SEE ALSO
.BR ares_set_servers (3)
.SH NOTES
The port option is currently ignored by c-ares internals
and the standard port is always used.
This function was added in c-ares 1.7.2
.SH AUTHOR
Ben Greear