/* * Copyright (C) 2006-2017 by Internet Systems Consortium, Inc. ("ISC") * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * 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. */ /*! \file server/dhcpv6.c */ #include "dhcpd.h" #ifdef DHCPv6 #ifdef DHCP4o6 static void forw_dhcpv4_query(struct packet *packet); static void send_dhcpv4_response(struct data_string *raw); static void recv_dhcpv4_query(struct data_string *raw); static void dhcp4o6_dhcpv4_query(struct data_string *reply_ret, struct packet *packet); struct udp_data4o6 { u_int16_t src_port; u_int8_t rsp_opt_exist; u_int8_t reserved; }; static int offset_data4o6 = 36; /* 16+16+4 */ #endif /* * 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) */ /* * DHCPv6 Reply workflow assist. A Reply packet is built by various * different functions; this gives us one location where we keep state * regarding a reply. */ struct reply_state { /* root level persistent state */ struct shared_network *shared; struct host_decl *host; struct subnet *subnet; /* Used to match fixed-addrs to subnet scopes. */ struct option_state *opt_state; struct packet *packet; struct data_string client_id; /* IA level persistent state */ unsigned ia_count; unsigned pd_count; unsigned client_resources; isc_boolean_t resources_included; isc_boolean_t static_lease; unsigned static_prefixes; struct ia_xx *ia; struct ia_xx *old_ia; struct option_state *reply_ia; struct data_string fixed; struct iaddrcidrnet fixed_pref; /* static prefix for logging */ /* IAADDR/PREFIX level persistent state */ struct iasubopt *lease; /* * "t1", "t2", preferred, and valid lifetimes records for calculating * t1 and t2 (min/max). */ u_int32_t renew, rebind, min_prefer, min_valid; /* Client-requested valid and preferred lifetimes. */ u_int32_t client_valid, client_prefer; /* Chosen values to transmit for valid and preferred lifetimes. */ u_int32_t send_valid, send_prefer; /* Preferred prefix length (-1 is any). */ int preflen; /* Index into the data field that has been consumed. */ unsigned cursor; /* Space for the on commit statements for a fixed host */ struct on_star on_star; union reply_buffer { unsigned char data[65536]; struct dhcpv6_packet reply; } buf; }; /* * Prototypes local to this file. */ 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, int offset); static void build_dhcpv6_reply(struct data_string *, struct packet *); static isc_result_t shared_network_from_packet6(struct shared_network **shared, struct packet *packet); static void seek_shared_host(struct host_decl **hp, struct shared_network *shared); static isc_boolean_t fixed_matches_shared(struct host_decl *host, struct shared_network *shared); static isc_result_t reply_process_ia_na(struct reply_state *reply, struct option_cache *ia); static isc_result_t reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia); static isc_result_t reply_process_addr(struct reply_state *reply, struct option_cache *addr); static isc_boolean_t address_is_owned(struct reply_state *reply, struct iaddr *addr); static isc_boolean_t temporary_is_available(struct reply_state *reply, struct iaddr *addr); static isc_result_t find_client_temporaries(struct reply_state *reply); static isc_result_t reply_process_try_addr(struct reply_state *reply, struct iaddr *addr); static isc_result_t find_client_address(struct reply_state *reply); static isc_result_t reply_process_is_addressed(struct reply_state *reply, struct binding_scope **scope, struct group *group); static isc_result_t reply_process_send_addr(struct reply_state *reply, struct iaddr *addr); static struct iasubopt *lease_compare(struct iasubopt *alpha, struct iasubopt *beta); static isc_result_t reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia_pd); static struct group *find_group_by_prefix(struct reply_state *reply); static isc_result_t reply_process_prefix(struct reply_state *reply, struct option_cache *pref); static isc_boolean_t prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref); static isc_result_t find_client_prefix(struct reply_state *reply); static isc_result_t reply_process_try_prefix(struct reply_state *reply, struct iaddrcidrnet *pref); static isc_result_t reply_process_is_prefixed(struct reply_state *reply, struct binding_scope **scope, struct group *group); static isc_result_t reply_process_send_prefix(struct reply_state *reply, struct iaddrcidrnet *pref); static struct iasubopt *prefix_compare(struct reply_state *reply, struct iasubopt *alpha, struct iasubopt *beta); static void schedule_lease_timeout_reply(struct reply_state *reply); static int eval_prefix_mode(int thislen, int preflen, int prefix_mode); static isc_result_t pick_v6_prefix_helper(struct reply_state *reply, int prefix_mode); static void unicast_reject(struct data_string *reply_ret, struct packet *packet, const struct data_string *client_id, const struct data_string *server_id); static isc_boolean_t is_unicast_option_defined(struct packet *packet); static isc_result_t shared_network_from_requested_addr (struct shared_network **shared, struct packet* packet); static isc_result_t get_first_ia_addr_val (struct packet* packet, int addr_type, struct iaddr* iaddr); static void set_reply_tee_times(struct reply_state* reply, unsigned ia_cursor); static const char *iasubopt_plen_str(struct iasubopt *lease); static int release_on_roam(struct reply_state *reply); static int reuse_lease6(struct reply_state *reply, struct iasubopt *lease); static void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease, time_t age, int threshold); static void write_to_packet(struct reply_state *reply, unsigned ia_cursor); static const char *iasubopt_plen_str(struct iasubopt *lease); #ifdef NSUPDATE static void ddns_update_static6(struct reply_state* reply); #endif #ifdef DHCP4o6 /* * \brief Omapi I/O handler * * The inter-process communication receive handler. * Get the message, put it into the raw data_string * and call \ref send_dhcpv4_response() (DHCPv6 side) or * \ref recv_dhcpv4_query() (DHCPv4 side) * * \param h the OMAPI object * \return a result for I/O success or error (used by the I/O subsystem) */ isc_result_t dhcpv4o6_handler(omapi_object_t *h) { char buf[65536]; struct data_string raw; int cc; if (h->type != dhcp4o6_type) return DHCP_R_INVALIDARG; cc = recv(dhcp4o6_fd, buf, sizeof(buf), 0); if (cc < DHCP_FIXED_NON_UDP + offset_data4o6) return ISC_R_UNEXPECTED; memset(&raw, 0, sizeof(raw)); if (!buffer_allocate(&raw.buffer, cc, MDL)) { log_error("dhcpv4o6_handler: no memory buffer."); return ISC_R_NOMEMORY; } raw.data = raw.buffer->data; raw.len = cc; memcpy(raw.buffer->data, buf, cc); if (local_family == AF_INET6) { send_dhcpv4_response(&raw); } else { recv_dhcpv4_query(&raw); } data_string_forget(&raw, MDL); return ISC_R_SUCCESS; } /* * \brief Send the DHCPv4-response back to the DHCPv6 side * (DHCPv6 server function) * * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-response message * * \param raw the IPC message content */ static void send_dhcpv4_response(struct data_string *raw) { struct interface_info *ip; char name[16 + 1]; struct sockaddr_in6 to_addr; char pbuf[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255")]; struct udp_data4o6 udp_data; int send_ret; memset(name, 0, sizeof(name)); memcpy(name, raw->data, 16); for (ip = interfaces; ip != NULL; ip = ip->next) { if (!strcmp(name, ip->name)) break; } if (ip == NULL) { log_error("send_dhcpv4_response: can't find interface %s.", name); return; } memset(&to_addr, 0, sizeof(to_addr)); to_addr.sin6_family = AF_INET6; memcpy(&to_addr.sin6_addr, raw->data + 16, 16); memset(&udp_data, 0, sizeof(udp_data)); memcpy(&udp_data, raw->data + 32, 4); if ((raw->data[36] == DHCPV6_RELAY_FORW) || (raw->data[36] == DHCPV6_RELAY_REPL)) { if (udp_data.rsp_opt_exist) { to_addr.sin6_port = udp_data.src_port; } else { to_addr.sin6_port = local_port; } } else { to_addr.sin6_port = remote_port; } log_info("send_dhcpv4_response(): sending %s on %s to %s port %d", dhcpv6_type_names[raw->data[36]], name, inet_ntop(AF_INET6, raw->data + 16, pbuf, sizeof(pbuf)), ntohs(to_addr.sin6_port)); send_ret = send_packet6(ip, raw->data + 36, raw->len - 36, &to_addr); if (send_ret < 0) { log_error("send_dhcpv4_response: send_packet6(): %m"); } else if (send_ret != raw->len - 36) { log_error("send_dhcpv4_response: send_packet6() " "sent %d of %d bytes", send_ret, raw->len - 36); } } #endif /* DHCP4o6 */ /* * Schedule lease timeouts for all of the iasubopts in the reply. * This is currently used to schedule timeouts for soft leases. */ static void schedule_lease_timeout_reply(struct reply_state *reply) { struct iasubopt *tmp; int i; /* sanity check the reply */ if ((reply == NULL) || (reply->ia == NULL) || (reply->ia->iasubopt == NULL)) return; /* walk through the list, scheduling as we go */ for (i = 0 ; i < reply->ia->num_iasubopt ; i++) { tmp = reply->ia->iasubopt[i]; schedule_lease_timeout(tmp->ipv6_pool); } } /* * 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, 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 DHCP_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 DHCP_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)", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), print_hex_1(client_id->len, client_id->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: data_string_forget(&data, MDL); if (!ret_val) { 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) { data_string_forget(server_id, MDL); 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; struct data_string client_id; char client_id_str[80]; /* print_hex_1() uses maximum 60 characters, plus a few more for extra information */ ret_val = 0; memset(server_id, 0, sizeof(*server_id)); memset(&client_id, 0, sizeof(client_id)); /* * Make a string that we can print out to give more * information about the client if we need to. * * By RFC 3315, Section 18.1.5 clients SHOULD have a * client-id on an Information-request packet, but it * is not strictly necessary. */ if (get_client_id(packet, &client_id) == ISC_R_SUCCESS) { snprintf(client_id_str, sizeof(client_id_str), " (CLIENTID %s)", print_hex_1(client_id.len, client_id.data, 60)); data_string_forget(&client_id, MDL); } else { client_id_str[0] = '\0'; } /* * Required by RFC 3315, section 15. */ if (packet->unicast) { log_debug("Discarding %s from %s; packet sent unicast%s", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), client_id_str); 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%s", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), client_id_str); 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%s", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), client_id_str); goto exit; } oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD); if (oc != NULL) { log_debug("Discarding %s from %s; " "IA_PD option present%s", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), client_id_str); 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%s", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr), client_id_str); 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)%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), client_id_str); goto exit; } } /* looks good */ ret_val = 1; exit: if (!ret_val) { 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, D6O_PREFERENCE, 0 }; static const int required_opts_solicit[] = { D6O_CLIENTID, D6O_SERVERID, D6O_IA_NA, D6O_IA_TA, D6O_IA_PD, D6O_RAPID_COMMIT, D6O_STATUS_CODE, D6O_RECONF_ACCEPT, D6O_PREFERENCE, 0 }; static const int required_opts_agent[] = { D6O_INTERFACE_ID, #if defined(RELAY_PORT) D6O_RELAY_SOURCE_PORT, #endif D6O_RELAY_MSG, 0 }; static const int required_opts_IA[] = { D6O_IAADDR, D6O_STATUS_CODE, 0 }; static const int required_opts_IA_PD[] = { D6O_IAPREFIX, D6O_STATUS_CODE, 0 }; static const int required_opts_STATUS_CODE[] = { D6O_STATUS_CODE, 0 }; #ifdef DHCP4o6 static const int required_opts_4o6[] = { D6O_DHCPV4_MSG, 0 }; #endif static const int unicast_reject_opts[] = { D6O_CLIENTID, D6O_SERVERID, D6O_STATUS_CODE, 0 }; /* * Extracts from packet contents an IA_* option, storing the IA structure * in its entirety in enc_opt_data, and storing any decoded DHCPv6 options * in enc_opt_state for later lookup and evaluation. The 'offset' indicates * where in the IA_* the DHCPv6 options commence. */ 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, int offset) { /* * 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 < offset) { 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 + offset, enc_opt_data->len - offset, &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, (unsigned 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; } void check_pool6_threshold(struct reply_state *reply, struct iasubopt *lease) { struct ipv6_pond *pond; isc_uint64_t used, count, high_threshold; int poolhigh = 0, poollow = 0; char *shared_name = "no name"; char tmp_addr[INET6_ADDRSTRLEN]; if ((lease->ipv6_pool == NULL) || (lease->ipv6_pool->ipv6_pond == NULL)) return; pond = lease->ipv6_pool->ipv6_pond; /* If the address range is too large to track, just skip all this. */ if (pond->jumbo_range == 1) { return; } count = pond->num_total; used = pond->num_active; /* get network name for logging */ if ((pond->shared_network != NULL) && (pond->shared_network->name != NULL)) { shared_name = pond->shared_network->name; } /* The logged flag indicates if we have already crossed the high * threshold and emitted a log message. If it is set we check to * see if we have re-crossed the low threshold and need to reset * things. When we cross the high threshold we determine what * the low threshold is and save it into the low_threshold value. * When we cross that threshold we reset the logged flag and * the low_threshold to 0 which allows the high threshold message * to be emitted once again. * if we haven't recrossed the boundry we don't need to do anything. */ if (pond->logged !=0) { if (used <= pond->low_threshold) { pond->low_threshold = 0; pond->logged = 0; log_error("Pool threshold reset - shared subnet: %s; " "address: %s; low threshold %llu/%llu.", shared_name, inet_ntop(AF_INET6, &lease->addr, tmp_addr, sizeof(tmp_addr)), (long long unsigned)(used), (long long unsigned)(count)); } return; } /* find the high threshold */ if (get_option_int(&poolhigh, &server_universe, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, reply->opt_state, &lease->scope, SV_LOG_THRESHOLD_HIGH, MDL) == 0) { /* no threshold bail out */ return; } /* We do have a threshold for this pool, see if its valid */ if ((poolhigh <= 0) || (poolhigh > 100)) { /* not valid */ return; } /* we have a valid value, have we exceeded it */ high_threshold = FIND_POND6_PERCENT(count, poolhigh); if (used < high_threshold) { /* nope, no more to do */ return; } /* we've exceeded it, output a message */ log_error("Pool threshold exceeded - shared subnet: %s; " "address: %s; high threshold %d%% %llu/%llu.", shared_name, inet_ntop(AF_INET6, &lease->addr, tmp_addr, sizeof(tmp_addr)), poolhigh, (long long unsigned)(used), (long long unsigned)(count)); /* handle the low threshold now, if we don't * have one we default to 0. */ if ((get_option_int(&poollow, &server_universe, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, reply->opt_state, &lease->scope, SV_LOG_THRESHOLD_LOW, MDL) == 0) || (poollow > 100)) { poollow = 0; } /* * If the low theshold is higher than the high threshold we continue to log * If it isn't then we set the flag saying we already logged and determine * what the reset threshold is. */ if (poollow < poolhigh) { pond->logged = 1; pond->low_threshold = FIND_POND6_PERCENT(count, poollow); } } /* * 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; const unsigned char *server_id_data; int server_id_len; /* * 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, NULL); /* * 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) { /* Rapid-commit in action. */ reply->msg_type = DHCPV6_REPLY; } else { /* Don't want a rapid-commit in advertise. */ delete_option(&dhcpv6_universe, *opt_state, D6O_RAPID_COMMIT); } } } else { reply->msg_type = DHCPV6_REPLY; /* Delete the rapid-commit from the sent options. */ oc = lookup_option(&dhcpv6_universe, *opt_state, D6O_RAPID_COMMIT); if (oc != NULL) { delete_option(&dhcpv6_universe, *opt_state, D6O_RAPID_COMMIT); } } /* * Use the client's transaction identifier for the reply. */ memcpy(reply->transaction_id, packet->dhcpv6_transaction_id, sizeof(reply->transaction_id)); /* * 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 = server_duid.data; server_id_len = server_duid.len; } else { server_id_data = server_id->data; server_id_len = server_id->len; } if (!save_option_buffer(&dhcpv6_universe, *opt_state, NULL, (unsigned char *)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, (unsigned char *)"", 0, D6O_RECONF_ACCEPT, 0)) { log_error("start_reply: " "error saving RECONF_ACCEPT option."); option_state_dereference(opt_state, MDL); return 0; } } 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 iasubopt **addr, struct ipv6_pool *pool, const struct data_string *requested_addr) { struct in6_addr tmp_addr; isc_result_t result; if (requested_addr->len < sizeof(tmp_addr)) { return DHCP_R_INVALIDARG; } memcpy(&tmp_addr, requested_addr->data, sizeof(tmp_addr)); if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr)) { return ISC_R_FAILURE; } /* * The address is not covered by this (or possibly any) dynamic * range. */ if (!ipv6_in_pool(&tmp_addr, pool)) { return ISC_R_ADDRNOTAVAIL; } if (lease6_exists(pool, &tmp_addr)) { return ISC_R_ADDRINUSE; } result = iasubopt_allocate(addr, MDL); if (result != ISC_R_SUCCESS) { return result; } (*addr)->addr = tmp_addr; (*addr)->plen = 0; /* Default is soft binding for 2 minutes. */ result = add_lease6(pool, *addr, cur_time + 120); if (result != ISC_R_SUCCESS) { iasubopt_dereference(addr, MDL); } return result; } /*! * * \brief Get an IPv6 address for the client. * * Attempt to find a usable address for the client. We walk through * the ponds checking for permit and deny then through the pools * seeing if they have an available address. * * \param reply = the state structure for the current work on this request * if we create a lease we return it using reply->lease * * \return * ISC_R_SUCCESS = we were able to find an address and are returning a * pointer to the lease * ISC_R_NORESOURCES = there don't appear to be any free addresses. This * is probabalistic. We don't exhaustively try the * address range, instead we hash the duid and if * the address derived from the hash is in use we * hash the address. After a number of failures we * conclude the pool is basically full. */ static isc_result_t pick_v6_address(struct reply_state *reply) { struct ipv6_pool *p = NULL; struct ipv6_pond *pond; int i; int start_pool; unsigned int attempts; char tmp_buf[INET6_ADDRSTRLEN]; struct iasubopt **addr = &reply->lease; isc_uint64_t total = 0; isc_uint64_t active = 0; isc_uint64_t abandoned = 0; int jumbo_range = 0; char *shared_name = (reply->shared->name ? reply->shared->name : "(no name)"); /* * Do a quick walk through of the ponds and pools * to see if we have any NA address pools */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (pond->ipv6_pools == NULL) continue; for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) { if (p->pool_type == D6O_IA_NA) break; } if (p != NULL) break; } /* If we get here and p is NULL we have no useful pools */ if (p == NULL) { log_debug("Unable to pick client address: " "no IPv6 pools on this shared network"); return ISC_R_NORESOURCES; } /* * We have at least one pool that could provide an address * Now we walk through the ponds and pools again and check * to see if the client is permitted and if an address is * available * * Within a given pond 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. */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { isc_result_t result = ISC_R_FAILURE; if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; #ifdef EUI_64 /* If pond is EUI-64 but client duid isn't a valid EUI-64 * id, then skip this pond */ if (pond->use_eui_64 && !valid_eui_64_duid(&reply->ia->iaid_duid, IAID_LEN)) { continue; } #endif start_pool = pond->last_ipv6_pool; i = start_pool; do { p = pond->ipv6_pools[i]; if (p->pool_type == D6O_IA_NA) { #ifdef EUI_64 if (pond->use_eui_64) { result = create_lease6_eui_64(p, addr, &reply->ia->iaid_duid, cur_time + 120); } else #endif { result = create_lease6(p, addr, &attempts, &reply->ia->iaid_duid, cur_time + 120); } if (result == ISC_R_SUCCESS) { /* * Record the pool used (or next one if * there was a collision). */ if (attempts > 1) { i++; if (pond->ipv6_pools[i] == NULL) { i = 0; } } pond->last_ipv6_pool = i; log_debug("Picking pool address %s", inet_ntop(AF_INET6, &((*addr)->addr), tmp_buf, sizeof(tmp_buf))); return (ISC_R_SUCCESS); } } i++; if (pond->ipv6_pools[i] == NULL) { i = 0; } } while (i != start_pool); if (result == ISC_R_NORESOURCES) { jumbo_range += pond->jumbo_range; total += pond->num_total; active += pond->num_active; abandoned += pond->num_abandoned; } } /* * If we failed to pick an IPv6 address from any of the subnets. * Presumably that means we have no addresses for the client. */ if (jumbo_range != 0) { log_debug("Unable to pick client address: " "no addresses available - shared network %s: " " 2^64-1 < total, %llu active, %llu abandoned", shared_name, (long long unsigned)(active - abandoned), (long long unsigned)(abandoned)); } else { log_debug("Unable to pick client address: " "no addresses available - shared network %s: " "%llu total, %llu active, %llu abandoned", shared_name, (long long unsigned)(total), (long long unsigned)(active - abandoned), (long long unsigned)(abandoned)); } return ISC_R_NORESOURCES; } /* * Try to get the IPv6 prefix the client asked for from the * prefix pool. * * pref is the result (should be a pointer to NULL on entry) * pool is the prefix pool to search in * requested_pref is the address the client wants */ static isc_result_t try_client_v6_prefix(struct iasubopt **pref, struct ipv6_pool *pool, const struct data_string *requested_pref) { u_int8_t tmp_plen; struct in6_addr tmp_pref; struct iaddr ia; isc_result_t result; if (requested_pref->len < sizeof(tmp_plen) + sizeof(tmp_pref)) { return DHCP_R_INVALIDARG; } tmp_plen = (int) requested_pref->data[0]; if ((tmp_plen < 3) || (tmp_plen > 128)) { return ISC_R_FAILURE; } memcpy(&tmp_pref, requested_pref->data + 1, sizeof(tmp_pref)); if (IN6_IS_ADDR_UNSPECIFIED(&tmp_pref)) { return ISC_R_FAILURE; } ia.len = 16; memcpy(&ia.iabuf, &tmp_pref, 16); if (!is_cidr_mask_valid(&ia, (int) tmp_plen)) { return ISC_R_FAILURE; } if (!ipv6_in_pool(&tmp_pref, pool) || ((int)tmp_plen != pool->units)) { return ISC_R_ADDRNOTAVAIL; } if (prefix6_exists(pool, &tmp_pref, tmp_plen)) { return ISC_R_ADDRINUSE; } result = iasubopt_allocate(pref, MDL); if (result != ISC_R_SUCCESS) { return result; } (*pref)->addr = tmp_pref; (*pref)->plen = tmp_plen; /* Default is soft binding for 2 minutes. */ result = add_lease6(pool, *pref, cur_time + 120); if (result != ISC_R_SUCCESS) { iasubopt_dereference(pref, MDL); } return result; } /*! * * \brief Get an IPv6 prefix for the client. * * Attempt to find a usable prefix for the client. Based upon the prefix * length mode and the plen supplied by the client (if one), we make one * or more calls to pick_v6_prefix_helper() to find a prefix as follows: * * PLM_IGNORE or client specifies a plen of zero, use the first available * prefix regardless of it's length. * * PLM_PREFER – look for an exact match to client's plen first, if none * found, use the first available prefix of any length * * PLM_EXACT – look for an exact match first, if none found then fail. This * is the default behavior. * * PLM_MAXIMUM - look for an exact match first, then the first available whose * prefix length is less than client's plen, otherwise fail. * * PLM_MINIMUM - look for an exact match first, then the first available whose * prefix length is greater than client's plen, otherwise fail. * * Note that the selection mode is configurable at the global scope only via * prefix-len-mode. * * \param reply = the state structure for the current work on this request * if we create a lease we return it using reply->lease * * \return * ISC_R_SUCCESS = we were able to find an prefix and are returning a * pointer to the lease * ISC_R_NORESOURCES = there don't appear to be any free addresses. This * is probabalistic. We don't exhaustively try the * address range, instead we hash the duid and if * the address derived from the hash is in use we * hash the address. After a number of failures we * conclude the pool is basically full. */ static isc_result_t pick_v6_prefix(struct reply_state *reply) { struct ipv6_pool *p = NULL; struct ipv6_pond *pond; int i; isc_result_t result; /* * Do a quick walk through of the ponds and pools * to see if we have any prefix pools */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (pond->ipv6_pools == NULL) continue; for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) { if (p->pool_type == D6O_IA_PD) break; } if (p != NULL) break; } /* If we get here and p is NULL we have no useful pools */ if (p == NULL) { log_debug("Unable to pick client prefix: " "no IPv6 pools on this shared network"); return ISC_R_NORESOURCES; } if (reply->preflen <= 0) { /* If we didn't get a plen (-1) or client plen is 0, then just * select first available (same as PLM_INGORE) */ result = pick_v6_prefix_helper(reply, PLM_IGNORE); } else { switch (prefix_length_mode) { case PLM_PREFER: /* First we look for an exact match, if not found * then first available */ result = pick_v6_prefix_helper(reply, PLM_EXACT); if (result != ISC_R_SUCCESS) { result = pick_v6_prefix_helper(reply, PLM_IGNORE); } break; case PLM_EXACT: /* Match exactly or fail */ result = pick_v6_prefix_helper(reply, PLM_EXACT); break; case PLM_MINIMUM: case PLM_MAXIMUM: /* First we look for an exact match, if not found * then first available by mode */ result = pick_v6_prefix_helper(reply, PLM_EXACT); if (result != ISC_R_SUCCESS) { result = pick_v6_prefix_helper(reply, prefix_length_mode); } break; default: /* First available */ result = pick_v6_prefix_helper(reply, PLM_IGNORE); break; } } if (result == ISC_R_SUCCESS) { char tmp_buf[INET6_ADDRSTRLEN]; log_debug("Picking pool prefix %s/%u", inet_ntop(AF_INET6, &(reply->lease->addr), tmp_buf, sizeof(tmp_buf)), (unsigned)(reply->lease->plen)); return (ISC_R_SUCCESS); } /* * If we failed to pick an IPv6 prefix * Presumably that means we have no prefixes for the client. */ log_debug("Unable to pick client prefix: no prefixes available"); return ISC_R_NORESOURCES; } /*! * * \brief Get an IPv6 prefix for the client based upon selection mode. * * We walk through the ponds checking for permit and deny. If a pond is * permissable to use, loop through its PD pools checking prefix lengths * against the client plen based on the prefix length mode, looking for * available prefixes. * * \param reply = the state structure for the current work on this request * if we create a lease we return it using reply->lease * \prefix_mode = selection mode to use * * \return * ISC_R_SUCCESS = we were able to find a prefix and are returning a * pointer to the lease * ISC_R_NORESOURCES = there don't appear to be any free addresses. This * is probabalistic. We don't exhaustively try the * address range, instead we hash the duid and if * the address derived from the hash is in use we * hash the address. After a number of failures we * conclude the pool is basically full. */ isc_result_t pick_v6_prefix_helper(struct reply_state *reply, int prefix_mode) { struct ipv6_pool *p = NULL; struct ipv6_pond *pond; int i; unsigned int attempts; struct iasubopt **pref = &reply->lease; for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) { if ((p->pool_type == D6O_IA_PD) && (eval_prefix_mode(p->units, reply->preflen, prefix_mode) == 1) && (create_prefix6(p, pref, &attempts, &reply->ia->iaid_duid, cur_time + 120) == ISC_R_SUCCESS)) { return (ISC_R_SUCCESS); } } } return ISC_R_NORESOURCES; } /*! * * \brief Test a prefix length against another based on prefix length mode * * \param len - prefix length to test * \param preflen - preferred prefix length against which to test * \param prefix_mode - prefix selection mode with which to test * * Note that the case of preferred length of 0 is not short-cut here as it * is assumed to be done at a higher level. * * \return 1 if the given length is usable based upon mode and a preferred * length, 0 if not. */ int eval_prefix_mode(int len, int preflen, int prefix_mode) { int use_it = 1; switch (prefix_mode) { case PLM_EXACT: use_it = (len == preflen); break; case PLM_MINIMUM: /* they asked for a prefix length no "shorter" than preflen */ use_it = (len >= preflen); break; case PLM_MAXIMUM: /* they asked for a prefix length no "longer" than preflen */ use_it = (len <= preflen); break; default: /* otherwise use it */ break; } return (use_it); } /* *! \file server/dhcpv6.c * * \brief construct a reply containing information about a client's lease * * lease_to_client() is called from several messages to construct a * reply that contains all that we know about the client's correct lease * (or projected lease). * * Solicit - "Soft" binding, ignore unknown addresses or bindings, just * send what we "may" give them on a request. * * Request - "Hard" binding, but ignore supplied addresses (just provide what * the client should really use). * * Renew - "Hard" binding, but client-supplied addresses are 'real'. Error * Rebind out any "wrong" addresses the client sends. This means we send * an empty IA_NA with a status code of NoBinding or NotOnLink or * possibly send the address with zeroed lifetimes. * * Information-Request - No binding. * * The basic structure is to traverse the client-supplied data first, and * validate and echo back any contents that can be. If the client-supplied * data does not error out (on renew/rebind as above), but we did not send * any addresses, attempt to allocate one. * * At the end of the this function we call commit_leases_timed() to * fsync and rotate the file as necessary. commit_leases_timed() will * check that we have written at least one lease to the file and that * some time has passed before doing any fsync or file rewrite so we * don't bother tracking if we did a write_ia during this function. */ /* TODO: look at client hints for lease times */ 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) { static struct reply_state reply; struct option_cache *oc; struct data_string packet_oro; int i; memset(&packet_oro, 0, sizeof(packet_oro)); /* Locate the client. */ if (shared_network_from_packet6(&reply.shared, packet) != ISC_R_SUCCESS) goto exit; /* * Initialize the reply. */ packet_reference(&reply.packet, packet, MDL); data_string_copy(&reply.client_id, client_id, MDL); if (!start_reply(packet, client_id, server_id, &reply.opt_state, &reply.buf.reply)) goto exit; /* Set the write cursor to just past the reply header. */ reply.cursor = REPLY_OPTIONS_INDEX; /* * 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; } } /* * Find a host record that matches the packet, if any, and is * valid for the shared network the client is on. */ if (find_hosts6(&reply.host, packet, client_id, MDL)) { packet->known = 1; seek_shared_host(&reply.host, reply.shared); } /* Process the client supplied IA's onto the reply buffer. */ reply.ia_count = 0; oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA); for (; oc != NULL ; oc = oc->next) { isc_result_t status; /* Start counting resources (addresses) offered. */ reply.client_resources = 0; reply.resources_included = ISC_FALSE; status = reply_process_ia_na(&reply, oc); /* * We continue to try other IA's whether we can address * this one or not. Any other result is an immediate fail. */ if ((status != ISC_R_SUCCESS) && (status != ISC_R_NORESOURCES)) goto exit; } oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA); for (; oc != NULL ; oc = oc->next) { isc_result_t status; /* Start counting resources (addresses) offered. */ reply.client_resources = 0; reply.resources_included = ISC_FALSE; status = reply_process_ia_ta(&reply, oc); /* * We continue to try other IA's whether we can address * this one or not. Any other result is an immediate fail. */ if ((status != ISC_R_SUCCESS) && (status != ISC_R_NORESOURCES)) goto exit; } /* Same for IA_PD's. */ reply.pd_count = 0; oc = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD); for (; oc != NULL ; oc = oc->next) { isc_result_t status; /* Start counting resources (prefixes) offered. */ reply.client_resources = 0; reply.resources_included = ISC_FALSE; status = reply_process_ia_pd(&reply, oc); /* * We continue to try other IA_PD's whether we can address * this one or not. Any other result is an immediate fail. */ if ((status != ISC_R_SUCCESS) && (status != ISC_R_NORESOURCES)) goto exit; } /* * Make no reply if we gave no resources and is not * for Information-Request. */ if ((reply.ia_count == 0) && (reply.pd_count == 0)) { if (reply.packet->dhcpv6_msg_type != DHCPV6_INFORMATION_REQUEST) goto exit; /* * Because we only execute statements on a per-IA basis, * we need to execute statements in any non-IA reply to * source configuration. */ execute_statements_in_scope(NULL, reply.packet, NULL, NULL, reply.packet->options, reply.opt_state, &global_scope, reply.shared->group, root_group, NULL); /* Execute statements from class scopes. */ for (i = reply.packet->class_count; i > 0; i--) { execute_statements_in_scope(NULL, reply.packet, NULL, NULL, reply.packet->options, reply.opt_state, &global_scope, reply.packet->classes[i - 1]->group, reply.shared->group, NULL); } /* Bring in any configuration from a host record. */ if (reply.host != NULL) execute_statements_in_scope(NULL, reply.packet, NULL, NULL, reply.packet->options, reply.opt_state, &global_scope, reply.host->group, reply.shared->group, NULL); } /* * RFC3315 section 17.2.2 (Solicit): * * If the server will not assign any addresses to any IAs in a * subsequent Request from the client, the server MUST send an * Advertise message to the client that includes only a Status * Code option with code NoAddrsAvail and a status message for * the user, a Server Identifier option with the server's DUID, * and a Client Identifier option with the client's DUID. * * This has been updated by an errata such that the server * can always send an IA. * * Section 18.2.1 (Request): * * If the server cannot assign any addresses to an IA in the * message from the client, the server MUST include the IA in * the Reply message with no addresses in the IA and a Status * Code option in the IA containing status code NoAddrsAvail. * * Section 18.1.8 (Client Behavior): * * Leave unchanged any information about addresses the client has * recorded in the IA but that were not included in the IA from * the server. * Sends a Renew/Rebind if the IA is not in the Reply message. */ /* * Having stored the client's IA's, store any options that * will fit in the remaining space. */ reply.cursor += store_options6((char *)reply.buf.data + reply.cursor, sizeof(reply.buf) - reply.cursor, reply.opt_state, reply.packet, required_opts_solicit, &packet_oro); /* Return our reply to the caller. */ reply_ret->len = reply.cursor; reply_ret->buffer = NULL; if (!buffer_allocate(&reply_ret->buffer, reply.cursor, MDL)) { log_fatal("No memory to store Reply."); } memcpy(reply_ret->buffer->data, reply.buf.data, reply.cursor); reply_ret->data = reply_ret->buffer->data; /* If appropriate commit and rotate the lease file */ (void) commit_leases_timed(); exit: /* Cleanup. */ if (reply.shared != NULL) shared_network_dereference(&reply.shared, MDL); if (reply.host != NULL) host_dereference(&reply.host, MDL); if (reply.opt_state != NULL) option_state_dereference(&reply.opt_state, MDL); if (reply.packet != NULL) packet_dereference(&reply.packet, MDL); if (reply.client_id.data != NULL) data_string_forget(&reply.client_id, MDL); if (packet_oro.buffer != NULL) data_string_forget(&packet_oro, MDL); reply.renew = reply.rebind = reply.min_prefer = reply.min_valid = 0; reply.cursor = 0; } /* Process a client-supplied IA_NA. This may append options to the tail of * the reply packet being built in the reply_state structure. */ static isc_result_t reply_process_ia_na(struct reply_state *reply, struct option_cache *ia) { isc_result_t status = ISC_R_SUCCESS; u_int32_t iaid; unsigned ia_cursor; struct option_state *packet_ia; struct option_cache *oc; struct data_string ia_data, data; /* Initialize values that will get cleaned up on return. */ packet_ia = NULL; memset(&ia_data, 0, sizeof(ia_data)); memset(&data, 0, sizeof(data)); /* * Note that find_client_address() may set reply->lease. */ /* Make sure there is at least room for the header. */ if ((reply->cursor + IA_NA_OFFSET + 4) > sizeof(reply->buf)) { log_error("reply_process_ia_na: Reply too long for IA."); return ISC_R_NOSPACE; } /* Fetch the IA_NA contents. */ if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet, ia, IA_NA_OFFSET)) { log_error("reply_process_ia_na: error evaluating ia"); status = ISC_R_FAILURE; goto cleanup; } /* Extract IA_NA header contents. */ iaid = getULong(ia_data.data); reply->renew = getULong(ia_data.data + 4); reply->rebind = getULong(ia_data.data + 8); /* Create an IA_NA structure. */ if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data, reply->client_id.len, MDL) != ISC_R_SUCCESS) { log_error("reply_process_ia_na: no memory for ia."); status = ISC_R_NOMEMORY; goto cleanup; } reply->ia->ia_type = D6O_IA_NA; /* Cache pre-existing IA, if any. */ ia_hash_lookup(&reply->old_ia, ia_na_active, (unsigned char *)reply->ia->iaid_duid.data, reply->ia->iaid_duid.len, MDL); /* * Create an option cache to carry the IA_NA option contents, and * execute any user-supplied values into it. */ if (!option_state_allocate(&reply->reply_ia, MDL)) { status = ISC_R_NOMEMORY; goto cleanup; } /* Check & cache the fixed host record. */ if ((reply->host != NULL) && (reply->host->fixed_addr != NULL)) { struct iaddr tmp_addr; if (!evaluate_option_cache(&reply->fixed, NULL, NULL, NULL, NULL, NULL, &global_scope, reply->host->fixed_addr, MDL)) { log_error("reply_process_ia_na: unable to evaluate " "fixed address."); status = ISC_R_FAILURE; goto cleanup; } if (reply->fixed.len < 16) { log_error("reply_process_ia_na: invalid fixed address."); status = DHCP_R_INVALIDARG; goto cleanup; } /* Find the static lease's subnet. */ tmp_addr.len = 16; memcpy(tmp_addr.iabuf, reply->fixed.data, 16); if (find_grouped_subnet(&reply->subnet, reply->shared, tmp_addr, MDL) == 0) log_fatal("Impossible condition at %s:%d.", MDL); reply->static_lease = ISC_TRUE; } else reply->static_lease = ISC_FALSE; /* * Save the cursor position at the start of the IA, so we can * set length and adjust t1/t2 values later. We write a temporary * header out now just in case we decide to adjust the packet * within sub-process functions. */ ia_cursor = reply->cursor; /* Initialize the IA_NA header. First the code. */ putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_NA); reply->cursor += 2; /* Then option length. */ putUShort(reply->buf.data + reply->cursor, 0x0Cu); reply->cursor += 2; /* Then IA_NA header contents; IAID. */ putULong(reply->buf.data + reply->cursor, iaid); reply->cursor += 4; /* We store the client's t1 for now, and may over-ride it later. */ putULong(reply->buf.data + reply->cursor, reply->renew); reply->cursor += 4; /* We store the client's t2 for now, and may over-ride it later. */ putULong(reply->buf.data + reply->cursor, reply->rebind); reply->cursor += 4; /* * For each address in this IA_NA, decide what to do about it. * * Guidelines: * * The client leaves unchanged any information about addresses * it has recorded but are not included ("cancel/break" below). * A not included IA ("cleanup" below) could give a Renew/Rebind. */ oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR); reply->min_valid = reply->min_prefer = INFINITE_TIME; reply->client_valid = reply->client_prefer = 0; for (; oc != NULL ; oc = oc->next) { status = reply_process_addr(reply, oc); /* * Canceled means we did not allocate addresses to the * client, but we're "done" with this IA - we set a status * code. So transmit this reply, e.g., move on to the next * IA. */ if (status == ISC_R_CANCELED) break; if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE) && (status != ISC_R_ADDRNOTAVAIL)) goto cleanup; } reply->ia_count++; /* * If we fell through the above and never gave the client * an address, give it one now. */ if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) { status = find_client_address(reply); if (status == ISC_R_NORESOURCES) { switch (reply->packet->dhcpv6_msg_type) { case DHCPV6_SOLICIT: /* * No address for any IA is handled * by the caller. */ /* FALL THROUGH */ case DHCPV6_REQUEST: /* Section 18.2.1 (Request): * * If the server cannot assign any addresses to * an IA in the message from the client, the * server MUST include the IA in the Reply * message with no addresses in the IA and a * Status Code option in the IA containing * status code NoAddrsAvail. */ option_state_dereference(&reply->reply_ia, MDL); if (!option_state_allocate(&reply->reply_ia, MDL)) { log_error("reply_process_ia_na: No " "memory for option state " "wipe."); status = ISC_R_NOMEMORY; goto cleanup; } if (!set_status_code(STATUS_NoAddrsAvail, "No addresses available " "for this interface.", reply->reply_ia)) { log_error("reply_process_ia_na: Unable " "to set NoAddrsAvail status " "code."); status = ISC_R_FAILURE; goto cleanup; } status = ISC_R_SUCCESS; break; default: /* * RFC 3315 does not tell us to emit a status * code in this condition, or anything else. * * If we included non-allocated addresses * (zeroed lifetimes) in an IA, then the client * will deconfigure them. * * So we want to include the IA even if we * can't give it a new address if it includes * zeroed lifetime addresses. * * We don't want to include the IA if we * provide zero addresses including zeroed * lifetimes. */ if (reply->resources_included) status = ISC_R_SUCCESS; else goto cleanup; break; } } if (status != ISC_R_SUCCESS) goto cleanup; } /* * yes, goto's aren't the best but we also want to avoid extra * indents */ if (status == ISC_R_CANCELED) { /* We're replying with a status code so we still need to * write it out in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); goto cleanup; } /* * Handle static leases, we always log stuff and if it's * a hard binding we run any commit statements that we have */ if (reply->static_lease) { char tmp_addr[INET6_ADDRSTRLEN]; log_info("%s NA: address %s to client with duid %s iaid = %d " "static", dhcpv6_type_names[reply->buf.reply.msg_type], inet_ntop(AF_INET6, reply->fixed.data, tmp_addr, sizeof(tmp_addr)), print_hex_1(reply->client_id.len, reply->client_id.data, 60), iaid); /* Write the lease out in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); #ifdef NSUPDATE /* Performs DDNS updates if we're configured to do them */ ddns_update_static6(reply); #endif if ((reply->buf.reply.msg_type == DHCPV6_REPLY) && (reply->on_star.on_commit != NULL)) { execute_statements(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, NULL, reply->on_star.on_commit, NULL); executable_statement_dereference (&reply->on_star.on_commit, MDL); } goto cleanup; } /* * If we have any addresses log what we are doing. */ if (reply->ia->num_iasubopt != 0) { struct iasubopt *tmp; int i; char tmp_addr[INET6_ADDRSTRLEN]; for (i = 0 ; i < reply->ia->num_iasubopt ; i++) { tmp = reply->ia->iasubopt[i]; log_info("%s NA: address %s to client with duid %s " "iaid = %d valid for %u seconds", dhcpv6_type_names[reply->buf.reply.msg_type], inet_ntop(AF_INET6, &tmp->addr, tmp_addr, sizeof(tmp_addr)), print_hex_1(reply->client_id.len, reply->client_id.data, 60), iaid, tmp->valid); } } /* * If this is not a 'soft' binding, consume the new changes into * the database (if any have been attached to the ia_na). * * Loop through the assigned dynamic addresses, referencing the * leases onto this IA_NA rather than any old ones, and updating * pool timers for each (if any). * * Note that we must do ddns_updates() before we test for lease * reuse (so we'll know if DNS entries are different). To ensure * we don't break any configs, we run on_commit statements before * we do ddns_updates() just in case the former affects the later. * This is symetrical with v4 logic. We always run on_commit and * ddns_udpates() whether a lease is reused or renewed. */ if ((reply->ia->num_iasubopt != 0) && (reply->buf.reply.msg_type == DHCPV6_REPLY)) { int must_commit = 0; struct iasubopt *tmp; struct data_string *ia_id; int i; for (i = 0 ; i < reply->ia->num_iasubopt ; i++) { tmp = reply->ia->iasubopt[i]; if (tmp->ia != NULL) { ia_dereference(&tmp->ia, MDL); } ia_reference(&tmp->ia, reply->ia, MDL); /* If we have anything to do on commit do it now */ if (tmp->on_star.on_commit != NULL) { execute_statements(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &tmp->scope, tmp->on_star.on_commit, &tmp->on_star); executable_statement_dereference (&tmp->on_star.on_commit, MDL); } #if defined (NSUPDATE) /* Perform ddns updates */ oc = lookup_option(&server_universe, reply->opt_state, SV_DDNS_UPDATES); if ((oc == NULL) || evaluate_boolean_option_cache(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &tmp->scope, oc, MDL)) { ddns_updates(reply->packet, NULL, NULL, tmp, NULL, reply->opt_state); } #endif if (!reuse_lease6(reply, tmp)) { /* Commit 'hard' bindings. */ must_commit = 1; renew_lease6(tmp->ipv6_pool, tmp); schedule_lease_timeout(tmp->ipv6_pool); /* Do our threshold check. */ check_pool6_threshold(reply, tmp); } } /* write the IA_NA in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); /* Remove any old ia from the hash. */ if (reply->old_ia != NULL) { if (!release_on_roam(reply)) { ia_id = &reply->old_ia->iaid_duid; ia_hash_delete(ia_na_active, (unsigned char *)ia_id->data, ia_id->len, MDL); } ia_dereference(&reply->old_ia, MDL); } /* Put new ia into the hash. */ reply->ia->cltt = cur_time; ia_id = &reply->ia->iaid_duid; ia_hash_add(ia_na_active, (unsigned char *)ia_id->data, ia_id->len, reply->ia, MDL); /* If we couldn't reuse all of the iasubopts, we * must update udpate the lease db */ if (must_commit) { write_ia(reply->ia); } } else { /* write the IA_NA in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); schedule_lease_timeout_reply(reply); } cleanup: if (packet_ia != NULL) option_state_dereference(&packet_ia, MDL); if (reply->reply_ia != NULL) option_state_dereference(&reply->reply_ia, MDL); if (ia_data.data != NULL) data_string_forget(&ia_data, MDL); if (data.data != NULL) data_string_forget(&data, MDL); if (reply->ia != NULL) ia_dereference(&reply->ia, MDL); if (reply->old_ia != NULL) ia_dereference(&reply->old_ia, MDL); if (reply->lease != NULL) iasubopt_dereference(&reply->lease, MDL); if (reply->fixed.data != NULL) data_string_forget(&reply->fixed, MDL); if (reply->subnet != NULL) subnet_dereference(&reply->subnet, MDL); if (reply->on_star.on_expiry != NULL) executable_statement_dereference (&reply->on_star.on_expiry, MDL); if (reply->on_star.on_release != NULL) executable_statement_dereference (&reply->on_star.on_release, MDL); /* * ISC_R_CANCELED is a status code used by the addr processing to * indicate we're replying with a status code. This is still a * success at higher layers. */ return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status); } /* * Writes the populated IA_xx in wire format to the reply buffer */ void write_to_packet(struct reply_state *reply, unsigned ia_cursor) { reply->cursor += store_options6((char *)reply->buf.data + reply->cursor, sizeof(reply->buf) - reply->cursor, reply->reply_ia, reply->packet, (reply->ia->ia_type != D6O_IA_PD ? required_opts_IA : required_opts_IA_PD), NULL); /* Reset the length of this IA to match what was just written. */ putUShort(reply->buf.data + ia_cursor + 2, reply->cursor - (ia_cursor + 4)); if (reply->ia->ia_type != D6O_IA_TA) { /* Calculate T1/T2 and stuff them in the reply */ set_reply_tee_times(reply, ia_cursor); } } /* * Process an IAADDR within a given IA_xA, storing any IAADDR reply contents * into the reply's current ia-scoped option cache. Returns ISC_R_CANCELED * in the event we are replying with a status code and do not wish to process * more IAADDRs within this IA. */ static isc_result_t reply_process_addr(struct reply_state *reply, struct option_cache *addr) { u_int32_t pref_life, valid_life; struct binding_scope **scope; struct group *group; struct subnet *subnet; struct iaddr tmp_addr; struct option_cache *oc; struct data_string iaaddr, data; isc_result_t status = ISC_R_SUCCESS; #ifdef EUI_64 int invalid_for_eui_64 = 0; #endif /* Initializes values that will be cleaned up. */ memset(&iaaddr, 0, sizeof(iaaddr)); memset(&data, 0, sizeof(data)); /* Note that reply->lease may be set by address_is_owned() */ /* * There is no point trying to process an incoming address if there * is no room for an outgoing address. */ if ((reply->cursor + 28) > sizeof(reply->buf)) { log_error("reply_process_addr: Out of room for address."); return ISC_R_NOSPACE; } /* Extract this IAADDR option. */ if (!evaluate_option_cache(&iaaddr, reply->packet, NULL, NULL, reply->packet->options, NULL, &global_scope, addr, MDL) || (iaaddr.len < IAADDR_OFFSET)) { log_error("reply_process_addr: error evaluating IAADDR."); status = ISC_R_FAILURE; goto cleanup; } /* The first 16 bytes are the IPv6 address. */ pref_life = getULong(iaaddr.data + 16); valid_life = getULong(iaaddr.data + 20); if ((reply->client_valid == 0) || (reply->client_valid > valid_life)) reply->client_valid = valid_life; if ((reply->client_prefer == 0) || (reply->client_prefer > pref_life)) reply->client_prefer = pref_life; /* * Clients may choose to send :: as an address, with the idea to give * hints about preferred-lifetime or valid-lifetime. */ tmp_addr.len = 16; memset(tmp_addr.iabuf, 0, 16); if (!memcmp(iaaddr.data, tmp_addr.iabuf, 16)) { /* Status remains success; we just ignore this one. */ goto cleanup; } /* tmp_addr len remains 16 */ memcpy(tmp_addr.iabuf, iaaddr.data, 16); /* * Verify that this address is on the client's network. */ for (subnet = reply->shared->subnets ; subnet != NULL ; subnet = subnet->next_sibling) { if (addr_eq(subnet_number(tmp_addr, subnet->netmask), subnet->net)) break; } #ifdef EUI_64 if (subnet) { /* If the requested address falls into an EUI-64 pool, then * we need to verify if it has EUI-64 duid AND the requested * address is correct for that duid. If not we treat it just * like an not-on-link request. */ struct ipv6_pool* pool = NULL; struct in6_addr* addr = (struct in6_addr*)(iaaddr.data); if ((find_ipv6_pool(&pool, D6O_IA_NA, addr) == ISC_R_SUCCESS) && (pool->ipv6_pond->use_eui_64) && (!valid_for_eui_64_pool(pool, &reply->client_id, 0, addr))) { log_debug ("Requested address: %s," " not valid for EUI-64 pool", pin6_addr(addr)); invalid_for_eui_64 = 1; } } #endif /* Address not found on shared network. */ #ifdef EUI_64 if ((subnet == NULL) || invalid_for_eui_64) { #else if (subnet == NULL) { #endif /* Ignore this address on 'soft' bindings. */ if (reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) { /* disable rapid commit */ reply->buf.reply.msg_type = DHCPV6_ADVERTISE; delete_option(&dhcpv6_universe, reply->opt_state, D6O_RAPID_COMMIT); /* status remains success */ goto cleanup; } /* * RFC3315 section 18.2.1: * * If the server finds that the prefix on one or more IP * addresses in any IA in the message from the client is not * appropriate for the link to which the client is connected, * the server MUST return the IA to the client with a Status * Code option with the value NotOnLink. */ if (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) { /* Rewind the IA_NA to empty. */ option_state_dereference(&reply->reply_ia, MDL); if (!option_state_allocate(&reply->reply_ia, MDL)) { log_error("reply_process_addr: No memory for " "option state wipe."); status = ISC_R_NOMEMORY; goto cleanup; } /* Append a NotOnLink status code. */ if (!set_status_code(STATUS_NotOnLink, "Address not for use on this " "link.", reply->reply_ia)) { log_error("reply_process_addr: Failure " "setting status code."); status = ISC_R_FAILURE; goto cleanup; } /* Fin (no more IAADDRs). */ status = ISC_R_CANCELED; goto cleanup; } /* * RFC3315 sections 18.2.3 and 18.2.4 have identical language: * * If the server finds that any of the addresses are not * appropriate for the link to which the client is attached, * the server returns the address to the client with lifetimes * of 0. */ if ((reply->packet->dhcpv6_msg_type != DHCPV6_RENEW) && (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) { log_error("It is impossible to lease a client that is " "not sending a solicit, request, renew, or " "rebind."); status = ISC_R_FAILURE; goto cleanup; } reply->send_prefer = reply->send_valid = 0; goto send_addr; } /* Verify the address belongs to the client. */ if (!address_is_owned(reply, &tmp_addr)) { /* * For solicit and request, any addresses included are * 'requested' addresses. For rebind, we actually have * no direction on what to do from 3315 section 18.2.4! * So I think the best bet is to try and give it out, and if * we can't, zero lifetimes. */ if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) || (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) || (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) { status = reply_process_try_addr(reply, &tmp_addr); /* * If the address is in use, or isn't in any dynamic * range, continue as normal. If any other error was * found, error out. */ if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE) && (status != ISC_R_ADDRNOTAVAIL)) goto cleanup; /* * If we didn't honor this lease, for solicit and * request we simply omit it from our answer. For * rebind, we send it with zeroed lifetimes. */ if (reply->lease == NULL) { if (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND) { reply->send_prefer = 0; reply->send_valid = 0; goto send_addr; } /* status remains success - ignore */ goto cleanup; } /* * RFC3315 section 18.2.3: * * If the server cannot find a client entry for the IA the * server returns the IA containing no addresses with a Status * Code option set to NoBinding in the Reply message. * * On mismatch we (ab)use this pretending we have not the IA * as soon as we have not an address. */ } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) { /* Rewind the IA_NA to empty. */ option_state_dereference(&reply->reply_ia, MDL); if (!option_state_allocate(&reply->reply_ia, MDL)) { log_error("reply_process_addr: No memory for " "option state wipe."); status = ISC_R_NOMEMORY; goto cleanup; } /* Append a NoBinding status code. */ if (!set_status_code(STATUS_NoBinding, "Address not bound to this " "interface.", reply->reply_ia)) { log_error("reply_process_addr: Unable to " "attach status code."); status = ISC_R_FAILURE; goto cleanup; } /* Fin (no more IAADDRs). */ status = ISC_R_CANCELED; goto cleanup; } else { log_error("It is impossible to lease a client that is " "not sending a solicit, request, renew, or " "rebind message."); status = ISC_R_FAILURE; goto cleanup; } } if (reply->static_lease) { if (reply->host == NULL) log_fatal("Impossible condition at %s:%d.", MDL); scope = &global_scope; group = reply->subnet->group; } else { if (reply->lease == NULL) log_fatal("Impossible condition at %s:%d.", MDL); scope = &reply->lease->scope; group = reply->lease->ipv6_pool->ipv6_pond->group; } /* * If client_resources is nonzero, then the reply_process_is_addressed * function has executed configuration state into the reply option * cache. We will use that valid cache to derive configuration for * whether or not to engage in additional addresses, and similar. */ if (reply->client_resources != 0) { unsigned limit = 1; /* * Does this client have "enough" addresses already? Default * to one. Everybody gets one, and one should be enough for * anybody. */ oc = lookup_option(&server_universe, reply->opt_state, SV_LIMIT_ADDRS_PER_IA); if (oc != NULL) { if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { log_error("reply_process_addr: unable to " "evaluate addrs-per-ia value."); status = ISC_R_FAILURE; goto cleanup; } limit = getULong(data.data); data_string_forget(&data, MDL); } /* * If we wish to limit the client to a certain number of * addresses, then omit the address from the reply. */ if (reply->client_resources >= limit) goto cleanup; } status = reply_process_is_addressed(reply, scope, group); if (status != ISC_R_SUCCESS) goto cleanup; send_addr: status = reply_process_send_addr(reply, &tmp_addr); cleanup: if (iaaddr.data != NULL) data_string_forget(&iaaddr, MDL); if (data.data != NULL) data_string_forget(&data, MDL); if (reply->lease != NULL) iasubopt_dereference(&reply->lease, MDL); return status; } /* * Verify the address belongs to the client. If we've got a host * record with a fixed address, it has to be the assigned address * (fault out all else). Otherwise it's a dynamic address, so lookup * that address and make sure it belongs to this DUID:IAID pair. */ static isc_boolean_t address_is_owned(struct reply_state *reply, struct iaddr *addr) { int i; struct ipv6_pond *pond; /* * This faults out addresses that don't match fixed addresses. */ if (reply->static_lease) { if (reply->fixed.data == NULL) log_fatal("Impossible condition at %s:%d.", MDL); if (memcmp(addr->iabuf, reply->fixed.data, 16) == 0) return (ISC_TRUE); return (ISC_FALSE); } if ((reply->old_ia == NULL) || (reply->old_ia->num_iasubopt == 0)) return (ISC_FALSE); for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) { struct iasubopt *tmp; tmp = reply->old_ia->iasubopt[i]; if (memcmp(addr->iabuf, &tmp->addr, 16) == 0) { if (lease6_usable(tmp) == ISC_FALSE) { return (ISC_FALSE); } pond = tmp->ipv6_pool->ipv6_pond; if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) return (ISC_FALSE); iasubopt_reference(&reply->lease, tmp, MDL); return (ISC_TRUE); } } return (ISC_FALSE); } /* Process a client-supplied IA_TA. This may append options to the tail of * the reply packet being built in the reply_state structure. */ static isc_result_t reply_process_ia_ta(struct reply_state *reply, struct option_cache *ia) { isc_result_t status = ISC_R_SUCCESS; u_int32_t iaid; unsigned ia_cursor; struct option_state *packet_ia; struct option_cache *oc; struct data_string ia_data, data; struct data_string iaaddr; u_int32_t pref_life, valid_life; struct iaddr tmp_addr; /* Initialize values that will get cleaned up on return. */ packet_ia = NULL; memset(&ia_data, 0, sizeof(ia_data)); memset(&data, 0, sizeof(data)); memset(&iaaddr, 0, sizeof(iaaddr)); /* Make sure there is at least room for the header. */ if ((reply->cursor + IA_TA_OFFSET + 4) > sizeof(reply->buf)) { log_error("reply_process_ia_ta: Reply too long for IA."); return ISC_R_NOSPACE; } /* Fetch the IA_TA contents. */ if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet, ia, IA_TA_OFFSET)) { log_error("reply_process_ia_ta: error evaluating ia"); status = ISC_R_FAILURE; goto cleanup; } /* Extract IA_TA header contents. */ iaid = getULong(ia_data.data); /* Create an IA_TA structure. */ if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data, reply->client_id.len, MDL) != ISC_R_SUCCESS) { log_error("reply_process_ia_ta: no memory for ia."); status = ISC_R_NOMEMORY; goto cleanup; } reply->ia->ia_type = D6O_IA_TA; /* Cache pre-existing IA, if any. */ ia_hash_lookup(&reply->old_ia, ia_ta_active, (unsigned char *)reply->ia->iaid_duid.data, reply->ia->iaid_duid.len, MDL); /* * Create an option cache to carry the IA_TA option contents, and * execute any user-supplied values into it. */ if (!option_state_allocate(&reply->reply_ia, MDL)) { status = ISC_R_NOMEMORY; goto cleanup; } /* * Temporary leases are dynamic by definition. */ reply->static_lease = ISC_FALSE; /* * Save the cursor position at the start of the IA, so we can * set length later. We write a temporary * header out now just in case we decide to adjust the packet * within sub-process functions. */ ia_cursor = reply->cursor; /* Initialize the IA_TA header. First the code. */ putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_TA); reply->cursor += 2; /* Then option length. */ putUShort(reply->buf.data + reply->cursor, 0x04u); reply->cursor += 2; /* Then IA_TA header contents; IAID. */ putULong(reply->buf.data + reply->cursor, iaid); reply->cursor += 4; /* * Deal with an IAADDR for lifetimes. * For all or none, process IAADDRs as hints. */ reply->min_valid = reply->min_prefer = INFINITE_TIME; reply->client_valid = reply->client_prefer = 0; oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAADDR); for (; oc != NULL; oc = oc->next) { memset(&iaaddr, 0, sizeof(iaaddr)); if (!evaluate_option_cache(&iaaddr, reply->packet, NULL, NULL, reply->packet->options, NULL, &global_scope, oc, MDL) || (iaaddr.len < IAADDR_OFFSET)) { log_error("reply_process_ia_ta: error " "evaluating IAADDR."); status = ISC_R_FAILURE; goto cleanup; } /* The first 16 bytes are the IPv6 address. */ pref_life = getULong(iaaddr.data + 16); valid_life = getULong(iaaddr.data + 20); if ((reply->client_valid == 0) || (reply->client_valid > valid_life)) reply->client_valid = valid_life; if ((reply->client_prefer == 0) || (reply->client_prefer > pref_life)) reply->client_prefer = pref_life; /* Nothing more if something has failed. */ if (status == ISC_R_CANCELED) continue; tmp_addr.len = 16; memcpy(tmp_addr.iabuf, iaaddr.data, 16); if (!temporary_is_available(reply, &tmp_addr)) goto bad_temp; status = reply_process_is_addressed(reply, &reply->lease->scope, reply->lease->ipv6_pool->ipv6_pond->group); if (status != ISC_R_SUCCESS) goto bad_temp; status = reply_process_send_addr(reply, &tmp_addr); if (status != ISC_R_SUCCESS) goto bad_temp; if (reply->lease != NULL) iasubopt_dereference(&reply->lease, MDL); continue; bad_temp: /* Rewind the IA_TA to empty. */ option_state_dereference(&reply->reply_ia, MDL); if (!option_state_allocate(&reply->reply_ia, MDL)) { status = ISC_R_NOMEMORY; goto cleanup; } status = ISC_R_CANCELED; reply->client_resources = 0; reply->resources_included = ISC_FALSE; if (reply->lease != NULL) iasubopt_dereference(&reply->lease, MDL); } reply->ia_count++; /* * Give the client temporary addresses. */ if (reply->client_resources != 0) goto store; status = find_client_temporaries(reply); if (status == ISC_R_NORESOURCES) { switch (reply->packet->dhcpv6_msg_type) { case DHCPV6_SOLICIT: /* * No address for any IA is handled * by the caller. */ /* FALL THROUGH */ case DHCPV6_REQUEST: /* Section 18.2.1 (Request): * * If the server cannot assign any addresses to * an IA in the message from the client, the * server MUST include the IA in the Reply * message with no addresses in the IA and a * Status Code option in the IA containing * status code NoAddrsAvail. */ option_state_dereference(&reply->reply_ia, MDL); if (!option_state_allocate(&reply->reply_ia, MDL)) { log_error("reply_process_ia_ta: No " "memory for option state wipe."); status = ISC_R_NOMEMORY; goto cleanup; } if (!set_status_code(STATUS_NoAddrsAvail, "No addresses available " "for this interface.", reply->reply_ia)) { log_error("reply_process_ia_ta: Unable " "to set NoAddrsAvail status code."); status = ISC_R_FAILURE; goto cleanup; } status = ISC_R_SUCCESS; break; default: /* * We don't want to include the IA if we * provide zero addresses including zeroed * lifetimes. */ if (reply->resources_included) status = ISC_R_SUCCESS; else goto cleanup; break; } } else if (status != ISC_R_SUCCESS) goto cleanup; store: /* * yes, goto's aren't the best but we also want to avoid extra * indents */ if (status == ISC_R_CANCELED) { /* We're replying with a status code so we still need to * write it out in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); goto cleanup; } /* * If we have any addresses log what we are doing. */ if (reply->ia->num_iasubopt != 0) { struct iasubopt *tmp; int i; char tmp_addr[INET6_ADDRSTRLEN]; for (i = 0 ; i < reply->ia->num_iasubopt ; i++) { tmp = reply->ia->iasubopt[i]; log_info("%s TA: address %s to client with duid %s " "iaid = %d valid for %u seconds", dhcpv6_type_names[reply->buf.reply.msg_type], inet_ntop(AF_INET6, &tmp->addr, tmp_addr, sizeof(tmp_addr)), print_hex_1(reply->client_id.len, reply->client_id.data, 60), iaid, tmp->valid); } } /* * For hard bindings we consume the new changes into * the database (if any have been attached to the ia_ta). * * Loop through the assigned dynamic addresses, referencing the * leases onto this IA_TA rather than any old ones, and updating * pool timers for each (if any). */ if ((reply->ia->num_iasubopt != 0) && (reply->buf.reply.msg_type == DHCPV6_REPLY)) { int must_commit = 0; struct iasubopt *tmp; struct data_string *ia_id; int i; for (i = 0 ; i < reply->ia->num_iasubopt ; i++) { tmp = reply->ia->iasubopt[i]; if (tmp->ia != NULL) ia_dereference(&tmp->ia, MDL); ia_reference(&tmp->ia, reply->ia, MDL); /* If we have anything to do on commit do it now */ if (tmp->on_star.on_commit != NULL) { execute_statements(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &tmp->scope, tmp->on_star.on_commit, &tmp->on_star); executable_statement_dereference (&tmp->on_star.on_commit, MDL); } #if defined (NSUPDATE) /* * Perform ddns updates. */ oc = lookup_option(&server_universe, reply->opt_state, SV_DDNS_UPDATES); if ((oc == NULL) || evaluate_boolean_option_cache(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &tmp->scope, oc, MDL)) { ddns_updates(reply->packet, NULL, NULL, tmp, NULL, reply->opt_state); } #endif if (!reuse_lease6(reply, tmp)) { /* Commit 'hard' bindings. */ must_commit = 1; renew_lease6(tmp->ipv6_pool, tmp); schedule_lease_timeout(tmp->ipv6_pool); /* Do our threshold check. */ check_pool6_threshold(reply, tmp); } } /* write the IA_TA in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); /* Remove any old ia from the hash. */ if (reply->old_ia != NULL) { if (!release_on_roam(reply)) { ia_id = &reply->old_ia->iaid_duid; ia_hash_delete(ia_ta_active, (unsigned char *)ia_id->data, ia_id->len, MDL); } ia_dereference(&reply->old_ia, MDL); } /* Put new ia into the hash. */ reply->ia->cltt = cur_time; ia_id = &reply->ia->iaid_duid; ia_hash_add(ia_ta_active, (unsigned char *)ia_id->data, ia_id->len, reply->ia, MDL); /* If we couldn't reuse all of the iasubopts, we * must update udpate the lease db */ if (must_commit) { write_ia(reply->ia); } } else { /* write the IA_TA in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); schedule_lease_timeout_reply(reply); } cleanup: if (packet_ia != NULL) option_state_dereference(&packet_ia, MDL); if (iaaddr.data != NULL) data_string_forget(&iaaddr, MDL); if (reply->reply_ia != NULL) option_state_dereference(&reply->reply_ia, MDL); if (ia_data.data != NULL) data_string_forget(&ia_data, MDL); if (data.data != NULL) data_string_forget(&data, MDL); if (reply->ia != NULL) ia_dereference(&reply->ia, MDL); if (reply->old_ia != NULL) ia_dereference(&reply->old_ia, MDL); if (reply->lease != NULL) iasubopt_dereference(&reply->lease, MDL); /* * ISC_R_CANCELED is a status code used by the addr processing to * indicate we're replying with other addresses. This is still a * success at higher layers. */ return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status); } /* * Determines if a lease (iasubopt) can be reused without extending it. * If dhcp-cache-threshold is greater than zero (i.e enabled) then * a lease may be reused without going through a full renewal if * it meets all the requirements. In short it must be active, younger * than the threshold, and not have DNS changes. * * If it is determined that it can be reused, that a call to * shorten_lifetimes() is made to reduce the valid and preferred lifetimes * sent to the client by the age of the lease. * * Returns 1 if lease can be reused, 0 otherwise */ int reuse_lease6(struct reply_state *reply, struct iasubopt *lease) { int threshold = DEFAULT_CACHE_THRESHOLD; struct option_cache* oc = NULL; struct data_string d1; time_t age; time_t limit; int reuse_it = 0; /* In order to even qualify for reuse consideration: * 1. Lease must be active * 2. It must have been accepted at least once * 3. DNS info must not have changed */ if ((lease->state != FTS_ACTIVE) || (lease->hard_lifetime_end_time == 0) || (lease->ddns_cb != NULL)) { return (0); } /* Look up threshold value */ memset(&d1, 0, sizeof(struct data_string)); oc = lookup_option(&server_universe, reply->opt_state, SV_CACHE_THRESHOLD); if (oc && evaluate_option_cache(&d1, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &lease->scope, oc, MDL)) { if (d1.len == 1 && (d1.data[0] < 100)) { threshold = d1.data[0]; } data_string_forget(&d1, MDL); } if (threshold <= 0) { return (0); } if (lease->valid >= MAX_TIME) { /* Infinite leases are always reused. We have to make * a choice because we cannot determine when they actually * began, so we either always reuse them or we never do. */ log_debug ("reusing infinite lease for: %s%s", pin6_addr(&lease->addr), iasubopt_plen_str(lease)); return (1); } age = cur_tv.tv_sec - (lease->hard_lifetime_end_time - lease->valid); if (lease->valid <= (INT_MAX / threshold)) limit = lease->valid * threshold / 100; else limit = lease->valid / 100 * threshold; if (age < limit) { /* Reduce valid/preferred going to the client by age */ shorten_lifetimes(reply, lease, age, threshold); reuse_it = 1; } return (reuse_it); } /* * Reduces the valid and preferred lifetimes for a given lease (iasubopt) * * We cannot determine until after a iasubopt has been added to * the reply if the lease can be reused. Therefore, when we do reuse a * lease we need a way to alter the lifetimes that will be sent to the client. * That's where this function comes in handy: * * Locate the iasubopt by it's address within the reply the reduce both * the preferred and valid lifetimes by the given number of seconds. * * Note that this function, by necessity, works directly with the * option_cache data. Sort of a no-no but I don't have any better ideas. */ void shorten_lifetimes(struct reply_state *reply, struct iasubopt *lease, time_t age, int threshold) { struct option_cache* oc = NULL; int subopt_type; int addr_offset; int pref_offset; int val_offset; int exp_length; if (reply->ia->ia_type != D6O_IA_PD) { subopt_type = D6O_IAADDR; addr_offset = IASUBOPT_NA_ADDR_OFFSET; pref_offset = IASUBOPT_NA_PREF_OFFSET; val_offset = IASUBOPT_NA_VALID_OFFSET; exp_length = IASUBOPT_NA_LEN; } else { subopt_type = D6O_IAPREFIX; addr_offset = IASUBOPT_PD_PREFIX_OFFSET; pref_offset = IASUBOPT_PD_PREF_OFFSET; val_offset = IASUBOPT_PD_VALID_OFFSET; exp_length = IASUBOPT_PD_LEN; } // loop through the iasubopts for the one that matches this lease oc = lookup_option(&dhcpv6_universe, reply->reply_ia, subopt_type); for (; oc != NULL ; oc = oc->next) { if (oc->data.data == NULL || oc->data.len != exp_length) { /* shouldn't happen */ continue; } /* If address matches (and for PDs the prefix len matches) * we assume this is our subopt, so update the lifetimes */ if (!memcmp(oc->data.data + addr_offset, &lease->addr, 16) && (subopt_type != D6O_IAPREFIX || (oc->data.data[IASUBOPT_PD_PREFLEN_OFFSET] == lease->plen))) { u_int32_t pref_life = getULong(oc->data.data + pref_offset); u_int32_t valid_life = getULong(oc->data.data + val_offset); if (pref_life < MAX_TIME && pref_life > age) { pref_life -= age; putULong((unsigned char*)(oc->data.data) + pref_offset, pref_life); if (reply->min_prefer > pref_life) { reply->min_prefer = pref_life; } } if (valid_life < MAX_TIME && valid_life > age) { valid_life -= age; putULong((unsigned char*)(oc->data.data) + val_offset, valid_life); if (reply->min_valid > reply->send_valid) { reply->min_valid = valid_life; } } log_debug ("Reusing lease for: %s%s, " "age %ld secs < %d%%," " sending shortened lifetimes -" " preferred: %u, valid %u", pin6_addr(&lease->addr), iasubopt_plen_str(lease), (long)age, threshold, pref_life, valid_life); break; } } } /* * Verify the temporary address is available. */ static isc_boolean_t temporary_is_available(struct reply_state *reply, struct iaddr *addr) { struct in6_addr tmp_addr; struct subnet *subnet; struct ipv6_pool *pool = NULL; struct ipv6_pond *pond = NULL; int i; memcpy(&tmp_addr, addr->iabuf, sizeof(tmp_addr)); /* * Clients may choose to send :: as an address, with the idea to give * hints about preferred-lifetime or valid-lifetime. * So this is not a request for this address. */ if (IN6_IS_ADDR_UNSPECIFIED(&tmp_addr)) return ISC_FALSE; /* * Verify that this address is on the client's network. */ for (subnet = reply->shared->subnets ; subnet != NULL ; subnet = subnet->next_sibling) { if (addr_eq(subnet_number(*addr, subnet->netmask), subnet->net)) break; } /* Address not found on shared network. */ if (subnet == NULL) return ISC_FALSE; /* * Check if this address is owned (must be before next step). */ if (address_is_owned(reply, addr)) return ISC_TRUE; /* * Verify that this address is in a temporary pool and try to get it. */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; for (i = 0 ; (pool = pond->ipv6_pools[i]) != NULL ; i++) { if (pool->pool_type != D6O_IA_TA) continue; if (ipv6_in_pool(&tmp_addr, pool)) break; } if (pool != NULL) break; } if (pool == NULL) return ISC_FALSE; if (lease6_exists(pool, &tmp_addr)) return ISC_FALSE; if (iasubopt_allocate(&reply->lease, MDL) != ISC_R_SUCCESS) return ISC_FALSE; reply->lease->addr = tmp_addr; reply->lease->plen = 0; /* Default is soft binding for 2 minutes. */ if (add_lease6(pool, reply->lease, cur_time + 120) != ISC_R_SUCCESS) return ISC_FALSE; return ISC_TRUE; } /* * Get a temporary address per prefix. */ static isc_result_t find_client_temporaries(struct reply_state *reply) { int i; struct ipv6_pool *p = NULL; struct ipv6_pond *pond; isc_result_t status = ISC_R_NORESOURCES;; unsigned int attempts; struct iaddr send_addr; /* * Do a quick walk through of the ponds and pools * to see if we have any prefix pools */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (pond->ipv6_pools == NULL) continue; for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) { if (p->pool_type == D6O_IA_TA) break; } if (p != NULL) break; } /* If we get here and p is NULL we have no useful pools */ if (p == NULL) { log_debug("Unable to get client addresses: " "no IPv6 pools on this shared network"); return ISC_R_NORESOURCES; } /* * We have at least one pool that could provide an address * Now we walk through the ponds and pools again and check * to see if the client is permitted and if an address is * available */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; for (i = 0; (p = pond->ipv6_pools[i]) != NULL; i++) { if (p->pool_type != D6O_IA_TA) { continue; } /* * Get an address in this temporary pool. */ status = create_lease6(p, &reply->lease, &attempts, &reply->client_id, cur_time + 120); if (status != ISC_R_SUCCESS) { log_debug("Unable to get a temporary address."); goto cleanup; } status = reply_process_is_addressed(reply, &reply->lease->scope, pond->group); if (status != ISC_R_SUCCESS) { goto cleanup; } send_addr.len = 16; memcpy(send_addr.iabuf, &reply->lease->addr, 16); status = reply_process_send_addr(reply, &send_addr); if (status != ISC_R_SUCCESS) { goto cleanup; } /* * reply->lease can't be null as we use it above * add check if that changes */ iasubopt_dereference(&reply->lease, MDL); } } cleanup: if (reply->lease != NULL) { iasubopt_dereference(&reply->lease, MDL); } return status; } /* * This function only returns failure on 'hard' failures. If it succeeds, * it will leave a lease structure behind. */ static isc_result_t reply_process_try_addr(struct reply_state *reply, struct iaddr *addr) { isc_result_t status = ISC_R_ADDRNOTAVAIL; struct ipv6_pool *pool = NULL; struct ipv6_pond *pond = NULL; int i; struct data_string data_addr; if ((reply == NULL) || (reply->shared == NULL) || (addr == NULL) || (reply->lease != NULL)) return (DHCP_R_INVALIDARG); /* * Do a quick walk through of the ponds and pools * to see if we have any NA address pools */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (pond->ipv6_pools == NULL) continue; for (i = 0; ; i++) { pool = pond->ipv6_pools[i]; if ((pool == NULL) || (pool->pool_type == D6O_IA_NA)) break; } if (pool != NULL) break; } /* If we get here and p is NULL we have no useful pools */ if (pool == NULL) { return (ISC_R_ADDRNOTAVAIL); } memset(&data_addr, 0, sizeof(data_addr)); data_addr.len = addr->len; data_addr.data = addr->iabuf; /* * We have at least one pool that could provide an address * Now we walk through the ponds and pools again and check * to see if the client is permitted and if an address is * available * * Within a given pond 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. */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; for (i = 0 ; (pool = pond->ipv6_pools[i]) != NULL ; i++) { if (pool->pool_type != D6O_IA_NA) continue; status = try_client_v6_address(&reply->lease, pool, &data_addr); if (status == ISC_R_SUCCESS) break; } if (status == ISC_R_SUCCESS) break; } /* Note that this is just pedantry. There is no allocation to free. */ data_string_forget(&data_addr, MDL); /* Return just the most recent status... */ return (status); } /* Look around for an address to give the client. First, look through the * old IA for addresses we can extend. Second, try to allocate a new address. * Finally, actually add that address into the current reply IA. */ static isc_result_t find_client_address(struct reply_state *reply) { struct iaddr send_addr; isc_result_t status = ISC_R_NORESOURCES; struct iasubopt *lease, *best_lease = NULL; struct binding_scope **scope; struct group *group; int i; if (reply->static_lease) { if (reply->host == NULL) return DHCP_R_INVALIDARG; send_addr.len = 16; memcpy(send_addr.iabuf, reply->fixed.data, 16); scope = &global_scope; group = reply->subnet->group; goto send_addr; } if (reply->old_ia != NULL) { for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) { struct shared_network *candidate_shared; struct ipv6_pond *pond; lease = reply->old_ia->iasubopt[i]; candidate_shared = lease->ipv6_pool->shared_network; pond = lease->ipv6_pool->ipv6_pond; /* * Look for the best lease on the client's shared * network, that is still permitted */ if ((candidate_shared != reply->shared) || (lease6_usable(lease) != ISC_TRUE)) continue; if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; best_lease = lease_compare(lease, best_lease); } } /* Try to pick a new address if we didn't find one, or if we found an * abandoned lease. */ if ((best_lease == NULL) || (best_lease->state == FTS_ABANDONED)) { status = pick_v6_address(reply); } else if (best_lease != NULL) { iasubopt_reference(&reply->lease, best_lease, MDL); status = ISC_R_SUCCESS; } /* Pick the abandoned lease as a last resort. */ if ((status == ISC_R_NORESOURCES) && (best_lease != NULL)) { /* I don't see how this is supposed to be done right now. */ log_error("Best match for DUID %s is an abandoned address," " This may be a result of multiple clients attempting" " to use this DUID", print_hex_1(reply->client_id.len, reply->client_id.data, 60)); /* iasubopt_reference(&reply->lease, best_lease, MDL); */ } /* Give up now if we didn't find a lease. */ if (status != ISC_R_SUCCESS) return status; if (reply->lease == NULL) log_fatal("Impossible condition at %s:%d.", MDL); /* Draw binding scopes from the lease's binding scope, and config * from the lease's containing subnet and higher. Note that it may * be desirable to place the group attachment directly in the pool. */ scope = &reply->lease->scope; group = reply->lease->ipv6_pool->ipv6_pond->group; send_addr.len = 16; memcpy(send_addr.iabuf, &reply->lease->addr, 16); send_addr: status = reply_process_is_addressed(reply, scope, group); if (status != ISC_R_SUCCESS) return status; status = reply_process_send_addr(reply, &send_addr); return status; } /* Once an address is found for a client, perform several common functions; * Calculate and store valid and preferred lease times, draw client options * into the option state. */ static isc_result_t reply_process_is_addressed(struct reply_state *reply, struct binding_scope **scope, struct group *group) { isc_result_t status = ISC_R_SUCCESS; struct data_string data; struct option_cache *oc; struct option_state *tmp_options = NULL; struct on_star *on_star; int i; /* Initialize values we will cleanup. */ memset(&data, 0, sizeof(data)); /* * Find the proper on_star block to use. We use the * one in the lease if we have a lease or the one in * the reply if we don't have a lease because this is * a static instance */ if (reply->lease) { on_star = &reply->lease->on_star; } else { on_star = &reply->on_star; } /* * Bring in the root configuration. We only do this to bring * in the on * statements, as we didn't have the lease available * we did it the first time. */ option_state_allocate(&tmp_options, MDL); execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, tmp_options, &global_scope, root_group, NULL, on_star); if (tmp_options != NULL) { option_state_dereference(&tmp_options, MDL); } /* * Bring configured options into the root packet level cache - start * with the lease's closest enclosing group (passed in by the caller * as 'group'). */ execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, group, root_group, on_star); /* Execute statements from class scopes. */ for (i = reply->packet->class_count; i > 0; i--) { execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, reply->packet->classes[i - 1]->group, group, on_star); } /* * If there is a host record, over-ride with values configured there, * without re-evaluating configuration from the previously executed * group or its common enclosers. */ if (reply->host != NULL) execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, reply->host->group, group, on_star); /* Determine valid lifetime. */ if (reply->client_valid == 0) reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME; else reply->send_valid = reply->client_valid; oc = lookup_option(&server_universe, reply->opt_state, SV_DEFAULT_LEASE_TIME); if (oc != NULL) { if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { log_error("reply_process_is_addressed: unable to " "evaluate default lease time"); status = ISC_R_FAILURE; goto cleanup; } reply->send_valid = getULong(data.data); data_string_forget(&data, MDL); } /* Check to see if the lease time would cause us to wrap * in which case we make it infinite. * The following doesn't work on at least some systems: * (cur_time + reply->send_valid < cur_time) */ if (reply->send_valid != INFINITE_TIME) { time_t test_time = cur_time + reply->send_valid; if (test_time < cur_time) reply->send_valid = INFINITE_TIME; } if (reply->client_prefer == 0) reply->send_prefer = reply->send_valid; else reply->send_prefer = reply->client_prefer; if ((reply->send_prefer >= reply->send_valid) && (reply->send_valid != INFINITE_TIME)) reply->send_prefer = (reply->send_valid / 2) + (reply->send_valid / 8); oc = lookup_option(&server_universe, reply->opt_state, SV_PREFER_LIFETIME); if (oc != NULL) { if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { log_error("reply_process_is_addressed: unable to " "evaluate preferred lease time"); status = ISC_R_FAILURE; goto cleanup; } reply->send_prefer = getULong(data.data); data_string_forget(&data, MDL); } /* Note lowest values for later calculation of renew/rebind times. */ if (reply->min_prefer > reply->send_prefer) reply->min_prefer = reply->send_prefer; if (reply->min_valid > reply->send_valid) reply->min_valid = reply->send_valid; #if 0 /* * XXX: Old 4.0.0 alpha code would change the host {} record * XXX: uid upon lease assignment. This was intended to cover the * XXX: case where a client first identifies itself using vendor * XXX: options in a solicit, or request, but later neglects to include * XXX: these options in a Renew or Rebind. It is not clear that this * XXX: is required, and has some startling ramifications (such as * XXX: how to recover this dynamic host {} state across restarts). */ if (reply->host != NULL) change_host_uid(host, reply->client_id->data, reply->client_id->len); #endif /* 0 */ /* Perform dynamic lease related update work. */ if (reply->lease != NULL) { /* Cached lifetimes */ reply->lease->prefer = reply->send_prefer; reply->lease->valid = reply->send_valid; /* Advance (or rewind) the valid lifetime. * In the protocol 0xFFFFFFFF is infinite * when connecting to the lease file MAX_TIME is */ if (reply->buf.reply.msg_type == DHCPV6_REPLY) { if (reply->send_valid == INFINITE_TIME) { reply->lease->soft_lifetime_end_time = MAX_TIME; } else { reply->lease->soft_lifetime_end_time = cur_time + reply->send_valid; } /* Wait before renew! */ } status = ia_add_iasubopt(reply->ia, reply->lease, MDL); if (status != ISC_R_SUCCESS) { log_fatal("reply_process_is_addressed: Unable to " "attach lease to new IA: %s", isc_result_totext(status)); } /* * If this is a new lease, make sure it is attached somewhere. */ if (reply->lease->ia == NULL) { ia_reference(&reply->lease->ia, reply->ia, MDL); } } /* Bring a copy of the relevant options into the IA scope. */ execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->reply_ia, scope, group, root_group, NULL); /* Execute statements from class scopes. */ for (i = reply->packet->class_count; i > 0; i--) { execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->reply_ia, scope, reply->packet->classes[i - 1]->group, group, NULL); } /* * And bring in host record configuration, if any, but not to overlap * the previous group or its common enclosers. */ if (reply->host != NULL) execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->reply_ia, scope, reply->host->group, group, NULL); cleanup: if (data.data != NULL) data_string_forget(&data, MDL); if (status == ISC_R_SUCCESS) reply->client_resources++; return status; } /* Simply send an IAADDR within the IA scope as described. */ static isc_result_t reply_process_send_addr(struct reply_state *reply, struct iaddr *addr) { isc_result_t status = ISC_R_SUCCESS; struct data_string data; memset(&data, 0, sizeof(data)); /* Now append the lease. */ data.len = IAADDR_OFFSET; if (!buffer_allocate(&data.buffer, data.len, MDL)) { log_error("reply_process_send_addr: out of memory" "allocating new IAADDR buffer."); status = ISC_R_NOMEMORY; goto cleanup; } data.data = data.buffer->data; memcpy(data.buffer->data, addr->iabuf, 16); putULong(data.buffer->data + 16, reply->send_prefer); putULong(data.buffer->data + 20, reply->send_valid); if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia, data.buffer, data.buffer->data, data.len, D6O_IAADDR, 0)) { log_error("reply_process_send_addr: unable " "to save IAADDR option"); status = ISC_R_FAILURE; goto cleanup; } reply->resources_included = ISC_TRUE; cleanup: if (data.data != NULL) data_string_forget(&data, MDL); return status; } /* Choose the better of two leases. */ static struct iasubopt * lease_compare(struct iasubopt *alpha, struct iasubopt *beta) { if (alpha == NULL) return beta; if (beta == NULL) return alpha; switch(alpha->state) { case FTS_ACTIVE: switch(beta->state) { case FTS_ACTIVE: /* Choose the lease with the longest lifetime (most * likely the most recently allocated). */ if (alpha->hard_lifetime_end_time < beta->hard_lifetime_end_time) return beta; else return alpha; case FTS_EXPIRED: case FTS_ABANDONED: return alpha; default: log_fatal("Impossible condition at %s:%d.", MDL); } break; case FTS_EXPIRED: switch (beta->state) { case FTS_ACTIVE: return beta; case FTS_EXPIRED: /* Choose the most recently expired lease. */ if (alpha->hard_lifetime_end_time < beta->hard_lifetime_end_time) return beta; else if ((alpha->hard_lifetime_end_time == beta->hard_lifetime_end_time) && (alpha->soft_lifetime_end_time < beta->soft_lifetime_end_time)) return beta; else return alpha; case FTS_ABANDONED: return alpha; default: log_fatal("Impossible condition at %s:%d.", MDL); } break; case FTS_ABANDONED: switch (beta->state) { case FTS_ACTIVE: case FTS_EXPIRED: return alpha; case FTS_ABANDONED: /* Choose the lease that was abandoned longest ago. */ if (alpha->hard_lifetime_end_time < beta->hard_lifetime_end_time) return alpha; else return beta; default: log_fatal("Impossible condition at %s:%d.", MDL); } break; default: log_fatal("Impossible condition at %s:%d.", MDL); } log_fatal("Triple impossible condition at %s:%d.", MDL); return NULL; } /* Process a client-supplied IA_PD. This may append options to the tail of * the reply packet being built in the reply_state structure. */ static isc_result_t reply_process_ia_pd(struct reply_state *reply, struct option_cache *ia) { isc_result_t status = ISC_R_SUCCESS; u_int32_t iaid; unsigned ia_cursor; struct option_state *packet_ia; struct option_cache *oc; struct data_string ia_data, data; /* Initialize values that will get cleaned up on return. */ packet_ia = NULL; memset(&ia_data, 0, sizeof(ia_data)); memset(&data, 0, sizeof(data)); /* * Note that find_client_prefix() may set reply->lease. */ /* Make sure there is at least room for the header. */ if ((reply->cursor + IA_PD_OFFSET + 4) > sizeof(reply->buf)) { log_error("reply_process_ia_pd: Reply too long for IA."); return ISC_R_NOSPACE; } /* Fetch the IA_PD contents. */ if (!get_encapsulated_IA_state(&packet_ia, &ia_data, reply->packet, ia, IA_PD_OFFSET)) { log_error("reply_process_ia_pd: error evaluating ia"); status = ISC_R_FAILURE; goto cleanup; } /* Extract IA_PD header contents. */ iaid = getULong(ia_data.data); reply->renew = getULong(ia_data.data + 4); reply->rebind = getULong(ia_data.data + 8); /* Create an IA_PD structure. */ if (ia_allocate(&reply->ia, iaid, (char *)reply->client_id.data, reply->client_id.len, MDL) != ISC_R_SUCCESS) { log_error("reply_process_ia_pd: no memory for ia."); status = ISC_R_NOMEMORY; goto cleanup; } reply->ia->ia_type = D6O_IA_PD; /* Cache pre-existing IA_PD, if any. */ ia_hash_lookup(&reply->old_ia, ia_pd_active, (unsigned char *)reply->ia->iaid_duid.data, reply->ia->iaid_duid.len, MDL); /* * Create an option cache to carry the IA_PD option contents, and * execute any user-supplied values into it. */ if (!option_state_allocate(&reply->reply_ia, MDL)) { status = ISC_R_NOMEMORY; goto cleanup; } /* Check & count the fixed prefix host records. */ reply->static_prefixes = 0; if ((reply->host != NULL) && (reply->host->fixed_prefix != NULL)) { struct iaddrcidrnetlist *fp; for (fp = reply->host->fixed_prefix; fp != NULL; fp = fp->next) { reply->static_prefixes += 1; } } /* * Save the cursor position at the start of the IA_PD, so we can * set length and adjust t1/t2 values later. We write a temporary * header out now just in case we decide to adjust the packet * within sub-process functions. */ ia_cursor = reply->cursor; /* Initialize the IA_PD header. First the code. */ putUShort(reply->buf.data + reply->cursor, (unsigned)D6O_IA_PD); reply->cursor += 2; /* Then option length. */ putUShort(reply->buf.data + reply->cursor, 0x0Cu); reply->cursor += 2; /* Then IA_PD header contents; IAID. */ putULong(reply->buf.data + reply->cursor, iaid); reply->cursor += 4; /* We store the client's t1 for now, and may over-ride it later. */ putULong(reply->buf.data + reply->cursor, reply->renew); reply->cursor += 4; /* We store the client's t2 for now, and may over-ride it later. */ putULong(reply->buf.data + reply->cursor, reply->rebind); reply->cursor += 4; /* * For each prefix in this IA_PD, decide what to do about it. */ oc = lookup_option(&dhcpv6_universe, packet_ia, D6O_IAPREFIX); reply->min_valid = reply->min_prefer = INFINITE_TIME; reply->client_valid = reply->client_prefer = 0; reply->preflen = -1; for (; oc != NULL ; oc = oc->next) { status = reply_process_prefix(reply, oc); /* * Canceled means we did not allocate prefixes to the * client, but we're "done" with this IA - we set a status * code. So transmit this reply, e.g., move on to the next * IA. */ if (status == ISC_R_CANCELED) break; if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE) && (status != ISC_R_ADDRNOTAVAIL)) goto cleanup; } reply->pd_count++; /* * If we fell through the above and never gave the client * a prefix, give it one now. */ if ((status != ISC_R_CANCELED) && (reply->client_resources == 0)) { status = find_client_prefix(reply); if (status == ISC_R_NORESOURCES) { switch (reply->packet->dhcpv6_msg_type) { case DHCPV6_SOLICIT: /* * No prefix for any IA is handled * by the caller. */ /* FALL THROUGH */ case DHCPV6_REQUEST: /* Same than for addresses. */ option_state_dereference(&reply->reply_ia, MDL); if (!option_state_allocate(&reply->reply_ia, MDL)) { log_error("reply_process_ia_pd: No " "memory for option state " "wipe."); status = ISC_R_NOMEMORY; goto cleanup; } if (!set_status_code(STATUS_NoPrefixAvail, "No prefixes available " "for this interface.", reply->reply_ia)) { log_error("reply_process_ia_pd: " "Unable to set " "NoPrefixAvail status " "code."); status = ISC_R_FAILURE; goto cleanup; } status = ISC_R_SUCCESS; break; default: if (reply->resources_included) status = ISC_R_SUCCESS; else goto cleanup; break; } } if (status != ISC_R_SUCCESS) goto cleanup; } /* * yes, goto's aren't the best but we also want to avoid extra * indents */ if (status == ISC_R_CANCELED) { /* We're replying with a status code so we still need to * write it out in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); goto cleanup; } /* * Handle static prefixes, we always log stuff and if it's * a hard binding we run any commit statements that we have */ if (reply->static_prefixes != 0) { char tmp_addr[INET6_ADDRSTRLEN]; log_info("%s PD: address %s/%d to client with duid %s " "iaid = %d static", dhcpv6_type_names[reply->buf.reply.msg_type], inet_ntop(AF_INET6, reply->fixed_pref.lo_addr.iabuf, tmp_addr, sizeof(tmp_addr)), reply->fixed_pref.bits, print_hex_1(reply->client_id.len, reply->client_id.data, 60), iaid); /* Write the lease out in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); if ((reply->buf.reply.msg_type == DHCPV6_REPLY) && (reply->on_star.on_commit != NULL)) { execute_statements(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, NULL, reply->on_star.on_commit, NULL); executable_statement_dereference (&reply->on_star.on_commit, MDL); } goto cleanup; } /* * If we have any addresses log what we are doing. */ if (reply->ia->num_iasubopt != 0) { struct iasubopt *tmp; int i; char tmp_addr[INET6_ADDRSTRLEN]; for (i = 0 ; i < reply->ia->num_iasubopt ; i++) { tmp = reply->ia->iasubopt[i]; log_info("%s PD: address %s/%d to client with duid %s" " iaid = %d valid for %u seconds", dhcpv6_type_names[reply->buf.reply.msg_type], inet_ntop(AF_INET6, &tmp->addr, tmp_addr, sizeof(tmp_addr)), (int)tmp->plen, print_hex_1(reply->client_id.len, reply->client_id.data, 60), iaid, tmp->valid); } } /* * If this is not a 'soft' binding, consume the new changes into * the database (if any have been attached to the ia_pd). * * Loop through the assigned dynamic prefixes, referencing the * prefixes onto this IA_PD rather than any old ones, and updating * prefix pool timers for each (if any). * * If a lease can be reused we skip renewing it or checking the * pool threshold. If it can't we flag that the IA must be commited * to the db and do the renewal and pool check. */ if ((reply->buf.reply.msg_type == DHCPV6_REPLY) && (reply->ia->num_iasubopt != 0)) { int must_commit = 0; struct iasubopt *tmp; struct data_string *ia_id; int i; for (i = 0 ; i < reply->ia->num_iasubopt ; i++) { tmp = reply->ia->iasubopt[i]; if (tmp->ia != NULL) ia_dereference(&tmp->ia, MDL); ia_reference(&tmp->ia, reply->ia, MDL); /* If we have anything to do on commit do it now */ if (tmp->on_star.on_commit != NULL) { execute_statements(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &tmp->scope, tmp->on_star.on_commit, &tmp->on_star); executable_statement_dereference (&tmp->on_star.on_commit, MDL); } if (!reuse_lease6(reply, tmp)) { /* Commit 'hard' bindings. */ must_commit = 1; renew_lease6(tmp->ipv6_pool, tmp); schedule_lease_timeout(tmp->ipv6_pool); /* Do our threshold check. */ check_pool6_threshold(reply, tmp); } } /* write the IA_PD in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); /* Remove any old ia from the hash. */ if (reply->old_ia != NULL) { if (!release_on_roam(reply)) { ia_id = &reply->old_ia->iaid_duid; ia_hash_delete(ia_pd_active, (unsigned char *)ia_id->data, ia_id->len, MDL); } ia_dereference(&reply->old_ia, MDL); } /* Put new ia into the hash. */ reply->ia->cltt = cur_time; ia_id = &reply->ia->iaid_duid; ia_hash_add(ia_pd_active, (unsigned char *)ia_id->data, ia_id->len, reply->ia, MDL); /* If we couldn't reuse all of the iasubopts, we * must udpate the lease db */ if (must_commit) { write_ia(reply->ia); } } else { /* write the IA_PD in wire-format to the outbound buffer */ write_to_packet(reply, ia_cursor); schedule_lease_timeout_reply(reply); } cleanup: if (packet_ia != NULL) option_state_dereference(&packet_ia, MDL); if (reply->reply_ia != NULL) option_state_dereference(&reply->reply_ia, MDL); if (ia_data.data != NULL) data_string_forget(&ia_data, MDL); if (data.data != NULL) data_string_forget(&data, MDL); if (reply->ia != NULL) ia_dereference(&reply->ia, MDL); if (reply->old_ia != NULL) ia_dereference(&reply->old_ia, MDL); if (reply->lease != NULL) iasubopt_dereference(&reply->lease, MDL); if (reply->on_star.on_expiry != NULL) executable_statement_dereference (&reply->on_star.on_expiry, MDL); if (reply->on_star.on_release != NULL) executable_statement_dereference (&reply->on_star.on_release, MDL); /* * ISC_R_CANCELED is a status code used by the prefix processing to * indicate we're replying with a status code. This is still a * success at higher layers. */ return((status == ISC_R_CANCELED) ? ISC_R_SUCCESS : status); } /*! * * \brief Find the proper scoping group for use with a v6 static prefix. * * We start by trying to find a subnet based on the given prefix and * the shared network. If we don't find one then the prefix has been * declared outside of any subnets. If there is a static address * associated with the host we use it to try and find a subnet (this * should succeed). If there isn't a static address we fall back * to the shared subnet itself. * Once we have a subnet we extract the group from it and return it. * * \param reply - the reply structure we use to collect information * we will use the fields shared, fixed_pref and host * from the structure * * \return a pointer to the group structure to use for scoping */ static struct group * find_group_by_prefix(struct reply_state *reply) { /* default group if we don't find anything better */ struct group *group = reply->shared->group; struct subnet *subnet = NULL; struct iaddr tmp_addr; struct data_string fixed_addr; /* Try with the prefix first */ if (find_grouped_subnet(&subnet, reply->shared, reply->fixed_pref.lo_addr, MDL) != 0) { group = subnet->group; subnet_dereference(&subnet, MDL); return (group); } /* Didn't find a subnet via prefix, what about fixed address */ /* The caller has already tested reply->host != NULL */ memset(&fixed_addr, 0, sizeof(fixed_addr)); if ((reply->host->fixed_addr != NULL) && (evaluate_option_cache(&fixed_addr, NULL, NULL, NULL, NULL, NULL, &global_scope, reply->host->fixed_addr, MDL))) { if (fixed_addr.len >= 16) { tmp_addr.len = 16; memcpy(tmp_addr.iabuf, fixed_addr.data, 16); if (find_grouped_subnet(&subnet, reply->shared, tmp_addr, MDL) != 0) { group = subnet->group; subnet_dereference(&subnet, MDL); } } data_string_forget(&fixed_addr, MDL); } /* return whatever we got */ return (group); } /* * Process an IAPREFIX within a given IA_PD, storing any IAPREFIX reply * contents into the reply's current ia_pd-scoped option cache. Returns * ISC_R_CANCELED in the event we are replying with a status code and do * not wish to process more IAPREFIXes within this IA_PD. */ static isc_result_t reply_process_prefix(struct reply_state *reply, struct option_cache *pref) { u_int32_t pref_life, valid_life; struct binding_scope **scope; struct iaddrcidrnet tmp_pref; struct option_cache *oc; struct data_string iapref, data; isc_result_t status = ISC_R_SUCCESS; struct group *group; /* Initializes values that will be cleaned up. */ memset(&iapref, 0, sizeof(iapref)); memset(&data, 0, sizeof(data)); /* Note that reply->lease may be set by prefix_is_owned() */ /* * There is no point trying to process an incoming prefix if there * is no room for an outgoing prefix. */ if ((reply->cursor + 29) > sizeof(reply->buf)) { log_error("reply_process_prefix: Out of room for prefix."); return ISC_R_NOSPACE; } /* Extract this IAPREFIX option. */ if (!evaluate_option_cache(&iapref, reply->packet, NULL, NULL, reply->packet->options, NULL, &global_scope, pref, MDL) || (iapref.len < IAPREFIX_OFFSET)) { log_error("reply_process_prefix: error evaluating IAPREFIX."); status = ISC_R_FAILURE; goto cleanup; } /* * Layout: preferred and valid lifetimes followed by the prefix * length and the IPv6 address. */ pref_life = getULong(iapref.data); valid_life = getULong(iapref.data + 4); if ((reply->client_valid == 0) || (reply->client_valid > valid_life)) reply->client_valid = valid_life; if ((reply->client_prefer == 0) || (reply->client_prefer > pref_life)) reply->client_prefer = pref_life; /* * Clients may choose to send ::/0 as a prefix, with the idea to give * hints about preferred-lifetime or valid-lifetime. */ tmp_pref.lo_addr.len = 16; memset(tmp_pref.lo_addr.iabuf, 0, 16); if ((iapref.data[8] == 0) && (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0)) { /* Status remains success; we just ignore this one. */ goto cleanup; } /* * Clients may choose to send ::/X as a prefix to specify a * preferred/requested prefix length. Note X is never zero here. */ tmp_pref.bits = (int) iapref.data[8]; if (reply->preflen < 0) { /* Cache the first preferred prefix length. */ reply->preflen = tmp_pref.bits; } if (memcmp(iapref.data + 9, tmp_pref.lo_addr.iabuf, 16) == 0) { goto cleanup; } memcpy(tmp_pref.lo_addr.iabuf, iapref.data + 9, 16); /* Verify the prefix belongs to the client. */ if (!prefix_is_owned(reply, &tmp_pref)) { /* Same than for addresses. */ if ((reply->packet->dhcpv6_msg_type == DHCPV6_SOLICIT) || (reply->packet->dhcpv6_msg_type == DHCPV6_REQUEST) || (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND)) { status = reply_process_try_prefix(reply, &tmp_pref); /* Either error out or skip this prefix. */ if ((status != ISC_R_SUCCESS) && (status != ISC_R_ADDRINUSE) && (status != ISC_R_ADDRNOTAVAIL)) goto cleanup; if (reply->lease == NULL) { if (reply->packet->dhcpv6_msg_type == DHCPV6_REBIND) { reply->send_prefer = 0; reply->send_valid = 0; goto send_pref; } /* status remains success - ignore */ goto cleanup; } /* * RFC3633 section 18.2.3: * * If the delegating router cannot find a binding * for the requesting router's IA_PD the delegating * router returns the IA_PD containing no prefixes * with a Status Code option set to NoBinding in the * Reply message. * * On mismatch we (ab)use this pretending we have not the IA * as soon as we have not a prefix. */ } else if (reply->packet->dhcpv6_msg_type == DHCPV6_RENEW) { /* Rewind the IA_PD to empty. */ option_state_dereference(&reply->reply_ia, MDL); if (!option_state_allocate(&reply->reply_ia, MDL)) { log_error("reply_process_prefix: No memory " "for option state wipe."); status = ISC_R_NOMEMORY; goto cleanup; } /* Append a NoBinding status code. */ if (!set_status_code(STATUS_NoBinding, "Prefix not bound to this " "interface.", reply->reply_ia)) { log_error("reply_process_prefix: Unable to " "attach status code."); status = ISC_R_FAILURE; goto cleanup; } /* Fin (no more IAPREFIXes). */ status = ISC_R_CANCELED; goto cleanup; } else { log_error("It is impossible to lease a client that is " "not sending a solicit, request, renew, or " "rebind message."); status = ISC_R_FAILURE; goto cleanup; } } if (reply->static_prefixes > 0) { if (reply->host == NULL) log_fatal("Impossible condition at %s:%d.", MDL); scope = &global_scope; /* Copy the static prefix for logging and finding the group */ memcpy(&reply->fixed_pref, &tmp_pref, sizeof(tmp_pref)); /* Try to find a group for the static prefix */ group = find_group_by_prefix(reply); } else { if (reply->lease == NULL) log_fatal("Impossible condition at %s:%d.", MDL); scope = &reply->lease->scope; group = reply->lease->ipv6_pool->ipv6_pond->group; } /* * If client_resources is nonzero, then the reply_process_is_prefixed * function has executed configuration state into the reply option * cache. We will use that valid cache to derive configuration for * whether or not to engage in additional prefixes, and similar. */ if (reply->client_resources != 0) { unsigned limit = 1; /* * Does this client have "enough" prefixes already? Default * to one. Everybody gets one, and one should be enough for * anybody. */ oc = lookup_option(&server_universe, reply->opt_state, SV_LIMIT_PREFS_PER_IA); if (oc != NULL) { if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { log_error("reply_process_prefix: unable to " "evaluate prefs-per-ia value."); status = ISC_R_FAILURE; goto cleanup; } limit = getULong(data.data); data_string_forget(&data, MDL); } /* * If we wish to limit the client to a certain number of * prefixes, then omit the prefix from the reply. */ if (reply->client_resources >= limit) goto cleanup; } status = reply_process_is_prefixed(reply, scope, group); if (status != ISC_R_SUCCESS) goto cleanup; send_pref: status = reply_process_send_prefix(reply, &tmp_pref); cleanup: if (iapref.data != NULL) data_string_forget(&iapref, MDL); if (data.data != NULL) data_string_forget(&data, MDL); if (reply->lease != NULL) iasubopt_dereference(&reply->lease, MDL); return status; } /* * Verify the prefix belongs to the client. If we've got a host * record with fixed prefixes, it has to be an assigned prefix * (fault out all else). Otherwise it's a dynamic prefix, so lookup * that prefix and make sure it belongs to this DUID:IAID pair. */ static isc_boolean_t prefix_is_owned(struct reply_state *reply, struct iaddrcidrnet *pref) { struct iaddrcidrnetlist *l; int i; struct ipv6_pond *pond; /* * This faults out prefixes that don't match fixed prefixes. */ if (reply->static_prefixes > 0) { for (l = reply->host->fixed_prefix; l != NULL; l = l->next) { if ((pref->bits == l->cidrnet.bits) && (memcmp(pref->lo_addr.iabuf, l->cidrnet.lo_addr.iabuf, 16) == 0)) return (ISC_TRUE); } return (ISC_FALSE); } if ((reply->old_ia == NULL) || (reply->old_ia->num_iasubopt == 0)) return (ISC_FALSE); for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) { struct iasubopt *tmp; tmp = reply->old_ia->iasubopt[i]; if ((pref->bits == (int) tmp->plen) && (memcmp(pref->lo_addr.iabuf, &tmp->addr, 16) == 0)) { if (lease6_usable(tmp) == ISC_FALSE) { return (ISC_FALSE); } pond = tmp->ipv6_pool->ipv6_pond; if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) return (ISC_FALSE); iasubopt_reference(&reply->lease, tmp, MDL); return (ISC_TRUE); } } return (ISC_FALSE); } /* * This function only returns failure on 'hard' failures. If it succeeds, * it will leave a prefix structure behind. */ static isc_result_t reply_process_try_prefix(struct reply_state *reply, struct iaddrcidrnet *pref) { isc_result_t status = ISC_R_ADDRNOTAVAIL; struct ipv6_pool *pool = NULL; struct ipv6_pond *pond = NULL; int i; struct data_string data_pref; if ((reply == NULL) || (reply->shared == NULL) || (pref == NULL) || (reply->lease != NULL)) return (DHCP_R_INVALIDARG); /* * Do a quick walk through of the ponds and pools * to see if we have any prefix pools */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (pond->ipv6_pools == NULL) continue; for (i = 0; (pool = pond->ipv6_pools[i]) != NULL; i++) { if (pool->pool_type == D6O_IA_PD) break; } if (pool != NULL) break; } /* If we get here and p is NULL we have no useful pools */ if (pool == NULL) { return (ISC_R_ADDRNOTAVAIL); } memset(&data_pref, 0, sizeof(data_pref)); data_pref.len = 17; if (!buffer_allocate(&data_pref.buffer, data_pref.len, MDL)) { log_error("reply_process_try_prefix: out of memory."); return (ISC_R_NOMEMORY); } data_pref.data = data_pref.buffer->data; data_pref.buffer->data[0] = (u_int8_t) pref->bits; memcpy(data_pref.buffer->data + 1, pref->lo_addr.iabuf, 16); /* * We have at least one pool that could provide a prefix * Now we walk through the ponds and pools again and check * to see if the client is permitted and if an prefix is * available * */ for (pond = reply->shared->ipv6_pond; pond != NULL; pond = pond->next) { if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; for (i = 0; (pool = pond->ipv6_pools[i]) != NULL; i++) { if (pool->pool_type != D6O_IA_PD) { continue; } status = try_client_v6_prefix(&reply->lease, pool, &data_pref); /* If we found it in this pool (either in use or available), there is no need to look further. */ if ( (status == ISC_R_SUCCESS) || (status == ISC_R_ADDRINUSE) ) break; } if ( (status == ISC_R_SUCCESS) || (status == ISC_R_ADDRINUSE) ) break; } data_string_forget(&data_pref, MDL); /* Return just the most recent status... */ return (status); } /* Look around for a prefix to give the client. First, look through the old * IA_PD for prefixes we can extend. Second, try to allocate a new prefix. * Finally, actually add that prefix into the current reply IA_PD. */ static isc_result_t find_client_prefix(struct reply_state *reply) { struct iaddrcidrnet send_pref; isc_result_t status = ISC_R_NORESOURCES; struct iasubopt *prefix, *best_prefix = NULL; struct binding_scope **scope; int i; struct group *group; if (reply->static_prefixes > 0) { struct iaddrcidrnetlist *l; if (reply->host == NULL) return DHCP_R_INVALIDARG; for (l = reply->host->fixed_prefix; l != NULL; l = l->next) { if (l->cidrnet.bits == reply->preflen) break; } if (l == NULL) { /* * If no fixed prefix has the preferred length, * get the first one. */ l = reply->host->fixed_prefix; } memcpy(&send_pref, &l->cidrnet, sizeof(send_pref)); scope = &global_scope; /* Copy the prefix for logging purposes */ memcpy(&reply->fixed_pref, &l->cidrnet, sizeof(send_pref)); /* Try to find a group for the static prefix */ group = find_group_by_prefix(reply); goto send_pref; } if (reply->old_ia != NULL) { for (i = 0 ; i < reply->old_ia->num_iasubopt ; i++) { struct shared_network *candidate_shared; struct ipv6_pond *pond; prefix = reply->old_ia->iasubopt[i]; candidate_shared = prefix->ipv6_pool->shared_network; pond = prefix->ipv6_pool->ipv6_pond; /* * Consider this prefix if it is in a global pool or * if it is scoped in a pool under the client's shared * network. */ if (((candidate_shared != NULL) && (candidate_shared != reply->shared)) || (lease6_usable(prefix) != ISC_TRUE)) continue; /* * And check if the prefix is still permitted */ if (((pond->prohibit_list != NULL) && (permitted(reply->packet, pond->prohibit_list))) || ((pond->permit_list != NULL) && (!permitted(reply->packet, pond->permit_list)))) continue; best_prefix = prefix_compare(reply, prefix, best_prefix); } /* * If we have prefix length hint and we're not igoring them, * then toss the best match if it doesn't match the hint, * unless this is in response to a rebind. In the latter * case we're supposed to return it with zero lifetimes. * (See rt45780) */ if (best_prefix && (reply->preflen > 0) && (prefix_length_mode != PLM_IGNORE) && (reply->preflen != best_prefix->plen) && (reply->packet->dhcpv6_msg_type != DHCPV6_REBIND)) { best_prefix = NULL; } } /* Try to pick a new prefix if we didn't find one, or if we found an * abandoned prefix. */ if ((best_prefix == NULL) || (best_prefix->state == FTS_ABANDONED)) { status = pick_v6_prefix(reply); } else if (best_prefix != NULL) { iasubopt_reference(&reply->lease, best_prefix, MDL); status = ISC_R_SUCCESS; } /* Pick the abandoned prefix as a last resort. */ if ((status == ISC_R_NORESOURCES) && (best_prefix != NULL)) { /* I don't see how this is supposed to be done right now. */ log_error("Reclaiming abandoned prefixes is not yet " "supported. Treating this as an out of space " "condition."); /* iasubopt_reference(&reply->lease, best_prefix, MDL); */ } /* Give up now if we didn't find a prefix. */ if (status != ISC_R_SUCCESS) return status; if (reply->lease == NULL) log_fatal("Impossible condition at %s:%d.", MDL); scope = &reply->lease->scope; group = reply->lease->ipv6_pool->ipv6_pond->group; send_pref.lo_addr.len = 16; memcpy(send_pref.lo_addr.iabuf, &reply->lease->addr, 16); send_pref.bits = (int) reply->lease->plen; send_pref: status = reply_process_is_prefixed(reply, scope, group); if (status != ISC_R_SUCCESS) return status; status = reply_process_send_prefix(reply, &send_pref); return status; } /* Once a prefix is found for a client, perform several common functions; * Calculate and store valid and preferred prefix times, draw client options * into the option state. */ static isc_result_t reply_process_is_prefixed(struct reply_state *reply, struct binding_scope **scope, struct group *group) { isc_result_t status = ISC_R_SUCCESS; struct data_string data; struct option_cache *oc; struct option_state *tmp_options = NULL; struct on_star *on_star; int i; /* Initialize values we will cleanup. */ memset(&data, 0, sizeof(data)); /* * Find the proper on_star block to use. We use the * one in the lease if we have a lease or the one in * the reply if we don't have a lease because this is * a static instance */ if (reply->lease) { on_star = &reply->lease->on_star; } else { on_star = &reply->on_star; } /* * Bring in the root configuration. We only do this to bring * in the on * statements, as we didn't have the lease available * we we did it the first time. */ option_state_allocate(&tmp_options, MDL); execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, tmp_options, &global_scope, root_group, NULL, on_star); if (tmp_options != NULL) { option_state_dereference(&tmp_options, MDL); } /* * Bring configured options into the root packet level cache - start * with the lease's closest enclosing group (passed in by the caller * as 'group'). */ execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, group, root_group, on_star); /* Execute statements from class scopes. */ for (i = reply->packet->class_count; i > 0; i--) { execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, reply->packet->classes[i - 1]->group, group, on_star); } /* * If there is a host record, over-ride with values configured there, * without re-evaluating configuration from the previously executed * group or its common enclosers. */ if (reply->host != NULL) execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, reply->host->group, group, on_star); /* Determine valid lifetime. */ if (reply->client_valid == 0) reply->send_valid = DEFAULT_DEFAULT_LEASE_TIME; else reply->send_valid = reply->client_valid; oc = lookup_option(&server_universe, reply->opt_state, SV_DEFAULT_LEASE_TIME); if (oc != NULL) { if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { log_error("reply_process_is_prefixed: unable to " "evaluate default prefix time"); status = ISC_R_FAILURE; goto cleanup; } reply->send_valid = getULong(data.data); data_string_forget(&data, MDL); } /* Check to see if the lease time would cause us to wrap * in which case we make it infinite. * The following doesn't work on at least some systems: * (cur_time + reply->send_valid < cur_time) */ if (reply->send_valid != INFINITE_TIME) { time_t test_time = cur_time + reply->send_valid; if (test_time < cur_time) reply->send_valid = INFINITE_TIME; } if (reply->client_prefer == 0) reply->send_prefer = reply->send_valid; else reply->send_prefer = reply->client_prefer; if ((reply->send_prefer >= reply->send_valid) && (reply->send_valid != INFINITE_TIME)) reply->send_prefer = (reply->send_valid / 2) + (reply->send_valid / 8); oc = lookup_option(&server_universe, reply->opt_state, SV_PREFER_LIFETIME); if (oc != NULL) { if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, scope, oc, MDL) || (data.len != 4)) { log_error("reply_process_is_prefixed: unable to " "evaluate preferred prefix time"); status = ISC_R_FAILURE; goto cleanup; } reply->send_prefer = getULong(data.data); data_string_forget(&data, MDL); } /* Note lowest values for later calculation of renew/rebind times. */ if (reply->min_prefer > reply->send_prefer) reply->min_prefer = reply->send_prefer; if (reply->min_valid > reply->send_valid) reply->min_valid = reply->send_valid; /* Perform dynamic prefix related update work. */ if (reply->lease != NULL) { /* Cached lifetimes */ reply->lease->prefer = reply->send_prefer; reply->lease->valid = reply->send_valid; /* Advance (or rewind) the valid lifetime. * In the protocol 0xFFFFFFFF is infinite * when connecting to the lease file MAX_TIME is */ if (reply->buf.reply.msg_type == DHCPV6_REPLY) { if (reply->send_valid == INFINITE_TIME) { reply->lease->soft_lifetime_end_time = MAX_TIME; } else { reply->lease->soft_lifetime_end_time = cur_time + reply->send_valid; } /* Wait before renew! */ } status = ia_add_iasubopt(reply->ia, reply->lease, MDL); if (status != ISC_R_SUCCESS) { log_fatal("reply_process_is_prefixed: Unable to " "attach prefix to new IA_PD: %s", isc_result_totext(status)); } /* * If this is a new prefix, make sure it is attached somewhere. */ if (reply->lease->ia == NULL) { ia_reference(&reply->lease->ia, reply->ia, MDL); } } /* Bring a copy of the relevant options into the IA_PD scope. */ execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->reply_ia, scope, group, root_group, NULL); /* Execute statements from class scopes. */ for (i = reply->packet->class_count; i > 0; i--) { execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->reply_ia, scope, reply->packet->classes[i - 1]->group, group, NULL); } /* * And bring in host record configuration, if any, but not to overlap * the previous group or its common enclosers. */ if (reply->host != NULL) execute_statements_in_scope(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->reply_ia, scope, reply->host->group, group, NULL); cleanup: if (data.data != NULL) data_string_forget(&data, MDL); if (status == ISC_R_SUCCESS) reply->client_resources++; return status; } /* Simply send an IAPREFIX within the IA_PD scope as described. */ static isc_result_t reply_process_send_prefix(struct reply_state *reply, struct iaddrcidrnet *pref) { isc_result_t status = ISC_R_SUCCESS; struct data_string data; memset(&data, 0, sizeof(data)); /* Now append the prefix. */ data.len = IAPREFIX_OFFSET; if (!buffer_allocate(&data.buffer, data.len, MDL)) { log_error("reply_process_send_prefix: out of memory" "allocating new IAPREFIX buffer."); status = ISC_R_NOMEMORY; goto cleanup; } data.data = data.buffer->data; putULong(data.buffer->data, reply->send_prefer); putULong(data.buffer->data + 4, reply->send_valid); data.buffer->data[8] = pref->bits; memcpy(data.buffer->data + 9, pref->lo_addr.iabuf, 16); if (!append_option_buffer(&dhcpv6_universe, reply->reply_ia, data.buffer, data.buffer->data, data.len, D6O_IAPREFIX, 0)) { log_error("reply_process_send_prefix: unable " "to save IAPREFIX option"); status = ISC_R_FAILURE; goto cleanup; } reply->resources_included = ISC_TRUE; cleanup: if (data.data != NULL) data_string_forget(&data, MDL); return status; } /* Choose the better of two prefixes. */ static struct iasubopt * prefix_compare(struct reply_state *reply, struct iasubopt *alpha, struct iasubopt *beta) { if (alpha == NULL) return beta; if (beta == NULL) return alpha; if (reply->preflen >= 0) { if ((alpha->plen == reply->preflen) && (beta->plen != reply->preflen)) return alpha; if ((beta->plen == reply->preflen) && (alpha->plen != reply->preflen)) return beta; } switch(alpha->state) { case FTS_ACTIVE: switch(beta->state) { case FTS_ACTIVE: /* Choose the prefix with the longest lifetime (most * likely the most recently allocated). */ if (alpha->hard_lifetime_end_time < beta->hard_lifetime_end_time) return beta; else return alpha; case FTS_EXPIRED: case FTS_ABANDONED: return alpha; default: log_fatal("Impossible condition at %s:%d.", MDL); } break; case FTS_EXPIRED: switch (beta->state) { case FTS_ACTIVE: return beta; case FTS_EXPIRED: /* Choose the most recently expired prefix. */ if (alpha->hard_lifetime_end_time < beta->hard_lifetime_end_time) return beta; else if ((alpha->hard_lifetime_end_time == beta->hard_lifetime_end_time) && (alpha->soft_lifetime_end_time < beta->soft_lifetime_end_time)) return beta; else return alpha; case FTS_ABANDONED: return alpha; default: log_fatal("Impossible condition at %s:%d.", MDL); } break; case FTS_ABANDONED: switch (beta->state) { case FTS_ACTIVE: case FTS_EXPIRED: return alpha; case FTS_ABANDONED: /* Choose the prefix that was abandoned longest ago. */ if (alpha->hard_lifetime_end_time < beta->hard_lifetime_end_time) return alpha; else return beta; default: log_fatal("Impossible condition at %s:%d.", MDL); } break; default: log_fatal("Impossible condition at %s:%d.", MDL); } log_fatal("Triple impossible condition at %s:%d.", MDL); return NULL; } /* * 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. */ static void dhcpv6_solicit(struct data_string *reply_ret, struct packet *packet) { struct data_string client_id; /* * 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. */ 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; } /* If the REQUEST arrived via unicast and unicast option isn't set, * reject it per RFC 3315, Sec 18.2.1 */ if (packet->unicast == ISC_TRUE && is_unicast_option_defined(packet) == ISC_FALSE) { unicast_reject(reply_ret, packet, &client_id, &server_id); } else { /* * 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); } /* Find a DHCPv6 packet's shared network from hints in the packet. */ static isc_result_t shared_network_from_packet6(struct shared_network **shared, struct packet *packet) { const struct packet *chk_packet; const struct in6_addr *link_addr, *first_link_addr; struct iaddr tmp_addr; struct subnet *subnet; isc_result_t status; if ((shared == NULL) || (*shared != NULL) || (packet == NULL)) return DHCP_R_INVALIDARG; /* * First, find the link address where the packet from the client * first appeared (if this packet was relayed). */ 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; break; } chk_packet = chk_packet->dhcpv6_container_packet; } /* * If there is a relayed 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; } status = shared_network_reference(shared, 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 if (packet->interface != NULL) { status = shared_network_reference(shared, packet->interface->shared_network, MDL); if (packet->dhcpv6_container_packet != NULL) { log_info("[L2 Relay] No link address in relay packet " "assuming L2 relay and using receiving " "interface"); } } else { /* * We shouldn't be able to get here but if there is no link * address and no interface we don't know where to get the * pool from log an error and return an error. */ log_error("No interface and no link address " "can't determine pool"); status = DHCP_R_INVALIDARG; } return status; } /* * When a client thinks it might be on a new link, it sends a * Confirm message. * * From RFC3315 section 18.2.2: * * When the server receives a Confirm message, the server determines * whether the addresses in the Confirm message are appropriate for the * link to which the client is attached. If all of the addresses in the * Confirm message pass this test, the server returns a status of * Success. If any of the addresses do not pass this test, the server * returns a status of NotOnLink. If the server is unable to perform * this test (for example, the server does not have information about * prefixes on the link to which the client is connected), or there were * no addresses in any of the IAs sent by the client, the server MUST * NOT send a reply to the client. */ static void dhcpv6_confirm(struct data_string *reply_ret, struct packet *packet) { struct shared_network *shared; struct subnet *subnet; struct option_cache *ia, *ta, *oc; struct data_string cli_enc_opt_data, iaaddr, client_id, packet_oro; struct option_state *cli_enc_opt_state, *opt_state; struct iaddr cli_addr; int pass; isc_boolean_t inappropriate, has_addrs; char reply_data[65536]; struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data; int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options)); /* * Basic client message validation. */ memset(&client_id, 0, sizeof(client_id)); if (!valid_client_msg(packet, &client_id)) { return; } /* * Do not process Confirms that do not have IA's we do not recognize. */ ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_NA); ta = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_TA); if ((ia == NULL) && (ta == NULL)) return; /* * IA_PD's are simply ignored. */ delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD); /* * Bit of variable initialization. */ opt_state = cli_enc_opt_state = NULL; memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data)); memset(&iaaddr, 0, sizeof(iaaddr)); memset(&packet_oro, 0, sizeof(packet_oro)); /* Determine what shared network the client is connected to. We * must not respond if we don't have any information about the * network the client is on. */ shared = NULL; if ((shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS) || (shared == NULL)) goto exit; /* If there are no recorded subnets, then we have no * information about this subnet - ignore Confirms. */ subnet = shared->subnets; if (subnet == NULL) goto exit; /* Are the addresses in all the IA's appropriate for that link? */ has_addrs = inappropriate = ISC_FALSE; pass = D6O_IA_NA; while(!inappropriate) { /* If we've reached the end of the IA_NA pass, move to the * IA_TA pass. */ if ((pass == D6O_IA_NA) && (ia == NULL)) { pass = D6O_IA_TA; ia = ta; } /* If we've reached the end of all passes, we're done. */ if (ia == NULL) break; if (((pass == D6O_IA_NA) && !get_encapsulated_IA_state(&cli_enc_opt_state, &cli_enc_opt_data, packet, ia, IA_NA_OFFSET)) || ((pass == D6O_IA_TA) && !get_encapsulated_IA_state(&cli_enc_opt_state, &cli_enc_opt_data, packet, ia, IA_TA_OFFSET))) { goto exit; } oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, D6O_IAADDR); for ( ; oc != NULL ; oc = oc->next) { if (!evaluate_option_cache(&iaaddr, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL) || (iaaddr.len < IAADDR_OFFSET)) { log_error("dhcpv6_confirm: " "error evaluating IAADDR."); goto exit; } /* Copy out the IPv6 address for processing. */ cli_addr.len = 16; memcpy(cli_addr.iabuf, iaaddr.data, 16); data_string_forget(&iaaddr, MDL); /* Record that we've processed at least one address. */ has_addrs = ISC_TRUE; /* Find out if any subnets cover this address. */ for (subnet = shared->subnets ; subnet != NULL ; subnet = subnet->next_sibling) { if (addr_eq(subnet_number(cli_addr, subnet->netmask), subnet->net)) break; } /* If we reach the end of the subnet list, and no * subnet matches the client address, then it must * be inappropriate to the link (so far as our * configuration says). Once we've found one * inappropriate address, there is no reason to * continue searching. */ if (subnet == NULL) { inappropriate = ISC_TRUE; break; } } option_state_dereference(&cli_enc_opt_state, MDL); data_string_forget(&cli_enc_opt_data, MDL); /* Advance to the next IA_*. */ ia = ia->next; } /* If the client supplied no addresses, do not reply. */ if (!has_addrs) goto exit; /* * Set up reply. */ if (!start_reply(packet, &client_id, NULL, &opt_state, reply)) { goto exit; } /* * Set our status. */ if (inappropriate) { 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: /* Cleanup any stale data strings. */ if (cli_enc_opt_data.buffer != NULL) data_string_forget(&cli_enc_opt_data, MDL); if (iaaddr.buffer != NULL) data_string_forget(&iaaddr, MDL); if (client_id.buffer != NULL) data_string_forget(&client_id, MDL); if (packet_oro.buffer != NULL) data_string_forget(&packet_oro, MDL); /* Release any stale option states. */ if (cli_enc_opt_state != NULL) option_state_dereference(&cli_enc_opt_state, MDL); if (opt_state != NULL) option_state_dereference(&opt_state, MDL); } /* * Renew is when a client wants to extend its lease/prefix, at time T1. * * We handle this the same as if the client wants a new lease/prefix, * except for the error code of when addresses don't match. */ 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; } /* If the RENEW arrived via unicast and unicast option isn't set, * reject it per RFC 3315, Sec 18.2.3 */ if (packet->unicast == ISC_TRUE && is_unicast_option_defined(packet) == ISC_FALSE) { unicast_reject(reply, packet, &client_id, &server_id); } else { /* * 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. */ 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 iasubopt *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); lease->ia->cltt = cur_time; write_ia(lease->ia); } } 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((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA); /* option length */ putUShort((unsigned char *)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((unsigned char *)reply_data+(*reply_ofs)+8, 0); putULong((unsigned char *)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, const 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 data_string iaaddr; struct data_string fixed_addr; char reply_data[65536]; struct dhcpv6_packet *reply = (struct dhcpv6_packet *)reply_data; int reply_ofs = (int)(offsetof(struct dhcpv6_packet, options)); char status_msg[32]; struct iasubopt *lease; struct ia_xx *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)); lease = NULL; /* * Find the host record that matches from the packet, if any. */ packet_host = NULL; find_hosts6(&packet_host, packet, client_id, MDL); /* * 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, 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, (unsigned 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) { if (!get_encapsulated_IA_state(&cli_enc_opt_state, &cli_enc_opt_data, packet, ia, IA_NA_OFFSET)) { 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 (encapsulated option contents * matching a host record by option). * * 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 >= IAADDR_OFFSET)) { /* * Find existing IA_NA. */ if (ia_make_key(&key, iaid, (char *)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_hash_lookup(&existing_ia_na, ia_na_active, (unsigned char *)key.data, key.len, MDL)) { /* * Make sure this address is in the IA_NA. */ for (i=0; inum_iasubopt; i++) { struct iasubopt *tmp; struct in6_addr *in6_addr; tmp = existing_ia_na->iasubopt[i]; in6_addr = &tmp->addr; if (memcmp(in6_addr, iaaddr.data, 16) == 0) { iasubopt_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) { iasubopt_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) { iasubopt_dereference(&lease, 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 occurrence. * * 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: 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; } /* If the DECLINE arrived via unicast and unicast option isn't set, * reject it per RFC 3315, Sec 18.2.7 */ if (packet->unicast == ISC_TRUE && is_unicast_option_defined(packet) == ISC_FALSE) { unicast_reject(reply, packet, &client_id, &server_id); } else { /* * Undefined for IA_PD. */ delete_option(&dhcpv6_universe, packet->options, D6O_IA_PD); /* * 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); } data_string_forget(&server_id, MDL); data_string_forget(&client_id, MDL); } static void ia_na_match_release(const struct data_string *client_id, const struct data_string *iaaddr, struct iasubopt *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); lease->ia->cltt = cur_time; write_ia(lease->ia); } } 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_nomatch_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_nomatch_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((unsigned char *)reply_data+(*reply_ofs), D6O_IA_NA); /* option length */ putUShort((unsigned char *)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((unsigned char *)reply_data+(*reply_ofs)+8, 0); putULong((unsigned char *)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 ia_pd_match_release(const struct data_string *client_id, const struct data_string *iapref, struct iasubopt *prefix) { char tmp_addr[INET6_ADDRSTRLEN]; log_info("Client %s releases prefix %s/%u", print_hex_1(client_id->len, client_id->data, 60), inet_ntop(AF_INET6, iapref->data + 9, tmp_addr, sizeof(tmp_addr)), (unsigned) getUChar(iapref->data + 8)); if (prefix != NULL) { release_lease6(prefix->ipv6_pool, prefix); prefix->ia->cltt = cur_time; write_ia(prefix->ia); } } static void ia_pd_nomatch_release(const struct data_string *client_id, const struct data_string *iapref, u_int32_t *ia_pd_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 prefix %s/%u, which is not leased to it.", print_hex_1(client_id->len, client_id->data, 60), inet_ntop(AF_INET6, iapref->data + 9, tmp_addr, sizeof(tmp_addr)), (unsigned) getUChar(iapref->data + 8)); /* * Create state for this IA_PD. */ host_opt_state = NULL; if (!option_state_allocate(&host_opt_state, MDL)) { log_error("ia_pd_nomatch_release: out of memory " "allocating option_state."); goto exit; } if (!set_status_code(STATUS_NoBinding, "Release for non-leased prefix.", host_opt_state)) { goto exit; } /* * Insure we have enough space */ if (reply_len < (*reply_ofs + 16)) { log_error("ia_pd_nomatch_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_PD into our reply packet. Defined in RFC 3315, * section 22.4. */ /* option number */ putUShort((unsigned char *)reply_data+(*reply_ofs), D6O_IA_PD); /* option length */ putUShort((unsigned char *)reply_data+(*reply_ofs)+2, len + 12); /* IA_PD, copied from the client */ memcpy(reply_data+(*reply_ofs)+4, ia_pd_id, 4); /* t1 and t2, odd that we need them, but here it is */ putULong((unsigned char *)reply_data+(*reply_ofs)+8, 0); putULong((unsigned char *)reply_data+(*reply_ofs)+12, 0); /* * Get ready for next IA_PD. */ *reply_ofs += (len + 16); exit: option_state_dereference(&host_opt_state, MDL); } static void iterate_over_ia_pd(struct data_string *reply_ret, struct packet *packet, const struct data_string *client_id, const struct data_string *server_id, const char *packet_type, void (*ia_pd_match)(), void (*ia_pd_nomatch)()) { struct data_string reply_new; int reply_len; 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_PD options */ struct data_string cli_enc_opt_data; struct option_state *cli_enc_opt_state; struct host_decl *host; struct data_string iaprefix; char reply_data[65536]; int reply_ofs; struct iasubopt *prefix; struct ia_xx *existing_ia_pd; int i; struct data_string key; u_int32_t iaid; /* * Initialize to empty values, in case we have to exit early. */ memset(&reply_new, 0, sizeof(reply_new)); opt_state = NULL; memset(&cli_enc_opt_data, 0, sizeof(cli_enc_opt_data)); cli_enc_opt_state = NULL; memset(&iaprefix, 0, sizeof(iaprefix)); prefix = NULL; /* * Compute the available length for the reply. */ reply_len = sizeof(reply_data) - reply_ret->len; reply_ofs = 0; /* * Find the host record that matches from the packet, if any. */ packet_host = NULL; find_hosts6(&packet_host, packet, client_id, MDL); /* * Build our option state for reply. */ opt_state = NULL; if (!option_state_allocate(&opt_state, MDL)) { log_error("iterate_over_ia_pd: no memory for option_state."); goto exit; } execute_statements_in_scope(NULL, packet, NULL, NULL, packet->options, opt_state, &global_scope, root_group, NULL, NULL); /* * Loop through the IA_PD reported by the client, and deal with * prefixes reported as already in use. */ for (ia = lookup_option(&dhcpv6_universe, packet->options, D6O_IA_PD); ia != NULL; ia = ia->next) { if (!get_encapsulated_IA_state(&cli_enc_opt_state, &cli_enc_opt_data, packet, ia, IA_PD_OFFSET)) { goto exit; } iaid = getULong(cli_enc_opt_data.data); oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, D6O_IAPREFIX); if (oc == NULL) { /* no prefix given for this IA_PD, ignore */ option_state_dereference(&cli_enc_opt_state, MDL); data_string_forget(&cli_enc_opt_data, MDL); continue; } for (; oc != NULL; oc = oc->next) { memset(&iaprefix, 0, sizeof(iaprefix)); if (!evaluate_option_cache(&iaprefix, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("iterate_over_ia_pd: " "error evaluating IAPREFIX."); goto exit; } /* * Now we need to figure out which host record matches * this IA_PD and IAPREFIX (encapsulated option contents * matching a host record by option). * * XXX: We don't currently track IA_PD 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_prefix != NULL) { struct iaddrcidrnetlist *l; int plen = (int) getUChar(iaprefix.data + 8); for (l = host->fixed_prefix; l != NULL; l = l->next) { if (plen != l->cidrnet.bits) continue; if (memcmp(iaprefix.data + 9, l->cidrnet.lo_addr.iabuf, 16) == 0) break; } if ((l != NULL) && (iaprefix.len >= 17)) break; } host = host->n_ipaddr; } if ((host == NULL) && (iaprefix.len >= IAPREFIX_OFFSET)) { /* * Find existing IA_PD. */ if (ia_make_key(&key, iaid, (char *)client_id->data, client_id->len, MDL) != ISC_R_SUCCESS) { log_fatal("iterate_over_ia_pd: no memory for " "key."); } existing_ia_pd = NULL; if (ia_hash_lookup(&existing_ia_pd, ia_pd_active, (unsigned char *)key.data, key.len, MDL)) { /* * Make sure this prefix is in the IA_PD. */ for (i = 0; i < existing_ia_pd->num_iasubopt; i++) { struct iasubopt *tmp; u_int8_t plen; plen = getUChar(iaprefix.data + 8); tmp = existing_ia_pd->iasubopt[i]; if ((tmp->plen == plen) && (memcmp(&tmp->addr, iaprefix.data + 9, 16) == 0)) { iasubopt_reference(&prefix, tmp, MDL); break; } } } data_string_forget(&key, MDL); } if ((host != NULL) || (prefix != NULL)) { ia_pd_match(client_id, &iaprefix, prefix); } else { ia_pd_nomatch(client_id, &iaprefix, (u_int32_t *)cli_enc_opt_data.data, packet, reply_data, &reply_ofs, reply_len - reply_ofs); } if (prefix != NULL) { iasubopt_dereference(&prefix, MDL); } data_string_forget(&iaprefix, MDL); } option_state_dereference(&cli_enc_opt_state, MDL); data_string_forget(&cli_enc_opt_data, MDL); } /* * Return our reply to the caller. * The IA_NA routine has already filled at least the header. */ reply_new.len = reply_ret->len + reply_ofs; if (!buffer_allocate(&reply_new.buffer, reply_new.len, MDL)) { log_fatal("No memory to store reply."); } reply_new.data = reply_new.buffer->data; memcpy(reply_new.buffer->data, reply_ret->buffer->data, reply_ret->len); memcpy(reply_new.buffer->data + reply_ret->len, reply_data, reply_ofs); data_string_forget(reply_ret, MDL); data_string_copy(reply_ret, &reply_new, MDL); data_string_forget(&reply_new, MDL); exit: if (prefix != NULL) { iasubopt_dereference(&prefix, MDL); } if (iaprefix.buffer != NULL) { data_string_forget(&iaprefix, 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); } } /* * Release means a client is done with the leases. */ 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; } /* If the RELEASE arrived via unicast and unicast option isn't set, * reject it per RFC 3315, Sec 18.2.6 */ if (packet->unicast == ISC_TRUE && is_unicast_option_defined(packet) == ISC_FALSE) { unicast_reject(reply, packet, &client_id, &server_id); } else { /* * 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); /* * And operate on each IA_PD in this packet. */ iterate_over_ia_pd(reply, packet, &client_id, &server_id, "Release", ia_pd_match_release, ia_pd_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. */ 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 that would cause us to allocate * resources to the client. */ lease_to_client(reply, packet, &client_id, server_id.data != NULL ? &server_id : NULL); /* * 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 */ /* DHCPv6 server side */ static void dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) { 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 a_opt, packet_ero; struct option_state *opt_state; static char reply_data[65536]; struct dhcpv6_relay_packet *reply; int reply_ofs; /* * Initialize variables for early exit. */ opt_state = NULL; memset(&a_opt, 0, sizeof(a_opt)); memset(&packet_ero, 0, sizeof(packet_ero)); memset(&enc_reply, 0, sizeof(enc_reply)); memset(&enc_opt_data, 0, sizeof(enc_opt_data)); enc_packet = NULL; /* * 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; } if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, NULL, NULL, &global_scope, oc, MDL)) { /* should be dhcpv6_relay_forw */ log_error("dhcpv6_forw_relay: error evaluating " "relayed message."); goto exit; } if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) { /* should be dhcpv6_relay_forw */ 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)) { /* should be dhcpv6_relay_forw */ log_error("dhcpv6_forw_relay: " "no memory for encapsulated packet."); goto exit; } if (!option_state_allocate(&enc_packet->options, MDL)) { /* should be dhcpv6_relay_forw */ 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; interface_reference(&enc_packet->interface, packet->interface, MDL); enc_packet->dhcpv6_container_packet = packet; msg_type = enc_opt_data.data[0]; if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) { int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options)); 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 - relaylen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ goto exit; } } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || (msg_type == DHCPV6_DHCPV4_RESPONSE)) { #ifdef DHCP4o6 if (!dhcpv4_over_dhcpv6 || (msg_type == DHCPV6_DHCPV4_RESPONSE)) { log_error("dhcpv6_relay_forw: " "unsupported %s message type.", dhcpv6_type_names[msg_type]); goto exit; } forw_dhcpv4_query(packet); goto exit; #else /* DHCP4o6 */ log_error("dhcpv6_relay_forw: unsupported %s message type.", dhcpv6_type_names[msg_type]); goto exit; #endif /* DHCP4o6 */ } else { int msglen = (int)(offsetof(struct dhcpv6_packet, options)); 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 - msglen, &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; } /* * Now we can use the reply_data buffer. * Packet header stuff all comes from the forward message. */ reply = (struct dhcpv6_relay_packet *)reply_data; 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)); reply_ofs = (int)(offsetof(struct dhcpv6_relay_packet, options)); /* * Get the reply option state. */ opt_state = NULL; if (!option_state_allocate(&opt_state, MDL)) { log_error("dhcpv6_relay_forw: no memory for option state."); goto exit; } /* * Append the interface-id if present. */ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_INTERFACE_ID); if (oc != NULL) { if (!evaluate_option_cache(&a_opt, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("dhcpv6_relay_forw: error evaluating " "Interface ID."); goto exit; } if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)a_opt.data, a_opt.len, D6O_INTERFACE_ID, 0)) { log_error("dhcpv6_relay_forw: error saving " "Interface ID."); goto exit; } data_string_forget(&a_opt, MDL); } #if defined(RELAY_PORT) /* * Append the relay_source_port option if present. */ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_SOURCE_PORT); if (oc != NULL) { if (!evaluate_option_cache(&a_opt, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("dhcpv6_relay_forw: error evaluating " "Relay Source Port."); goto exit; } if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)a_opt.data, a_opt.len, D6O_RELAY_SOURCE_PORT, 0)) { log_error("dhcpv6_relay_forw: error saving " "Relay Source Port."); goto exit; } data_string_forget(&a_opt, MDL); packet->relay_source_port = ISC_TRUE; } #endif /* * Append our encapsulated stuff for caller. */ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)enc_reply.data, enc_reply.len, D6O_RELAY_MSG, 0)) { log_error("dhcpv6_relay_forw: error saving Relay MSG."); goto exit; } /* * Get the ERO if any. */ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ERO); if (oc != NULL) { unsigned req; int i; if (!evaluate_option_cache(&packet_ero, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL) || (packet_ero.len & 1)) { log_error("dhcpv6_relay_forw: error evaluating ERO."); goto exit; } /* Decode and apply the ERO. */ for (i = 0; i < packet_ero.len; i += 2) { req = getUShort(packet_ero.data + i); /* Already in the reply? */ oc = lookup_option(&dhcpv6_universe, opt_state, req); if (oc != NULL) continue; /* Get it from the packet if present. */ oc = lookup_option(&dhcpv6_universe, packet->options, req); if (oc == NULL) continue; if (!evaluate_option_cache(&a_opt, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("dhcpv6_relay_forw: error " "evaluating option %u.", req); goto exit; } if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)a_opt.data, a_opt.len, req, 0)) { log_error("dhcpv6_relay_forw: error saving " "option %u.", req); goto exit; } data_string_forget(&a_opt, MDL); } } reply_ofs += store_options6(reply_data + reply_ofs, sizeof(reply_data) - reply_ofs, opt_state, packet, required_opts_agent, &packet_ero); /* * Return our reply to the caller. */ reply_ret->len = reply_ofs; 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_data, reply_ofs); exit: if (opt_state != NULL) option_state_dereference(&opt_state, MDL); if (a_opt.data != NULL) { data_string_forget(&a_opt, MDL); } if (packet_ero.data != NULL) { data_string_forget(&packet_ero, 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); } } #ifdef DHCP4o6 /* \brief Internal processing of a relayed DHCPv4-query * (DHCPv4 server side) * * Code copied from \ref dhcpv6_relay_forw() which itself is * from \ref do_packet6(). * * \param reply_ret pointer to the response * \param packet the query */ static void dhcp4o6_relay_forw(struct data_string *reply_ret, struct packet *packet) { struct option_cache *oc; struct data_string enc_opt_data; struct packet *enc_packet; unsigned char msg_type; const struct dhcpv6_relay_packet *relay; const struct dhcpv4_over_dhcpv6_packet *msg; 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 a_opt, packet_ero; struct option_state *opt_state; static char reply_data[65536]; struct dhcpv6_relay_packet *reply; int reply_ofs; /* * Initialize variables for early exit. */ opt_state = NULL; memset(&a_opt, 0, sizeof(a_opt)); memset(&packet_ero, 0, sizeof(packet_ero)); memset(&enc_reply, 0, sizeof(enc_reply)); memset(&enc_opt_data, 0, sizeof(enc_opt_data)); enc_packet = NULL; /* * 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; } if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, NULL, NULL, &global_scope, oc, MDL)) { log_error("dhcp4o6_relay_forw: error evaluating " "relayed message."); goto exit; } if (!packet6_len_okay((char *)enc_opt_data.data, enc_opt_data.len)) { log_error("dhcp4o6_relay_forw: " "encapsulated packet too short."); goto exit; } /* * Build a packet structure from this encapsulated packet. */ if (!packet_allocate(&enc_packet, MDL)) { log_error("dhcp4o6_relay_forw: " "no memory for encapsulated packet."); goto exit; } if (!option_state_allocate(&enc_packet->options, MDL)) { log_error("dhcp4o6_relay_forw: " "no memory for encapsulated packet's options."); goto exit; } enc_packet->client_port = packet->client_port; enc_packet->client_addr = packet->client_addr; interface_reference(&enc_packet->interface, packet->interface, MDL); enc_packet->dhcpv6_container_packet = packet; msg_type = enc_opt_data.data[0]; if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) { int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options)); 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 - relaylen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ goto exit; } } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || (msg_type == DHCPV6_DHCPV4_RESPONSE)) { int msglen = (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); msg = (struct dhcpv4_over_dhcpv6_packet *)enc_opt_data.data; enc_packet->dhcpv6_msg_type = msg->msg_type; /* message-specific data */ memcpy(enc_packet->dhcp4o6_flags, msg->flags, sizeof(enc_packet->dhcp4o6_flags)); if (!parse_option_buffer(enc_packet->options, msg->options, enc_opt_data.len - msglen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ goto exit; } } else { log_error("dhcp4o6_relay_forw: unexpected message of type %d.", (int)msg_type); 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; } /* * Now we can use the reply_data buffer. * Packet header stuff all comes from the forward message. */ reply = (struct dhcpv6_relay_packet *)reply_data; 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)); reply_ofs = (int)(offsetof(struct dhcpv6_relay_packet, options)); /* * Get the reply option state. */ if (!option_state_allocate(&opt_state, MDL)) { log_error("dhcp4o6_relay_forw: no memory for option state."); goto exit; } /* * Append the interface-id if present. */ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_INTERFACE_ID); if (oc != NULL) { if (!evaluate_option_cache(&a_opt, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("dhcp4o6_relay_forw: error evaluating " "Interface ID."); goto exit; } if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)a_opt.data, a_opt.len, D6O_INTERFACE_ID, 0)) { log_error("dhcp4o6_relay_forw: error saving " "Interface ID."); goto exit; } data_string_forget(&a_opt, MDL); } #if defined(RELAY_PORT) /* * Append the relay_source_port option if present. */ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_RELAY_SOURCE_PORT); if (oc != NULL) { if (!evaluate_option_cache(&a_opt, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("dhcpv4o6_relay_forw: error evaluating " "Relay Source Port."); goto exit; } if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)a_opt.data, a_opt.len, D6O_RELAY_SOURCE_PORT, 0)) { log_error("dhcpv4o6_relay_forw: error saving " "Relay Source Port."); goto exit; } data_string_forget(&a_opt, MDL); packet->relay_source_port = ISC_TRUE; } #endif /* * Append our encapsulated stuff for caller. */ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)enc_reply.data, enc_reply.len, D6O_RELAY_MSG, 0)) { log_error("dhcp4o6_relay_forw: error saving Relay MSG."); goto exit; } /* * Get the ERO if any. */ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_ERO); if (oc != NULL) { unsigned req; int i; if (!evaluate_option_cache(&packet_ero, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL) || (packet_ero.len & 1)) { log_error("dhcp4o6_relay_forw: error evaluating ERO."); goto exit; } /* Decode and apply the ERO. */ for (i = 0; i < packet_ero.len; i += 2) { req = getUShort(packet_ero.data + i); /* Already in the reply? */ oc = lookup_option(&dhcpv6_universe, opt_state, req); if (oc != NULL) continue; /* Get it from the packet if present. */ oc = lookup_option(&dhcpv6_universe, packet->options, req); if (oc == NULL) continue; if (!evaluate_option_cache(&a_opt, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("dhcp4o6_relay_forw: error " "evaluating option %u.", req); goto exit; } if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)a_opt.data, a_opt.len, req, 0)) { log_error("dhcp4o6_relay_forw: error saving " "option %u.", req); goto exit; } data_string_forget(&a_opt, MDL); } } reply_ofs += store_options6(reply_data + reply_ofs, sizeof(reply_data) - reply_ofs, opt_state, packet, required_opts_agent, &packet_ero); /* * Return our reply to the caller. */ reply_ret->len = reply_ofs; 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_data, reply_ofs); exit: if (opt_state != NULL) option_state_dereference(&opt_state, MDL); if (a_opt.data != NULL) { data_string_forget(&a_opt, MDL); } if (packet_ero.data != NULL) { data_string_forget(&packet_ero, 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); } } /* * \brief Internal processing of a DHCPv4-query * (DHCPv4 server function) * * Code copied from \ref do_packet(). * * \param reply_ret pointer to the response * \param packet the query */ static void dhcp4o6_dhcpv4_query(struct data_string *reply_ret, struct packet *packet) { struct option_cache *oc; struct data_string enc_opt_data; struct packet *enc_packet; struct data_string enc_response; struct option_state *opt_state; static char response_data[65536]; struct dhcpv4_over_dhcpv6_packet *response; int response_ofs; /* * Initialize variables for early exit. */ opt_state = NULL; memset(&enc_response, 0, sizeof(enc_response)); memset(&enc_opt_data, 0, sizeof(enc_opt_data)); enc_packet = NULL; /* * Get our encapsulated relay message. */ oc = lookup_option(&dhcpv6_universe, packet->options, D6O_DHCPV4_MSG); if (oc == NULL) { log_info("DHCPv4-query from %s missing DHCPv4 Message option.", piaddr(packet->client_addr)); goto exit; } if (!evaluate_option_cache(&enc_opt_data, NULL, NULL, NULL, NULL, NULL, &global_scope, oc, MDL)) { log_error("dhcp4o6_dhcpv4_query: error evaluating " "DHCPv4 message."); goto exit; } if (enc_opt_data.len < DHCP_FIXED_NON_UDP) { log_error("dhcp4o6_dhcpv4_query: DHCPv4 packet too short."); goto exit; } /* * Build a packet structure from this encapsulated packet. */ if (!packet_allocate(&enc_packet, MDL)) { log_error("dhcp4o6_dhcpv4_query: " "no memory for encapsulated packet."); goto exit; } enc_packet->raw = (struct dhcp_packet *)enc_opt_data.data; enc_packet->packet_length = enc_opt_data.len; enc_packet->dhcp4o6_response = &enc_response; enc_packet->client_port = packet->client_port; enc_packet->client_addr = packet->client_addr; interface_reference(&enc_packet->interface, packet->interface, MDL); enc_packet->dhcpv6_container_packet = packet; if (packet->dhcp4o6_flags[0] & DHCP4O6_QUERY_UNICAST) enc_packet->unicast = 1; if (enc_packet->raw->hlen > sizeof(enc_packet->raw->chaddr)) { log_info("dhcp4o6_dhcpv4_query: " "discarding packet with bogus hlen."); goto exit; } /* Allocate packet->options now so it is non-null for all packets */ if (!option_state_allocate (&enc_packet->options, MDL)) { log_error("dhcp4o6_dhcpv4_query: no memory for options."); goto exit; } /* If there's an option buffer, try to parse it. */ if (enc_packet->packet_length >= DHCP_FIXED_NON_UDP + 4) { struct option_cache *op; if (!parse_options(enc_packet)) { if (enc_packet->options) option_state_dereference (&enc_packet->options, MDL); packet_dereference (&enc_packet, MDL); goto exit; } if (enc_packet->options_valid && (op = lookup_option(&dhcp_universe, enc_packet->options, DHO_DHCP_MESSAGE_TYPE))) { struct data_string dp; memset(&dp, 0, sizeof dp); evaluate_option_cache(&dp, enc_packet, NULL, NULL, enc_packet->options, NULL, NULL, op, MDL); if (dp.len > 0) enc_packet->packet_type = dp.data[0]; else enc_packet->packet_type = 0; data_string_forget(&dp, MDL); } } if (validate_packet(enc_packet) != 0) { if (enc_packet->packet_type) dhcp(enc_packet); else bootp(enc_packet); } /* If the caller kept the packet, they'll have upped the refcnt. */ packet_dereference(&enc_packet, MDL); /* * If we got no response data, then it is discarded, and * our DHCPv4-response is also discarded. */ if (enc_response.data == NULL) { goto exit; } /* * Now we can use the response_data buffer. */ response = (struct dhcpv4_over_dhcpv6_packet *)response_data; response->msg_type = DHCPV6_DHCPV4_RESPONSE; response->flags[0] = response->flags[1] = response->flags[2] = 0; response_ofs = (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); /* * Get the response option state. */ if (!option_state_allocate(&opt_state, MDL)) { log_error("dhcp4o6_dhcpv4_query: no memory for option state."); goto exit; } /* * Append our encapsulated stuff for caller. */ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, (unsigned char *)enc_response.data, enc_response.len, D6O_DHCPV4_MSG, 0)) { log_error("dhcp4o6_dhcpv4_query: error saving DHCPv4 MSG."); goto exit; } response_ofs += store_options6(response_data + response_ofs, sizeof(response_data) - response_ofs, opt_state, packet, required_opts_4o6, NULL); /* * Return our response to the caller. */ reply_ret->len = response_ofs; reply_ret->buffer = NULL; if (!buffer_allocate(&reply_ret->buffer, reply_ret->len, MDL)) { log_fatal("dhcp4o6_dhcpv4_query: no memory to store reply."); } reply_ret->data = reply_ret->buffer->data; memcpy(reply_ret->buffer->data, response_data, response_ofs); exit: if (opt_state != NULL) option_state_dereference(&opt_state, MDL); if (enc_response.data != NULL) { data_string_forget(&enc_response, MDL); } if (enc_opt_data.data != NULL) { data_string_forget(&enc_opt_data, MDL); } if (enc_packet != NULL) { packet_dereference(&enc_packet, MDL); } } /* * \brief Forward a DHCPv4-query message to the DHCPv4 side * (DHCPv6 server function) * * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-query message * * \brief packet the DHCPv6 DHCPv4-query message */ static void forw_dhcpv4_query(struct packet *packet) { struct data_string ds; struct udp_data4o6 udp_data; unsigned len; int cc; /* Get the initial message. */ while (packet->dhcpv6_container_packet != NULL) packet = packet->dhcpv6_container_packet; /* Check the initial message. */ if ((packet->raw == NULL) || (packet->client_addr.len != 16) || (packet->interface == NULL)) { log_error("forw_dhcpv4_query: can't find initial message."); return; } /* Get a buffer. */ len = packet->packet_length + 36; memset(&ds, 0, sizeof(ds)); if (!buffer_allocate(&ds.buffer, len, MDL)) { log_error("forw_dhcpv4_query: " "no memory for encapsulating packet."); return; } ds.data = ds.buffer->data; ds.len = len; /* Fill the buffer. */ strncpy((char *)ds.buffer->data, packet->interface->name, 16); memcpy(ds.buffer->data + 16, packet->client_addr.iabuf, 16); memset(&udp_data, 0, sizeof(udp_data)); udp_data.src_port = packet->client_port; memcpy(ds.buffer->data + 32, &udp_data, 4); memcpy(ds.buffer->data + 36, (unsigned char *)packet->raw, packet->packet_length); /* Forward to the DHCPv4 server. */ cc = send(dhcp4o6_fd, ds.data, ds.len, 0); if (cc < 0) log_error("forw_dhcpv4_query: send(): %m"); data_string_forget(&ds, MDL); } #endif 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)); /* I would like to classify the client once here, but * as I don't want to classify all of the incoming packets * I need to do it before handling specific types. * We don't need to classify if we are tossing the packet * or if it is a relay - the classification step will get * done when we process the inner client packet. */ switch (packet->dhcpv6_msg_type) { case DHCPV6_SOLICIT: classify_client(packet); dhcpv6_solicit(reply, packet); break; case DHCPV6_ADVERTISE: dhcpv6_discard(packet); break; case DHCPV6_REQUEST: classify_client(packet); dhcpv6_request(reply, packet); break; case DHCPV6_CONFIRM: classify_client(packet); dhcpv6_confirm(reply, packet); break; case DHCPV6_RENEW: classify_client(packet); dhcpv6_renew(reply, packet); break; case DHCPV6_REBIND: classify_client(packet); dhcpv6_rebind(reply, packet); break; case DHCPV6_REPLY: dhcpv6_discard(packet); break; case DHCPV6_RELEASE: classify_client(packet); dhcpv6_release(reply, packet); break; case DHCPV6_DECLINE: classify_client(packet); dhcpv6_decline(reply, packet); break; case DHCPV6_RECONFIGURE: dhcpv6_discard(packet); break; case DHCPV6_INFORMATION_REQUEST: classify_client(packet); dhcpv6_information_request(reply, packet); break; case DHCPV6_RELAY_FORW: #ifdef DHCP4o6 if (dhcpv4_over_dhcpv6 && (local_family == AF_INET)) dhcp4o6_relay_forw(reply, packet); else #endif /* DHCP4o6 */ dhcpv6_relay_forw(reply, packet); break; case DHCPV6_RELAY_REPL: dhcpv6_discard(packet); break; case DHCPV6_LEASEQUERY: classify_client(packet); dhcpv6_leasequery(reply, packet); break; case DHCPV6_LEASEQUERY_REPLY: dhcpv6_discard(packet); break; case DHCPV6_DHCPV4_QUERY: #ifdef DHCP4o6 if (dhcpv4_over_dhcpv6) { if (local_family == AF_INET6) { forw_dhcpv4_query(packet); } else { dhcp4o6_dhcpv4_query(reply, packet); } } else #endif /* DHCP4o6 */ dhcpv6_discard(packet); break; case DHCPV6_DHCPV4_RESPONSE: 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; 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 if ((packet->dhcpv6_msg_type != DHCPV6_DHCPV4_QUERY) && (packet->dhcpv6_msg_type != DHCPV6_DHCPV4_RESPONSE)) { 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; } #if defined (REPLY_TO_SOURCE_PORT) /* * This appears to have been included for testing so we would * not need a root client, but was accidently left in the * final code. We continue to include it in case * some users have come to rely upon it, but leave * it off by default as it's a bad idea. */ to_addr.sin6_port = packet->client_port; #endif #if defined(RELAY_PORT) /* * Check relay source port. */ if (packet->relay_source_port) { to_addr.sin6_port = packet->client_port; } #endif memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf, sizeof(to_addr.sin6_addr)); 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); } } #ifdef DHCP4o6 /* * \brief Receive a DHCPv4-query message from the DHCPv6 side * (DHCPv4 server function) * * Receive a message with a DHCPv4-query inside from the DHCPv6 server. * (code copied from \ref do_packet6() \ref and dhcpv6()) * * Format: interface:16 + address:16 + udp:4 + DHCPv6 DHCPv4-query message * * \param raw the DHCPv6 DHCPv4-query message raw content */ static void recv_dhcpv4_query(struct data_string *raw) { struct interface_info *ip; char name[16 + 1]; struct iaddr iaddr; struct packet *packet; unsigned char msg_type; const struct dhcpv6_relay_packet *relay; const struct dhcpv4_over_dhcpv6_packet *msg; struct data_string reply; struct data_string ds; struct udp_data4o6 udp_data; unsigned len; int cc; memset(name, 0, sizeof(name)); memcpy(name, raw->data, 16); for (ip = interfaces; ip != NULL; ip = ip->next) { if (!strcmp(name, ip->name)) break; } if (ip == NULL) { log_error("recv_dhcpv4_query: can't find interface %s.", name); return; } iaddr.len = 16; memcpy(iaddr.iabuf, raw->data + 16, 16); memset(&udp_data, 0, sizeof(udp_data)); memcpy(&udp_data, raw->data + 32, 4); /* * From do_packet6(). */ if (!packet6_len_okay((char *)raw->data + 36, raw->len - 36)) { log_error("recv_dhcpv4_query: " "short packet from %s, len %d, dropped", piaddr(iaddr), raw->len - 36); return; } /* * Build a packet structure. */ packet = NULL; if (!packet_allocate(&packet, MDL)) { log_error("recv_dhcpv4_query: no memory for packet."); return; } if (!option_state_allocate(&packet->options, MDL)) { log_error("recv_dhcpv4_query: no memory for options."); packet_dereference(&packet, MDL); return; } packet->raw = (struct dhcp_packet *)(raw->data + 36); packet->packet_length = raw->len - 36; packet->client_port = udp_data.src_port; packet->client_addr = iaddr; interface_reference(&packet->interface, ip, MDL); msg_type = raw->data[36]; if ((msg_type == DHCPV6_RELAY_FORW) || (msg_type == DHCPV6_RELAY_REPL)) { int relaylen = (int)(offsetof(struct dhcpv6_relay_packet, options)); relay = (const struct dhcpv6_relay_packet *)(raw->data + 36); packet->dhcpv6_msg_type = relay->msg_type; /* relay-specific data */ packet->dhcpv6_hop_count = relay->hop_count; memcpy(&packet->dhcpv6_link_address, relay->link_address, sizeof(relay->link_address)); memcpy(&packet->dhcpv6_peer_address, relay->peer_address, sizeof(relay->peer_address)); if (!parse_option_buffer(packet->options, relay->options, raw->len - 36 - relaylen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ packet_dereference(&packet, MDL); return; } } else if ((msg_type == DHCPV6_DHCPV4_QUERY) || (msg_type == DHCPV6_DHCPV4_RESPONSE)) { int msglen = (int)(offsetof(struct dhcpv4_over_dhcpv6_packet, options)); msg = (struct dhcpv4_over_dhcpv6_packet *)(raw->data + 36); packet->dhcpv6_msg_type = msg->msg_type; /* message-specific data */ memcpy(packet->dhcp4o6_flags, msg->flags, sizeof(packet->dhcp4o6_flags)); if (!parse_option_buffer(packet->options, msg->options, raw->len - 36 - msglen, &dhcpv6_universe)) { /* no logging here, as parse_option_buffer() logs all cases where it fails */ packet_dereference(&packet, MDL); return; } } else { log_error("recv_dhcpv4_query: unexpected message of type %d.", (int)msg_type); packet_dereference(&packet, MDL); return; } /* * From dhcpv6(). */ /* * Log a message that we received this packet. */ /* log_packet_in(packet); */ memset(&ds, 0, sizeof(ds)); if (packet->dhcpv6_msg_type < dhcpv6_type_name_max) { data_string_sprintfa(&ds, "%s message from %s", dhcpv6_type_names[packet->dhcpv6_msg_type], piaddr(packet->client_addr)); } else { data_string_sprintfa(&ds, "Unknown message type %d from %s", packet->dhcpv6_msg_type, piaddr(packet->client_addr)); } if ((packet->dhcpv6_msg_type == DHCPV6_RELAY_FORW) || (packet->dhcpv6_msg_type == DHCPV6_RELAY_REPL)) { char tmp_addr[INET6_ADDRSTRLEN]; const void *addr; addr = &packet->dhcpv6_link_address; data_string_sprintfa(&ds, ", link address %s", inet_ntop(AF_INET6, addr, tmp_addr, sizeof(tmp_addr))); addr = &packet->dhcpv6_peer_address; data_string_sprintfa(&ds, ", peer address %s", inet_ntop(AF_INET6, addr, tmp_addr, sizeof(tmp_addr))); } else if ((packet->dhcpv6_msg_type != DHCPV6_DHCPV4_QUERY) && (packet->dhcpv6_msg_type != DHCPV6_DHCPV4_RESPONSE)) { u_int32_t tid = 0; memcpy(((char *)&tid)+1, packet->dhcpv6_transaction_id, 3); data_string_sprintfa(&ds, ", transaction ID 0x%06X", tid); } log_info("%s", ds.data); data_string_forget(&ds, MDL); /* * Build our reply packet. */ build_dhcpv6_reply(&reply, packet); if (reply.data == NULL) { packet_dereference(&packet, MDL); return; } /* * Forward the response. */ len = reply.len + 36; memset(&ds, 0, sizeof(ds)); if (!buffer_allocate(&ds.buffer, len, MDL)) { log_error("recv_dhcpv4_query: no memory."); packet_dereference(&packet, MDL); return; } ds.data = ds.buffer->data; ds.len = len; memcpy(ds.buffer->data, name, 16); memcpy(ds.buffer->data + 16, iaddr.iabuf, 16); udp_data.rsp_opt_exist = packet->relay_source_port ? 1 : 0; memcpy(ds.buffer->data + 32, &udp_data, 4); memcpy(ds.buffer->data + 36, reply.data, reply.len); /* * Now we can release the packet. */ packet_dereference(&packet, MDL); cc = send(dhcp4o6_fd, ds.data, ds.len, 0); if (cc < 0) log_error("recv_dhcpv4_query: send(): %m"); data_string_forget(&ds, MDL); } #endif /* DHCP4o6 */ static void seek_shared_host(struct host_decl **hp, struct shared_network *shared) { struct host_decl *nofixed = NULL; struct host_decl *seek, *hold = NULL; /* * Seek forward through fixed addresses for the right link. * * Note: how to do this for fixed prefixes??? */ host_reference(&hold, *hp, MDL); host_dereference(hp, MDL); seek = hold; while (seek != NULL) { if (seek->fixed_addr == NULL) nofixed = seek; else if (fixed_matches_shared(seek, shared)) break; seek = seek->n_ipaddr; } if ((seek == NULL) && (nofixed != NULL)) seek = nofixed; if (seek != NULL) host_reference(hp, seek, MDL); } static isc_boolean_t fixed_matches_shared(struct host_decl *host, struct shared_network *shared) { struct subnet *subnet; struct data_string addr; isc_boolean_t matched; struct iaddr fixed; if (host->fixed_addr == NULL) return ISC_FALSE; memset(&addr, 0, sizeof(addr)); if (!evaluate_option_cache(&addr, NULL, NULL, NULL, NULL, NULL, &global_scope, host->fixed_addr, MDL)) return ISC_FALSE; if (addr.len < 16) { data_string_forget(&addr, MDL); return ISC_FALSE; } fixed.len = 16; memcpy(fixed.iabuf, addr.data, 16); matched = ISC_FALSE; for (subnet = shared->subnets ; subnet != NULL ; subnet = subnet->next_sibling) { if (addr_eq(subnet_number(fixed, subnet->netmask), subnet->net)) { matched = ISC_TRUE; break; } } data_string_forget(&addr, MDL); return matched; } /*! * * \brief Constructs a REPLY with status of UseMulticast to a given packet * * Per RFC 3315 Secs 18.2.1,3,6 & 7, when a server rejects a client's * unicast-sent packet, the response must only contain the client id, * server id, and a status code option of 5 (UseMulticast). This function * constructs such a packet and returns it as a data_string. * * \param reply_ret = data_string which will receive the newly constructed * reply * \param packet = client request which is being rejected * \param client_id = data_string which contains the client id * \param server_id = data_string which which contains the server id * */ void unicast_reject(struct data_string *reply_ret, struct packet *packet, const struct data_string *client_id, const struct data_string *server_id) { struct reply_state reply; memset(&reply, 0x0, sizeof(struct reply_state)); /* Locate the client. */ if (shared_network_from_packet6(&reply.shared, packet) != ISC_R_SUCCESS) { log_error("unicast_reject: could not locate client."); return; } /* Initialize the reply. */ packet_reference(&reply.packet, packet, MDL); data_string_copy(&reply.client_id, client_id, MDL); if (start_reply(packet, client_id, server_id, &reply.opt_state, &reply.buf.reply)) { /* Set the UseMulticast status code. */ if (!set_status_code(STATUS_UseMulticast, "Unicast not allowed by server.", reply.opt_state)) { log_error("unicast_reject: Unable to set status code."); } else { /* Set write cursor to just past the reply header. */ reply.cursor = REPLY_OPTIONS_INDEX; reply.cursor += store_options6(((char *)reply.buf.data + reply.cursor), (sizeof(reply.buf) - reply.cursor), reply.opt_state, reply.packet, unicast_reject_opts, NULL); /* Return our reply to the caller. */ reply_ret->len = reply.cursor; reply_ret->buffer = NULL; if (!buffer_allocate(&reply_ret->buffer, reply.cursor, MDL)) { log_fatal("unicast_reject:" "No memory to store Reply."); } memcpy(reply_ret->buffer->data, reply.buf.data, reply.cursor); reply_ret->data = reply_ret->buffer->data; } } /* Cleanup. */ if (reply.shared != NULL) shared_network_dereference(&reply.shared, MDL); if (reply.opt_state != NULL) option_state_dereference(&reply.opt_state, MDL); if (reply.packet != NULL) packet_dereference(&reply.packet, MDL); if (reply.client_id.data != NULL) data_string_forget(&reply.client_id, MDL); } /*! * * \brief Checks if the dhcp6.unicast option has been defined * * Scans the option space for the presence of the dhcp6.unicast option. The * function attempts to map the inbound packet to a shared network first * by an ip address specified via an D6O_IA_XX option and if that fails then * by the packet's source information (e.g. relay link, link, or interace). * Once the packet is mapped to a shared network, the function executes all * statements from the network's group outward into a local option cache. * The option cache is then scanned for the presence of unicast option. If * the packet cannot be mapped to a shared network, the function returns * ISC_FALSE. * \param packet inbound packet from the client * * \return ISC_TRUE if the dhcp6.unicast option is defined, false otherwise. * */ isc_boolean_t is_unicast_option_defined(struct packet *packet) { isc_boolean_t is_defined = ISC_FALSE; struct option_state *opt_state = NULL; struct option_cache *oc = NULL; struct shared_network *shared = NULL; if (!option_state_allocate(&opt_state, MDL)) { log_fatal("is_unicast_option_defined:" "No memory for option state."); } /* We try to map the packet to a network first by an IA_XX value. * If that fails, we try by packet source. */ if (((shared_network_from_requested_addr(&shared, packet) != ISC_R_SUCCESS) && (shared_network_from_packet6(&shared, packet) != ISC_R_SUCCESS)) || (shared == NULL)) { /* @todo what would this really mean? I think wrong network * logic will catch it */ log_error("is_unicast_option_defined:" "cannot attribute packet to a network."); return (ISC_FALSE); } /* Now that we've mapped it to a network, execute statments to that * scope, looking for the unicast option. We don't care about the * value of the option, only whether or not it is defined. */ execute_statements_in_scope(NULL, NULL, NULL, NULL, NULL, opt_state, &global_scope, shared->group, NULL, NULL); oc = lookup_option(&dhcpv6_universe, opt_state, D6O_UNICAST); is_defined = (oc != NULL ? ISC_TRUE : ISC_FALSE); log_debug("is_unicast_option_defined: option found : %d", is_defined); if (shared != NULL) { shared_network_dereference(&shared, MDL); } if (opt_state != NULL) { option_state_dereference(&opt_state, MDL); } return (is_defined); } /*! * * \brief Maps a packet to a shared network based on the requested IP address * * The function attempts to find a subnet that matches the first requested IP * address contained within the given packet. Note that it looks first for * D6O_IA_NAs, then D6O_IA_PDs and lastly D6O_IA_TAs. If a matching network is * found, a reference to it is returned in the parameter, shared. * * \param shared shared_network pointer which will receive the matching network * \param packet inbound packet from the client * * \return ISC_R_SUCCESS if the packet can be mapped to a shared_network. * */ static isc_result_t shared_network_from_requested_addr (struct shared_network **shared, struct packet* packet) { struct iaddr iaddr; struct subnet* subnet = NULL; isc_result_t status = ISC_R_FAILURE; /* Try to match first IA_ address or prefix we find to a subnet. In * theory all IA_ values in a given request are supposed to be in the * same subnet so we only need to try one right? */ if ((get_first_ia_addr_val(packet, D6O_IA_NA, &iaddr) != ISC_R_SUCCESS) && (get_first_ia_addr_val(packet, D6O_IA_PD, &iaddr) != ISC_R_SUCCESS) && (get_first_ia_addr_val(packet, D6O_IA_TA, &iaddr) != ISC_R_SUCCESS)) { /* we found nothing to match against */ log_debug("share_network_from_request_addr: nothing to match"); return (ISC_R_FAILURE); } if (!find_subnet(&subnet, iaddr, MDL)) { log_debug("shared_network_from_requested_addr:" "No subnet found for addr %s.", piaddr(iaddr)); } else { status = shared_network_reference(shared, subnet->shared_network, MDL); subnet_dereference(&subnet, MDL); log_debug("shared_network_from_requested_addr:" " found shared network %s for address %s.", ((*shared)->name ? (*shared)->name : "unnamed"), piaddr(iaddr)); return (status); } return (ISC_R_FAILURE); } /*! * * \brief Retrieves the first IP address from a given packet of a given type * * Search a packet for options of a given type (D6O_IA_AN, D6O_IA_PD, or * D6O_IA_TA) for the first non-blank IA_XX value and return its IP address * component. * * \param packet packet received from the client * \param addr_type the address option type (D6O_IA_NA , D6O_IA_PD, or * D6O_IP_TA) to look for within the packet. * \param iaddr pointer to the iaddr structure which will receive the extracted * address. * * \return ISC_R_SUCCESS if an address was succesfully extracted, ISC_R_FALURE * otherwise. * */ static isc_result_t get_first_ia_addr_val (struct packet* packet, int addr_type, struct iaddr* iaddr) { struct option_cache *ia; struct option_cache *oc = NULL; struct data_string cli_enc_opt_data; struct option_state *cli_enc_opt_state; int addr_opt_offset; int addr_opt; int addr_opt_data_len; int ip_addr_offset; isc_result_t status = ISC_R_FAILURE; memset(iaddr, 0, sizeof(struct iaddr)); /* Set up address type specifics */ switch (addr_type) { case D6O_IA_NA: addr_opt_offset = IA_NA_OFFSET; addr_opt = D6O_IAADDR; addr_opt_data_len = 24; ip_addr_offset = 0; break; case D6O_IA_TA: addr_opt_offset = IA_TA_OFFSET; addr_opt = D6O_IAADDR; addr_opt_data_len = 24; ip_addr_offset = 0; break; case D6O_IA_PD: addr_opt_offset = IA_PD_OFFSET; addr_opt = D6O_IAPREFIX; addr_opt_data_len = 25; ip_addr_offset = 9; break; default: /* shouldn't be here */ log_error ("get_first_ia_addr_val: invalid opt type %d", addr_type); return (ISC_R_FAILURE); } /* Find the first, non-blank IA_XX value within an D6O_IA_XX option. */ for (ia = lookup_option(&dhcpv6_universe, packet->options, addr_type); ia != NULL && oc == NULL; ia = ia->next) { if (!get_encapsulated_IA_state(&cli_enc_opt_state, &cli_enc_opt_data, packet, ia, addr_opt_offset)) { log_debug ("get_first_ia_addr_val:" " couldn't unroll enclosing option"); return (ISC_R_FAILURE); } oc = lookup_option(&dhcpv6_universe, cli_enc_opt_state, addr_opt); 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); } } /* If we found a non-blank IA_XX then extract its ip address. */ if (oc != NULL) { struct data_string iaddr_str; memset(&iaddr_str, 0, sizeof(iaddr_str)); if (!evaluate_option_cache(&iaddr_str, packet, NULL, NULL, packet->options, NULL, &global_scope, oc, MDL)) { log_error("get_first_ia_addr_val: " "error evaluating IA_XX option."); } else { if (iaddr_str.len != addr_opt_data_len) { log_error("shared_network_from_requested_addr:" " invalid length %d, expected %d", iaddr_str.len, addr_opt_data_len); } else { iaddr->len = 16; memcpy (iaddr->iabuf, iaddr_str.data + ip_addr_offset, 16); status = ISC_R_SUCCESS; } data_string_forget(&iaddr_str, MDL); } option_state_dereference(&cli_enc_opt_state, MDL); data_string_forget(&cli_enc_opt_data, MDL); } return (status); } /* * \brief Calculates the reply T1/T2 times and stuffs them in outbound buffer * * T1/T2 time selection is kind of weird. We actually use DHCP * (v4) scoped * options, dhcp-renewal-time and dhcp-rebinding-time, as handy existing places * where these can be configured by an administrator. A value of zero tells the * client it may choose its own value. * * When those options are not defined, the values will be set to zero unless * the global option, dhcpv6-set-tee-times is enabled. When this option is * enabled the values are calculated as recommended by RFC 3315, Section 22.4: * * T1 will be set to 0.5 times the shortest preferred lifetime * in the IA_XX option. If the "shortest" preferred lifetime is * 0xFFFFFFFF, T1 will set to 0xFFFFFFFF. * * T2 will be set to 0.8 times the shortest preferred lifetime * in the IA_XX option. If the "shortest" preferred lifetime is * 0xFFFFFFFF, T2 will set to 0xFFFFFFFF. * * Note that dhcpv6-set-tee-times is intended to be transitional and will * likely be removed in 4.4.0, leaving the behavior as getting the values * either from the configured parameters (if you want zeros, define them as * zeros) or by calculating them per the RFC. * * \param reply - pointer to the reply_state structure * \param ia_cursor - offset of the beginning of the IA_XX option within the * reply's outbound data buffer */ static void set_reply_tee_times(struct reply_state* reply, unsigned ia_cursor) { struct option_cache *oc; int set_tee_times; /* Found out if calculated values are enabled. */ oc = lookup_option(&server_universe, reply->opt_state, SV_DHCPV6_SET_TEE_TIMES); set_tee_times = (oc && evaluate_boolean_option_cache(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &global_scope, oc, MDL)); oc = lookup_option(&dhcp_universe, reply->opt_state, DHO_DHCP_RENEWAL_TIME); if (oc != NULL) { /* dhcp-renewal-time is defined, use it */ struct data_string data; memset(&data, 0x00, sizeof(data)); if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &global_scope, oc, MDL) || (data.len != 4)) { log_error("Invalid renewal time."); reply->renew = 0; } else { reply->renew = getULong(data.data); } if (data.data != NULL) data_string_forget(&data, MDL); } else if (set_tee_times) { /* Setting them is enabled so T1 is either infinite or * 0.5 * the shortest preferred lifetime in the IA_XX */ if (reply->min_prefer == INFINITE_TIME) reply->renew = INFINITE_TIME; else reply->renew = reply->min_prefer / 2; } else { /* Default is to let the client choose */ reply->renew = 0; } putULong(reply->buf.data + ia_cursor + 8, reply->renew); /* Now T2. */ oc = lookup_option(&dhcp_universe, reply->opt_state, DHO_DHCP_REBINDING_TIME); if (oc != NULL) { /* dhcp-rebinding-time is defined, use it */ struct data_string data; memset(&data, 0x00, sizeof(data)); if (!evaluate_option_cache(&data, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, &global_scope, oc, MDL) || (data.len != 4)) { log_error("Invalid rebinding time."); reply->rebind = 0; } else { reply->rebind = getULong(data.data); } if (data.data != NULL) data_string_forget(&data, MDL); } else if (set_tee_times) { /* Setting them is enabled so T2 is either infinite or * 0.8 * the shortest preferred lifetime in the reply */ if (reply->min_prefer == INFINITE_TIME) reply->rebind = INFINITE_TIME; else reply->rebind = (reply->min_prefer / 5) * 4; } else { /* Default is to let the client choose */ reply->rebind = 0; } putULong(reply->buf.data + ia_cursor + 12, reply->rebind); } /* * Releases the iasubopts in the pre-existing IA, if they are not in * the same shared-network as the new IA. * * returns 1 if the release was done, 0 otherwise */ int release_on_roam(struct reply_state* reply) { struct ia_xx* old_ia = reply->old_ia; struct iasubopt *lease = NULL; int i; if ((!do_release_on_roam) || old_ia == NULL || old_ia->num_iasubopt <= 0) { return(0); } /* If the old shared-network and new are the same, client hasn't * roamed, nothing to do. We only check the first one because you * cannot have iasubopts on different shared-networks within a * single ia. */ lease = old_ia->iasubopt[0]; if (lease->ipv6_pool->shared_network == reply->shared) { return (0); } /* Old and new are on different shared networks so the client must * roamed. Release the old leases. */ for (i = 0; i < old_ia->num_iasubopt; i++) { lease = old_ia->iasubopt[i]; log_info("Client: %s roamed to new network," " releasing lease: %s%s", print_hex_1(reply->client_id.len, reply->client_id.data, 60), pin6_addr(&lease->addr), iasubopt_plen_str(lease)); release_lease6(lease->ipv6_pool, lease); lease->ia->cltt = cur_time; write_ia(lease->ia); } return (1); } /* * Convenience function which returns a string (static buffer) * containing either a "/" followed by the prefix length or an * empty string depending on the lease type */ const char *iasubopt_plen_str(struct iasubopt *lease) { static char prefix_buf[16]; *prefix_buf = 0; if ((lease->ia) && (lease->ia->ia_type == D6O_IA_PD)) { sprintf(prefix_buf, "/%-d", lease->plen); } return (prefix_buf); } #ifdef NSUPDATE /* * Initiates DDNS updates for static v6 leases if configured to do so. * * The function, which must be called after the IA has been written to the * packet, adds an iasubopt to the IA for static lease. This is done so we * have an iasubopt to pass into ddns_updates(). A reference to the IA is * added to the DDNS control block to ensure it and it's iasubopt remain in * scope until the update is complete. * */ void ddns_update_static6(struct reply_state* reply) { struct iasubopt *iasub = NULL; struct binding_scope *scope = NULL; struct option_cache *oc = NULL; oc = lookup_option(&server_universe, reply->opt_state, SV_DDNS_UPDATES); if ((oc != NULL) && (evaluate_boolean_option_cache(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, NULL, oc, MDL) == 0)) { return; } oc = lookup_option(&server_universe, reply->opt_state, SV_UPDATE_STATIC_LEASES); if ((oc == NULL) || (evaluate_boolean_option_cache(NULL, reply->packet, NULL, NULL, reply->packet->options, reply->opt_state, NULL, oc, MDL) == 0)) { return; } if (iasubopt_allocate(&iasub, MDL) != ISC_R_SUCCESS) { log_fatal("No memory for iasubopt."); } if (ia_add_iasubopt(reply->ia, iasub, MDL) != ISC_R_SUCCESS) { log_fatal("Could not add iasubopt."); } ia_reference(&iasub->ia, reply->ia, MDL); memcpy(iasub->addr.s6_addr, reply->fixed.data, 16); iasub->plen = 0; iasub->prefer = MAX_TIME; iasub->valid = MAX_TIME; iasub->static_lease = 1; if (!binding_scope_allocate(&scope, MDL)) { log_fatal("Out of memory for binding scope."); } binding_scope_reference(&iasub->scope, scope, MDL); ddns_updates(reply->packet, NULL, NULL, iasub, NULL, reply->opt_state); } #endif /* NSUPDATE */ #endif /* DHCPv6 */