2007-05-08 23:05:22 +00:00
|
|
|
/*
|
2007-05-19 19:16:28 +00:00
|
|
|
* Copyright (C) 2006-2007 by Internet Systems Consortium, Inc. ("ISC")
|
2007-05-08 23:05:22 +00:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "dhcpd.h"
|
|
|
|
|
2007-05-19 18:47:15 +00:00
|
|
|
#ifdef DHCPv6
|
|
|
|
|
2007-05-08 23:05:22 +00:00
|
|
|
/*
|
|
|
|
* We use print_hex_1() to output DUID values. We could actually output
|
|
|
|
* the DUID with more information... MAC address if using type 1 or 3,
|
|
|
|
* and so on. However, RFC 3315 contains Grave Warnings against actually
|
|
|
|
* attempting to understand a DUID.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO: gettext() or other method of localization for the messages
|
|
|
|
* for status codes (and probably for log formats eventually)
|
|
|
|
* TODO: refactoring (simplify, simplify, simplify)
|
|
|
|
* TODO: support multiple shared_networks on each interface (this
|
|
|
|
* will allow the server to issue multiple IPv6 addresses to
|
|
|
|
* a single interface)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prototypes local to this file.
|
|
|
|
*/
|
|
|
|
static void build_dhcpv6_reply(struct data_string *, struct packet *);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DUID time starts 2000-01-01.
|
|
|
|
* This constant is the number of seconds since 1970-01-01,
|
|
|
|
* when the Unix epoch began.
|
|
|
|
*/
|
|
|
|
#define DUID_TIME_EPOCH 946684800
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This function returns the time since DUID time start for the
|
|
|
|
* given time_t value.
|
|
|
|
*/
|
|
|
|
static u_int32_t
|
|
|
|
duid_time(time_t when) {
|
|
|
|
/*
|
|
|
|
* This time is modulo 2^32.
|
|
|
|
*/
|
|
|
|
while ((when - DUID_TIME_EPOCH) > 4294967295u) {
|
|
|
|
/* use 2^31 to avoid spurious compiler warnings */
|
|
|
|
when -= 2147483648u;
|
|
|
|
when -= 2147483648u;
|
|
|
|
}
|
|
|
|
|
|
|
|
return when - DUID_TIME_EPOCH;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Server DUID.
|
|
|
|
*
|
|
|
|
* This must remain the same for the lifetime of this server, because
|
|
|
|
* clients return the server DUID that we sent them in Request packets.
|
|
|
|
*
|
|
|
|
* We pick the server DUID like this:
|
|
|
|
*
|
|
|
|
* 1. Check dhcpd.conf - any value the administrator has configured
|
|
|
|
* overrides any possible values.
|
|
|
|
* 2. Check the leases.txt - we want to use the previous value if
|
|
|
|
* possible.
|
|
|
|
* 3. Check if dhcpd.conf specifies a type of server DUID to use,
|
|
|
|
* and generate that type.
|
|
|
|
* 4. Generate a type 1 (time + hardware address) DUID.
|
|
|
|
*/
|
|
|
|
static struct data_string server_duid;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if the server_duid has been set.
|
|
|
|
*/
|
|
|
|
isc_boolean_t
|
|
|
|
server_duid_isset(void) {
|
|
|
|
return (server_duid.data != NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the server_duid.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
copy_server_duid(struct data_string *ds, const char *file, int line) {
|
|
|
|
data_string_copy(ds, &server_duid, file, line);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the server DUID to a specified value. This is used when
|
|
|
|
* the server DUID is stored in persistent memory (basically the
|
|
|
|
* leases.txt file).
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
set_server_duid(struct data_string *new_duid) {
|
|
|
|
/* INSIST(new_duid != NULL); */
|
|
|
|
/* INSIST(new_duid->data != NULL); */
|
|
|
|
|
|
|
|
if (server_duid_isset()) {
|
|
|
|
data_string_forget(&server_duid, MDL);
|
|
|
|
}
|
|
|
|
data_string_copy(&server_duid, new_duid, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the server DUID based on the D6O_SERVERID option. This handles
|
|
|
|
* the case where the administrator explicitly put it in the dhcpd.conf
|
|
|
|
* file.
|
|
|
|
*/
|
|
|
|
isc_result_t
|
|
|
|
set_server_duid_from_option(void) {
|
|
|
|
struct option_state *opt_state;
|
|
|
|
struct option_cache *oc;
|
|
|
|
struct data_string option_duid;
|
|
|
|
isc_result_t ret_val;
|
|
|
|
|
|
|
|
opt_state = NULL;
|
|
|
|
if (!option_state_allocate(&opt_state, MDL)) {
|
|
|
|
log_fatal("No memory for server DUID.");
|
|
|
|
}
|
|
|
|
|
|
|
|
execute_statements_in_scope(NULL, NULL, NULL, NULL, NULL,
|
|
|
|
opt_state, &global_scope, root_group, NULL);
|
|
|
|
|
|
|
|
oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
|
|
|
|
if (oc == NULL) {
|
|
|
|
ret_val = ISC_R_NOTFOUND;
|
|
|
|
} else {
|
|
|
|
memset(&option_duid, 0, sizeof(option_duid));
|
|
|
|
if (!evaluate_option_cache(&option_duid, NULL, NULL, NULL,
|
|
|
|
opt_state, NULL, &global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
ret_val = ISC_R_UNEXPECTED;
|
|
|
|
} else {
|
|
|
|
set_server_duid(&option_duid);
|
|
|
|
data_string_forget(&option_duid, MDL);
|
|
|
|
ret_val = ISC_R_SUCCESS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
option_state_dereference(&opt_state, MDL);
|
|
|
|
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* DUID layout, as defined in RFC 3315, section 9.
|
|
|
|
*
|
|
|
|
* We support type 1 (hardware address plus time) and type 3 (hardware
|
|
|
|
* address).
|
|
|
|
*
|
|
|
|
* We can support type 2 for specific vendors in the future, if they
|
|
|
|
* publish the specification. And of course there may be additional
|
|
|
|
* types later.
|
|
|
|
*/
|
|
|
|
static int server_duid_type = DUID_LLT;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the DUID type.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
set_server_duid_type(int type) {
|
|
|
|
server_duid_type = type;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generate a new server DUID. This is done if there was no DUID in
|
|
|
|
* the leases.txt or in the dhcpd.conf file.
|
|
|
|
*/
|
|
|
|
isc_result_t
|
|
|
|
generate_new_server_duid(void) {
|
|
|
|
struct interface_info *p;
|
|
|
|
u_int32_t time_val;
|
|
|
|
struct data_string generated_duid;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify we have a type that we support.
|
|
|
|
*/
|
|
|
|
if ((server_duid_type != DUID_LL) && (server_duid_type != DUID_LLT)) {
|
|
|
|
log_error("Invalid DUID type %d specified, "
|
|
|
|
"only LL and LLT types supported", server_duid_type);
|
|
|
|
return ISC_R_INVALIDARG;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find an interface with a hardware address.
|
|
|
|
* Any will do. :)
|
|
|
|
*/
|
|
|
|
for (p = interfaces; p != NULL; p = p->next) {
|
|
|
|
if (p->hw_address.hlen > 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (p == NULL) {
|
|
|
|
return ISC_R_UNEXPECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build our DUID.
|
|
|
|
*/
|
|
|
|
memset(&generated_duid, 0, sizeof(generated_duid));
|
|
|
|
if (server_duid_type == DUID_LLT) {
|
|
|
|
time_val = duid_time(time(NULL));
|
|
|
|
generated_duid.len = 8 + p->hw_address.hlen - 1;
|
|
|
|
if (!buffer_allocate(&generated_duid.buffer,
|
|
|
|
generated_duid.len, MDL)) {
|
|
|
|
log_fatal("No memory for server DUID.");
|
|
|
|
}
|
|
|
|
generated_duid.data = generated_duid.buffer->data;
|
|
|
|
putUShort(generated_duid.buffer->data, DUID_LLT);
|
|
|
|
putUShort(generated_duid.buffer->data + 2,
|
|
|
|
p->hw_address.hbuf[0]);
|
|
|
|
putULong(generated_duid.buffer->data + 4, time_val);
|
|
|
|
memcpy(generated_duid.buffer->data + 8,
|
|
|
|
p->hw_address.hbuf+1, p->hw_address.hlen-1);
|
|
|
|
} else if (server_duid_type == DUID_LL) {
|
|
|
|
generated_duid.len = 4 + p->hw_address.hlen - 1;
|
|
|
|
if (!buffer_allocate(&generated_duid.buffer,
|
|
|
|
generated_duid.len, MDL)) {
|
|
|
|
log_fatal("No memory for server DUID.");
|
|
|
|
}
|
|
|
|
generated_duid.data = generated_duid.buffer->data;
|
|
|
|
putUShort(generated_duid.buffer->data, DUID_LL);
|
|
|
|
putUShort(generated_duid.buffer->data + 2,
|
|
|
|
p->hw_address.hbuf[0]);
|
|
|
|
memcpy(generated_duid.buffer->data +4,
|
|
|
|
p->hw_address.hbuf+1, p->hw_address.hlen-1);
|
|
|
|
} else {
|
|
|
|
log_fatal("Unsupported server DUID type %d.", server_duid_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
set_server_duid(&generated_duid);
|
|
|
|
data_string_forget(&generated_duid, MDL);
|
|
|
|
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the client identifier from the packet.
|
|
|
|
*/
|
|
|
|
isc_result_t
|
|
|
|
get_client_id(struct packet *packet, struct data_string *client_id) {
|
|
|
|
struct option_cache *oc;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify our client_id structure is empty.
|
|
|
|
*/
|
|
|
|
if ((client_id->data != NULL) || (client_id->len != 0)) {
|
|
|
|
return ISC_R_INVALIDARG;
|
|
|
|
}
|
|
|
|
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_CLIENTID);
|
|
|
|
if (oc == NULL) {
|
|
|
|
return ISC_R_NOTFOUND;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!evaluate_option_cache(client_id, packet, NULL, NULL,
|
|
|
|
packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
return ISC_R_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Message validation, defined in RFC 3315, sections 15.2, 15.5, 15.7:
|
|
|
|
*
|
|
|
|
* Servers MUST discard any Solicit messages that do not include a
|
|
|
|
* Client Identifier option or that do include a Server Identifier
|
|
|
|
* option.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_client_msg(struct packet *packet, struct data_string *client_id) {
|
|
|
|
int ret_val;
|
|
|
|
struct option_cache *oc;
|
|
|
|
struct data_string data;
|
|
|
|
|
|
|
|
ret_val = 0;
|
|
|
|
memset(client_id, 0, sizeof(*client_id));
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
|
|
|
|
|
|
switch (get_client_id(packet, client_id)) {
|
|
|
|
case ISC_R_SUCCESS:
|
|
|
|
break;
|
|
|
|
case ISC_R_NOTFOUND:
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"client identifier missing",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
default:
|
|
|
|
log_error("Error processing %s from %s; "
|
|
|
|
"unable to evaluate Client Identifier",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Required by RFC 3315, section 15.
|
|
|
|
*/
|
|
|
|
if (packet->unicast) {
|
|
|
|
log_debug("Discarding %s from %s; packet sent unicast "
|
|
|
|
"(CLIENTID %s, SERVERID %s)",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60),
|
|
|
|
print_hex_2(data.len, data.data, 60));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
|
|
|
|
if (oc != NULL) {
|
|
|
|
if (evaluate_option_cache(&data, packet, NULL, NULL,
|
|
|
|
packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"server identifier found "
|
|
|
|
"(CLIENTID %s, SERVERID %s)",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
print_hex_1(client_id->len,
|
|
|
|
client_id->data, 60),
|
|
|
|
print_hex_2(data.len,
|
|
|
|
data.data, 60));
|
|
|
|
} else {
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"server identifier found "
|
|
|
|
"(CLIENTID %s)",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
print_hex_1(client_id->len,
|
|
|
|
client_id->data, 60),
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
}
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* looks good */
|
|
|
|
ret_val = 1;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (data.len > 0) {
|
|
|
|
data_string_forget(&data, MDL);
|
|
|
|
}
|
|
|
|
if (!ret_val) {
|
|
|
|
if (client_id->len > 0) {
|
|
|
|
data_string_forget(client_id, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Response validation, defined in RFC 3315, sections 15.4, 15.6, 15.8,
|
|
|
|
* 15.9 (slightly different wording, but same meaning):
|
|
|
|
*
|
|
|
|
* Servers MUST discard any received Request message that meet any of
|
|
|
|
* the following conditions:
|
|
|
|
*
|
|
|
|
* - the message does not include a Server Identifier option.
|
|
|
|
* - the contents of the Server Identifier option do not match the
|
|
|
|
* server's DUID.
|
|
|
|
* - the message does not include a Client Identifier option.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_client_resp(struct packet *packet,
|
|
|
|
struct data_string *client_id,
|
|
|
|
struct data_string *server_id) {
|
|
|
|
int ret_val;
|
|
|
|
struct option_cache *oc;
|
|
|
|
|
|
|
|
/* INSIST((duid.data != NULL) && (duid.len > 0)); */
|
|
|
|
|
|
|
|
ret_val = 0;
|
|
|
|
memset(client_id, 0, sizeof(*client_id));
|
|
|
|
memset(server_id, 0, sizeof(*server_id));
|
|
|
|
|
|
|
|
switch (get_client_id(packet, client_id)) {
|
|
|
|
case ISC_R_SUCCESS:
|
|
|
|
break;
|
|
|
|
case ISC_R_NOTFOUND:
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"client identifier missing",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
default:
|
|
|
|
log_error("Error processing %s from %s; "
|
|
|
|
"unable to evaluate Client Identifier",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
|
|
|
|
if (oc == NULL) {
|
|
|
|
log_debug("Discarding %s from %s: "
|
|
|
|
"server identifier missing (CLIENTID %s)",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
if (!evaluate_option_cache(server_id, packet, NULL, NULL,
|
|
|
|
packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
log_error("Error processing %s from %s; "
|
|
|
|
"unable to evaluate Server Identifier (CLIENTID %s)",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
if ((server_duid.len != server_id->len) ||
|
|
|
|
(memcmp(server_duid.data, server_id->data, server_duid.len) != 0)) {
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"not our server identifier "
|
|
|
|
"(CLIENTID %s, SERVERID %s, server DUID %s)",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60),
|
|
|
|
print_hex_2(server_id->len, server_id->data, 60),
|
|
|
|
print_hex_3(server_duid.len, server_duid.data, 60));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* looks good */
|
|
|
|
ret_val = 1;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Information request validation, defined in RFC 3315, section 15.12:
|
|
|
|
*
|
|
|
|
* Servers MUST discard any received Information-request message that
|
|
|
|
* meets any of the following conditions:
|
|
|
|
*
|
|
|
|
* - The message includes a Server Identifier option and the DUID in
|
|
|
|
* the option does not match the server's DUID.
|
|
|
|
*
|
|
|
|
* - The message includes an IA option.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
valid_client_info_req(struct packet *packet, struct data_string *server_id) {
|
|
|
|
int ret_val;
|
|
|
|
struct option_cache *oc;
|
|
|
|
|
|
|
|
/* INSIST((duid.data != NULL) && (duid.len > 0)); */
|
|
|
|
|
|
|
|
ret_val = 0;
|
|
|
|
memset(server_id, 0, sizeof(*server_id));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Required by RFC 3315, section 15.
|
|
|
|
*/
|
|
|
|
if (packet->unicast) {
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"IA_NA option present",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
|
|
|
|
if (oc != NULL) {
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"IA_NA option present",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA);
|
|
|
|
if (oc != NULL) {
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"IA_TA option present",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_SERVERID);
|
|
|
|
if (oc != NULL) {
|
|
|
|
if (!evaluate_option_cache(server_id, packet, NULL, NULL,
|
|
|
|
packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
log_error("Error processing %s from %s; "
|
|
|
|
"unable to evaluate Server Identifier",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
if ((server_duid.len != server_id->len) ||
|
|
|
|
(memcmp(server_duid.data, server_id->data,
|
|
|
|
server_duid.len) != 0)) {
|
|
|
|
log_debug("Discarding %s from %s; "
|
|
|
|
"not our server identifier "
|
|
|
|
"(SERVERID %s, server DUID %s)",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
print_hex_1(server_id->len,
|
|
|
|
server_id->data, 60),
|
|
|
|
print_hex_2(server_duid.len,
|
|
|
|
server_duid.data, 60));
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* looks good */
|
|
|
|
ret_val = 1;
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (!ret_val) {
|
|
|
|
if (server_id->len > 0) {
|
|
|
|
data_string_forget(server_id, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Options that we want to send, in addition to what was requested
|
|
|
|
* via the ORO.
|
|
|
|
*/
|
|
|
|
static const int required_opts[] = {
|
|
|
|
D6O_CLIENTID,
|
|
|
|
D6O_SERVERID,
|
|
|
|
D6O_STATUS_CODE,
|
|
|
|
0
|
|
|
|
};
|
|
|
|
static const int required_opts_solicit[] = {
|
|
|
|
D6O_CLIENTID,
|
|
|
|
D6O_SERVERID,
|
|
|
|
D6O_IA_NA,
|
|
|
|
D6O_IA_TA,
|
|
|
|
D6O_RAPID_COMMIT,
|
|
|
|
D6O_STATUS_CODE,
|
|
|
|
D6O_VENDOR_OPTS,
|
|
|
|
D6O_RECONF_ACCEPT,
|
|
|
|
0
|
|
|
|
};
|
|
|
|
static const int required_opts_IA_NA[] = {
|
|
|
|
D6O_IAADDR,
|
|
|
|
D6O_STATUS_CODE,
|
|
|
|
D6O_VENDOR_OPTS,
|
|
|
|
0
|
|
|
|
};
|
|
|
|
static const int required_opts_STATUS_CODE[] = {
|
|
|
|
D6O_STATUS_CODE,
|
|
|
|
0
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Creates an option state and data string, based on the packet contents,
|
|
|
|
* and the specific option defined in the option cache.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
get_encapsulated_IA_state(struct option_state **enc_opt_state,
|
|
|
|
struct data_string *enc_opt_data,
|
|
|
|
struct packet *packet,
|
|
|
|
struct option_cache *oc) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the raw data for the encapsulated options.
|
|
|
|
*/
|
|
|
|
memset(enc_opt_data, 0, sizeof(*enc_opt_data));
|
|
|
|
if (!evaluate_option_cache(enc_opt_data, packet,
|
|
|
|
NULL, NULL, packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
log_error("get_encapsulated_IA_state: "
|
|
|
|
"error evaluating raw option.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (enc_opt_data->len < 12) {
|
|
|
|
log_error("get_encapsulated_IA_state: raw option too small.");
|
|
|
|
data_string_forget(enc_opt_data, MDL);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now create the option state structure, and pass it to the
|
|
|
|
* function that parses options.
|
|
|
|
*/
|
|
|
|
*enc_opt_state = NULL;
|
|
|
|
if (!option_state_allocate(enc_opt_state, MDL)) {
|
|
|
|
log_error("get_encapsulated_IA_state: no memory for options.");
|
|
|
|
data_string_forget(enc_opt_data, MDL);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!parse_option_buffer(*enc_opt_state,
|
|
|
|
enc_opt_data->data+12,
|
|
|
|
enc_opt_data->len-12,
|
|
|
|
&dhcpv6_universe)) {
|
|
|
|
log_error("get_encapsulated_IA_state: error parsing options.");
|
|
|
|
option_state_dereference(enc_opt_state, MDL);
|
|
|
|
data_string_forget(enc_opt_data, MDL);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
set_status_code(u_int16_t status_code, const char *status_message,
|
|
|
|
struct option_state *opt_state) {
|
|
|
|
struct data_string d;
|
|
|
|
int ret_val;
|
|
|
|
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
d.len = sizeof(status_code) + strlen(status_message);
|
|
|
|
if (!buffer_allocate(&d.buffer, d.len, MDL)) {
|
|
|
|
log_fatal("set_status_code: no memory for status code.");
|
|
|
|
}
|
|
|
|
d.data = d.buffer->data;
|
|
|
|
putUShort(d.buffer->data, status_code);
|
|
|
|
memcpy(d.buffer->data + sizeof(status_code),
|
|
|
|
status_message, d.len - sizeof(status_code));
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe, opt_state,
|
|
|
|
d.buffer, (char *)d.data, d.len,
|
|
|
|
D6O_STATUS_CODE, 0)) {
|
|
|
|
log_error("set_status_code: error saving status code.");
|
|
|
|
ret_val = 0;
|
|
|
|
} else {
|
|
|
|
ret_val = 1;
|
|
|
|
}
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We have a set of operations we do to set up the reply packet, which
|
|
|
|
* is the same for many message types.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
start_reply(struct packet *packet,
|
|
|
|
const struct data_string *client_id,
|
|
|
|
const struct data_string *server_id,
|
|
|
|
struct option_state **opt_state,
|
|
|
|
struct dhcpv6_packet *reply) {
|
|
|
|
struct option_cache *oc;
|
|
|
|
struct data_string server_oro;
|
|
|
|
char *server_id_data;
|
|
|
|
int server_id_len;
|
|
|
|
|
|
|
|
reply->msg_type = DHCPV6_REPLY;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use the client's transaction identifier for the reply.
|
|
|
|
*/
|
|
|
|
memcpy(reply->transaction_id, packet->dhcpv6_transaction_id,
|
|
|
|
sizeof(reply->transaction_id));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build our option state for reply.
|
|
|
|
*/
|
|
|
|
*opt_state = NULL;
|
|
|
|
if (!option_state_allocate(opt_state, MDL)) {
|
|
|
|
log_error("start_reply: no memory for option_state.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
execute_statements_in_scope(NULL, packet, NULL, NULL,
|
|
|
|
packet->options, *opt_state,
|
|
|
|
&global_scope, root_group, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* RFC 3315, section 18.2 says we need server identifier and
|
|
|
|
* client identifier.
|
|
|
|
*
|
|
|
|
* If the server ID is defined via the configuration file, then
|
|
|
|
* it will already be present in the option state at this point,
|
|
|
|
* so we don't need to set it.
|
|
|
|
*
|
|
|
|
* If we have a server ID passed in from the caller,
|
|
|
|
* use that, otherwise use the global DUID.
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, *opt_state, D6O_SERVERID);
|
|
|
|
if (oc == NULL) {
|
|
|
|
if (server_id == NULL) {
|
|
|
|
server_id_data = (char *)server_duid.data;
|
|
|
|
server_id_len = server_duid.len;
|
|
|
|
} else {
|
|
|
|
server_id_data = (char *)server_id->data;
|
|
|
|
server_id_len = server_id->len;
|
|
|
|
}
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe, *opt_state,
|
|
|
|
NULL, server_id_data, server_id_len,
|
|
|
|
D6O_SERVERID, 0)) {
|
|
|
|
log_error("start_reply: "
|
|
|
|
"error saving server identifier.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (client_id->buffer != NULL) {
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe, *opt_state,
|
|
|
|
client_id->buffer,
|
|
|
|
(unsigned char *)client_id->data,
|
|
|
|
client_id->len,
|
|
|
|
D6O_CLIENTID, 0)) {
|
|
|
|
log_error("start_reply: error saving "
|
|
|
|
"client identifier.");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the client accepts reconfiguration, let it know that we
|
|
|
|
* will send them.
|
|
|
|
*
|
|
|
|
* Note: we don't actually do this yet, but DOCSIS requires we
|
|
|
|
* claim to.
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options,
|
|
|
|
D6O_RECONF_ACCEPT);
|
|
|
|
if (oc != NULL) {
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe, *opt_state,
|
|
|
|
NULL, "", 0, D6O_RECONF_ACCEPT, 0)) {
|
|
|
|
log_error("start_reply: "
|
|
|
|
"error saving RECONF_ACCEPT option.");
|
|
|
|
option_state_dereference(opt_state, MDL);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the ORO for the main packet.
|
|
|
|
*/
|
|
|
|
build_server_oro(&server_oro, *opt_state, MDL);
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe, *opt_state,
|
|
|
|
server_oro.buffer, (char *)server_oro.data,
|
|
|
|
server_oro.len, D6O_ORO, 0)) {
|
|
|
|
log_error("start_reply: error saving server ORO.");
|
|
|
|
data_string_forget(&server_oro, MDL);
|
|
|
|
option_state_dereference(opt_state, MDL);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
data_string_forget(&server_oro, MDL);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try to get the IPv6 address the client asked for from the
|
|
|
|
* pool.
|
|
|
|
*
|
|
|
|
* addr is the result (should be a pointer to NULL on entry)
|
|
|
|
* pool is the pool to search in
|
|
|
|
* requested_addr is the address the client wants
|
|
|
|
*/
|
|
|
|
static isc_result_t
|
|
|
|
try_client_v6_address(struct iaaddr **addr,
|
|
|
|
struct ipv6_pool *pool,
|
|
|
|
const struct data_string *requested_addr) {
|
|
|
|
struct in6_addr tmp_addr;
|
|
|
|
isc_result_t result;
|
|
|
|
struct iaddrmatch match;
|
|
|
|
|
|
|
|
if (requested_addr->len < sizeof(tmp_addr)) {
|
|
|
|
return ISC_R_INVALIDARG;
|
|
|
|
}
|
|
|
|
memcpy(&tmp_addr, requested_addr->data, sizeof(tmp_addr));
|
|
|
|
if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr)) {
|
|
|
|
return ISC_R_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ipv6_addr_in_pool(&tmp_addr, pool)) {
|
|
|
|
return ISC_R_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lease6_exists(pool, &tmp_addr)) {
|
|
|
|
return ISC_R_ADDRINUSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = iaaddr_allocate(addr, MDL);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
(*addr)->addr = tmp_addr;
|
|
|
|
|
|
|
|
result = add_lease6(pool, *addr, 0);
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
|
|
iaaddr_dereference(addr, MDL);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get an IPv6 address for the client.
|
|
|
|
*
|
|
|
|
* addr is the result (should be a pointer to NULL on entry)
|
|
|
|
* packet is the information about the packet from the client
|
|
|
|
* requested_iaaddr is a hint from the client
|
|
|
|
* client_id is the DUID for the client
|
|
|
|
*/
|
|
|
|
static isc_result_t
|
|
|
|
pick_v6_address(struct iaaddr **addr,
|
|
|
|
struct ipv6_pool **pool,
|
|
|
|
struct packet *packet,
|
|
|
|
const struct data_string *requested_iaaddr,
|
|
|
|
const struct data_string *client_id) {
|
|
|
|
const struct packet *chk_packet;
|
|
|
|
const struct in6_addr *link_addr;
|
|
|
|
const struct in6_addr *first_link_addr;
|
|
|
|
struct iaddr tmp_addr;
|
|
|
|
struct subnet *subnet;
|
|
|
|
struct shared_network *shared_network;
|
|
|
|
struct ipv6_pool *p;
|
|
|
|
int i;
|
|
|
|
int start_pool;
|
|
|
|
int attempts;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First, find the link address where the packet from the client
|
|
|
|
* first appeared.
|
|
|
|
*/
|
|
|
|
first_link_addr = NULL;
|
|
|
|
chk_packet = packet->dhcpv6_container_packet;
|
|
|
|
while (chk_packet != NULL) {
|
|
|
|
link_addr = &chk_packet->dhcpv6_link_address;
|
|
|
|
if (!IN6_IS_ADDR_UNSPECIFIED(link_addr) &&
|
|
|
|
!IN6_IS_ADDR_LINKLOCAL(link_addr)) {
|
|
|
|
first_link_addr = link_addr;
|
|
|
|
}
|
|
|
|
chk_packet = chk_packet->dhcpv6_container_packet;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there is a link address, find the subnet associated
|
|
|
|
* with that, and use that to get the appropriate
|
|
|
|
* shared_network.
|
|
|
|
*/
|
|
|
|
if (first_link_addr != NULL) {
|
|
|
|
tmp_addr.len = sizeof(*first_link_addr);
|
|
|
|
memcpy(tmp_addr.iabuf,
|
|
|
|
first_link_addr, sizeof(*first_link_addr));
|
|
|
|
subnet = NULL;
|
|
|
|
if (!find_subnet(&subnet, tmp_addr, MDL)) {
|
|
|
|
log_debug("No subnet found for link-address %s.",
|
|
|
|
piaddr(tmp_addr));
|
|
|
|
return ISC_R_NOTFOUND;
|
|
|
|
}
|
|
|
|
shared_network = NULL;
|
|
|
|
shared_network_reference(&shared_network,
|
|
|
|
subnet->shared_network, MDL);
|
|
|
|
subnet_dereference(&subnet, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there is no link address, we will use the interface
|
|
|
|
* that this packet came in on to pick the shared_network.
|
|
|
|
*/
|
|
|
|
else {
|
|
|
|
shared_network = NULL;
|
|
|
|
shared_network_reference(&shared_network,
|
|
|
|
packet->interface->shared_network,
|
|
|
|
MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* No pools, we're done.
|
|
|
|
*/
|
|
|
|
if (shared_network->ipv6_pools == NULL) {
|
|
|
|
shared_network_dereference(&shared_network, MDL);
|
|
|
|
return ISC_R_NORESOURCES;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the client requested an address, try each subnet to see
|
|
|
|
* if the address is available in that subnet. We will try to
|
|
|
|
* respect the client's request if possible.
|
|
|
|
*/
|
|
|
|
if ((requested_iaaddr != NULL) && (requested_iaaddr->len > 0)) {
|
|
|
|
for (i=0; shared_network->ipv6_pools[i] != NULL; i++) {
|
|
|
|
p = shared_network->ipv6_pools[i];
|
|
|
|
if (try_client_v6_address(addr, p, requested_iaaddr)
|
|
|
|
== ISC_R_SUCCESS) {
|
|
|
|
ipv6_pool_reference(pool, p, MDL);
|
|
|
|
shared_network_dereference(&shared_network,
|
|
|
|
MDL);
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Otherwise try to get a lease from the first subnet possible.
|
|
|
|
*
|
|
|
|
* We start looking at the last pool we allocated from, unless
|
|
|
|
* it had a collision trying to allocate an address. This will
|
|
|
|
* tend to move us into less-filled pools.
|
|
|
|
*/
|
|
|
|
start_pool = shared_network->last_ipv6_pool;
|
|
|
|
i = start_pool;
|
|
|
|
do {
|
|
|
|
|
|
|
|
p = shared_network->ipv6_pools[i];
|
|
|
|
if (activate_lease6(p, addr, &attempts,
|
|
|
|
client_id, 0) == ISC_R_SUCCESS) {
|
|
|
|
ipv6_pool_reference(pool, p, MDL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Record the pool used (or next one if there
|
|
|
|
* was a collision).
|
|
|
|
*/
|
|
|
|
if (attempts > 1) {
|
|
|
|
i++;
|
|
|
|
if (shared_network->ipv6_pools[i] == NULL) {
|
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
shared_network->last_ipv6_pool = i;
|
|
|
|
|
|
|
|
shared_network_dereference(&shared_network, MDL);
|
|
|
|
return ISC_R_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
i++;
|
|
|
|
if (shared_network->ipv6_pools[i] == NULL) {
|
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
} while (i != start_pool);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we failed to pick an IPv6 address from any of the subnets.
|
|
|
|
* Presumably that means we have no addresses for the client.
|
|
|
|
*/
|
|
|
|
shared_network_dereference(&shared_network, MDL);
|
|
|
|
return ISC_R_NORESOURCES;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TODO: IA_TA */
|
|
|
|
/* TODO: look at client hints for lease times */
|
|
|
|
/* XXX: need to add IA_NA to our ORO? */
|
|
|
|
static void
|
|
|
|
lease_to_client(struct data_string *reply_ret,
|
|
|
|
struct packet *packet,
|
|
|
|
const struct data_string *client_id,
|
|
|
|
const struct data_string *server_id) {
|
|
|
|
struct option_cache *oc;
|
|
|
|
struct data_string packet_oro;
|
|
|
|
struct host_decl *packet_host;
|
|
|
|
int matched_packet_host;
|
|
|
|
struct option_cache *ia;
|
|
|
|
int rapid_commit;
|
|
|
|
char reply_data[65536];
|
|
|
|
struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
|
|
|
|
int reply_ofs = (int)((char *)reply->options - (char *)reply);
|
|
|
|
struct option_state *opt_state;
|
|
|
|
struct host_decl *host;
|
|
|
|
struct option_state *host_opt_state;
|
|
|
|
/* cli_enc_... variables come from the IA_NA/IA_TA options */
|
|
|
|
struct data_string cli_enc_opt_data;
|
|
|
|
struct option_state *cli_enc_opt_state;
|
|
|
|
u_int32_t preferred_lifetime;
|
|
|
|
u_int32_t valid_lifetime;
|
|
|
|
struct data_string iaaddr;
|
|
|
|
struct data_string fixed_addr;
|
|
|
|
struct data_string d;
|
|
|
|
u_int16_t len;
|
|
|
|
u_int32_t t1, t2;
|
|
|
|
struct host_decl *save_host;
|
|
|
|
char zeros[24];
|
|
|
|
struct ipv6_pool *pool;
|
|
|
|
struct iaaddr *lease;
|
|
|
|
struct group *group;
|
|
|
|
u_int32_t iaid;
|
|
|
|
struct ia_na *ia_na;
|
|
|
|
struct ia_na *existing_ia_na;
|
2007-05-18 09:26:58 +00:00
|
|
|
struct ia_na *old_ia_na;
|
2007-05-08 23:05:22 +00:00
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize to empty values, in case we have to exit early.
|
|
|
|
*/
|
|
|
|
opt_state = NULL;
|
|
|
|
memset(&packet_oro, 0, sizeof(packet_oro));
|
|
|
|
memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
|
|
|
|
cli_enc_opt_state = NULL;
|
|
|
|
host_opt_state = NULL;
|
|
|
|
memset(&fixed_addr, 0, sizeof(fixed_addr));
|
|
|
|
memset(&iaaddr, 0, sizeof(iaaddr));
|
|
|
|
ia_na = NULL;
|
|
|
|
lease = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up reply.
|
|
|
|
*/
|
|
|
|
if (!start_reply(packet, client_id, NULL, &opt_state, reply)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the ORO from the packet, if any.
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ORO);
|
|
|
|
if (oc != NULL) {
|
|
|
|
if (!evaluate_option_cache(&packet_oro, packet,
|
|
|
|
NULL, NULL,
|
|
|
|
packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
log_error("lease_to_client: error evaluating ORO.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A small bit of special handling for Solicit messages.
|
|
|
|
*
|
|
|
|
* We could move the logic into a flag, but for now just check
|
|
|
|
* explicitly.
|
|
|
|
*/
|
|
|
|
if (packet->dhcpv6_msg_type == DHCPV6_SOLICIT) {
|
|
|
|
|
|
|
|
reply->msg_type = DHCPV6_ADVERTISE;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If:
|
|
|
|
* - this message type supports rapid commit (Solicit), and
|
|
|
|
* - the server is configured to supply a rapid commit, and
|
|
|
|
* - the client requests a rapid commit,
|
|
|
|
* Then we add a rapid commit option, and send Reply (instead
|
|
|
|
* of an Advertise).
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe,
|
|
|
|
opt_state, D6O_RAPID_COMMIT);
|
|
|
|
if (oc != NULL) {
|
|
|
|
oc = lookup_option(&dhcpv6_universe,
|
|
|
|
packet->options, D6O_RAPID_COMMIT);
|
|
|
|
if (oc != NULL) {
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe,
|
|
|
|
opt_state, NULL, "", 0,
|
|
|
|
D6O_RAPID_COMMIT, 0)) {
|
|
|
|
log_error("start_reply: error saving "
|
|
|
|
"RAPID_COMMIT option.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
reply->msg_type = DHCPV6_REPLY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the host record that matches from the packet, if any.
|
|
|
|
*/
|
|
|
|
packet_host = NULL;
|
|
|
|
if (!find_hosts_by_option(&packet_host, packet, packet->options, MDL)) {
|
|
|
|
packet_host = NULL;
|
|
|
|
/*
|
|
|
|
* If we don't have a match from the packet contents,
|
|
|
|
* see if we can match by UID.
|
|
|
|
*/
|
|
|
|
if (!find_hosts_by_uid(&packet_host,
|
|
|
|
client_id->data, client_id->len, MDL)) {
|
|
|
|
packet_host = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
matched_packet_host = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add our options that are not associated with any IA_NA or IA_TA.
|
|
|
|
*/
|
|
|
|
reply_ofs += store_options6(reply_data+reply_ofs,
|
|
|
|
sizeof(reply_data)-reply_ofs,
|
|
|
|
opt_state, packet,
|
|
|
|
required_opts_solicit, &packet_oro);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now loop across each IA_NA that we have.
|
|
|
|
*/
|
|
|
|
ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
|
|
|
|
while (ia != NULL) {
|
|
|
|
/*
|
|
|
|
* T1 and T2, set to 0 means the client can choose.
|
|
|
|
*
|
|
|
|
* We will adjust this based on preferred and valid
|
|
|
|
* times if we have an address.
|
|
|
|
*/
|
|
|
|
t1 = 0;
|
|
|
|
t2 = 0;
|
|
|
|
|
|
|
|
if (!get_encapsulated_IA_state(&cli_enc_opt_state,
|
|
|
|
&cli_enc_opt_data,
|
|
|
|
packet, ia)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create an IA_NA structure.
|
|
|
|
*/
|
|
|
|
iaid = getULong(cli_enc_opt_data.data);
|
|
|
|
ia_na = NULL;
|
|
|
|
if (ia_na_allocate(&ia_na, iaid, client_id->data,
|
|
|
|
client_id->len, MDL) != ISC_R_SUCCESS) {
|
|
|
|
log_fatal("lease_to_client: no memory for ia_na.");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create state for this IA_NA.
|
|
|
|
*/
|
|
|
|
host_opt_state = NULL;
|
|
|
|
if (!option_state_allocate(&host_opt_state, MDL)) {
|
|
|
|
log_error("lease_to_client: out of memory "
|
|
|
|
"allocating option_state.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if this NA has an address. If so, we use it
|
|
|
|
* when trying to find a matching host record.
|
|
|
|
*/
|
|
|
|
memset(&iaaddr, 0, sizeof(iaaddr));
|
|
|
|
oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
|
|
|
|
D6O_IAADDR);
|
|
|
|
if (oc != NULL) {
|
|
|
|
/*
|
|
|
|
* TODO: Check that the address is on one of
|
|
|
|
* the networks that we have, and issue
|
|
|
|
* NotOnLink if not (for Solicit/Request),
|
|
|
|
* or remember to set the address lifetime
|
|
|
|
* to 0 (for Renew/Rebind).
|
|
|
|
*/
|
|
|
|
if (!evaluate_option_cache(&iaaddr, packet,
|
|
|
|
NULL, NULL,
|
|
|
|
packet->options,
|
|
|
|
NULL, &global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
log_error("lease_to_client: "
|
|
|
|
"error evaluating IAADDR.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Clients may choose to send :: as an address,
|
|
|
|
* with the idea to give hints about
|
|
|
|
* preferred-lifetime or valid-lifetime.
|
|
|
|
*
|
|
|
|
* We ignore this.
|
|
|
|
*/
|
|
|
|
memset(zeros, 0, sizeof(zeros));
|
|
|
|
if ((iaaddr.len >= 24) &&
|
|
|
|
!memcmp(iaaddr.data, zeros, 16)) {
|
|
|
|
data_string_forget(&iaaddr, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now we need to figure out which host record matches
|
|
|
|
* this IA_NA.
|
|
|
|
*
|
|
|
|
* We first try matching the encapsulated option state.
|
|
|
|
* If nothing is there, then we will use the host entry
|
|
|
|
* matched from the non-encapsulated option state, if
|
|
|
|
* there was one.
|
|
|
|
*
|
|
|
|
* We will only use this host entry for one of the
|
|
|
|
* IA_NA in the packet, to avoid having the same address
|
|
|
|
* on multiple interfaces.
|
|
|
|
*/
|
|
|
|
host = NULL;
|
|
|
|
if (!find_hosts_by_option(&host, packet,
|
|
|
|
cli_enc_opt_state, MDL)) {
|
|
|
|
if ((packet_host != NULL) && !matched_packet_host) {
|
|
|
|
matched_packet_host = 1;
|
|
|
|
host = packet_host;
|
|
|
|
} else {
|
|
|
|
host = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (iaaddr.len == 0) {
|
|
|
|
/*
|
|
|
|
* Client did not specify the IAADDR to use.
|
|
|
|
*
|
|
|
|
* If we are renewing, this is a problem. Set
|
|
|
|
* to no host, and this will cause the appropriate
|
|
|
|
* response to be sent.
|
|
|
|
*
|
|
|
|
* Otherwise, we simply find the first matching host.
|
|
|
|
*/
|
|
|
|
if (packet->dhcpv6_msg_type == DHCPV6_RENEW) {
|
|
|
|
host = NULL;
|
|
|
|
} else {
|
|
|
|
while ((host != NULL) &&
|
|
|
|
(host->fixed_addr == NULL)) {
|
|
|
|
host = host->n_ipaddr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Client wanted a specific IAADDR, so we will
|
|
|
|
* only accept a host entry that matches.
|
|
|
|
*/
|
|
|
|
save_host = host;
|
|
|
|
for (; host != NULL; host = host->n_ipaddr) {
|
|
|
|
if (host->fixed_addr == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!evaluate_option_cache(&fixed_addr, NULL,
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
NULL, &global_scope,
|
|
|
|
host->fixed_addr,
|
|
|
|
MDL)) {
|
|
|
|
log_error("lease_to_client: error "
|
|
|
|
"evaluating host address.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
if ((iaaddr.len >= 16) &&
|
|
|
|
!memcmp(fixed_addr.data, iaaddr.data, 16)) {
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* If we got a Solicit and don't have a matching
|
|
|
|
* host record, have gotten a bad hint.
|
|
|
|
* Pick a host record.
|
|
|
|
*/
|
|
|
|
if ((host == NULL) &&
|
|
|
|
(packet->dhcpv6_msg_type == DHCPV6_SOLICIT)) {
|
|
|
|
host = save_host;
|
|
|
|
while ((host != NULL) &&
|
|
|
|
(host->fixed_addr == NULL)) {
|
|
|
|
host = host->n_ipaddr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* At this point, if we don't have a host entry,
|
|
|
|
* try to find the appropriate pool for this IA.
|
|
|
|
*/
|
|
|
|
group = NULL;
|
|
|
|
lease = NULL;
|
|
|
|
if (host != NULL) {
|
|
|
|
group = host->group;
|
|
|
|
} else if (num_pools > 0) {
|
|
|
|
struct iaaddr *tmp;
|
|
|
|
struct in6_addr *in6_addr;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find existing IA_NA.
|
|
|
|
*/
|
|
|
|
existing_ia_na = NULL;
|
|
|
|
if (ia_na_hash_lookup(&existing_ia_na, ia_active,
|
|
|
|
(char *)ia_na->iaid_duid.data,
|
|
|
|
ia_na->iaid_duid.len, MDL) == 0) {
|
|
|
|
existing_ia_na = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there are no addresses, we'll ignore this IA_NA.
|
|
|
|
*/
|
|
|
|
if ((existing_ia_na != NULL) &&
|
|
|
|
(existing_ia_na->num_iaaddr == 0)) {
|
|
|
|
existing_ia_na = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we have this IA_NA, then use that.
|
|
|
|
*/
|
|
|
|
if ((iaaddr.len == 0) && (existing_ia_na != NULL)) {
|
|
|
|
/*
|
|
|
|
* If the client doesn't ask for a specific
|
|
|
|
* address, we're cool.
|
|
|
|
*/
|
|
|
|
tmp = existing_ia_na->iaaddr[0];
|
|
|
|
pool = NULL;
|
|
|
|
ipv6_pool_reference(&pool, tmp->ipv6_pool, MDL);
|
|
|
|
iaaddr_reference(&lease, tmp, MDL);
|
|
|
|
} else if (existing_ia_na != NULL) {
|
|
|
|
/*
|
|
|
|
* Make sure this address is in the IA_NA.
|
|
|
|
*/
|
|
|
|
pool = NULL;
|
|
|
|
for (i=0; i<existing_ia_na->num_iaaddr; i++) {
|
|
|
|
tmp = existing_ia_na->iaaddr[i];
|
|
|
|
in6_addr = &tmp->addr;
|
|
|
|
if (memcmp(in6_addr,
|
|
|
|
iaaddr.data, 16) == 0) {
|
|
|
|
ipv6_pool_reference(&pool,
|
|
|
|
tmp->ipv6_pool, MDL);
|
|
|
|
iaaddr_reference(&lease,
|
|
|
|
tmp, MDL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we didn't find a pool it means that the
|
|
|
|
* client sent an address we don't know about
|
|
|
|
* and we'll consider it a non-match.
|
|
|
|
*/
|
|
|
|
if (pool == NULL) {
|
|
|
|
existing_ia_na = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we don't have a matching IA_NA for this
|
|
|
|
* client, then we need to get a new address.
|
|
|
|
*/
|
|
|
|
if (existing_ia_na == NULL) {
|
|
|
|
pool = NULL;
|
|
|
|
pick_v6_address(&lease, &pool, packet,
|
|
|
|
&iaaddr, client_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we got an address, get our group information
|
|
|
|
* from the pool used.
|
|
|
|
*/
|
|
|
|
if (pool != NULL) {
|
|
|
|
group = pool->shared_network->group;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the lease time from the group.
|
|
|
|
*/
|
|
|
|
if (group != NULL) {
|
|
|
|
/*
|
|
|
|
* Execute statements for the host's group.
|
|
|
|
*/
|
|
|
|
execute_statements_in_scope(NULL, packet, NULL, NULL,
|
|
|
|
packet->options,
|
|
|
|
host_opt_state,
|
|
|
|
&global_scope,
|
|
|
|
group, root_group);
|
|
|
|
/*
|
|
|
|
* Get our lease time. Note that "preferred lifetime"
|
|
|
|
* and "valid lifetime" are defined in RFC 2462.
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&server_universe, opt_state,
|
|
|
|
SV_DEFAULT_LEASE_TIME);
|
|
|
|
valid_lifetime = DEFAULT_DEFAULT_LEASE_TIME;
|
|
|
|
if (oc != NULL) {
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
if (!evaluate_option_cache(&d, packet, NULL,
|
|
|
|
NULL,
|
|
|
|
packet->options,
|
|
|
|
opt_state,
|
|
|
|
&global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
log_error("lease_to_client: error "
|
|
|
|
"getting lease time, "
|
|
|
|
"using default.");
|
|
|
|
} else {
|
|
|
|
/* INSIST(d.len == 4); */
|
|
|
|
valid_lifetime = getULong(d.data);
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* T1: RENEW time.
|
|
|
|
* T2: REBIND time.
|
|
|
|
* preferred: 'deprecate' address.
|
|
|
|
* valid: address expires.
|
|
|
|
*
|
|
|
|
* Values are required for valid and preferred
|
|
|
|
* lifetimes. T1 and T2, if zero, will allow
|
|
|
|
* the client to select their own behaviour.
|
|
|
|
*/
|
|
|
|
t1 = t2 = 0;
|
|
|
|
/* XXX: This is more than a little weird. */
|
|
|
|
oc = lookup_option(&dhcp_universe, opt_state,
|
|
|
|
DHO_DHCP_RENEWAL_TIME);
|
|
|
|
if (oc != NULL) {
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
if (!evaluate_option_cache(&d, packet, NULL,
|
|
|
|
NULL,
|
|
|
|
packet->options,
|
|
|
|
opt_state,
|
|
|
|
&global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
/* XXX: I think there are already log
|
|
|
|
* lines by this point.
|
|
|
|
*/
|
|
|
|
log_error("lease_to_client: error "
|
|
|
|
"evaluating renew time, "
|
|
|
|
"defaulting to 0");
|
|
|
|
} else {
|
|
|
|
t1 = getULong(d.data);
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
oc = lookup_option(&dhcp_universe, opt_state,
|
|
|
|
DHO_DHCP_REBINDING_TIME);
|
|
|
|
if (oc != NULL) {
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
if (!evaluate_option_cache(&d, packet, NULL,
|
|
|
|
NULL,
|
|
|
|
packet->options,
|
|
|
|
opt_state,
|
|
|
|
&global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
/* XXX: I think there are already log
|
|
|
|
* lines by this point.
|
|
|
|
*/
|
|
|
|
log_error("lease_to_client: error "
|
|
|
|
"evaluating rebinding "
|
|
|
|
"time, defaulting to 0");
|
|
|
|
} else {
|
|
|
|
t2 = getULong(d.data);
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
preferred_lifetime = t1 + t2;
|
|
|
|
|
|
|
|
if (preferred_lifetime == 0 ||
|
|
|
|
preferred_lifetime >= valid_lifetime)
|
|
|
|
preferred_lifetime = (valid_lifetime / 2) +
|
|
|
|
(valid_lifetime / 4);
|
|
|
|
|
|
|
|
oc = lookup_option(&server_universe, opt_state,
|
|
|
|
SV_PREFER_LIFETIME);
|
|
|
|
if (oc != NULL) {
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
if (!evaluate_option_cache(&d, packet, NULL,
|
|
|
|
NULL,
|
|
|
|
packet->options,
|
|
|
|
opt_state,
|
|
|
|
&global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
/* XXX: I think there are already log
|
|
|
|
* lines by this point.
|
|
|
|
*/
|
|
|
|
log_error("lease_to_client: error "
|
|
|
|
"evaluating preferred "
|
|
|
|
"lifetime, defaulting to "
|
|
|
|
"%d", preferred_lifetime);
|
|
|
|
} else {
|
|
|
|
preferred_lifetime = getULong(d.data);
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Shift the lease to the right place, based on our timeout.
|
|
|
|
*/
|
|
|
|
if (lease != NULL) {
|
|
|
|
lease->valid_lifetime_end_time = valid_lifetime +
|
|
|
|
cur_time;
|
|
|
|
renew_lease6(pool, lease);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the address.
|
|
|
|
*/
|
|
|
|
memset(&fixed_addr, 0, sizeof(fixed_addr));
|
|
|
|
if (host != NULL) {
|
|
|
|
if (!evaluate_option_cache(&fixed_addr, NULL, NULL,
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
&global_scope,
|
|
|
|
host->fixed_addr, MDL)) {
|
|
|
|
log_error("lease_to_client: error "
|
|
|
|
"evaluating host address.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
if (fixed_addr.len != 16) {
|
|
|
|
log_error("lease_to_client: invalid address "
|
|
|
|
"length (%d)", fixed_addr.len);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
} else if (lease != NULL) {
|
|
|
|
fixed_addr.len = 16;
|
|
|
|
if (!buffer_allocate(&fixed_addr.buffer, 16, MDL)) {
|
|
|
|
log_fatal("lease_to_client: no memory for "
|
|
|
|
"address information.");
|
|
|
|
}
|
|
|
|
fixed_addr.data = fixed_addr.buffer->data;
|
|
|
|
memcpy(fixed_addr.buffer->data, &lease->addr, 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fixed_addr.len == 16) {
|
|
|
|
struct iaaddr *store_iaaddr;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store the address.
|
|
|
|
*
|
|
|
|
* XXX: This should allow multiple addresses, rather
|
|
|
|
* than only a single one!
|
|
|
|
*/
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
d.len = 24; /* From RFC 3315, section 22.6 */
|
|
|
|
if (!buffer_allocate(&d.buffer, d.len, MDL)) {
|
|
|
|
log_fatal("lease_to_client: no memory for "
|
|
|
|
"address information.");
|
|
|
|
}
|
|
|
|
d.data = d.buffer->data;
|
|
|
|
memcpy(d.buffer->data, fixed_addr.data, 16);
|
|
|
|
putULong(d.buffer->data+16, preferred_lifetime);
|
|
|
|
putULong(d.buffer->data+20, valid_lifetime);
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe,
|
|
|
|
host_opt_state,
|
|
|
|
d.buffer,
|
|
|
|
d.buffer->data,
|
|
|
|
d.len, D6O_IAADDR, 0)) {
|
|
|
|
log_error("lease_to_client: error saving "
|
|
|
|
"IAADDR.");
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((host != NULL) || (lease != NULL)) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Remember the client identifier so we can look
|
|
|
|
* it up later.
|
|
|
|
*/
|
|
|
|
if (host != NULL) {
|
|
|
|
change_host_uid(host, client_id->data,
|
|
|
|
client_id->len);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Otherwise save the IA_NA, for the same reason.
|
|
|
|
*/
|
|
|
|
else if (packet->dhcpv6_msg_type != DHCPV6_SOLICIT) {
|
2007-05-18 09:26:58 +00:00
|
|
|
/*
|
|
|
|
* Remove previous version of this IA_NA,
|
|
|
|
* if one exists.
|
|
|
|
*/
|
|
|
|
struct data_string *d = &ia_na->iaid_duid;
|
|
|
|
old_ia_na = NULL;
|
|
|
|
if (ia_na_hash_lookup(&old_ia_na, ia_active,
|
|
|
|
(char *)d->data,
|
|
|
|
d->len, MDL)) {
|
|
|
|
ia_na_hash_delete(ia_active,
|
|
|
|
(char *)d->data,
|
|
|
|
d->len, MDL);
|
|
|
|
ia_na_dereference(&old_ia_na, MDL);
|
|
|
|
}
|
|
|
|
|
2007-05-08 23:05:22 +00:00
|
|
|
/*
|
|
|
|
* ia_na_add_iaaddr() will reference the
|
|
|
|
* lease, so we need to dereference the
|
|
|
|
* previous reference to the lease if one
|
|
|
|
* exists.
|
|
|
|
*/
|
|
|
|
if (lease->ia_na != NULL) {
|
|
|
|
ia_na_dereference(&lease->ia_na, MDL);
|
|
|
|
}
|
|
|
|
if (ia_na_add_iaaddr(ia_na, lease,
|
|
|
|
MDL) != ISC_R_SUCCESS) {
|
|
|
|
log_fatal("lease_to_client: out of "
|
|
|
|
"memory adding IAADDR");
|
|
|
|
}
|
|
|
|
ia_na_hash_add(ia_active,
|
|
|
|
(char *)ia_na->iaid_duid.data,
|
|
|
|
ia_na->iaid_duid.len,
|
|
|
|
ia_na, MDL);
|
|
|
|
write_ia_na(ia_na);
|
2007-05-18 09:26:58 +00:00
|
|
|
schedule_lease_timeout(lease->ipv6_pool);
|
2007-05-08 23:05:22 +00:00
|
|
|
|
|
|
|
/* If this constitutes a binding, and we
|
|
|
|
* are performing ddns updates, then give
|
|
|
|
* ddns_updates() a chance to do its mojo.
|
|
|
|
*/
|
|
|
|
if (((packet->dhcpv6_msg_type
|
|
|
|
== DHCPV6_REQUEST) ||
|
|
|
|
(packet->dhcpv6_msg_type
|
|
|
|
== DHCPV6_RENEW) ||
|
|
|
|
(packet->dhcpv6_msg_type
|
|
|
|
== DHCPV6_REBIND)) &&
|
|
|
|
(((oc = lookup_option(&server_universe,
|
|
|
|
opt_state,
|
|
|
|
SV_DDNS_UPDATES))
|
|
|
|
== NULL) ||
|
|
|
|
evaluate_boolean_option_cache(NULL,
|
|
|
|
packet, NULL, NULL,
|
|
|
|
packet->options, opt_state,
|
|
|
|
&lease->scope, oc, MDL))) {
|
|
|
|
ddns_updates(packet, NULL, NULL,
|
|
|
|
lease, /* XXX */ NULL,
|
|
|
|
opt_state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We send slightly different errors, depending on
|
|
|
|
* whether the client is asking for a new address, or
|
|
|
|
* attempting to renew an address it thinks it has.
|
|
|
|
*/
|
|
|
|
if (packet->dhcpv6_msg_type == DHCPV6_REBIND) {
|
|
|
|
if (iaaddr.len >= 24) {
|
|
|
|
/*
|
|
|
|
* If the we have a client address,
|
|
|
|
* tell the client it is invalid.
|
|
|
|
*/
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
|
|
d.len = 24;
|
|
|
|
if (!buffer_allocate(&d.buffer,
|
|
|
|
d.len, MDL)) {
|
|
|
|
log_fatal("lease_to_client: "
|
|
|
|
"no memory for "
|
|
|
|
"address "
|
|
|
|
"information.");
|
|
|
|
}
|
|
|
|
d.data = d.buffer->data;
|
|
|
|
memcpy(d.buffer->data, iaaddr.data, 16);
|
|
|
|
putULong(d.buffer->data+16, 0);
|
|
|
|
putULong(d.buffer->data+20, 0);
|
|
|
|
if (!save_option_buffer(
|
|
|
|
&dhcpv6_universe,
|
|
|
|
host_opt_state,
|
|
|
|
d.buffer,
|
|
|
|
d.buffer->data,
|
|
|
|
d.len,
|
|
|
|
D6O_IAADDR, 0)) {
|
|
|
|
log_error("lease_to_client: "
|
|
|
|
"error saving "
|
|
|
|
"IAADDR.");
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
data_string_forget(&d, MDL);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Otherwise, this is an error.
|
|
|
|
*
|
|
|
|
* XXX: The other possibility is to
|
|
|
|
* not say anything for this
|
|
|
|
* IA, which might be more
|
|
|
|
* correct. RFC 3315 does not
|
|
|
|
* address this case.
|
|
|
|
*/
|
|
|
|
if (!set_status_code(STATUS_UnspecFail,
|
|
|
|
"Rebind requested without "
|
|
|
|
"including an addresses.",
|
|
|
|
host_opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (packet->dhcpv6_msg_type == DHCPV6_RENEW) {
|
|
|
|
if (!set_status_code(STATUS_NoBinding,
|
|
|
|
"Address not bound "
|
|
|
|
"to this interface.",
|
|
|
|
host_opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
} else if ((iaaddr.len >= 24) &&
|
|
|
|
(packet->dhcpv6_msg_type == DHCPV6_REQUEST)){
|
|
|
|
if (!set_status_code(STATUS_NotOnLink,
|
|
|
|
"Address not for "
|
|
|
|
"use on this link.",
|
|
|
|
host_opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!set_status_code(STATUS_NoAddrsAvail,
|
|
|
|
"No addresses available "
|
|
|
|
"for this interface.",
|
|
|
|
host_opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Insure we have enough space
|
|
|
|
*/
|
|
|
|
if (sizeof(reply_data) < (reply_ofs + 16)) {
|
|
|
|
log_error("lease_to_client: "
|
|
|
|
"out of space for reply packet.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store the encapsulated option data for this IA_NA into
|
|
|
|
* our reply packet.
|
|
|
|
*/
|
|
|
|
len = store_options6(reply_data+reply_ofs+16,
|
|
|
|
sizeof(reply_data)-reply_ofs-16,
|
|
|
|
host_opt_state, packet,
|
|
|
|
required_opts_IA_NA, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store the non-encapsulated option data for this IA_NA
|
|
|
|
* into our reply packet. Defined in RFC 3315, section 22.4.
|
|
|
|
*/
|
|
|
|
/* option number */
|
|
|
|
putShort(reply_data+reply_ofs, D6O_IA_NA);
|
|
|
|
/* option length */
|
|
|
|
putUShort(reply_data+reply_ofs+2, len + 12);
|
|
|
|
/* IA_NA, copied from the client */
|
|
|
|
memcpy(reply_data+reply_ofs+4, cli_enc_opt_data.data, 4);
|
|
|
|
/* T1 and T2, set previously */
|
|
|
|
putULong(reply_data+reply_ofs+8, t1);
|
|
|
|
putULong(reply_data+reply_ofs+12, t2);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get ready for next IA_NA.
|
|
|
|
*/
|
|
|
|
reply_ofs += (len + 16);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bit of cleanup.
|
|
|
|
*/
|
|
|
|
if (lease != NULL) {
|
|
|
|
iaaddr_dereference(&lease, MDL);
|
|
|
|
}
|
|
|
|
ia_na_dereference(&ia_na, MDL);
|
|
|
|
if (iaaddr.data != NULL) {
|
|
|
|
data_string_forget(&iaaddr, MDL);
|
|
|
|
}
|
|
|
|
option_state_dereference(&host_opt_state, MDL);
|
|
|
|
option_state_dereference(&cli_enc_opt_state, MDL);
|
|
|
|
data_string_forget(&cli_enc_opt_data, MDL);
|
|
|
|
ia = ia->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return our reply to the caller.
|
|
|
|
*/
|
|
|
|
reply_ret->len = reply_ofs;
|
|
|
|
reply_ret->buffer = NULL;
|
|
|
|
if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
|
|
|
|
log_fatal("No memory to store reply.");
|
|
|
|
}
|
|
|
|
reply_ret->data = reply_ret->buffer->data;
|
|
|
|
memcpy(reply_ret->buffer->data, reply, reply_ofs);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (lease != NULL) {
|
|
|
|
iaaddr_dereference(&lease, MDL);
|
|
|
|
}
|
|
|
|
if (ia_na != NULL) {
|
|
|
|
ia_na_dereference(&ia_na, MDL);
|
|
|
|
}
|
|
|
|
if (iaaddr.buffer != NULL) {
|
|
|
|
data_string_forget(&iaaddr, MDL);
|
|
|
|
}
|
|
|
|
if (fixed_addr.buffer != NULL) {
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
}
|
|
|
|
if (host_opt_state != NULL) {
|
|
|
|
option_state_dereference(&host_opt_state, MDL);
|
|
|
|
}
|
|
|
|
if (cli_enc_opt_state != NULL) {
|
|
|
|
option_state_dereference(&cli_enc_opt_state, MDL);
|
|
|
|
}
|
|
|
|
if (cli_enc_opt_data.buffer != NULL) {
|
|
|
|
data_string_forget(&cli_enc_opt_data, MDL);
|
|
|
|
}
|
|
|
|
if (packet_oro.buffer != NULL) {
|
|
|
|
data_string_forget(&packet_oro, MDL);
|
|
|
|
}
|
|
|
|
if (opt_state != NULL) {
|
|
|
|
option_state_dereference(&opt_state, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Solicit is how a client starts requesting addresses.
|
|
|
|
*
|
|
|
|
* If the client asks for rapid commit, and we support it, we will
|
|
|
|
* allocate the addresses and reply.
|
|
|
|
*
|
|
|
|
* Otherwise we will send an advertise message.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages */
|
|
|
|
static void
|
|
|
|
dhcpv6_solicit(struct data_string *reply_ret, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
struct option_cache *oc;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate our input.
|
|
|
|
*/
|
|
|
|
if (!valid_client_msg(packet, &client_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lease_to_client(reply_ret, packet, &client_id, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clean up.
|
|
|
|
*/
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Request is how a client actually requests addresses.
|
|
|
|
*
|
|
|
|
* Very similar to Solicit handling, except the server DUID is required.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages, unless we set unicast option */
|
|
|
|
static void
|
|
|
|
dhcpv6_request(struct data_string *reply_ret, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
struct data_string server_id;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate our input.
|
|
|
|
*/
|
|
|
|
if (!valid_client_resp(packet, &client_id, &server_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Issue our lease.
|
|
|
|
*/
|
|
|
|
lease_to_client(reply_ret, packet, &client_id, &server_id);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Cleanup.
|
|
|
|
*/
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
data_string_forget(&server_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When a client thinks it might be on a new link, it sends a
|
|
|
|
* Confirm message.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages, unless we set unicast option */
|
|
|
|
static void
|
|
|
|
dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
struct option_state *opt_state;
|
|
|
|
char reply_data[65536];
|
|
|
|
struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
|
|
|
|
int reply_ofs = (int)((char *)reply->options - (char *)reply);
|
|
|
|
struct option_cache *ia;
|
|
|
|
struct option_cache *oc;
|
|
|
|
/* cli_enc_... variables come from the IA_NA/IA_TA options */
|
|
|
|
struct data_string cli_enc_opt_data;
|
|
|
|
struct option_state *cli_enc_opt_state;
|
|
|
|
struct data_string iaaddr;
|
|
|
|
struct host_decl *host;
|
|
|
|
int num_ia;
|
|
|
|
int num_addr;
|
|
|
|
struct data_string fixed_addr;
|
|
|
|
struct data_string packet_oro;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate the message.
|
|
|
|
*/
|
|
|
|
if (!valid_client_msg(packet, &client_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Bit of variable initialization.
|
|
|
|
*/
|
|
|
|
memset(&iaaddr, 0, sizeof(iaaddr));
|
|
|
|
num_ia = 0;
|
|
|
|
num_addr = 0;
|
|
|
|
memset(&fixed_addr, 0, sizeof(fixed_addr));
|
|
|
|
memset(&packet_oro, 0, sizeof(packet_oro));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up reply.
|
|
|
|
*/
|
|
|
|
if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Search each IA to make sure we know about it.
|
|
|
|
*/
|
|
|
|
ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
|
|
|
|
for (; ia != NULL; ia = ia->next) {
|
|
|
|
num_ia++;
|
|
|
|
|
|
|
|
if (!get_encapsulated_IA_state(&cli_enc_opt_state,
|
|
|
|
&cli_enc_opt_data,
|
|
|
|
packet, ia)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&iaaddr, 0, sizeof(iaaddr));
|
|
|
|
oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
|
|
|
|
D6O_IAADDR);
|
|
|
|
if (oc == NULL) {
|
|
|
|
/*
|
|
|
|
* No address with this IA, check next.
|
|
|
|
*/
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!evaluate_option_cache(&iaaddr, packet,
|
|
|
|
NULL, NULL,
|
|
|
|
packet->options,
|
|
|
|
NULL, &global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
log_error("dhcpv6_confirm: "
|
|
|
|
"error evaluating IAADDR.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
host = NULL;
|
|
|
|
if (!find_hosts_by_uid(&host,
|
|
|
|
client_id.data, client_id.len, MDL)) {
|
|
|
|
/*
|
|
|
|
* RFC 3315, section 18.2.2 implies that when the
|
|
|
|
* server doesn't know about the client it should
|
|
|
|
* not send a reply.
|
|
|
|
*/
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the matching host entry, if any.
|
|
|
|
*/
|
|
|
|
for (; host != NULL; host = host->n_ipaddr) {
|
|
|
|
if (host->fixed_addr == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!evaluate_option_cache(&fixed_addr, NULL,
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
NULL, &global_scope,
|
|
|
|
host->fixed_addr,
|
|
|
|
MDL)) {
|
|
|
|
log_error("dhcpv6_confirm: error "
|
|
|
|
"evaluating host address.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
if ((iaaddr.len >= 16) &&
|
|
|
|
!memcmp(fixed_addr.data, iaaddr.data, 16)) {
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* No matching entry found, so this is a bad address.
|
|
|
|
*/
|
|
|
|
if (host != NULL) {
|
|
|
|
num_addr++;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we have no addresses from the client, we don't send a reply.
|
|
|
|
*/
|
|
|
|
if (num_addr == 0) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set our status.
|
|
|
|
*/
|
|
|
|
if (num_addr < num_ia) {
|
|
|
|
if (!set_status_code(STATUS_NotOnLink,
|
|
|
|
"Some of the addresses are not on link.",
|
|
|
|
opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!set_status_code(STATUS_Success,
|
|
|
|
"All addresses still on link.",
|
|
|
|
opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Only one option: add it.
|
|
|
|
*/
|
|
|
|
reply_ofs += store_options6(reply_data+reply_ofs,
|
|
|
|
sizeof(reply_data)-reply_ofs,
|
|
|
|
opt_state, packet,
|
|
|
|
required_opts, &packet_oro);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return our reply to the caller.
|
|
|
|
*/
|
|
|
|
reply_ret->len = reply_ofs;
|
|
|
|
reply_ret->buffer = NULL;
|
|
|
|
if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
|
|
|
|
log_fatal("No memory to store reply.");
|
|
|
|
}
|
|
|
|
reply_ret->data = reply_ret->buffer->data;
|
|
|
|
memcpy(reply_ret->buffer->data, reply, reply_ofs);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (iaaddr.buffer != NULL) {
|
|
|
|
data_string_forget(&iaaddr, MDL);
|
|
|
|
}
|
|
|
|
if (fixed_addr.buffer != NULL) {
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
}
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Renew is when a client wants to extend its lease, at time T1.
|
|
|
|
*
|
|
|
|
* We handle this the same as if the client wants a new lease, except
|
|
|
|
* for the error code of when addresses don't match.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages, unless we set unicast option */
|
|
|
|
static void
|
|
|
|
dhcpv6_renew(struct data_string *reply, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
struct data_string server_id;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate the request.
|
|
|
|
*/
|
|
|
|
if (!valid_client_resp(packet, &client_id, &server_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Renew our lease.
|
|
|
|
*/
|
|
|
|
lease_to_client(reply, packet, &client_id, &server_id);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Cleanup.
|
|
|
|
*/
|
|
|
|
data_string_forget(&server_id, MDL);
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Rebind is when a client wants to extend its lease, at time T2.
|
|
|
|
*
|
|
|
|
* We handle this the same as if the client wants a new lease, except
|
|
|
|
* for the error code of when addresses don't match.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages, unless we set unicast option */
|
|
|
|
static void
|
|
|
|
dhcpv6_rebind(struct data_string *reply, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
|
|
|
|
if (!valid_client_msg(packet, &client_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lease_to_client(reply, packet, &client_id, NULL);
|
|
|
|
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ia_na_match_decline(const struct data_string *client_id,
|
|
|
|
const struct data_string *iaaddr,
|
|
|
|
struct iaaddr *lease) {
|
|
|
|
char tmp_addr[INET6_ADDRSTRLEN];
|
|
|
|
|
|
|
|
log_error("Client %s reports address %s is "
|
|
|
|
"already in use by another host!",
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60),
|
|
|
|
inet_ntop(AF_INET6, iaaddr->data,
|
|
|
|
tmp_addr, sizeof(tmp_addr)));
|
|
|
|
if (lease != NULL) {
|
|
|
|
decline_lease6(lease->ipv6_pool, lease);
|
|
|
|
write_ia_na(lease->ia_na);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ia_na_nomatch_decline(const struct data_string *client_id,
|
|
|
|
const struct data_string *iaaddr,
|
|
|
|
u_int32_t *ia_na_id,
|
|
|
|
struct packet *packet,
|
|
|
|
char *reply_data,
|
|
|
|
int *reply_ofs,
|
|
|
|
int reply_len) {
|
|
|
|
char tmp_addr[INET6_ADDRSTRLEN];
|
|
|
|
struct option_state *host_opt_state;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
log_info("Client %s declines address %s, which is not offered to it.",
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60),
|
|
|
|
inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create state for this IA_NA.
|
|
|
|
*/
|
|
|
|
host_opt_state = NULL;
|
|
|
|
if (!option_state_allocate(&host_opt_state, MDL)) {
|
|
|
|
log_error("ia_na_nomatch_decline: out of memory "
|
|
|
|
"allocating option_state.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!set_status_code(STATUS_NoBinding, "Decline for unknown address.",
|
|
|
|
host_opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Insure we have enough space
|
|
|
|
*/
|
|
|
|
if (reply_len < (*reply_ofs + 16)) {
|
|
|
|
log_error("ia_na_nomatch_decline: "
|
|
|
|
"out of space for reply packet.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put our status code into the reply packet.
|
|
|
|
*/
|
|
|
|
len = store_options6(reply_data+(*reply_ofs)+16,
|
|
|
|
reply_len-(*reply_ofs)-16,
|
|
|
|
host_opt_state, packet,
|
|
|
|
required_opts_STATUS_CODE, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store the non-encapsulated option data for this
|
|
|
|
* IA_NA into our reply packet. Defined in RFC 3315,
|
|
|
|
* section 22.4.
|
|
|
|
*/
|
|
|
|
/* option number */
|
|
|
|
putUShort(reply_data+(*reply_ofs), D6O_IA_NA);
|
|
|
|
/* option length */
|
|
|
|
putUShort(reply_data+(*reply_ofs)+2, len + 12);
|
|
|
|
/* IA_NA, copied from the client */
|
|
|
|
memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
|
|
|
|
/* t1 and t2, odd that we need them, but here it is */
|
|
|
|
putULong(reply_data+(*reply_ofs)+8, 0);
|
|
|
|
putULong(reply_data+(*reply_ofs)+12, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get ready for next IA_NA.
|
|
|
|
*/
|
|
|
|
*reply_ofs += (len + 16);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
option_state_dereference(&host_opt_state, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
iterate_over_ia_na(struct data_string *reply_ret,
|
|
|
|
struct packet *packet,
|
|
|
|
const struct data_string *client_id,
|
|
|
|
const struct data_string *server_id,
|
|
|
|
char *packet_type,
|
|
|
|
void (*ia_na_match)(),
|
|
|
|
void (*ia_na_nomatch)()) {
|
|
|
|
struct option_state *opt_state;
|
|
|
|
struct host_decl *packet_host;
|
|
|
|
struct option_cache *ia;
|
|
|
|
struct option_cache *oc;
|
|
|
|
/* cli_enc_... variables come from the IA_NA/IA_TA options */
|
|
|
|
struct data_string cli_enc_opt_data;
|
|
|
|
struct option_state *cli_enc_opt_state;
|
|
|
|
struct host_decl *host;
|
|
|
|
struct option_state *host_opt_state;
|
|
|
|
char tmp_addr[INET6_ADDRSTRLEN];
|
|
|
|
int len;
|
|
|
|
struct data_string iaaddr;
|
|
|
|
struct data_string fixed_addr;
|
|
|
|
int iaaddr_is_found;
|
|
|
|
char reply_data[65536];
|
|
|
|
struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data;
|
|
|
|
int reply_ofs = (int)((char *)reply->options - (char *)reply);
|
|
|
|
char status_msg[32];
|
|
|
|
struct iaaddr *lease;
|
|
|
|
struct ia_na *existing_ia_na;
|
|
|
|
int i;
|
|
|
|
struct data_string key;
|
|
|
|
u_int32_t iaid;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize to empty values, in case we have to exit early.
|
|
|
|
*/
|
|
|
|
opt_state = NULL;
|
|
|
|
memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data));
|
|
|
|
cli_enc_opt_state = NULL;
|
|
|
|
memset(&iaaddr, 0, sizeof(iaaddr));
|
|
|
|
memset(&fixed_addr, 0, sizeof(fixed_addr));
|
|
|
|
host_opt_state = NULL;
|
|
|
|
lease = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Find the host record that matches from the packet, if any.
|
|
|
|
*/
|
|
|
|
packet_host = NULL;
|
|
|
|
if (!find_hosts_by_uid(&packet_host,
|
|
|
|
client_id->data, client_id->len, MDL)) {
|
|
|
|
packet_host = NULL;
|
|
|
|
/*
|
|
|
|
* Note: In general, we don't expect a client to provide
|
|
|
|
* enough information to match by option for these
|
|
|
|
* types of messages, but if we don't have a UID
|
|
|
|
* match we can check anyway.
|
|
|
|
*/
|
|
|
|
if (!find_hosts_by_option(&packet_host,
|
|
|
|
packet, packet->options, MDL)) {
|
|
|
|
packet_host = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set our reply information.
|
|
|
|
*/
|
|
|
|
reply->msg_type = DHCPV6_REPLY;
|
|
|
|
memcpy(reply->transaction_id, packet->dhcpv6_transaction_id,
|
|
|
|
sizeof(reply->transaction_id));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build our option state for reply.
|
|
|
|
*/
|
|
|
|
opt_state = NULL;
|
|
|
|
if (!option_state_allocate(&opt_state, MDL)) {
|
|
|
|
log_error("iterate_over_ia_na: no memory for option_state.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
execute_statements_in_scope(NULL, packet, NULL, NULL,
|
|
|
|
packet->options, opt_state,
|
|
|
|
&global_scope, root_group, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* RFC 3315, section 18.2.7 tells us which options to include.
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, opt_state, D6O_SERVERID);
|
|
|
|
if (oc == NULL) {
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe, opt_state,
|
|
|
|
NULL, (char *)server_duid.data,
|
|
|
|
server_duid.len, D6O_SERVERID, 0)) {
|
|
|
|
log_error("iterate_over_ia_na: "
|
|
|
|
"error saving server identifier.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe, opt_state,
|
|
|
|
client_id->buffer,
|
|
|
|
(unsigned char *)client_id->data,
|
|
|
|
client_id->len,
|
|
|
|
D6O_CLIENTID, 0)) {
|
|
|
|
log_error("iterate_over_ia_na: "
|
|
|
|
"error saving client identifier.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf(status_msg, sizeof(status_msg), "%s received.", packet_type);
|
|
|
|
if (!set_status_code(STATUS_Success, status_msg, opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add our options that are not associated with any IA_NA or IA_TA.
|
|
|
|
*/
|
|
|
|
reply_ofs += store_options6(reply_data+reply_ofs,
|
|
|
|
sizeof(reply_data)-reply_ofs,
|
|
|
|
opt_state, packet,
|
|
|
|
required_opts, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loop through the IA_NA reported by the client, and deal with
|
|
|
|
* addresses reported as already in use.
|
|
|
|
*/
|
|
|
|
for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA);
|
|
|
|
ia != NULL; ia = ia->next) {
|
|
|
|
iaaddr_is_found = 0;
|
|
|
|
|
|
|
|
if (!get_encapsulated_IA_state(&cli_enc_opt_state,
|
|
|
|
&cli_enc_opt_data,
|
|
|
|
packet, ia)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
iaid = getULong(cli_enc_opt_data.data);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX: It is possible that we can get multiple addresses
|
|
|
|
* sent by the client. We don't send multiple
|
|
|
|
* addresses, so this indicates a client error.
|
|
|
|
* We should check for multiple IAADDR options, log
|
|
|
|
* if found, and set as an error.
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state,
|
|
|
|
D6O_IAADDR);
|
|
|
|
if (oc == NULL) {
|
|
|
|
/* no address given for this IA, ignore */
|
|
|
|
option_state_dereference(&cli_enc_opt_state, MDL);
|
|
|
|
data_string_forget(&cli_enc_opt_data, MDL);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&iaaddr, 0, sizeof(iaaddr));
|
|
|
|
if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL,
|
|
|
|
packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
log_error("iterate_over_ia_na: "
|
|
|
|
"error evaluating IAADDR.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now we need to figure out which host record matches
|
|
|
|
* this IA_NA and IAADDR.
|
|
|
|
*
|
|
|
|
* XXX: We don't currently track IA_NA separately, but
|
|
|
|
* we will need to do this!
|
|
|
|
*/
|
|
|
|
host = NULL;
|
|
|
|
if (!find_hosts_by_option(&host, packet,
|
|
|
|
cli_enc_opt_state, MDL)) {
|
|
|
|
if (packet_host != NULL) {
|
|
|
|
host = packet_host;
|
|
|
|
} else {
|
|
|
|
host = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (host != NULL) {
|
|
|
|
if (host->fixed_addr != NULL) {
|
|
|
|
if (!evaluate_option_cache(&fixed_addr, NULL,
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
NULL, &global_scope,
|
|
|
|
host->fixed_addr,
|
|
|
|
MDL)) {
|
|
|
|
log_error("iterate_over_ia_na: error "
|
|
|
|
"evaluating host address.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
if ((iaaddr.len >= 16) &&
|
|
|
|
!memcmp(fixed_addr.data, iaaddr.data, 16)) {
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
}
|
|
|
|
host = host->n_ipaddr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((host == NULL) && (iaaddr.len >= 24)) {
|
|
|
|
/*
|
|
|
|
* Find existing IA_NA.
|
|
|
|
*/
|
|
|
|
if (ia_na_make_key(&key, iaid, client_id->data,
|
|
|
|
client_id->len,
|
|
|
|
MDL) != ISC_R_SUCCESS) {
|
|
|
|
log_fatal("iterate_over_ia_na: no memory for "
|
|
|
|
"key.");
|
|
|
|
}
|
|
|
|
|
|
|
|
existing_ia_na = NULL;
|
|
|
|
if (ia_na_hash_lookup(&existing_ia_na, ia_active,
|
|
|
|
(char *)key.data, key.len, MDL)) {
|
|
|
|
/*
|
|
|
|
* Make sure this address is in the IA_NA.
|
|
|
|
*/
|
|
|
|
for (i=0; i<existing_ia_na->num_iaaddr; i++) {
|
|
|
|
struct iaaddr *tmp;
|
|
|
|
struct in6_addr *in6_addr;
|
|
|
|
|
|
|
|
tmp = existing_ia_na->iaaddr[i];
|
|
|
|
in6_addr = &tmp->addr;
|
|
|
|
if (memcmp(in6_addr,
|
|
|
|
iaaddr.data, 16) == 0) {
|
|
|
|
iaaddr_reference(&lease,
|
|
|
|
tmp, MDL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
data_string_forget(&key, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((host != NULL) || (lease != NULL)) {
|
|
|
|
ia_na_match(client_id, &iaaddr, lease);
|
|
|
|
} else {
|
|
|
|
ia_na_nomatch(client_id, &iaaddr,
|
|
|
|
(u_int32_t *)cli_enc_opt_data.data,
|
|
|
|
packet, reply_data, &reply_ofs,
|
|
|
|
sizeof(reply_data));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lease != NULL) {
|
|
|
|
iaaddr_dereference(&lease, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
data_string_forget(&iaaddr, MDL);
|
|
|
|
option_state_dereference(&cli_enc_opt_state, MDL);
|
|
|
|
data_string_forget(&cli_enc_opt_data, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return our reply to the caller.
|
|
|
|
*/
|
|
|
|
reply_ret->len = reply_ofs;
|
|
|
|
reply_ret->buffer = NULL;
|
|
|
|
if (!buffer_allocate(&reply_ret->buffer, reply_ofs, MDL)) {
|
|
|
|
log_fatal("No memory to store reply.");
|
|
|
|
}
|
|
|
|
reply_ret->data = reply_ret->buffer->data;
|
|
|
|
memcpy(reply_ret->buffer->data, reply, reply_ofs);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (lease != NULL) {
|
|
|
|
iaaddr_dereference(&lease, MDL);
|
|
|
|
}
|
|
|
|
if (host_opt_state != NULL) {
|
|
|
|
option_state_dereference(&host_opt_state, MDL);
|
|
|
|
}
|
|
|
|
if (fixed_addr.buffer != NULL) {
|
|
|
|
data_string_forget(&fixed_addr, MDL);
|
|
|
|
}
|
|
|
|
if (iaaddr.buffer != NULL) {
|
|
|
|
data_string_forget(&iaaddr, MDL);
|
|
|
|
}
|
|
|
|
if (cli_enc_opt_state != NULL) {
|
|
|
|
option_state_dereference(&cli_enc_opt_state, MDL);
|
|
|
|
}
|
|
|
|
if (cli_enc_opt_data.buffer != NULL) {
|
|
|
|
data_string_forget(&cli_enc_opt_data, MDL);
|
|
|
|
}
|
|
|
|
if (opt_state != NULL) {
|
|
|
|
option_state_dereference(&opt_state, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Decline means a client has detected that something else is using an
|
|
|
|
* address we gave it.
|
|
|
|
*
|
|
|
|
* Since we're only dealing with fixed leases for now, there's not
|
|
|
|
* much we can do, other that log the occurrance.
|
|
|
|
*
|
|
|
|
* When we start issuing addresses from pools, then we will have to
|
|
|
|
* record our declined addresses and issue another. In general with
|
|
|
|
* IPv6 there is no worry about DoS by clients exhausting space, but
|
|
|
|
* we still need to be aware of this possibility.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages, unless we set unicast option */
|
|
|
|
/* TODO: IA_TA */
|
|
|
|
static void
|
|
|
|
dhcpv6_decline(struct data_string *reply, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
struct data_string server_id;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate our input.
|
|
|
|
*/
|
|
|
|
if (!valid_client_resp(packet, &client_id, &server_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* And operate on each IA_NA in this packet.
|
|
|
|
*/
|
|
|
|
iterate_over_ia_na(reply, packet, &client_id, &server_id, "Decline",
|
|
|
|
ia_na_match_decline, ia_na_nomatch_decline);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ia_na_match_release(const struct data_string *client_id,
|
|
|
|
const struct data_string *iaaddr,
|
|
|
|
struct iaaddr *lease) {
|
|
|
|
char tmp_addr[INET6_ADDRSTRLEN];
|
|
|
|
|
|
|
|
log_info("Client %s releases address %s",
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60),
|
|
|
|
inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
|
|
|
|
if (lease != NULL) {
|
|
|
|
release_lease6(lease->ipv6_pool, lease);
|
|
|
|
write_ia_na(lease->ia_na);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ia_na_nomatch_release(const struct data_string *client_id,
|
|
|
|
const struct data_string *iaaddr,
|
|
|
|
u_int32_t *ia_na_id,
|
|
|
|
struct packet *packet,
|
|
|
|
char *reply_data,
|
|
|
|
int *reply_ofs,
|
|
|
|
int reply_len) {
|
|
|
|
char tmp_addr[INET6_ADDRSTRLEN];
|
|
|
|
struct option_state *host_opt_state;
|
|
|
|
int len;
|
|
|
|
|
|
|
|
log_info("Client %s releases address %s, which is not leased to it.",
|
|
|
|
print_hex_1(client_id->len, client_id->data, 60),
|
|
|
|
inet_ntop(AF_INET6, iaaddr->data, tmp_addr, sizeof(tmp_addr)));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create state for this IA_NA.
|
|
|
|
*/
|
|
|
|
host_opt_state = NULL;
|
|
|
|
if (!option_state_allocate(&host_opt_state, MDL)) {
|
|
|
|
log_error("ia_na_match_release: out of memory "
|
|
|
|
"allocating option_state.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!set_status_code(STATUS_NoBinding,
|
|
|
|
"Release for non-leased address.",
|
|
|
|
host_opt_state)) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Insure we have enough space
|
|
|
|
*/
|
|
|
|
if (reply_len < (*reply_ofs + 16)) {
|
|
|
|
log_error("ia_na_match_release: "
|
|
|
|
"out of space for reply packet.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put our status code into the reply packet.
|
|
|
|
*/
|
|
|
|
len = store_options6(reply_data+(*reply_ofs)+16,
|
|
|
|
reply_len-(*reply_ofs)-16,
|
|
|
|
host_opt_state, packet,
|
|
|
|
required_opts_STATUS_CODE, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store the non-encapsulated option data for this
|
|
|
|
* IA_NA into our reply packet. Defined in RFC 3315,
|
|
|
|
* section 22.4.
|
|
|
|
*/
|
|
|
|
/* option number */
|
|
|
|
putUShort(reply_data+(*reply_ofs), D6O_IA_NA);
|
|
|
|
/* option length */
|
|
|
|
putUShort(reply_data+(*reply_ofs)+2, len + 12);
|
|
|
|
/* IA_NA, copied from the client */
|
|
|
|
memcpy(reply_data+(*reply_ofs)+4, ia_na_id, 4);
|
|
|
|
/* t1 and t2, odd that we need them, but here it is */
|
|
|
|
putULong(reply_data+(*reply_ofs)+8, 0);
|
|
|
|
putULong(reply_data+(*reply_ofs)+12, 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get ready for next IA_NA.
|
|
|
|
*/
|
|
|
|
*reply_ofs += (len + 16);
|
|
|
|
|
|
|
|
exit:
|
|
|
|
option_state_dereference(&host_opt_state, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Release means a client is done with the addresses.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages, unless we set unicast option */
|
|
|
|
static void
|
|
|
|
dhcpv6_release(struct data_string *reply, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
struct data_string server_id;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate our input.
|
|
|
|
*/
|
|
|
|
if (!valid_client_resp(packet, &client_id, &server_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* And operate on each IA_NA in this packet.
|
|
|
|
*/
|
|
|
|
iterate_over_ia_na(reply, packet, &client_id, &server_id, "Release",
|
|
|
|
ia_na_match_release, ia_na_nomatch_release);
|
|
|
|
|
|
|
|
data_string_forget(&server_id, MDL);
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Information-Request is used by clients who have obtained an address
|
|
|
|
* from other means, but want configuration information from the server.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO: discard unicast messages, unless we set unicast option */
|
|
|
|
static void
|
|
|
|
dhcpv6_information_request(struct data_string *reply, struct packet *packet) {
|
|
|
|
struct data_string client_id;
|
|
|
|
struct data_string server_id;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate our input.
|
|
|
|
*/
|
|
|
|
if (!valid_client_info_req(packet, &server_id)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get our client ID, if there is one.
|
|
|
|
*/
|
|
|
|
memset(&client_id, 0, sizeof(client_id));
|
|
|
|
if (get_client_id(packet, &client_id) != ISC_R_SUCCESS) {
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Use the lease_to_client() function. This will work fine,
|
|
|
|
* because the valid_client_info_req() insures that we
|
|
|
|
* don't have any IA_NA or IA_TA that would cause us to
|
|
|
|
* allocate addresses to the client.
|
|
|
|
*/
|
|
|
|
lease_to_client(reply, packet, &client_id, &server_id);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Cleanup.
|
|
|
|
*/
|
|
|
|
if (client_id.data != NULL) {
|
|
|
|
data_string_forget(&client_id, MDL);
|
|
|
|
}
|
|
|
|
data_string_forget(&server_id, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The Relay-forw message is sent by relays. It typically contains a
|
|
|
|
* single option, which encapsulates an entire packet.
|
|
|
|
*
|
|
|
|
* We need to build an encapsulated reply.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* XXX: this is very, very similar to do_packet6(), and should probably
|
|
|
|
be combined in a clever way */
|
|
|
|
static void
|
|
|
|
dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) {
|
|
|
|
struct dhcpv6_relay_packet reply;
|
|
|
|
struct option_cache *oc;
|
|
|
|
struct data_string enc_opt_data;
|
|
|
|
struct packet *enc_packet;
|
|
|
|
unsigned char msg_type;
|
|
|
|
const struct dhcpv6_packet *msg;
|
|
|
|
const struct dhcpv6_relay_packet *relay;
|
|
|
|
struct data_string enc_reply;
|
|
|
|
char link_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
|
|
|
|
char peer_addr[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")];
|
|
|
|
struct data_string interface_id;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Intialize variables for early exit.
|
|
|
|
*/
|
|
|
|
memset(&enc_opt_data, 0, sizeof(enc_opt_data));
|
|
|
|
enc_packet = NULL;
|
|
|
|
memset(&enc_reply, 0, sizeof(enc_reply));
|
|
|
|
memset(&interface_id, 0, sizeof(interface_id));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get our encapsulated relay message.
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_MSG);
|
|
|
|
if (oc == NULL) {
|
|
|
|
inet_ntop(AF_INET6, &packet->dhcpv6_link_address,
|
|
|
|
link_addr, sizeof(link_addr));
|
|
|
|
inet_ntop(AF_INET6, &packet->dhcpv6_peer_address,
|
|
|
|
peer_addr, sizeof(peer_addr));
|
|
|
|
log_info("Relay-forward from %s with link address=%s and "
|
|
|
|
"peer address=%s missing Relay Message option.",
|
|
|
|
piaddr(packet->client_addr), link_addr, peer_addr);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&enc_opt_data, 0, sizeof(enc_opt_data));
|
|
|
|
if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL,
|
|
|
|
NULL, NULL, &global_scope, oc, MDL)) {
|
|
|
|
log_error("dhcpv6_forw_relay: error evaluating "
|
|
|
|
"relayed message.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!packet6_len_okay(enc_opt_data.data, enc_opt_data.len)) {
|
|
|
|
log_error("dhcpv6_forw_relay: encapsulated packet too short.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build a packet structure from this encapsulated packet.
|
|
|
|
*/
|
|
|
|
enc_packet = NULL;
|
|
|
|
if (!packet_allocate(&enc_packet, MDL)) {
|
|
|
|
log_error("dhcpv6_forw_relay: "
|
|
|
|
"no memory for encapsulated packet.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!option_state_allocate(&enc_packet->options, MDL)) {
|
|
|
|
log_error("dhcpv6_forw_relay: "
|
|
|
|
"no memory for encapsulated packet's options.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
enc_packet->client_port = packet->client_port;
|
|
|
|
enc_packet->client_addr = packet->client_addr;
|
|
|
|
enc_packet->dhcpv6_container_packet = packet;
|
|
|
|
|
|
|
|
msg_type = enc_opt_data.data[0];
|
|
|
|
if ((msg_type == DHCPV6_RELAY_FORW) ||
|
|
|
|
(msg_type == DHCPV6_RELAY_REPL)) {
|
|
|
|
relay = (struct dhcpv6_relay_packet *)enc_opt_data.data;
|
|
|
|
enc_packet->dhcpv6_msg_type = relay->msg_type;
|
|
|
|
|
|
|
|
/* relay-specific data */
|
|
|
|
enc_packet->dhcpv6_hop_count = relay->hop_count;
|
|
|
|
memcpy(&enc_packet->dhcpv6_link_address,
|
|
|
|
relay->link_address, sizeof(relay->link_address));
|
|
|
|
memcpy(&enc_packet->dhcpv6_peer_address,
|
|
|
|
relay->peer_address, sizeof(relay->peer_address));
|
|
|
|
|
|
|
|
if (!parse_option_buffer(enc_packet->options,
|
|
|
|
relay->options,
|
|
|
|
enc_opt_data.len-sizeof(*relay),
|
|
|
|
&dhcpv6_universe)) {
|
|
|
|
/* no logging here, as parse_option_buffer() logs all
|
|
|
|
cases where it fails */
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
msg = (struct dhcpv6_packet *)enc_opt_data.data;
|
|
|
|
enc_packet->dhcpv6_msg_type = msg->msg_type;
|
|
|
|
|
|
|
|
/* message-specific data */
|
|
|
|
memcpy(enc_packet->dhcpv6_transaction_id,
|
|
|
|
msg->transaction_id,
|
|
|
|
sizeof(enc_packet->dhcpv6_transaction_id));
|
|
|
|
|
|
|
|
if (!parse_option_buffer(enc_packet->options,
|
|
|
|
msg->options,
|
|
|
|
enc_opt_data.len-sizeof(*msg),
|
|
|
|
&dhcpv6_universe)) {
|
|
|
|
/* no logging here, as parse_option_buffer() logs all
|
|
|
|
cases where it fails */
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is recursive. It is possible to exceed maximum packet size.
|
|
|
|
* XXX: This will cause the packet send to fail.
|
|
|
|
*/
|
|
|
|
build_dhcpv6_reply(&enc_reply, enc_packet);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we got no encapsulated data, then it is discarded, and
|
|
|
|
* our reply-forw is also discarded.
|
|
|
|
*/
|
|
|
|
if (enc_reply.data == NULL) {
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Append the interface-id if present
|
|
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_INTERFACE_ID);
|
|
|
|
if (oc != NULL) {
|
|
|
|
memset(&interface_id, 0, sizeof(interface_id));
|
|
|
|
if (!evaluate_option_cache(&interface_id, NULL, NULL, NULL,
|
|
|
|
NULL, NULL, &global_scope,
|
|
|
|
oc, MDL)) {
|
|
|
|
log_error("dhcpv6_forw_relay: error evaluating "
|
|
|
|
"Interface ID.");
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Packet header stuff all comes from the forward message.
|
|
|
|
*/
|
|
|
|
reply.msg_type = DHCPV6_RELAY_REPL;
|
|
|
|
reply.hop_count = packet->dhcpv6_hop_count;
|
|
|
|
memcpy(reply.link_address, &packet->dhcpv6_link_address,
|
|
|
|
sizeof(reply.link_address));
|
|
|
|
memcpy(reply.peer_address, &packet->dhcpv6_peer_address,
|
|
|
|
sizeof(reply.peer_address));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy our encapsulated stuff for caller.
|
|
|
|
*/
|
|
|
|
reply_ret->len = sizeof(reply) + 4 + enc_reply.len;
|
|
|
|
if (interface_id.data != NULL) {
|
|
|
|
reply_ret->len += 4 + interface_id.len;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* XXX: We should not allow this to happen, perhaps by letting
|
|
|
|
* build_dhcp_reply() know our space remaining.
|
|
|
|
*/
|
|
|
|
if (reply_ret->len >= 65536) {
|
|
|
|
log_error("dhcpv6_forw_relay: RELAY-REPL too big (%d bytes)",
|
|
|
|
reply_ret->len);
|
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
reply_ret->buffer = NULL;
|
|
|
|
if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) {
|
|
|
|
log_fatal("No memory to store reply.");
|
|
|
|
}
|
|
|
|
reply_ret->data = reply_ret->buffer->data;
|
|
|
|
memcpy(reply_ret->buffer->data, &reply, sizeof(reply));
|
|
|
|
putShort(reply_ret->buffer->data+sizeof(reply), D6O_RELAY_MSG);
|
|
|
|
putShort(reply_ret->buffer->data+sizeof(reply)+2, enc_reply.len);
|
|
|
|
memcpy(reply_ret->buffer->data+sizeof(reply)+4,
|
|
|
|
enc_reply.data, enc_reply.len);
|
|
|
|
if (interface_id.data != NULL) {
|
|
|
|
putShort(reply_ret->buffer->data+sizeof(reply)+4+enc_reply.len,
|
|
|
|
D6O_INTERFACE_ID);
|
|
|
|
putShort(reply_ret->buffer->data+sizeof(reply)+6+enc_reply.len,
|
|
|
|
interface_id.len);
|
|
|
|
memcpy(reply_ret->buffer->data+sizeof(reply)+8+enc_reply.len,
|
|
|
|
interface_id.data, interface_id.len);
|
|
|
|
}
|
|
|
|
|
|
|
|
exit:
|
|
|
|
if (interface_id.data != NULL) {
|
|
|
|
data_string_forget(&interface_id, MDL);
|
|
|
|
}
|
|
|
|
if (enc_reply.data != NULL) {
|
|
|
|
data_string_forget(&enc_reply, MDL);
|
|
|
|
}
|
|
|
|
if (enc_opt_data.data != NULL) {
|
|
|
|
data_string_forget(&enc_opt_data, MDL);
|
|
|
|
}
|
|
|
|
if (enc_packet != NULL) {
|
|
|
|
packet_dereference(&enc_packet, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dhcpv6_discard(struct packet *packet) {
|
|
|
|
/* INSIST(packet->msg_type > 0); */
|
|
|
|
/* INSIST(packet->msg_type < dhcpv6_type_name_max); */
|
|
|
|
|
|
|
|
log_debug("Discarding %s from %s; message type not handled by server",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
build_dhcpv6_reply(struct data_string *reply, struct packet *packet) {
|
|
|
|
memset(reply, 0, sizeof(*reply));
|
|
|
|
switch (packet->dhcpv6_msg_type) {
|
|
|
|
case DHCPV6_SOLICIT:
|
|
|
|
dhcpv6_solicit(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_ADVERTISE:
|
|
|
|
dhcpv6_discard(packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_REQUEST:
|
|
|
|
dhcpv6_request(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_CONFIRM:
|
|
|
|
dhcpv6_confirm(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_RENEW:
|
|
|
|
dhcpv6_renew(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_REBIND:
|
|
|
|
dhcpv6_rebind(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_REPLY:
|
|
|
|
dhcpv6_discard(packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_RELEASE:
|
|
|
|
dhcpv6_release(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_DECLINE:
|
|
|
|
dhcpv6_decline(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_RECONFIGURE:
|
|
|
|
dhcpv6_discard(packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_INFORMATION_REQUEST:
|
|
|
|
dhcpv6_information_request(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_RELAY_FORW:
|
|
|
|
dhcpv6_relay_forw(reply, packet);
|
|
|
|
break;
|
|
|
|
case DHCPV6_RELAY_REPL:
|
|
|
|
dhcpv6_discard(packet);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* XXX: would be nice if we had "notice" level,
|
|
|
|
as syslog, for this */
|
|
|
|
log_info("Discarding unknown DHCPv6 message type %d "
|
|
|
|
"from %s", packet->dhcpv6_msg_type,
|
|
|
|
piaddr(packet->client_addr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
log_packet_in(const struct packet *packet) {
|
|
|
|
struct data_string s;
|
|
|
|
u_int32_t tid;
|
|
|
|
char tmp_addr[INET6_ADDRSTRLEN];
|
|
|
|
const void *addr;
|
|
|
|
struct option_cache *oc;
|
|
|
|
struct data_string tmp_ds;
|
|
|
|
|
|
|
|
memset(&s, 0, sizeof(s));
|
|
|
|
|
|
|
|
if (packet->dhcpv6_msg_type < dhcpv6_type_name_max) {
|
|
|
|
data_string_sprintfa(&s, "%s message from %s port %d",
|
|
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
ntohs(packet->client_port));
|
|
|
|
} else {
|
|
|
|
data_string_sprintfa(&s,
|
|
|
|
"Unknown message type %d from %s port %d",
|
|
|
|
packet->dhcpv6_msg_type,
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
ntohs(packet->client_port));
|
|
|
|
}
|
|
|
|
if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) ||
|
|
|
|
(packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
|
|
|
|
addr = &packet->dhcpv6_link_address;
|
|
|
|
data_string_sprintfa(&s, ", link address %s",
|
|
|
|
inet_ntop(AF_INET6, addr,
|
|
|
|
tmp_addr, sizeof(tmp_addr)));
|
|
|
|
addr = &packet->dhcpv6_peer_address;
|
|
|
|
data_string_sprintfa(&s, ", peer address %s",
|
|
|
|
inet_ntop(AF_INET6, addr,
|
|
|
|
tmp_addr, sizeof(tmp_addr)));
|
|
|
|
} else {
|
|
|
|
tid = 0;
|
|
|
|
memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3);
|
|
|
|
data_string_sprintfa(&s, ", transaction ID 0x%06X", tid);
|
|
|
|
|
|
|
|
/*
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options,
|
|
|
|
D6O_CLIENTID);
|
|
|
|
if (oc != NULL) {
|
|
|
|
memset(&tmp_ds, 0, sizeof(tmp_ds_));
|
|
|
|
if (!evaluate_option_cache(&tmp_ds, packet, NULL, NULL,
|
|
|
|
packet->options, NULL,
|
|
|
|
&global_scope, oc, MDL)) {
|
|
|
|
log_error("Error evaluating Client Identifier");
|
|
|
|
} else {
|
|
|
|
data_strint_sprintf(&s, ", client ID %s",
|
|
|
|
|
|
|
|
data_string_forget(&tmp_ds, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
|
|
}
|
|
|
|
log_info("%s", s.data);
|
|
|
|
|
|
|
|
data_string_forget(&s, MDL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
dhcpv6(struct packet *packet) {
|
|
|
|
struct data_string reply;
|
|
|
|
struct sockaddr_in6 to_addr;
|
|
|
|
int send_ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Log a message that we received this packet.
|
|
|
|
*/
|
|
|
|
log_packet_in(packet);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build our reply packet.
|
|
|
|
*/
|
|
|
|
build_dhcpv6_reply(&reply, packet);
|
|
|
|
|
|
|
|
if (reply.data != NULL) {
|
|
|
|
/*
|
|
|
|
* Send our reply, if we have one.
|
|
|
|
*/
|
|
|
|
memset(&to_addr, 0, sizeof(to_addr));
|
|
|
|
to_addr.sin6_family = AF_INET6;
|
|
|
|
if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) ||
|
|
|
|
(packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) {
|
|
|
|
to_addr.sin6_port = local_port;
|
|
|
|
} else {
|
|
|
|
to_addr.sin6_port = remote_port;
|
|
|
|
}
|
|
|
|
/* For testing, we reply to the sending port, so we don't need a root client */
|
|
|
|
to_addr.sin6_port = packet->client_port;
|
|
|
|
memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf,
|
|
|
|
sizeof(to_addr.sin6_addr));
|
|
|
|
|
|
|
|
log_info("Sending %s to %s port %d",
|
|
|
|
dhcpv6_type_names[reply.data[0]],
|
|
|
|
piaddr(packet->client_addr),
|
|
|
|
ntohs(to_addr.sin6_port));
|
|
|
|
|
|
|
|
send_ret = send_packet6(packet->interface,
|
|
|
|
reply.data, reply.len, &to_addr);
|
|
|
|
if (send_ret != reply.len) {
|
|
|
|
log_error("dhcpv6: send_packet6() sent %d of %d bytes",
|
|
|
|
send_ret, reply.len);
|
|
|
|
}
|
|
|
|
data_string_forget(&reply, MDL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-05-19 18:47:15 +00:00
|
|
|
#endif /* DHCPv6 */
|
|
|
|
|