--- embedaddon/dnsmasq/src/dhcp6.c 2014/06/15 16:31:38 1.1.1.2 +++ embedaddon/dnsmasq/src/dhcp6.c 2021/03/17 00:56:46 1.1.1.4 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2021 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 @@ -27,17 +27,10 @@ struct iface_param { int ind, addr_match; }; -struct mac_param { - struct in6_addr *target; - unsigned char *mac; - unsigned int maclen; -}; - 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 find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv); static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm); void dhcp6_init(void) @@ -58,9 +51,9 @@ 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)) @@ -142,8 +135,17 @@ void dhcp6_packet(time_t now) if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name)) return; - if ((port = relay_reply6(&from, sz, ifr.ifr_name)) == 0) + if ((port = relay_reply6(&from, sz, ifr.ifr_name)) != 0) { + from.sin6_port = htons(port); + 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)) return; @@ -160,6 +162,30 @@ void dhcp6_packet(time_t now) 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) @@ -201,208 +227,217 @@ void dhcp6_packet(time_t now) inet_pton(AF_INET6, ALL_SERVERS, &all_servers); if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers)) - relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id); + relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id, now); 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); + 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); } - - /* 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); - while (sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, save_counter(0), - 0, (struct sockaddr *)&from, sizeof(from)) == -1 && - retry_send()); - } } -void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep) +void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep, time_t now) { - /* Recieving a packet from a host does not populate the neighbour + /* 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; - struct sockaddr_in6 addr; - struct mac_param mac_param; - int i; + union mysockaddr addr; + int i, maclen; 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.sin6_len = sizeof(struct sockaddr_in6); + addr.in6.sin6_len = sizeof(struct sockaddr_in6); #endif - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(IPPROTO_ICMPV6); - addr.sin6_addr = *client; - addr.sin6_scope_id = iface; + addr.in6.sin6_family = AF_INET6; + addr.in6.sin6_port = htons(IPPROTO_ICMPV6); + addr.in6.sin6_addr = *client; + addr.in6.sin6_scope_id = iface; - mac_param.target = client; - mac_param.maclen = 0; - mac_param.mac = mac; - for (i = 0; i < 5; i++) { struct timespec ts; - iface_enumerate(AF_UNSPEC, &mac_param, find_mac); - - if (mac_param.maclen != 0) + if ((maclen = find_mac(&addr, mac, 0, now)) != 0) break; + + sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, &addr.sa, sizeof(addr)); - sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, (struct sockaddr *)&addr, sizeof(addr)); - ts.tv_sec = 0; ts.tv_nsec = 100000000; /* 100ms */ nanosleep(&ts, NULL); } - *maclenp = mac_param.maclen; + *maclenp = maclen; *mactypep = ARPHRD_ETHER; } -static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv) -{ - struct mac_param *parm = parmv; - - if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(parm->target, (struct in6_addr *)addrp)) - { - if (maclen <= DHCP_CHADDR_MAX) - { - parm->maclen = maclen; - memcpy(parm->mac, mac, maclen); - } - - return 0; /* found, abort */ - } - - return 1; -} - 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; (void)scope; /* warning */ - if (if_index == param->ind) - { - if (IN6_IS_ADDR_LINKLOCAL(local)) - param->ll_addr = *local; - else if (IN6_IS_ADDR_ULA(local)) - param->ula_addr = *local; - - if (!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; - - /* 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 && - is_same_net6(local, &context->start6, context->prefix) && - is_same_net6(local, &context->end6, context->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; - } - } - } - } - - for (relay = daemon->relay6; relay; relay = relay->next) - if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr.addr6) && relay->current == relay && - (IN6_IS_ADDR_UNSPECIFIED(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->relay_local))) + 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; + + 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)) + 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)) { - relay->current = param->relay; - param->relay = relay; - param->relay_local = *local; + 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; } - - } - - return 1; + 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; + } + } + } + } + + for (relay = daemon->relay6; relay; relay = relay->next) + if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6) && relay->current == relay && + (IN6_IS_ADDR_UNSPECIFIED(¶m->relay_local) || IN6_ARE_ADDR_EQUAL(local, ¶m->relay_local))) + { + relay->current = param->relay; + param->relay = relay; + param->relay_local = *local; + } + + 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 temp_addr, - int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans) + 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. @@ -424,7 +459,7 @@ struct dhcp_context *address6_allocate(struct dhcp_con j = rand64(); else for (j = iaid, i = 0; i < clid_len; i++) - j += clid[i] + (j << 6) + (j << 16) - j; + j = clid[i] + (j << 6) + (j << 16) - j; for (pass = 0; pass <= plain_range ? 1 : 0; pass++) for (c = context; c; c = c->current) @@ -435,11 +470,27 @@ struct dhcp_context *address6_allocate(struct dhcp_con else { if (!temp_addr && option_bool(OPT_CONSEC_ADDR)) - /* seed is largest extant lease addr in this context */ - start = lease_find_max_addr6(c) + serial; + { + /* 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; @@ -448,16 +499,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) @@ -511,27 +561,6 @@ 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; -} - void make_duid(time_t now) { (void)now; @@ -612,7 +641,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; @@ -631,17 +661,30 @@ static int construct_worker(struct in6_addr *local, in if (flags & IFACE_DEPRECATED) return 1; - if (!indextoname(daemon->icmp6fd, if_index, ifrn_name)) - return 0; + /* 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, 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; } @@ -656,24 +699,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)) { - int flags = context->flags; - context->flags &= ~(CONTEXT_GC | CONTEXT_OLD); - if (flags & CONTEXT_OLD) + /* 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) { - /* address went, now it's back */ - log_context(AF_INET6, context); - /* fast RAs for a while */ - ra_start_unsolicted(param->now, context); - param->newone = 1; - /* Add address to name again */ - if (context->flags & CONTEXT_RA_NAME) - param->newname = 1; + 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; } - break; } if (!context && (context = whine_malloc(sizeof (struct dhcp_context)))) @@ -690,7 +742,7 @@ static int construct_worker(struct in6_addr *local, in 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; @@ -727,8 +779,7 @@ void dhcp_construct_contexts(time_t now) if (context->flags & CONTEXT_GC && !(context->flags & CONTEXT_OLD)) { - if ((context->flags & (CONTEXT_RA_ONLY | CONTEXT_RA_NAME | CONTEXT_RA_STATELESS)) || - option_bool(OPT_RA)) + if ((context->flags & CONTEXT_RA) || option_bool(OPT_RA)) { /* previously constructed context has gone. advertise it's demise */ context->flags |= CONTEXT_OLD; @@ -739,7 +790,7 @@ void dhcp_construct_contexts(time_t now) /* maximum time is 2 hours, from RFC */ if (context->saved_valid > 7200) /* 2 hours */ context->saved_valid = 7200; - ra_start_unsolicted(now, context); + ra_start_unsolicited(now, context); param.newone = 1; /* include deletion */ if (context->flags & CONTEXT_RA_NAME)