dhcp.c revision 903a11ebdc8df157c4700150f41f1f262f4a8ae8
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stdio.h>
#include <stdlib.h>
#include <alloca.h>
#include <stdarg.h>
#include <sys/sysmacros.h>
#include <assert.h>
#include <sys/byteorder.h>
#include <string.h>
#include <time.h>
#include <syslog.h>
#include <netdb.h>
#include <dhcp_symbol.h>
#include <nss_dbdefs.h>
#include <dlfcn.h>
#include "dhcpd.h"
#include "per_dnet.h"
#include "interfaces.h"
#include <locale.h>
#include <resolv.h>
static char *disp_clnt_msg(PKT_LIST *, char *, int);
/*
* Offer cache.
*
* The DHCP server maintains a cache of DHCP OFFERs it has extended to DHCP
* clients. It does so because:
* a) Subsequent requests get the same answer, and the same IP address
* isn't offered to a different client.
*
* b) No ICMP validation is required the second time through, nor is a
* database lookup required.
*
* c) If the client accepts the OFFER and sends a REQUEST, we can simply
* lookup the record by client IP address, the one field guaranteed to
* be unique within the dhcp network table.
*
* We don't explicitly delete entries from the offer cache. We let them time
* out on their own. This is done to ensure the server responds correctly when
* many pending client requests are queued (duplicates). We don't want to ICMP
* validate an IP address we just allocated.
*
* The offer cache (and any database records cached in select_offer()) will
* diverge from the database for the length of the D_OFFER lifetime.
* SIGHUP flushes the offer cache, allowing management tools to inform the
* server of changes in a timely manner.
*/
/*
* Dispatch the DHCP packet based on its type.
*/
void
{
"Garbled DHCP Message type option from client: %s\n",
return;
}
case DISCOVER:
#ifdef DEBUG
#endif /* DEBUG */
#ifdef DEBUG
#endif /* DEBUG */
break;
case REQUEST:
#ifdef DEBUG
#endif /* DEBUG */
#ifdef DEBUG
#endif /* DEBUG */
break;
case DECLINE:
#ifdef DEBUG
#endif /* DEBUG */
#ifdef DEBUG
#endif /* DEBUG */
break;
case RELEASE:
#ifdef DEBUG
#endif /* DEBUG */
#ifdef DEBUG
#endif /* DEBUG */
break;
case INFORM:
#ifdef DEBUG
#endif /* DEBUG */
#ifdef DEBUG
#endif /* DEBUG */
break;
default:
"Unexpected DHCP message type: %d from client: %s.\n",
break;
}
}
/*
* Responding to a DISCOVER message. icmp echo check (if done) is synchronous.
* Previously known requests are in the OFFER cache.
*/
static void
{
int used_pkt_len;
*class_ecp, *class_vecp,
char *class_id;
int err = 0;
char class_idbuf[DSYM_CLASS_SIZE];
/*
* Purge offers when expired or the database has been re-read.
*
* Multi-threading: to better distribute garbage collection
* and data structure aging tasks, each thread must actively
* implement policy, rather then specialized, non-scalable
* threads which halt the server and update all data
* structures.
*
* The test below checks whether the offer has expired,
* due to aging, or re-reading of the dhcptab, via timeout
* or explicit signal.
*/
/*
* We've already validated this IP address in the past, and
* due to the OFFER hash table, we would not have offered this
* IP address to another client, so use the offer-cached record.
*/
} else {
/* Try to find an existing usable entry for the client. */
/* No bootp records, thank you. */
/*
* We don't limit this search by SIP, because this client
* may be owned by another server, and we need to detect this
* since that record may be MANUAL.
*/
/*
* An IP address, but not ours! It's up to the
* primary to respond to DISCOVERs on this
* address.
*/
if (verbose) {
m1 = "MANUAL";
m2 = " No other IP address "
"will be allocated.";
} else {
m1 = "DYNAMIC";
m2 = "";
}
sizeof (sipstr));
sizeof (cipstr));
"%2$s %3$s owned by server: "
}
/* We give up if that IP address is manual */
goto leave_offer;
} else {
sizeof (cipstr));
"MANUAL record %2$s is UNUSABLE. "
"No other IP address will be "
cipstr);
goto leave_offer;
} else
break; /* success */
}
}
/*
* select_offer() ONLY selects IP addresses owned
* by us. Only log a notice if we own any IP addresses
* at all. Otherwise, this is an informational server.
*/
"No more IP addresses on %1$s "
}
goto leave_offer;
}
} else
/*
* ICMP echo validate the address.
*/
if (!noping) {
/*
* If icmp echo validation fails, let the plp fall by
* the wayside.
*/
"registered for: %s, ignoring\n", cipstr);
goto leave_offer;
}
if (result) {
"ICMP ECHO reply to OFFER candidate: "
"%s, disabling.\n", cipstr);
&ndn)) != DSVC_SUCCESS) {
"ICMP ECHO reply to OFFER "
"candidate: %1$s. No "
"modifiable dhcp network "
"record. (%2$s)\n", cipstr,
} else {
/* Keep the cached entry current. */
}
goto leave_offer;
}
}
}
/*
* At this point, we've ICMP validated (if requested) the IP
* address, and can go about producing an OFFER for the client.
*/
if (!no_dhcptab) {
open_macros();
/*
* Macros are evaluated this way: First apply parameters from
* a client class macro (if present), then apply those from the
* network macro (if present), then apply those from the
* dhcp network macro (if present), and finally apply those
* from a client id macro (if present).
*/
/*
* First get a handle on network, dhcp network table macro,
* and client id macro values.
*/
/* Get a handle on the class id macro (if it exists). */
/*
* Locate the ENCODE list for encapsulated
* options associated with our class id within
* the class id macro.
*/
}
/*
* Locate the ENCODE list for encapsulated options
* associated with our class id within the network,
* dhcp network, and client macros.
*/
/*
* Combine the encapsulated option encode lists
* associated with our class id in the order defined
* above (class, net, dhcp network, client id)
*/
}
/*
* Combine standard option encode lists in the order defined
* above (class, net, dhcp network, and client id).
*/
else
/* If dhcptab configured to return hostname, do so. */
char hbuf[NSS_BUFLEN_HOSTS];
}
}
/* If dhcptab configured to echo client class, do so. */
NULL) {
}
}
/*
* For OFFERs, we don't check the client's lease nor LeaseNeg,
* regardless of whether the client has an existing allocation
* or not. Lease expiration (w/o LeaseNeg) only occur during
*/
if (existing_allocation) {
} else {
else {
}
}
}
/* First get a generic reply packet. */
/* Set the client's IP address */
/* Calculate lease time. */
/*
* Client is requesting specific options. let's try and ensure it
* gets what it wants, if at all possible.
*/
/* Now load all the asked for / configured options */
if (!no_dhcptab)
close_macros();
if (used_pkt_len < sizeof (PKT))
used_pkt_len = sizeof (PKT);
else
} else {
}
if (unreserve)
}
/*
* Responding to REQUEST message.
*
* Very similar to dhcp_offer(), except that we need to be more
* discriminating.
*
* The ciaddr field is TRUSTED. A INIT-REBOOTing client will place its
* notion of its IP address in the requested IP address option. INIT
* clients will place the value in the OFFERs yiaddr in the requested
* IP address option. INIT-REBOOT packets are differentiated from INIT
* packets in that the server id option is missing. ciaddr will only
*
* Error messages may be generated. Database write failures are no longer
* fatal, since we'll only respond to the client if the write succeeds.
*/
static void
{
int actual_len;
*class_ecp, *class_vecp,
*macro_ecp, *macro_vecp,
char *class_id;
char nak_mesg[DHCP_SCRATCH];
int err = 0;
char class_idbuf[DSYM_CLASS_SIZE];
/* Determine type of REQUEST we've got. */
"client: '%1$s'. Len is %2$d, when it should be "
sizeof (struct in_addr));
goto leave_ack;
}
/*
* Request in response to an OFFER. ciaddr must not
* be set. Requested IP address option will hold address
* we offered the client.
*/
if (verbose) {
"missing requested IP option.\n",
}
goto leave_ack;
}
sizeof (struct in_addr)) {
"client: '%1$s'. Len is %2$d, when it should be "
"%3$d \n",
sizeof (struct in_addr));
goto leave_ack;
}
sizeof (struct in_addr));
/*
* Someone else was selected. See if we made an
* offer, and clear it if we did. If offer expired
* before client responded, then no need to do
* anything.
*/
if (verbose) {
"Client: %1$s chose %2$s from server: %3$s,"
sizeof (ntoaa)),
sizeof (ntoab)),
sizeof (ntoac)));
}
goto leave_ack;
}
/*
* See comment at the top of the file for description of
* OFFER cache.
*
* If the offer expires before the client got around to
* requesting, and we can't confirm the address is still free,
* we'll silently ignore the client, until it drops back and
* tries to discover again. We will print a message in
* verbose mode however. If the Offer hasn't timed out, we
* bump it up again in case we have a bounce of queued up
* INIT requests to respond to.
*/
/*
* Hopefully, the timeout value is fairly long to
* prevent this.
*/
if (verbose) {
"Offer on %1$s expired for client: %2$s\n",
}
goto leave_ack;
} else
/*
* The client selected us. Create a ACK, and send
* it off to the client, commit to permanent
* storage the new binding.
*/
/*
* If client thinks we offered it a different address, then
* ignore it.
*/
sizeof (struct in_addr)) != 0) {
if (verbose) {
"offered IP address %2$s is different than "
sizeof (ntoab)));
}
goto leave_ack;
}
/*
* Clear out any temporary ARP table entry we may have
* created during the offer.
*/
} else {
/*
* Either a client in the INIT-REBOOT state, or one in
* either RENEW or REBIND states. The latter will have
* ciaddr set, whereas the former will place its concept
* of its IP address in the requested IP address option.
*/
/*
* Client isn't sure of its IP address. It's
* attempting to verify its address, thus requested
* IP option better be present, and correct.
*/
"Client: %s REQUEST is missing "
goto leave_ack;
}
sizeof (struct in_addr)) {
"from client: '%1$s'. Len is %2$d, when it "
sizeof (struct in_addr));
goto leave_ack;
}
sizeof (struct in_addr));
/* No bootp records, thank you. */
} else {
/*
* Client knows its IP address. It is trying to
* and use it to locate the client's record. If we
* can't find the client's record, then we keep
* silent. If the client id of the record doesn't
* match this client, then the database is
* inconsistent, and we'll ignore it.
*/
/* No bootp records, thank you. */
}
goto leave_ack;
/*
* If this address is not owned by this server and
* the client is trying to verify the address, then
* ignore the client. If the client is simply trying
* to rebind, then don't respond until after
* renog_secs passes, to give the server that *OWNS*
* the address time to respond first.
*/
if (clnt_state == INIT_REBOOT_STATE) {
if (verbose) {
"%1$s is requesting "
"verification of %2$s "
"owned by %3$s\n",
}
goto leave_ack;
} else {
goto leave_ack;
}
}
/*
* Client has the wrong IP address. Nak.
*/
"Incorrect IP address.");
} else {
sizeof (nak_mesg),
"Lease has expired.");
}
}
} else {
if (clnt_state == RENEW_REBIND_STATE) {
"renew %2$s, an IP address it has not "
goto leave_ack;
}
/*
* There is no such client registered for this
* address. Check if their address is on the correct
* net. If it is, then we'll assume that some other,
* non-database sharing DHCP server knows about this
* client. If the client is on the wrong net, NAK'em.
*/
/* Right net, but no record of client. */
if (verbose) {
"Client: %1$s is trying to verify "
"unrecorded address: %2$s, "
}
goto leave_ack;
} else {
sizeof (nak_mesg),
"No valid configuration exists on "
} else {
if (verbose) {
"Client: %1$s is not "
"recorded as having "
"address: %2$s\n",
}
goto leave_ack;
}
}
}
}
/*
* Produce the appropriate response.
*/
/*
* Setting yiaddr to the client's ciaddr abuses the
* semantics of yiaddr, So we set this to 0L.
*
* We twiddle the broadcast flag to force the
*
* Exception: If a client's lease has expired, and it
* is still trying to renegotiate its lease, AND ciaddr
* is set, AND ciaddr is on a "remote" net, unicast the
* NAK. Gross, huh? But SPA could make this happen with
* super short leases.
*/
} else {
}
*optp++ = CD_MESSAGE;
if (actual_len < sizeof (PKT))
actual_len = sizeof (PKT);
} else {
/* Set the client's IP address */
/*
* Macros are evaluated this way: First apply parameters
* from a client class macro (if present), then apply
* those from the network macro (if present), then apply
* those from the server macro (if present), and finally
* apply those from a client id macro (if present).
*/
if (!no_dhcptab) {
open_macros();
class_id);
}
class_id);
}
class_id);
class_id);
}
ENC_COPY);
}
ENC_COPY);
} else
/*
* If the server is configured to do host name updates
* and the REQUEST packet contains a hostname request,
* see whether we can honor it.
*
* First, determine (via name_avail()) whether the host
* name is unassigned or belongs to an unleased IP
* address under our control. If not, we won't do a
* host name update on behalf of the client.
*
* Second, if we own the IP address and it is in the
* correct network table, see whether an update is
* necessary (or, in the lucky case, whether the name
* requested already belongs to that address), in which
* case we need do nothing more than return the option.
*/
if ((nsutimeout_secs != DHCP_NO_NSU) &&
int hlen;
/* turn hostname option into a string */
&iap)) {
/*
* If we pass this test, it means either
* no address is currently associated
* with the requested host name (iap is
* NULL) or the address doesn't match
* the one to be leased; in either case
* an update attempt is needed.
*
* Otherwise (in the else case), we need
* only send the response - the name and
* address already match.
*/
hecp = make_encode(
ENC_COPY);
hecp,
}
} else {
hecp = make_encode(
ENC_COPY);
}
}
}
/*
* If dhcptab configured to return hostname, do so.
*/
if ((hostname_update == B_FALSE) &&
CD_BOOL_HOSTNAME) != NULL)) {
char hbuf[NSS_BUFLEN_HOSTS];
}
}
/*
* If dhcptab configured to echo client class, do so.
*/
CD_BOOL_ECHO_VCLASS) != NULL) {
ENC_COPY);
}
}
else {
/*
* Offered absolute Lease time is cached
* in the lease field of the record. If
* that's expired, then they'll get the
* policy value again here. Must have been
*/
else
} else
}
NULL)
else
/*
* Modify changed fields in new database record.
*/
/*
* This is a little longer than we offered (not taking into
* account the secs field), but since I trust the UNIX
* clock better than the PC's, it is a good idea to give
* the PC a little more time than it thinks, just due to
* clock slop on PC's.
*/
else
/*
* It is critical to write the database record if the
* client is in the INIT state, so we don't reply to the
* client if this fails. However, if the client is simply
* trying to verify its address or extend its lease, then
* we'll reply regardless of the status of the write,
* although we'll return the old lease time.
*
* If the client is in the INIT_REBOOT state, and the
* lease time hasn't changed, we don't bother with the
* write, since nothing has changed.
*/
/* Keep state of the cached entry current. */
if (write_error == DSVC_SUCCESS) {
}
} else {
if (verbose) {
"Database write unnecessary for "
"DHCP client: "
}
}
if (write_error == DSVC_SUCCESS ||
clnt_state == INIT_REBOOT_STATE) {
if (write_error != DSVC_SUCCESS)
else {
/* Note that the conversation has completed. */
}
/* Now load all the asked for / configured options */
vecp);
if (actual_len < sizeof (PKT))
actual_len = sizeof (PKT);
if (verbose) {
"Client: %1$s maps to IP: %2$s\n",
}
if (clnt_state == INIT_STATE)
else
}
if (!no_dhcptab)
close_macros();
}
}
/* Reacting to a client's DECLINE or RELEASE. */
static void
{
char *fmtp;
int err = 0;
char ipb[INET_ADDRSTRLEN];
char clnt_msg[DHCP_MAX_OPT_SIZE];
sizeof (struct in_addr)) {
sizeof (struct in_addr));
}
} else
/* Look for a matching IP address and Client ID */
if (verbose) {
fmtp = "Unregistered client: %1$s is "
"DECLINEing address: %2$s.\n";
} else {
fmtp = "Unregistered client: %1$s is "
"RELEASEing address: %2$s.\n";
}
}
return;
}
/* If the entry is not one of ours, then give up. */
if (verbose) {
fmtp = "Client: %1$s is DECLINEing: "
"%2$s not owned by this server.\n";
} else {
fmtp = "Client: %1$s is RELEASEing: "
"%2$s not owned by this server.\n";
}
}
goto leave_dec_rel;
}
}
} else {
"Client: %1$s is trying to RELEASE manual "
goto leave_dec_rel;
}
if (verbose) {
"Client: %1$s RELEASED address: %2$s\n",
"RELEASE: client message: %s\n",
sizeof (clnt_msg)));
}
}
}
/* Clear out the cid and lease fields */
}
/* Ignore write errors. */
if (err != DSVC_SUCCESS) {
"%1$s: ERROR modifying database: %2$s for client %3$s\n",
} else {
/*
* performance: save select_offer() lots of work by
* caching this perfectly good ip address in freerec.
*/
}
}
}
/*
* Responding to an INFORM message.
*
* INFORM messages are received from clients that already have their network
* parameters (such as IP address and subnet mask), but wish to receive
* other configuration parameters. The server will not check for an existing
* lease as clients may have obtained their network parameters by some
* means other than DHCP. Similarly, the DHCPACK generated in response to
* the INFORM message will not include lease time information. All other
* configuration parameters are returned.
*/
static void
{
int used_pkt_len;
char *class_id;
char class_idbuf[DSYM_CLASS_SIZE];
/*
* Macros are evaluated this way: First apply parameters from
* a client class macro (if present), then apply those from the
* network macro (if present), and finally apply those from a
* client id macro (if present).
*/
if (!no_dhcptab) {
open_macros();
class_id);
}
ENC_COPY);
}
}
/* First get a generic reply packet. */
/*
* Client is requesting specific options. let's try and ensure it
* gets what it wants, if at all possible.
*/
/*
* Explicitly set the ciaddr to be that which the client gave
* us.
*/
/*
* Now load all the asked for / configured options. DON'T send
* any lease time info!
*/
if (!no_dhcptab)
close_macros();
if (used_pkt_len < sizeof (PKT))
used_pkt_len = sizeof (PKT);
}
static char *
{
}
return (bufp);
}
/*
* serverip expected in host order
*/
static PKT *
{
/*
* We need to determine the packet size. Perhaps the client has told
* us?
*/
"from\nclient: '%1$s'. Len is %2$d, when it should "
"be %3$d. Defaulting to %4$d.\n",
sizeof (uint16_t), DHCP_DEF_MAX_SIZE);
} else {
sizeof (uint16_t));
}
} else {
/*
* Define size to be a fixed length. Too hard to add up all
* without doing just about as much work as constructing the
* whole reply packet.
*/
}
/* Generate a generically initialized BOOTP packet */
/*
* Set pkt type.
*/
/*
* All reply packets have server id set.
*/
#if defined(_LITTLE_ENDIAN)
#else
#endif /* _LITTLE_ENDIAN */
return (reply_pktp);
}
/*
* If the client requests it, and either it isn't currently configured
* or hasn't already been added, provide the option now. Will also work
* for NULL ENCODE lists, but initializing them to point to the requested
* options.
*
* If nsswitch contains host name services which hang, big problems occur
* with dhcp server, since the main thread hangs waiting for that name
* service's timeout.
*
* NOTE: this function should be called only after all other parameter
* merges have taken place (combine_encode).
*/
static void
{
char hbuf[NSS_BUFLEN_HOSTS];
int herrno;
/* Find the end. */
if (*ecp) {
/* null */;
}
/* HOSTNAME */
if (end_ecp) {
} else {
}
}
}
/*
* all bets off for the following if thru a relay agent.
*/
return;
/* SUBNET MASK */
if (end_ecp) {
} else
}
/* BROADCAST ADDRESS */
if (end_ecp) {
} else
}
/* IP MTU */
if (end_ecp) {
} else
}
}
/*
* Is a specific option requested? Returns True if so, False otherwise.
*/
static int
{
return (B_TRUE);
}
return (B_FALSE);
}
/*
* Locates lease option, if possible, otherwise allocates an encode and
* appends it to the end. Changes current lease setting.
*
* TODO: ugh. We don't address the case where the Lease time changes, but
* T1 and T2 don't. We don't want T1 or T2 to be greater than the lease
* time! Perhaps T1 and T2 should be a percentage of lease time... Later..
*/
static void
{
} else {
} else {
}
}
}
/*
* Sets appropriate option in passed ENCODE list for lease. Returns
* calculated relative lease time.
*/
static int
{
sizeof (lease_t));
} else
else {
/* sorry! */
if (oldlease)
else
}
} else {
/*
* lease is not automatic and is negotiable!
* If the dhcp-network lease is bigger than the current
* policy value, then let the client benefit from this
* situation.
*/
if (oldlease > rel_current)
/*
* Client is requesting a lease renegotiation.
*/
/*
* Note that this comparison handles permanent
* leases as well. Limit lease to configured value.
*/
if (newlease > rel_current)
} else
}
return (newlease);
}
/*
* If a packet has the classid set, return the value, else return null.
*/
char *
{
char *retp;
/*
* If the class id is set, see if there is a macro by this
* name. If so, then "OR" the ENCODE settings of the class
* macro with the packet macro. Settings in the packet macro
* OVERRIDE settings in the class macro.
*/
} else
return (retp);
}
/*
* Checks whether an offer ip address in the per net inet address
* cache.
*
* pnd - per net structure
* reservep - address to check, in network order.
*/
static boolean_t
{
}
/*
* Adds or updates an offer to the per client data structure. The client
* struct is hashed by clientid into the per net ctable hash table, and
* by offer address in the itable hash table, which is used to reserve the
* ip address. Lease time is expected to be set by caller.
* Will update existing OFFER if already provided.
*
* pcd - per client data struct.
* dnlp - pointer to current container entry. Performance: caching reduces
* datastore activity, structure copying.
* nlease - new lease time.
* reservep - new offer address (expected in network order).
* purge_cache - Multithreading: avoid redundant cache purging in
* select_offer().
*/
{
char ntoab[INET_ADDRSTRLEN];
/* Save the original datastore record. */
}
/* Determine the offer address. */
else {
"Neither offer IP nor IP to reserve present\n");
return (B_FALSE);
}
/* If updating, release the old offer address. */
} else {
} else
}
if (nlease != 0)
/* Prepare to insert pcd into the offer hash table. */
if (insert) {
"to client: %2$s\n",
}
return (B_FALSE);
}
} else
if (offer) {
}
if (debug) {
} else if (update) {
} else {
}
}
return (B_TRUE);
}
/*
* Deletes an offer.
*
* pcd - per client struct
* expired - has offer expired, or been purged
* purge_cache - Multi-threading: avoid redundant cache purging in
* select_offer().
*/
void
{
char ntoab[INET_ADDRSTRLEN];
if (debug) {
else
}
/*
* The offer cache ensures that recently granted offer
* addresses won't attempt to be reused from the dnet
* caches. When purging one of these offers, be sure to
* remove the associated record from the dnet cache,
* to avoid collisions.
*/
if (purge_cache)
}
/* Prepare to delete pcd from the offer hash table. */
if (expired)
}
}
/*
* Allocate a new entry in the dhcp-network db for the cid, taking into
* account requested IP address. Verify address.
*
* The network portion of the address doesn't have to be the same as ours,
* just owned by us. We also make sure we don't select a record which is
* currently in use, by reserving the address in the offer cache. Database
* records are cached up to the D_OFFER lifetime to improve performance.
*
* Returns: 1 if there's a usable entry for the client, 0
* if not. Places the record in the dn_rec_list_t structure
* pointer handed in.
*/
/*ARGSUSED*/
{
int nrecords;
int retry;
else
if (!is_bootp) {
/*
* Is the DHCP client requesting a specific address? Is so, and
* we can satisfy him, do so.
*/
sizeof (struct in_addr));
int hlen;
/* turn hostname option into a string */
hlen);
"select_offer: hostname request for %s\n", hname);
}
"available, req_ip %x\n", hname,
} else
"false or no address for %s\n", hname);
}
}
/*
* Check the offer list and table entry.
*/
if (!found) {
/*
* Try to find a free entry. Look for an AVAILABLE entry
* (cid == 0x00, len == 1), owned by us.
* The outer loop runs through the server ips owned by us.
*
* Multi-threading: to improve performance, the following
* algorithm coordinates accesses to the underlying table,
* so only one thread is initiating lookups per network.
* This is crucial, as lookup operations are expensive,
* and not sufficiently malleable to allow partitioned
* lookups (e.g. all that can be asked for are n free or
* server-owned entries, multiple threads will retrieve
* the same records).
*
* The three iterations through the inner loop attempt to use
*
* 1) the next cached entry
* 2) all cached entries
* 3) all free or per-server entries in the underlying table
*
* Since many threads are consuming the cached entries,
* any thread may find itself in the role of having to
* refresh the cache. We always read at least enough
* entries to satisfy all current threads. Reading all
* records is prohibitively expensive, and should only
* be done as a last resort.
*
* As always, to better distribute garbage
* collection and data structure aging tasks, each
* thread must actively implement policy, checking
* for offer expiration (which invalidates the cache).
*/
/*
* Initialize query.
*/
/*
* Decide whether a bootp record is required.
*/
if (is_bootp)
/*
* These flags are used counter-intuitively.
* This says that the setting of the bit
* (off) in the dn.dn_flags matches the
* setting in the record (off).
*/
/*
* Purge cached records when expired or database
* re-read.
*/
}
if (retry == 0) {
/* Try the next cached record */
} else if (retry == 1) {
/*
* Try all remaining cached
* records
*/
}
}
if (retry > 1) {
/* Try all possible records in datastore. */
nrecords = -1;
}
}
}
if (io_done) {
/*
* Note time when records were read.
*/
}
}
/* Save any leftover records for later use. */
/* null statement */;
}
}
}
}
/*
* Struck out. No usable available addresses. Let's look for
* the LRU expired address. Only makes sense for dhcp
* clients. First we'll try the next record from
* the lru list (this assumes lru database search capability).
* Next we'll try all records. Finally we'll go get all
* free records.
*
* Multi-threading: to improve performance, the following
* algorithm coordinates accesses to the underlying table,
* so only one thread is initiating lookups per network.
* This is crucial, as lookup operations are expensive,
* and not sufficiently malleable to allow partitioned
* lookups (e.g. all that can be asked for are n free or
* server-owned entries, multiple threads will retrieve
* the same records).
*
* We only consider clients owned by us.
* The outer loop runs through the server ips owned by us
*
* The three iterations through the inner loop attempt to use
*
* 1) the next cached entry
* 2) all cached entries
* 3) all free or per-server entries in the underlying table
*
* Since many threads are consuming the cached entries,
* any thread may find itself in the role of having to
* refresh the cache. We always read at least enough
* entries to satisfy all current threads. Reading all
* records is prohibitively expensive, and should only
* be done as a last resort.
*
* As always, to better distribute garbage
* collection and data structure aging tasks, each
* thread must actively implement policy, checking
* for offer expiration (which invalidates the cache).
*/
/*
* Initialize query.
*/
/*
* These flags are used counter-intuitively.
* This says that the setting of the bit
* (off) in the dn.dn_flags matches the
* setting in the record (off).
*/
/*
* Purge cached records when expired or database
* re-read.
*/
}
if (retry == 0) {
/* Try the next cached record */
} else if (retry == 1) {
/*
* Try all remaining cached
* records
*/
}
}
if (retry > 1) {
/* Try all possible records */
nrecords = -1;
dnsp);
}
}
B_TRUE);
}
if (io_done) {
}
}
/*
* Save any leftover records for possible
* later use
*/
/* null statement */;
}
}
}
}
return (found);
}
/*
* purge_dnet_cache() - remove conflicting entries from the
* free and lru dnet caches when records are modified. Expensive
* but necessary.
*
* pnd - per net struct
*/
static void
{
} else {
}
break;
}
}
} else {
}
break;
}
}
}
/*
* add_dnet_cache() - add a free entry back to the free dnet cache.
*
* Performance: this can greatly reduce the amount of work select_offer()
* must perform.
*
* pnd - per net struct
*/
static void
{
}
static char unowned_net[] = "the DHCP server believes the IP address that"
" corresponds to the requested host name belongs to a network not"
" managed by the DHCP server.\n";
static char unowned_addr[] = "the DHCP server believes the IP address that"
" corresponds to the requested host name is not managed by the DHCP"
" server.\n";
/*
* Determine whether the requested IP address is available to the requesting
* client. To be so, its IP address must be managed by us, be on the ``right''
* network and neither currently leased nor currently under offer to another
* client.
*/
static boolean_t
{
/*
* first, check the ICMP list or offer list.
*/
if (isname) {
/* Offered to someone else. Sorry. */
" check_offer failed\n");
return (B_FALSE);
}
} else {
/* Offered to someone else. Sorry. */
if (isname) {
" check_other_offers failed\n");
}
return (B_FALSE);
}
}
/*
* entry_available() searches for owner_ips
* query on DN_QCIP will suffice here
*/
(void **)&dnip, 0);
/*
* Ok, the requested IP exists. But is it available?
*/
return (B_FALSE);
}
} else {
if (isname)
else
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Determine whether "name" is available. To be so, it must either not have
* a corresponding IP address, or its IP address must be managed by us and
* neither currently leased nor currently under offer to a client.
*
* To determine this, we first attempt to translate the name to an address.
* If no name-to-address translation exists, it's automatically available.
* Otherwise, we next check for any outstanding offers. Finally, we look
* at the flags in the corresponding per-network table to see whether the
* address is currently leased.
*
* Upon successful completion, we also return the vetted IP address as a
* value result parameter.
*/
static boolean_t
{
char hbuf[NSS_BUFLEN_HOSTS];
/*
* If possible, use a fully-qualified name to do the name-to-
* address query. The complication is that the domain name
* with which to qualify the client's host name resides in a
* dhcptab macro unavailable at the time of the DHCPOFFER.
* ecp will be non-NULL if we may have the means to fully-qualify
* the name given.
*/
} else {
/*
* Append '.' domain-name '.' to hostname.
* Note the use of the trailing '.' to avoid any surprises
* because of the ndots value (see resolv.conf(4) for more
* information about the latter).
*
* First see whether we can dredge up domain-name from the
* ENCODE list.
*/
/*
* name_avail() should never be called unless the
* CD_HOSTNAME option is present in the client's
* packet.
*/
/* null at end of the hostname */
}
return (B_FALSE);
"found CD_DNSDOMAIN and qualified: %s\n", fqname);
} else {
/*
* No DNS domain in the ENCODE list, have to use
* local domain name.
*/
return (B_FALSE);
"name_avail: unqualified name\n"
"qualified with local domain: %s\n", fqname);
}
}
/*
* Try a forward lookup on the requested name.
* Consider the name available if we get a definitive
* ``name doesn't exist'' indication.
*/
"name_avail(T): gethostbyname_r failed\n");
return (B_TRUE);
} else {
"name_avail(F): gethostbyname_r failed, err %d\n",
err);
return (B_FALSE);
}
/*
* Check that the address has not been leased to someone else.
* Bear in mind that there may be inactive A records in the DNS
* (since we don't delete them when a lease expires or is released).
* Try a reverse lookup on the address returned in hp.
* If the owner of this address is different to the requested name
* we can infer that owner is a stale A record.
*/
/* If there's no PTR record the address can't be in use */
"name_avail(T): gethostbyaddr_r failed\n");
return (B_TRUE);
} else {
"name_avail(F): gethostbyaddr_r failed\n");
return (B_FALSE);
}
}
/* If name returned is not a FQDN, qualify with local domain name */
} else {
return (B_FALSE);
"name_avail: address owner qualified with %s\n",
}
/* Forward lookup found an inactive record - ignore it */
owner);
return (B_TRUE);
}
/* Get pnd of the current client */
/* get pnd of previous owner of the hostname */
/* we must not manage the net containing this address */
return (B_FALSE);
}
}
/*
* Test that the address has not been offered to someone else.
*/
if (isopen) {
}
return (B_FALSE);
}
if (isopen)
/* LINTED */
return (B_TRUE);
}
static boolean_t
{
char ntoab[INET_ADDRSTRLEN];
" %s is manually allocated or not usable\n",
ntoab);
return (B_FALSE);
}
" %s is a permanent address or reserved for BOOTP\n",
ntoab);
return (B_FALSE);
}
" lease on %s has not expired\n",
ntoab);
return (B_FALSE);
}
" %s does not match owner_ip\n",
ntoab);
return (B_FALSE);
}
/* Input IP is good. */
return (B_TRUE);
}
static char msft_classid[] = "MSFT ";
static char no_domain[] = "name service update on behalf of client with ID"
" %s failed because requested name was not fully-qualified and no DNS"
" domain name was specified for this client in the dhcptab\n";
/*
* Given a host name and IP address, try to do a host name update.
*/
static boolean_t
{
char class_idbuf[DSYM_CLASS_SIZE];
int puthostent_ret;
/*
* hostent information is dynamically allocated so that threads spawned
* by dns_puthostent() will have access to it after the calling thread
* has returned.
*/
/*
* Convert address to network order, as that's what hostent's are
* expected to be.
*/
/* LINTED */
/*
* Is the host name unqualified? If so, try to qualify it. If that
* can't be done, explain why the update won't be attempted.
*/
/*
* See whether we can dredge up the DNS domain from the
* ENCODE list.
*/
NULL) {
char *fqname;
/*
* We need room for
*
* hostname len +
* strlen(".") +
* domainname len +
* strlen(".") +
* trailing '\0'
*
* Note the use of the trailing '.' to avoid any
* surprises because of the ndots value (see
* resolv.conf(4) for more information about
* the latter).
*/
}
/* first copy host name, ... */
/* then '.', ... */
/* ... then domain name, */
/* then a trailing '.', ... */
/* no need to null-terminate - smalloc() did it */
"found CD_DNSDOMAIN and qualified: %s\n", fqname);
} else {
}
} else {
}
/* returns -1 or the number of name service updates done */
if (puthostent_ret == -1) {
return (B_FALSE);
} else if (puthostent_ret == 0) {
/*
* dns_puthostent() didn't see any errors occur,
* but no updates were done; Microsoft clients
* (i.e. clients with a Microsoft class ID) expect
* it to succeed, so we lie to them.
*/
sizeof (class_idbuf))) != NULL) &&
sizeof (msft_classid)) == 0)) {
return (B_TRUE);
} else
return (B_FALSE);
} else {
return (B_TRUE);
}
}