summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDominik Derigs <dl6er@dl6er.de>2022-11-05 11:49:52 +0000
committerSimon Kelley <simon@thekelleys.org.uk>2022-11-05 11:49:52 +0000
commit2d8905dafd41330d661f2e6e78f19b4199ce9e6b (patch)
treee70fe39be574f3aa20222ce37264532baa7b40cb
parent9002108551a8f6bb0c7846c41caf3ae897078352 (diff)
downloaddnsmasq-2d8905dafd41330d661f2e6e78f19b4199ce9e6b.tar.gz
Allow domain names as well is IP addresses in --server options.
-rw-r--r--CHANGELOG12
-rw-r--r--man/dnsmasq.810
-rw-r--r--src/dbus.c8
-rw-r--r--src/dnsmasq.h13
-rw-r--r--src/option.c310
5 files changed, 267 insertions, 86 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 7c9ca09..1090589 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -34,8 +34,18 @@ version 2.88
Add --no-round-robin option.
+ Allow domain names as well as IP addresses when specifying
+ upstream DNS servers. There are some gotchas associated with this,
+ (it will mysteriously fail to work if the dnsmasq instance
+ being started is in the path from the system resolver to the DNS)
+ and a seemingly sensible configuration like
+ --server=domain.name@1.2.3.4 is unactionable if domain.name
+ only resolves to an IPv6 address). There are, however,
+ cases where is can be useful. Thanks to Dominik Derigs for
+ the patch.
+
-version 2.87
+(version 2.87
Allow arbitrary prefix lengths in --rev-server and
--domain=....,local
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 09c6a17..a2dde82 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -462,8 +462,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
@@ -531,6 +531,12 @@ 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.
+If the system's configuration is such that the system resolver sends DNS queries
+through the dnsmasq instance which is starting up then this will time-out and fail.
.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 96abdf4..97dcc40 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
@@ -1292,6 +1293,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);
@@ -1467,8 +1476,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 811d31f..163421a 100644
--- a/src/option.c
+++ b/src/option.c
@@ -855,117 +855,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)
{
- addr->in.sin_port = htons(serv_port);
- addr->sa.sa_family = source_addr->sa.sa_family = AF_INET;
+ 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 or TCP connections. */
+ hints.ai_socktype = 0;
+
+ /* Get address associated with this hostname */
+ ecode = getaddrinfo(arg, NULL, &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)
+ {
+ 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);
+ sdetails->source_addr->in.sin_addr.s_addr = INADDR_ANY;
+ sdetails->source_addr->in.sin_port = htons(daemon->query_port);
- if (source)
+ 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;
@@ -976,10 +1096,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)
@@ -1035,10 +1161,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)
@@ -2800,6 +2932,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. */
@@ -2832,36 +2970,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;
+ }
+
+ if (!add_update_server(flags, sdetails.addr, sdetails.source_addr, sdetails.interface, domain, &addr))
+ ret_err(gen_err);
+
+ if (!lastdomain || domain == lastdomain)
+ break;
- /* address=/#/ matches the same as without domain */
- if (option == 'A' && domain[0] == '#' && domain[1] == 0)
- domain[0] = 0;
+ 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;
}