--- embedaddon/dnsmasq/src/dhcp6.c 2013/07/29 19:37:40 1.1.1.1 +++ embedaddon/dnsmasq/src/dhcp6.c 2023/09/27 11:02:07 1.1.1.5 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2022 Simon Kelley This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,16 +18,18 @@ #ifdef HAVE_DHCP6 +#include + struct iface_param { struct dhcp_context *current; - struct in6_addr fallback; + struct in6_addr fallback, ll_addr, ula_addr; int ind, addr_match; }; + static int complete_context6(struct in6_addr *local, int prefix, int scope, int if_index, int flags, unsigned int preferred, unsigned int valid, void *vparam); - static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm); void dhcp6_init(void) @@ -48,22 +50,22 @@ void dhcp6_init(void) !set_ipv6pktinfo(fd)) die (_("cannot create DHCPv6 socket: %s"), NULL, EC_BADNET); - /* When bind-interfaces is set, there might be more than one dnmsasq + /* When bind-interfaces is set, there might be more than one dnsmasq instance binding port 547. That's OK if they serve different networks. - Need to set REUSEADDR|REUSEPORT to make this posible. + Need to set REUSEADDR|REUSEPORT to make this possible. Handle the case that REUSEPORT is defined, but the kernel doesn't support it. This handles the introduction of REUSEPORT on Linux. */ if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) { - int rc = -1, porterr = 0; + int rc = 0; #ifdef SO_REUSEPORT if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 && - errno != ENOPROTOOPT) - porterr = 1; + errno == ENOPROTOOPT) + rc = 0; #endif - if (rc == -1 && !porterr) + if (rc != -1) rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); if (rc == -1) @@ -100,6 +102,10 @@ void dhcp6_packet(time_t now) struct ifreq ifr; struct iname *tmp; unsigned short port; + struct in6_addr dst_addr; + struct in6_addr all_servers; + + memset(&dst_addr, 0, sizeof(dst_addr)); msg.msg_control = control_u.control6; msg.msg_controllen = sizeof(control_u); @@ -112,6 +118,11 @@ void dhcp6_packet(time_t now) if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1) return; +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_DHCPV6, (void *)daemon->dhcp_packet.iov_base, sz, + (union mysockaddr *)&from, NULL, daemon->dhcp6fd); +#endif + for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo) { @@ -122,159 +133,318 @@ void dhcp6_packet(time_t now) p.c = CMSG_DATA(cmptr); if_index = p.p->ipi6_ifindex; + dst_addr = p.p->ipi6_addr; } if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) return; - - for (tmp = daemon->if_except; tmp; tmp = tmp->next) - if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) - return; - for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) - return; - - parm.current = NULL; - parm.ind = if_index; - parm.addr_match = 0; - memset(&parm.fallback, 0, IN6ADDRSZ); - - for (context = daemon->dhcp6; context; context = context->next) - if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0) - { - /* wildcard context for DHCP-stateless only */ - parm.current = context; - context->current = NULL; - } - else - { - /* unlinked contexts are marked by context->current == context */ - context->current = context; - memset(&context->local6, 0, IN6ADDRSZ); - } - - if (!iface_enumerate(AF_INET6, &parm, complete_context6)) - return; - - if (daemon->if_names || daemon->if_addrs) + if (relay_reply6(&from, sz, ifr.ifr_name)) { +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_DHCPV6, (void *)daemon->outpacket.iov_base, save_counter(-1), NULL, + (union mysockaddr *)&from, daemon->dhcp6fd); +#endif - for (tmp = daemon->if_names; tmp; tmp = tmp->next) + while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, + save_counter(-1), 0, (struct sockaddr *)&from, + sizeof(from)))); + } + else + { + struct dhcp_bridge *bridge, *alias; + + for (tmp = daemon->if_except; tmp; tmp = tmp->next) if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) - break; - - if (!tmp && !parm.addr_match) + return; + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + return; + + parm.current = NULL; + parm.ind = if_index; + parm.addr_match = 0; + memset(&parm.fallback, 0, IN6ADDRSZ); + memset(&parm.ll_addr, 0, IN6ADDRSZ); + memset(&parm.ula_addr, 0, IN6ADDRSZ); + + /* If the interface on which the DHCPv6 request was received is + an alias of some other interface (as specified by the + --bridge-interface option), change parm.ind so that we look + for DHCPv6 contexts associated with the aliased interface + instead of with the aliasing one. */ + for (bridge = daemon->bridges; bridge; bridge = bridge->next) + { + for (alias = bridge->alias; alias; alias = alias->next) + if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE)) + { + parm.ind = if_nametoindex(bridge->iface); + if (!parm.ind) + { + my_syslog(MS_DHCP | LOG_WARNING, + _("unknown interface %s in bridge-interface"), + bridge->iface); + return; + } + break; + } + if (alias) + break; + } + + for (context = daemon->dhcp6; context; context = context->next) + if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0) + { + /* wildcard context for DHCP-stateless only */ + parm.current = context; + context->current = NULL; + } + else + { + /* unlinked contexts are marked by context->current == context */ + context->current = context; + memset(&context->local6, 0, IN6ADDRSZ); + } + + /* Ignore requests sent to the ALL_SERVERS multicast address for relay when + we're listening there for DHCPv6 server reasons. */ + inet_pton(AF_INET6, ALL_SERVERS, &all_servers); + + if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers) && + relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now)) return; + + if (!iface_enumerate(AF_INET6, &parm, complete_context6)) + return; + + /* Check for a relay again after iface_enumerate/complete_context has had + chance to fill in relay->iface_index fields. This handles first time through + and any changes in interface config. */ + if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers) && + relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now)) + return; + + if (daemon->if_names || daemon->if_addrs) + { + + for (tmp = daemon->if_names; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + break; + + if (!tmp && !parm.addr_match) + return; + } + + /* May have configured relay, but not DHCP server */ + if (!daemon->doing_dhcp6) + return; + + lease_prune(NULL, now); /* lose any expired leases */ + + port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, + &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now); + + /* The port in the source address of the original request should + be correct, but at least once client sends from the server port, + so we explicitly send to the client port to a client, and the + server port to a relay. */ + if (port != 0) + { + from.sin6_port = htons(port); + +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_DHCPV6, (void *)daemon->outpacket.iov_base, save_counter(-1), + NULL, (union mysockaddr *)&from, daemon->dhcp6fd); +#endif + + while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, + save_counter(-1), 0, (struct sockaddr *)&from, sizeof(from)))); + } + + /* These need to be called _after_ we send DHCPv6 packet, since lease_update_file() + may trigger sending an RA packet, which overwrites our buffer. */ + lease_update_file(now); + lease_update_dns(0); } +} - lease_prune(NULL, now); /* lose any expired leases */ +void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep, time_t now) +{ + /* Receiving a packet from a host does not populate the neighbour + cache, so we send a neighbour discovery request if we can't + find the sender. Repeat a few times in case of packet loss. */ + + struct neigh_packet neigh; + union mysockaddr addr; + int i, maclen; - port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, - sz, IN6_IS_ADDR_MULTICAST(&from.sin6_addr), now); + neigh.type = ND_NEIGHBOR_SOLICIT; + neigh.code = 0; + neigh.reserved = 0; + neigh.target = *client; + /* RFC4443 section-2.3: checksum has to be zero to be calculated */ + neigh.checksum = 0; + + memset(&addr, 0, sizeof(addr)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr.in6.sin6_len = sizeof(struct sockaddr_in6); +#endif + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_port = htons(IPPROTO_ICMPV6); + addr.in6.sin6_addr = *client; + addr.in6.sin6_scope_id = iface; - lease_update_file(now); - lease_update_dns(0); - - /* The port in the source address of the original request should - be correct, but at least once client sends from the server port, - so we explicitly send to the client port to a client, and the - server port to a relay. */ - if (port != 0) + for (i = 0; i < 5; i++) { - from.sin6_port = htons(port); - while (sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, save_counter(0), - 0, (struct sockaddr *)&from, sizeof(from)) == -1 && - retry_send()); + struct timespec ts; + + if ((maclen = find_mac(&addr, mac, 0, now)) != 0) + break; + + while(retry_send(sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, &addr.sa, sizeof(addr)))); + + ts.tv_sec = 0; + ts.tv_nsec = 100000000; /* 100ms */ + nanosleep(&ts, NULL); } -} + *maclenp = maclen; + *mactypep = ARPHRD_ETHER; +} + static int complete_context6(struct in6_addr *local, int prefix, int scope, int if_index, int flags, unsigned int preferred, unsigned int valid, void *vparam) { struct dhcp_context *context; + struct shared_network *share; + struct dhcp_relay *relay; struct iface_param *param = vparam; struct iname *tmp; + int match = !daemon->if_addrs; (void)scope; /* warning */ - if (if_index == param->ind && - !IN6_IS_ADDR_LOOPBACK(local) && - !IN6_IS_ADDR_LINKLOCAL(local) && - !IN6_IS_ADDR_MULTICAST(local)) - { - /* if we have --listen-address config, see if the - arrival interface has a matching address. */ - for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) - if (tmp->addr.sa.sa_family == AF_INET6 && - IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local)) - param->addr_match = 1; + if (if_index != param->ind) + return 1; + + if (IN6_IS_ADDR_LINKLOCAL(local)) + param->ll_addr = *local; + else if (IN6_IS_ADDR_ULA(local)) + param->ula_addr = *local; - /* Determine a globally address on the arrival interface, even - if we have no matching dhcp-context, because we're only - allocating on remote subnets via relays. This - is used as a default for the DNS server option. */ - param->fallback = *local; - - for (context = daemon->dhcp6; context; context = context->next) - { - if ((context->flags & CONTEXT_DHCP) && - !(context->flags & CONTEXT_TEMPLATE) && - prefix == context->prefix && - is_same_net6(local, &context->start6, prefix) && - is_same_net6(local, &context->end6, prefix)) - { - - - /* link it onto the current chain if we've not seen it before */ - if (context->current == context) - { - struct dhcp_context *tmp, **up; - - /* use interface values only for contructed contexts */ - if (!(context->flags & CONTEXT_CONSTRUCTED)) - preferred = valid = 0xffffffff; - else if (flags & IFACE_DEPRECATED) - preferred = 0; - - if (context->flags & CONTEXT_DEPRECATE) - preferred = 0; - - /* order chain, longest preferred time first */ - for (up = ¶m->current, tmp = param->current; tmp; tmp = tmp->current) - if (tmp->preferred <= preferred) - break; - else - up = &tmp->current; - - context->current = *up; - *up = context; - context->local6 = *local; - context->preferred = preferred; - context->valid = valid; - } - } - } - } + if (IN6_IS_ADDR_LOOPBACK(local) || + IN6_IS_ADDR_LINKLOCAL(local) || + IN6_IS_ADDR_MULTICAST(local)) + return 1; + + /* if we have --listen-address config, see if the + arrival interface has a matching address. */ + for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if (tmp->addr.sa.sa_family == AF_INET6 && + IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local)) + match = param->addr_match = 1; + + /* Determine a globally address on the arrival interface, even + if we have no matching dhcp-context, because we're only + allocating on remote subnets via relays. This + is used as a default for the DNS server option. */ + param->fallback = *local; + + for (context = daemon->dhcp6; context; context = context->next) + if ((context->flags & CONTEXT_DHCP) && + !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) && + prefix <= context->prefix && + context->current == context) + { + if (is_same_net6(local, &context->start6, context->prefix) && + is_same_net6(local, &context->end6, context->prefix)) + { + struct dhcp_context *tmp, **up; + + /* use interface values only for constructed contexts */ + if (!(context->flags & CONTEXT_CONSTRUCTED)) + preferred = valid = 0xffffffff; + else if (flags & IFACE_DEPRECATED) + preferred = 0; + + if (context->flags & CONTEXT_DEPRECATE) + preferred = 0; + + /* order chain, longest preferred time first */ + for (up = ¶m->current, tmp = param->current; tmp; tmp = tmp->current) + if (tmp->preferred <= preferred) + break; + else + up = &tmp->current; + + context->current = *up; + *up = context; + context->local6 = *local; + context->preferred = preferred; + context->valid = valid; + } + else + { + for (share = daemon->shared_networks; share; share = share->next) + { + /* IPv4 shared_address - ignore */ + if (share->shared_addr.s_addr != 0) + continue; + + if (share->if_index != 0) + { + if (share->if_index != if_index) + continue; + } + else + { + if (!IN6_ARE_ADDR_EQUAL(&share->match_addr6, local)) + continue; + } + + if (is_same_net6(&share->shared_addr6, &context->start6, context->prefix) && + is_same_net6(&share->shared_addr6, &context->end6, context->prefix)) + { + context->current = param->current; + param->current = context; + context->local6 = *local; + context->preferred = context->flags & CONTEXT_DEPRECATE ? 0 :0xffffffff; + context->valid = 0xffffffff; + } + } + } + } + + if (match) + for (relay = daemon->relay6; relay; relay = relay->next) + if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6)) + relay->iface_index = if_index; + return 1; } -struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr) +struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, struct in6_addr *addr) { struct dhcp_config *config; for (config = configs; config; config = config->next) - if ((config->flags & CONFIG_ADDR6) && - is_same_net6(&config->addr6, net, prefix) && - (prefix == 128 || addr6part(&config->addr6) == addr)) - 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; } -struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, - int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans) +struct dhcp_context *address6_allocate(struct dhcp_context *context, unsigned char *clid, int clid_len, int temp_addr, + unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans) { /* Find a free address: exclude anything in use and anything allocated to a particular hwaddr/clientid/hostname in our configuration. @@ -290,9 +460,13 @@ struct dhcp_context *address6_allocate(struct dhcp_con u64 j; /* hash hwaddr: use the SDBM hashing algorithm. This works - for MAC addresses, let's see how it manages with client-ids! */ - for (j = iaid, i = 0; i < clid_len; i++) - j += clid[i] + (j << 6) + (j << 16) - j; + for MAC addresses, let's see how it manages with client-ids! + For temporary addresses, we generate a new random one each time. */ + if (temp_addr) + j = rand64(); + else + for (j = iaid, i = 0; i < clid_len; i++) + j = clid[i] + (j << 6) + (j << 16) - j; for (pass = 0; pass <= plain_range ? 1 : 0; pass++) for (c = context; c; c = c->current) @@ -302,12 +476,28 @@ struct dhcp_context *address6_allocate(struct dhcp_con continue; else { - if (option_bool(OPT_CONSEC_ADDR)) - /* seed is largest extant lease addr in this context */ - start = lease_find_max_addr6(c) + serial; + if (!temp_addr && option_bool(OPT_CONSEC_ADDR)) + { + /* seed is largest extant lease addr in this context, + skip addresses equal to the number of addresses rejected + by clients. This should avoid the same client being offered the same + address after it has rjected it. */ + start = lease_find_max_addr6(c) + 1 + serial + c->addr_epoch; + if (c->addr_epoch) + c->addr_epoch--; + } else - start = addr6part(&c->start6) + ((j + c->addr_epoch) % (1 + addr6part(&c->end6) - addr6part(&c->start6))); + { + u64 range = 1 + addr6part(&c->end6) - addr6part(&c->start6); + u64 offset = j + c->addr_epoch; + /* don't divide by zero if range is whole 2^64 */ + if (range != 0) + offset = offset % range; + + start = addr6part(&c->start6) + offset; + } + /* iterate until we find a free address. */ addr = start; @@ -316,16 +506,15 @@ struct dhcp_context *address6_allocate(struct dhcp_con for (d = context; d; d = d->current) if (addr == addr6part(&d->local6)) break; + + *ans = c->start6; + setaddr6part (ans, addr); if (!d && !lease6_find_by_addr(&c->start6, c->prefix, addr) && - !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr)) - { - *ans = c->start6; - setaddr6part (ans, addr); - return c; - } - + !config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, ans)) + return c; + addr++; if (addr == addr6part(&c->end6) + 1) @@ -379,71 +568,10 @@ struct dhcp_context *address6_valid(struct dhcp_contex return NULL; } -int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr) -{ - if (!config || !(config->flags & CONFIG_ADDR6)) - return 0; - - if ((config->flags & CONFIG_WILDCARD) && context->prefix == 64) - { - *addr = context->start6; - setaddr6part(addr, addr6part(&config->addr6)); - return 1; - } - - if (is_same_net6(&context->start6, &config->addr6, context->prefix)) - { - *addr = config->addr6; - return 1; - } - - return 0; -} - -static int is_config_in_context6(struct dhcp_context *context, struct dhcp_config *config) -{ - if (!(config->flags & CONFIG_ADDR6) || - (config->flags & CONFIG_WILDCARD)) - - return 1; - - for (; context; context = context->current) - if (is_same_net6(&config->addr6, &context->start6, context->prefix)) - return 1; - - return 0; -} - - -struct dhcp_config *find_config6(struct dhcp_config *configs, - struct dhcp_context *context, - unsigned char *duid, int duid_len, - char *hostname) -{ - struct dhcp_config *config; - - if (duid) - for (config = configs; config; config = config->next) - if (config->flags & CONFIG_CLID) - { - if (config->clid_len == duid_len && - memcmp(config->clid, duid, duid_len) == 0 && - is_config_in_context6(context, config)) - return config; - } - - if (hostname && context) - for (config = configs; config; config = config->next) - if ((config->flags & CONFIG_NAME) && - hostname_isequal(config->hostname, hostname) && - is_config_in_context6(context, config)) - return config; - - return NULL; -} - void make_duid(time_t now) { + (void)now; + if (daemon->duid_config) { unsigned char *p; @@ -456,8 +584,14 @@ void make_duid(time_t now) } else { + time_t newnow = 0; + + /* If we have no persistent lease database, or a non-stable RTC, use DUID_LL (newnow == 0) */ +#ifndef HAVE_BROKEN_RTC /* rebase epoch to 1/1/2000 */ - time_t newnow = now - 946684800; + if (!option_bool(OPT_LEASE_RO) || daemon->lease_change_command) + newnow = now - 946684800; +#endif iface_enumerate(AF_LOCAL, &newnow, make_duid1); @@ -475,23 +609,28 @@ static int make_duid1(int index, unsigned int type, ch unsigned char *p; (void)index; - + (void)parm; + time_t newnow = *((time_t *)parm); + if (type >= 256) return 1; -#ifdef HAVE_BROKEN_RTC - daemon->duid = p = safe_malloc(maclen + 4); - daemon->duid_len = maclen + 4; - PUTSHORT(3, p); /* DUID_LL */ - PUTSHORT(type, p); /* address type */ -#else - daemon->duid = p = safe_malloc(maclen + 8); - daemon->duid_len = maclen + 8; - PUTSHORT(1, p); /* DUID_LLT */ - PUTSHORT(type, p); /* address type */ - PUTLONG(*((time_t *)parm), p); /* time */ -#endif - + if (newnow == 0) + { + daemon->duid = p = safe_malloc(maclen + 4); + daemon->duid_len = maclen + 4; + PUTSHORT(3, p); /* DUID_LL */ + PUTSHORT(type, p); /* address type */ + } + else + { + daemon->duid = p = safe_malloc(maclen + 8); + daemon->duid_len = maclen + 8; + PUTSHORT(1, p); /* DUID_LLT */ + PUTSHORT(type, p); /* address type */ + PUTLONG(*((time_t *)parm), p); /* time */ + } + memcpy(p, mac, maclen); return 0; @@ -509,7 +648,8 @@ static int construct_worker(struct in6_addr *local, in char ifrn_name[IFNAMSIZ]; struct in6_addr start6, end6; struct dhcp_context *template, *context; - + struct iname *tmp; + (void)scope; (void)flags; (void)valid; @@ -522,23 +662,43 @@ static int construct_worker(struct in6_addr *local, in IN6_IS_ADDR_MULTICAST(local)) return 1; - if (!indextoname(daemon->doing_dhcp6 ? daemon->dhcp6fd : daemon->icmp6fd, if_index, ifrn_name)) - return 0; + if (!(flags & IFACE_PERMANENT)) + return 1; + + if (flags & IFACE_DEPRECATED) + return 1; + + /* Ignore interfaces where we're not doing RA/DHCP6 */ + if (!indextoname(daemon->icmp6fd, if_index, ifrn_name) || + !iface_check(AF_LOCAL, NULL, ifrn_name, NULL)) + return 1; + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifrn_name)) + return 1; + for (template = daemon->dhcp6; template; template = template->next) - if (!(template->flags & CONTEXT_TEMPLATE)) + if (!(template->flags & (CONTEXT_TEMPLATE | CONTEXT_CONSTRUCTED))) { /* non-template entries, just fill in interface and local addresses */ - if (prefix == template->prefix && - is_same_net6(local, &template->start6, prefix) && - is_same_net6(local, &template->end6, prefix)) + if (prefix <= template->prefix && + is_same_net6(local, &template->start6, template->prefix) && + is_same_net6(local, &template->end6, template->prefix)) { + /* First time found, do fast RA. */ + if (template->if_index == 0) + { + ra_start_unsolicited(param->now, template); + param->newone = 1; + } + template->if_index = if_index; template->local6 = *local; } } - else if (addr6part(local) == addr6part(&template->start6) && wildcard_match(template->template_interface, ifrn_name)) + else if (wildcard_match(template->template_interface, ifrn_name) && + template->prefix >= prefix) { start6 = *local; setaddr6part(&start6, addr6part(&template->start6)); @@ -546,12 +706,33 @@ static int construct_worker(struct in6_addr *local, in setaddr6part(&end6, addr6part(&template->end6)); for (context = daemon->dhcp6; context; context = context->next) - if ((context->flags & CONTEXT_CONSTRUCTED) && + if (!(context->flags & CONTEXT_TEMPLATE) && IN6_ARE_ADDR_EQUAL(&start6, &context->start6) && IN6_ARE_ADDR_EQUAL(&end6, &context->end6)) { - context->flags &= ~CONTEXT_GC; - break; + /* If there's an absolute address context covering this address + then don't construct one as well. */ + if (!(context->flags & CONTEXT_CONSTRUCTED)) + break; + + if (context->if_index == if_index) + { + int cflags = context->flags; + context->flags &= ~(CONTEXT_GC | CONTEXT_OLD); + if (cflags & CONTEXT_OLD) + { + /* address went, now it's back, and on the same interface */ + log_context(AF_INET6, context); + /* fast RAs for a while */ + ra_start_unsolicited(param->now, context); + param->newone = 1; + /* Add address to name again */ + if (context->flags & CONTEXT_RA_NAME) + param->newname = 1; + + } + break; + } } if (!context && (context = whine_malloc(sizeof (struct dhcp_context)))) @@ -563,11 +744,12 @@ static int construct_worker(struct in6_addr *local, in context->flags |= CONTEXT_CONSTRUCTED; context->if_index = if_index; context->local6 = *local; + context->saved_valid = 0; context->next = daemon->dhcp6; daemon->dhcp6 = context; - ra_start_unsolicted(param->now, context); + ra_start_unsolicited(param->now, context); /* we created a new one, need to call lease_update_file to get periodic functions called */ param->newone = 1; @@ -585,35 +767,55 @@ static int construct_worker(struct in6_addr *local, in void dhcp_construct_contexts(time_t now) { - struct dhcp_context *tmp, *context, **up; + struct dhcp_context *context, *tmp, **up; struct cparam param; param.newone = 0; param.newname = 0; param.now = now; for (context = daemon->dhcp6; context; context = context->next) - { - context->if_index = 0; - if (context->flags & CONTEXT_CONSTRUCTED) - context->flags |= CONTEXT_GC; - } - + if (context->flags & CONTEXT_CONSTRUCTED) + context->flags |= CONTEXT_GC; + iface_enumerate(AF_INET6, ¶m, construct_worker); for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp) { - tmp = context->next; - if (context->flags & CONTEXT_GC) + tmp = context->next; + + if (context->flags & CONTEXT_GC && !(context->flags & CONTEXT_OLD)) { - *up = context->next; - param.newone = 1; /* include deletion */ - if (context->flags & CONTEXT_RA_NAME) - param.newname = 1; - free(context); + if ((context->flags & CONTEXT_RA) || option_bool(OPT_RA)) + { + /* previously constructed context has gone. advertise it's demise */ + context->flags |= CONTEXT_OLD; + context->address_lost_time = now; + /* Apply same ceiling of configured lease time as in radv.c */ + if (context->saved_valid > context->lease_time) + context->saved_valid = context->lease_time; + /* maximum time is 2 hours, from RFC */ + if (context->saved_valid > 7200) /* 2 hours */ + context->saved_valid = 7200; + ra_start_unsolicited(now, context); + param.newone = 1; /* include deletion */ + + if (context->flags & CONTEXT_RA_NAME) + param.newname = 1; + + log_context(AF_INET6, context); + + up = &context->next; + } + else + { + /* we were never doing RA for this, so free now */ + *up = context->next; + free(context); + } } else - up = &context->next; + up = &context->next; } if (param.newone) @@ -630,6 +832,4 @@ void dhcp_construct_contexts(time_t now) } } -#endif - - +#endif /* HAVE_DHCP6 */