Discussion:
[Dnsmasq-discuss] [PATCH] Support --server syntax in resolv-file
Kristian Evensen
2017-03-23 18:16:39 UTC
Permalink
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 ***@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 <***@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
Kristian Evensen
2017-04-06 18:10:47 UTC
Permalink
Hi,

A gentle ping for this patch, in case it got lost when moving house, etc. :)

Best regards,
Kristian
Simon Kelley
2017-04-07 21:27:31 UTC
Permalink
The overriding objection to this is that it adds to the syntax and
semantics of the resolv-file format, but dnsmasq doesn't "own" that
format: it's actually a libc configuration file, and dnsmasq takes
advantage of the fact that the format is "well known" to extract useful
information from it. If you start adding extra fields to
/etc/resolv.conf then the c-library will get upset.

I understand the desire to be able to specify resolvers dynamically with
the full set of source-address and routing options; that's actually
already available, and has been for a long time, using the DBus
interface to dnsmasq, which includes the "SetDomainServers" method,
which takes strings identical to argument to --server. I've not looked
at the code, but your previous patch to allow binding both IP and
interface should have automatically added that feature to
SetDomainServers. (if it didn't then I'd certainly take a patch to
correct that.)


Cheers,

Simon.
Post by Kristian Evensen
Automatically specifying which source address and interface to be used for
communicating with a given DNS server is very convenient on multihomed hosts.
* 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
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.
Kristian Evensen
2017-04-08 05:01:50 UTC
Permalink
Hi Simon,
Post by Simon Kelley
I understand the desire to be able to specify resolvers dynamically with
the full set of source-address and routing options; that's actually
already available, and has been for a long time, using the DBus
interface to dnsmasq, which includes the "SetDomainServers" method,
which takes strings identical to argument to --server. I've not looked
at the code, but your previous patch to allow binding both IP and
interface should have automatically added that feature to
SetDomainServers. (if it didn't then I'd certainly take a patch to
correct that.)
Thanks for pointing me to the DBus-interface, that does indeed sound like
the correct approach. I will test if SetDomaimServers automatically support
the server/interface/adress syntax, and provide a patch if not.

Have a nice weekend,
Kristian
Post by Simon Kelley
Cheers,
Simon.
Post by Kristian Evensen
Automatically specifying which source address and interface to be used
for
Post by Kristian Evensen
communicating with a given DNS server is very convenient on multihomed
hosts.
Post by Kristian Evensen
* Several mobile broadband providers hand out private IP-addresses, but
the DNS
Post by Kristian Evensen
servers are global. Unless special routing rules are added, then the
default
Post by Kristian Evensen
route will be used for resolving domains. This is not ideal, as it might
lead to
Post by Kristian Evensen
higher latencies for replies, or an additional cost to the user if DNS
requests
Post by Kristian Evensen
to the "local" servers are free.
* Several mobile broadband devices act as small routers, and some of the
most
Post by Kristian Evensen
popular types only hand out the same IP, DNS server, etc. To make
matters worse,
Post by Kristian Evensen
if these devices loose connectivity, they will highjack any DNS request
and
Post by Kristian Evensen
reply with its own IP. If you have multiple of these devices, you risk
being
Post by Kristian Evensen
stuck without working DNS as all requests might be forwarded to the
disconnected
Post by Kristian Evensen
device. Adding support for binding to interface and IP will make sure
that
Post by Kristian Evensen
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
Post by Kristian Evensen
the --server option. This patch adds support for specifying which source
address
Post by Kristian Evensen
and/or interface to use for a server in the resolv-file, using the same
syntax
Post by Kristian Evensen
as for --server. For example, in order to specify that source ip
100.76.125.47
Post by Kristian Evensen
and interface wwan1 should be used to communicate with server
213.158.199.1, the
Post by Kristian Evensen
Since the syntax is not standard, the --multihomed-resolver command line
option
Post by Kristian Evensen
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
Post by Kristian Evensen
have factored out this parsing into a separate function. parse_server()
is
Post by Kristian Evensen
converted to use this function.
_______________________________________________
Dnsmasq-discuss mailing list
http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss
Kristian Evensen
2017-04-10 08:34:35 UTC
Permalink
Hi Simon,
Post by Simon Kelley
The overriding objection to this is that it adds to the syntax and
semantics of the resolv-file format, but dnsmasq doesn't "own" that
format: it's actually a libc configuration file, and dnsmasq takes
advantage of the fact that the format is "well known" to extract useful
information from it. If you start adding extra fields to
/etc/resolv.conf then the c-library will get upset.
I have been thinking some more about this. The reason for locking the
support for server-strings in the resolv behind a command line option,
was to avoid what you are describing here - compatibility issues with
the existing resolv-file format. I assume that if anyone enables the
option, they know what they are doing and what implications it might
have. I should maybe have explained this better in either the commit
or the addition to the man-page.
Post by Simon Kelley
I understand the desire to be able to specify resolvers dynamically with
the full set of source-address and routing options; that's actually
already available, and has been for a long time, using the DBus
interface to dnsmasq, which includes the "SetDomainServers" method,
which takes strings identical to argument to --server. I've not looked
at the code, but your previous patch to allow binding both IP and
interface should have automatically added that feature to
SetDomainServers. (if it didn't then I'd certainly take a patch to
correct that.)
I did a quick test and it seems that specifying servers (with the
additional interface/ip-information) using the DBus-interface works
fine. However, this does not help on systems not using DBus (like
OpenWRT/LEDE). Do you have any suggestions for an acceptable way to
implement this feature, without requiring the use DBus?

Thanks in advance for the help,
Kristian
Vladislav Grishenko
2017-04-10 11:53:06 UTC
Permalink
Hi Kristian,

FYI, changing resolv.conf format could lead libc resolver to fail, so it's quite dangerous change.
As I understand, you want dynamic DNS servers update with additional info (interface/src ip binding).
With no DBUS, can't it be done with --servers-file option (available since 2.69)?
This files allow full format of --server & --rev-server and are reread on SIGHUP, polling is not supported at the moment.

Best Regards, Vladislav Grishenko

-----Original Message-----
From: Dnsmasq-discuss [mailto:dnsmasq-discuss-***@lists.thekelleys.org.uk] On Behalf Of Kristian Evensen
Sent: Monday, April 10, 2017 1:35 PM
To: Simon Kelley <***@thekelleys.org.uk>
Cc: dnsmasq-***@lists.thekelleys.org.uk
Subject: Re: [Dnsmasq-discuss] [PATCH] Support --server syntax in resolv-file

Hi Simon,
Post by Simon Kelley
The overriding objection to this is that it adds to the syntax and
semantics of the resolv-file format, but dnsmasq doesn't "own" that
format: it's actually a libc configuration file, and dnsmasq takes
advantage of the fact that the format is "well known" to extract
useful information from it. If you start adding extra fields to
/etc/resolv.conf then the c-library will get upset.
I have been thinking some more about this. The reason for locking the support for server-strings in the resolv behind a command line option, was to avoid what you are describing here - compatibility issues with the existing resolv-file format. I assume that if anyone enables the option, they know what they are doing and what implications it might have. I should maybe have explained this better in either the commit or the addition to the man-page.
Post by Simon Kelley
I understand the desire to be able to specify resolvers dynamically
with the full set of source-address and routing options; that's
actually already available, and has been for a long time, using the
DBus interface to dnsmasq, which includes the "SetDomainServers"
method, which takes strings identical to argument to --server. I've
not looked at the code, but your previous patch to allow binding both
IP and interface should have automatically added that feature to
SetDomainServers. (if it didn't then I'd certainly take a patch to
correct that.)
I did a quick test and it seems that specifying servers (with the additional interface/ip-information) using the DBus-interface works fine. However, this does not help on systems not using DBus (like OpenWRT/LEDE). Do you have any suggestions for an acceptable way to implement this feature, without requiring the use DBus?

Thanks in advance for the help,
Kristian
Kristian Evensen
2017-04-10 11:56:21 UTC
Permalink
Hi,

On Mon, Apr 10, 2017 at 1:53 PM, Vladislav Grishenko
Post by Vladislav Grishenko
FYI, changing resolv.conf format could lead libc resolver to fail, so it's quite dangerous change.
As I understand, you want dynamic DNS servers update with additional info (interface/src ip binding).
With no DBUS, can't it be done with --servers-file option (available since 2.69)?
This files allow full format of --server & --rev-server and are reread on SIGHUP, polling is not supported at the moment.
Thanks a lot for the pointer to this command line option. It looks
like the perfect solution to my problem :)

-Kristian

Loading...