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@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.even...@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 _______________________________________________ Dnsmasq-discuss mailing list Dnsmasq-discuss@lists.thekelleys.org.uk http://lists.thekelleys.org.uk/mailman/listinfo/dnsmasq-discuss