/*
- * Copyright (C) 2006-2016 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 2006-2017 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.
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
static void recv_dhcpv4_query(struct data_string *raw);
static void dhcp4o6_dhcpv4_query(struct data_string *reply_ret,
struct packet *packet);
+
+struct udp_data4o6 {
+ u_int16_t src_port;
+ u_int8_t rsp_opt_exist;
+ u_int8_t reserved;
+};
+
+static int offset_data4o6 = 36; /* 16+16+4 */
#endif
/*
static void
set_reply_tee_times(struct reply_state* reply, unsigned ia_cursor);
+static const char *iasubopt_plen_str(struct iasubopt *lease);
+static int release_on_roam(struct reply_state *reply);
+
+static int reuse_lease6(struct reply_state *reply, struct iasubopt *lease);
+static void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease,
+ time_t age, int threshold);
+static void write_to_packet(struct reply_state *reply, unsigned ia_cursor);
+static const char *iasubopt_plen_str(struct iasubopt *lease);
+
+#ifdef NSUPDATE
+static void ddns_update_static6(struct reply_state* reply);
+#endif
+
#ifdef DHCP4o6
/*
* \brief Omapi I/O handler
cc = recv(dhcp4o6_fd, buf, sizeof(buf), 0);
- if (cc < DHCP_FIXED_NON_UDP + 32)
+ if (cc < DHCP_FIXED_NON_UDP + offset_data4o6)
return ISC_R_UNEXPECTED;
memset(&raw, 0, sizeof(raw));
if (!buffer_allocate(&raw.buffer, cc, MDL)) {
* \brief Send the DHCPv4-response back to the DHCPv6 side
* (DHCPv6 server function)
*
- * Format: interface:16 + address:16 + DHCPv6 DHCPv4-response message
+ * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-response message
*
* \param raw the IPC message content
*/
char name[16 + 1];
struct sockaddr_in6 to_addr;
char pbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
+ struct udp_data4o6 udp_data;
int send_ret;
memset(name, 0, sizeof(name));
memset(&to_addr, 0, sizeof(to_addr));
to_addr.sin6_family = AF_INET6;
memcpy(&to_addr.sin6_addr, raw->data + 16, 16);
- if ((raw->data[32] == DHCPV6_RELAY_FORW) ||
- (raw->data[32] == DHCPV6_RELAY_REPL)) {
- to_addr.sin6_port = local_port;
+ memset(&udp_data, 0, sizeof(udp_data));
+ memcpy(&udp_data, raw->data + 32, 4);
+ if ((raw->data[36] == DHCPV6_RELAY_FORW) ||
+ (raw->data[36] == DHCPV6_RELAY_REPL)) {
+ if (udp_data.rsp_opt_exist) {
+ to_addr.sin6_port = udp_data.src_port;
+ } else {
+ to_addr.sin6_port = local_port;
+ }
} else {
to_addr.sin6_port = remote_port;
}
log_info("send_dhcpv4_response(): sending %s on %s to %s port %d",
- dhcpv6_type_names[raw->data[32]],
+ dhcpv6_type_names[raw->data[36]],
name,
inet_ntop(AF_INET6, raw->data + 16, pbuf, sizeof(pbuf)),
ntohs(to_addr.sin6_port));
- send_ret = send_packet6(ip, raw->data + 32, raw->len - 32, &to_addr);
+ send_ret = send_packet6(ip, raw->data + 36, raw->len - 36, &to_addr);
if (send_ret < 0) {
log_error("send_dhcpv4_response: send_packet6(): %m");
- } else if (send_ret != raw->len - 32) {
+ } else if (send_ret != raw->len - 36) {
log_error("send_dhcpv4_response: send_packet6() "
"sent %d of %d bytes",
- send_ret, raw->len - 32);
+ send_ret, raw->len - 36);
}
}
#endif /* DHCP4o6 */
ret_val = 1;
exit:
- if (data.len > 0) {
- data_string_forget(&data, MDL);
- }
+ data_string_forget(&data, MDL);
if (!ret_val) {
- if (client_id->len > 0) {
- data_string_forget(client_id, MDL);
- }
+ data_string_forget(client_id, MDL);
}
return ret_val;
}
exit:
if (!ret_val) {
- if (server_id->len > 0) {
- data_string_forget(server_id, MDL);
- }
- if (client_id->len > 0) {
- data_string_forget(client_id, MDL);
- }
+ data_string_forget(server_id, MDL);
+ data_string_forget(client_id, MDL);
}
return ret_val;
}
exit:
if (!ret_val) {
- if (server_id->len > 0) {
- data_string_forget(server_id, MDL);
- }
+ data_string_forget(server_id, MDL);
}
return ret_val;
}
};
static const int required_opts_agent[] = {
D6O_INTERFACE_ID,
+#if defined(RELAY_PORT)
+ D6O_RELAY_SOURCE_PORT,
+#endif
D6O_RELAY_MSG,
0
};
shared_name,
inet_ntop(AF_INET6, &lease->addr,
tmp_addr, sizeof(tmp_addr)),
- used, count);
+ (long long unsigned)(used),
+ (long long unsigned)(count));
}
return;
}
"address: %s; high threshold %d%% %llu/%llu.",
shared_name,
inet_ntop(AF_INET6, &lease->addr, tmp_addr, sizeof(tmp_addr)),
- poolhigh, used, count);
+ poolhigh, (long long unsigned)(used),
+ (long long unsigned)(count));
/* handle the low threshold now, if we don't
* have one we default to 0. */
return result;
}
-
/*!
*
* \brief Get an IPv6 address for the client.
(!permitted(reply->packet, pond->permit_list))))
continue;
+#ifdef EUI_64
+ /* If pond is EUI-64 but client duid isn't a valid EUI-64
+ * id, then skip this pond */
+ if (pond->use_eui_64 &&
+ !valid_eui_64_duid(&reply->ia->iaid_duid, IAID_LEN)) {
+ continue;
+ }
+#endif
+
start_pool = pond->last_ipv6_pool;
i = start_pool;
do {
p = pond->ipv6_pools[i];
if (p->pool_type == D6O_IA_NA) {
- result = create_lease6(p, addr, &attempts,
- &reply->ia->iaid_duid,
- cur_time + 120);
+#ifdef EUI_64
+ if (pond->use_eui_64) {
+ result =
+ create_lease6_eui_64(p, addr,
+ &reply->ia->iaid_duid,
+ cur_time + 120);
+ }
+ else
+#endif
+ {
+ result =
+ create_lease6(p, addr, &attempts,
+ &reply->ia->iaid_duid,
+ cur_time + 120);
+
+ }
+
if (result == ISC_R_SUCCESS) {
/*
* Record the pool used (or next one if
log_debug("Unable to pick client address: "
"no addresses available - shared network %s: "
" 2^64-1 < total, %llu active, %llu abandoned",
- shared_name, active - abandoned, abandoned);
+ shared_name, (long long unsigned)(active - abandoned),
+ (long long unsigned)(abandoned));
} else {
log_debug("Unable to pick client address: "
"no addresses available - shared network %s: "
"%llu total, %llu active, %llu abandoned",
- shared_name, total, active - abandoned, abandoned);
+ shared_name, (long long unsigned)(total),
+ (long long unsigned)(active - abandoned),
+ (long long unsigned)(abandoned));
}
return ISC_R_NORESOURCES;
if (requested_pref->len < sizeof(tmp_plen) + sizeof(tmp_pref)) {
return DHCP_R_INVALIDARG;
}
+
tmp_plen = (int) requested_pref->data[0];
- if ((tmp_plen < 3) || (tmp_plen > 128) ||
- ((int)tmp_plen != pool->units)) {
+ if ((tmp_plen < 3) || (tmp_plen > 128)) {
return ISC_R_FAILURE;
}
+
memcpy(&tmp_pref, requested_pref->data + 1, sizeof(tmp_pref));
if (IN6_IS_ADDR_UNSPECIFIED(&tmp_pref)) {
return ISC_R_FAILURE;
}
+
ia.len = 16;
memcpy(&ia.iabuf, &tmp_pref, 16);
if (!is_cidr_mask_valid(&ia, (int) tmp_plen)) {
return ISC_R_FAILURE;
}
- if (!ipv6_in_pool(&tmp_pref, pool)) {
+ if (!ipv6_in_pool(&tmp_pref, pool) ||
+ ((int)tmp_plen != pool->units)) {
return ISC_R_ADDRNOTAVAIL;
}
if (result != ISC_R_SUCCESS) {
return result;
}
+
(*pref)->addr = tmp_pref;
(*pref)->plen = tmp_plen;
if (result != ISC_R_SUCCESS) {
iasubopt_dereference(pref, MDL);
}
+
return result;
}
break;
}
-#if defined (DEBUG)
- log_debug("eval_prefix_mode: "
- "len %d, preflen %d, mode %s, use_it %d",
- len, preflen,
- prefix_length_modes.values[prefix_mode].name, use_it);
-#endif
-
return (use_it);
}
* A not included IA ("cleanup" below) could give a Renew/Rebind.
*/
oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
- reply->min_valid = reply->min_prefer = 0xffffffff;
+ reply->min_valid = reply->min_prefer = INFINITE_TIME;
reply->client_valid = reply->client_prefer = 0;
for (; oc != NULL ; oc = oc->next) {
status = reply_process_addr(reply, oc);
goto cleanup;
}
- reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
- sizeof(reply->buf) - reply->cursor,
- reply->reply_ia, reply->packet,
- required_opts_IA, NULL);
-
- /* Reset the length of this IA to match what was just written. */
- putUShort(reply->buf.data + ia_cursor + 2,
- reply->cursor - (ia_cursor + 4));
-
- /* Calculate T1/T2 and stuff them in the reply */
- set_reply_tee_times(reply, ia_cursor);
-
/*
* yes, goto's aren't the best but we also want to avoid extra
* indents
*/
- if (status == ISC_R_CANCELED)
+ if (status == ISC_R_CANCELED) {
+ /* We're replying with a status code so we still need to
+ * write it out in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
goto cleanup;
+ }
/*
* Handle static leases, we always log stuff and if it's
reply->client_id.data, 60),
iaid);
+ /* Write the lease out in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
+#ifdef NSUPDATE
+ /* Performs DDNS updates if we're configured to do them */
+ ddns_update_static6(reply);
+#endif
if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
(reply->on_star.on_commit != NULL)) {
execute_statements(NULL, reply->packet, NULL, NULL,
* Loop through the assigned dynamic addresses, referencing the
* leases onto this IA_NA rather than any old ones, and updating
* pool timers for each (if any).
+ *
+ * Note that we must do ddns_updates() before we test for lease
+ * reuse (so we'll know if DNS entries are different). To ensure
+ * we don't break any configs, we run on_commit statements before
+ * we do ddns_updates() just in case the former affects the later.
+ * This is symetrical with v4 logic. We always run on_commit and
+ * ddns_udpates() whether a lease is reused or renewed.
*/
-
if ((reply->ia->num_iasubopt != 0) &&
(reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+ int must_commit = 0;
struct iasubopt *tmp;
struct data_string *ia_id;
int i;
for (i = 0 ; i < reply->ia->num_iasubopt ; i++) {
tmp = reply->ia->iasubopt[i];
-
- if (tmp->ia != NULL)
+ if (tmp->ia != NULL) {
ia_dereference(&tmp->ia, MDL);
- ia_reference(&tmp->ia, reply->ia, MDL);
+ }
- /* Commit 'hard' bindings. */
- renew_lease6(tmp->ipv6_pool, tmp);
- schedule_lease_timeout(tmp->ipv6_pool);
+ ia_reference(&tmp->ia, reply->ia, MDL);
/* If we have anything to do on commit do it now */
if (tmp->on_star.on_commit != NULL) {
}
#if defined (NSUPDATE)
- /*
- * Perform ddns updates.
- */
+
+ /* Perform ddns updates */
oc = lookup_option(&server_universe, reply->opt_state,
SV_DDNS_UPDATES);
if ((oc == NULL) ||
tmp, NULL, reply->opt_state);
}
#endif
- /* Do our threshold check. */
- check_pool6_threshold(reply, tmp);
+ if (!reuse_lease6(reply, tmp)) {
+ /* Commit 'hard' bindings. */
+ must_commit = 1;
+ renew_lease6(tmp->ipv6_pool, tmp);
+ schedule_lease_timeout(tmp->ipv6_pool);
+
+ /* Do our threshold check. */
+ check_pool6_threshold(reply, tmp);
+ }
}
+ /* write the IA_NA in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
+
/* Remove any old ia from the hash. */
if (reply->old_ia != NULL) {
- ia_id = &reply->old_ia->iaid_duid;
- ia_hash_delete(ia_na_active,
- (unsigned char *)ia_id->data,
- ia_id->len, MDL);
+ if (!release_on_roam(reply)) {
+ ia_id = &reply->old_ia->iaid_duid;
+ ia_hash_delete(ia_na_active,
+ (unsigned char *)ia_id->data,
+ ia_id->len, MDL);
+ }
+
ia_dereference(&reply->old_ia, MDL);
}
ia_hash_add(ia_na_active, (unsigned char *)ia_id->data,
ia_id->len, reply->ia, MDL);
- write_ia(reply->ia);
+ /* If we couldn't reuse all of the iasubopts, we
+ * must update udpate the lease db */
+ if (must_commit) {
+ write_ia(reply->ia);
+ }
} else {
+ /* write the IA_NA in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
schedule_lease_timeout_reply(reply);
}
return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
}
+/*
+ * Writes the populated IA_xx in wire format to the reply buffer
+ */
+void
+write_to_packet(struct reply_state *reply, unsigned ia_cursor) {
+ reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
+ sizeof(reply->buf) - reply->cursor,
+ reply->reply_ia, reply->packet,
+ (reply->ia->ia_type != D6O_IA_PD ?
+ required_opts_IA : required_opts_IA_PD),
+ NULL);
+
+ /* Reset the length of this IA to match what was just written. */
+ putUShort(reply->buf.data + ia_cursor + 2,
+ reply->cursor - (ia_cursor + 4));
+
+ if (reply->ia->ia_type != D6O_IA_TA) {
+ /* Calculate T1/T2 and stuff them in the reply */
+ set_reply_tee_times(reply, ia_cursor);
+ }
+}
+
/*
* Process an IAADDR within a given IA_xA, storing any IAADDR reply contents
* into the reply's current ia-scoped option cache. Returns ISC_R_CANCELED
struct option_cache *oc;
struct data_string iaaddr, data;
isc_result_t status = ISC_R_SUCCESS;
+#ifdef EUI_64
+ int invalid_for_eui_64 = 0;
+#endif
/* Initializes values that will be cleaned up. */
memset(&iaaddr, 0, sizeof(iaaddr));
break;
}
+#ifdef EUI_64
+ if (subnet) {
+ /* If the requested address falls into an EUI-64 pool, then
+ * we need to verify if it has EUI-64 duid AND the requested
+ * address is correct for that duid. If not we treat it just
+ * like an not-on-link request. */
+ struct ipv6_pool* pool = NULL;
+ struct in6_addr* addr = (struct in6_addr*)(iaaddr.data);
+ if ((find_ipv6_pool(&pool, D6O_IA_NA, addr) == ISC_R_SUCCESS)
+ && (pool->ipv6_pond->use_eui_64) &&
+ (!valid_for_eui_64_pool(pool, &reply->client_id, 0, addr))) {
+ log_debug ("Requested address: %s,"
+ " not valid for EUI-64 pool",
+ pin6_addr(addr));
+ invalid_for_eui_64 = 1;
+ }
+ }
+#endif
+
/* Address not found on shared network. */
+#ifdef EUI_64
+ if ((subnet == NULL) || invalid_for_eui_64) {
+#else
if (subnet == NULL) {
+#endif
/* Ignore this address on 'soft' bindings. */
if (reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
/* disable rapid commit */
goto send_addr;
}
+
/* Verify the address belongs to the client. */
if (!address_is_owned(reply, &tmp_addr)) {
/*
* Deal with an IAADDR for lifetimes.
* For all or none, process IAADDRs as hints.
*/
- reply->min_valid = reply->min_prefer = 0xffffffff;
+ reply->min_valid = reply->min_prefer = INFINITE_TIME;
reply->client_valid = reply->client_prefer = 0;
oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR);
for (; oc != NULL; oc = oc->next) {
goto cleanup;
store:
- reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
- sizeof(reply->buf) - reply->cursor,
- reply->reply_ia, reply->packet,
- required_opts_IA, NULL);
-
- /* Reset the length of this IA to match what was just written. */
- putUShort(reply->buf.data + ia_cursor + 2,
- reply->cursor - (ia_cursor + 4));
/*
* yes, goto's aren't the best but we also want to avoid extra
* indents
*/
- if (status == ISC_R_CANCELED)
+ if (status == ISC_R_CANCELED) {
+ /* We're replying with a status code so we still need to
+ * write it out in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
goto cleanup;
+ }
/*
* If we have any addresses log what we are doing.
*/
if ((reply->ia->num_iasubopt != 0) &&
(reply->buf.reply.msg_type == DHCPV6_REPLY)) {
+ int must_commit = 0;
struct iasubopt *tmp;
struct data_string *ia_id;
int i;
ia_dereference(&tmp->ia, MDL);
ia_reference(&tmp->ia, reply->ia, MDL);
- /* Commit 'hard' bindings. */
- renew_lease6(tmp->ipv6_pool, tmp);
- schedule_lease_timeout(tmp->ipv6_pool);
-
/* If we have anything to do on commit do it now */
if (tmp->on_star.on_commit != NULL) {
execute_statements(NULL, reply->packet,
tmp, NULL, reply->opt_state);
}
#endif
- /* Do our threshold check. */
- check_pool6_threshold(reply, tmp);
+
+ if (!reuse_lease6(reply, tmp)) {
+ /* Commit 'hard' bindings. */
+ must_commit = 1;
+ renew_lease6(tmp->ipv6_pool, tmp);
+ schedule_lease_timeout(tmp->ipv6_pool);
+
+ /* Do our threshold check. */
+ check_pool6_threshold(reply, tmp);
+ }
}
+ /* write the IA_TA in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
+
/* Remove any old ia from the hash. */
if (reply->old_ia != NULL) {
- ia_id = &reply->old_ia->iaid_duid;
- ia_hash_delete(ia_ta_active,
- (unsigned char *)ia_id->data,
- ia_id->len, MDL);
+ if (!release_on_roam(reply)) {
+ ia_id = &reply->old_ia->iaid_duid;
+ ia_hash_delete(ia_ta_active,
+ (unsigned char *)ia_id->data,
+ ia_id->len, MDL);
+ }
+
ia_dereference(&reply->old_ia, MDL);
}
ia_hash_add(ia_ta_active, (unsigned char *)ia_id->data,
ia_id->len, reply->ia, MDL);
- write_ia(reply->ia);
+ /* If we couldn't reuse all of the iasubopts, we
+ * must update udpate the lease db */
+ if (must_commit) {
+ write_ia(reply->ia);
+ }
} else {
+ /* write the IA_TA in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
schedule_lease_timeout_reply(reply);
}
*/
return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status);
}
+/*
+ * Determines if a lease (iasubopt) can be reused without extending it.
+ * If dhcp-cache-threshold is greater than zero (i.e enabled) then
+ * a lease may be reused without going through a full renewal if
+ * it meets all the requirements. In short it must be active, younger
+ * than the threshold, and not have DNS changes.
+ *
+ * If it is determined that it can be reused, that a call to
+ * shorten_lifetimes() is made to reduce the valid and preferred lifetimes
+ * sent to the client by the age of the lease.
+ *
+ * Returns 1 if lease can be reused, 0 otherwise
+ */
+int
+reuse_lease6(struct reply_state *reply, struct iasubopt *lease) {
+ int threshold = DEFAULT_CACHE_THRESHOLD;
+ struct option_cache* oc = NULL;
+ struct data_string d1;
+ time_t age;
+ time_t limit;
+ int reuse_it = 0;
+
+ /* In order to even qualify for reuse consideration:
+ * 1. Lease must be active
+ * 2. It must have been accepted at least once
+ * 3. DNS info must not have changed */
+ if ((lease->state != FTS_ACTIVE) ||
+ (lease->hard_lifetime_end_time == 0) ||
+ (lease->ddns_cb != NULL)) {
+ return (0);
+ }
+
+ /* Look up threshold value */
+ memset(&d1, 0, sizeof(struct data_string));
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_CACHE_THRESHOLD);
+ if (oc &&
+ evaluate_option_cache(&d1, reply->packet, NULL, NULL,
+ reply->packet->options, reply->opt_state,
+ &lease->scope, oc, MDL)) {
+ if (d1.len == 1 && (d1.data[0] < 100)) {
+ threshold = d1.data[0];
+ }
+
+ data_string_forget(&d1, MDL);
+ }
+
+ if (threshold <= 0) {
+ return (0);
+ }
+
+ if (lease->valid >= MAX_TIME) {
+ /* Infinite leases are always reused. We have to make
+ * a choice because we cannot determine when they actually
+ * began, so we either always reuse them or we never do. */
+ log_debug ("reusing infinite lease for: %s%s",
+ pin6_addr(&lease->addr), iasubopt_plen_str(lease));
+ return (1);
+ }
+
+ age = cur_tv.tv_sec - (lease->hard_lifetime_end_time - lease->valid);
+ if (lease->valid <= (INT_MAX / threshold))
+ limit = lease->valid * threshold / 100;
+ else
+ limit = lease->valid / 100 * threshold;
+
+ if (age < limit) {
+ /* Reduce valid/preferred going to the client by age */
+ shorten_lifetimes(reply, lease, age, threshold);
+ reuse_it = 1;
+ }
+
+ return (reuse_it);
+}
+
+/*
+ * Reduces the valid and preferred lifetimes for a given lease (iasubopt)
+ *
+ * We cannot determine until after a iasubopt has been added to
+ * the reply if the lease can be reused. Therefore, when we do reuse a
+ * lease we need a way to alter the lifetimes that will be sent to the client.
+ * That's where this function comes in handy:
+ *
+ * Locate the iasubopt by it's address within the reply the reduce both
+ * the preferred and valid lifetimes by the given number of seconds.
+ *
+ * Note that this function, by necessity, works directly with the
+ * option_cache data. Sort of a no-no but I don't have any better ideas.
+ */
+void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease,
+ time_t age, int threshold) {
+ struct option_cache* oc = NULL;
+ int subopt_type;
+ int addr_offset;
+ int pref_offset;
+ int val_offset;
+ int exp_length;
+
+ if (reply->ia->ia_type != D6O_IA_PD) {
+ subopt_type = D6O_IAADDR;
+ addr_offset = IASUBOPT_NA_ADDR_OFFSET;
+ pref_offset = IASUBOPT_NA_PREF_OFFSET;
+ val_offset = IASUBOPT_NA_VALID_OFFSET;
+ exp_length = IASUBOPT_NA_LEN;
+ }
+ else {
+ subopt_type = D6O_IAPREFIX;
+ addr_offset = IASUBOPT_PD_PREFIX_OFFSET;
+ pref_offset = IASUBOPT_PD_PREF_OFFSET;
+ val_offset = IASUBOPT_PD_VALID_OFFSET;
+ exp_length = IASUBOPT_PD_LEN;
+ }
+
+ // loop through the iasubopts for the one that matches this lease
+ oc = lookup_option(&dhcpv6_universe, reply->reply_ia, subopt_type);
+ for (; oc != NULL ; oc = oc->next) {
+ if (oc->data.data == NULL || oc->data.len != exp_length) {
+ /* shouldn't happen */
+ continue;
+ }
+
+ /* If address matches (and for PDs the prefix len matches)
+ * we assume this is our subopt, so update the lifetimes */
+ if (!memcmp(oc->data.data + addr_offset, &lease->addr, 16) &&
+ (subopt_type != D6O_IAPREFIX ||
+ (oc->data.data[IASUBOPT_PD_PREFLEN_OFFSET] ==
+ lease->plen))) {
+ u_int32_t pref_life = getULong(oc->data.data +
+ pref_offset);
+ u_int32_t valid_life = getULong(oc->data.data +
+ val_offset);
+
+ if (pref_life < MAX_TIME && pref_life > age) {
+ pref_life -= age;
+ putULong((unsigned char*)(oc->data.data) +
+ pref_offset, pref_life);
+
+ if (reply->min_prefer > pref_life) {
+ reply->min_prefer = pref_life;
+ }
+ }
+
+ if (valid_life < MAX_TIME && valid_life > age) {
+ valid_life -= age;
+ putULong((unsigned char*)(oc->data.data) +
+ val_offset, valid_life);
+
+ if (reply->min_valid > reply->send_valid) {
+ reply->min_valid = valid_life;
+ }
+ }
+
+ log_debug ("Reusing lease for: %s%s, "
+ "age %ld secs < %d%%,"
+ " sending shortened lifetimes -"
+ " preferred: %u, valid %u",
+ pin6_addr(&lease->addr),
+ iasubopt_plen_str(lease),
+ (long)age, threshold,
+ pref_life, valid_life);
+ break;
+ }
+ }
+}
/*
* Verify the temporary address is available.
* Get an address in this temporary pool.
*/
status = create_lease6(p, &reply->lease, &attempts,
- &reply->client_id, cur_time + 120);
+ &reply->client_id,
+ cur_time + 120);
+
if (status != ISC_R_SUCCESS) {
log_debug("Unable to get a temporary address.");
goto cleanup;
* The following doesn't work on at least some systems:
* (cur_time + reply->send_valid < cur_time)
*/
- if (reply->send_valid != 0xFFFFFFFF) {
+ if (reply->send_valid != INFINITE_TIME) {
time_t test_time = cur_time + reply->send_valid;
if (test_time < cur_time)
- reply->send_valid = 0xFFFFFFFF;
+ reply->send_valid = INFINITE_TIME;
}
if (reply->client_prefer == 0)
reply->send_prefer = reply->client_prefer;
if ((reply->send_prefer >= reply->send_valid) &&
- (reply->send_valid != 0xFFFFFFFF))
+ (reply->send_valid != INFINITE_TIME))
reply->send_prefer = (reply->send_valid / 2) +
(reply->send_valid / 8);
* when connecting to the lease file MAX_TIME is
*/
if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
- if (reply->send_valid == 0xFFFFFFFF) {
+ if (reply->send_valid == INFINITE_TIME) {
reply->lease->soft_lifetime_end_time = MAX_TIME;
} else {
reply->lease->soft_lifetime_end_time =
* For each prefix in this IA_PD, decide what to do about it.
*/
oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAPREFIX);
- reply->min_valid = reply->min_prefer = 0xffffffff;
+ reply->min_valid = reply->min_prefer = INFINITE_TIME;
reply->client_valid = reply->client_prefer = 0;
reply->preflen = -1;
for (; oc != NULL ; oc = oc->next) {
goto cleanup;
}
- reply->cursor += store_options6((char *)reply->buf.data + reply->cursor,
- sizeof(reply->buf) - reply->cursor,
- reply->reply_ia, reply->packet,
- required_opts_IA_PD, NULL);
-
- /* Reset the length of this IA_PD to match what was just written. */
- putUShort(reply->buf.data + ia_cursor + 2,
- reply->cursor - (ia_cursor + 4));
-
- /* Calculate T1/T2 and stuff them in the reply */
- set_reply_tee_times(reply, ia_cursor);
-
/*
* yes, goto's aren't the best but we also want to avoid extra
* indents
*/
- if (status == ISC_R_CANCELED)
+ if (status == ISC_R_CANCELED) {
+ /* We're replying with a status code so we still need to
+ * write it out in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
goto cleanup;
+ }
/*
* Handle static prefixes, we always log stuff and if it's
print_hex_1(reply->client_id.len,
reply->client_id.data, 60),
iaid);
+
+ /* Write the lease out in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
+
if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
(reply->on_star.on_commit != NULL)) {
execute_statements(NULL, reply->packet, NULL, NULL,
* Loop through the assigned dynamic prefixes, referencing the
* prefixes onto this IA_PD rather than any old ones, and updating
* prefix pool timers for each (if any).
+ *
+ * If a lease can be reused we skip renewing it or checking the
+ * pool threshold. If it can't we flag that the IA must be commited
+ * to the db and do the renewal and pool check.
*/
if ((reply->buf.reply.msg_type == DHCPV6_REPLY) &&
(reply->ia->num_iasubopt != 0)) {
+ int must_commit = 0;
struct iasubopt *tmp;
struct data_string *ia_id;
int i;
ia_dereference(&tmp->ia, MDL);
ia_reference(&tmp->ia, reply->ia, MDL);
- /* Commit 'hard' bindings. */
- renew_lease6(tmp->ipv6_pool, tmp);
- schedule_lease_timeout(tmp->ipv6_pool);
-
/* If we have anything to do on commit do it now */
if (tmp->on_star.on_commit != NULL) {
execute_statements(NULL, reply->packet,
(&tmp->on_star.on_commit, MDL);
}
- /* Do our threshold check. */
- check_pool6_threshold(reply, tmp);
+ if (!reuse_lease6(reply, tmp)) {
+ /* Commit 'hard' bindings. */
+ must_commit = 1;
+ renew_lease6(tmp->ipv6_pool, tmp);
+ schedule_lease_timeout(tmp->ipv6_pool);
+
+ /* Do our threshold check. */
+ check_pool6_threshold(reply, tmp);
+ }
}
+ /* write the IA_PD in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
+
/* Remove any old ia from the hash. */
if (reply->old_ia != NULL) {
- ia_id = &reply->old_ia->iaid_duid;
- ia_hash_delete(ia_pd_active,
- (unsigned char *)ia_id->data,
- ia_id->len, MDL);
+ if (!release_on_roam(reply)) {
+ ia_id = &reply->old_ia->iaid_duid;
+ ia_hash_delete(ia_pd_active,
+ (unsigned char *)ia_id->data,
+ ia_id->len, MDL);
+ }
+
ia_dereference(&reply->old_ia, MDL);
}
ia_hash_add(ia_pd_active, (unsigned char *)ia_id->data,
ia_id->len, reply->ia, MDL);
- write_ia(reply->ia);
+ /* If we couldn't reuse all of the iasubopts, we
+ * must udpate the lease db */
+ if (must_commit) {
+ write_ia(reply->ia);
+ }
} else {
+ /* write the IA_PD in wire-format to the outbound buffer */
+ write_to_packet(reply, ia_cursor);
schedule_lease_timeout_reply(reply);
}
best_prefix = prefix_compare(reply, prefix,
best_prefix);
}
+
+ /*
+ * If we have prefix length hint and we're not igoring them,
+ * then toss the best match if it doesn't match the hint,
+ * unless this is in response to a rebind. In the latter
+ * case we're supposed to return it with zero lifetimes.
+ * (See rt45780) */
+ if (best_prefix && (reply->preflen > 0)
+ && (prefix_length_mode != PLM_IGNORE)
+ && (reply->preflen != best_prefix->plen)
+ && (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) {
+ best_prefix = NULL;
+ }
}
/* Try to pick a new prefix if we didn't find one, or if we found an
* The following doesn't work on at least some systems:
* (cur_time + reply->send_valid < cur_time)
*/
- if (reply->send_valid != 0xFFFFFFFF) {
+ if (reply->send_valid != INFINITE_TIME) {
time_t test_time = cur_time + reply->send_valid;
if (test_time < cur_time)
- reply->send_valid = 0xFFFFFFFF;
+ reply->send_valid = INFINITE_TIME;
}
if (reply->client_prefer == 0)
reply->send_prefer = reply->client_prefer;
if ((reply->send_prefer >= reply->send_valid) &&
- (reply->send_valid != 0xFFFFFFFF))
+ (reply->send_valid != INFINITE_TIME))
reply->send_prefer = (reply->send_valid / 2) +
(reply->send_valid / 8);
* when connecting to the lease file MAX_TIME is
*/
if (reply->buf.reply.msg_type == DHCPV6_REPLY) {
- if (reply->send_valid == 0xFFFFFFFF) {
+ if (reply->send_valid == INFINITE_TIME) {
reply->lease->soft_lifetime_end_time = MAX_TIME;
} else {
reply->lease->soft_lifetime_end_time =
data_string_forget(&a_opt, MDL);
}
+#if defined(RELAY_PORT)
+ /*
+ * Append the relay_source_port option if present.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options,
+ D6O_RELAY_SOURCE_PORT);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&a_opt, packet,
+ NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("dhcpv6_relay_forw: error evaluating "
+ "Relay Source Port.");
+ goto exit;
+ }
+ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+ (unsigned char *)a_opt.data,
+ a_opt.len,
+ D6O_RELAY_SOURCE_PORT, 0)) {
+ log_error("dhcpv6_relay_forw: error saving "
+ "Relay Source Port.");
+ goto exit;
+ }
+ data_string_forget(&a_opt, MDL);
+
+ packet->relay_source_port = ISC_TRUE;
+ }
+#endif
+
/*
* Append our encapsulated stuff for caller.
*/
data_string_forget(&a_opt, MDL);
}
+#if defined(RELAY_PORT)
+ /*
+ * Append the relay_source_port option if present.
+ */
+ oc = lookup_option(&dhcpv6_universe, packet->options,
+ D6O_RELAY_SOURCE_PORT);
+ if (oc != NULL) {
+ if (!evaluate_option_cache(&a_opt, packet,
+ NULL, NULL,
+ packet->options, NULL,
+ &global_scope, oc, MDL)) {
+ log_error("dhcpv4o6_relay_forw: error evaluating "
+ "Relay Source Port.");
+ goto exit;
+ }
+ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL,
+ (unsigned char *)a_opt.data,
+ a_opt.len,
+ D6O_RELAY_SOURCE_PORT, 0)) {
+ log_error("dhcpv4o6_relay_forw: error saving "
+ "Relay Source Port.");
+ goto exit;
+ }
+ data_string_forget(&a_opt, MDL);
+
+ packet->relay_source_port = ISC_TRUE;
+ }
+#endif
+
/*
* Append our encapsulated stuff for caller.
*/
* \brief Forward a DHCPv4-query message to the DHCPv4 side
* (DHCPv6 server function)
*
- * Format: interface:16 + address:16 + DHCPv6 DHCPv4-query message
+ * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-query message
*
* \brief packet the DHCPv6 DHCPv4-query message
*/
static void forw_dhcpv4_query(struct packet *packet) {
struct data_string ds;
+ struct udp_data4o6 udp_data;
unsigned len;
int cc;
}
/* Get a buffer. */
- len = packet->packet_length + 32;
+ len = packet->packet_length + 36;
memset(&ds, 0, sizeof(ds));
if (!buffer_allocate(&ds.buffer, len, MDL)) {
log_error("forw_dhcpv4_query: "
strncpy((char *)ds.buffer->data, packet->interface->name, 16);
memcpy(ds.buffer->data + 16,
packet->client_addr.iabuf, 16);
- memcpy(ds.buffer->data + 32,
+ memset(&udp_data, 0, sizeof(udp_data));
+ udp_data.src_port = packet->client_port;
+ memcpy(ds.buffer->data + 32, &udp_data, 4);
+ memcpy(ds.buffer->data + 36,
(unsigned char *)packet->raw,
packet->packet_length);
to_addr.sin6_port = packet->client_port;
#endif
+#if defined(RELAY_PORT)
+ /*
+ * Check relay source port.
+ */
+ if (packet->relay_source_port) {
+ to_addr.sin6_port = packet->client_port;
+ }
+#endif
+
memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf,
sizeof(to_addr.sin6_addr));
* Receive a message with a DHCPv4-query inside from the DHCPv6 server.
* (code copied from \ref do_packet6() \ref and dhcpv6())
*
- * Format: interface:16 + address:16 + DHCPv6 DHCPv4-query message
+ * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-query message
*
* \param raw the DHCPv6 DHCPv4-query message raw content
*/
const struct dhcpv4_over_dhcpv6_packet *msg;
struct data_string reply;
struct data_string ds;
+ struct udp_data4o6 udp_data;
unsigned len;
int cc;
iaddr.len = 16;
memcpy(iaddr.iabuf, raw->data + 16, 16);
+ memset(&udp_data, 0, sizeof(udp_data));
+ memcpy(&udp_data, raw->data + 32, 4);
+
/*
* From do_packet6().
*/
- if (!packet6_len_okay((char *)raw->data + 32, raw->len - 32)) {
+ if (!packet6_len_okay((char *)raw->data + 36, raw->len - 36)) {
log_error("recv_dhcpv4_query: "
"short packet from %s, len %d, dropped",
- piaddr(iaddr), raw->len - 32);
+ piaddr(iaddr), raw->len - 36);
return;
}
return;
}
- packet->raw = (struct dhcp_packet *)(raw->data + 32);
- packet->packet_length = raw->len - 32;
- packet->client_port = remote_port;
+ packet->raw = (struct dhcp_packet *)(raw->data + 36);
+ packet->packet_length = raw->len - 36;
+ packet->client_port = udp_data.src_port;
packet->client_addr = iaddr;
interface_reference(&packet->interface, ip, MDL);
- msg_type = raw->data[32];
+ msg_type = raw->data[36];
if ((msg_type == DHCPV6_RELAY_FORW) ||
(msg_type == DHCPV6_RELAY_REPL)) {
int relaylen =
(int)(offsetof(struct dhcpv6_relay_packet, options));
- relay = (const struct dhcpv6_relay_packet *)(raw->data + 32);
+ relay = (const struct dhcpv6_relay_packet *)(raw->data + 36);
packet->dhcpv6_msg_type = relay->msg_type;
/* relay-specific data */
if (!parse_option_buffer(packet->options,
relay->options,
- raw->len - 32 - relaylen,
+ raw->len - 36 - relaylen,
&dhcpv6_universe)) {
/* no logging here, as parse_option_buffer() logs all
cases where it fails */
(msg_type == DHCPV6_DHCPV4_RESPONSE)) {
int msglen =
(int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options));
- msg = (struct dhcpv4_over_dhcpv6_packet *)(raw->data + 32);
+ msg = (struct dhcpv4_over_dhcpv6_packet *)(raw->data + 36);
packet->dhcpv6_msg_type = msg->msg_type;
/* message-specific data */
if (!parse_option_buffer(packet->options,
msg->options,
- raw->len - 32 - msglen,
+ raw->len - 36 - msglen,
&dhcpv6_universe)) {
/* no logging here, as parse_option_buffer() logs all
cases where it fails */
*/
build_dhcpv6_reply(&reply, packet);
- packet_dereference(&packet, MDL);
-
- if (reply.data == NULL)
+ if (reply.data == NULL) {
+ packet_dereference(&packet, MDL);
return;
+ }
/*
* Forward the response.
*/
- len = reply.len + 32;
+ len = reply.len + 36;
memset(&ds, 0, sizeof(ds));
if (!buffer_allocate(&ds.buffer, len, MDL)) {
log_error("recv_dhcpv4_query: no memory.");
+ packet_dereference(&packet, MDL);
return;
}
ds.data = ds.buffer->data;
memcpy(ds.buffer->data, name, 16);
memcpy(ds.buffer->data + 16, iaddr.iabuf, 16);
- memcpy(ds.buffer->data + 32, reply.data, reply.len);
+ udp_data.rsp_opt_exist = packet->relay_source_port ? 1 : 0;
+ memcpy(ds.buffer->data + 32, &udp_data, 4);
+ memcpy(ds.buffer->data + 36, reply.data, reply.len);
+
+ /*
+ * Now we can release the packet.
+ */
+ packet_dereference(&packet, MDL);
+
cc = send(dhcp4o6_fd, ds.data, ds.len, 0);
if (cc < 0)
log_error("recv_dhcpv4_query: send(): %m");
} else if (set_tee_times) {
/* Setting them is enabled so T1 is either infinite or
* 0.5 * the shortest preferred lifetime in the IA_XX */
- reply->renew = (reply->min_prefer == 0xFFFFFFFF ? 0xFFFFFFFF
- : reply->min_prefer / 2);
+ if (reply->min_prefer == INFINITE_TIME)
+ reply->renew = INFINITE_TIME;
+ else
+ reply->renew = reply->min_prefer / 2;
} else {
/* Default is to let the client choose */
reply->renew = 0;
} else if (set_tee_times) {
/* Setting them is enabled so T2 is either infinite or
* 0.8 * the shortest preferred lifetime in the reply */
- reply->rebind = (reply->min_prefer == 0xFFFFFFFF ? 0xFFFFFFFF
- : (reply->min_prefer / 5) * 4);
+ if (reply->min_prefer == INFINITE_TIME)
+ reply->rebind = INFINITE_TIME;
+ else
+ reply->rebind = (reply->min_prefer / 5) * 4;
} else {
/* Default is to let the client choose */
reply->rebind = 0;
putULong(reply->buf.data + ia_cursor + 12, reply->rebind);
}
+/*
+ * Releases the iasubopts in the pre-existing IA, if they are not in
+ * the same shared-network as the new IA.
+ *
+ * returns 1 if the release was done, 0 otherwise
+ */
+int
+release_on_roam(struct reply_state* reply) {
+ struct ia_xx* old_ia = reply->old_ia;
+ struct iasubopt *lease = NULL;
+ int i;
+
+ if ((!do_release_on_roam) || old_ia == NULL
+ || old_ia->num_iasubopt <= 0) {
+ return(0);
+ }
+
+ /* If the old shared-network and new are the same, client hasn't
+ * roamed, nothing to do. We only check the first one because you
+ * cannot have iasubopts on different shared-networks within a
+ * single ia. */
+ lease = old_ia->iasubopt[0];
+ if (lease->ipv6_pool->shared_network == reply->shared) {
+ return (0);
+ }
+
+ /* Old and new are on different shared networks so the client must
+ * roamed. Release the old leases. */
+ for (i = 0; i < old_ia->num_iasubopt; i++) {
+ lease = old_ia->iasubopt[i];
+
+ log_info("Client: %s roamed to new network,"
+ " releasing lease: %s%s",
+ print_hex_1(reply->client_id.len,
+ reply->client_id.data, 60),
+ pin6_addr(&lease->addr), iasubopt_plen_str(lease));
+
+ release_lease6(lease->ipv6_pool, lease);
+ lease->ia->cltt = cur_time;
+ write_ia(lease->ia);
+ }
+
+ return (1);
+}
+
+/*
+ * Convenience function which returns a string (static buffer)
+ * containing either a "/" followed by the prefix length or an
+ * empty string depending on the lease type
+ */
+const char *iasubopt_plen_str(struct iasubopt *lease) {
+ static char prefix_buf[16];
+ *prefix_buf = 0;
+ if ((lease->ia) && (lease->ia->ia_type == D6O_IA_PD)) {
+ sprintf(prefix_buf, "/%-d", lease->plen);
+ }
+
+ return (prefix_buf);
+}
+
+#ifdef NSUPDATE
+/*
+ * Initiates DDNS updates for static v6 leases if configured to do so.
+ *
+ * The function, which must be called after the IA has been written to the
+ * packet, adds an iasubopt to the IA for static lease. This is done so we
+ * have an iasubopt to pass into ddns_updates(). A reference to the IA is
+ * added to the DDNS control block to ensure it and it's iasubopt remain in
+ * scope until the update is complete.
+ *
+ */
+void ddns_update_static6(struct reply_state* reply) {
+ struct iasubopt *iasub = NULL;
+ struct binding_scope *scope = NULL;
+ struct option_cache *oc = NULL;
+
+ oc = lookup_option(&server_universe, reply->opt_state, SV_DDNS_UPDATES);
+ if ((oc != NULL) &&
+ (evaluate_boolean_option_cache(NULL, reply->packet, NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, NULL,
+ oc, MDL) == 0)) {
+ return;
+ }
+
+ oc = lookup_option(&server_universe, reply->opt_state,
+ SV_UPDATE_STATIC_LEASES);
+ if ((oc == NULL) ||
+ (evaluate_boolean_option_cache(NULL, reply->packet,
+ NULL, NULL,
+ reply->packet->options,
+ reply->opt_state, NULL,
+ oc, MDL) == 0)) {
+ return;
+ }
+
+ if (iasubopt_allocate(&iasub, MDL) != ISC_R_SUCCESS) {
+ log_fatal("No memory for iasubopt.");
+ }
+
+ if (ia_add_iasubopt(reply->ia, iasub, MDL) != ISC_R_SUCCESS) {
+ log_fatal("Could not add iasubopt.");
+ }
+
+ ia_reference(&iasub->ia, reply->ia, MDL);
+
+ memcpy(iasub->addr.s6_addr, reply->fixed.data, 16);
+ iasub->plen = 0;
+ iasub->prefer = MAX_TIME;
+ iasub->valid = MAX_TIME;
+ iasub->static_lease = 1;
+
+ if (!binding_scope_allocate(&scope, MDL)) {
+ log_fatal("Out of memory for binding scope.");
+ }
+
+ binding_scope_reference(&iasub->scope, scope, MDL);
+
+ ddns_updates(reply->packet, NULL, NULL, iasub, NULL, reply->opt_state);
+}
+#endif /* NSUPDATE */
#endif /* DHCPv6 */