local-bind: Support binding to local interface/IPs
authorBen Greear <greearb@candelatech.com>
Sun, 18 Jul 2010 21:58:39 +0000 (23:58 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sun, 18 Jul 2010 21:58:39 +0000 (23:58 +0200)
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>
ares.h
ares_init.c
ares_options.c
ares_private.h
ares_process.c
ares_set_local_dev.3 [new file with mode: 0644]
ares_set_local_ip4.3 [new file with mode: 0644]
ares_set_local_ip6.3 [new file with mode: 0644]
ares_set_servers.3
ares_set_servers_csv.3 [new file with mode: 0644]

diff --git a/ares.h b/ares.h
index 368c73a..3b4ff13 100644 (file)
--- 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);
 
index a812b2d..718d552 100644 (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)
index d00368a..bb1d5d5 100644 (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;
+}
index 0df5cb7..6f86a3a 100644 (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 */
index 3ef1ddb..14dce71 100644 (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;
diff --git a/ares_set_local_dev.3 b/ares_set_local_dev.3
new file mode 100644 (file)
index 0000000..b967c89
--- /dev/null
@@ -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
diff --git a/ares_set_local_ip4.3 b/ares_set_local_ip4.3
new file mode 100644 (file)
index 0000000..b7955b7
--- /dev/null
@@ -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
diff --git a/ares_set_local_ip6.3 b/ares_set_local_ip6.3
new file mode 100644 (file)
index 0000000..7ca0afc
--- /dev/null
@@ -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
index 63fb461..6f25c29 100644 (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)
diff --git a/ares_set_servers_csv.3 b/ares_set_servers_csv.3
new file mode 100644 (file)
index 0000000..a37c437
--- /dev/null
@@ -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