From b7cf754f6f621f5b12d6dbd4532bf6ee21f639bc Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 4 Mar 2021 16:54:14 +0000 Subject: [PATCH] Add --dynamic-host option. A and AAAA records which take their network part from the network of a local interface. Useful for routers with dynamically prefixes. --- CHANGELOG | 11 +++-- man/dnsmasq.8 | 10 ++++- src/dnsmasq.h | 9 +++- src/network.c | 117 ++++++++++++++++++++++++++++++++++++++++---------- src/option.c | 67 +++++++++++++++++++++-------- 5 files changed, 170 insertions(+), 44 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a5b2c44..c7a9649 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,13 +14,18 @@ version 2.85 sorts before v2.83test1. This fixes the problem which lead to 2.84 announcing itself as 2.84rc2. - Avoid treating a --dhcp-host which has an IPv6 address + Avoid treating a --dhcp-host which has an IPv6 address as eligable for use with DHCPv4 on the grounds that it has no address, and vice-versa. Thanks to Viktor Papp for spotting the problem. (This bug was fixed was back in 2.67, and then regessed in 2.81). - - + + Add --dynamic-host option: A and AAAA records which take their + network part from the network of a local interface. Useful + for routers with dynamically prefixes. Thanks + to Fred F for the suggestion. + + version 2.84 Fix a problem, introduced in 2.83, which could see DNS replies being sent via the wrong socket. On machines running both diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 86a6634..8d34908 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -296,7 +296,7 @@ option requires non-standard networking APIs and it is only available under Linux. On other platforms it falls-back to \fB--bind-interfaces\fP mode. .TP .B \-y, --localise-queries -Return answers to DNS queries from /etc/hosts and \fB--interface-name\fP which depend on the interface over which the query was +Return answers to DNS queries from /etc/hosts and \fB--interface-name\fP and \fB--dynamic-host\fP which depend on the interface over which the query was received. If a name has more than one address associated with it, and at least one of those addresses is on the same subnet as the interface to which the query was sent, then return only the @@ -591,6 +591,12 @@ If the time-to-live is given, it overrides the default, which is zero or the value of \fB--local-ttl\fP. The value is a positive integer and gives the time-to-live in seconds. .TP +.B --dynamic-host=,[IPv4-address],[IPv6-address], +Add A, AAAA and PTR records to the DNS in the same subnet as the specified interface. The address is derived from the network part of each address associated with the interface, and the host part from the specified address. For example +.B --dynamic-host=example.com,0.0.0.8,eth0 +will, when eth0 has the address 192.168.78.x and netmask 255.255.255.0 give the +name example.com an A record for 192.168.78.8. The same principle applies to IPv6 addresses. Note that if an interface has more than one address, more than one A or AAAA record will be created. The TTL of the records is always zero, and any changes to interface addresses will be immediately reflected in them. +.TP .B \-Y, --txt-record=[[,],] Return a TXT DNS record. The value of TXT record is a set of strings, so any number may be included, delimited by commas; use quotes to put @@ -2380,6 +2386,8 @@ IPv4 and IPv6 addresses from /etc/hosts (and .B --host-record and .B --interface-name +and +.B ---dynamic-host provided the address falls into one of the subnets specified in the .B --auth-zone. .PP diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 7b36608..1c680e3 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -423,10 +423,17 @@ struct host_record { struct host_record *next; }; +#define IN4 1 +#define IN6 2 +#define INP4 4 +#define INP6 8 + struct interface_name { char *name; /* domain name */ char *intr; /* interface name */ - int family; /* AF_INET, AF_INET6 or zero for both */ + int flags; + struct in_addr proto4; + struct in6_addr proto6; struct addrlist *addr; struct interface_name *next; }; diff --git a/src/network.c b/src/network.c index 3b4ac97..61a05c2 100644 --- a/src/network.c +++ b/src/network.c @@ -348,36 +348,109 @@ static int iface_allowed(struct iface_param *param, int if_index, char *label, /* Update addresses from interface_names. These are a set independent of the set we're listening on. */ for (int_name = daemon->int_names; int_name; int_name = int_name->next) - if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0 && - (addr->sa.sa_family == int_name->family || int_name->family == 0)) + if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0) { - if (param->spare) + struct addrlist *lp; + + al = NULL; + + if (addr->sa.sa_family == AF_INET && (int_name->flags & (IN4 | INP4))) { - al = param->spare; - param->spare = al->next; + struct in_addr newaddr = addr->in.sin_addr; + + if (int_name->flags & INP4) + { + if (netmask.s_addr == 0xffff) + continue; + + newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) | + (int_name->proto4.s_addr & ~netmask.s_addr); + } + + /* check for duplicates. */ + for (lp = int_name->addr; lp; lp = lp->next) + if (lp->flags == 0 && lp->addr.addr4.s_addr == newaddr.s_addr) + break; + + if (!lp) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->flags = 0; + al->addr.addr4 = newaddr; + } + } + } + + if (addr->sa.sa_family == AF_INET6 && (int_name->flags & (IN6 | INP6))) + { + struct in6_addr newaddr = addr->in6.sin6_addr; + + if (int_name->flags & INP6) + { + int i; + + /* No sense in doing /128. */ + if (prefixlen == 128) + continue; + + for (i = 0; i < 16; i++) + { + int bits = ((i+1)*8) - prefixlen; + + if (bits >= 8) + newaddr.s6_addr[i] = int_name->proto6.s6_addr[i]; + else if (bits >= 0) + { + unsigned char mask = 0xff << bits; + newaddr.s6_addr[i] = + (addr->in6.sin6_addr.s6_addr[i] & mask) | + (int_name->proto6.s6_addr[i] & ~mask); + } + } + } + + /* check for duplicates. */ + for (lp = int_name->addr; lp; lp = lp->next) + if ((lp->flags & ADDRLIST_IPV6) && + IN6_ARE_ADDR_EQUAL(&lp->addr.addr6, &newaddr)) + break; + + if (!lp) + { + if (param->spare) + { + al = param->spare; + param->spare = al->next; + } + else + al = whine_malloc(sizeof(struct addrlist)); + + if (al) + { + al->flags = ADDRLIST_IPV6; + al->addr.addr6 = newaddr; + + /* Privacy addresses and addresses still undergoing DAD and deprecated addresses + don't appear in forward queries, but will in reverse ones. */ + if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE))) + al->flags |= ADDRLIST_REVONLY; + } + } } - else - al = whine_malloc(sizeof(struct addrlist)); if (al) { al->next = int_name->addr; int_name->addr = al; - - if (addr->sa.sa_family == AF_INET) - { - al->addr.addr4 = addr->in.sin_addr; - al->flags = 0; - } - else - { - al->addr.addr6 = addr->in6.sin6_addr; - al->flags = ADDRLIST_IPV6; - /* Privacy addresses and addresses still undergoing DAD and deprecated addresses - don't appear in forward queries, but will in reverse ones. */ - if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE))) - al->flags |= ADDRLIST_REVONLY; - } } } } diff --git a/src/option.c b/src/option.c index 0a72406..5b8b2ed 100644 --- a/src/option.c +++ b/src/option.c @@ -168,6 +168,7 @@ struct myoption { #define LOPT_SINGLE_PORT 359 #define LOPT_SCRIPT_TIME 360 #define LOPT_PXE_VENDOR 361 +#define LOPT_DYNHOST 362 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -341,6 +342,7 @@ static const struct myoption opts[] = { "dumpfile", 1, 0, LOPT_DUMPFILE }, { "dumpmask", 1, 0, LOPT_DUMPMASK }, { "dhcp-ignore-clid", 0, 0, LOPT_IGNORE_CLID }, + { "dynamic-host", 1, 0, LOPT_DYNHOST }, { NULL, 0, 0, 0 } }; @@ -491,6 +493,7 @@ static struct { { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL }, { LOPT_DUID, ARG_ONE, ",", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL }, { LOPT_HOST_REC, ARG_DUP, ",
[,]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL }, + { LOPT_DYNHOST, ARG_DUP, ",[][,],", gettext_noop("Specify host record in interface subnet"), NULL }, { LOPT_CAA, ARG_DUP, ",,,", gettext_noop("Specify certification authority authorization record"), NULL }, { LOPT_RR, ARG_DUP, ",,[]", gettext_noop("Specify arbitrary DNS resource record"), NULL }, { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL }, @@ -4075,36 +4078,66 @@ err: } case LOPT_INTNAME: /* --interface-name */ + case LOPT_DYNHOST: /* --dynamic-host */ { struct interface_name *new, **up; - char *domain = NULL; - - comma = split(arg); + char *domain = arg; - if (!comma || !(domain = canonicalise_opt(arg))) - ret_err(_("bad interface name")); + arg = split(arg); new = opt_malloc(sizeof(struct interface_name)); - new->next = NULL; - new->addr = NULL; + memset(new, 0, sizeof(struct interface_name)); + new->flags = IN4 | IN6; /* Add to the end of the list, so that first name of an interface is used for PTR lookups. */ for (up = &daemon->int_names; *up; up = &((*up)->next)); *up = new; - new->name = domain; - new->family = 0; - arg = split_chr(comma, '/'); - if (arg) + + while ((comma = split(arg))) { - if (strcmp(arg, "4") == 0) - new->family = AF_INET; - else if (strcmp(arg, "6") == 0) - new->family = AF_INET6; + if (inet_pton(AF_INET, arg, &new->proto4)) + new->flags |= INP4; + else if (inet_pton(AF_INET6, arg, &new->proto6)) + new->flags |= INP6; + else + break; + + arg = comma; + } + + if ((comma = split_chr(arg, '/'))) + { + if (strcmp(comma, "4") == 0) + new->flags &= ~IN6; + else if (strcmp(comma, "6") == 0) + new->flags &= ~IN4; else ret_err_free(gen_err, new); - } - new->intr = opt_string_alloc(comma); + } + + new->intr = opt_string_alloc(arg); + + if (option == LOPT_DYNHOST) + { + if (!(new->flags & (INP4 | INP6))) + ret_err(_("missing address in dynamic host")); + + if (!(new->flags & IN4) || !(new->flags & IN6)) + arg = NULL; /* provoke error below */ + + new->flags &= ~(IN4 | IN6); + } + else + { + if (new->flags & (INP4 | INP6)) + arg = NULL; /* provoke error below */ + } + + if (!domain || !arg || !(new->name = canonicalise_opt(domain))) + ret_err(option == LOPT_DYNHOST ? + _("bad dynamic host") : _("bad interface name")); + break; }