diff options
Diffstat (limited to 'client/dhc6.c')
-rw-r--r-- | client/dhc6.c | 5128 |
1 files changed, 5128 insertions, 0 deletions
diff --git a/client/dhc6.c b/client/dhc6.c new file mode 100644 index 0000000..633f9b1 --- /dev/null +++ b/client/dhc6.c @@ -0,0 +1,5128 @@ +/* dhc6.c - DHCPv6 client routines. */ + +/* + * Copyright (c) 2006-2010 by Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Internet Systems Consortium, Inc. + * 950 Charter Street + * Redwood City, CA 94063 + * <info@isc.org> + * https://www.isc.org/ + */ + +#include "dhcpd.h" + +#ifdef DHCPv6 + +struct sockaddr_in6 DHCPv6DestAddr; + +/* + * Option definition structures that are used by the software - declared + * here once and assigned at startup to save lookups. + */ +struct option *clientid_option = NULL; +struct option *elapsed_option = NULL; +struct option *ia_na_option = NULL; +struct option *ia_ta_option = NULL; +struct option *ia_pd_option = NULL; +struct option *iaaddr_option = NULL; +struct option *iaprefix_option = NULL; +struct option *oro_option = NULL; +struct option *irt_option = NULL; + +static struct dhc6_lease *dhc6_dup_lease(struct dhc6_lease *lease, + const char *file, int line); +static struct dhc6_ia *dhc6_dup_ia(struct dhc6_ia *ia, + const char *file, int line); +static struct dhc6_addr *dhc6_dup_addr(struct dhc6_addr *addr, + const char *file, int line); +static void dhc6_ia_destroy(struct dhc6_ia **src, const char *file, int line); +static isc_result_t dhc6_parse_ia_na(struct dhc6_ia **pia, + struct packet *packet, + struct option_state *options); +static isc_result_t dhc6_parse_ia_ta(struct dhc6_ia **pia, + struct packet *packet, + struct option_state *options); +static isc_result_t dhc6_parse_ia_pd(struct dhc6_ia **pia, + struct packet *packet, + struct option_state *options); +static isc_result_t dhc6_parse_addrs(struct dhc6_addr **paddr, + struct packet *packet, + struct option_state *options); +static isc_result_t dhc6_parse_prefixes(struct dhc6_addr **ppref, + struct packet *packet, + struct option_state *options); +static struct dhc6_ia *find_ia(struct dhc6_ia *head, + u_int16_t type, const char *id); +static struct dhc6_addr *find_addr(struct dhc6_addr *head, + struct iaddr *address); +static struct dhc6_addr *find_pref(struct dhc6_addr *head, + struct iaddr *prefix, u_int8_t plen); +void init_handler(struct packet *packet, struct client_state *client); +void info_request_handler(struct packet *packet, struct client_state *client); +void rapid_commit_handler(struct packet *packet, struct client_state *client); +void do_init6(void *input); +void do_info_request6(void *input); +void do_confirm6(void *input); +void reply_handler(struct packet *packet, struct client_state *client); +static isc_result_t dhc6_add_ia_na(struct client_state *client, + struct data_string *packet, + struct dhc6_lease *lease, + u_int8_t message); +static isc_result_t dhc6_add_ia_ta(struct client_state *client, + struct data_string *packet, + struct dhc6_lease *lease, + u_int8_t message); +static isc_result_t dhc6_add_ia_pd(struct client_state *client, + struct data_string *packet, + struct dhc6_lease *lease, + u_int8_t message); +static isc_boolean_t stopping_finished(void); +static void dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst); +void do_select6(void *input); +void do_refresh6(void *input); +static void do_release6(void *input); +static void start_bound(struct client_state *client); +static void start_informed(struct client_state *client); +void informed_handler(struct packet *packet, struct client_state *client); +void bound_handler(struct packet *packet, struct client_state *client); +void start_renew6(void *input); +void start_rebind6(void *input); +void do_depref(void *input); +void do_expire(void *input); +static void make_client6_options(struct client_state *client, + struct option_state **op, + struct dhc6_lease *lease, u_int8_t message); +static void script_write_params6(struct client_state *client, + const char *prefix, + struct option_state *options); +static isc_boolean_t active_prefix(struct client_state *client); + +static int check_timing6(struct client_state *client, u_int8_t msg_type, + char *msg_str, struct dhc6_lease *lease, + struct data_string *ds); + +extern int onetry; +extern int stateless; + +/* + * The "best" default DUID, since we cannot predict any information + * about the system (such as whether or not the hardware addresses are + * integrated into the motherboard or similar), is the "LLT", link local + * plus time, DUID. For real stateless "LL" is better. + * + * Once generated, this duid is stored into the state database, and + * retained across restarts. + * + * For the time being, there is probably a different state database for + * every daemon, so this winds up being a per-interface identifier...which + * is not how it is intended. Upcoming rearchitecting the client should + * address this "one daemon model." + */ +void +form_duid(struct data_string *duid, const char *file, int line) +{ + struct interface_info *ip; + int len; + + /* For now, just use the first interface on the list. */ + ip = interfaces; + + if (ip == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + if ((ip->hw_address.hlen == 0) || + (ip->hw_address.hlen > sizeof(ip->hw_address.hbuf))) + log_fatal("Impossible hardware address length at %s:%d.", MDL); + + if (duid_type == 0) + duid_type = stateless ? DUID_LL : DUID_LLT; + + /* + * 2 bytes for the 'duid type' field. + * 2 bytes for the 'htype' field. + * (DUID_LLT) 4 bytes for the 'current time'. + * enough bytes for the hardware address (note that hw_address has + * the 'htype' on byte zero). + */ + len = 4 + (ip->hw_address.hlen - 1); + if (duid_type == DUID_LLT) + len += 4; + if (!buffer_allocate(&duid->buffer, len, MDL)) + log_fatal("no memory for default DUID!"); + duid->data = duid->buffer->data; + duid->len = len; + + /* Basic Link Local Address type of DUID. */ + if (duid_type == DUID_LLT) { + putUShort(duid->buffer->data, DUID_LLT); + putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]); + putULong(duid->buffer->data + 4, cur_time - DUID_TIME_EPOCH); + memcpy(duid->buffer->data + 8, ip->hw_address.hbuf + 1, + ip->hw_address.hlen - 1); + } else { + putUShort(duid->buffer->data, DUID_LL); + putUShort(duid->buffer->data + 2, ip->hw_address.hbuf[0]); + memcpy(duid->buffer->data + 4, ip->hw_address.hbuf + 1, + ip->hw_address.hlen - 1); + } +} + +/* + * Assign DHCPv6 port numbers as a client. + */ +void +dhcpv6_client_assignments(void) +{ + struct servent *ent; + unsigned code; + + if (path_dhclient_pid == NULL) + path_dhclient_pid = _PATH_DHCLIENT6_PID; + if (path_dhclient_db == NULL) + path_dhclient_db = _PATH_DHCLIENT6_DB; + + if (local_port == 0) { + ent = getservbyname("dhcpv6-client", "udp"); + if (ent == NULL) + local_port = htons(546); + else + local_port = ent->s_port; + } + + if (remote_port == 0) { + ent = getservbyname("dhcpv6-server", "udp"); + if (ent == NULL) + remote_port = htons(547); + else + remote_port = ent->s_port; + } + + memset(&DHCPv6DestAddr, 0, sizeof(DHCPv6DestAddr)); + DHCPv6DestAddr.sin6_family = AF_INET6; + DHCPv6DestAddr.sin6_port = remote_port; + inet_pton(AF_INET6, All_DHCP_Relay_Agents_and_Servers, + &DHCPv6DestAddr.sin6_addr); + + code = D6O_CLIENTID; + if (!option_code_hash_lookup(&clientid_option, + dhcpv6_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find the CLIENTID option definition."); + + code = D6O_ELAPSED_TIME; + if (!option_code_hash_lookup(&elapsed_option, + dhcpv6_universe.code_hash, &code, 0, MDL)) + log_fatal("Unable to find the ELAPSED_TIME option definition."); + + code = D6O_IA_NA; + if (!option_code_hash_lookup(&ia_na_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IA_NA option definition."); + + code = D6O_IA_TA; + if (!option_code_hash_lookup(&ia_ta_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IA_TA option definition."); + + code = D6O_IA_PD; + if (!option_code_hash_lookup(&ia_pd_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IA_PD option definition."); + + code = D6O_IAADDR; + if (!option_code_hash_lookup(&iaaddr_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IAADDR option definition."); + + code = D6O_IAPREFIX; + if (!option_code_hash_lookup(&iaprefix_option, + dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IAPREFIX option definition."); + + code = D6O_ORO; + if (!option_code_hash_lookup(&oro_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the ORO option definition."); + + code = D6O_INFORMATION_REFRESH_TIME; + if (!option_code_hash_lookup(&irt_option, dhcpv6_universe.code_hash, + &code, 0, MDL)) + log_fatal("Unable to find the IRT option definition."); + +#ifndef __CYGWIN32__ /* XXX */ + endservent(); +#endif +} + +/* + * Instead of implementing RFC3315 RAND (section 14) as a float "between" + * -0.1 and 0.1 non-inclusive, we implement it as an integer. + * + * The result is expected to follow this table: + * + * split range answer + * - ERROR - base <= 0 + * 0 1 0..0 1 <= base <= 10 + * 1 3 -1..1 11 <= base <= 20 + * 2 5 -2..2 21 <= base <= 30 + * 3 7 -3..3 31 <= base <= 40 + * ... + * + * XXX: For this to make sense, we really need to do timing on a + * XXX: usec scale...we currently can assume zero for any value less than + * XXX: 11, which are very common in early stages of transmission for most + * XXX: messages. + */ +static TIME +dhc6_rand(TIME base) +{ + TIME rval; + TIME range; + TIME split; + + /* + * A zero or less timeout is a bad thing...we don't want to + * DHCP-flood anyone. + */ + if (base <= 0) + log_fatal("Impossible condition at %s:%d.", MDL); + + /* + * The first thing we do is count how many random integers we want + * in either direction (best thought of as the maximum negative + * integer, as we will subtract this potentially from a random 0). + */ + split = (base - 1) / 10; + + /* Don't bother with the rest of the math if we know we'll get 0. */ + if (split == 0) + return 0; + + /* + * Then we count the total number of integers in this set. This + * is twice the number of integers in positive and negative + * directions, plus zero (-1, 0, 1 is 3, -2..2 adds 2 to 5, so forth). + */ + range = (split * 2) + 1; + + /* Take a random number from [0..(range-1)]. */ + rval = random(); + rval %= range; + + /* Offset it to uncover potential negative values. */ + rval -= split; + + return rval; +} + +/* Initialize message exchange timers (set RT from Initial-RT). */ +static void +dhc6_retrans_init(struct client_state *client) +{ + int xid; + + /* Initialize timers. */ + client->txcount = 0; + client->RT = client->IRT + dhc6_rand(client->IRT); + + /* Generate a new random 24-bit transaction ID for this exchange. */ + +#if (RAND_MAX >= 0x00ffffff) + xid = random(); +#elif (RAND_MAX >= 0x0000ffff) + xid = (random() << 16) ^ random(); +#elif (RAND_MAX >= 0x000000ff) + xid = (random() << 16) ^ (random() << 8) ^ random(); +#else +# error "Random number generator of less than 8 bits not supported." +#endif + + client->dhcpv6_transaction_id[0] = (xid >> 16) & 0xff; + client->dhcpv6_transaction_id[1] = (xid >> 8) & 0xff; + client->dhcpv6_transaction_id[2] = xid & 0xff; +} + +/* Advance the DHCPv6 retransmission state once. */ +static void +dhc6_retrans_advance(struct client_state *client) +{ + struct timeval elapsed; + + /* elapsed = cur - start */ + elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec; + elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec; + if (elapsed.tv_usec < 0) { + elapsed.tv_sec -= 1; + elapsed.tv_usec += 1000000; + } + /* retrans_advance is called after consuming client->RT. */ + /* elapsed += RT */ + elapsed.tv_sec += client->RT / 100; + elapsed.tv_usec += (client->RT % 100) * 10000; + if (elapsed.tv_usec >= 1000000) { + elapsed.tv_sec += 1; + elapsed.tv_usec -= 1000000; + } + + /* + * RT for each subsequent message transmission is based on the previous + * value of RT: + * + * RT = 2*RTprev + RAND*RTprev + */ + client->RT += client->RT + dhc6_rand(client->RT); + + /* + * MRT specifies an upper bound on the value of RT (disregarding the + * randomization added by the use of RAND). If MRT has a value of 0, + * there is no upper limit on the value of RT. Otherwise: + * + * if (RT > MRT) + * RT = MRT + RAND*MRT + */ + if ((client->MRT != 0) && (client->RT > client->MRT)) + client->RT = client->MRT + dhc6_rand(client->MRT); + + /* + * Further, if there's an MRD, we should wake up upon reaching + * the MRD rather than at some point after it. + */ + if (client->MRD == 0) { + /* Done. */ + client->txcount++; + return; + } + /* elapsed += client->RT */ + elapsed.tv_sec += client->RT / 100; + elapsed.tv_usec += (client->RT % 100) * 10000; + if (elapsed.tv_usec >= 1000000) { + elapsed.tv_sec += 1; + elapsed.tv_usec -= 1000000; + } + if (elapsed.tv_sec >= client->MRD) { + /* + * wake at RT + cur = start + MRD + */ + client->RT = client->MRD + + (client->start_time.tv_sec - cur_tv.tv_sec); + client->RT = client->RT * 100 + + (client->start_time.tv_usec - cur_tv.tv_usec) / 10000; + } + client->txcount++; +} + +/* Quick validation of DHCPv6 ADVERTISE packet contents. */ +static int +valid_reply(struct packet *packet, struct client_state *client) +{ + struct data_string sid, cid; + struct option_cache *oc; + int rval = ISC_TRUE; + + memset(&sid, 0, sizeof(sid)); + memset(&cid, 0, sizeof(cid)); + + if (!lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID)) { + log_error("Response without a server identifier received."); + rval = ISC_FALSE; + } + + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_CLIENTID); + if (!oc || + !evaluate_option_cache(&sid, packet, NULL, client, packet->options, + client->sent_options, &global_scope, oc, + MDL)) { + log_error("Response without a client identifier."); + rval = ISC_FALSE; + } + + oc = lookup_option(&dhcpv6_universe, client->sent_options, + D6O_CLIENTID); + if (!oc || + !evaluate_option_cache(&cid, packet, NULL, client, + client->sent_options, NULL, &global_scope, + oc, MDL)) { + log_error("Local client identifier is missing!"); + rval = ISC_FALSE; + } + + if (sid.len == 0 || + sid.len != cid.len || + memcmp(sid.data, cid.data, sid.len)) { + log_error("Advertise with matching transaction ID, but " + "mismatching client id."); + rval = ISC_FALSE; + } + + return rval; +} + +/* + * Create a complete copy of a DHCPv6 lease structure. + */ +static struct dhc6_lease * +dhc6_dup_lease(struct dhc6_lease *lease, const char *file, int line) +{ + struct dhc6_lease *copy; + struct dhc6_ia **insert_ia, *ia; + + copy = dmalloc(sizeof(*copy), file, line); + if (copy == NULL) { + log_error("Out of memory for v6 lease structure."); + return NULL; + } + + data_string_copy(©->server_id, &lease->server_id, file, line); + copy->pref = lease->pref; + + memcpy(copy->dhcpv6_transaction_id, lease->dhcpv6_transaction_id, + sizeof(copy->dhcpv6_transaction_id)); + + option_state_reference(©->options, lease->options, file, line); + + insert_ia = ©->bindings; + for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + *insert_ia = dhc6_dup_ia(ia, file, line); + + if (*insert_ia == NULL) { + dhc6_lease_destroy(©, file, line); + return NULL; + } + + insert_ia = &(*insert_ia)->next; + } + + return copy; +} + +/* + * Duplicate an IA structure. + */ +static struct dhc6_ia * +dhc6_dup_ia(struct dhc6_ia *ia, const char *file, int line) +{ + struct dhc6_ia *copy; + struct dhc6_addr **insert_addr, *addr; + + copy = dmalloc(sizeof(*ia), file, line); + + memcpy(copy->iaid, ia->iaid, sizeof(copy->iaid)); + + copy->ia_type = ia->ia_type; + copy->starts = ia->starts; + copy->renew = ia->renew; + copy->rebind = ia->rebind; + + insert_addr = ©->addrs; + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + *insert_addr = dhc6_dup_addr(addr, file, line); + + if (*insert_addr == NULL) { + dhc6_ia_destroy(©, file, line); + return NULL; + } + + insert_addr = &(*insert_addr)->next; + } + + if (ia->options != NULL) + option_state_reference(©->options, ia->options, + file, line); + + return copy; +} + +/* + * Duplicate an IAADDR or IAPREFIX structure. + */ +static struct dhc6_addr * +dhc6_dup_addr(struct dhc6_addr *addr, const char *file, int line) +{ + struct dhc6_addr *copy; + + copy = dmalloc(sizeof(*addr), file, line); + + if (copy == NULL) + return NULL; + + memcpy(©->address, &addr->address, sizeof(copy->address)); + + copy->plen = addr->plen; + copy->flags = addr->flags; + copy->starts = addr->starts; + copy->preferred_life = addr->preferred_life; + copy->max_life = addr->max_life; + + if (addr->options != NULL) + option_state_reference(©->options, addr->options, + file, line); + + return copy; +} + +/* + * Form a DHCPv6 lease structure based upon packet contents. Creates and + * populates IA's and any IAADDR/IAPREFIX's they contain. + * Parsed options are deleted in order to not save them in the lease file. + */ +static struct dhc6_lease * +dhc6_leaseify(struct packet *packet) +{ + struct data_string ds; + struct dhc6_lease *lease; + struct option_cache *oc; + + lease = dmalloc(sizeof(*lease), MDL); + if (lease == NULL) { + log_error("Out of memory for v6 lease structure."); + return NULL; + } + + memcpy(lease->dhcpv6_transaction_id, packet->dhcpv6_transaction_id, 3); + option_state_reference(&lease->options, packet->options, MDL); + + memset(&ds, 0, sizeof(ds)); + + /* Determine preference (default zero). */ + oc = lookup_option(&dhcpv6_universe, lease->options, D6O_PREFERENCE); + if (oc && + evaluate_option_cache(&ds, packet, NULL, NULL, lease->options, + NULL, &global_scope, oc, MDL)) { + if (ds.len != 1) { + log_error("Invalid length of DHCPv6 Preference option " + "(%d != 1)", ds.len); + data_string_forget(&ds, MDL); + dhc6_lease_destroy(&lease, MDL); + return NULL; + } else { + lease->pref = ds.data[0]; + log_debug("RCV: X-- Preference %u.", + (unsigned)lease->pref); + } + + data_string_forget(&ds, MDL); + } + delete_option(&dhcpv6_universe, lease->options, D6O_PREFERENCE); + + /* + * Dig into recursive DHCPv6 pockets for IA_NA and contained IAADDR + * options. + */ + if (dhc6_parse_ia_na(&lease->bindings, packet, + lease->options) != ISC_R_SUCCESS) { + /* Error conditions are logged by the caller. */ + dhc6_lease_destroy(&lease, MDL); + return NULL; + } + /* + * Dig into recursive DHCPv6 pockets for IA_TA and contained IAADDR + * options. + */ + if (dhc6_parse_ia_ta(&lease->bindings, packet, + lease->options) != ISC_R_SUCCESS) { + /* Error conditions are logged by the caller. */ + dhc6_lease_destroy(&lease, MDL); + return NULL; + } + /* + * Dig into recursive DHCPv6 pockets for IA_PD and contained IAPREFIX + * options. + */ + if (dhc6_parse_ia_pd(&lease->bindings, packet, + lease->options) != ISC_R_SUCCESS) { + /* Error conditions are logged by the caller. */ + dhc6_lease_destroy(&lease, MDL); + return NULL; + } + + /* + * This is last because in the future we may want to make a different + * key based upon additional information from the packet (we may need + * to allow multiple leases in one client state per server, but we're + * not sure based on what additional keys now). + */ + oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID); + if (!evaluate_option_cache(&lease->server_id, packet, NULL, NULL, + lease->options, NULL, &global_scope, + oc, MDL) || + lease->server_id.len == 0) { + /* This should be impossible due to validation checks earlier. + */ + log_error("Invalid SERVERID option cache."); + dhc6_lease_destroy(&lease, MDL); + return NULL; + } else { + log_debug("RCV: X-- Server ID: %s", + print_hex_1(lease->server_id.len, + lease->server_id.data, 52)); + } + + return lease; +} + +static isc_result_t +dhc6_parse_ia_na(struct dhc6_ia **pia, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct dhc6_ia *ia; + struct option_cache *oc; + isc_result_t result; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IA_NA); + for ( ; oc != NULL ; oc = oc->next) { + ia = dmalloc(sizeof(*ia), MDL); + if (ia == NULL) { + log_error("Out of memory allocating IA_NA structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, + &global_scope, oc, MDL) && + ds.len >= 12) { + memcpy(ia->iaid, ds.data, 4); + ia->ia_type = D6O_IA_NA; + ia->starts = cur_time; + ia->renew = getULong(ds.data + 4); + ia->rebind = getULong(ds.data + 8); + + log_debug("RCV: X-- IA_NA %s", + print_hex_1(4, ia->iaid, 59)); + /* XXX: This should be the printed time I think. */ + log_debug("RCV: | X-- starts %u", + (unsigned)ia->starts); + log_debug("RCV: | X-- t1 - renew +%u", ia->renew); + log_debug("RCV: | X-- t2 - rebind +%u", ia->rebind); + + /* + * RFC3315 section 22.4, discard IA_NA's that + * have t1 greater than t2, and both not zero. + * Since RFC3315 defines this behaviour, it is not + * an error - just normal operation. + * + * Note that RFC3315 says we MUST honor these values + * if they are not zero. So insane values are + * totally OK. + */ + if ((ia->renew > 0) && (ia->rebind > 0) && + (ia->renew > ia->rebind)) { + log_debug("RCV: | !-- INVALID renew/rebind " + "times, IA_NA discarded."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + continue; + } + + if (ds.len > 12) { + log_debug("RCV: | X-- [Options]"); + + if (!option_state_allocate(&ia->options, + MDL)) { + log_error("Out of memory allocating " + "IA_NA option state."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(ia->options, + ds.data + 12, + ds.len - 12, + &dhcpv6_universe)) { + log_error("Corrupt IA_NA options."); + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return DHCP_R_BADPARSE; + } + } + data_string_forget(&ds, MDL); + + if (ia->options != NULL) { + result = dhc6_parse_addrs(&ia->addrs, packet, + ia->options); + if (result != ISC_R_SUCCESS) { + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + return result; + } + } + + while (*pia != NULL) + pia = &(*pia)->next; + *pia = ia; + pia = &ia->next; + } else { + log_error("Invalid IA_NA option cache."); + dfree(ia, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + delete_option(&dhcpv6_universe, options, D6O_IA_NA); + + return ISC_R_SUCCESS; +} + +static isc_result_t +dhc6_parse_ia_ta(struct dhc6_ia **pia, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct dhc6_ia *ia; + struct option_cache *oc; + isc_result_t result; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IA_TA); + for ( ; oc != NULL ; oc = oc->next) { + ia = dmalloc(sizeof(*ia), MDL); + if (ia == NULL) { + log_error("Out of memory allocating IA_TA structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, + &global_scope, oc, MDL) && + ds.len >= 4) { + memcpy(ia->iaid, ds.data, 4); + ia->ia_type = D6O_IA_TA; + ia->starts = cur_time; + + log_debug("RCV: X-- IA_TA %s", + print_hex_1(4, ia->iaid, 59)); + /* XXX: This should be the printed time I think. */ + log_debug("RCV: | X-- starts %u", + (unsigned)ia->starts); + + if (ds.len > 4) { + log_debug("RCV: | X-- [Options]"); + + if (!option_state_allocate(&ia->options, + MDL)) { + log_error("Out of memory allocating " + "IA_TA option state."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(ia->options, + ds.data + 4, + ds.len - 4, + &dhcpv6_universe)) { + log_error("Corrupt IA_TA options."); + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return DHCP_R_BADPARSE; + } + } + data_string_forget(&ds, MDL); + + if (ia->options != NULL) { + result = dhc6_parse_addrs(&ia->addrs, packet, + ia->options); + if (result != ISC_R_SUCCESS) { + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + return result; + } + } + + while (*pia != NULL) + pia = &(*pia)->next; + *pia = ia; + pia = &ia->next; + } else { + log_error("Invalid IA_TA option cache."); + dfree(ia, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + delete_option(&dhcpv6_universe, options, D6O_IA_TA); + + return ISC_R_SUCCESS; +} + +static isc_result_t +dhc6_parse_ia_pd(struct dhc6_ia **pia, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct dhc6_ia *ia; + struct option_cache *oc; + isc_result_t result; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IA_PD); + for ( ; oc != NULL ; oc = oc->next) { + ia = dmalloc(sizeof(*ia), MDL); + if (ia == NULL) { + log_error("Out of memory allocating IA_PD structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, + &global_scope, oc, MDL) && + ds.len >= 12) { + memcpy(ia->iaid, ds.data, 4); + ia->ia_type = D6O_IA_PD; + ia->starts = cur_time; + ia->renew = getULong(ds.data + 4); + ia->rebind = getULong(ds.data + 8); + + log_debug("RCV: X-- IA_PD %s", + print_hex_1(4, ia->iaid, 59)); + /* XXX: This should be the printed time I think. */ + log_debug("RCV: | X-- starts %u", + (unsigned)ia->starts); + log_debug("RCV: | X-- t1 - renew +%u", ia->renew); + log_debug("RCV: | X-- t2 - rebind +%u", ia->rebind); + + /* + * RFC3633 section 9, discard IA_PD's that + * have t1 greater than t2, and both not zero. + * Since RFC3633 defines this behaviour, it is not + * an error - just normal operation. + */ + if ((ia->renew > 0) && (ia->rebind > 0) && + (ia->renew > ia->rebind)) { + log_debug("RCV: | !-- INVALID renew/rebind " + "times, IA_PD discarded."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + continue; + } + + if (ds.len > 12) { + log_debug("RCV: | X-- [Options]"); + + if (!option_state_allocate(&ia->options, + MDL)) { + log_error("Out of memory allocating " + "IA_PD option state."); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(ia->options, + ds.data + 12, + ds.len - 12, + &dhcpv6_universe)) { + log_error("Corrupt IA_PD options."); + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + data_string_forget(&ds, MDL); + return DHCP_R_BADPARSE; + } + } + data_string_forget(&ds, MDL); + + if (ia->options != NULL) { + result = dhc6_parse_prefixes(&ia->addrs, + packet, + ia->options); + if (result != ISC_R_SUCCESS) { + option_state_dereference(&ia->options, + MDL); + dfree(ia, MDL); + return result; + } + } + + while (*pia != NULL) + pia = &(*pia)->next; + *pia = ia; + pia = &ia->next; + } else { + log_error("Invalid IA_PD option cache."); + dfree(ia, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + delete_option(&dhcpv6_universe, options, D6O_IA_PD); + + return ISC_R_SUCCESS; +} + + +static isc_result_t +dhc6_parse_addrs(struct dhc6_addr **paddr, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct option_cache *oc; + struct dhc6_addr *addr; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IAADDR); + for ( ; oc != NULL ; oc = oc->next) { + addr = dmalloc(sizeof(*addr), MDL); + if (addr == NULL) { + log_error("Out of memory allocating " + "address structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, &global_scope, + oc, MDL) && + (ds.len >= 24)) { + + addr->address.len = 16; + memcpy(addr->address.iabuf, ds.data, 16); + addr->starts = cur_time; + addr->preferred_life = getULong(ds.data + 16); + addr->max_life = getULong(ds.data + 20); + + log_debug("RCV: | | X-- IAADDR %s", + piaddr(addr->address)); + log_debug("RCV: | | | X-- Preferred lifetime %u.", + addr->preferred_life); + log_debug("RCV: | | | X-- Max lifetime %u.", + addr->max_life); + + /* + * RFC 3315 section 22.6 says we must discard + * addresses whose pref is later than valid. + */ + if ((addr->preferred_life > addr->max_life)) { + log_debug("RCV: | | | !-- INVALID lifetimes, " + "IAADDR discarded. Check your " + "server configuration."); + dfree(addr, MDL); + data_string_forget(&ds, MDL); + continue; + } + + /* + * Fortunately this is the last recursion in the + * protocol. + */ + if (ds.len > 24) { + if (!option_state_allocate(&addr->options, + MDL)) { + log_error("Out of memory allocating " + "IAADDR option state."); + dfree(addr, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(addr->options, + ds.data + 24, + ds.len - 24, + &dhcpv6_universe)) { + log_error("Corrupt IAADDR options."); + option_state_dereference(&addr->options, + MDL); + dfree(addr, MDL); + data_string_forget(&ds, MDL); + return DHCP_R_BADPARSE; + } + } + + if (addr->options != NULL) + log_debug("RCV: | | | X-- " + "[Options]"); + + data_string_forget(&ds, MDL); + + *paddr = addr; + paddr = &addr->next; + } else { + log_error("Invalid IAADDR option cache."); + dfree(addr, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + delete_option(&dhcpv6_universe, options, D6O_IAADDR); + + return ISC_R_SUCCESS; +} + +static isc_result_t +dhc6_parse_prefixes(struct dhc6_addr **ppfx, struct packet *packet, + struct option_state *options) +{ + struct data_string ds; + struct option_cache *oc; + struct dhc6_addr *pfx; + + memset(&ds, 0, sizeof(ds)); + + oc = lookup_option(&dhcpv6_universe, options, D6O_IAPREFIX); + for ( ; oc != NULL ; oc = oc->next) { + pfx = dmalloc(sizeof(*pfx), MDL); + if (pfx == NULL) { + log_error("Out of memory allocating " + "prefix structure."); + return ISC_R_NOMEMORY; + } else if (evaluate_option_cache(&ds, packet, NULL, NULL, + options, NULL, &global_scope, + oc, MDL) && + (ds.len >= 25)) { + + pfx->preferred_life = getULong(ds.data); + pfx->max_life = getULong(ds.data + 4); + pfx->plen = getUChar(ds.data + 8); + pfx->address.len = 16; + memcpy(pfx->address.iabuf, ds.data + 9, 16); + pfx->starts = cur_time; + + log_debug("RCV: | | X-- IAPREFIX %s/%d", + piaddr(pfx->address), (int)pfx->plen); + log_debug("RCV: | | | X-- Preferred lifetime %u.", + pfx->preferred_life); + log_debug("RCV: | | | X-- Max lifetime %u.", + pfx->max_life); + + /* Sanity check over the prefix length */ + if ((pfx->plen < 4) || (pfx->plen > 128)) { + log_debug("RCV: | | | !-- INVALID prefix " + "length, IAPREFIX discarded. " + "Check your server configuration."); + dfree(pfx, MDL); + data_string_forget(&ds, MDL); + continue; + } + /* + * RFC 3633 section 10 says we must discard + * prefixes whose pref is later than valid. + */ + if ((pfx->preferred_life > pfx->max_life)) { + log_debug("RCV: | | | !-- INVALID lifetimes, " + "IAPREFIX discarded. Check your " + "server configuration."); + dfree(pfx, MDL); + data_string_forget(&ds, MDL); + continue; + } + + /* + * Fortunately this is the last recursion in the + * protocol. + */ + if (ds.len > 25) { + if (!option_state_allocate(&pfx->options, + MDL)) { + log_error("Out of memory allocating " + "IAPREFIX option state."); + dfree(pfx, MDL); + data_string_forget(&ds, MDL); + return ISC_R_NOMEMORY; + } + + if (!parse_option_buffer(pfx->options, + ds.data + 25, + ds.len - 25, + &dhcpv6_universe)) { + log_error("Corrupt IAPREFIX options."); + option_state_dereference(&pfx->options, + MDL); + dfree(pfx, MDL); + data_string_forget(&ds, MDL); + return DHCP_R_BADPARSE; + } + } + + if (pfx->options != NULL) + log_debug("RCV: | | | X-- " + "[Options]"); + + data_string_forget(&ds, MDL); + + *ppfx = pfx; + ppfx = &pfx->next; + } else { + log_error("Invalid IAPREFIX option cache."); + dfree(pfx, MDL); + if (ds.len != 0) + data_string_forget(&ds, MDL); + return ISC_R_UNEXPECTED; + } + } + delete_option(&dhcpv6_universe, options, D6O_IAPREFIX); + + return ISC_R_SUCCESS; +} + +/* Clean up a lease object, deallocate all its parts, and set it to NULL. */ +void +dhc6_lease_destroy(struct dhc6_lease **src, const char *file, int line) +{ + struct dhc6_ia *ia, *nia; + struct dhc6_lease *lease; + + if (src == NULL || *src == NULL) { + log_error("Attempt to destroy null lease."); + return; + } + lease = *src; + + if (lease->server_id.len != 0) + data_string_forget(&lease->server_id, file, line); + + for (ia = lease->bindings ; ia != NULL ; ia = nia) { + nia = ia->next; + + dhc6_ia_destroy(&ia, file, line); + } + + if (lease->options != NULL) + option_state_dereference(&lease->options, file, line); + + dfree(lease, file, line); + *src = NULL; +} + +/* + * Traverse the addresses list, and destroy their contents, and NULL the + * list pointer. + */ +static void +dhc6_ia_destroy(struct dhc6_ia **src, const char *file, int line) +{ + struct dhc6_addr *addr, *naddr; + struct dhc6_ia *ia; + + if (src == NULL || *src == NULL) { + log_error("Attempt to destroy null IA."); + return; + } + ia = *src; + + for (addr = ia->addrs ; addr != NULL ; addr = naddr) { + naddr = addr->next; + + if (addr->options != NULL) + option_state_dereference(&addr->options, file, line); + + dfree(addr, file, line); + } + + if (ia->options != NULL) + option_state_dereference(&ia->options, file, line); + + dfree(ia, file, line); + *src = NULL; +} + +/* + * For a given lease, insert it into the tail of the lease list. Upon + * finding a duplicate by server id, remove it and take over its position. + */ +static void +insert_lease(struct dhc6_lease **head, struct dhc6_lease *new) +{ + while (*head != NULL) { + if ((*head)->server_id.len == new->server_id.len && + memcmp((*head)->server_id.data, new->server_id.data, + new->server_id.len) == 0) { + new->next = (*head)->next; + dhc6_lease_destroy(head, MDL); + break; + } + + head= &(*head)->next; + } + + *head = new; + return; +} + +/* + * Not really clear what to do here yet. + */ +static int +dhc6_score_lease(struct client_state *client, struct dhc6_lease *lease) +{ + struct dhc6_ia *ia; + struct dhc6_addr *addr; + struct option **req; + int i; + + if (lease->score) + return lease->score; + + lease->score = 1; + + /* If this lease lacks a required option, dump it. */ + /* XXX: we should be able to cache the failure... */ + req = client->config->required_options; + if (req != NULL) { + for (i = 0 ; req[i] != NULL ; i++) { + if (lookup_option(&dhcpv6_universe, lease->options, + req[i]->code) == NULL) { + lease->score = 0; + return lease->score; + } + } + } + + /* If this lease contains a requested option, improve its score. */ + req = client->config->requested_options; + if (req != NULL) { + for (i = 0 ; req[i] != NULL ; i++) { + if (lookup_option(&dhcpv6_universe, lease->options, + req[i]->code) != NULL) + lease->score++; + } + } + + for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + lease->score += 50; + + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + lease->score += 100; + } + } + + return lease->score; +} + +/* + * start_init6() kicks off the process, transmitting a packet and + * scheduling a retransmission event. + */ +void +start_init6(struct client_state *client) +{ + struct timeval tv; + + log_debug("PRC: Soliciting for leases (INIT)."); + client->state = S_INIT; + + /* Initialize timers, RFC3315 section 17.1.2. */ + client->IRT = SOL_TIMEOUT * 100; + client->MRT = SOL_MAX_RT * 100; + client->MRC = 0; + /* Default is 0 (no max) but -1 changes this. */ + if (!onetry) + client->MRD = 0; + else + client->MRD = client->config->timeout; + + dhc6_retrans_init(client); + + /* + * RFC3315 section 17.1.2 goes out of its way: + * Also, the first RT MUST be selected to be strictly greater than IRT + * by choosing RAND to be strictly greater than 0. + */ + /* if RAND < 0 then RAND = -RAND */ + if (client->RT <= client->IRT) + client->RT = client->IRT + (client->IRT - client->RT); + /* if RAND == 0 then RAND = 1 */ + if (client->RT <= client->IRT) + client->RT = client->IRT + 1; + + client->v6_handler = init_handler; + + /* + * RFC3315 section 17.1.2 says we MUST start the first packet + * between 0 and SOL_MAX_DELAY seconds. The good news is + * SOL_MAX_DELAY is 1. + */ + tv.tv_sec = cur_tv.tv_sec; + tv.tv_usec = cur_tv.tv_usec; + tv.tv_usec += (random() % (SOL_MAX_DELAY * 100)) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_init6, client, NULL, NULL); + + if (nowait) + go_daemon(); +} + +/* + * start_info_request6() kicks off the process, transmitting an info + * request packet and scheduling a retransmission event. + */ +void +start_info_request6(struct client_state *client) +{ + struct timeval tv; + + log_debug("PRC: Requesting information (INIT)."); + client->state = S_INIT; + + /* Initialize timers, RFC3315 section 18.1.5. */ + client->IRT = INF_TIMEOUT * 100; + client->MRT = INF_MAX_RT * 100; + client->MRC = 0; + /* Default is 0 (no max) but -1 changes this. */ + if (!onetry) + client->MRD = 0; + else + client->MRD = client->config->timeout; + + dhc6_retrans_init(client); + + client->v6_handler = info_request_handler; + + /* + * RFC3315 section 18.1.5 says we MUST start the first packet + * between 0 and INF_MAX_DELAY seconds. The good news is + * INF_MAX_DELAY is 1. + */ + tv.tv_sec = cur_tv.tv_sec; + tv.tv_usec = cur_tv.tv_usec; + tv.tv_usec += (random() % (INF_MAX_DELAY * 100)) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_info_request6, client, NULL, NULL); + + if (nowait) + go_daemon(); +} + +/* + * start_confirm6() kicks off an "init-reboot" version of the process, at + * startup to find out if old bindings are 'fair' and at runtime whenever + * a link cycles state we'll eventually want to do this. + */ +void +start_confirm6(struct client_state *client) +{ + struct timeval tv; + + /* If there is no active lease, there is nothing to check. */ + if ((client->active_lease == NULL) || + !active_prefix(client) || + client->active_lease->released) { + start_init6(client); + return; + } + + log_debug("PRC: Confirming active lease (INIT-REBOOT)."); + client->state = S_REBOOTING; + + /* Initialize timers, RFC3315 section 17.1.3. */ + client->IRT = CNF_TIMEOUT * 100; + client->MRT = CNF_MAX_RT * 100; + client->MRC = 0; + client->MRD = CNF_MAX_RD; + + dhc6_retrans_init(client); + + client->v6_handler = reply_handler; + + /* + * RFC3315 section 18.1.2 says we MUST start the first packet + * between 0 and CNF_MAX_DELAY seconds. The good news is + * CNF_MAX_DELAY is 1. + */ + tv.tv_sec = cur_tv.tv_sec; + tv.tv_usec = cur_tv.tv_usec; + tv.tv_usec += (random() % (CNF_MAX_DELAY * 100)) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + if (wanted_ia_pd != 0) { + client->state = S_REBINDING; + client->refresh_type = DHCPV6_REBIND; + add_timeout(&tv, do_refresh6, client, NULL, NULL); + } else + add_timeout(&tv, do_confirm6, client, NULL, NULL); +} + +/* + * check_timing6() check on the timing for sending a v6 message + * and then do the basic initialization for a v6 message. + */ +#define CHK_TIM_SUCCESS 0 +#define CHK_TIM_MRC_EXCEEDED 1 +#define CHK_TIM_MRD_EXCEEDED 2 +#define CHK_TIM_ALLOC_FAILURE 3 + +int +check_timing6 (struct client_state *client, u_int8_t msg_type, + char *msg_str, struct dhc6_lease *lease, + struct data_string *ds) +{ + struct timeval elapsed; + + /* + * Start_time starts at the first transmission. + */ + if (client->txcount == 0) { + client->start_time.tv_sec = cur_tv.tv_sec; + client->start_time.tv_usec = cur_tv.tv_usec; + } else if ((client->MRC != 0) && (client->txcount > client->MRC)) { + log_info("Max retransmission count exceeded."); + return(CHK_TIM_MRC_EXCEEDED); + } + + /* elapsed = cur - start */ + elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec; + elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec; + if (elapsed.tv_usec < 0) { + elapsed.tv_sec -= 1; + elapsed.tv_usec += 1000000; + } + + /* Check if finished (-1 argument). */ + if ((client->MRD != 0) && (elapsed.tv_sec > client->MRD)) { + log_info("Max retransmission duration exceeded."); + return(CHK_TIM_MRD_EXCEEDED); + } + + memset(ds, 0, sizeof(*ds)); + if (!buffer_allocate(&(ds->buffer), 4, MDL)) { + log_error("Unable to allocate memory for %s.", msg_str); + return(CHK_TIM_ALLOC_FAILURE); + } + ds->data = ds->buffer->data; + ds->len = 4; + + ds->buffer->data[0] = msg_type; + memcpy(ds->buffer->data + 1, client->dhcpv6_transaction_id, 3); + + /* Form an elapsed option. */ + /* Maximum value is 65535 1/100s coded as 0xffff. */ + if ((elapsed.tv_sec < 0) || (elapsed.tv_sec > 655) || + ((elapsed.tv_sec == 655) && (elapsed.tv_usec > 350000))) { + client->elapsed = 0xffff; + } else { + client->elapsed = elapsed.tv_sec * 100; + client->elapsed += elapsed.tv_usec / 10000; + } + + if (client->elapsed == 0) + log_debug("XMT: Forming %s, 0 ms elapsed.", msg_str); + else + log_debug("XMT: Forming %s, %u0 ms elapsed.", msg_str, + (unsigned)client->elapsed); + + client->elapsed = htons(client->elapsed); + + make_client6_options(client, &client->sent_options, lease, msg_type); + + return(CHK_TIM_SUCCESS); +} + +/* + * do_init6() marshals and transmits a solicit. + */ +void +do_init6(void *input) +{ + struct client_state *client; + struct dhc6_ia *old_ia; + struct dhc6_addr *old_addr; + struct data_string ds; + struct data_string ia; + struct data_string addr; + struct timeval tv; + u_int32_t t1, t2; + int i, idx, len, send_ret; + + client = input; + + /* + * In RFC3315 section 17.1.2, the retransmission timer is + * used as the selecting timer. + */ + if (client->advertised_leases != NULL) { + start_selecting6(client); + return; + } + + switch(check_timing6(client, DHCPV6_SOLICIT, "Solicit", NULL, &ds)) { + case CHK_TIM_MRC_EXCEEDED: + case CHK_TIM_ALLOC_FAILURE: + return; + case CHK_TIM_MRD_EXCEEDED: + client->state = S_STOPPED; + if (client->active_lease != NULL) { + dhc6_lease_destroy(&client->active_lease, MDL); + client->active_lease = NULL; + } + /* Stop if and only if this is the last client. */ + if (stopping_finished()) + exit(2); + return; + } + + /* + * Fetch any configured 'sent' options (includes DUID) in wire format. + */ + dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, + NULL, client->sent_options, &global_scope, + &dhcpv6_universe); + + /* Use a specific handler with rapid-commit. */ + if (lookup_option(&dhcpv6_universe, client->sent_options, + D6O_RAPID_COMMIT) != NULL) { + client->v6_handler = rapid_commit_handler; + } + + /* Append IA_NA. */ + for (i = 0; i < wanted_ia_na; i++) { + /* + * XXX: maybe the IA_NA('s) should be put into the sent_options + * cache. They'd have to be pulled down as they also contain + * different option caches in the same universe... + */ + memset(&ia, 0, sizeof(ia)); + if (!buffer_allocate(&ia.buffer, 12, MDL)) { + log_error("Unable to allocate memory for IA_NA."); + data_string_forget(&ds, MDL); + return; + } + ia.data = ia.buffer->data; + ia.len = 12; + + /* + * A simple IAID is the last 4 bytes + * of the hardware address. + */ + if (client->interface->hw_address.hlen > 4) { + idx = client->interface->hw_address.hlen - 4; + len = 4; + } else { + idx = 0; + len = client->interface->hw_address.hlen; + } + memcpy(ia.buffer->data, + client->interface->hw_address.hbuf + idx, + len); + if (i) + ia.buffer->data[3] += i; + + t1 = client->config->requested_lease / 2; + t2 = t1 + (t1 / 2); + putULong(ia.buffer->data + 4, t1); + putULong(ia.buffer->data + 8, t2); + + log_debug("XMT: X-- IA_NA %s", + print_hex_1(4, ia.buffer->data, 55)); + log_debug("XMT: | X-- Request renew in +%u", (unsigned)t1); + log_debug("XMT: | X-- Request rebind in +%u", (unsigned)t2); + + if ((client->active_lease != NULL) && + ((old_ia = find_ia(client->active_lease->bindings, + D6O_IA_NA, + (char *)ia.buffer->data)) != NULL)) { + /* + * For each address in the old IA_NA, + * request a binding. + */ + memset(&addr, 0, sizeof(addr)); + for (old_addr = old_ia->addrs ; old_addr != NULL ; + old_addr = old_addr->next) { + if (old_addr->address.len != 16) { + log_error("Invalid IPv6 address " + "length %d. " + "Ignoring. (%s:%d)", + old_addr->address.len, + MDL); + continue; + } + + if (!buffer_allocate(&addr.buffer, 24, MDL)) { + log_error("Unable to allocate memory " + "for IAADDR."); + data_string_forget(&ia, MDL); + data_string_forget(&ds, MDL); + return; + } + addr.data = addr.buffer->data; + addr.len = 24; + + memcpy(addr.buffer->data, + old_addr->address.iabuf, + 16); + + t1 = client->config->requested_lease; + t2 = t1 + (t1 / 2); + putULong(addr.buffer->data + 16, t1); + putULong(addr.buffer->data + 20, t2); + + log_debug("XMT: | X-- Request address %s.", + piaddr(old_addr->address)); + log_debug("XMT: | | X-- Request " + "preferred in +%u", + (unsigned)t1); + log_debug("XMT: | | X-- Request valid " + "in +%u", + (unsigned)t2); + + append_option(&ia, &dhcpv6_universe, + iaaddr_option, + &addr); + + data_string_forget(&addr, MDL); + } + } + + append_option(&ds, &dhcpv6_universe, ia_na_option, &ia); + data_string_forget(&ia, MDL); + } + + /* Append IA_TA. */ + for (i = 0; i < wanted_ia_ta; i++) { + /* + * XXX: maybe the IA_TA('s) should be put into the sent_options + * cache. They'd have to be pulled down as they also contain + * different option caches in the same universe... + */ + memset(&ia, 0, sizeof(ia)); + if (!buffer_allocate(&ia.buffer, 4, MDL)) { + log_error("Unable to allocate memory for IA_TA."); + data_string_forget(&ds, MDL); + return; + } + ia.data = ia.buffer->data; + ia.len = 4; + + /* + * A simple IAID is the last 4 bytes + * of the hardware address. + */ + if (client->interface->hw_address.hlen > 4) { + idx = client->interface->hw_address.hlen - 4; + len = 4; + } else { + idx = 0; + len = client->interface->hw_address.hlen; + } + memcpy(ia.buffer->data, + client->interface->hw_address.hbuf + idx, + len); + if (i) + ia.buffer->data[3] += i; + + log_debug("XMT: X-- IA_TA %s", + print_hex_1(4, ia.buffer->data, 55)); + + if ((client->active_lease != NULL) && + ((old_ia = find_ia(client->active_lease->bindings, + D6O_IA_TA, + (char *)ia.buffer->data)) != NULL)) { + /* + * For each address in the old IA_TA, + * request a binding. + */ + memset(&addr, 0, sizeof(addr)); + for (old_addr = old_ia->addrs ; old_addr != NULL ; + old_addr = old_addr->next) { + if (old_addr->address.len != 16) { + log_error("Invalid IPv6 address " + "length %d. " + "Ignoring. (%s:%d)", + old_addr->address.len, + MDL); + continue; + } + + if (!buffer_allocate(&addr.buffer, 24, MDL)) { + log_error("Unable to allocate memory " + "for IAADDR."); + data_string_forget(&ia, MDL); + data_string_forget(&ds, MDL); + return; + } + addr.data = addr.buffer->data; + addr.len = 24; + + memcpy(addr.buffer->data, + old_addr->address.iabuf, + 16); + + t1 = client->config->requested_lease; + t2 = t1 + (t1 / 2); + putULong(addr.buffer->data + 16, t1); + putULong(addr.buffer->data + 20, t2); + + log_debug("XMT: | X-- Request address %s.", + piaddr(old_addr->address)); + log_debug("XMT: | | X-- Request " + "preferred in +%u", + (unsigned)t1); + log_debug("XMT: | | X-- Request valid " + "in +%u", + (unsigned)t2); + + append_option(&ia, &dhcpv6_universe, + iaaddr_option, + &addr); + + data_string_forget(&addr, MDL); + } + } + + append_option(&ds, &dhcpv6_universe, ia_ta_option, &ia); + data_string_forget(&ia, MDL); + } + + /* Append IA_PD. */ + for (i = 0; i < wanted_ia_pd; i++) { + /* + * XXX: maybe the IA_PD('s) should be put into the sent_options + * cache. They'd have to be pulled down as they also contain + * different option caches in the same universe... + */ + memset(&ia, 0, sizeof(ia)); + if (!buffer_allocate(&ia.buffer, 12, MDL)) { + log_error("Unable to allocate memory for IA_PD."); + data_string_forget(&ds, MDL); + return; + } + ia.data = ia.buffer->data; + ia.len = 12; + + /* + * A simple IAID is the last 4 bytes + * of the hardware address. + */ + if (client->interface->hw_address.hlen > 4) { + idx = client->interface->hw_address.hlen - 4; + len = 4; + } else { + idx = 0; + len = client->interface->hw_address.hlen; + } + memcpy(ia.buffer->data, + client->interface->hw_address.hbuf + idx, + len); + if (i) + ia.buffer->data[3] += i; + + t1 = client->config->requested_lease / 2; + t2 = t1 + (t1 / 2); + putULong(ia.buffer->data + 4, t1); + putULong(ia.buffer->data + 8, t2); + + log_debug("XMT: X-- IA_PD %s", + print_hex_1(4, ia.buffer->data, 55)); + log_debug("XMT: | X-- Request renew in +%u", (unsigned)t1); + log_debug("XMT: | X-- Request rebind in +%u", (unsigned)t2); + + if ((client->active_lease != NULL) && + ((old_ia = find_ia(client->active_lease->bindings, + D6O_IA_PD, + (char *)ia.buffer->data)) != NULL)) { + /* + * For each prefix in the old IA_PD, + * request a binding. + */ + memset(&addr, 0, sizeof(addr)); + for (old_addr = old_ia->addrs ; old_addr != NULL ; + old_addr = old_addr->next) { + if (old_addr->address.len != 16) { + log_error("Invalid IPv6 prefix, " + "Ignoring. (%s:%d)", + MDL); + continue; + } + + if (!buffer_allocate(&addr.buffer, 25, MDL)) { + log_error("Unable to allocate memory " + "for IAPREFIX."); + data_string_forget(&ia, MDL); + data_string_forget(&ds, MDL); + return; + } + addr.data = addr.buffer->data; + addr.len = 25; + + t1 = client->config->requested_lease; + t2 = t1 + (t1 / 2); + putULong(addr.buffer->data, t1); + putULong(addr.buffer->data + 4, t2); + + putUChar(addr.buffer->data + 8, + old_addr->plen); + memcpy(addr.buffer->data + 9, + old_addr->address.iabuf, + 16); + + log_debug("XMT: | X-- Request prefix %s/%u.", + piaddr(old_addr->address), + (unsigned) old_addr->plen); + log_debug("XMT: | | X-- Request " + "preferred in +%u", + (unsigned)t1); + log_debug("XMT: | | X-- Request valid " + "in +%u", + (unsigned)t2); + + append_option(&ia, &dhcpv6_universe, + iaprefix_option, + &addr); + + data_string_forget(&addr, MDL); + } + } + + append_option(&ds, &dhcpv6_universe, ia_pd_option, &ia); + data_string_forget(&ia, MDL); + } + + /* Transmit and wait. */ + + log_info("XMT: Solicit on %s, interval %ld0ms.", + client->name ? client->name : client->interface->name, + (long int)client->RT); + + send_ret = send_packet6(client->interface, + ds.data, ds.len, &DHCPv6DestAddr); + if (send_ret != ds.len) { + log_error("dhc6: send_packet6() sent %d of %d bytes", + send_ret, ds.len); + } + + data_string_forget(&ds, MDL); + + /* Wait RT */ + tv.tv_sec = cur_tv.tv_sec + client->RT / 100; + tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_init6, client, NULL, NULL); + + dhc6_retrans_advance(client); +} + +/* do_info_request6() marshals and transmits an information-request. */ +void +do_info_request6(void *input) +{ + struct client_state *client; + struct data_string ds; + struct timeval tv; + int send_ret; + + client = input; + + switch(check_timing6(client, DHCPV6_INFORMATION_REQUEST, + "Info-Request", NULL, &ds)) { + case CHK_TIM_MRC_EXCEEDED: + case CHK_TIM_ALLOC_FAILURE: + return; + case CHK_TIM_MRD_EXCEEDED: + exit(2); + case CHK_TIM_SUCCESS: + break; + } + + /* Fetch any configured 'sent' options (includes DUID) in wire format. + */ + dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, + NULL, client->sent_options, &global_scope, + &dhcpv6_universe); + + /* Transmit and wait. */ + + log_info("XMT: Info-Request on %s, interval %ld0ms.", + client->name ? client->name : client->interface->name, + (long int)client->RT); + + send_ret = send_packet6(client->interface, + ds.data, ds.len, &DHCPv6DestAddr); + if (send_ret != ds.len) { + log_error("dhc6: send_packet6() sent %d of %d bytes", + send_ret, ds.len); + } + + data_string_forget(&ds, MDL); + + /* Wait RT */ + tv.tv_sec = cur_tv.tv_sec + client->RT / 100; + tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_info_request6, client, NULL, NULL); + + dhc6_retrans_advance(client); +} + +/* do_confirm6() creates a Confirm packet and transmits it. This function + * is called on every timeout to (re)transmit. + */ +void +do_confirm6(void *input) +{ + struct client_state *client; + struct data_string ds; + int send_ret; + struct timeval tv; + + client = input; + + if (client->active_lease == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + /* In section 17.1.3, it is said: + * + * If the client receives no responses before the message + * transmission process terminates, as described in section 14, + * the client SHOULD continue to use any IP addresses, using the + * last known lifetimes for those addresses, and SHOULD continue + * to use any other previously obtained configuration parameters. + * + * So if confirm times out, we go active. + * + * XXX: Should we reduce all IA's t1 to 0, so that we renew and + * stick there until we get a reply? + */ + + switch(check_timing6(client, DHCPV6_CONFIRM, "Confirm", + client->active_lease, &ds)) { + case CHK_TIM_MRC_EXCEEDED: + case CHK_TIM_MRD_EXCEEDED: + start_bound(client); + return; + case CHK_TIM_ALLOC_FAILURE: + return; + case CHK_TIM_SUCCESS: + break; + } + + /* Fetch any configured 'sent' options (includes DUID') in wire format. + */ + dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL, + client->sent_options, &global_scope, + &dhcpv6_universe); + + /* Append IA's. */ + if (wanted_ia_na && + dhc6_add_ia_na(client, &ds, client->active_lease, + DHCPV6_CONFIRM) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + return; + } + if (wanted_ia_ta && + dhc6_add_ia_ta(client, &ds, client->active_lease, + DHCPV6_CONFIRM) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + return; + } + + /* Transmit and wait. */ + + log_info("XMT: Confirm on %s, interval %ld0ms.", + client->name ? client->name : client->interface->name, + (long int)client->RT); + + send_ret = send_packet6(client->interface, ds.data, ds.len, + &DHCPv6DestAddr); + if (send_ret != ds.len) { + log_error("dhc6: sendpacket6() sent %d of %d bytes", + send_ret, ds.len); + } + + data_string_forget(&ds, MDL); + + /* Wait RT */ + tv.tv_sec = cur_tv.tv_sec + client->RT / 100; + tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_confirm6, client, NULL, NULL); + + dhc6_retrans_advance(client); +} + +/* + * Release addresses. + */ +void +start_release6(struct client_state *client) +{ + /* Cancel any pending transmissions */ + cancel_timeout(do_confirm6, client); + cancel_timeout(do_select6, client); + cancel_timeout(do_refresh6, client); + cancel_timeout(do_release6, client); + client->state = S_STOPPED; + + /* + * It is written: "The client MUST NOT use any of the addresses it + * is releasing as the source address in the Release message or in + * any subsequently transmitted message." So unconfigure now. + */ + unconfigure6(client, "RELEASE6"); + + /* Note this in the lease file. */ + if (client->active_lease == NULL) + return; + client->active_lease->released = ISC_TRUE; + write_client6_lease(client, client->active_lease, 0, 1); + + /* Set timers per RFC3315 section 18.1.6. */ + client->IRT = REL_TIMEOUT * 100; + client->MRT = 0; + client->MRC = REL_MAX_RC; + client->MRD = 0; + + dhc6_retrans_init(client); + client->v6_handler = reply_handler; + + do_release6(client); +} +/* + * do_release6() creates a Release packet and transmits it. + */ +static void +do_release6(void *input) +{ + struct client_state *client; + struct data_string ds; + int send_ret; + struct timeval tv; + + client = input; + + if ((client->active_lease == NULL) || !active_prefix(client)) + return; + + switch(check_timing6(client, DHCPV6_RELEASE, "Release", + client->active_lease, &ds)) { + case CHK_TIM_MRC_EXCEEDED: + case CHK_TIM_ALLOC_FAILURE: + case CHK_TIM_MRD_EXCEEDED: + goto release_done; + case CHK_TIM_SUCCESS: + break; + } + + /* + * Don't use unicast as we don't know if we still have an + * available address with enough scope. + */ + + dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL, + client->sent_options, &global_scope, + &dhcpv6_universe); + + /* Append IA's (but don't release temporary addresses). */ + if (wanted_ia_na && + dhc6_add_ia_na(client, &ds, client->active_lease, + DHCPV6_RELEASE) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + goto release_done; + } + if (wanted_ia_pd && + dhc6_add_ia_pd(client, &ds, client->active_lease, + DHCPV6_RELEASE) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + goto release_done; + } + + /* Transmit and wait. */ + log_info("XMT: Release on %s, interval %ld0ms.", + client->name ? client->name : client->interface->name, + (long int)client->RT); + + send_ret = send_packet6(client->interface, ds.data, ds.len, + &DHCPv6DestAddr); + if (send_ret != ds.len) { + log_error("dhc6: sendpacket6() sent %d of %d bytes", + send_ret, ds.len); + } + + data_string_forget(&ds, MDL); + + /* Wait RT */ + tv.tv_sec = cur_tv.tv_sec + client->RT / 100; + tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_release6, client, NULL, NULL); + dhc6_retrans_advance(client); + return; + + release_done: + dhc6_lease_destroy(&client->active_lease, MDL); + client->active_lease = NULL; + if (stopping_finished()) + exit(0); +} + +/* status_log() just puts a status code into displayable form and logs it + * to info level. + */ +static void +status_log(int code, const char *scope, const char *additional, int len) +{ + const char *msg = NULL; + + switch(code) { + case STATUS_Success: + msg = "Success"; + break; + + case STATUS_UnspecFail: + msg = "UnspecFail"; + break; + + case STATUS_NoAddrsAvail: + msg = "NoAddrsAvail"; + break; + + case STATUS_NoBinding: + msg = "NoBinding"; + break; + + case STATUS_NotOnLink: + msg = "NotOnLink"; + break; + + case STATUS_UseMulticast: + msg = "UseMulticast"; + break; + + case STATUS_NoPrefixAvail: + msg = "NoPrefixAvail"; + break; + + default: + msg = "UNKNOWN"; + break; + } + + if (len > 0) + log_info("%s status code %s: %s", scope, msg, + print_hex_1(len, + (const unsigned char *)additional, 50)); + else + log_info("%s status code %s.", scope, msg); +} + +/* Acquire a status code. + */ +static isc_result_t +dhc6_get_status_code(struct option_state *options, unsigned *code, + struct data_string *msg) +{ + struct option_cache *oc; + struct data_string ds; + isc_result_t rval = ISC_R_SUCCESS; + + if ((options == NULL) || (code == NULL)) + return DHCP_R_INVALIDARG; + + if ((msg != NULL) && (msg->len != 0)) + return DHCP_R_INVALIDARG; + + memset(&ds, 0, sizeof(ds)); + + /* Assume success if there is no option. */ + *code = STATUS_Success; + + oc = lookup_option(&dhcpv6_universe, options, D6O_STATUS_CODE); + if ((oc != NULL) && + evaluate_option_cache(&ds, NULL, NULL, NULL, options, + NULL, &global_scope, oc, MDL)) { + if (ds.len < 2) { + log_error("Invalid status code length %d.", ds.len); + rval = DHCP_R_FORMERR; + } else + *code = getUShort(ds.data); + + if ((msg != NULL) && (ds.len > 2)) { + data_string_copy(msg, &ds, MDL); + msg->data += 2; + msg->len -= 2; + } + + data_string_forget(&ds, MDL); + return rval; + } + + return ISC_R_NOTFOUND; +} + +/* Look at status codes in an advertise, and reform the return value. + */ +static isc_result_t +dhc6_check_status(isc_result_t rval, struct option_state *options, + const char *scope, unsigned *code) +{ + struct data_string msg; + isc_result_t status; + + if ((scope == NULL) || (code == NULL)) + return DHCP_R_INVALIDARG; + + /* If we don't find a code, we assume success. */ + *code = STATUS_Success; + + /* If there is no options cache, then there is no code. */ + if (options != NULL) { + memset(&msg, 0, sizeof(msg)); + status = dhc6_get_status_code(options, code, &msg); + + if (status == ISC_R_SUCCESS) { + status_log(*code, scope, (char *)msg.data, msg.len); + data_string_forget(&msg, MDL); + + if (*code != STATUS_Success) + rval = ISC_R_FAILURE; + + } else if (status != ISC_R_NOTFOUND) + rval = status; + } + + return rval; +} + +/* Look in the packet, any IA's, and any IAADDR's within those IA's to find + * status code options that are not SUCCESS. + */ +static isc_result_t +dhc6_check_advertise(struct dhc6_lease *lease) +{ + struct dhc6_ia *ia; + struct dhc6_addr *addr; + isc_result_t rval = ISC_R_SUCCESS; + int have_addrs = ISC_FALSE; + unsigned code; + const char *scope; + + rval = dhc6_check_status(rval, lease->options, "message", &code); + + for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + switch (ia->ia_type) { + case D6O_IA_NA: + scope = "IA_NA"; + break; + case D6O_IA_TA: + scope = "IA_TA"; + break; + case D6O_IA_PD: + scope = "IA_PD"; + break; + default: + log_error("dhc6_check_advertise: no type."); + return ISC_R_FAILURE; + } + rval = dhc6_check_status(rval, ia->options, scope, &code); + + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + if (ia->ia_type != D6O_IA_PD) + scope = "IAADDR"; + else + scope = "IAPREFIX"; + rval = dhc6_check_status(rval, addr->options, + scope, &code); + have_addrs = ISC_TRUE; + } + } + + if (have_addrs != ISC_TRUE) + rval = ISC_R_ADDRNOTAVAIL; + + return rval; +} + +/* status code <-> action matrix for the client in INIT state + * (rapid/commit). Returns always false as no action is defined. + */ +static isc_boolean_t +dhc6_init_action(struct client_state *client, isc_result_t *rvalp, + unsigned code) +{ + if (rvalp == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + if (client == NULL) { + *rvalp = DHCP_R_INVALIDARG; + return ISC_FALSE; + } + + if (*rvalp == ISC_R_SUCCESS) + return ISC_FALSE; + + /* No possible action in any case... */ + return ISC_FALSE; +} + +/* status code <-> action matrix for the client in SELECT state + * (request/reply). Returns true if action was taken (and the + * packet should be ignored), or false if no action was taken. + */ +static isc_boolean_t +dhc6_select_action(struct client_state *client, isc_result_t *rvalp, + unsigned code) +{ + struct dhc6_lease *lease; + isc_result_t rval; + + if (rvalp == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + if (client == NULL) { + *rvalp = DHCP_R_INVALIDARG; + return ISC_FALSE; + } + rval = *rvalp; + + if (rval == ISC_R_SUCCESS) + return ISC_FALSE; + + switch (code) { + /* We may have an earlier failure status code (so no + * success rval), and a success code now. This + * doesn't upgrade the rval to success, but it does + * mean we take no action here. + */ + case STATUS_Success: + /* Gimpy server, or possibly an attacker. */ + case STATUS_NoBinding: + case STATUS_UseMulticast: + /* Take no action. */ + return ISC_FALSE; + + /* If the server can't deal with us, either try the + * next advertised server, or continue retrying if there + * weren't any. + */ + default: + case STATUS_UnspecFail: + if (client->advertised_leases != NULL) { + dhc6_lease_destroy(&client->selected_lease, MDL); + client->selected_lease = NULL; + + start_selecting6(client); + + break; + } else /* Take no action - continue to retry. */ + return ISC_FALSE; + + /* If the server has no addresses, try other servers if + * we got some, otherwise go to INIT to hope for more + * servers. + */ + case STATUS_NoAddrsAvail: + case STATUS_NoPrefixAvail: + if (client->state == S_REBOOTING) + return ISC_FALSE; + + if (client->selected_lease == NULL) + log_fatal("Impossible case at %s:%d.", MDL); + + dhc6_lease_destroy(&client->selected_lease, MDL); + client->selected_lease = NULL; + + if (client->advertised_leases != NULL) + start_selecting6(client); + else + start_init6(client); + + break; + + /* If we got a NotOnLink from a Confirm, then we're not + * on link. Kill the old-active binding and start over. + * + * If we got a NotOnLink from our Request, something weird + * happened. Start over from scratch anyway. + */ + case STATUS_NotOnLink: + if (client->state == S_REBOOTING) { + if (client->active_lease == NULL) + log_fatal("Impossible case at %s:%d.", MDL); + + dhc6_lease_destroy(&client->active_lease, MDL); + } else { + if (client->selected_lease == NULL) + log_fatal("Impossible case at %s:%d.", MDL); + + dhc6_lease_destroy(&client->selected_lease, MDL); + client->selected_lease = NULL; + + while (client->advertised_leases != NULL) { + lease = client->advertised_leases; + client->advertised_leases = lease->next; + + dhc6_lease_destroy(&lease, MDL); + } + } + + start_init6(client); + break; + } + + return ISC_TRUE; +} + +static void +dhc6_withdraw_lease(struct client_state *client) +{ + struct dhc6_ia *ia; + struct dhc6_addr *addr; + + if ((client == NULL) || (client->active_lease == NULL)) + return; + + for (ia = client->active_lease->bindings ; ia != NULL ; + ia = ia->next) { + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + addr->max_life = addr->preferred_life = 0; + } + } + + /* Perform expiry. */ + do_expire(client); +} + +/* status code <-> action matrix for the client in BOUND state + * (request/reply). Returns true if action was taken (and the + * packet should be ignored), or false if no action was taken. + */ +static isc_boolean_t +dhc6_reply_action(struct client_state *client, isc_result_t *rvalp, + unsigned code) +{ + isc_result_t rval; + + if (rvalp == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + if (client == NULL) { + *rvalp = DHCP_R_INVALIDARG; + return ISC_FALSE; + } + rval = *rvalp; + + if (rval == ISC_R_SUCCESS) + return ISC_FALSE; + + switch (code) { + /* It's possible an earlier status code set rval to a failure + * code, and we've encountered a later success. + */ + case STATUS_Success: + /* In "refreshes" (where we get replies), we probably + * still have a valid lease. So "take no action" and + * the upper levels will keep retrying until the lease + * expires (or we rebind). + */ + case STATUS_UnspecFail: + /* For unknown codes...it's a soft (retryable) error. */ + default: + return ISC_FALSE; + + /* The server is telling us to use a multicast address, so + * we have to delete the unicast option from the active + * lease, then allow retransmission to occur normally. + * (XXX: It might be preferable in this case to retransmit + * sooner than the current interval, but for now we don't.) + */ + case STATUS_UseMulticast: + if (client->active_lease != NULL) + delete_option(&dhcp_universe, + client->active_lease->options, + D6O_UNICAST); + return ISC_FALSE; + + /* "When the client receives a NotOnLink status from the + * server in response to a Request, the client can either + * re-issue the Request without specifying any addresses + * or restart the DHCP server discovery process." + * + * This is strange. If competing server evaluation is + * useful (and therefore in the protocol), then why would + * a client's first reaction be to request from the same + * server on a different link? Surely you'd want to + * re-evaluate your server selection. + * + * Well, I guess that's the answer. + */ + case STATUS_NotOnLink: + /* In this case, we need to rescind all current active + * bindings (just 'expire' them all normally, if early). + * They're no use to us on the wrong link. Then head back + * to init, redo server selection and get new addresses. + */ + dhc6_withdraw_lease(client); + break; + + /* "If the status code is NoAddrsAvail, the client has + * received no usable addresses in the IA and may choose + * to try obtaining addresses for the IA from another + * server." + */ + case STATUS_NoAddrsAvail: + case STATUS_NoPrefixAvail: + /* Head back to init, keeping any active bindings (!). */ + start_init6(client); + break; + + /* - sends a Request message if the IA contained a Status + * Code option with the NoBinding status (and does not + * send any additional Renew/Rebind messages) + */ + case STATUS_NoBinding: + if (client->advertised_leases != NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + client->advertised_leases = + dhc6_dup_lease(client->active_lease, MDL); + start_selecting6(client); + break; + } + + return ISC_TRUE; +} + +/* status code <-> action matrix for the client in STOPPED state + * (release/decline). Returns true if action was taken (and the + * packet should be ignored), or false if no action was taken. + * NoBinding is translated into Success. + */ +static isc_boolean_t +dhc6_stop_action(struct client_state *client, isc_result_t *rvalp, + unsigned code) +{ + isc_result_t rval; + + if (rvalp == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + if (client == NULL) { + *rvalp = DHCP_R_INVALIDARG; + return ISC_FALSE; + } + rval = *rvalp; + + if (rval == ISC_R_SUCCESS) + return ISC_FALSE; + + switch (code) { + /* It's possible an earlier status code set rval to a failure + * code, and we've encountered a later success. + */ + case STATUS_Success: + /* For unknown codes...it's a soft (retryable) error. */ + case STATUS_UnspecFail: + default: + return ISC_FALSE; + + /* NoBinding is not an error */ + case STATUS_NoBinding: + if (rval == ISC_R_FAILURE) + *rvalp = ISC_R_SUCCESS; + return ISC_FALSE; + + /* Should not happen */ + case STATUS_NoAddrsAvail: + case STATUS_NoPrefixAvail: + break; + + /* Give up on it */ + case STATUS_NotOnLink: + break; + + /* The server is telling us to use a multicast address, so + * we have to delete the unicast option from the active + * lease, then allow retransmission to occur normally. + * (XXX: It might be preferable in this case to retransmit + * sooner than the current interval, but for now we don't.) + */ + case STATUS_UseMulticast: + if (client->active_lease != NULL) + delete_option(&dhcp_universe, + client->active_lease->options, + D6O_UNICAST); + return ISC_FALSE; + } + + return ISC_TRUE; +} + +/* Look at a new and old lease, and make sure the new information is not + * losing us any state. + */ +static isc_result_t +dhc6_check_reply(struct client_state *client, struct dhc6_lease *new) +{ + isc_boolean_t (*action)(struct client_state *, + isc_result_t *, unsigned); + struct dhc6_ia *ia; + struct dhc6_addr *addr; + isc_result_t rval = ISC_R_SUCCESS; + unsigned code; + const char *scope; + int nscore, sscore; + + if ((client == NULL) || (new == NULL)) + return DHCP_R_INVALIDARG; + + switch (client->state) { + case S_INIT: + action = dhc6_init_action; + break; + + case S_SELECTING: + case S_REBOOTING: + action = dhc6_select_action; + break; + + case S_RENEWING: + case S_REBINDING: + action = dhc6_reply_action; + break; + + case S_STOPPED: + action = dhc6_stop_action; + break; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + return ISC_R_CANCELED; + } + + /* If there is a code to extract, and if there is some + * action to take based on that code, then take the action + * and do not continue. + */ + rval = dhc6_check_status(rval, new->options, "message", &code); + if (action(client, &rval, code)) + return ISC_R_CANCELED; + + for (ia = new->bindings ; ia != NULL ; ia = ia->next) { + switch (ia->ia_type) { + case D6O_IA_NA: + scope = "IA_NA"; + break; + case D6O_IA_TA: + scope = "IA_TA"; + break; + case D6O_IA_PD: + scope = "IA_PD"; + break; + default: + log_error("dhc6_check_reply: no type."); + return DHCP_R_INVALIDARG; + } + rval = dhc6_check_status(rval, ia->options, + scope, &code); + if (action(client, &rval, code)) + return ISC_R_CANCELED; + + for (addr = ia->addrs ; addr != NULL ; + addr = addr->next) { + if (ia->ia_type != D6O_IA_PD) + scope = "IAADDR"; + else + scope = "IAPREFIX"; + rval = dhc6_check_status(rval, addr->options, + scope, &code); + if (action(client, &rval, code)) + return ISC_R_CANCELED; + } + } + + /* A Confirm->Reply is unsuitable for comparison to the old lease. */ + if (client->state == S_REBOOTING) + return rval; + + /* No old lease in rapid-commit. */ + if (client->state == S_INIT) + return rval; + + switch (client->state) { + case S_SELECTING: + /* Compare the new lease with the selected lease to make + * sure there is no risky business. + */ + nscore = dhc6_score_lease(client, new); + sscore = dhc6_score_lease(client, client->selected_lease); + if ((client->advertised_leases != NULL) && + (nscore < (sscore / 2))) { + /* XXX: An attacker might reply this way to make + * XXX: sure we latch onto their configuration. + * XXX: We might want to ignore the packet and + * XXX: schedule re-selection at the next timeout? + */ + log_error("PRC: BAIT AND SWITCH detected. Score of " + "supplied lease (%d) is substantially " + "smaller than the advertised score (%d). " + "Trying other servers.", + nscore, sscore); + + dhc6_lease_destroy(&client->selected_lease, MDL); + client->selected_lease = NULL; + + start_selecting6(client); + + return ISC_R_CANCELED; + } + break; + + case S_RENEWING: + case S_REBINDING: + /* This leaves one RFC3315 status check unimplemented: + * + * - sends a Renew/Rebind if the IA is not in the Reply + * message + * + * We rely on the scheduling system to note that the IA has + * not left Renewal/Rebinding/whatever since it still carries + * old times from the last successful binding. So this is + * implemented actually, just not explicitly. + */ + break; + + case S_STOPPED: + /* Nothing critical to do at this stage. */ + break; + + default: + log_fatal("REALLY impossible condition at %s:%d.", MDL); + return ISC_R_CANCELED; + } + + return rval; +} + +/* While in init state, we only collect advertisements. If there happens + * to be an advertisement with a preference option of 255, that's an + * automatic exit. Otherwise, we collect advertisements until our timeout + * expires (client->RT). + */ +void +init_handler(struct packet *packet, struct client_state *client) +{ + struct dhc6_lease *lease; + + /* In INIT state, we send solicits, we only expect to get + * advertises (rapid commit has its own handler). + */ + if (packet->dhcpv6_msg_type != DHCPV6_ADVERTISE) + return; + + /* RFC3315 section 15.3 validation (same as 15.10 since we + * always include a client id). + */ + if (!valid_reply(packet, client)) { + log_error("Invalid Advertise - rejecting."); + return; + } + + lease = dhc6_leaseify(packet); + + if (dhc6_check_advertise(lease) != ISC_R_SUCCESS) { + log_debug("PRC: Lease failed to satisfy."); + dhc6_lease_destroy(&lease, MDL); + return; + } + + insert_lease(&client->advertised_leases, lease); + + /* According to RFC3315 section 17.1.2, the client MUST wait for + * the first RT before selecting a lease. But on the 400th RT, + * we dont' want to wait the full timeout if we finally get an + * advertise. We could probably wait a second, but ohwell, + * RFC3315 doesn't say so. + * + * If the lease is highest possible preference, 255, RFC3315 claims + * we should continue immediately even on the first RT. We probably + * should not if the advertise contains less than one IA and address. + */ + if ((client->txcount > 1) || + ((lease->pref == 255) && + (dhc6_score_lease(client, lease) > 150))) { + log_debug("RCV: Advertisement immediately selected."); + cancel_timeout(do_init6, client); + start_selecting6(client); + } else + log_debug("RCV: Advertisement recorded."); +} + +/* info_request_handler() accepts a Reply to an Info-request. + */ +void +info_request_handler(struct packet *packet, struct client_state *client) +{ + isc_result_t check_status; + unsigned code; + + if (packet->dhcpv6_msg_type != DHCPV6_REPLY) + return; + + /* RFC3315 section 15.10 validation (same as 15.3 since we + * always include a client id). + */ + if (!valid_reply(packet, client)) { + log_error("Invalid Reply - rejecting."); + return; + } + + check_status = dhc6_check_status(ISC_R_SUCCESS, packet->options, + "message", &code); + if (check_status != ISC_R_SUCCESS) { + /* If no action was taken, but there is an error, then + * we wait for a retransmission. + */ + if (check_status != ISC_R_CANCELED) + return; + } + + /* We're done retransmitting at this point. */ + cancel_timeout(do_info_request6, client); + + /* Action was taken, so now that we've torn down our scheduled + * retransmissions, return. + */ + if (check_status == ISC_R_CANCELED) + return; + + /* Cleanup if a previous attempt to go bound failed. */ + if (client->old_lease != NULL) { + dhc6_lease_destroy(&client->old_lease, MDL); + client->old_lease = NULL; + } + + /* Cache options in the active_lease. */ + if (client->active_lease != NULL) + client->old_lease = client->active_lease; + client->active_lease = dmalloc(sizeof(struct dhc6_lease), MDL); + if (client->active_lease == NULL) + log_fatal("Out of memory for v6 lease structure."); + option_state_reference(&client->active_lease->options, + packet->options, MDL); + + start_informed(client); +} + +/* Specific version of init_handler() for rapid-commit. + */ +void +rapid_commit_handler(struct packet *packet, struct client_state *client) +{ + struct dhc6_lease *lease; + isc_result_t check_status; + + /* On ADVERTISE just fall back to the init_handler(). + */ + if (packet->dhcpv6_msg_type == DHCPV6_ADVERTISE) { + init_handler(packet, client); + return; + } else if (packet->dhcpv6_msg_type != DHCPV6_REPLY) + return; + + /* RFC3315 section 15.10 validation (same as 15.3 since we + * always include a client id). + */ + if (!valid_reply(packet, client)) { + log_error("Invalid Reply - rejecting."); + return; + } + + /* A rapid-commit option MUST be here. */ + if (lookup_option(&dhcpv6_universe, packet->options, + D6O_RAPID_COMMIT) == 0) { + log_error("Reply without Rapid-Commit - rejecting."); + return; + } + + lease = dhc6_leaseify(packet); + + /* This is an out of memory condition...hopefully a temporary + * problem. Returning now makes us try to retransmit later. + */ + if (lease == NULL) + return; + + check_status = dhc6_check_reply(client, lease); + if (check_status != ISC_R_SUCCESS) { + dhc6_lease_destroy(&lease, MDL); + return; + } + + /* Jump to the selecting state. */ + cancel_timeout(do_init6, client); + client->state = S_SELECTING; + + /* Merge any bindings in the active lease (if there is one) into + * the new active lease. + */ + dhc6_merge_lease(client->active_lease, lease); + + /* Cleanup if a previous attempt to go bound failed. */ + if (client->old_lease != NULL) { + dhc6_lease_destroy(&client->old_lease, MDL); + client->old_lease = NULL; + } + + /* Make this lease active and BIND to it. */ + if (client->active_lease != NULL) + client->old_lease = client->active_lease; + client->active_lease = lease; + + /* We're done with the ADVERTISEd leases, if any. */ + while(client->advertised_leases != NULL) { + lease = client->advertised_leases; + client->advertised_leases = lease->next; + + dhc6_lease_destroy(&lease, MDL); + } + + start_bound(client); +} + +/* Find the 'best' lease in the cache of advertised leases (usually). From + * RFC3315 Section 17.1.3: + * + * Upon receipt of one or more valid Advertise messages, the client + * selects one or more Advertise messages based upon the following + * criteria. + * + * - Those Advertise messages with the highest server preference value + * are preferred over all other Advertise messages. + * + * - Within a group of Advertise messages with the same server + * preference value, a client MAY select those servers whose + * Advertise messages advertise information of interest to the + * client. For example, the client may choose a server that returned + * an advertisement with configuration options of interest to the + * client. + * + * - The client MAY choose a less-preferred server if that server has a + * better set of advertised parameters, such as the available + * addresses advertised in IAs. + * + * Note that the first and third contradict each other. The third should + * probably be taken to mean that the client should prefer answers that + * offer bindings, even if that violates the preference rule. + * + * The above also isn't deterministic where there are ties. So the final + * tiebreaker we add, if all other values are equal, is to compare the + * server identifiers and to select the numerically lower one. + */ +static struct dhc6_lease * +dhc6_best_lease(struct client_state *client, struct dhc6_lease **head) +{ + struct dhc6_lease **rpos, *rval, **candp, *cand; + int cscore, rscore; + + if (head == NULL || *head == NULL) + return NULL; + + rpos = head; + rval = *rpos; + rscore = dhc6_score_lease(client, rval); + candp = &rval->next; + cand = *candp; + + log_debug("PRC: Considering best lease."); + log_debug("PRC: X-- Initial candidate %s (s: %d, p: %u).", + print_hex_1(rval->server_id.len, + rval->server_id.data, 48), + rscore, (unsigned)rval->pref); + + for (; cand != NULL ; candp = &cand->next, cand = *candp) { + cscore = dhc6_score_lease(client, cand); + + log_debug("PRC: X-- Candidate %s (s: %d, p: %u).", + print_hex_1(cand->server_id.len, + cand->server_id.data, 48), + cscore, (unsigned)cand->pref); + + /* Above you'll find quoted RFC3315 Section 17.1.3. + * + * The third clause tells us to give up on leases that + * have no bindings even if their preference is better. + * So where our 'selected' lease's score is less than 150 + * (1 ia + 1 addr), choose any candidate >= 150. + * + * The first clause tells us to make preference the primary + * deciding factor. So if it's lower, reject, if it's + * higher, select. + * + * The second clause tells us where the preference is + * equal, we should use 'our judgement' of what we like + * to see in an advertisement primarily. + * + * But there can still be a tie. To make this deterministic, + * we compare the server identifiers and select the binary + * lowest. + * + * Since server id's are unique in this list, there is + * no further tie to break. + */ + if ((rscore < 150) && (cscore >= 150)) { + log_debug("PRC: | X-- Selected, has bindings."); + } else if (cand->pref < rval->pref) { + log_debug("PRC: | X-- Rejected, lower preference."); + continue; + } else if (cand->pref > rval->pref) { + log_debug("PRC: | X-- Selected, higher preference."); + } else if (cscore > rscore) { + log_debug("PRC: | X-- Selected, equal preference, " + "higher score."); + } else if (cscore < rscore) { + log_debug("PRC: | X-- Rejected, equal preference, " + "lower score."); + continue; + } else if ((cand->server_id.len < rval->server_id.len) || + ((cand->server_id.len == rval->server_id.len) && + (memcmp(cand->server_id.data, + rval->server_id.data, + cand->server_id.len) < 0))) { + log_debug("PRC: | X-- Selected, equal preference, " + "equal score, binary lesser server ID."); + } else { + log_debug("PRC: | X-- Rejected, equal preference, " + "equal score, binary greater server ID."); + continue; + } + + rpos = candp; + rval = cand; + rscore = cscore; + } + + /* Remove the selected lease from the chain. */ + *rpos = rval->next; + + return rval; +} + +/* Select a lease out of the advertised leases and setup state to try and + * acquire that lease. + */ +void +start_selecting6(struct client_state *client) +{ + struct dhc6_lease *lease; + + if (client->advertised_leases == NULL) { + log_error("Can not enter DHCPv6 SELECTING state with no " + "leases to select from!"); + return; + } + + log_debug("PRC: Selecting best advertised lease."); + client->state = S_SELECTING; + + lease = dhc6_best_lease(client, &client->advertised_leases); + + if (lease == NULL) + log_fatal("Impossible error at %s:%d.", MDL); + + client->selected_lease = lease; + + /* Set timers per RFC3315 section 18.1.1. */ + client->IRT = REQ_TIMEOUT * 100; + client->MRT = REQ_MAX_RT * 100; + client->MRC = REQ_MAX_RC; + client->MRD = 0; + + dhc6_retrans_init(client); + + client->v6_handler = reply_handler; + + /* ("re")transmit the first packet. */ + do_select6(client); +} + +/* Transmit a Request to select a lease offered in Advertisements. In + * the event of failure, either move on to the next-best advertised lease, + * or head back to INIT state if there are none. + */ +void +do_select6(void *input) +{ + struct client_state *client; + struct dhc6_lease *lease; + struct data_string ds; + struct timeval tv; + int send_ret; + + client = input; + + /* 'lease' is fewer characters to type. */ + lease = client->selected_lease; + if (lease == NULL || lease->bindings == NULL) { + log_error("Illegal to attempt selection without selecting " + "a lease."); + return; + } + + switch(check_timing6(client, DHCPV6_REQUEST, "Request", lease, &ds)) { + case CHK_TIM_MRC_EXCEEDED: + case CHK_TIM_MRD_EXCEEDED: + log_debug("PRC: Lease %s failed.", + print_hex_1(lease->server_id.len, + lease->server_id.data, 56)); + + /* Get rid of the lease that timed/counted out. */ + dhc6_lease_destroy(&lease, MDL); + client->selected_lease = NULL; + + /* If there are more leases great. If not, get more. */ + if (client->advertised_leases != NULL) + start_selecting6(client); + else + start_init6(client); + return; + case CHK_TIM_ALLOC_FAILURE: + return; + case CHK_TIM_SUCCESS: + break; + } + + /* Now make a packet that looks suspiciously like the one we + * got from the server. But different. + * + * XXX: I guess IAID is supposed to be something the client + * indicates and uses as a key to its internal state. It is + * kind of odd to ask the server for IA's whose IAID the client + * did not manufacture. We first need a formal dhclient.conf + * construct for the iaid, then we can delve into this matter + * more properly. In the time being, this will work. + */ + + /* Fetch any configured 'sent' options (includes DUID) in wire format. + */ + dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, + NULL, client->sent_options, &global_scope, + &dhcpv6_universe); + + /* Now append any IA's, and within them any IAADDR/IAPREFIXs. */ + if (wanted_ia_na && + dhc6_add_ia_na(client, &ds, lease, + DHCPV6_REQUEST) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + return; + } + if (wanted_ia_ta && + dhc6_add_ia_ta(client, &ds, lease, + DHCPV6_REQUEST) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + return; + } + if (wanted_ia_pd && + dhc6_add_ia_pd(client, &ds, lease, + DHCPV6_REQUEST) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + return; + } + + log_info("XMT: Request on %s, interval %ld0ms.", + client->name ? client->name : client->interface->name, + (long int)client->RT); + + send_ret = send_packet6(client->interface, + ds.data, ds.len, &DHCPv6DestAddr); + if (send_ret != ds.len) { + log_error("dhc6: send_packet6() sent %d of %d bytes", + send_ret, ds.len); + } + + data_string_forget(&ds, MDL); + + /* Wait RT */ + tv.tv_sec = cur_tv.tv_sec + client->RT / 100; + tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_select6, client, NULL, NULL); + + dhc6_retrans_advance(client); +} + +/* For each IA_NA in the lease, for each address in the IA_NA, + * append that information onto the packet-so-far. + */ +static isc_result_t +dhc6_add_ia_na(struct client_state *client, struct data_string *packet, + struct dhc6_lease *lease, u_int8_t message) +{ + struct data_string iads; + struct data_string addrds; + struct dhc6_addr *addr; + struct dhc6_ia *ia; + isc_result_t rval = ISC_R_SUCCESS; + TIME t1, t2; + + memset(&iads, 0, sizeof(iads)); + memset(&addrds, 0, sizeof(addrds)); + for (ia = lease->bindings; + ia != NULL && rval == ISC_R_SUCCESS; + ia = ia->next) { + if (ia->ia_type != D6O_IA_NA) + continue; + + if (!buffer_allocate(&iads.buffer, 12, MDL)) { + log_error("Unable to allocate memory for IA_NA."); + rval = ISC_R_NOMEMORY; + break; + } + + /* Copy the IAID into the packet buffer. */ + memcpy(iads.buffer->data, ia->iaid, 4); + iads.data = iads.buffer->data; + iads.len = 12; + + switch (message) { + case DHCPV6_REQUEST: + case DHCPV6_RENEW: + case DHCPV6_REBIND: + + t1 = client->config->requested_lease / 2; + t2 = t1 + (t1 / 2); +#if MAX_TIME > 0xffffffff + if (t1 > 0xffffffff) + t1 = 0xffffffff; + if (t2 > 0xffffffff) + t2 = 0xffffffff; +#endif + putULong(iads.buffer->data + 4, t1); + putULong(iads.buffer->data + 8, t2); + + log_debug("XMT: X-- IA_NA %s", + print_hex_1(4, iads.data, 59)); + log_debug("XMT: | X-- Requested renew +%u", + (unsigned) t1); + log_debug("XMT: | X-- Requested rebind +%u", + (unsigned) t2); + break; + + case DHCPV6_CONFIRM: + case DHCPV6_RELEASE: + case DHCPV6_DECLINE: + /* Set t1 and t2 to zero; server will ignore them */ + memset(iads.buffer->data + 4, 0, 8); + log_debug("XMT: X-- IA_NA %s", + print_hex_1(4, iads.buffer->data, 55)); + + break; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + /* + * Do not confirm expired addresses, do not request + * expired addresses (but we keep them around for + * solicit). + */ + if (addr->flags & DHC6_ADDR_EXPIRED) + continue; + + if (addr->address.len != 16) { + log_error("Illegal IPv6 address length (%d), " + "ignoring. (%s:%d)", + addr->address.len, MDL); + continue; + } + + if (!buffer_allocate(&addrds.buffer, 24, MDL)) { + log_error("Unable to allocate memory for " + "IAADDR."); + rval = ISC_R_NOMEMORY; + break; + } + + addrds.data = addrds.buffer->data; + addrds.len = 24; + + /* Copy the address into the packet buffer. */ + memcpy(addrds.buffer->data, addr->address.iabuf, 16); + + /* Copy in additional information as appropriate */ + switch (message) { + case DHCPV6_REQUEST: + case DHCPV6_RENEW: + case DHCPV6_REBIND: + t1 = client->config->requested_lease; + t2 = t1 + 300; + putULong(addrds.buffer->data + 16, t1); + putULong(addrds.buffer->data + 20, t2); + + log_debug("XMT: | | X-- IAADDR %s", + piaddr(addr->address)); + log_debug("XMT: | | | X-- Preferred " + "lifetime +%u", (unsigned)t1); + log_debug("XMT: | | | X-- Max lifetime +%u", + (unsigned)t2); + + break; + + case DHCPV6_CONFIRM: + /* + * Set preferred and max life to zero, + * per 17.1.3. + */ + memset(addrds.buffer->data + 16, 0, 8); + log_debug("XMT: | X-- Confirm Address %s", + piaddr(addr->address)); + break; + + case DHCPV6_RELEASE: + /* Preferred and max life are irrelevant */ + memset(addrds.buffer->data + 16, 0, 8); + log_debug("XMT: | X-- Release Address %s", + piaddr(addr->address)); + break; + + case DHCPV6_DECLINE: + /* Preferred and max life are irrelevant */ + memset(addrds.buffer->data + 16, 0, 8); + log_debug("XMT: | X-- Decline Address %s", + piaddr(addr->address)); + break; + + default: + log_fatal("Impossible condition at %s:%d.", + MDL); + } + + append_option(&iads, &dhcpv6_universe, iaaddr_option, + &addrds); + data_string_forget(&addrds, MDL); + } + + /* + * It doesn't make sense to make a request without an + * address. + */ + if (ia->addrs == NULL) { + log_debug("!!!: V IA_NA has no IAADDRs - removed."); + rval = ISC_R_FAILURE; + } else if (rval == ISC_R_SUCCESS) { + log_debug("XMT: V IA_NA appended."); + append_option(packet, &dhcpv6_universe, ia_na_option, + &iads); + } + + data_string_forget(&iads, MDL); + } + + return rval; +} + +/* For each IA_TA in the lease, for each address in the IA_TA, + * append that information onto the packet-so-far. + */ +static isc_result_t +dhc6_add_ia_ta(struct client_state *client, struct data_string *packet, + struct dhc6_lease *lease, u_int8_t message) +{ + struct data_string iads; + struct data_string addrds; + struct dhc6_addr *addr; + struct dhc6_ia *ia; + isc_result_t rval = ISC_R_SUCCESS; + TIME t1, t2; + + memset(&iads, 0, sizeof(iads)); + memset(&addrds, 0, sizeof(addrds)); + for (ia = lease->bindings; + ia != NULL && rval == ISC_R_SUCCESS; + ia = ia->next) { + if (ia->ia_type != D6O_IA_TA) + continue; + + if (!buffer_allocate(&iads.buffer, 4, MDL)) { + log_error("Unable to allocate memory for IA_TA."); + rval = ISC_R_NOMEMORY; + break; + } + + /* Copy the IAID into the packet buffer. */ + memcpy(iads.buffer->data, ia->iaid, 4); + iads.data = iads.buffer->data; + iads.len = 4; + + log_debug("XMT: X-- IA_TA %s", + print_hex_1(4, iads.buffer->data, 55)); + + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + /* + * Do not confirm expired addresses, do not request + * expired addresses (but we keep them around for + * solicit). + */ + if (addr->flags & DHC6_ADDR_EXPIRED) + continue; + + if (addr->address.len != 16) { + log_error("Illegal IPv6 address length (%d), " + "ignoring. (%s:%d)", + addr->address.len, MDL); + continue; + } + + if (!buffer_allocate(&addrds.buffer, 24, MDL)) { + log_error("Unable to allocate memory for " + "IAADDR."); + rval = ISC_R_NOMEMORY; + break; + } + + addrds.data = addrds.buffer->data; + addrds.len = 24; + + /* Copy the address into the packet buffer. */ + memcpy(addrds.buffer->data, addr->address.iabuf, 16); + + /* Copy in additional information as appropriate */ + switch (message) { + case DHCPV6_REQUEST: + case DHCPV6_RENEW: + case DHCPV6_REBIND: + t1 = client->config->requested_lease; + t2 = t1 + 300; + putULong(addrds.buffer->data + 16, t1); + putULong(addrds.buffer->data + 20, t2); + + log_debug("XMT: | | X-- IAADDR %s", + piaddr(addr->address)); + log_debug("XMT: | | | X-- Preferred " + "lifetime +%u", (unsigned)t1); + log_debug("XMT: | | | X-- Max lifetime +%u", + (unsigned)t2); + + break; + + case DHCPV6_CONFIRM: + /* + * Set preferred and max life to zero, + * per 17.1.3. + */ + memset(addrds.buffer->data + 16, 0, 8); + log_debug("XMT: | X-- Confirm Address %s", + piaddr(addr->address)); + break; + + case DHCPV6_RELEASE: + /* Preferred and max life are irrelevant */ + memset(addrds.buffer->data + 16, 0, 8); + log_debug("XMT: | X-- Release Address %s", + piaddr(addr->address)); + break; + + default: + log_fatal("Impossible condition at %s:%d.", + MDL); + } + + append_option(&iads, &dhcpv6_universe, iaaddr_option, + &addrds); + data_string_forget(&addrds, MDL); + } + + /* + * It doesn't make sense to make a request without an + * address. + */ + if (ia->addrs == NULL) { + log_debug("!!!: V IA_TA has no IAADDRs - removed."); + rval = ISC_R_FAILURE; + } else if (rval == ISC_R_SUCCESS) { + log_debug("XMT: V IA_TA appended."); + append_option(packet, &dhcpv6_universe, ia_ta_option, + &iads); + } + + data_string_forget(&iads, MDL); + } + + return rval; +} + +/* For each IA_PD in the lease, for each prefix in the IA_PD, + * append that information onto the packet-so-far. + */ +static isc_result_t +dhc6_add_ia_pd(struct client_state *client, struct data_string *packet, + struct dhc6_lease *lease, u_int8_t message) +{ + struct data_string iads; + struct data_string prefds; + struct dhc6_addr *pref; + struct dhc6_ia *ia; + isc_result_t rval = ISC_R_SUCCESS; + TIME t1, t2; + + memset(&iads, 0, sizeof(iads)); + memset(&prefds, 0, sizeof(prefds)); + for (ia = lease->bindings; + ia != NULL && rval == ISC_R_SUCCESS; + ia = ia->next) { + if (ia->ia_type != D6O_IA_PD) + continue; + + if (!buffer_allocate(&iads.buffer, 12, MDL)) { + log_error("Unable to allocate memory for IA_PD."); + rval = ISC_R_NOMEMORY; + break; + } + + /* Copy the IAID into the packet buffer. */ + memcpy(iads.buffer->data, ia->iaid, 4); + iads.data = iads.buffer->data; + iads.len = 12; + + switch (message) { + case DHCPV6_REQUEST: + case DHCPV6_RENEW: + case DHCPV6_REBIND: + + t1 = client->config->requested_lease / 2; + t2 = t1 + (t1 / 2); +#if MAX_TIME > 0xffffffff + if (t1 > 0xffffffff) + t1 = 0xffffffff; + if (t2 > 0xffffffff) + t2 = 0xffffffff; +#endif + putULong(iads.buffer->data + 4, t1); + putULong(iads.buffer->data + 8, t2); + + log_debug("XMT: X-- IA_PD %s", + print_hex_1(4, iads.data, 59)); + log_debug("XMT: | X-- Requested renew +%u", + (unsigned) t1); + log_debug("XMT: | X-- Requested rebind +%u", + (unsigned) t2); + break; + + case DHCPV6_RELEASE: + /* Set t1 and t2 to zero; server will ignore them */ + memset(iads.buffer->data + 4, 0, 8); + log_debug("XMT: X-- IA_PD %s", + print_hex_1(4, iads.buffer->data, 55)); + + break; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + + for (pref = ia->addrs ; pref != NULL ; pref = pref->next) { + /* + * Do not confirm expired prefixes, do not request + * expired prefixes (but we keep them around for + * solicit). + */ + if (pref->flags & DHC6_ADDR_EXPIRED) + continue; + + if (pref->address.len != 16) { + log_error("Illegal IPv6 prefix " + "ignoring. (%s:%d)", + MDL); + continue; + } + + if (pref->plen == 0) { + log_info("Null IPv6 prefix, " + "ignoring. (%s:%d)", + MDL); + } + + if (!buffer_allocate(&prefds.buffer, 25, MDL)) { + log_error("Unable to allocate memory for " + "IAPREFIX."); + rval = ISC_R_NOMEMORY; + break; + } + + prefds.data = prefds.buffer->data; + prefds.len = 25; + + /* Copy the prefix into the packet buffer. */ + putUChar(prefds.buffer->data + 8, pref->plen); + memcpy(prefds.buffer->data + 9, + pref->address.iabuf, + 16); + + /* Copy in additional information as appropriate */ + switch (message) { + case DHCPV6_REQUEST: + case DHCPV6_RENEW: + case DHCPV6_REBIND: + t1 = client->config->requested_lease; + t2 = t1 + 300; + putULong(prefds.buffer->data, t1); + putULong(prefds.buffer->data + 4, t2); + + log_debug("XMT: | | X-- IAPREFIX %s/%u", + piaddr(pref->address), + (unsigned) pref->plen); + log_debug("XMT: | | | X-- Preferred " + "lifetime +%u", (unsigned)t1); + log_debug("XMT: | | | X-- Max lifetime +%u", + (unsigned)t2); + + break; + + case DHCPV6_RELEASE: + /* Preferred and max life are irrelevant */ + memset(prefds.buffer->data, 0, 8); + log_debug("XMT: | X-- Release Prefix %s/%u", + piaddr(pref->address), + (unsigned) pref->plen); + break; + + default: + log_fatal("Impossible condition at %s:%d.", + MDL); + } + + append_option(&iads, &dhcpv6_universe, + iaprefix_option, &prefds); + data_string_forget(&prefds, MDL); + } + + /* + * It doesn't make sense to make a request without an + * address. + */ + if (ia->addrs == NULL) { + log_debug("!!!: V IA_PD has no IAPREFIXs - removed."); + rval = ISC_R_FAILURE; + } else if (rval == ISC_R_SUCCESS) { + log_debug("XMT: V IA_PD appended."); + append_option(packet, &dhcpv6_universe, + ia_pd_option, &iads); + } + + data_string_forget(&iads, MDL); + } + + return rval; +} + +/* stopping_finished() checks if there is a remaining work to do. + */ +static isc_boolean_t +stopping_finished(void) +{ + struct interface_info *ip; + struct client_state *client; + + for (ip = interfaces; ip; ip = ip -> next) { + for (client = ip -> client; client; client = client -> next) { + if (client->state != S_STOPPED) + return ISC_FALSE; + if (client->active_lease != NULL) + return ISC_FALSE; + } + } + return ISC_TRUE; +} + +/* reply_handler() accepts a Reply while we're attempting Select or Renew or + * Rebind. Basically any Reply packet. + */ +void +reply_handler(struct packet *packet, struct client_state *client) +{ + struct dhc6_lease *lease; + isc_result_t check_status; + + if (packet->dhcpv6_msg_type != DHCPV6_REPLY) + return; + + /* RFC3315 section 15.10 validation (same as 15.3 since we + * always include a client id). + */ + if (!valid_reply(packet, client)) { + log_error("Invalid Reply - rejecting."); + return; + } + + lease = dhc6_leaseify(packet); + + /* This is an out of memory condition...hopefully a temporary + * problem. Returning now makes us try to retransmit later. + */ + if (lease == NULL) + return; + + check_status = dhc6_check_reply(client, lease); + if (check_status != ISC_R_SUCCESS) { + dhc6_lease_destroy(&lease, MDL); + + /* If no action was taken, but there is an error, then + * we wait for a retransmission. + */ + if (check_status != ISC_R_CANCELED) + return; + } + + /* We're done retransmitting at this point. */ + cancel_timeout(do_confirm6, client); + cancel_timeout(do_select6, client); + cancel_timeout(do_refresh6, client); + cancel_timeout(do_release6, client); + + /* If this is in response to a Release/Decline, clean up and return. */ + if (client->state == S_STOPPED) { + if (client->active_lease == NULL) + return; + + dhc6_lease_destroy(&client->active_lease, MDL); + client->active_lease = NULL; + /* We should never wait for nothing!? */ + if (stopping_finished()) + exit(0); + return; + } + + /* Action was taken, so now that we've torn down our scheduled + * retransmissions, return. + */ + if (check_status == ISC_R_CANCELED) + return; + + if (client->selected_lease != NULL) { + dhc6_lease_destroy(&client->selected_lease, MDL); + client->selected_lease = NULL; + } + + /* If this is in response to a confirm, we use the lease we've + * already got, not the reply we were sent. + */ + if (client->state == S_REBOOTING) { + if (client->active_lease == NULL) + log_fatal("Impossible condition at %s:%d.", MDL); + + dhc6_lease_destroy(&lease, MDL); + start_bound(client); + return; + } + + /* Merge any bindings in the active lease (if there is one) into + * the new active lease. + */ + dhc6_merge_lease(client->active_lease, lease); + + /* Cleanup if a previous attempt to go bound failed. */ + if (client->old_lease != NULL) { + dhc6_lease_destroy(&client->old_lease, MDL); + client->old_lease = NULL; + } + + /* Make this lease active and BIND to it. */ + if (client->active_lease != NULL) + client->old_lease = client->active_lease; + client->active_lease = lease; + + /* We're done with the ADVERTISEd leases, if any. */ + while(client->advertised_leases != NULL) { + lease = client->advertised_leases; + client->advertised_leases = lease->next; + + dhc6_lease_destroy(&lease, MDL); + } + + start_bound(client); +} + +/* DHCPv6 packets are a little sillier than they needed to be - the root + * packet contains options, then IA's which contain options, then within + * that IAADDR's which contain options. + * + * To sort this out at dhclient-script time (which fetches config parameters + * in environment variables), start_bound() iterates over each IAADDR, and + * calls this function to marshall an environment variable set that includes + * the most-specific option values related to that IAADDR in particular. + * + * To achieve this, we load environment variables for the root options space, + * then the IA, then the IAADDR. Any duplicate option names will be + * over-written by the later versions. + */ +static void +dhc6_marshall_values(const char *prefix, struct client_state *client, + struct dhc6_lease *lease, struct dhc6_ia *ia, + struct dhc6_addr *addr) +{ + /* Option cache contents, in descending order of + * scope. + */ + if ((lease != NULL) && (lease->options != NULL)) + script_write_params6(client, prefix, lease->options); + if ((ia != NULL) && (ia->options != NULL)) + script_write_params6(client, prefix, ia->options); + if ((addr != NULL) && (addr->options != NULL)) + script_write_params6(client, prefix, addr->options); + + /* addr fields. */ + if (addr != NULL) { + if ((ia != NULL) && (ia->ia_type == D6O_IA_PD)) { + client_envadd(client, prefix, + "ip6_prefix", "%s/%u", + piaddr(addr->address), + (unsigned) addr->plen); + } else { + /* Current practice is that all subnets are /64's, but + * some suspect this may not be permanent. + */ + client_envadd(client, prefix, "ip6_prefixlen", + "%d", 64); + client_envadd(client, prefix, "ip6_address", + "%s", piaddr(addr->address)); + } + if ((ia != NULL) && (ia->ia_type == D6O_IA_TA)) { + client_envadd(client, prefix, + "ip6_type", "temporary"); + } + client_envadd(client, prefix, "life_starts", "%d", + (int)(addr->starts)); + client_envadd(client, prefix, "preferred_life", "%d", + (int)(addr->preferred_life)); + client_envadd(client, prefix, "max_life", "%d", + (int)(addr->max_life)); + } + + /* ia fields. */ + if (ia != NULL) { + client_envadd(client, prefix, "iaid", "%s", + print_hex_1(4, ia->iaid, 12)); + client_envadd(client, prefix, "starts", "%d", + (int)(ia->starts)); + client_envadd(client, prefix, "renew", "%u", ia->renew); + client_envadd(client, prefix, "rebind", "%u", ia->rebind); + } +} + +/* Look at where the client's active lease is sitting. If it's looking to + * time out on renew, rebind, depref, or expiration, do those things. + */ +static void +dhc6_check_times(struct client_state *client) +{ + struct dhc6_lease *lease; + struct dhc6_ia *ia; + struct dhc6_addr *addr; + TIME renew=MAX_TIME, rebind=MAX_TIME, depref=MAX_TIME, + lo_expire=MAX_TIME, hi_expire=0, tmp; + int has_addrs = ISC_FALSE; + struct timeval tv; + + lease = client->active_lease; + + /* Bit spammy. We should probably keep record of scheduled + * events instead. + */ + cancel_timeout(start_renew6, client); + cancel_timeout(start_rebind6, client); + cancel_timeout(do_depref, client); + cancel_timeout(do_expire, client); + + for(ia = lease->bindings ; ia != NULL ; ia = ia->next) { + TIME this_ia_lo_expire, this_ia_hi_expire, use_expire; + + this_ia_lo_expire = MAX_TIME; + this_ia_hi_expire = 0; + + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + if(!(addr->flags & DHC6_ADDR_DEPREFFED)) { + if (addr->preferred_life == 0xffffffff) + tmp = MAX_TIME; + else + tmp = addr->starts + + addr->preferred_life; + + if (tmp < depref) + depref = tmp; + } + + if (!(addr->flags & DHC6_ADDR_EXPIRED)) { + /* Find EPOCH-relative expiration. */ + if (addr->max_life == 0xffffffff) + tmp = MAX_TIME; + else + tmp = addr->starts + addr->max_life; + + /* Make the times ia->starts relative. */ + tmp -= ia->starts; + + if (tmp > this_ia_hi_expire) + this_ia_hi_expire = tmp; + if (tmp < this_ia_lo_expire) + this_ia_lo_expire = tmp; + + has_addrs = ISC_TRUE; + } + } + + /* These times are ia->starts relative. */ + if (this_ia_lo_expire <= (this_ia_hi_expire / 2)) + use_expire = this_ia_hi_expire; + else + use_expire = this_ia_lo_expire; + + /* + * If the auto-selected expiration time is "infinite", or + * zero, assert a reasonable default. + */ + if ((use_expire == MAX_TIME) || (use_expire <= 1)) + use_expire = client->config->requested_lease / 2; + else + use_expire /= 2; + + /* Don't renew/rebind temporary addresses. */ + if (ia->ia_type != D6O_IA_TA) { + + if (ia->renew == 0) { + tmp = ia->starts + use_expire; + } else if (ia->renew == 0xffffffff) + tmp = MAX_TIME; + else + tmp = ia->starts + ia->renew; + + if (tmp < renew) + renew = tmp; + + if (ia->rebind == 0) { + /* Set rebind to 3/4 expiration interval. */ + tmp = ia->starts; + tmp += use_expire + (use_expire / 2); + } else if (ia->renew == 0xffffffff) + tmp = MAX_TIME; + else + tmp = ia->starts + ia->rebind; + + if (tmp < rebind) + rebind = tmp; + } + + /* + * Return expiration ranges to EPOCH relative for event + * scheduling (add_timeout()). + */ + this_ia_hi_expire += ia->starts; + this_ia_lo_expire += ia->starts; + + if (this_ia_hi_expire > hi_expire) + hi_expire = this_ia_hi_expire; + if (this_ia_lo_expire < lo_expire) + lo_expire = this_ia_lo_expire; + } + + /* If there are no addresses, give up, go to INIT. + * Note that if an address is unexpired with a date in the past, + * we're scheduling an expiration event to ocurr in the past. We + * could probably optimize this to expire now (but then there's + * recursion). + * + * In the future, we may decide that we're done here, or to + * schedule a future request (using 4-pkt info-request model). + */ + if (has_addrs == ISC_FALSE) { + dhc6_lease_destroy(&client->active_lease, MDL); + client->active_lease = NULL; + + /* Go back to the beginning. */ + start_init6(client); + return; + } + + switch(client->state) { + case S_BOUND: + /* We'd like to hit renewing, but if rebinding has already + * passed (time warp), head straight there. + */ + if ((rebind > cur_time) && (renew < rebind)) { + log_debug("PRC: Renewal event scheduled in %d seconds, " + "to run for %u seconds.", + (int)(renew - cur_time), + (unsigned)(rebind - renew)); + client->next_MRD = rebind; + tv.tv_sec = renew; + tv.tv_usec = 0; + add_timeout(&tv, start_renew6, client, NULL, NULL); + + break; + } + /* FALL THROUGH */ + case S_RENEWING: + /* While actively renewing, MRD is bounded by the time + * we stop renewing and start rebinding. This helps us + * process the state change on time. + */ + client->MRD = rebind - cur_time; + if (rebind != MAX_TIME) { + log_debug("PRC: Rebind event scheduled in %d seconds, " + "to run for %d seconds.", + (int)(rebind - cur_time), + (int)(hi_expire - rebind)); + client->next_MRD = hi_expire; + tv.tv_sec = rebind; + tv.tv_usec = 0; + add_timeout(&tv, start_rebind6, client, NULL, NULL); + } + break; + + case S_REBINDING: + /* For now, we rebind up until the last lease expires. In + * the future, we might want to start SOLICITing when we've + * depreffed an address. + */ + client->MRD = hi_expire - cur_time; + break; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + } + + /* Separately, set a time at which we will depref and expire + * leases. This might happen with multiple addresses while we + * keep trying to refresh. + */ + if (depref != MAX_TIME) { + log_debug("PRC: Depreference scheduled in %d seconds.", + (int)(depref - cur_time)); + tv.tv_sec = depref; + tv.tv_usec = 0; + add_timeout(&tv, do_depref, client, NULL, NULL); + } + if (lo_expire != MAX_TIME) { + log_debug("PRC: Expiration scheduled in %d seconds.", + (int)(lo_expire - cur_time)); + tv.tv_sec = lo_expire; + tv.tv_usec = 0; + add_timeout(&tv, do_expire, client, NULL, NULL); + } +} + +/* In a given IA chain, find the IA with the same type and 'iaid'. */ +static struct dhc6_ia * +find_ia(struct dhc6_ia *head, u_int16_t type, const char *id) +{ + struct dhc6_ia *ia; + + for (ia = head ; ia != NULL ; ia = ia->next) { + if (ia->ia_type != type) + continue; + if (memcmp(ia->iaid, id, 4) == 0) + return ia; + } + + return NULL; +} + +/* In a given address chain, find a matching address. */ +static struct dhc6_addr * +find_addr(struct dhc6_addr *head, struct iaddr *address) +{ + struct dhc6_addr *addr; + + for (addr = head ; addr != NULL ; addr = addr->next) { + if ((addr->address.len == address->len) && + (memcmp(addr->address.iabuf, address->iabuf, + address->len) == 0)) + return addr; + } + + return NULL; +} + +/* In a given prefix chain, find a matching prefix. */ +static struct dhc6_addr * +find_pref(struct dhc6_addr *head, struct iaddr *prefix, u_int8_t plen) +{ + struct dhc6_addr *pref; + + for (pref = head ; pref != NULL ; pref = pref->next) { + if ((pref->address.len == prefix->len) && + (pref->plen == plen) && + (memcmp(pref->address.iabuf, prefix->iabuf, + prefix->len) == 0)) + return pref; + } + + return NULL; +} + +/* Merge the bindings from the source lease into the destination lease + * structure, where they are missing. We have to copy the stateful + * objects rather than move them over, because later code needs to be + * able to compare new versus old if they contain any bindings. + */ +static void +dhc6_merge_lease(struct dhc6_lease *src, struct dhc6_lease *dst) +{ + struct dhc6_ia *sia, *dia, *tia; + struct dhc6_addr *saddr, *daddr, *taddr; + int changes = 0; + + if ((dst == NULL) || (src == NULL)) + return; + + for (sia = src->bindings ; sia != NULL ; sia = sia->next) { + dia = find_ia(dst->bindings, sia->ia_type, (char *)sia->iaid); + + if (dia == NULL) { + tia = dhc6_dup_ia(sia, MDL); + + if (tia == NULL) + log_fatal("Out of memory merging lease - " + "Unable to continue without losing " + "state! (%s:%d)", MDL); + + /* XXX: consider sorting? */ + tia->next = dst->bindings; + dst->bindings = tia; + changes = 1; + } else { + for (saddr = sia->addrs ; saddr != NULL ; + saddr = saddr->next) { + if (sia->ia_type != D6O_IA_PD) + daddr = find_addr(dia->addrs, + &saddr->address); + else + daddr = find_pref(dia->addrs, + &saddr->address, + saddr->plen); + + if (daddr == NULL) { + taddr = dhc6_dup_addr(saddr, MDL); + + if (taddr == NULL) + log_fatal("Out of memory " + "merging lease - " + "Unable to continue " + "without losing " + "state! (%s:%d)", + MDL); + + /* XXX: consider sorting? */ + taddr->next = dia->addrs; + dia->addrs = taddr; + changes = 1; + } + } + } + } + + /* If we made changes, reset the score to 0 so it is recalculated. */ + if (changes) + dst->score = 0; +} + +/* We've either finished selecting or succeeded in Renew or Rebinding our + * lease. In all cases we got a Reply. Give dhclient-script a tickle + * to inform it about the new values, and then lay in wait for the next + * event. + */ +static void +start_bound(struct client_state *client) +{ + struct dhc6_ia *ia, *oldia; + struct dhc6_addr *addr, *oldaddr; + struct dhc6_lease *lease, *old; + const char *reason; +#if defined (NSUPDATE) + TIME dns_update_offset = 1; +#endif + + lease = client->active_lease; + if (lease == NULL) { + log_error("Cannot enter bound state unless an active lease " + "is selected."); + return; + } + lease->released = ISC_FALSE; + old = client->old_lease; + + client->v6_handler = bound_handler; + + switch (client->state) { + case S_SELECTING: + case S_REBOOTING: /* Pretend we got bound. */ + reason = "BOUND6"; + break; + + case S_RENEWING: + reason = "RENEW6"; + break; + + case S_REBINDING: + reason = "REBIND6"; + break; + + default: + log_fatal("Impossible condition at %s:%d.", MDL); + /* Silence compiler warnings. */ + return; + } + + log_debug("PRC: Bound to lease %s.", + print_hex_1(client->active_lease->server_id.len, + client->active_lease->server_id.data, 55)); + client->state = S_BOUND; + + write_client6_lease(client, lease, 0, 1); + + oldia = NULL; + for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + if (old != NULL) + oldia = find_ia(old->bindings, + ia->ia_type, + (char *)ia->iaid); + else + oldia = NULL; + + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + if (oldia != NULL) { + if (ia->ia_type != D6O_IA_PD) + oldaddr = find_addr(oldia->addrs, + &addr->address); + else + oldaddr = find_pref(oldia->addrs, + &addr->address, + addr->plen); + } else + oldaddr = NULL; + +#if defined (NSUPDATE) + if ((oldaddr == NULL) && (ia->ia_type == D6O_IA_NA)) + dhclient_schedule_updates(client, + &addr->address, + dns_update_offset++); +#endif + + /* Shell out to setup the new binding. */ + script_init(client, reason, NULL); + + if (old != NULL) + dhc6_marshall_values("old_", client, old, + oldia, oldaddr); + dhc6_marshall_values("new_", client, lease, ia, addr); + + script_go(client); + } + + /* XXX: maybe we should loop on the old values instead? */ + if (ia->addrs == NULL) { + script_init(client, reason, NULL); + + if (old != NULL) + dhc6_marshall_values("old_", client, old, + oldia, + oldia != NULL ? + oldia->addrs : NULL); + + dhc6_marshall_values("new_", client, lease, ia, + NULL); + + script_go(client); + } + } + + /* XXX: maybe we should loop on the old values instead? */ + if (lease->bindings == NULL) { + script_init(client, reason, NULL); + + if (old != NULL) + dhc6_marshall_values("old_", client, old, + old->bindings, + (old->bindings != NULL) ? + old->bindings->addrs : NULL); + + dhc6_marshall_values("new_", client, lease, NULL, NULL); + + script_go(client); + } + + go_daemon(); + + if (client->old_lease != NULL) { + dhc6_lease_destroy(&client->old_lease, MDL); + client->old_lease = NULL; + } + + /* Schedule events. */ + dhc6_check_times(client); +} + +/* While bound, ignore packets. In the future we'll want to answer + * Reconfigure-Request messages and the like. + */ +void +bound_handler(struct packet *packet, struct client_state *client) +{ + log_debug("RCV: Input packets are ignored once bound."); +} + +/* start_renew6() gets us all ready to go to start transmitting Renew packets. + * Note that client->next_MRD must be set before entering this function - + * it must be set to the time at which the client should start Rebinding. + */ +void +start_renew6(void *input) +{ + struct client_state *client; + + client = (struct client_state *)input; + + log_info("PRC: Renewing lease on %s.", + client->name ? client->name : client->interface->name); + client->state = S_RENEWING; + + client->v6_handler = reply_handler; + + /* Times per RFC3315 section 18.1.3. */ + client->IRT = REN_TIMEOUT * 100; + client->MRT = REN_MAX_RT * 100; + client->MRC = 0; + /* MRD is special in renew - we need to set it by checking timer + * state. + */ + client->MRD = client->next_MRD - cur_time; + + dhc6_retrans_init(client); + + client->refresh_type = DHCPV6_RENEW; + do_refresh6(client); +} + +/* do_refresh6() transmits one DHCPv6 packet, be it a Renew or Rebind, and + * gives the retransmission state a bump for the next time. Note that + * client->refresh_type must be set before entering this function. + */ +void +do_refresh6(void *input) +{ + struct option_cache *oc; + struct sockaddr_in6 unicast, *dest_addr = &DHCPv6DestAddr; + struct data_string ds; + struct client_state *client; + struct dhc6_lease *lease; + struct timeval elapsed, tv; + int send_ret; + + client = (struct client_state *)input; + memset(&ds, 0, sizeof(ds)); + + lease = client->active_lease; + if (lease == NULL) { + log_error("Cannot renew without an active binding."); + return; + } + + /* Ensure we're emitting a valid message type. */ + switch (client->refresh_type) { + case DHCPV6_RENEW: + case DHCPV6_REBIND: + break; + + default: + log_fatal("Internal inconsistency (%d) at %s:%d.", + client->refresh_type, MDL); + } + + /* + * Start_time starts at the first transmission. + */ + if (client->txcount == 0) { + client->start_time.tv_sec = cur_tv.tv_sec; + client->start_time.tv_usec = cur_tv.tv_usec; + } + + /* elapsed = cur - start */ + elapsed.tv_sec = cur_tv.tv_sec - client->start_time.tv_sec; + elapsed.tv_usec = cur_tv.tv_usec - client->start_time.tv_usec; + if (elapsed.tv_usec < 0) { + elapsed.tv_sec -= 1; + elapsed.tv_usec += 1000000; + } + if (((client->MRC != 0) && (client->txcount > client->MRC)) || + ((client->MRD != 0) && (elapsed.tv_sec >= client->MRD))) { + /* We're done. Move on to the next phase, if any. */ + dhc6_check_times(client); + return; + } + + /* + * Check whether the server has sent a unicast option; if so, we can + * use the address it specified for RENEWs. + */ + oc = lookup_option(&dhcpv6_universe, lease->options, D6O_UNICAST); + if (oc && evaluate_option_cache(&ds, NULL, NULL, NULL, + lease->options, NULL, &global_scope, + oc, MDL)) { + if (ds.len < 16) { + log_error("Invalid unicast option length %d.", ds.len); + } else { + memset(&unicast, 0, sizeof(DHCPv6DestAddr)); + unicast.sin6_family = AF_INET6; + unicast.sin6_port = remote_port; + memcpy(&unicast.sin6_addr, ds.data, 16); + if (client->refresh_type == DHCPV6_RENEW) { + dest_addr = &unicast; + } + } + + data_string_forget(&ds, MDL); + } + + /* Commence forming a renew packet. */ + memset(&ds, 0, sizeof(ds)); + if (!buffer_allocate(&ds.buffer, 4, MDL)) { + log_error("Unable to allocate memory for packet."); + return; + } + ds.data = ds.buffer->data; + ds.len = 4; + + ds.buffer->data[0] = client->refresh_type; + memcpy(ds.buffer->data + 1, client->dhcpv6_transaction_id, 3); + + /* Form an elapsed option. */ + /* Maximum value is 65535 1/100s coded as 0xffff. */ + if ((elapsed.tv_sec < 0) || (elapsed.tv_sec > 655) || + ((elapsed.tv_sec == 655) && (elapsed.tv_usec > 350000))) { + client->elapsed = 0xffff; + } else { + client->elapsed = elapsed.tv_sec * 100; + client->elapsed += elapsed.tv_usec / 10000; + } + + if (client->elapsed == 0) + log_debug("XMT: Forming %s, 0 ms elapsed.", + dhcpv6_type_names[client->refresh_type]); + else + log_debug("XMT: Forming %s, %u0 ms elapsed.", + dhcpv6_type_names[client->refresh_type], + (unsigned)client->elapsed); + + client->elapsed = htons(client->elapsed); + + make_client6_options(client, &client->sent_options, lease, + client->refresh_type); + + /* Put in any options from the sent cache. */ + dhcpv6_universe.encapsulate(&ds, NULL, NULL, client, NULL, + client->sent_options, &global_scope, + &dhcpv6_universe); + + /* Append IA's */ + if (wanted_ia_na && + dhc6_add_ia_na(client, &ds, lease, + client->refresh_type) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + return; + } + if (wanted_ia_pd && + dhc6_add_ia_pd(client, &ds, lease, + client->refresh_type) != ISC_R_SUCCESS) { + data_string_forget(&ds, MDL); + return; + } + + log_info("XMT: %s on %s, interval %ld0ms.", + dhcpv6_type_names[client->refresh_type], + client->name ? client->name : client->interface->name, + (long int)client->RT); + + send_ret = send_packet6(client->interface, ds.data, ds.len, dest_addr); + + if (send_ret != ds.len) { + log_error("dhc6: send_packet6() sent %d of %d bytes", + send_ret, ds.len); + } + + data_string_forget(&ds, MDL); + + /* Wait RT */ + tv.tv_sec = cur_tv.tv_sec + client->RT / 100; + tv.tv_usec = cur_tv.tv_usec + (client->RT % 100) * 10000; + if (tv.tv_usec >= 1000000) { + tv.tv_sec += 1; + tv.tv_usec -= 1000000; + } + add_timeout(&tv, do_refresh6, client, NULL, NULL); + + dhc6_retrans_advance(client); +} + +/* start_rebind6() gets us all set up to go and rebind a lease. Note that + * client->next_MRD must be set before entering this function. In this case, + * MRD must be set to the maximum time any address in the packet will + * expire. + */ +void +start_rebind6(void *input) +{ + struct client_state *client; + + client = (struct client_state *)input; + + log_info("PRC: Rebinding lease on %s.", + client->name ? client->name : client->interface->name); + client->state = S_REBINDING; + + client->v6_handler = reply_handler; + + /* Times per RFC3315 section 18.1.4. */ + client->IRT = REB_TIMEOUT * 100; + client->MRT = REB_MAX_RT * 100; + client->MRC = 0; + /* MRD is special in rebind - it's determined by the timer + * state. + */ + client->MRD = client->next_MRD - cur_time; + + dhc6_retrans_init(client); + + client->refresh_type = DHCPV6_REBIND; + do_refresh6(client); +} + +/* do_depref() runs through a given lease's addresses, for each that has + * not yet been depreffed, shells out to the dhclient-script to inform it + * of the status change. The dhclient-script should then do...something... + * to encourage applications to move off the address and onto one of the + * remaining 'preferred' addresses. + */ +void +do_depref(void *input) +{ + struct client_state *client; + struct dhc6_lease *lease; + struct dhc6_ia *ia; + struct dhc6_addr *addr; + + client = (struct client_state *)input; + + lease = client->active_lease; + if (lease == NULL) + return; + + for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + if (addr->flags & DHC6_ADDR_DEPREFFED) + continue; + + if (addr->starts + addr->preferred_life <= cur_time) { + script_init(client, "DEPREF6", NULL); + dhc6_marshall_values("cur_", client, lease, + ia, addr); + script_go(client); + + addr->flags |= DHC6_ADDR_DEPREFFED; + + if (ia->ia_type != D6O_IA_PD) + log_info("PRC: Address %s depreferred.", + piaddr(addr->address)); + else + log_info("PRC: Prefix %s/%u depreferred.", + piaddr(addr->address), + (unsigned) addr->plen); + +#if defined (NSUPDATE) + /* Remove DDNS bindings at depref time. */ + if ((ia->ia_type == D6O_IA_NA) && + client->config->do_forward_update) + client_dns_remove(client, + &addr->address); +#endif + } + } + } + + dhc6_check_times(client); +} + +/* do_expire() searches through all the addresses on a given lease, and + * expires/removes any addresses that are no longer valid. + */ +void +do_expire(void *input) +{ + struct client_state *client; + struct dhc6_lease *lease; + struct dhc6_ia *ia; + struct dhc6_addr *addr; + int has_addrs = ISC_FALSE; + + client = (struct client_state *)input; + + lease = client->active_lease; + if (lease == NULL) + return; + + for (ia = lease->bindings ; ia != NULL ; ia = ia->next) { + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + if (addr->flags & DHC6_ADDR_EXPIRED) + continue; + + if (addr->starts + addr->max_life <= cur_time) { + script_init(client, "EXPIRE6", NULL); + dhc6_marshall_values("old_", client, lease, + ia, addr); + script_go(client); + + addr->flags |= DHC6_ADDR_EXPIRED; + + if (ia->ia_type != D6O_IA_PD) + log_info("PRC: Address %s expired.", + piaddr(addr->address)); + else + log_info("PRC: Prefix %s/%u expired.", + piaddr(addr->address), + (unsigned) addr->plen); + +#if defined (NSUPDATE) + /* We remove DNS records at depref time, but + * it is possible that we might get here + * without depreffing. + */ + if ((ia->ia_type == D6O_IA_NA) && + client->config->do_forward_update && + !(addr->flags & DHC6_ADDR_DEPREFFED)) + client_dns_remove(client, + &addr->address); +#endif + + continue; + } + + has_addrs = ISC_TRUE; + } + } + + /* Clean up empty leases. */ + if (has_addrs == ISC_FALSE) { + log_info("PRC: Bound lease is devoid of active addresses." + " Re-initializing."); + + dhc6_lease_destroy(&lease, MDL); + client->active_lease = NULL; + + start_init6(client); + return; + } + + /* Schedule the next run through. */ + dhc6_check_times(client); +} + +/* + * Run client script to unconfigure interface. + * Called with reason STOP6 when dhclient -x is run, or with reason + * RELEASE6 when server has replied to a Release message. + * Stateless is a special case. + */ +void +unconfigure6(struct client_state *client, const char *reason) +{ + struct dhc6_ia *ia; + struct dhc6_addr *addr; + + if (stateless) { + script_init(client, reason, NULL); + if (client->active_lease != NULL) + script_write_params6(client, "old_", + client->active_lease->options); + script_go(client); + return; + } + + if (client->active_lease == NULL) + return; + + for (ia = client->active_lease->bindings ; ia != NULL ; ia = ia->next) { + if (ia->ia_type == D6O_IA_TA) + continue; + + for (addr = ia->addrs ; addr != NULL ; addr = addr->next) { + script_init(client, reason, NULL); + dhc6_marshall_values("old_", client, + client->active_lease, ia, addr); + script_go(client); + +#if defined (NSUPDATE) + if ((ia->ia_type == D6O_IA_NA) && + client->config->do_forward_update) + client_dns_remove(client, &addr->address); +#endif + } + } +} + +void +refresh_info_request6(void *input) +{ + struct client_state *client; + + client = (struct client_state *)input; + start_info_request6(client); +} + +/* Timeout for Information-Request (using the IRT option). + */ +static void +dhc6_check_irt(struct client_state *client) +{ + struct option **req; + struct option_cache *oc; + TIME expire = MAX_TIME; + struct timeval tv; + int i; + isc_boolean_t found = ISC_FALSE; + + cancel_timeout(refresh_info_request6, client); + + req = client->config->requested_options; + for (i = 0; req[i] != NULL; i++) { + if (req[i] == irt_option) { + found = ISC_TRUE; + break; + } + } + /* Simply return gives a endless loop waiting for nothing. */ + if (!found) + exit(0); + + oc = lookup_option(&dhcpv6_universe, client->active_lease->options, + D6O_INFORMATION_REFRESH_TIME); + if (oc != NULL) { + struct data_string irt; + + memset(&irt, 0, sizeof(irt)); + if (!evaluate_option_cache(&irt, NULL, NULL, client, + client->active_lease->options, + NULL, &global_scope, oc, MDL) || + (irt.len < 4)) { + log_error("Can't evaluate IRT."); + } else { + expire = getULong(irt.data); + if (expire < IRT_MINIMUM) + expire = IRT_MINIMUM; + if (expire == 0xffffffff) + expire = MAX_TIME; + } + data_string_forget(&irt, MDL); + } else + expire = IRT_DEFAULT; + + if (expire != MAX_TIME) { + log_debug("PRC: Refresh event scheduled in %u seconds.", + (unsigned) expire); + tv.tv_sec = cur_time + expire; + tv.tv_usec = 0; + add_timeout(&tv, refresh_info_request6, client, NULL, NULL); + } +} + +/* We got a Reply. Give dhclient-script a tickle to inform it about + * the new values, and then lay in wait for the next event. + */ +static void +start_informed(struct client_state *client) +{ + client->v6_handler = informed_handler; + + log_debug("PRC: Done."); + + client->state = S_BOUND; + + script_init(client, "RENEW6", NULL); + if (client->old_lease != NULL) + script_write_params6(client, "old_", + client->old_lease->options); + script_write_params6(client, "new_", client->active_lease->options); + script_go(client); + + go_daemon(); + + if (client->old_lease != NULL) { + dhc6_lease_destroy(&client->old_lease, MDL); + client->old_lease = NULL; + } + + /* Schedule events. */ + dhc6_check_irt(client); +} + +/* While informed, ignore packets. + */ +void +informed_handler(struct packet *packet, struct client_state *client) +{ + log_debug("RCV: Input packets are ignored once bound."); +} + +/* make_client6_options() fetches option caches relevant to the client's + * scope and places them into the sent_options cache. This cache is later + * used to populate DHCPv6 output packets with options. + */ +static void +make_client6_options(struct client_state *client, struct option_state **op, + struct dhc6_lease *lease, u_int8_t message) +{ + struct option_cache *oc; + struct option **req; + struct buffer *buffer; + int buflen, i, oro_len; + + if ((op == NULL) || (client == NULL)) + return; + + if (*op) + option_state_dereference(op, MDL); + + /* Create a cache to carry options to transmission. */ + option_state_allocate(op, MDL); + + /* Create and store an 'elapsed time' option in the cache. */ + oc = NULL; + if (option_cache_allocate(&oc, MDL)) { + const unsigned char *cdata; + + cdata = (unsigned char *)&client->elapsed; + + if (make_const_data(&oc->expression, cdata, 2, 0, 0, MDL)) { + option_reference(&oc->option, elapsed_option, MDL); + save_option(&dhcpv6_universe, *op, oc); + } + + option_cache_dereference(&oc, MDL); + } + + /* Bring in any configured options to send. */ + if (client->config->on_transmission) + execute_statements_in_scope(NULL, NULL, NULL, client, + lease ? lease->options : NULL, + *op, &global_scope, + client->config->on_transmission, + NULL); + + /* Rapid-commit is only for SOLICITs. */ + if (message != DHCPV6_SOLICIT) + delete_option(&dhcpv6_universe, *op, D6O_RAPID_COMMIT); + + /* See if the user configured a DUID in a relevant scope. If not, + * introduce our default manufactured id. + */ + if ((oc = lookup_option(&dhcpv6_universe, *op, + D6O_CLIENTID)) == NULL) { + if (!option_cache(&oc, &default_duid, NULL, clientid_option, + MDL)) + log_fatal("Failure assembling a DUID."); + + save_option(&dhcpv6_universe, *op, oc); + option_cache_dereference(&oc, MDL); + } + + /* In cases where we're responding to a single server, put the + * server's id in the response. + * + * Note that lease is NULL for SOLICIT or INFO request messages, + * and otherwise MUST be present. + */ + if (lease == NULL) { + if ((message != DHCPV6_SOLICIT) && + (message != DHCPV6_INFORMATION_REQUEST)) + log_fatal("Impossible condition at %s:%d.", MDL); + } else if ((message != DHCPV6_REBIND) && + (message != DHCPV6_CONFIRM)) { + oc = lookup_option(&dhcpv6_universe, lease->options, + D6O_SERVERID); + if (oc != NULL) + save_option(&dhcpv6_universe, *op, oc); + } + + /* 'send dhcp6.oro foo;' syntax we used in 4.0.0a1/a2 has been + * deprecated by adjustments to the 'request' syntax also used for + * DHCPv4. + */ + if (lookup_option(&dhcpv6_universe, *op, D6O_ORO) != NULL) + log_error("'send dhcp6.oro' syntax is deprecated, please " + "use the 'request' syntax (\"man dhclient.conf\")."); + + /* Construct and store an ORO (Option Request Option). It is a + * fatal error to fail to send an ORO (of at least zero length). + * + * Discussion: RFC3315 appears to be inconsistent in its statements + * of whether or not the ORO is mandatory. In section 18.1.1 + * ("Creation and Transmission of Request Messages"): + * + * The client MUST include an Option Request option (see section + * 22.7) to indicate the options the client is interested in + * receiving. The client MAY include options with data values as + * hints to the server about parameter values the client would like + * to have returned. + * + * This MUST is missing from the creation/transmission of other + * messages (such as Renew and Rebind), and the section 22.7 ("Option + * Request Option" format and definition): + * + * A client MAY include an Option Request option in a Solicit, + * Request, Renew, Rebind, Confirm or Information-request message to + * inform the server about options the client wants the server to + * send to the client. A server MAY include an Option Request + * option in a Reconfigure option to indicate which options the + * client should request from the server. + * + * seems to relax the requirement from MUST to MAY (and still other + * language in RFC3315 supports this). + * + * In lieu of a clarification of RFC3315, we will conform with the + * MUST. Instead of an absent ORO, we will if there are no options + * to request supply an empty ORO. Theoretically, an absent ORO is + * difficult to interpret (does the client want all options or no + * options?). A zero-length ORO is intuitively clear: requesting + * nothing. + */ + buffer = NULL; + oro_len = 0; + buflen = 32; + if (!buffer_allocate(&buffer, buflen, MDL)) + log_fatal("Out of memory constructing DHCPv6 ORO."); + req = client->config->requested_options; + if (req != NULL) { + for (i = 0 ; req[i] != NULL ; i++) { + if (buflen == oro_len) { + struct buffer *tmpbuf = NULL; + + buflen += 32; + + /* Shell game. */ + buffer_reference(&tmpbuf, buffer, MDL); + buffer_dereference(&buffer, MDL); + + if (!buffer_allocate(&buffer, buflen, MDL)) + log_fatal("Out of memory resizing " + "DHCPv6 ORO buffer."); + + memcpy(buffer->data, tmpbuf->data, oro_len); + + buffer_dereference(&tmpbuf, MDL); + } + + if (req[i]->universe == &dhcpv6_universe) { + /* Append the code to the ORO. */ + putUShort(buffer->data + oro_len, + req[i]->code); + oro_len += 2; + } + } + } + + oc = NULL; + if (make_const_option_cache(&oc, &buffer, NULL, oro_len, + oro_option, MDL)) { + save_option(&dhcpv6_universe, *op, oc); + } else { + log_fatal("Unable to create ORO option cache."); + } + + /* + * Note: make_const_option_cache() consumes the buffer, we do not + * need to dereference it (XXX). + */ + option_cache_dereference(&oc, MDL); +} + +/* A clone of the DHCPv4 script_write_params() minus the DHCPv4-specific + * filename, server-name, etc specifics. + * + * Simply, store all values present in all universes of the option state + * (probably derived from a DHCPv6 packet) into environment variables + * named after the option names (and universe names) but with the 'prefix' + * prepended. + * + * Later, dhclient-script may compare for example "new_time_servers" and + * "old_time_servers" for differences, and only upon detecting a change + * bother to rewrite ntp.conf and restart it. Or something along those + * generic lines. + */ +static void +script_write_params6(struct client_state *client, const char *prefix, + struct option_state *options) +{ + struct envadd_state es; + int i; + + if (options == NULL) + return; + + es.client = client; + es.prefix = prefix; + + for (i = 0 ; i < options->universe_count ; i++) { + option_space_foreach(NULL, NULL, client, NULL, options, + &global_scope, universes[i], &es, + client_option_envadd); + } +} + +/* + * Check if there is something not fully defined in the active lease. + */ +static isc_boolean_t +active_prefix(struct client_state *client) +{ + struct dhc6_lease *lease; + struct dhc6_ia *ia; + struct dhc6_addr *pref; + char zeros[16]; + + lease = client->active_lease; + if (lease == NULL) + return ISC_FALSE; + memset(zeros, 0, 16); + for (ia = lease->bindings; ia != NULL; ia = ia->next) { + if (ia->ia_type != D6O_IA_PD) + continue; + for (pref = ia->addrs; pref != NULL; pref = pref->next) { + if (pref->plen == 0) + return ISC_FALSE; + if (pref->address.len != 16) + return ISC_FALSE; + if (memcmp(pref->address.iabuf, zeros, 16) == 0) + return ISC_FALSE; + } + } + return ISC_TRUE; +} +#endif /* DHCPv6 */ |