[Dnsmasq-discuss] [PATCH] Support --server syntax in resolv-file

Kristian Evensen kristian.evensen at gmail.com
Thu Mar 23 18:16:39 GMT 2017


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 at 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.evensen at 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




More information about the Dnsmasq-discuss mailing list