mirror of
https://gitlab.isc.org/isc-projects/dhcp
synced 2025-08-22 01:49:35 +00:00
1259 lines
31 KiB
C
1259 lines
31 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "dhcpd.h"
|
|
|
|
/*
|
|
* TODO: RFC4388 specifies that the server SHOULD return the same
|
|
* options it would for a DHCREQUEST message, if no Parameter
|
|
* Request List option (option 55) is passed. We do not do that.
|
|
*
|
|
* TODO: RFC4388 specifies the creation of a "non-sensitive options"
|
|
* configuration list, and that these SHOULD be returned. We
|
|
* have no such list.
|
|
*
|
|
* TODO: RFC4388 says the server SHOULD use RFC3118, "Authentication
|
|
* for DHCP Messages".
|
|
*
|
|
* TODO: RFC4388 specifies that you SHOULD insure that you cannot be
|
|
* DoS'ed by DHCPLEASEQUERY message.
|
|
*/
|
|
|
|
/*
|
|
* If you query by hardware address or by client ID, then you may have
|
|
* more than one IP address for your query argument. We need to do two
|
|
* things:
|
|
*
|
|
* 1. Find the most recent lease.
|
|
* 2. Find all additional IP addresses for the query argument.
|
|
*
|
|
* We do this by looking through all of the leases associated with a
|
|
* given hardware address or client ID. We use the cltt (client last
|
|
* transaction time) of the lease, which only has a resolution of one
|
|
* second, so we might not actually give the very latest IP.
|
|
*/
|
|
|
|
static struct lease*
|
|
next_hw(const struct lease *lease) {
|
|
/* INSIST(lease != NULL); */
|
|
return lease->n_hw;
|
|
}
|
|
|
|
static struct lease*
|
|
next_uid(const struct lease *lease) {
|
|
/* INSIST(lease != NULL); */
|
|
return lease->n_uid;
|
|
}
|
|
|
|
void
|
|
get_newest_lease(struct lease **retval,
|
|
struct lease *lease,
|
|
struct lease *(*next)(const struct lease *)) {
|
|
|
|
struct lease *p;
|
|
struct lease *newest;
|
|
|
|
/* INSIST(newest != NULL); */
|
|
/* INSIST(next != NULL); */
|
|
|
|
*retval = NULL;
|
|
|
|
if (lease == NULL) {
|
|
return;
|
|
}
|
|
|
|
newest = lease;
|
|
for (p=next(lease); p != NULL; p=next(p)) {
|
|
if (newest->binding_state == FTS_ACTIVE) {
|
|
if ((p->binding_state == FTS_ACTIVE) &&
|
|
(p->cltt > newest->cltt)) {
|
|
newest = p;
|
|
}
|
|
} else {
|
|
if (p->ends > newest->ends) {
|
|
newest = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
lease_reference(retval, newest, MDL);
|
|
}
|
|
|
|
static int
|
|
get_associated_ips(const struct lease *lease,
|
|
struct lease *(*next)(const struct lease *),
|
|
const struct lease *newest,
|
|
u_int32_t *associated_ips,
|
|
unsigned int associated_ips_size) {
|
|
|
|
const struct lease *p;
|
|
int cnt;
|
|
|
|
/* INSIST(next != NULL); */
|
|
/* INSIST(associated_ips != NULL); */
|
|
|
|
if (lease == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
cnt = 0;
|
|
for (p=lease; p != NULL; p=next(p)) {
|
|
if ((p->binding_state == FTS_ACTIVE) && (p != newest)) {
|
|
if (cnt < associated_ips_size) {
|
|
memcpy(&associated_ips[cnt],
|
|
p->ip_addr.iabuf,
|
|
sizeof(associated_ips[cnt]));
|
|
}
|
|
cnt++;
|
|
}
|
|
}
|
|
return cnt;
|
|
}
|
|
|
|
|
|
void
|
|
dhcpleasequery(struct packet *packet, int ms_nulltp) {
|
|
char msgbuf[256];
|
|
char dbg_info[128];
|
|
struct iaddr cip;
|
|
struct iaddr gip;
|
|
struct data_string uid;
|
|
struct hardware h;
|
|
struct lease *tmp_lease;
|
|
struct lease *lease;
|
|
int want_associated_ip;
|
|
int assoc_ip_cnt;
|
|
u_int32_t assoc_ips[40]; /* XXXSK: arbitrary maximum number of IPs */
|
|
const int nassoc_ips = sizeof(assoc_ips) / sizeof(assoc_ips[0]);
|
|
|
|
unsigned char dhcpMsgType;
|
|
const char *dhcp_msg_type_name;
|
|
struct subnet *subnet;
|
|
struct group *relay_group;
|
|
struct option_state *options;
|
|
struct option_cache *oc;
|
|
int allow_leasequery;
|
|
int ignorep;
|
|
u_int32_t lease_duration;
|
|
u_int32_t time_renewal;
|
|
u_int32_t time_rebinding;
|
|
u_int32_t time_expiry;
|
|
u_int32_t client_last_transaction_time;
|
|
#if defined(RELAY_PORT)
|
|
u_int16_t relay_port = 0;
|
|
#endif
|
|
struct sockaddr_in to;
|
|
struct in_addr siaddr;
|
|
struct data_string prl;
|
|
struct data_string *prl_ptr;
|
|
|
|
int i;
|
|
struct interface_info *interface;
|
|
|
|
/* INSIST(packet != NULL); */
|
|
|
|
/*
|
|
* Prepare log information.
|
|
*/
|
|
snprintf(msgbuf, sizeof(msgbuf),
|
|
"DHCPLEASEQUERY from %s", inet_ntoa(packet->raw->giaddr));
|
|
|
|
/*
|
|
* We can't reply if there is no giaddr field.
|
|
*/
|
|
/*
|
|
* Note: this makes DHCPv4-over-DHCPv6 always fail but it should not
|
|
* really be a problem because it is not a specified use case
|
|
* (or even one that makes sense).
|
|
*/
|
|
if (!packet->raw->giaddr.s_addr) {
|
|
log_info("%s: missing giaddr, ciaddr is %s, no reply sent",
|
|
msgbuf, inet_ntoa(packet->raw->ciaddr));
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Initially we use the 'giaddr' subnet options scope to determine if
|
|
* the giaddr-identified relay agent is permitted to perform a
|
|
* leasequery. The subnet is not required, and may be omitted, in
|
|
* which case we are essentially interrogating the root options class
|
|
* to find a globally permit.
|
|
*/
|
|
gip.len = sizeof(packet->raw->giaddr);
|
|
memcpy(gip.iabuf, &packet->raw->giaddr, sizeof(packet->raw->giaddr));
|
|
|
|
subnet = NULL;
|
|
find_subnet(&subnet, gip, MDL);
|
|
if (subnet != NULL)
|
|
relay_group = subnet->group;
|
|
else
|
|
relay_group = root_group;
|
|
|
|
subnet_dereference(&subnet, MDL);
|
|
|
|
options = NULL;
|
|
if (!option_state_allocate(&options, MDL)) {
|
|
log_error("No memory for option state.");
|
|
log_info("%s: out of memory, no reply sent", msgbuf);
|
|
return;
|
|
}
|
|
|
|
execute_statements_in_scope(NULL, packet, NULL, NULL, packet->options,
|
|
options, &global_scope, relay_group,
|
|
NULL, NULL);
|
|
|
|
for (i=packet->class_count-1; i>=0; i--) {
|
|
execute_statements_in_scope(NULL, packet, NULL, NULL,
|
|
packet->options, options,
|
|
&global_scope,
|
|
packet->classes[i]->group,
|
|
relay_group, NULL);
|
|
}
|
|
|
|
/*
|
|
* Because LEASEQUERY has some privacy concerns, default to deny.
|
|
*/
|
|
allow_leasequery = 0;
|
|
|
|
/*
|
|
* See if we are authorized to do LEASEQUERY.
|
|
*/
|
|
oc = lookup_option(&server_universe, options, SV_LEASEQUERY);
|
|
if (oc != NULL) {
|
|
allow_leasequery = evaluate_boolean_option_cache(&ignorep,
|
|
packet, NULL, NULL, packet->options,
|
|
options, &global_scope, oc, MDL);
|
|
}
|
|
|
|
if (!allow_leasequery) {
|
|
log_info("%s: LEASEQUERY not allowed, query ignored", msgbuf);
|
|
option_state_dereference(&options, MDL);
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* Copy out the client IP address.
|
|
*/
|
|
cip.len = sizeof(packet->raw->ciaddr);
|
|
memcpy(cip.iabuf, &packet->raw->ciaddr, sizeof(packet->raw->ciaddr));
|
|
|
|
/*
|
|
* If the client IP address is valid (not all zero), then we
|
|
* are looking for information about that IP address.
|
|
*/
|
|
assoc_ip_cnt = 0;
|
|
lease = tmp_lease = NULL;
|
|
if (memcmp(cip.iabuf, "\0\0\0", 4)) {
|
|
|
|
want_associated_ip = 0;
|
|
|
|
snprintf(dbg_info, sizeof(dbg_info), "IP %s", piaddr(cip));
|
|
find_lease_by_ip_addr(&lease, cip, MDL);
|
|
|
|
|
|
} else {
|
|
|
|
want_associated_ip = 1;
|
|
|
|
/*
|
|
* If the client IP address is all zero, then we will
|
|
* either look up by the client identifier (if we have
|
|
* one), or by the MAC address.
|
|
*/
|
|
|
|
memset(&uid, 0, sizeof(uid));
|
|
if (get_option(&uid,
|
|
&dhcp_universe,
|
|
packet,
|
|
NULL,
|
|
NULL,
|
|
packet->options,
|
|
NULL,
|
|
packet->options,
|
|
&global_scope,
|
|
DHO_DHCP_CLIENT_IDENTIFIER,
|
|
MDL)) {
|
|
|
|
snprintf(dbg_info,
|
|
sizeof(dbg_info),
|
|
"client-id %s",
|
|
print_hex_1(uid.len, uid.data, 60));
|
|
|
|
find_lease_by_uid(&tmp_lease, uid.data, uid.len, MDL);
|
|
data_string_forget(&uid, MDL);
|
|
get_newest_lease(&lease, tmp_lease, next_uid);
|
|
assoc_ip_cnt = get_associated_ips(tmp_lease,
|
|
next_uid,
|
|
lease,
|
|
assoc_ips,
|
|
nassoc_ips);
|
|
|
|
} else {
|
|
|
|
if (packet->raw->hlen+1 > sizeof(h.hbuf)) {
|
|
log_info("%s: hardware length too long, "
|
|
"no reply sent", msgbuf);
|
|
option_state_dereference(&options, MDL);
|
|
return;
|
|
}
|
|
|
|
h.hlen = packet->raw->hlen + 1;
|
|
h.hbuf[0] = packet->raw->htype;
|
|
memcpy(&h.hbuf[1],
|
|
packet->raw->chaddr,
|
|
packet->raw->hlen);
|
|
|
|
snprintf(dbg_info,
|
|
sizeof(dbg_info),
|
|
"MAC address %s",
|
|
print_hw_addr(h.hbuf[0],
|
|
h.hlen - 1,
|
|
&h.hbuf[1]));
|
|
|
|
find_lease_by_hw_addr(&tmp_lease, h.hbuf, h.hlen, MDL);
|
|
get_newest_lease(&lease, tmp_lease, next_hw);
|
|
assoc_ip_cnt = get_associated_ips(tmp_lease,
|
|
next_hw,
|
|
lease,
|
|
assoc_ips,
|
|
nassoc_ips);
|
|
|
|
}
|
|
|
|
lease_dereference(&tmp_lease, MDL);
|
|
|
|
if (lease != NULL) {
|
|
memcpy(&packet->raw->ciaddr,
|
|
lease->ip_addr.iabuf,
|
|
sizeof(packet->raw->ciaddr));
|
|
}
|
|
|
|
/*
|
|
* Log if we have too many IP addresses associated
|
|
* with this client.
|
|
*/
|
|
if (want_associated_ip && (assoc_ip_cnt > nassoc_ips)) {
|
|
log_info("%d IP addresses associated with %s, "
|
|
"only %d sent in reply.",
|
|
assoc_ip_cnt, dbg_info, nassoc_ips);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We now know the query target too, so can report this in
|
|
* our log message.
|
|
*/
|
|
snprintf(msgbuf, sizeof(msgbuf),
|
|
"DHCPLEASEQUERY from %s for %s",
|
|
inet_ntoa(packet->raw->giaddr), dbg_info);
|
|
|
|
/*
|
|
* Figure our our return type.
|
|
*/
|
|
if (lease == NULL) {
|
|
dhcpMsgType = DHCPLEASEUNKNOWN;
|
|
dhcp_msg_type_name = "DHCPLEASEUNKNOWN";
|
|
} else {
|
|
if (lease->binding_state == FTS_ACTIVE) {
|
|
dhcpMsgType = DHCPLEASEACTIVE;
|
|
dhcp_msg_type_name = "DHCPLEASEACTIVE";
|
|
} else {
|
|
dhcpMsgType = DHCPLEASEUNASSIGNED;
|
|
dhcp_msg_type_name = "DHCPLEASEUNASSIGNED";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set options that only make sense if we have an active lease.
|
|
*/
|
|
|
|
if (dhcpMsgType == DHCPLEASEACTIVE)
|
|
{
|
|
/*
|
|
* RFC 4388 uses the PRL to request options for the agent to
|
|
* receive that are "about" the client. It is confusing
|
|
* because in some cases it wants to know what was sent to
|
|
* the client (lease times, adjusted), and in others it wants
|
|
* to know information the client sent. You're supposed to
|
|
* know this on a case-by-case basis.
|
|
*
|
|
* "Name servers", "domain name", and the like from the relay
|
|
* agent's scope seems less than useful. Our options are to
|
|
* restart the option cache from the lease's best point of view
|
|
* (execute statements from the lease pool's group), or to
|
|
* simply restart the option cache from empty.
|
|
*
|
|
* I think restarting the option cache from empty best
|
|
* approaches RFC 4388's intent; specific options are included.
|
|
*/
|
|
option_state_dereference(&options, MDL);
|
|
|
|
if (!option_state_allocate(&options, MDL)) {
|
|
log_error("%s: out of memory, no reply sent", msgbuf);
|
|
lease_dereference(&lease, MDL);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set the hardware address fields.
|
|
*/
|
|
|
|
packet->raw->hlen = lease->hardware_addr.hlen - 1;
|
|
packet->raw->htype = lease->hardware_addr.hbuf[0];
|
|
memcpy(packet->raw->chaddr,
|
|
&lease->hardware_addr.hbuf[1],
|
|
sizeof(packet->raw->chaddr));
|
|
|
|
/*
|
|
* Set client identifier option.
|
|
*/
|
|
if (lease->uid_len > 0) {
|
|
if (!add_option(options,
|
|
DHO_DHCP_CLIENT_IDENTIFIER,
|
|
lease->uid,
|
|
lease->uid_len)) {
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_info("%s: out of memory, no reply sent",
|
|
msgbuf);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Calculate T1 and T2, the times when the client
|
|
* tries to extend its lease on its networking
|
|
* address.
|
|
* These seem to be hard-coded in ISC DHCP, to 0.5 and
|
|
* 0.875 of the lease time.
|
|
*/
|
|
|
|
lease_duration = lease->ends - lease->starts;
|
|
time_renewal = lease->starts +
|
|
(lease_duration / 2);
|
|
time_rebinding = lease->starts +
|
|
(lease_duration / 2) +
|
|
(lease_duration / 4) +
|
|
(lease_duration / 8);
|
|
|
|
if (time_renewal > cur_time) {
|
|
time_renewal = htonl(time_renewal - cur_time);
|
|
|
|
if (!add_option(options,
|
|
DHO_DHCP_RENEWAL_TIME,
|
|
&time_renewal,
|
|
sizeof(time_renewal))) {
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_info("%s: out of memory, no reply sent",
|
|
msgbuf);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (time_rebinding > cur_time) {
|
|
time_rebinding = htonl(time_rebinding - cur_time);
|
|
|
|
if (!add_option(options,
|
|
DHO_DHCP_REBINDING_TIME,
|
|
&time_rebinding,
|
|
sizeof(time_rebinding))) {
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_info("%s: out of memory, no reply sent",
|
|
msgbuf);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (lease->ends > cur_time) {
|
|
time_expiry = htonl(lease->ends - cur_time);
|
|
|
|
if (!add_option(options,
|
|
DHO_DHCP_LEASE_TIME,
|
|
&time_expiry,
|
|
sizeof(time_expiry))) {
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_info("%s: out of memory, no reply sent",
|
|
msgbuf);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Supply the Vendor-Class-Identifier. */
|
|
if (lease->scope != NULL) {
|
|
struct data_string vendor_class;
|
|
|
|
memset(&vendor_class, 0, sizeof(vendor_class));
|
|
|
|
if (find_bound_string(&vendor_class, lease->scope,
|
|
"vendor-class-identifier")) {
|
|
if (!add_option(options,
|
|
DHO_VENDOR_CLASS_IDENTIFIER,
|
|
(void *)vendor_class.data,
|
|
vendor_class.len)) {
|
|
option_state_dereference(&options,
|
|
MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_error("%s: error adding vendor "
|
|
"class identifier, no reply "
|
|
"sent", msgbuf);
|
|
data_string_forget(&vendor_class, MDL);
|
|
return;
|
|
}
|
|
data_string_forget(&vendor_class, MDL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the relay agent info.
|
|
*
|
|
* Note that because agent info is appended without regard
|
|
* to the PRL in cons_options(), this will be sent as the
|
|
* last option in the packet whether it is listed on PRL or
|
|
* not.
|
|
*/
|
|
|
|
if (lease->agent_options != NULL) {
|
|
int idx = agent_universe.index;
|
|
struct option_chain_head **tmp1 =
|
|
(struct option_chain_head **)
|
|
&(options->universes[idx]);
|
|
struct option_chain_head *tmp2 =
|
|
(struct option_chain_head *)
|
|
lease->agent_options;
|
|
|
|
option_chain_head_reference(tmp1, tmp2, MDL);
|
|
}
|
|
|
|
/*
|
|
* Set the client last transaction time.
|
|
* We check to make sure we have a timestamp. For
|
|
* lease files that were saved before running a
|
|
* timestamp-aware version of the server, this may
|
|
* not be set.
|
|
*/
|
|
|
|
if (lease->cltt != MIN_TIME) {
|
|
if (cur_time > lease->cltt) {
|
|
client_last_transaction_time =
|
|
htonl(cur_time - lease->cltt);
|
|
} else {
|
|
client_last_transaction_time = htonl(0);
|
|
}
|
|
if (!add_option(options,
|
|
DHO_CLIENT_LAST_TRANSACTION_TIME,
|
|
&client_last_transaction_time,
|
|
sizeof(client_last_transaction_time))) {
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_info("%s: out of memory, no reply sent",
|
|
msgbuf);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set associated IPs, if requested and there are some.
|
|
*/
|
|
if (want_associated_ip && (assoc_ip_cnt > 0)) {
|
|
if (!add_option(options,
|
|
DHO_ASSOCIATED_IP,
|
|
assoc_ips,
|
|
assoc_ip_cnt * sizeof(assoc_ips[0]))) {
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_info("%s: out of memory, no reply sent",
|
|
msgbuf);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the message type.
|
|
*/
|
|
|
|
packet->raw->op = BOOTREPLY;
|
|
|
|
/*
|
|
* Set DHCP message type.
|
|
*/
|
|
if (!add_option(options,
|
|
DHO_DHCP_MESSAGE_TYPE,
|
|
&dhcpMsgType,
|
|
sizeof(dhcpMsgType))) {
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
log_info("%s: error adding option, no reply sent", msgbuf);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Log the message we've received.
|
|
*/
|
|
log_info("%s", msgbuf);
|
|
|
|
/*
|
|
* Figure out which address to use to send from.
|
|
*/
|
|
get_server_source_address(&siaddr, options, options, packet);
|
|
|
|
/*
|
|
* Set up the option buffer.
|
|
*/
|
|
|
|
memset(&prl, 0, sizeof(prl));
|
|
oc = lookup_option(&dhcp_universe, options,
|
|
DHO_DHCP_PARAMETER_REQUEST_LIST);
|
|
if (oc != NULL) {
|
|
evaluate_option_cache(&prl,
|
|
packet,
|
|
NULL,
|
|
NULL,
|
|
packet->options,
|
|
options,
|
|
&global_scope,
|
|
oc,
|
|
MDL);
|
|
}
|
|
if (prl.len > 0) {
|
|
prl_ptr = &prl;
|
|
} else {
|
|
prl_ptr = NULL;
|
|
}
|
|
|
|
packet->packet_length = cons_options(packet,
|
|
packet->raw,
|
|
lease,
|
|
NULL,
|
|
0,
|
|
packet->options,
|
|
options,
|
|
&global_scope,
|
|
0,
|
|
0,
|
|
0,
|
|
prl_ptr,
|
|
NULL);
|
|
|
|
data_string_forget(&prl, MDL); /* SK: safe, even if empty */
|
|
option_state_dereference(&options, MDL);
|
|
lease_dereference(&lease, MDL);
|
|
|
|
to.sin_family = AF_INET;
|
|
#ifdef HAVE_SA_LEN
|
|
to.sin_len = sizeof(to);
|
|
#endif
|
|
memset(to.sin_zero, 0, sizeof(to.sin_zero));
|
|
|
|
#if defined(RELAY_PORT)
|
|
relay_port = dhcp_check_relayport(packet);
|
|
#endif
|
|
|
|
/*
|
|
* Leasequery packets are be sent to the gateway address.
|
|
*/
|
|
to.sin_addr = packet->raw->giaddr;
|
|
if (packet->raw->giaddr.s_addr != htonl(INADDR_LOOPBACK)) {
|
|
#if defined(RELAY_PORT)
|
|
to.sin_port = relay_port ? relay_port : local_port;
|
|
#else
|
|
to.sin_port = local_port;
|
|
#endif
|
|
} else {
|
|
to.sin_port = remote_port; /* XXXSK: For debugging. */
|
|
}
|
|
|
|
/*
|
|
* The fallback_interface lets us send with a real IP
|
|
* address. The packet interface sends from all-zeros.
|
|
*/
|
|
if (fallback_interface != NULL) {
|
|
interface = fallback_interface;
|
|
} else {
|
|
interface = packet->interface;
|
|
}
|
|
|
|
/*
|
|
* Report what we're sending.
|
|
*/
|
|
log_info("%s to %s for %s (%d associated IPs)",
|
|
dhcp_msg_type_name,
|
|
inet_ntoa(to.sin_addr), dbg_info, assoc_ip_cnt);
|
|
|
|
send_packet(interface,
|
|
NULL,
|
|
packet->raw,
|
|
packet->packet_length,
|
|
siaddr,
|
|
&to,
|
|
NULL);
|
|
}
|
|
|
|
#ifdef DHCPv6
|
|
|
|
/*
|
|
* TODO: RFC5007 query-by-clientid.
|
|
*
|
|
* TODO: RFC5007 look at the pools according to the link-address.
|
|
*
|
|
* TODO: get fixed leases too.
|
|
*
|
|
* TODO: RFC5007 ORO in query-options.
|
|
*
|
|
* TODO: RFC5007 lq-relay-data.
|
|
*
|
|
* TODO: RFC5007 lq-client-link.
|
|
*
|
|
* Note: the code is still nearly compliant and usable for the target
|
|
* case with these missing features!
|
|
*/
|
|
|
|
/*
|
|
* The structure to handle a leasequery.
|
|
*/
|
|
struct lq6_state {
|
|
struct packet *packet;
|
|
struct data_string client_id;
|
|
struct data_string server_id;
|
|
struct data_string lq_query;
|
|
uint8_t query_type;
|
|
struct in6_addr link_addr;
|
|
struct option_state *query_opts;
|
|
|
|
struct option_state *reply_opts;
|
|
unsigned cursor;
|
|
union reply_buffer {
|
|
unsigned char data[65536];
|
|
struct dhcpv6_packet reply;
|
|
} buf;
|
|
};
|
|
|
|
/*
|
|
* Options that we want to send.
|
|
*/
|
|
static const int required_opts_lq[] = {
|
|
D6O_CLIENTID,
|
|
D6O_SERVERID,
|
|
D6O_STATUS_CODE,
|
|
D6O_CLIENT_DATA,
|
|
D6O_LQ_RELAY_DATA,
|
|
D6O_LQ_CLIENT_LINK,
|
|
0
|
|
};
|
|
static const int required_opt_CLIENT_DATA[] = {
|
|
D6O_CLIENTID,
|
|
D6O_IAADDR,
|
|
D6O_IAPREFIX,
|
|
D6O_CLT_TIME,
|
|
0
|
|
};
|
|
|
|
/*
|
|
* Get the lq-query option from the packet.
|
|
*/
|
|
static isc_result_t
|
|
get_lq_query(struct lq6_state *lq)
|
|
{
|
|
struct data_string *lq_query = &lq->lq_query;
|
|
struct packet *packet = lq->packet;
|
|
struct option_cache *oc;
|
|
|
|
/*
|
|
* Verify our lq_query structure is empty.
|
|
*/
|
|
if ((lq_query->data != NULL) || (lq_query->len != 0)) {
|
|
return DHCP_R_INVALIDARG;
|
|
}
|
|
|
|
oc = lookup_option(&dhcpv6_universe, packet->options, D6O_LQ_QUERY);
|
|
if (oc == NULL) {
|
|
return ISC_R_NOTFOUND;
|
|
}
|
|
|
|
if (!evaluate_option_cache(lq_query, packet, NULL, NULL,
|
|
packet->options, NULL,
|
|
&global_scope, oc, MDL)) {
|
|
return ISC_R_FAILURE;
|
|
}
|
|
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Message validation, RFC 5007 section 4.2.1:
|
|
* dhcpv6.c:valid_client_msg() - unicast + lq-query option.
|
|
*/
|
|
static int
|
|
valid_query_msg(struct lq6_state *lq) {
|
|
struct packet *packet = lq->packet;
|
|
int ret_val = 0;
|
|
struct option_cache *oc;
|
|
|
|
/* INSIST((lq != NULL) || (packet != NULL)); */
|
|
|
|
switch (get_client_id(packet, &lq->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) {
|
|
if (evaluate_option_cache(&lq->server_id, 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(lq->client_id.len,
|
|
lq->client_id.data, 60),
|
|
print_hex_2(lq->server_id.len,
|
|
lq->server_id.data, 60));
|
|
} else {
|
|
log_debug("Discarding %s from %s; "
|
|
"server identifier found "
|
|
"(CLIENTID %s)",
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
print_hex_1(lq->client_id.len,
|
|
lq->client_id.data, 60),
|
|
piaddr(packet->client_addr));
|
|
}
|
|
goto exit;
|
|
}
|
|
|
|
switch (get_lq_query(lq)) {
|
|
case ISC_R_SUCCESS:
|
|
break;
|
|
case ISC_R_NOTFOUND:
|
|
log_debug("Discarding %s from %s; lq-query 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 LQ-Query",
|
|
dhcpv6_type_names[packet->dhcpv6_msg_type],
|
|
piaddr(packet->client_addr));
|
|
goto exit;
|
|
}
|
|
|
|
/* looks good */
|
|
ret_val = 1;
|
|
|
|
exit:
|
|
if (!ret_val) {
|
|
data_string_forget(&lq->client_id, MDL);
|
|
data_string_forget(&lq->server_id, MDL);
|
|
data_string_forget(&lq->lq_query, MDL);
|
|
}
|
|
return ret_val;
|
|
}
|
|
|
|
/*
|
|
* Set an error in a status-code option (from set_status_code).
|
|
*/
|
|
static int
|
|
set_error(struct lq6_state *lq, u_int16_t code, const char *message) {
|
|
struct data_string d;
|
|
int ret_val;
|
|
|
|
memset(&d, 0, sizeof(d));
|
|
d.len = sizeof(code) + strlen(message);
|
|
if (!buffer_allocate(&d.buffer, d.len, MDL)) {
|
|
log_fatal("set_error: no memory for status code.");
|
|
}
|
|
d.data = d.buffer->data;
|
|
putUShort(d.buffer->data, code);
|
|
memcpy(d.buffer->data + sizeof(code), message, d.len - sizeof(code));
|
|
if (!save_option_buffer(&dhcpv6_universe, lq->reply_opts,
|
|
d.buffer, (unsigned char *)d.data, d.len,
|
|
D6O_STATUS_CODE, 0)) {
|
|
log_error("set_error: error saving status code.");
|
|
ret_val = 0;
|
|
} else {
|
|
ret_val = 1;
|
|
}
|
|
data_string_forget(&d, MDL);
|
|
return ret_val;
|
|
}
|
|
|
|
/*
|
|
* Process a by-address lease query.
|
|
*/
|
|
static int
|
|
process_lq_by_address(struct lq6_state *lq) {
|
|
struct packet *packet = lq->packet;
|
|
struct option_cache *oc;
|
|
struct ipv6_pool *pool = NULL;
|
|
struct data_string data;
|
|
struct in6_addr addr;
|
|
struct iasubopt *iaaddr = NULL;
|
|
struct option_state *opt_state = NULL;
|
|
u_int32_t lifetime;
|
|
unsigned opt_cursor;
|
|
int ret_val = 0;
|
|
|
|
/*
|
|
* Get the IAADDR.
|
|
*/
|
|
oc = lookup_option(&dhcpv6_universe, lq->query_opts, D6O_IAADDR);
|
|
if (oc == NULL) {
|
|
if (!set_error(lq, STATUS_MalformedQuery,
|
|
"No OPTION_IAADDR.")) {
|
|
log_error("process_lq_by_address: unable "
|
|
"to set MalformedQuery status code.");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
memset(&data, 0, sizeof(data));
|
|
if (!evaluate_option_cache(&data, packet,
|
|
NULL, NULL,
|
|
lq->query_opts, NULL,
|
|
&global_scope, oc, MDL) ||
|
|
(data.len < IAADDR_OFFSET)) {
|
|
log_error("process_lq_by_address: error evaluating IAADDR.");
|
|
goto exit;
|
|
}
|
|
memcpy(&addr, data.data, sizeof(addr));
|
|
data_string_forget(&data, MDL);
|
|
|
|
/*
|
|
* Find the lease.
|
|
* Note the RFC 5007 says to use the link-address to find the link
|
|
* or the ia-aadr when it is :: but in any case the ia-addr has
|
|
* to be on the link, so we ignore the link-address here.
|
|
*/
|
|
if (find_ipv6_pool(&pool, D6O_IA_NA, &addr) != ISC_R_SUCCESS) {
|
|
if (!set_error(lq, STATUS_NotConfigured,
|
|
"Address not in a pool.")) {
|
|
log_error("process_lq_by_address: unable "
|
|
"to set NotConfigured status code.");
|
|
goto exit;
|
|
}
|
|
ret_val = 1;
|
|
goto exit;
|
|
}
|
|
if (iasubopt_hash_lookup(&iaaddr, pool->leases, &addr,
|
|
sizeof(addr), MDL) == 0) {
|
|
ret_val = 1;
|
|
goto exit;
|
|
}
|
|
if ((iaaddr == NULL) || (iaaddr->state != FTS_ACTIVE) ||
|
|
(iaaddr->ia == NULL) || (iaaddr->ia->iaid_duid.len <= 4)) {
|
|
ret_val = 1;
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Build the client-data option (with client-id, ia-addr and clt-time).
|
|
*/
|
|
if (!option_state_allocate(&opt_state, MDL)) {
|
|
log_error("process_lq_by_address: "
|
|
"no memory for option state.");
|
|
goto exit;
|
|
}
|
|
|
|
data_string_copy(&data, &iaaddr->ia->iaid_duid, MDL);
|
|
data.data += 4;
|
|
data.len -= 4;
|
|
if (!save_option_buffer(&dhcpv6_universe, opt_state,
|
|
NULL, (unsigned char *)data.data, data.len,
|
|
D6O_CLIENTID, 0)) {
|
|
log_error("process_lq_by_address: error saving client ID.");
|
|
goto exit;
|
|
}
|
|
data_string_forget(&data, MDL);
|
|
|
|
data.len = IAADDR_OFFSET;
|
|
if (!buffer_allocate(&data.buffer, data.len, MDL)) {
|
|
log_error("process_lq_by_address: no memory for ia-addr.");
|
|
goto exit;
|
|
}
|
|
data.data = data.buffer->data;
|
|
memcpy(data.buffer->data, &iaaddr->addr, 16);
|
|
lifetime = iaaddr->prefer;
|
|
putULong(data.buffer->data + 16, lifetime);
|
|
lifetime = iaaddr->valid;
|
|
putULong(data.buffer->data + 20, lifetime);
|
|
if (!save_option_buffer(&dhcpv6_universe, opt_state,
|
|
NULL, (unsigned char *)data.data, data.len,
|
|
D6O_IAADDR, 0)) {
|
|
log_error("process_lq_by_address: error saving ia-addr.");
|
|
goto exit;
|
|
}
|
|
data_string_forget(&data, MDL);
|
|
|
|
lifetime = htonl(iaaddr->ia->cltt);
|
|
if (!save_option_buffer(&dhcpv6_universe, opt_state,
|
|
NULL, (unsigned char *)&lifetime, 4,
|
|
D6O_CLT_TIME, 0)) {
|
|
log_error("process_lq_by_address: error saving clt time.");
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Store the client-data option.
|
|
*/
|
|
opt_cursor = lq->cursor;
|
|
putUShort(lq->buf.data + lq->cursor, (unsigned)D6O_CLIENT_DATA);
|
|
lq->cursor += 2;
|
|
/* Skip option length. */
|
|
lq->cursor += 2;
|
|
|
|
lq->cursor += store_options6((char *)lq->buf.data + lq->cursor,
|
|
sizeof(lq->buf) - lq->cursor,
|
|
opt_state, lq->packet,
|
|
required_opt_CLIENT_DATA, NULL);
|
|
/* Reset the length. */
|
|
putUShort(lq->buf.data + opt_cursor + 2,
|
|
lq->cursor - (opt_cursor + 4));
|
|
|
|
/* Done. */
|
|
ret_val = 1;
|
|
|
|
exit:
|
|
if (data.data != NULL)
|
|
data_string_forget(&data, MDL);
|
|
if (pool != NULL)
|
|
ipv6_pool_dereference(&pool, MDL);
|
|
if (iaaddr != NULL)
|
|
iasubopt_dereference(&iaaddr, MDL);
|
|
if (opt_state != NULL)
|
|
option_state_dereference(&opt_state, MDL);
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
/*
|
|
* Process a lease query.
|
|
*/
|
|
void
|
|
dhcpv6_leasequery(struct data_string *reply_ret, struct packet *packet) {
|
|
static struct lq6_state lq;
|
|
struct option_cache *oc;
|
|
int allow_lq;
|
|
|
|
/*
|
|
* Initialize the lease query state.
|
|
*/
|
|
lq.packet = NULL;
|
|
memset(&lq.client_id, 0, sizeof(lq.client_id));
|
|
memset(&lq.server_id, 0, sizeof(lq.server_id));
|
|
memset(&lq.lq_query, 0, sizeof(lq.lq_query));
|
|
lq.query_opts = NULL;
|
|
lq.reply_opts = NULL;
|
|
packet_reference(&lq.packet, packet, MDL);
|
|
|
|
/*
|
|
* Validate our input.
|
|
*/
|
|
if (!valid_query_msg(&lq)) {
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Prepare our reply.
|
|
*/
|
|
if (!option_state_allocate(&lq.reply_opts, MDL)) {
|
|
log_error("dhcpv6_leasequery: no memory for option state.");
|
|
goto exit;
|
|
}
|
|
execute_statements_in_scope(NULL, lq.packet, NULL, NULL,
|
|
lq.packet->options, lq.reply_opts,
|
|
&global_scope, root_group, NULL, NULL);
|
|
|
|
lq.buf.reply.msg_type = DHCPV6_LEASEQUERY_REPLY;
|
|
|
|
memcpy(lq.buf.reply.transaction_id,
|
|
lq.packet->dhcpv6_transaction_id,
|
|
sizeof(lq.buf.reply.transaction_id));
|
|
|
|
/*
|
|
* Because LEASEQUERY has some privacy concerns, default to deny.
|
|
*/
|
|
allow_lq = 0;
|
|
|
|
/*
|
|
* See if we are authorized to do LEASEQUERY.
|
|
*/
|
|
oc = lookup_option(&server_universe, lq.reply_opts, SV_LEASEQUERY);
|
|
if (oc != NULL) {
|
|
allow_lq = evaluate_boolean_option_cache(NULL,
|
|
lq.packet,
|
|
NULL, NULL,
|
|
lq.packet->options,
|
|
lq.reply_opts,
|
|
&global_scope,
|
|
oc, MDL);
|
|
}
|
|
|
|
if (!allow_lq) {
|
|
log_info("dhcpv6_leasequery: not allowed, query ignored.");
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Same than transmission of REPLY message in RFC 3315:
|
|
* server-id
|
|
* client-id
|
|
*/
|
|
|
|
oc = lookup_option(&dhcpv6_universe, lq.reply_opts, D6O_SERVERID);
|
|
if (oc == NULL) {
|
|
/* If not already in options, get from query then global. */
|
|
if (lq.server_id.data == NULL)
|
|
copy_server_duid(&lq.server_id, MDL);
|
|
if (!save_option_buffer(&dhcpv6_universe,
|
|
lq.reply_opts,
|
|
NULL,
|
|
(unsigned char *)lq.server_id.data,
|
|
lq.server_id.len,
|
|
D6O_SERVERID,
|
|
0)) {
|
|
log_error("dhcpv6_leasequery: "
|
|
"error saving server identifier.");
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
if (!save_option_buffer(&dhcpv6_universe,
|
|
lq.reply_opts,
|
|
lq.client_id.buffer,
|
|
(unsigned char *)lq.client_id.data,
|
|
lq.client_id.len,
|
|
D6O_CLIENTID,
|
|
0)) {
|
|
log_error("dhcpv6_leasequery: "
|
|
"error saving client identifier.");
|
|
goto exit;
|
|
}
|
|
|
|
lq.cursor = 4;
|
|
|
|
/*
|
|
* Decode the lq-query option.
|
|
*/
|
|
|
|
if (lq.lq_query.len <= LQ_QUERY_OFFSET) {
|
|
if (!set_error(&lq, STATUS_MalformedQuery,
|
|
"OPTION_LQ_QUERY too short.")) {
|
|
log_error("dhcpv6_leasequery: unable "
|
|
"to set MalformedQuery status code.");
|
|
goto exit;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
lq.query_type = lq.lq_query.data [0];
|
|
memcpy(&lq.link_addr, lq.lq_query.data + 1, sizeof(lq.link_addr));
|
|
switch (lq.query_type) {
|
|
case LQ6QT_BY_ADDRESS:
|
|
break;
|
|
case LQ6QT_BY_CLIENTID:
|
|
if (!set_error(&lq, STATUS_UnknownQueryType,
|
|
"QUERY_BY_CLIENTID not supported.")) {
|
|
log_error("dhcpv6_leasequery: unable to "
|
|
"set UnknownQueryType status code.");
|
|
goto exit;
|
|
}
|
|
goto done;
|
|
default:
|
|
if (!set_error(&lq, STATUS_UnknownQueryType,
|
|
"Unknown query-type.")) {
|
|
log_error("dhcpv6_leasequery: unable to "
|
|
"set UnknownQueryType status code.");
|
|
goto exit;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
if (!option_state_allocate(&lq.query_opts, MDL)) {
|
|
log_error("dhcpv6_leasequery: no memory for option state.");
|
|
goto exit;
|
|
}
|
|
if (!parse_option_buffer(lq.query_opts,
|
|
lq.lq_query.data + LQ_QUERY_OFFSET,
|
|
lq.lq_query.len - LQ_QUERY_OFFSET,
|
|
&dhcpv6_universe)) {
|
|
log_error("dhcpv6_leasequery: error parsing query-options.");
|
|
if (!set_error(&lq, STATUS_MalformedQuery,
|
|
"Bad query-options.")) {
|
|
log_error("dhcpv6_leasequery: unable "
|
|
"to set MalformedQuery status code.");
|
|
goto exit;
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
/* Do it. */
|
|
if (!process_lq_by_address(&lq))
|
|
goto exit;
|
|
|
|
done:
|
|
/* Store the options. */
|
|
lq.cursor += store_options6((char *)lq.buf.data + lq.cursor,
|
|
sizeof(lq.buf) - lq.cursor,
|
|
lq.reply_opts,
|
|
lq.packet,
|
|
required_opts_lq,
|
|
NULL);
|
|
|
|
/* Return our reply to the caller. */
|
|
reply_ret->len = lq.cursor;
|
|
reply_ret->buffer = NULL;
|
|
if (!buffer_allocate(&reply_ret->buffer, lq.cursor, MDL)) {
|
|
log_fatal("dhcpv6_leasequery: no memory to store Reply.");
|
|
}
|
|
memcpy(reply_ret->buffer->data, lq.buf.data, lq.cursor);
|
|
reply_ret->data = reply_ret->buffer->data;
|
|
|
|
exit:
|
|
/* Cleanup. */
|
|
if (lq.packet != NULL)
|
|
packet_dereference(&lq.packet, MDL);
|
|
if (lq.client_id.data != NULL)
|
|
data_string_forget(&lq.client_id, MDL);
|
|
if (lq.server_id.data != NULL)
|
|
data_string_forget(&lq.server_id, MDL);
|
|
if (lq.lq_query.data != NULL)
|
|
data_string_forget(&lq.lq_query, MDL);
|
|
if (lq.query_opts != NULL)
|
|
option_state_dereference(&lq.query_opts, MDL);
|
|
if (lq.reply_opts != NULL)
|
|
option_state_dereference(&lq.reply_opts, MDL);
|
|
}
|
|
|
|
#endif /* DHCPv6 */
|