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 93f597e943283124af2e39620e748635cc6a04d6 Mon Sep 17 00:00:00 2001
From: Dominik Derigs <dl...@dl6er.de>
Date: Thu, 3 Feb 2022 16:12:16 +0100
Subject: [PATCH] Extend server to accept hostnames for upstream resolver

Signed-off-by: DL6ER <dl...@dl6er.de>
---
 man/dnsmasq.8 |  4 +++
 src/config.h  |  3 +++
 src/option.c  | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 75 insertions(+), 1 deletion(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 9af4ec8..87486a5 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -511,6 +511,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.
+
+If dnsmasq is compiled with HAVE_RESOLVESERVER, the upstream server 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.
 .TP
 .B --rev-server=<ip-address>[/<prefix-len>][,<ipaddr>][#<port>][@<interface>][@<source-ip>[#<port>]]
 This is functionally the same as 
diff --git a/src/config.h b/src/config.h
index 227fb1f..20b9487 100644
--- a/src/config.h
+++ b/src/config.h
@@ -138,6 +138,9 @@ HAVE_LOOP
 HAVE_INOTIFY
    use the Linux inotify facility to efficiently re-read configuration files.
 
+HAVE_RESOLVESERVER
+   lookup servers if specified via hostnames instead of IP addresses.
+
 NO_ID
    Don't report *.bind CHAOS info to clients, forward such requests upstream instead.
 NO_TFTP
diff --git a/src/option.c b/src/option.c
index 5230eaf..bf1087a 100644
--- a/src/option.c
+++ b/src/option.c
@@ -19,6 +19,10 @@
 #include "dnsmasq.h"
 #include <setjmp.h>
 
+#ifdef HAVE_RESOLVESERVER
+#include <netdb.h>
+#endif
+
 static volatile int mem_recover = 0;
 static jmp_buf mem_jmp;
 static int one_file(char *file, int hard_opt);
@@ -846,6 +850,11 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
   char *interface_opt = NULL;
   int scope_index = 0;
   char *scope_id;
+  int addr_type = 0;
+#ifdef HAVE_RESOLVESERVER
+  int ecode = 0;
+  struct addrinfo *hostinfo, hints = { 0 };
+#endif
 
   *interface = 0;
 
@@ -882,6 +891,64 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
   }
 
   if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
+      addr_type = AF_INET;
+  else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+      addr_type = AF_INET6;
+#ifdef HAVE_RESOLVESERVER
+  /* 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 */
+  else
+    {
+      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 address associated with this hostname */
+      ecode = getaddrinfo(arg, "domain", &hints, &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. For
+           simplicity, we only look at the first returned address here. */
+	addr_type = hostinfo->ai_family;
+	if(addr_type == AF_INET)
+	  memcpy(&addr->in.sin_addr, &((struct sockaddr_in *) hostinfo->ai_addr)->sin_addr, sizeof(addr->in.sin_addr));
+	else if(addr_type == AF_INET6)
+	  memcpy(&addr->in6.sin6_addr, &((struct sockaddr_in6 *) hostinfo->ai_addr)->sin6_addr, sizeof(addr->in6.sin6_addr));
+	freeaddrinfo(hostinfo);
+      }
+      else
+      {
+	/* Lookup failed, return human readable error string */
+	if (ecode == EAI_AGAIN)
+	  return (char*)"Cannot resolve server name";
+	else
+	  return (char*)gai_strerror(ecode);
+      }
+    }
+#endif
+
+    if (addr_type == AF_INET)
     {
       addr->in.sin_port = htons(serv_port);	
       addr->sa.sa_family = source_addr->sa.sa_family = AF_INET;
@@ -910,7 +977,7 @@ char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_a
 	    }
 	}
     }
-  else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+  else if (addr_type == AF_INET6)
     {
       if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0)
 	return _("bad interface name");
_______________________________________________
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