From c491296241396d0144750f178ffd5c0e0b089a80 Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Thu, 6 Feb 2020 22:09:30 +0000 Subject: [PATCH] Extend 79aba0f10ad0157fb4f48afbbcb03f094caff97a for multiple IPv6 addresses. (cherry picked from commit 137286e9baecf6a3ba97722ef1b49c851b531810) --- man/dnsmasq.8 | 7 +- src/dhcp-common.c | 55 ++++-- src/dhcp6.c | 13 +- src/dnsmasq.h | 13 +- src/option.c | 416 +++++++++++++++++++++++++--------------------- src/rfc3315.c | 88 +++++----- 6 files changed, 330 insertions(+), 262 deletions(-) diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 95cd3ca..d1caeed 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1008,14 +1008,15 @@ allowed to specify the client ID as text, like this: A single .B --dhcp-host -may contain an IPv4 address or an IPv6 address, or both. IPv6 addresses must be bracketed by square brackets thus: +may contain an IPv4 address or one or more IPv6 addresses, or both. IPv6 addresses must be bracketed by square brackets thus: .B --dhcp-host=laptop,[1234::56] IPv6 addresses may contain only the host-identifier part: .B --dhcp-host=laptop,[::56] in which case they act as wildcards in constructed dhcp ranges, with -the appropriate network part inserted. For IPv6, the address may include a prefix length: +the appropriate network part inserted. For IPv6, an address may include a prefix length: .B --dhcp-host=laptop,[1234:50/126] -which (in this case) specifies four addresses, 1234::50 to 1234::53. This is useful +which (in this case) specifies four addresses, 1234::50 to 1234::53. This (an the ability +to specify multiple addresses) is useful when a host presents either a consistent name or hardware-ID, but varying DUIDs, since it allows dnsmasq to honour the static address allocation but assign a different adddress for each DUID. This typically occurs when chain netbooting, as each stage of the chain gets in turn allocates an address. diff --git a/src/dhcp-common.c b/src/dhcp-common.c index 99d34c8..2933343 100644 --- a/src/dhcp-common.c +++ b/src/dhcp-common.c @@ -271,26 +271,35 @@ static int is_config_in_context(struct dhcp_context *context, struct dhcp_config { if (!context) /* called via find_config() from lease_update_from_configs() */ return 1; - - if (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6))) - return 1; #ifdef HAVE_DHCP6 - if ((context->flags & CONTEXT_V6) && (config->flags & CONFIG_WILDCARD)) - return 1; -#endif + if (context->flags & CONTEXT_V6) + { + struct addrlist *addr_list; - for (; context; context = context->current) -#ifdef HAVE_DHCP6 - if (context->flags & CONTEXT_V6) - { - if ((config->flags & CONFIG_ADDR6) && is_same_net6(&config->addr6, &context->start6, context->prefix)) - return 1; - } - else + if (!(config->flags & CONFIG_ADDR6)) + return 1; + + for (; context; context = context->current) + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + { + if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) + return 1; + + if (is_same_net6(&addr_list->addr.addr6, &context->start6, context->prefix)) + return 1; + } + } + else #endif - if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) + { + if (!(config->flags & CONFIG_ADDR)) return 1; + + for (; context; context = context->current) + if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) + return 1; + } return 0; } @@ -420,9 +429,19 @@ void dhcp_update_configs(struct dhcp_config *configs) if (prot == AF_INET6 && (!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr.addr.addr6)) || conf_tmp == config)) { - memcpy(&config->addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ); - config->flags |= CONFIG_ADDR6 | CONFIG_ADDR_HOSTS; - config->flags &= ~CONFIG_PREFIX; + /* host must have exactly one address if comming from /etc/hosts. */ + if (!config->addr6 && (config->addr6 = whine_malloc(sizeof(struct addrlist)))) + { + config->addr6->next = NULL; + config->addr6->flags = 0; + } + + if (config->addr6 && !config->addr6->next && !(config->addr6->flags & (ADDRLIST_WILDCARD|ADDRLIST_PREFIX))) + { + memcpy(&config->addr6->addr.addr6, &crec->addr.addr6, IN6ADDRSZ); + config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS; + } + continue; } #endif diff --git a/src/dhcp6.c b/src/dhcp6.c index 11a9d83..4e28e61 100644 --- a/src/dhcp6.c +++ b/src/dhcp6.c @@ -389,10 +389,15 @@ struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct struct dhcp_config *config; for (config = configs; config; config = config->next) - if ((config->flags & CONFIG_ADDR6) && - (!net || is_same_net6(&config->addr6, net, prefix)) && - is_same_net6(&config->addr6, addr, (config->flags & CONFIG_PREFIX) ? config->prefix : 128)) - return config; + if (config->flags & CONFIG_ADDR6) + { + struct addrlist *addr_list; + + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + if ((!net || is_same_net6(&addr_list->addr.addr6, net, prefix) || ((addr_list->flags & ADDRLIST_WILDCARD) && prefix == 64)) && + is_same_net6(&addr_list->addr.addr6, addr, (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128)) + return config; + } return NULL; } diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 86b8168..08484ba 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -350,9 +350,11 @@ struct ds_config { struct ds_config *next; }; -#define ADDRLIST_LITERAL 1 -#define ADDRLIST_IPV6 2 -#define ADDRLIST_REVONLY 4 +#define ADDRLIST_LITERAL 1 +#define ADDRLIST_IPV6 2 +#define ADDRLIST_REVONLY 4 +#define ADDRLIST_PREFIX 8 +#define ADDRLIST_WILDCARD 16 struct addrlist { struct all_addr addr; @@ -774,8 +776,7 @@ struct dhcp_config { char *hostname, *domain; struct dhcp_netid_list *netid; #ifdef HAVE_DHCP6 - struct in6_addr addr6; - int prefix; + struct addrlist *addr6; #endif struct in_addr addr; time_t decline_time; @@ -797,8 +798,6 @@ struct dhcp_config { #define CONFIG_DECLINED 1024 /* address declined by client */ #define CONFIG_BANK 2048 /* from dhcp hosts file */ #define CONFIG_ADDR6 4096 -#define CONFIG_WILDCARD 8192 -#define CONFIG_PREFIX 32768 /* addr6 is a set, size given by prefix */ struct dhcp_opt { int opt, len, flags; diff --git a/src/option.c b/src/option.c index 389eb02..2bbb11b 100644 --- a/src/option.c +++ b/src/option.c @@ -988,6 +988,19 @@ static void dhcp_config_free(struct dhcp_config *config) free(list); } +#ifdef HAVE_DHCP6 + if (config->flags & CONFIG_ADDR6) + { + struct addrlist *addr, *tmp; + + for (addr = config->addr6; addr; addr = tmp) + { + tmp = addr->next; + free(addr); + } + } +#endif + if (config->flags & CONFIG_NAME) free(config->hostname); @@ -995,6 +1008,11 @@ static void dhcp_config_free(struct dhcp_config *config) } + + + + + /* This is too insanely large to keep in-line in the switch */ static int parse_dhcp_opt(char *errstr, char *arg, int flags) { @@ -3053,8 +3071,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma case LOPT_BANK: case 'G': /* --dhcp-host */ { - int j, k = 0; - char *a[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; struct dhcp_config *new; struct in_addr in; @@ -3064,203 +3080,227 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma new->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0; new->hwaddr = NULL; new->netid = NULL; + new->addr6 = NULL; - if ((a[0] = arg)) - for (k = 1; k < 7; k++) - if (!(a[k] = split(a[k-1]))) - break; - - for (j = 0; j < k; j++) - if (strchr(a[j], ':')) /* ethernet address, netid or binary CLID */ - { - char *arg = a[j]; - - if ((arg[0] == 'i' || arg[0] == 'I') && - (arg[1] == 'd' || arg[1] == 'D') && - arg[2] == ':') - { - if (arg[3] == '*') - new->flags |= CONFIG_NOCLID; - else - { - int len; - arg += 3; /* dump id: */ - if (strchr(arg, ':')) - len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL); - else - { - unhide_metas(arg); - len = (int) strlen(arg); - } - - if (len == -1) - ret_err(_("bad hex constant")); - else if ((new->clid = opt_malloc(len))) - { - new->flags |= CONFIG_CLID; - new->clid_len = len; - memcpy(new->clid, arg, len); - } - } - } - /* dhcp-host has strange backwards-compat needs. */ - else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg) - { - struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); - struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list)); - newtag->net = opt_malloc(strlen(arg + 4) + 1); - newlist->next = new->netid; - new->netid = newlist; - newlist->list = newtag; - strcpy(newtag->net, arg+4); - unhide_metas(newtag->net); - } - else if (strstr(arg, "tag:") == arg) - ret_err(_("cannot match tags in --dhcp-host")); + while (arg) + { + comma = split(arg); + if (strchr(arg, ':')) /* ethernet address, netid or binary CLID */ + { + if ((arg[0] == 'i' || arg[0] == 'I') && + (arg[1] == 'd' || arg[1] == 'D') && + arg[2] == ':') + { + if (arg[3] == '*') + new->flags |= CONFIG_NOCLID; + else + { + int len; + arg += 3; /* dump id: */ + if (strchr(arg, ':')) + len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL); + else + { + unhide_metas(arg); + len = (int) strlen(arg); + } + + if (len == -1) + { + dhcp_config_free(new); + ret_err(_("bad hex constant")); + } + else if ((new->clid = opt_malloc(len))) + { + new->flags |= CONFIG_CLID; + new->clid_len = len; + memcpy(new->clid, arg, len); + } + } + } + /* dhcp-host has strange backwards-compat needs. */ + else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg) + { + struct dhcp_netid *newtag = opt_malloc(sizeof(struct dhcp_netid)); + struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list)); + newtag->net = opt_malloc(strlen(arg + 4) + 1); + newlist->next = new->netid; + new->netid = newlist; + newlist->list = newtag; + strcpy(newtag->net, arg+4); + unhide_metas(newtag->net); + } + else if (strstr(arg, "tag:") == arg) + { + + dhcp_config_free(new); + ret_err(_("cannot match tags in --dhcp-host")); + } #ifdef HAVE_DHCP6 - else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') - { - char *pref; - - arg[strlen(arg)-1] = 0; - arg++; - pref = split_chr(arg, '/'); - - if (!inet_pton(AF_INET6, arg, &new->addr6)) - ret_err(_("bad IPv6 address")); - - if (pref) - { - u64 addrpart = addr6part(&new->addr6); - - if (!atoi_check(pref, &new->prefix) || - new->prefix > 128 || - (((1<<(128-new->prefix))-1) & addrpart) != 0) - { - dhcp_config_free(new); - ret_err(_("bad IPv6 prefix")); - } - - new->flags |= CONFIG_PREFIX; - } - - for (i= 0; i < 8; i++) - if (new->addr6.s6_addr[i] != 0) - break; + else if (arg[0] == '[' && arg[strlen(arg)-1] == ']') + { + char *pref; + struct in6_addr in6; + struct addrlist *new_addr; + + arg[strlen(arg)-1] = 0; + arg++; + pref = split_chr(arg, '/'); + + if (!inet_pton(AF_INET6, arg, &in6)) + { + dhcp_config_free(new); + ret_err(_("bad IPv6 address")); + } - /* set WILDCARD if network part all zeros */ - if (i == 8) - new->flags |= CONFIG_WILDCARD; + new_addr = opt_malloc(sizeof(struct addrlist)); + new_addr->next = new->addr6; + new_addr->flags = 0; + new_addr->addr.addr6 = in6; + new->addr6 = new_addr; + + if (pref) + { + u64 addrpart = addr6part(&in6); + + if (!atoi_check(pref, &new_addr->prefixlen) || + new_addr->prefixlen > 128 || + (((1<<(128-new_addr->prefixlen))-1) & addrpart) != 0) + { + dhcp_config_free(new); + ret_err(_("bad IPv6 prefix")); + } + + new_addr->flags |= ADDRLIST_PREFIX; + } - new->flags |= CONFIG_ADDR6; - } + for (i= 0; i < 8; i++) + if (in6.s6_addr[i] != 0) + break; + + /* set WILDCARD if network part all zeros */ + if (i == 8) + new_addr->flags |= ADDRLIST_WILDCARD; + + new->flags |= CONFIG_ADDR6; + } #endif - else - { - struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config)); - if ((newhw->hwaddr_len = parse_hex(a[j], newhw->hwaddr, DHCP_CHADDR_MAX, - &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1) - ret_err(_("bad hex constant")); - else - { - - newhw->next = new->hwaddr; - new->hwaddr = newhw; - } - } - } - else if (strchr(a[j], '.') && (inet_pton(AF_INET, a[j], &in) > 0)) - { - struct dhcp_config *configs; - - new->addr = in; - new->flags |= CONFIG_ADDR; - - /* If the same IP appears in more than one host config, then DISCOVER - for one of the hosts will get the address, but REQUEST will be NAKed, - since the address is reserved by the other one -> protocol loop. */ - for (configs = daemon->dhcp_conf; configs; configs = configs->next) - if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr) + else { - sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); - return 0; - } - } - else - { - char *cp, *lastp = NULL, last = 0; - int fac = 1, isdig = 0; - - if (strlen(a[j]) > 1) - { - lastp = a[j] + strlen(a[j]) - 1; - last = *lastp; - switch (last) + struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config)); + if ((newhw->hwaddr_len = parse_hex(arg, newhw->hwaddr, DHCP_CHADDR_MAX, + &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1) + { + free(newhw); + dhcp_config_free(new); + ret_err(_("bad hex constant")); + } + else + { + + newhw->next = new->hwaddr; + new->hwaddr = newhw; + } + } + } + else if (strchr(arg, '.') && (inet_pton(AF_INET, arg, &in) > 0)) + { + struct dhcp_config *configs; + + new->addr = in; + new->flags |= CONFIG_ADDR; + + /* If the same IP appears in more than one host config, then DISCOVER + for one of the hosts will get the address, but REQUEST will be NAKed, + since the address is reserved by the other one -> protocol loop. */ + for (configs = daemon->dhcp_conf; configs; configs = configs->next) + if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr) { - case 'w': - case 'W': - fac *= 7; - /* fall through */ - case 'd': - case 'D': - fac *= 24; - /* fall through */ - case 'h': - case 'H': - fac *= 60; - /* fall through */ - case 'm': - case 'M': - fac *= 60; - /* fall through */ - case 's': - case 'S': - *lastp = 0; - } - } - - for (cp = a[j]; *cp; cp++) - if (isdigit((unsigned char)*cp)) - isdig = 1; - else if (*cp != ' ') - break; + sprintf(errstr, _("duplicate dhcp-host IP address %s"), inet_ntoa(in)); + return 0; + } + } + else + { + char *cp, *lastp = NULL, last = 0; + int fac = 1, isdig = 0; + + if (strlen(arg) > 1) + { + lastp = arg + strlen(arg) - 1; + last = *lastp; + switch (last) + { + case 'w': + case 'W': + fac *= 7; + /* fall through */ + case 'd': + case 'D': + fac *= 24; + /* fall through */ + case 'h': + case 'H': + fac *= 60; + /* fall through */ + case 'm': + case 'M': + fac *= 60; + /* fall through */ + case 's': + case 'S': + *lastp = 0; + } + } + + for (cp = arg; *cp; cp++) + if (isdigit((unsigned char)*cp)) + isdig = 1; + else if (*cp != ' ') + break; + + if (*cp) + { + if (lastp) + *lastp = last; + if (strcmp(arg, "infinite") == 0) + { + new->lease_time = 0xffffffff; + new->flags |= CONFIG_TIME; + } + else if (strcmp(arg, "ignore") == 0) + new->flags |= CONFIG_DISABLE; + else + { + if (!(new->hostname = canonicalise_opt(arg)) || + !legal_hostname(new->hostname)) + { + dhcp_config_free(new); + ret_err(_("bad DHCP host name")); + } + + new->flags |= CONFIG_NAME; + new->domain = strip_hostname(new->hostname); + } + } + else if (isdig) + { + new->lease_time = atoi(arg) * fac; + /* Leases of a minute or less confuse + some clients, notably Apple's */ + if (new->lease_time < 120) + new->lease_time = 120; + new->flags |= CONFIG_TIME; + } + } + + arg = comma; + } - if (*cp) - { - if (lastp) - *lastp = last; - if (strcmp(a[j], "infinite") == 0) - { - new->lease_time = 0xffffffff; - new->flags |= CONFIG_TIME; - } - else if (strcmp(a[j], "ignore") == 0) - new->flags |= CONFIG_DISABLE; - else - { - if (!(new->hostname = canonicalise_opt(a[j])) || - !legal_hostname(new->hostname)) - ret_err(_("bad DHCP host name")); - - new->flags |= CONFIG_NAME; - new->domain = strip_hostname(new->hostname); - } - } - else if (isdig) - { - new->lease_time = atoi(a[j]) * fac; - /* Leases of a minute or less confuse - some clients, notably Apple's */ - if (new->lease_time < 120) - new->lease_time = 120; - new->flags |= CONFIG_TIME; - } - } - daemon->dhcp_conf = new; break; } - + case LOPT_TAG_IF: /* --tag-if */ { struct tag_if *new = opt_malloc(sizeof(struct tag_if)); diff --git a/src/rfc3315.c b/src/rfc3315.c index f4f032e..9dc33f9 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -1793,68 +1793,72 @@ static int config_implies(struct dhcp_config *config, struct dhcp_context *conte { int prefix; struct in6_addr wild_addr; - + struct addrlist *addr_list; + if (!config || !(config->flags & CONFIG_ADDR6)) return 0; - prefix = (config->flags & CONFIG_PREFIX) ? config->prefix : 128; - wild_addr = config->addr6; - - if (!is_same_net6(&context->start6, addr, context->prefix)) - return 0; - - if ((config->flags & CONFIG_WILDCARD)) + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) { - if (context->prefix != 64) - return 0; + prefix = (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128; + wild_addr = addr_list->addr.addr6; + + if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) + { + wild_addr = context->start6; + setaddr6part(&wild_addr, addr6part(&addr_list->addr.addr6)); + } + else if (!is_same_net6(&context->start6, addr, context->prefix)) + continue; - wild_addr = context->start6; - setaddr6part(&wild_addr, addr6part(&config->addr6)); + if (is_same_net6(&wild_addr, addr, prefix)) + return 1; } - if (is_same_net6(&wild_addr, addr, prefix)) - return 1; - return 0; } static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state) { u64 addrpart; - + struct addrlist *addr_list; + if (!config || !(config->flags & CONFIG_ADDR6)) return 0; - addrpart = addr6part(&config->addr6); - - if ((config->flags & CONFIG_WILDCARD)) + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) { - if (context->prefix != 64) - return 0; + addrpart = addr6part(&addr_list->addr.addr6); + + if ((addr_list->flags & ADDRLIST_WILDCARD)) + { + if (context->prefix != 64) + continue; - *addr = context->start6; - setaddr6part(addr, addrpart); + *addr = context->start6; + setaddr6part(addr, addrpart); + } + else if (is_same_net6(&context->start6, &addr_list->addr.addr6, context->prefix)) + *addr = addr_list->addr.addr6; + else + continue; + + while(1) + { + if (check_address(state, addr)) + return 1; + + if (!(addr_list->flags & ADDRLIST_PREFIX)) + break; + + addrpart++; + setaddr6part(addr, addrpart); + if (!is_same_net6(addr, &addr_list->addr.addr6, addr_list->prefixlen)) + break; + } } - else if (is_same_net6(&context->start6, &config->addr6, context->prefix)) - *addr = config->addr6; - else - return 0; - while(1) { - if (check_address(state, addr)) - return 1; - - if (!(config->flags & CONFIG_PREFIX)) - return 0; - - /* config may specify a set of addresses, return first one not in use - by another client */ - - addrpart++; - setaddr6part(addr, addrpart); - if (!is_same_net6(addr, &config->addr6, config->prefix)) - return 0; - } + return 0; } /* Calculate valid and preferred times to send in leases/renewals. -- 2.21.1