aboutsummaryrefslogtreecommitdiff
path: root/client/dhc6.c
diff options
context:
space:
mode:
Diffstat (limited to 'client/dhc6.c')
-rw-r--r--client/dhc6.c5128
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(&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(&copy->options, lease->options, file, line);
+
+ insert_ia = &copy->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(&copy, 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 = &copy->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(&copy, file, line);
+ return NULL;
+ }
+
+ insert_addr = &(*insert_addr)->next;
+ }
+
+ if (ia->options != NULL)
+ option_state_reference(&copy->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(&copy->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(&copy->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 */