Hey Simon,

when setting up a new docker server this morning I desperately
missed the specify-server-by-host feature in dnsmasq. I revived
my initial patch and added what was requested: I removed the
compile-time option and changed the code to always use all
suitable addresses (IPv6 is not suitable when source is IPv4 and
vice versa).

Along the way, I found a small bug: IPv6 source addresses are
incorrectly interpreted as interface names for IPv4 server
addresses (and vice versa). This is fixed in my patch:

BEFORE:
--server=1.2.3.4@fe80::
dnsmasq: using nameserver 1.2.3.4#53(via fe80::)

NOW:
--server=1.2.3.4@fe80::
dnsmasq: bad command line options: cannot use IPv4 server address
with IPv6 source address



Examples:

--server=localhost
dnsmasq: using nameserver 127.0.0.1#53


--server=a.test.dl6er.de@fe80::
dnsmasq: warning: no upstream servers configured


--server=both.test.dl6er.de@fe80::
dnsmasq: using nameserver dead:beef::#53


--server=both.test.dl6er.de@10.0.0.1
dnsmasq: using nameserver 1.2.3.4#53


--server=both.test.dl6er.de
dnsmasq: using nameserver dead:beef::#53
dnsmasq: using nameserver 1.2.3.4#53


--server=amazon.com
dnsmasq: using nameserver 54.239.28.85#53
dnsmasq: using nameserver 52.94.236.248#53
dnsmasq: using nameserver 205.251.242.103#53


--server=does-not-exist-4615465468435.com
dnsmasq: bad command line options: Name or service not known
  (dnsmasq refuses to start)


What do you think?

I'm undecided if dnsmasq should fail hard on the second example
(no IPv6 address for a hostname but IPv6 source address given)
but this may be a real edge-case where we can expect users to
understand what they're doing and read the logs. Catching this
would require extra logic (valid hostnames are returned but none
is used -> error out).

Best,
Dominik

On Thu, 2022-04-07 at 12:24 +0100, Simon Kelley wrote:
> This seems like a sensible idea, but it does need a clear
> warning in the 
> documentation that it will only work if the dnsmasq instance
> being 
> configured is not the one providing DNS to the local system.
> 
> Two comments about the patch.
> 
> 1) Geert's point is a good one: This patch uses only libc: it
> doesn't 
> add any build dependencies and it's small. There's no reason to
> make it 
> a compile-time option.
> 
> 2) Not handling multiple addresses from getaddrinfo() feels
> like a 
> mistake. What should happen in that case is obvious and the
> obvious 
> behaviour is useful. If it's not done now, we'll end up doing
> it later 
> when someone falls foul of this short-cut. the implementation
> is more 
> complex, but I think returning the struct addrinfo * linked
> list from 
> getaddrinfo instead of a single address should work: the caller
> of 
> parse_server becomes responsible for freeing the struct
> addrinfos
> 
> 3) One error that needs to be handled is if a source address is
> specified, and the address family of the source address doesn't
> match 
> the address family of an address returned  from getaddrinfo. My
> initial 
> thought was to make this a fatal error, but that has the
> problem that
> 
> server=dns.example.com@192.168.7.1
> 
> will work fine until an AAAA record is added for
> dns.example.com, when 
> dnsmasq would no longer start. Better I thing to only use DNS
> records 
> that match the source address type if it's specified.
> 
> 
> Cheers,
> 
> Simon.
> 
> 
> 
> 
> 
> On 02/04/2022 20:40, Dominik Derigs wrote:
> > Dear Simon,
> > 
> > In docker swarm and compose configurations, other containers
> > are
> > only reachable via hostnames. It is not always possible to
> > assign
> > IP addresses beforehand. Hence, the upstream server IP is not
> > known at dnsmasq start when the upstream is part of the
> > deployed
> > configuration, e.g., a local cloudflared or unbound
> > container.
> > 
> > So far, getting dnsmasq to run in such a case requires hacks
> > that
> > somehow try to determine the IP address before starting
> > dnsmasq.
> > An example for such a hack (not invented by me):
> > https://github.com/tschaffter/docker-dnsmasq/blob/54b5d5d551746b6f1708fbf4a705e2de66c2eaee/docker-entrypoint.sh#L14-L23
> > 
> > This patch implements name resolution functionality for
> > server=... by querying the system resolver for a hostname. It
> > is
> > only used when a user supplied something that is not a valid
> > IP
> > address (dnsmasq currently fails hard in this case so this
> > isn't
> > a breaking change) and can be omitted by a compile time flag
> > (I
> > think it's worthwhile to have it).
> > 
> > I know my proposal does sound somewhat strange (resolving a
> > DNS
> > server name) but this is something that is somewhat
> > frequently
> > needed and currently only possible through external hacks.

From d1565206d541a0a6eeb4bebd69be5aaec9cca4fb Mon Sep 17 00:00:00 2001
From: Dominik Derigs <dl...@dl6er.de>
Date: Sat, 15 Oct 2022 12:26:35 +0200
Subject: [PATCH] Extend server to accept hostnames for upstream resolver

Signed-off-by: DL6ER <dl...@dl6er.de>
---
 man/dnsmasq.8 |   8 +-
 src/dbus.c    |   8 +-
 src/dnsmasq.h |  13 ++-
 src/option.c  | 312 +++++++++++++++++++++++++++++++++++++-------------
 4 files changed, 255 insertions(+), 86 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 3ae3000..b5173ab 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -461,8 +461,8 @@ Tells dnsmasq to never forward A or AAAA queries for plain names, without dots
 or domain parts, to upstream nameservers. If the name is not known
 from /etc/hosts or DHCP then a "not found" answer is returned.
 .TP
-.B \-S, --local, --server=[/[<domain>]/[domain/]][<ipaddr>[#<port>]][@<interface>][@<source-ip>[#<port>]]
-Specify IP address of upstream servers directly. Setting this flag does
+.B \-S, --local, --server=[/[<domain>]/[domain/]][<server>[#<port>]][@<interface>][@<source-ip>[#<port>]]
+Specify upstream servers directly. Setting this flag does
 not suppress reading of /etc/resolv.conf, use \fB--no-resolv\fP to do that. If one or more
 optional domains are given, that server is used only for those domains
 and they are queried only using the specified server. This is
@@ -530,6 +530,10 @@ The query-port flag is ignored for any servers which have a
 source address specified but the port may be specified directly as
 part of the source address. Forcing queries to an interface is not
 implemented on all platforms supported by dnsmasq.
+
+Upstream servers may be specified with a hostname rather than an IP address.
+In this case, dnsmasq will try to use the system resolver to get the IP address
+of a server during startup. If name resolution fails, starting dnsmasq fails, too.
 .TP
 .B --rev-server=<ip-address>[/<prefix-len>][,<ipaddr>][#<port>][@<interface>][@<source-ip>[#<port>]]
 This is functionally the same as 
diff --git a/src/dbus.c b/src/dbus.c
index 4fdad46..5cbddd9 100644
--- a/src/dbus.c
+++ b/src/dbus.c
@@ -292,6 +292,11 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
       u16 flags = 0;
       char interface[IF_NAMESIZE];
       char *str_addr, *str_domain = NULL;
+      struct server_details sdetails = { 0 };
+      sdetails.addr = &addr;
+      sdetails.source_addr = &source_addr;
+      sdetails.interface = interface;
+      sdetails.flags = &flags;
 
       if (strings)
 	{
@@ -375,7 +380,8 @@ static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
 	}
 
       /* parse the IP address */
-      if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags)))
+      if ((addr_err = parse_server(str_addr, &sdetails, 0)) ||
+          (addr_err = parse_server_addr(&sdetails)))
 	{
           error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
                                                 "Invalid IP address '%s': %s",
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 1835a11..1575fe1 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -133,6 +133,7 @@ typedef unsigned long long u64;
 #include <sys/uio.h>
 #include <syslog.h>
 #include <dirent.h>
+#include <netdb.h>
 #ifndef HAVE_LINUX_NETWORK
 #  include <net/if_dl.h>
 #endif
@@ -1282,6 +1283,14 @@ extern struct daemon {
 #endif
 } *daemon;
 
+struct server_details {
+  union mysockaddr *addr, *source_addr;
+  struct addrinfo *hostinfo;
+  char *interface, *source, *scope_id, *interface_opt;
+  int serv_port, source_port, addr_type, scope_index, valid, resolved;
+  u16 *flags;
+};
+
 /* cache.c */
 void cache_init(void);
 void next_uid(struct crec *crecp);
@@ -1456,8 +1465,8 @@ void read_servers_file(void);
 void set_option_bool(unsigned int opt);
 void reset_option_bool(unsigned int opt);
 struct hostsfile *expand_filelist(struct hostsfile *list);
-char *parse_server(char *arg, union mysockaddr *addr, 
-		   union mysockaddr *source_addr, char *interface, u16 *flags);
+char *parse_server(char *arg, struct server_details *sdetails, const int can_resolve);
+char *parse_server_addr(struct server_details *sdetails);
 int option_read_dynfile(char *file, int flags);
 
 /* forward.c */
diff --git a/src/option.c b/src/option.c
index f2110cf..3fe86cb 100644
--- a/src/option.c
+++ b/src/option.c
@@ -852,117 +852,237 @@ static char *parse_mysockaddr(char *arg, union mysockaddr *addr)
   return NULL;
 }
 
-char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, u16 *flags)
+char *parse_server(char *arg, struct server_details *sdetails, const int can_resolve)
 {
-  int source_port = 0, serv_port = NAMESERVER_PORT;
-  char *portno, *source;
-  char *interface_opt = NULL;
-  int scope_index = 0;
-  char *scope_id;
+  sdetails->serv_port = NAMESERVER_PORT;
+  char *portno;
+  int ecode = 0;
+  struct addrinfo hints = { 0 };
 
-  *interface = 0;
+  *sdetails->interface = 0;
 
   if (strcmp(arg, "#") == 0)
     {
-      if (flags)
-	*flags |= SERV_USE_RESOLV;
+      if (sdetails->flags)
+	*sdetails->flags |= SERV_USE_RESOLV;
+      sdetails->valid = 1;
       return NULL;
     }
   
-  if ((source = split_chr(arg, '@')) && /* is there a source. */
-      (portno = split_chr(source, '#')) &&
-      !atoi_check16(portno, &source_port))
+  if ((sdetails->source = split_chr(arg, '@')) && /* is there a source. */
+      (portno = split_chr(sdetails->source, '#')) &&
+      !atoi_check16(portno, &sdetails->source_port))
     return _("bad port");
   
   if ((portno = split_chr(arg, '#')) && /* is there a port no. */
-      !atoi_check16(portno, &serv_port))
+      !atoi_check16(portno, &sdetails->serv_port))
     return _("bad port");
   
-  scope_id = split_chr(arg, '%');
+  sdetails->scope_id = split_chr(arg, '%');
   
-  if (source) {
-    interface_opt = split_chr(source, '@');
+  if (sdetails->source) {
+    sdetails->interface_opt = split_chr(sdetails->source, '@');
 
-    if (interface_opt)
+    if (sdetails->interface_opt)
       {
 #if defined(SO_BINDTODEVICE)
-	safe_strncpy(interface, source, IF_NAMESIZE);
-	source = interface_opt;
+	safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE);
+	sdetails->source = sdetails->interface_opt;
 #else
 	return _("interface binding not supported");
 #endif
       }
   }
 
-  if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
+  if (inet_pton(AF_INET, arg, &sdetails->addr->in.sin_addr) > 0)
+      sdetails->addr_type = AF_INET;
+  else if (inet_pton(AF_INET6, arg, &sdetails->addr->in6.sin6_addr) > 0)
+      sdetails->addr_type = AF_INET6;
+  /* if the argument is neither an IPv4 not an IPv6 address, it might be a
+     hostname and we should try to resolve it to a suitable address.
+     However, we don't try this in domain_rev4/6 (can_resolve = 0) */
+  else if(can_resolve)
+    {
+      memset(&hints, 0, sizeof(hints));
+      /* The AI_ADDRCONFIG flag ensures that then IPv4 addresses are returned in
+         the result only if the local system has at least one IPv4 address
+         configured, and IPv6 addresses are returned only if the local system
+         has at least one IPv6 address configured. The loopback address is not
+         considered for this case as valid as a configured address. This flag is
+         useful on, for example, IPv4-only systems, to ensure that getaddrinfo()
+         does not return IPv6 socket addresses that would always fail in
+         subsequent connect() or bind() attempts. */
+      hints.ai_flags = AI_ADDRCONFIG;
+#if defined(HAVE_IDN) && defined(AI_IDN)
+      /* If the AI_IDN flag is specified and we have glibc 2.3.4 or newer, then
+         the node name given in node is converted to IDN format if necessary.
+         The source encoding is that of the current locale. */
+      hints.ai_flags |= AI_IDN;
+#endif
+      /* The value AF_UNSPEC indicates that getaddrinfo() should return socket
+         addresses for any address family (either IPv4 or IPv6, for example)
+         that can be used with node <arg> and service "domain". */
+      hints.ai_family = AF_UNSPEC;
+
+      /* Get addresses suitable for sending datagrams to */
+      hints.ai_socktype = SOCK_DGRAM;
+
+      /* Get address associated with this hostname */
+      ecode = getaddrinfo(arg, "domain", &hints, &sdetails->hostinfo);
+      if (ecode == 0)
+      {
+	/* The getaddrinfo() function allocated and initialized a linked list of
+	   addrinfo structures, one for each network address that matches node
+	   and service, subject to the restrictions imposed by our <hints>
+	   above, and returns a pointer to the start of the list in <hostinfo>.
+	   The items in the linked list are linked by the <ai_next> field. */
+	sdetails->valid = 1;
+	sdetails->resolved = 1;
+	return NULL;
+      }
+      else
+      {
+	/* Lookup failed, return human readable error string */
+	if (ecode == EAI_AGAIN)
+	  return _("Cannot resolve server name");
+	else
+	  return _((char*)gai_strerror(ecode));
+      }
+    }
+  else
+    return _("bad address");
+
+  sdetails->valid = 1;
+  return NULL;
+}
+
+char *parse_server_addr(struct server_details *sdetails)
+{
+    if (sdetails->addr_type == AF_INET)
     {
-      addr->in.sin_port = htons(serv_port);	
-      addr->sa.sa_family = source_addr->sa.sa_family = AF_INET;
+      sdetails->addr->in.sin_port = htons(sdetails->serv_port);
+      sdetails->addr->sa.sa_family = sdetails->source_addr->sa.sa_family = AF_INET;
 #ifdef HAVE_SOCKADDR_SA_LEN
       source_addr->in.sin_len = addr->in.sin_len = sizeof(struct sockaddr_in);
 #endif
-      source_addr->in.sin_addr.s_addr = INADDR_ANY;
-      source_addr->in.sin_port = htons(daemon->query_port);
-      
-      if (source)
+      sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY;
+      sdetails->source_addr->in.sin_port = htons(daemon->query_port);
+
+      if (sdetails->source)
 	{
-	  if (flags)
-	    *flags |= SERV_HAS_SOURCE;
-	  source_addr->in.sin_port = htons(source_port);
-	  if (!(inet_pton(AF_INET, source, &source_addr->in.sin_addr) > 0))
+	  if (sdetails->flags)
+	    *sdetails->flags |= SERV_HAS_SOURCE;
+	  sdetails->source_addr->in.sin_port = htons(sdetails->source_port);
+	  if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 0)
 	    {
+	      if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 1)
+		{
+		  sdetails->source_addr->sa.sa_family = AF_INET6;
+		  /* When resolving a server IP by hostname, we can simply skip mismatching
+		     server / source IP pairs. Otherwise, when an IP address is given directly,
+		     this is a fatal error. */
+		  if(!sdetails->resolved)
+		    return _("cannot use IPv4 server address with IPv6 source address");
+		}
+	      else
+		{
 #if defined(SO_BINDTODEVICE)
-	      if (interface_opt)
-		return _("interface can only be specified once");
-	      
-	      source_addr->in.sin_addr.s_addr = INADDR_ANY;
-	      safe_strncpy(interface, source, IF_NAMESIZE);
+		  if (sdetails->interface_opt)
+		    return _("interface can only be specified once");
+
+		  sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY;
+		  safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE);
 #else
-	      return _("interface binding not supported");
+		  return _("interface binding not supported");
 #endif
+		}
 	    }
 	}
     }
-  else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+  else if (sdetails->addr_type == AF_INET6)
     {
-      if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0)
+      if (sdetails->scope_id && (sdetails->scope_index = if_nametoindex(sdetails->scope_id)) == 0)
 	return _("bad interface name");
-      
-      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_scope_id = 0;
-      addr->sa.sa_family = source_addr->sa.sa_family = AF_INET6;
-      addr->in6.sin6_flowinfo = source_addr->in6.sin6_flowinfo = 0;
+
+      sdetails->addr->in6.sin6_port = htons(sdetails->serv_port);
+      sdetails->addr->in6.sin6_scope_id = sdetails->scope_index;
+      sdetails->source_addr->in6.sin6_addr = in6addr_any;
+      sdetails->source_addr->in6.sin6_port = htons(daemon->query_port);
+      sdetails->source_addr->in6.sin6_scope_id = 0;
+      sdetails->addr->sa.sa_family = sdetails->source_addr->sa.sa_family = AF_INET6;
+      sdetails->addr->in6.sin6_flowinfo = sdetails->source_addr->in6.sin6_flowinfo = 0;
 #ifdef HAVE_SOCKADDR_SA_LEN
-      addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6);
+      sdetails->addr->in6.sin6_len = sdetails->source_addr->in6.sin6_len = sizeof(addr->in6);
 #endif
-      if (source)
+      if (sdetails->source)
 	{
-	  if (flags)
-	    *flags |= SERV_HAS_SOURCE;
-	  source_addr->in6.sin6_port = htons(source_port);
-	  if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0)
+	  if (sdetails->flags)
+	    *sdetails->flags |= SERV_HAS_SOURCE;
+	  sdetails->source_addr->in6.sin6_port = htons(sdetails->source_port);
+	  if (inet_pton(AF_INET6, sdetails->source, &sdetails->source_addr->in6.sin6_addr) == 0)
 	    {
+	      if (inet_pton(AF_INET, sdetails->source, &sdetails->source_addr->in.sin_addr) == 1)
+		{
+		  sdetails->source_addr->sa.sa_family = AF_INET;
+		  /* When resolving a server IP by hostname, we can simply skip mismatching
+		     server / source IP pairs. Otherwise, when an IP address is given directly,
+		     this is a fatal error. */
+		  if(!sdetails->resolved)
+		    return _("cannot use IPv6 server address with IPv4 source address");
+		}
+	      else
+		{
 #if defined(SO_BINDTODEVICE)
-	      if (interface_opt)
-		return _("interface can only be specified once");
-	      
-	      source_addr->in6.sin6_addr = in6addr_any;
-	      safe_strncpy(interface, source, IF_NAMESIZE);
+		  if (sdetails->interface_opt)
+		  return _("interface can only be specified once");
+
+		  sdetails->source_addr->in6.sin6_addr = in6addr_any;
+		  safe_strncpy(sdetails->interface, sdetails->source, IF_NAMESIZE);
 #else
-	      return _("interface binding not supported");
+		  return _("interface binding not supported");
 #endif
+		}
 	    }
 	}
     }
   else
     return _("bad address");
-
   return NULL;
 }
 
+static int parse_server_next(struct server_details *sdetails)
+{
+  /* Looping over resolved addresses? */
+  if (sdetails->hostinfo)
+    {
+      /* Get address type */
+      sdetails->addr_type = sdetails->hostinfo->ai_family;
+
+      /* Get address */
+      if (sdetails->addr_type == AF_INET)
+	memcpy(&sdetails->addr->in.sin_addr,
+		&((struct sockaddr_in *) sdetails->hostinfo->ai_addr)->sin_addr,
+		sizeof(sdetails->addr->in.sin_addr));
+      else if (sdetails->addr_type == AF_INET6)
+	memcpy(&sdetails->addr->in6.sin6_addr,
+		&((struct sockaddr_in6 *) sdetails->hostinfo->ai_addr)->sin6_addr,
+		sizeof(sdetails->addr->in6.sin6_addr));
+
+      /* Iterate to the next available address */
+      sdetails->valid = sdetails->hostinfo->ai_next != NULL;
+      sdetails->hostinfo = sdetails->hostinfo->ai_next;
+      return 1;
+    }
+  else if (sdetails->valid)
+    {
+      /* When using an IP address, we return the address only once */
+      sdetails->valid = 0;
+      return 1;
+    }
+  /* Stop iterating here, we used all available addresses */
+  return 0;
+}
+
 static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int size)
 {
   int i, j;
@@ -973,10 +1093,16 @@ static char *domain_rev4(int from_file, char *server, struct in_addr *addr4, int
   union mysockaddr serv_addr, source_addr;
   char interface[IF_NAMESIZE+1];
   int count = 1, rem, addrbytes, addrbits;
-  
+  struct server_details sdetails = { 0 };
+  sdetails.addr = &serv_addr;
+  sdetails.source_addr = &source_addr;
+  sdetails.interface = interface;
+  sdetails.flags = &flags;
+
   if (!server)
     flags = SERV_LITERAL_ADDRESS;
-  else if ((string = parse_server(server, &serv_addr, &source_addr, interface, &flags)))
+  else if ((string = parse_server(server, &sdetails, 0)) ||
+	   (string = parse_server_addr(&sdetails)))
     return string;
 
   if (from_file)
@@ -1032,10 +1158,16 @@ static char *domain_rev6(int from_file, char *server, struct in6_addr *addr6, in
   union mysockaddr serv_addr, source_addr;
   char interface[IF_NAMESIZE+1];
   int count = 1, rem, addrbytes, addrbits;
+  struct server_details sdetails = { 0 };
+  sdetails.addr = &serv_addr;
+  sdetails.source_addr = &source_addr;
+  sdetails.interface = interface;
+  sdetails.flags = &flags;
 
   if (!server)
     flags = SERV_LITERAL_ADDRESS;
-  else if ((string = parse_server(server, &serv_addr, &source_addr, interface, &flags)))
+  else if ((string = parse_server(server, &sdetails, 0)) ||
+	   (string = parse_server_addr(&sdetails)))
     return string;
 
   if (from_file)
@@ -2793,6 +2925,12 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	union mysockaddr serv_addr, source_addr;
 	char interface[IF_NAMESIZE+1];
 
+	struct server_details sdetails = { 0 };
+	sdetails.addr = &serv_addr;
+	sdetails.source_addr = &source_addr;
+	sdetails.interface = interface;
+	sdetails.flags = &flags;
+
 	unhide_metas(arg);
 	
 	/* split the domain args, if any and skip to the end of them. */
@@ -2825,36 +2963,48 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	  }
 	else
 	  {
-	    if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags)))
+	    if ((err = parse_server(arg, &sdetails, 1)))
 	      ret_err(err);
 	  }
 
 	if (servers_only && option == 'S')
 	  flags |= SERV_FROM_FILE;
 	
-	while (1)
+	while (parse_server_next(&sdetails))
 	  {
-	    /* server=//1.2.3.4 is special. */
-	    if (lastdomain)
+	    if ((err = parse_server_addr(&sdetails)))
+	      ret_err(err);
+
+	    /* When source is set only use DNS records of the same type and skip all others */
+	    if (flags & SERV_HAS_SOURCE && sdetails.addr_type != sdetails.source_addr->sa.sa_family)
+	      continue;
+
+	    while (1)
 	      {
-		if (strlen(domain) == 0)
-		  flags |= SERV_FOR_NODOTS;
-		else
-		  flags &= ~SERV_FOR_NODOTS;
+		/* server=//1.2.3.4 is special. */
+		if (lastdomain)
+		{
+		  if (strlen(domain) == 0)
+		    flags |= SERV_FOR_NODOTS;
+		  else
+		    flags &= ~SERV_FOR_NODOTS;
+
+		  /* address=/#/ matches the same as without domain */
+		  if (option == 'A' && domain[0] == '#' && domain[1] == 0)
+		    domain[0] = 0;
+		}
 
-		/* address=/#/ matches the same as without domain */
-		if (option == 'A' && domain[0] == '#' && domain[1] == 0)
-		  domain[0] = 0;
+		if (!add_update_server(flags, sdetails.addr, sdetails.source_addr, sdetails.interface, domain, &addr))
+		  ret_err(gen_err);
+
+		if (!lastdomain || domain == lastdomain)
+		  break;
+
+		domain += strlen(domain) + 1;
 	      }
-	    
-	    if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr))
-	      ret_err(gen_err);
-	    
-	    if (!lastdomain || domain == lastdomain)
-	      break;
-	    
-	    domain += strlen(domain) + 1;
 	  }
+	  if (sdetails.resolved)
+	    freeaddrinfo(sdetails.hostinfo);
 	
      	break;
       }
-- 
2.34.1

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

Reply via email to