diff options
author | Dominik Derigs <dl6er@dl6er.de> | 2022-11-05 11:49:52 +0000 |
---|---|---|
committer | Simon Kelley <simon@thekelleys.org.uk> | 2022-11-05 11:49:52 +0000 |
commit | 2d8905dafd41330d661f2e6e78f19b4199ce9e6b (patch) | |
tree | e70fe39be574f3aa20222ce37264532baa7b40cb | |
parent | 9002108551a8f6bb0c7846c41caf3ae897078352 (diff) | |
download | dnsmasq-2d8905dafd41330d661f2e6e78f19b4199ce9e6b.tar.gz |
Allow domain names as well is IP addresses in --server options.
-rw-r--r-- | CHANGELOG | 12 | ||||
-rw-r--r-- | man/dnsmasq.8 | 10 | ||||
-rw-r--r-- | src/dbus.c | 8 | ||||
-rw-r--r-- | src/dnsmasq.h | 13 | ||||
-rw-r--r-- | src/option.c | 310 |
5 files changed, 267 insertions, 86 deletions
@@ -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 @@ -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; } |