Automatically specifying which source address and interface to be used for
communicating with a given DNS server is very convenient on multihomed hosts.
Two use-cases I have had for this feature are:

* Several mobile broadband providers hand out private IP-addresses, but the DNS
servers are global. Unless special routing rules are added, then the default
route will be used for resolving domains. This is not ideal, as it might lead to
higher latencies for replies, or an additional cost to the user if DNS requests
to the "local" servers are free.

* Several mobile broadband devices act as small routers, and some of the most
popular types only hand out the same IP, DNS server, etc. To make matters worse,
if these devices loose connectivity, they will highjack any DNS request and
reply with its own IP. If you have multiple of these devices, you risk being
stuck without working DNS as all requests might be forwarded to the disconnected
device. Adding support for binding to interface and IP will make sure that
requests are sent to the correct device. Some external tool will still be
required to check that DNS is working fine and updating the resolv-file
accordingly.

Dnsmasq already supports reading and binding to an ip-adress/interface through
the --server option. This patch adds support for specifying which source address
and/or interface to use for a server in the resolv-file, using the same syntax
as for --server. For example, in order to specify that source ip 100.76.125.47
and interface wwan1 should be used to communicate with server 213.158.199.1, the
following line would have to be added to the resolv-file:

nameserver 213.158.199.1@100.76.125.47@wwan1

Since the syntax is not standard, the --multihomed-resolver command line option
must be enabled. Please note that lines with and without source
address/interface can be mixed.

Since we now have two places where the interface-part of --server is parsed, I
have factored out this parsing into a separate function. parse_server() is
converted to use this function.

Signed-off-by: Kristian Evensen <kristian.even...@gmail.com>
---
 man/dnsmasq.8 | 10 +++++++
 src/dnsmasq.h |  3 +-
 src/network.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++--------
 src/option.c  | 68 ++++++++++++++++++++++++++++--------------
 4 files changed, 141 insertions(+), 36 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 2e5ef21..05a8db1 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -1884,6 +1884,16 @@ A special case of
 which differs in two respects. Firstly, only --server and --rev-server are 
allowed 
 in the configuration file included. Secondly, the file is re-read and the 
configuration
 therein is updated when dnsmasq receives SIGHUP.
+.TP
+.B --multihomed-resolver
+Enable support for binding nameservers read from resolv-conf to an ip-address
+and interface, using the same syntax as for --server. In order to use a given
+ip-address/interface for a given nameserver, the nameserver line must follow 
the
+following format:
+.B nameserver <nameserver address>@<source ip-address>@<local interface name>
+Note that specifying only an ip-address or interface name is supported, and
+nameserver lines with and without ip-adress/interface can be mixed.
+
 .SH CONFIG FILE
 At startup, dnsmasq reads
 .I /etc/dnsmasq.conf,
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 6b44e53..f65de13 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -238,7 +238,8 @@ struct event_desc {
 #define OPT_SCRIPT_ARP     53
 #define OPT_MAC_B64        54
 #define OPT_MAC_HEX        55
-#define OPT_LAST           56
+#define OPT_MULTIH_RESOLV  56
+#define OPT_LAST           57
 
 /* extra flags for my_syslog, we use a couple of facilities since they are 
known 
    not to occupy the same bits as priorities, no matter how syslog.h is set 
up. */
diff --git a/src/network.c b/src/network.c
index e5ceb76..65ae50c 100644
--- a/src/network.c
+++ b/src/network.c
@@ -16,6 +16,9 @@
 
 #include "dnsmasq.h"
 
+char *split_server(char *arg, char **source, char **scope_id,
+                  char **interface_opt, int *source_port, int *serv_port);
+
 #ifdef HAVE_LINUX_NETWORK
 
 int indextoname(int fd, int index, char *name)
@@ -1437,7 +1440,7 @@ void add_update_server(int flags,
        serv->flags |= SERV_HAS_DOMAIN;
       
       if (interface)
-       strcpy(serv->interface, interface);      
+       strncpy(serv->interface, interface, IF_NAMESIZE - 1);
       if (addr)
        serv->addr = *addr;
       if (source_addr)
@@ -1618,8 +1621,16 @@ int reload_servers(char *fname)
   while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
     {
       union mysockaddr addr, source_addr;
+      int source_port = daemon->query_port, serv_port = NAMESERVER_PORT;
+      char *split_server_error;
+      char *source = NULL, *interface_opt = NULL;
+#ifdef HAVE_IPV6
+      int scope_index = 0;
+      char *scope_id = NULL;
+#endif
+
       char *token = strtok(line, " \t\n\r");
-      
+
       if (!token)
        continue;
       if (strcmp(token, "nameserver") != 0 && strcmp(token, "server") != 0)
@@ -1629,22 +1640,60 @@ int reload_servers(char *fname)
       
       memset(&addr, 0, sizeof(addr));
       memset(&source_addr, 0, sizeof(source_addr));
-      
+
+      //Guard with daemon flag
+      if (option_bool(OPT_MULTIH_RESOLV))
+       {
+#ifdef HAVE_IPV6
+         split_server_error = split_server(token, &source, &scope_id,
+                                           &interface_opt, &source_port,
+                                           &serv_port);
+#else
+         split_server_error = split_server(token, &source, NULL,
+                                           &interface_opt, &source_port,
+                                           &serv_port);
+#endif
+         if (split_server_error)
+           {
+             my_syslog(LOG_WARNING, _("parsing nameserver: %s failed"), token);
+             continue;
+           }
+       }
+
       if ((addr.in.sin_addr.s_addr = inet_addr(token)) != (in_addr_t) -1)
        {
 #ifdef HAVE_SOCKADDR_SA_LEN
          source_addr.in.sin_len = addr.in.sin_len = sizeof(source_addr.in);
 #endif
          source_addr.in.sin_family = addr.in.sin_family = AF_INET;
-         addr.in.sin_port = htons(NAMESERVER_PORT);
-         source_addr.in.sin_addr.s_addr = INADDR_ANY;
-         source_addr.in.sin_port = htons(daemon->query_port);
+         addr.in.sin_port = htons(serv_port);
+         source_addr.in.sin_port = htons(source_port);
+
+         if (source)
+           {
+             if (!(inet_pton(AF_INET, source, &source_addr.in.sin_addr) > 0))
+               {
+                 if (!interface_opt)
+                   {
+                     interface_opt = source;
+                   }
+                 else
+                   {
+                     my_syslog(LOG_WARNING, _("interface specified twice"));
+                     continue;
+                   }
+               }
+           }
+         else
+           {
+             source_addr.in.sin_addr.s_addr = INADDR_ANY;
+           }
        }
 #ifdef HAVE_IPV6
       else 
-       {       
-         int scope_index = 0;
-         char *scope_id = strchr(token, '%');
+       {
+         if (!option_bool(OPT_MULTIH_RESOLV))
+           scope_id = strchr(token, '%');
          
          if (scope_id)
            {
@@ -1659,11 +1708,33 @@ int reload_servers(char *fname)
 #endif
              source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6;
              source_addr.in6.sin6_flowinfo = addr.in6.sin6_flowinfo = 0;
-             addr.in6.sin6_port = htons(NAMESERVER_PORT);
+             addr.in6.sin6_port = htons(serv_port);
              addr.in6.sin6_scope_id = scope_index;
              source_addr.in6.sin6_addr = in6addr_any;
-             source_addr.in6.sin6_port = htons(daemon->query_port);
+             source_addr.in6.sin6_port = htons(source_port);
              source_addr.in6.sin6_scope_id = 0;
+
+             if (source)
+               {
+                 if (inet_pton(AF_INET6, source,
+                               &source_addr.in6.sin6_addr) == 0)
+                   {
+                     if (!interface_opt)
+                       {
+                         interface_opt = source;
+                       }
+                     else
+                       {
+                         my_syslog(LOG_WARNING, _("interfae specified twice"));
+                         continue;
+                       }
+                   }
+               }
+             else
+               {
+                 source_addr.in6.sin6_addr = in6addr_any;
+               }
+
            }
          else
            continue;
@@ -1673,7 +1744,8 @@ int reload_servers(char *fname)
        continue;
 #endif 
 
-      add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL);
+      add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, interface_opt,
+                       NULL);
       gotone = 1;
     }
   
diff --git a/src/option.c b/src/option.c
index 0c38db3..96c04b9 100644
--- a/src/option.c
+++ b/src/option.c
@@ -159,7 +159,8 @@ struct myoption {
 #define LOPT_SCRIPT_ARP    347
 #define LOPT_DHCPTTL       348
 #define LOPT_TFTP_MTU      349
- 
+#define LOPT_MULTIH_RESOLV 350
+
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
 #else
@@ -323,6 +324,7 @@ static const struct myoption opts[] =
     { "dns-loop-detect", 0, 0, LOPT_LOOP_DETECT },
     { "script-arp", 0, 0, LOPT_SCRIPT_ARP },
     { "dhcp-ttl", 1, 0 , LOPT_DHCPTTL },
+    { "multihomed-resolver", 0, 0 , LOPT_MULTIH_RESOLV },
     { NULL, 0, 0, 0 }
   };
 
@@ -494,6 +496,7 @@ static struct {
   { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove 
DNS forwarding loops."), NULL },
   { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses 
containing ipaddr."), NULL }, 
   { LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses 
with DHCP-derived addresses."), NULL }, 
+  { LOPT_MULTIH_RESOLV, OPT_MULTIH_RESOLV, NULL, gettext_noop("Indicate 
multihomed resolver. Nameservers will be parsed as the --server option"), NULL},
   { 0, 0, NULL, NULL, NULL }
 }; 
 
@@ -753,14 +756,38 @@ static char *parse_mysockaddr(char *arg, union mysockaddr 
*addr)
   return NULL;
 }
 
+char *split_server(char *arg, char **source, char **scope_id,
+                  char **interface_opt, int *source_port, int *serv_port)
+{
+  char *portno;
+
+  if ((*source = split_chr(arg, '@')) && /* is there a source. */
+      (portno = split_chr(*source, '#')) &&
+      !atoi_check16(portno, source_port))
+    return _("bad port");
+
+  if ((portno = split_chr(arg, '#')) && /* is there a port no. */
+      !atoi_check16(portno, serv_port))
+    return _("bad port");
+
+#ifdef HAVE_IPV6
+  *scope_id = split_chr(arg, '%');
+#endif
+
+  if (*source)
+    *interface_opt = split_chr(*source, '@');
+
+  return NULL;
+}
+
 char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr 
*source_addr, char *interface, int *flags)
 {
   int source_port = 0, serv_port = NAMESERVER_PORT;
-  char *portno, *source;
-  char *interface_opt = NULL;
+  char *split_server_error;
+  char *source = NULL, *interface_opt = NULL;
 #ifdef HAVE_IPV6
+  char *scope_id = NULL;
   int scope_index = 0;
-  char *scope_id;
 #endif
   
   if (!arg || strlen(arg) == 0)
@@ -770,31 +797,26 @@ char *parse_server(char *arg, union mysockaddr *addr, 
union mysockaddr *source_a
       return NULL;
     }
 
-  if ((source = split_chr(arg, '@')) && /* is there a source. */
-      (portno = split_chr(source, '#')) &&
-      !atoi_check16(portno, &source_port))
-    return _("bad port");
-  
-  if ((portno = split_chr(arg, '#')) && /* is there a port no. */
-      !atoi_check16(portno, &serv_port))
-    return _("bad port");
-  
 #ifdef HAVE_IPV6
-  scope_id = split_chr(arg, '%');
+  split_server_error = split_server(arg, &source, &scope_id, &interface_opt,
+                                   &source_port, &serv_port);
+#else
+  split_server_error = split_server(arg, &source, NULL, &interface_opt,
+                                   &source_port, &serv_port);
 #endif
-  
-  if (source) {
-    interface_opt = split_chr(source, '@');
 
-    if (interface_opt)
-      {
+
+  if (split_server_error)
+    return split_server_error;
+
+  if (interface_opt)
+    {
 #if defined(SO_BINDTODEVICE)
-       strncpy(interface, interface_opt, IF_NAMESIZE - 1);
+      strncpy(interface, interface_opt, IF_NAMESIZE - 1);
 #else
-       return _("interface binding not supported");
+      return _("interface binding not supported");
 #endif
-      }
-  }
+    }
 
   if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
     {
-- 
2.9.3


_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss@lists.thekelleys.org.uk
http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss

Reply via email to