Mercurial > illumos > onarm
view usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/dhcp.c @ 4:1a15d5aaf794
synchronized with onnv_86 (6202) in onnv-gate
author | Koji Uno <koji.uno@sun.com> |
---|---|
date | Mon, 31 Aug 2009 14:38:03 +0900 |
parents | c9caec207d52 |
children |
line wrap: on
line source
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 * or http://www.opensolaris.org/os/licensing. * 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 2005 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/types.h> #include <sys/sysmacros.h> #include <assert.h> #include <sys/socket.h> #include <sys/byteorder.h> #include <string.h> #include <time.h> #include <sys/socket.h> #include <syslog.h> #include <sys/errno.h> #include <net/if.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <netinet/dhcp.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 void dhcp_offer(dsvc_clnt_t *, PKT_LIST *); static void dhcp_req_ack(dsvc_clnt_t *, PKT_LIST *); static void dhcp_dec_rel(dsvc_clnt_t *, PKT_LIST *, int); static void dhcp_inform(dsvc_clnt_t *, PKT_LIST *); static PKT *gen_reply_pkt(dsvc_clnt_t *, PKT_LIST *, int, uint_t *, uchar_t **, struct in_addr *); static void set_lease_option(ENCODE **, lease_t); static int config_lease(PKT_LIST *, dn_rec_t *, ENCODE **, lease_t, boolean_t); static int is_option_requested(PKT_LIST *, ushort_t); static void add_request_list(IF *, PKT_LIST *, ENCODE **, struct in_addr *); static char *disp_clnt_msg(PKT_LIST *, char *, int); static void add_dnet_cache(dsvc_dnet_t *, dn_rec_list_t *); static void purge_dnet_cache(dsvc_dnet_t *, dn_rec_t *); static boolean_t addr_avail(dsvc_dnet_t *, dsvc_clnt_t *, dn_rec_list_t **, struct in_addr, boolean_t); static boolean_t name_avail(char *, dsvc_clnt_t *, PKT_LIST *, dn_rec_list_t **, ENCODE *, struct in_addr **); static boolean_t entry_available(dsvc_clnt_t *, dn_rec_t *); static boolean_t do_nsupdate(struct in_addr, ENCODE *, PKT_LIST *); extern int dns_puthostent(struct hostent *, time_t); /* * 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 dhcp(dsvc_clnt_t *pcd, PKT_LIST *plp) { if (plp->opts[CD_DHCP_TYPE]->len != 1) { dhcpmsg(LOG_ERR, "Garbled DHCP Message type option from client: %s\n", pcd->cidbuf); return; } pcd->state = *plp->opts[CD_DHCP_TYPE]->value; switch (pcd->state) { case DISCOVER: #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processing OFFER...\n"); #endif /* DEBUG */ dhcp_offer(pcd, plp); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processed OFFER.\n"); #endif /* DEBUG */ break; case REQUEST: #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processing REQUEST...\n"); #endif /* DEBUG */ dhcp_req_ack(pcd, plp); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processed REQUEST.\n"); #endif /* DEBUG */ break; case DECLINE: #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processing DECLINE...\n"); #endif /* DEBUG */ dhcp_dec_rel(pcd, plp, DECLINE); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processed DECLINE.\n"); #endif /* DEBUG */ break; case RELEASE: #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processing RELEASE...\n"); #endif /* DEBUG */ dhcp_dec_rel(pcd, plp, RELEASE); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processed RELEASE.\n"); #endif /* DEBUG */ break; case INFORM: #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processing INFORM...\n"); #endif /* DEBUG */ dhcp_inform(pcd, plp); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "dhcp() - processed INFORM.\n"); #endif /* DEBUG */ break; default: dhcpmsg(LOG_INFO, "Unexpected DHCP message type: %d from client: %s.\n", plp->opts[CD_DHCP_TYPE]->value, pcd->cidbuf); break; } } /* * Responding to a DISCOVER message. icmp echo check (if done) is synchronous. * Previously known requests are in the OFFER cache. */ static void dhcp_offer(dsvc_clnt_t *pcd, PKT_LIST *plp) { IF *ifp = pcd->ifp; boolean_t result; struct in_addr nsip, ncip; dsvc_dnet_t *pnd = pcd->pnd; uint_t replen; int used_pkt_len; PKT *rep_pktp = NULL; uchar_t *optp; ENCODE *ecp, *vecp, *macro_ecp, *macro_vecp, *class_ecp, *class_vecp, *cid_ecp, *cid_vecp, *net_ecp, *net_vecp; MACRO *net_mp, *pkt_mp, *class_mp, *cid_mp; char *class_id; time_t now = time(NULL); lease_t newlease, oldlease = 0; int err = 0; boolean_t existing_allocation = B_FALSE; boolean_t existing_offer = B_FALSE; char sipstr[INET_ADDRSTRLEN], cipstr[INET_ADDRSTRLEN]; char class_idbuf[DSYM_CLASS_SIZE]; dn_rec_t *dnp, dn, ndn; uint32_t query; dn_rec_list_t *dncp = NULL, *dnlp = NULL; boolean_t unreserve = B_FALSE; class_id = get_class_id(plp, class_idbuf, sizeof (class_idbuf)); /* * 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. */ if (pcd->off_ip.s_addr != htonl(INADDR_ANY) && PCD_OFFER_TIMEOUT(pcd, now)) purge_offer(pcd, B_TRUE, B_TRUE); if (pcd->off_ip.s_addr != htonl(INADDR_ANY)) { /* * 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. */ existing_offer = B_TRUE; dnlp = pcd->dnlp; dnp = dnlp->dnl_rec; ncip.s_addr = htonl(dnp->dn_cip.s_addr); } else { /* Try to find an existing usable entry for the client. */ DSVC_QINIT(query); DSVC_QEQ(query, DN_QCID); (void) memcpy(dn.dn_cid, pcd->cid, pcd->cid_len); dn.dn_cid_len = pcd->cid_len; /* No bootp records, thank you. */ DSVC_QNEQ(query, DN_QFBOOTP_ONLY); dn.dn_flags = DN_FBOOTP_ONLY; /* * 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. */ dncp = NULL; dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query, -1, &dn, (void **)&dncp, S_CID); while (dnlp != NULL) { dnp = dnlp->dnl_rec; if (match_ownerip(htonl(dnp->dn_sip.s_addr)) == NULL) { /* * An IP address, but not ours! It's up to the * primary to respond to DISCOVERs on this * address. */ if (verbose) { char *m1, *m2; if (dnp->dn_flags & DN_FMANUAL) { m1 = "MANUAL"; m2 = " No other IP address " "will be allocated."; } else { m1 = "DYNAMIC"; m2 = ""; } nsip.s_addr = htonl(dnp->dn_sip.s_addr); (void) inet_ntop(AF_INET, &nsip, sipstr, sizeof (sipstr)); ncip.s_addr = htonl(dnp->dn_cip.s_addr); (void) inet_ntop(AF_INET, &ncip, cipstr, sizeof (cipstr)); dhcpmsg(LOG_INFO, "Client: %1$s has " "%2$s %3$s owned by server: " "%4$s.%5$s\n", pcd->cidbuf, m1, cipstr, sipstr, m2); } /* We give up if that IP address is manual */ if (dnp->dn_flags & DN_FMANUAL) goto leave_offer; } else { uint_t bits = DN_FUNUSABLE | DN_FMANUAL; if ((dnp->dn_flags & bits) == bits) { ncip.s_addr = htonl(dnp->dn_cip.s_addr); (void) inet_ntop(AF_INET, &ncip, cipstr, sizeof (cipstr)); dhcpmsg(LOG_WARNING, "Client: %1$s " "MANUAL record %2$s is UNUSABLE. " "No other IP address will be " "allocated.\n", pcd->cidbuf, cipstr); goto leave_offer; } else break; /* success */ } free_dnrec_list(dnlp); dnlp = detach_dnrec_from_list(NULL, dncp, &dncp); } if (dnlp == NULL) { /* * 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. */ if (!select_offer(pnd, plp, pcd, &dnlp)) { if (pnd->naddrs > 0) { dhcpmsg(LOG_NOTICE, "No more IP addresses on %1$s " "network (%2$s)\n", pnd->network, pcd->cidbuf); } goto leave_offer; } dnp = dnlp->dnl_rec; } else existing_allocation = B_TRUE; ncip.s_addr = htonl(dnp->dn_cip.s_addr); (void) inet_ntop(AF_INET, &ncip, cipstr, sizeof (cipstr)); /* * ICMP echo validate the address. */ if (!noping) { /* * If icmp echo validation fails, let the plp fall by * the wayside. */ if (icmp_echo_check(&ncip, &result) != 0) { dhcpmsg(LOG_ERR, "ICMP ECHO check cannot be " "registered for: %s, ignoring\n", cipstr); unreserve = B_TRUE; goto leave_offer; } if (result) { dhcpmsg(LOG_WARNING, "ICMP ECHO reply to OFFER candidate: " "%s, disabling.\n", cipstr); ndn = *dnp; /* struct copy */ ndn.dn_flags |= DN_FUNUSABLE; if ((err = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn)) != DSVC_SUCCESS) { dhcpmsg(LOG_ERR, "ICMP ECHO reply to OFFER " "candidate: %1$s. No " "modifiable dhcp network " "record. (%2$s)\n", cipstr, dhcpsvc_errmsg(err)); } else { /* Keep the cached entry current. */ *dnp = ndn; /* struct copy */ } logtrans(P_DHCP, L_ICMP_ECHO, 0, ncip, server_ip, plp); unreserve = B_TRUE; 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. */ ecp = vecp = NULL; net_vecp = net_ecp = NULL; macro_vecp = macro_ecp = NULL; class_vecp = class_ecp = NULL; cid_vecp = cid_ecp = NULL; 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. */ if ((net_mp = get_macro(pnd->network)) != NULL) net_ecp = net_mp->head; if ((pkt_mp = get_macro(dnp->dn_macro)) != NULL) macro_ecp = pkt_mp->head; if ((cid_mp = get_macro(pcd->cidbuf)) != NULL) cid_ecp = cid_mp->head; if (class_id != NULL) { /* Get a handle on the class id macro (if it exists). */ if ((class_mp = get_macro(class_id)) != NULL) { /* * Locate the ENCODE list for encapsulated * options associated with our class id within * the class id macro. */ class_vecp = vendor_encodes(class_mp, class_id); class_ecp = class_mp->head; } /* * Locate the ENCODE list for encapsulated options * associated with our class id within the network, * dhcp network, and client macros. */ if (net_mp != NULL) net_vecp = vendor_encodes(net_mp, class_id); if (pkt_mp != NULL) macro_vecp = vendor_encodes(pkt_mp, class_id); if (cid_mp != NULL) cid_vecp = vendor_encodes(cid_mp, class_id); /* * Combine the encapsulated option encode lists * associated with our class id in the order defined * above (class, net, dhcp network, client id) */ vecp = combine_encodes(class_vecp, net_vecp, ENC_COPY); vecp = combine_encodes(vecp, macro_vecp, ENC_DONT_COPY); vecp = combine_encodes(vecp, cid_vecp, ENC_DONT_COPY); } /* * Combine standard option encode lists in the order defined * above (class, net, dhcp network, and client id). */ if (class_ecp != NULL) ecp = combine_encodes(class_ecp, net_ecp, ENC_COPY); else ecp = dup_encode_list(net_ecp); ecp = combine_encodes(ecp, macro_ecp, ENC_DONT_COPY); ecp = combine_encodes(ecp, cid_ecp, ENC_DONT_COPY); /* If dhcptab configured to return hostname, do so. */ if (find_encode(ecp, DSYM_INTERNAL, CD_BOOL_HOSTNAME) != NULL) { struct hostent h, *hp; char hbuf[NSS_BUFLEN_HOSTS]; ENCODE *hecp; hp = gethostbyaddr_r((char *)&ncip, sizeof (ncip), AF_INET, &h, hbuf, sizeof (hbuf), &err); if (hp != NULL) { hecp = make_encode(DSYM_STANDARD, CD_HOSTNAME, strlen(hp->h_name), hp->h_name, ENC_COPY); replace_encode(&ecp, hecp, ENC_DONT_COPY); } } /* If dhcptab configured to echo client class, do so. */ if (plp->opts[CD_CLASS_ID] != NULL && find_encode(ecp, DSYM_INTERNAL, CD_BOOL_ECHO_VCLASS) != NULL) { ENCODE *echo_ecp; DHCP_OPT *op = plp->opts[CD_CLASS_ID]; echo_ecp = make_encode(DSYM_STANDARD, CD_CLASS_ID, op->len, op->value, ENC_COPY); replace_encode(&ecp, echo_ecp, ENC_DONT_COPY); } } if ((ifp->flags & IFF_NOARP) == 0) (void) set_arp(ifp, &ncip, NULL, 0, DHCP_ARP_DEL); /* * 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 * RENEW/REBIND or INIT-REBOOT client states, not SELECTing state. */ if (existing_allocation) { if (dnp->dn_lease == DHCP_PERM || (dnp->dn_flags & DN_FAUTOMATIC)) { oldlease = DHCP_PERM; } else { if ((lease_t)dnp->dn_lease < (lease_t)now) oldlease = (lease_t)0; else { oldlease = (lease_t)dnp->dn_lease - (lease_t)now; } } } /* First get a generic reply packet. */ rep_pktp = gen_reply_pkt(pcd, plp, OFFER, &replen, &optp, &ifp->addr); /* Set the client's IP address */ rep_pktp->yiaddr.s_addr = htonl(dnp->dn_cip.s_addr); /* Calculate lease time. */ newlease = config_lease(plp, dnp, &ecp, oldlease, B_TRUE); /* * Client is requesting specific options. let's try and ensure it * gets what it wants, if at all possible. */ if (plp->opts[CD_REQUEST_LIST] != NULL) add_request_list(ifp, plp, &ecp, &ncip); /* Now load all the asked for / configured options */ used_pkt_len = load_options(DHCP_DHCP_CLNT | DHCP_SEND_LEASE, plp, rep_pktp, replen, optp, ecp, vecp); free_encode_list(ecp); free_encode_list(vecp); if (!no_dhcptab) close_macros(); if (used_pkt_len < sizeof (PKT)) used_pkt_len = sizeof (PKT); if (send_reply(ifp, rep_pktp, used_pkt_len, &ncip) == 0) { if (newlease == DHCP_PERM) newlease = htonl(newlease); else newlease = htonl(now + newlease); (void) update_offer(pcd, dnlp, newlease, NULL, B_TRUE); existing_offer = B_TRUE; } else { unreserve = B_TRUE; } leave_offer: if (unreserve) purge_offer(pcd, B_FALSE, B_TRUE); if (rep_pktp != NULL) free(rep_pktp); if (dncp != NULL) dhcp_free_dd_list(pnd->dh, dncp); if (dnlp != NULL && !existing_offer) dhcp_free_dd_list(pnd->dh, dnlp); } /* * 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 * appear from clients in the RENEW/REBIND states. * * 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 dhcp_req_ack(dsvc_clnt_t *pcd, PKT_LIST *plp) { dn_rec_t dn, ndn, *dnp; struct in_addr serverid, ciaddr, claddr, nreqaddr, cipaddr, ncipaddr, sipaddr; struct in_addr dest_in; dsvc_dnet_t *pnd = pcd->pnd; uint_t replen; int actual_len; int pkt_type = ACK; DHCP_MSG_CATEGORIES log; PKT *rep_pktp = NULL; uchar_t *optp; ENCODE *ecp, *vecp, *class_ecp, *class_vecp, *net_ecp, *net_vecp, *macro_ecp, *macro_vecp, *cid_ecp, *cid_vecp; MACRO *class_mp, *pkt_mp, *net_mp, *cid_mp; char *class_id; char nak_mesg[DHCP_SCRATCH]; time_t now; lease_t newlease, oldlease; boolean_t negot; int err = 0; int write_error = DSVC_SUCCESS, clnt_state; ushort_t boot_secs; char ntoaa[INET_ADDRSTRLEN], ntoab[INET_ADDRSTRLEN], ntoac[INET_ADDRSTRLEN]; char class_idbuf[DSYM_CLASS_SIZE]; boolean_t hostname_update = B_FALSE; dn_rec_list_t *nlp, *dncp = NULL, *dnlp = NULL; uint32_t query; IF *ifp = pcd->ifp; boolean_t existing_offer = B_FALSE; ciaddr.s_addr = plp->pkt->ciaddr.s_addr; boot_secs = ntohs(plp->pkt->secs); now = time(NULL); class_id = get_class_id(plp, class_idbuf, sizeof (class_idbuf)); /* Determine type of REQUEST we've got. */ if (plp->opts[CD_SERVER_ID] != NULL) { if (plp->opts[CD_SERVER_ID]->len != sizeof (struct in_addr)) { dhcpmsg(LOG_ERR, "Garbled DHCP Server ID option from " "client: '%1$s'. Len is %2$d, when it should be " "%3$d \n", pcd->cidbuf, plp->opts[CD_SERVER_ID]->len, 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. */ clnt_state = INIT_STATE; (void) memcpy((void *)&serverid, plp->opts[CD_SERVER_ID]->value, sizeof (struct in_addr)); if (plp->opts[CD_REQUESTED_IP_ADDR] == NULL) { if (verbose) { dhcpmsg(LOG_NOTICE, "%1$s: REQUEST on %2$s is " "missing requested IP option.\n", pcd->cidbuf, pcd->pnd->network); } goto leave_ack; } if (plp->opts[CD_REQUESTED_IP_ADDR]->len != sizeof (struct in_addr)) { dhcpmsg(LOG_ERR, "Garbled Requested IP option from " "client: '%1$s'. Len is %2$d, when it should be " "%3$d \n", pcd->cidbuf, plp->opts[CD_REQUESTED_IP_ADDR]->len, sizeof (struct in_addr)); goto leave_ack; } (void) memcpy((void *)&nreqaddr, plp->opts[CD_REQUESTED_IP_ADDR]->value, sizeof (struct in_addr)); if (serverid.s_addr != ifp->addr.s_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. */ purge_offer(pcd, B_FALSE, B_TRUE); if (verbose) { dhcpmsg(LOG_INFO, "Client: %1$s chose %2$s from server: %3$s," " not %4$s\n", pcd->cidbuf, inet_ntop(AF_INET, &nreqaddr, ntoaa, sizeof (ntoaa)), inet_ntop(AF_INET, &serverid, ntoab, sizeof (ntoab)), inet_ntop(AF_INET, &ifp->addr, ntoac, 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. */ if (pcd->off_ip.s_addr == htonl(INADDR_ANY) || PCD_OFFER_TIMEOUT(pcd, now)) { /* * Hopefully, the timeout value is fairly long to * prevent this. */ purge_offer(pcd, B_TRUE, B_TRUE); if (verbose) { dhcpmsg(LOG_INFO, "Offer on %1$s expired for client: %2$s\n", pcd->pnd->network, pcd->cidbuf); } goto leave_ack; } else (void) update_offer(pcd, NULL, 0, NULL, B_TRUE); /* * The client selected us. Create a ACK, and send * it off to the client, commit to permanent * storage the new binding. */ existing_offer = B_TRUE; dnlp = pcd->dnlp; dnp = dnlp->dnl_rec; ndn = *dnp; /* struct copy */ ndn.dn_lease = pcd->lease; ncipaddr.s_addr = htonl(dnp->dn_cip.s_addr); /* * If client thinks we offered it a different address, then * ignore it. */ if (memcmp((char *)&ncipaddr, plp->opts[CD_REQUESTED_IP_ADDR]->value, sizeof (struct in_addr)) != 0) { if (verbose) { dhcpmsg(LOG_INFO, "Client %1$s believes " "offered IP address %2$s is different than " "what was offered.\n", pcd->cidbuf, inet_ntop(AF_INET, &ncipaddr, ntoab, sizeof (ntoab))); } goto leave_ack; } /* * Clear out any temporary ARP table entry we may have * created during the offer. */ if ((ifp->flags & IFF_NOARP) == 0) (void) set_arp(ifp, &ncipaddr, NULL, 0, DHCP_ARP_DEL); } 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. */ if (ciaddr.s_addr == htonl(INADDR_ANY)) { clnt_state = INIT_REBOOT_STATE; /* * Client isn't sure of its IP address. It's * attempting to verify its address, thus requested * IP option better be present, and correct. */ if (plp->opts[CD_REQUESTED_IP_ADDR] == NULL) { dhcpmsg(LOG_ERR, "Client: %s REQUEST is missing " "requested IP option.\n", pcd->cidbuf); goto leave_ack; } if (plp->opts[CD_REQUESTED_IP_ADDR]->len != sizeof (struct in_addr)) { dhcpmsg(LOG_ERR, "Garbled Requested IP option " "from client: '%1$s'. Len is %2$d, when it " "should be %3$d \n", pcd->cidbuf, plp->opts[CD_REQUESTED_IP_ADDR]->len, sizeof (struct in_addr)); goto leave_ack; } (void) memcpy(&claddr, plp->opts[CD_REQUESTED_IP_ADDR]->value, sizeof (struct in_addr)); DSVC_QINIT(query); DSVC_QEQ(query, DN_QCID); (void) memcpy(dn.dn_cid, pcd->cid, pcd->cid_len); dn.dn_cid_len = pcd->cid_len; /* No bootp records, thank you. */ DSVC_QNEQ(query, DN_QFBOOTP_ONLY); dn.dn_flags = DN_FBOOTP_ONLY; } else { clnt_state = RENEW_REBIND_STATE; /* * Client knows its IP address. It is trying to * RENEW/REBIND (extend its lease). We trust ciaddr, * 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. */ DSVC_QINIT(query); DSVC_QEQ(query, DN_QCID|DN_QCIP); (void) memcpy(dn.dn_cid, pcd->cid, pcd->cid_len); dn.dn_cid_len = pcd->cid_len; dn.dn_cip.s_addr = ntohl(ciaddr.s_addr); /* No bootp records, thank you. */ DSVC_QNEQ(query, DN_QFBOOTP_ONLY); dn.dn_flags = DN_FBOOTP_ONLY; claddr.s_addr = ciaddr.s_addr; } dncp = NULL; dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query, -1, &dn, (void **)&dncp, S_CID); if (dnlp != NULL) { dnp = dnlp->dnl_rec; if (dnp->dn_flags & DN_FUNUSABLE) goto leave_ack; sipaddr.s_addr = htonl(dnp->dn_sip.s_addr); cipaddr.s_addr = htonl(dnp->dn_cip.s_addr); /* * 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 (match_ownerip(sipaddr.s_addr) == NULL) { if (clnt_state == INIT_REBOOT_STATE) { if (verbose) { dhcpmsg(LOG_NOTICE, "Client: " "%1$s is requesting " "verification of %2$s " "owned by %3$s\n", pcd->cidbuf, inet_ntop(AF_INET, &cipaddr, ntoab, sizeof (ntoab)), inet_ntop(AF_INET, &sipaddr, ntoac, sizeof (ntoac))); } goto leave_ack; } else { /* RENEW/REBIND - wait for primary */ if (boot_secs < (ushort_t)renog_secs) goto leave_ack; } } if (claddr.s_addr != htonl(dnp->dn_cip.s_addr)) { /* * Client has the wrong IP address. Nak. */ (void) snprintf(nak_mesg, sizeof (nak_mesg), "Incorrect IP address."); pkt_type = NAK; } else { if (!(dnp->dn_flags & DN_FAUTOMATIC) && (lease_t)dnp->dn_lease < (lease_t)now) { (void) snprintf(nak_mesg, sizeof (nak_mesg), "Lease has expired."); pkt_type = NAK; } } } else { if (clnt_state == RENEW_REBIND_STATE) { dhcpmsg(LOG_ERR, "Client: %1$s is trying to " "renew %2$s, an IP address it has not " "leased.\n", pcd->cidbuf, inet_ntop(AF_INET, &ciaddr, ntoab, sizeof (ntoab))); 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. */ if ((claddr.s_addr & pnd->subnet.s_addr) == pnd->net.s_addr) { /* Right net, but no record of client. */ if (verbose) { dhcpmsg(LOG_INFO, "Client: %1$s is trying to verify " "unrecorded address: %2$s, " "ignored.\n", pcd->cidbuf, inet_ntop(AF_INET, &claddr, ntoab, sizeof (ntoab))); } goto leave_ack; } else { if (ciaddr.s_addr == 0L) { (void) snprintf(nak_mesg, sizeof (nak_mesg), "No valid configuration exists on " "network: %s", pnd->network); pkt_type = NAK; } else { if (verbose) { dhcpmsg(LOG_INFO, "Client: %1$s is not recorded as " "having address: %2$s\n", pcd->cidbuf, inet_ntop(AF_INET, &ciaddr, ntoab, sizeof (ntoab))); } goto leave_ack; } } } } /* * Produce the appropriate response. */ if (pkt_type == NAK) { rep_pktp = gen_reply_pkt(pcd, plp, NAK, &replen, &optp, &ifp->addr); /* * 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 * server/relay agents to broadcast the NAK. * * 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. */ rep_pktp->yiaddr.s_addr = 0L; if (ciaddr.s_addr != 0L && (ciaddr.s_addr & pnd->subnet.s_addr) != pnd->net.s_addr) { dest_in.s_addr = ciaddr.s_addr; } else { rep_pktp->flags |= htons(BCAST_MASK); dest_in.s_addr = INADDR_BROADCAST; } *optp++ = CD_MESSAGE; *optp++ = (uchar_t)strlen(nak_mesg); (void) memcpy(optp, nak_mesg, strlen(nak_mesg)); optp += strlen(nak_mesg); *optp = CD_END; actual_len = BASE_PKT_SIZE + (uint_t)(optp - rep_pktp->options); if (actual_len < sizeof (PKT)) actual_len = sizeof (PKT); (void) send_reply(ifp, rep_pktp, actual_len, &dest_in); logtrans(P_DHCP, L_NAK, 0, dest_in, server_ip, plp); } else { rep_pktp = gen_reply_pkt(pcd, plp, ACK, &replen, &optp, &ifp->addr); /* Set the client's IP address */ rep_pktp->yiaddr.s_addr = htonl(dnp->dn_cip.s_addr); dest_in.s_addr = htonl(dnp->dn_cip.s_addr); /* * 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). */ ecp = vecp = NULL; class_vecp = class_ecp = NULL; net_vecp = net_ecp = NULL; macro_vecp = macro_ecp = NULL; cid_vecp = cid_ecp = NULL; if (!no_dhcptab) { open_macros(); if ((net_mp = get_macro(pnd->network)) != NULL) net_ecp = net_mp->head; if ((pkt_mp = get_macro(dnp->dn_macro)) != NULL) macro_ecp = pkt_mp->head; if ((cid_mp = get_macro(pcd->cidbuf)) != NULL) cid_ecp = cid_mp->head; if (class_id != NULL) { if ((class_mp = get_macro(class_id)) != NULL) { class_vecp = vendor_encodes(class_mp, class_id); class_ecp = class_mp->head; } if (net_mp != NULL) { net_vecp = vendor_encodes(net_mp, class_id); } if (pkt_mp != NULL) macro_vecp = vendor_encodes(pkt_mp, class_id); if (cid_mp != NULL) { cid_vecp = vendor_encodes(cid_mp, class_id); } vecp = combine_encodes(class_vecp, net_vecp, ENC_COPY); vecp = combine_encodes(vecp, macro_vecp, ENC_DONT_COPY); vecp = combine_encodes(vecp, cid_vecp, ENC_DONT_COPY); } if (class_ecp != NULL) { ecp = combine_encodes(class_ecp, net_ecp, ENC_COPY); } else ecp = dup_encode_list(net_ecp); ecp = combine_encodes(ecp, macro_ecp, ENC_DONT_COPY); ecp = combine_encodes(ecp, cid_ecp, ENC_DONT_COPY); ncipaddr.s_addr = htonl(dnp->dn_cip.s_addr); /* * 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) && (plp->opts[CD_HOSTNAME] != NULL)) { char hname[MAXHOSTNAMELEN + 1]; int hlen; struct in_addr ia, *iap = &ia; /* turn hostname option into a string */ hlen = plp->opts[CD_HOSTNAME]->len; hlen = MIN(hlen, MAXHOSTNAMELEN); (void) memcpy(hname, plp->opts[CD_HOSTNAME]->value, hlen); hname[hlen] = '\0'; nlp = NULL; if (name_avail(hname, pcd, plp, &nlp, ecp, &iap)) { ENCODE *hecp; /* * 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. */ if ((iap == NULL) || (iap->s_addr != dnp->dn_cip.s_addr)) { if (do_nsupdate(dnp->dn_cip, ecp, plp)) { hecp = make_encode( DSYM_STANDARD, CD_HOSTNAME, strlen(hname), hname, ENC_COPY); replace_encode(&ecp, hecp, ENC_DONT_COPY); hostname_update = B_TRUE; } } else { hecp = make_encode( DSYM_STANDARD, CD_HOSTNAME, strlen(hname), hname, ENC_COPY); replace_encode(&ecp, hecp, ENC_DONT_COPY); hostname_update = B_TRUE; } if (nlp != NULL) dhcp_free_dd_list(pnd->dh, nlp); } } /* * If dhcptab configured to return hostname, do so. */ if ((hostname_update == B_FALSE) && (find_encode(ecp, DSYM_INTERNAL, CD_BOOL_HOSTNAME) != NULL)) { struct hostent h, *hp; ENCODE *hecp; char hbuf[NSS_BUFLEN_HOSTS]; hp = gethostbyaddr_r((char *)&ncipaddr, sizeof (struct in_addr), AF_INET, &h, hbuf, sizeof (hbuf), &err); if (hp != NULL) { hecp = make_encode(DSYM_STANDARD, CD_HOSTNAME, strlen(hp->h_name), hp->h_name, ENC_COPY); replace_encode(&ecp, hecp, ENC_DONT_COPY); } } /* * If dhcptab configured to echo client class, do so. */ if (plp->opts[CD_CLASS_ID] != NULL && find_encode(ecp, DSYM_INTERNAL, CD_BOOL_ECHO_VCLASS) != NULL) { ENCODE *echo_ecp; DHCP_OPT *op = plp->opts[CD_CLASS_ID]; echo_ecp = make_encode(DSYM_STANDARD, CD_CLASS_ID, op->len, op->value, ENC_COPY); replace_encode(&ecp, echo_ecp, ENC_DONT_COPY); } } if (dnp->dn_flags & DN_FAUTOMATIC || dnp->dn_lease == DHCP_PERM) oldlease = DHCP_PERM; else { if (plp->opts[CD_SERVER_ID] != NULL) { /* * 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 * LONG time between DISC/REQ! */ if ((lease_t)dnp->dn_lease < (lease_t)now) oldlease = (lease_t)0; else oldlease = dnp->dn_lease - now; } else oldlease = dnp->dn_lease - now; } if (find_encode(ecp, DSYM_INTERNAL, CD_BOOL_LEASENEG) != NULL) negot = B_TRUE; else negot = B_FALSE; /* * Modify changed fields in new database record. */ ndn = *dnp; /* struct copy */ (void) memcpy(ndn.dn_cid, pcd->cid, pcd->cid_len); ndn.dn_cid_len = pcd->cid_len; /* * 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. */ newlease = config_lease(plp, &ndn, &ecp, oldlease, negot); if (newlease != DHCP_PERM) ndn.dn_lease = now + newlease; else ndn.dn_lease = DHCP_PERM; /* * 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. */ if (clnt_state == INIT_STATE || oldlease != newlease) { write_error = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn); /* Keep state of the cached entry current. */ if (write_error == DSVC_SUCCESS) { *dnp = ndn; /* struct copy */ } } else { if (verbose) { dhcpmsg(LOG_INFO, "Database write unnecessary for DHCP client: " "%1$s, %2$s\n", pcd->cidbuf, inet_ntop(AF_INET, &ncipaddr, ntoab, sizeof (ntoab))); } } if (write_error == DSVC_SUCCESS || clnt_state == INIT_REBOOT_STATE) { if (write_error != DSVC_SUCCESS) set_lease_option(&ecp, oldlease); else { /* Note that the conversation has completed. */ pcd->state = ACK; } if (plp->opts[CD_REQUEST_LIST]) add_request_list(ifp, plp, &ecp, &ncipaddr); /* Now load all the asked for / configured options */ actual_len = load_options(DHCP_DHCP_CLNT | DHCP_SEND_LEASE, plp, rep_pktp, replen, optp, ecp, vecp); if (actual_len < sizeof (PKT)) actual_len = sizeof (PKT); if (verbose) { dhcpmsg(LOG_INFO, "Client: %1$s maps to IP: %2$s\n", pcd->cidbuf, inet_ntop(AF_INET, &ncipaddr, ntoab, sizeof (ntoab))); } (void) send_reply(ifp, rep_pktp, actual_len, &dest_in); if (clnt_state == INIT_STATE) log = L_ASSIGN; else log = L_REPLY; logtrans(P_DHCP, log, ndn.dn_lease, ncipaddr, server_ip, plp); } free_encode_list(ecp); free_encode_list(vecp); if (!no_dhcptab) close_macros(); } leave_ack: if (rep_pktp != NULL) free(rep_pktp); if (dncp != NULL) dhcp_free_dd_list(pnd->dh, dncp); if (dnlp != NULL && !existing_offer) dhcp_free_dd_list(pnd->dh, dnlp); } /* Reacting to a client's DECLINE or RELEASE. */ static void dhcp_dec_rel(dsvc_clnt_t *pcd, PKT_LIST *plp, int type) { char *fmtp; dn_rec_t *dnp, dn, ndn; dsvc_dnet_t *pnd; struct in_addr ip; int err = 0; DHCP_MSG_CATEGORIES log; dn_rec_list_t *dncp, *dnlp = NULL; uint32_t query; char ipb[INET_ADDRSTRLEN]; char clnt_msg[DHCP_MAX_OPT_SIZE]; pnd = pcd->pnd; if (type == DECLINE) { if (plp->opts[CD_REQUESTED_IP_ADDR] && plp->opts[CD_REQUESTED_IP_ADDR]->len == sizeof (struct in_addr)) { (void) memcpy((char *)&ip, plp->opts[CD_REQUESTED_IP_ADDR]->value, sizeof (struct in_addr)); } } else ip.s_addr = plp->pkt->ciaddr.s_addr; (void) inet_ntop(AF_INET, &ip, ipb, sizeof (ipb)); /* Look for a matching IP address and Client ID */ DSVC_QINIT(query); DSVC_QEQ(query, DN_QCID|DN_QCIP); (void) memcpy(dn.dn_cid, pcd->cid, pcd->cid_len); dn.dn_cid_len = pcd->cid_len; dn.dn_cip.s_addr = ntohl(ip.s_addr); dncp = NULL; dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query, -1, &dn, (void **)&dncp, S_CID); assert(dncp == NULL); if (dnlp == NULL) { if (verbose) { if (type == DECLINE) { fmtp = "Unregistered client: %1$s is " "DECLINEing address: %2$s.\n"; } else { fmtp = "Unregistered client: %1$s is " "RELEASEing address: %2$s.\n"; } dhcpmsg(LOG_INFO, fmtp, pcd->cidbuf, ipb); } return; } dnp = dnlp->dnl_rec; ndn = *dnp; /* struct copy */ /* If the entry is not one of ours, then give up. */ if (match_ownerip(htonl(ndn.dn_sip.s_addr)) == NULL) { if (verbose) { if (type == DECLINE) { 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"; } dhcpmsg(LOG_INFO, fmtp, pcd->cidbuf, ipb); } goto leave_dec_rel; } if (type == DECLINE) { log = L_DECLINE; dhcpmsg(LOG_ERR, "Client: %1$s DECLINED address: %2$s.\n", pcd->cidbuf, ipb); if (plp->opts[CD_MESSAGE]) { dhcpmsg(LOG_ERR, "DECLINE: client message: %s\n", disp_clnt_msg(plp, clnt_msg, sizeof (clnt_msg))); } ndn.dn_flags |= DN_FUNUSABLE; } else { log = L_RELEASE; if (ndn.dn_flags & DN_FMANUAL) { dhcpmsg(LOG_ERR, "Client: %1$s is trying to RELEASE manual " "address: %2$s\n", pcd->cidbuf, ipb); goto leave_dec_rel; } if (verbose) { dhcpmsg(LOG_INFO, "Client: %1$s RELEASED address: %2$s\n", pcd->cidbuf, ipb); if (plp->opts[CD_MESSAGE]) { dhcpmsg(LOG_INFO, "RELEASE: client message: %s\n", disp_clnt_msg(plp, clnt_msg, sizeof (clnt_msg))); } } } /* Clear out the cid and lease fields */ if (!(ndn.dn_flags & DN_FMANUAL)) { ndn.dn_cid[0] = '\0'; ndn.dn_cid_len = 1; ndn.dn_lease = (lease_t)0; } /* Ignore write errors. */ err = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn); if (err != DSVC_SUCCESS) { dhcpmsg(LOG_NOTICE, "%1$s: ERROR modifying database: %2$s for client %3$s\n", log == L_RELEASE ? "RELEASE" : "DECLINE", dhcpsvc_errmsg(err), ipb); } else { if (type == RELEASE) { /* * performance: save select_offer() lots of work by * caching this perfectly good ip address in freerec. */ *(dnlp->dnl_rec) = ndn; /* struct copy */ add_dnet_cache(pnd, dnlp); dnlp = NULL; } } logtrans(P_DHCP, log, ndn.dn_lease, ip, server_ip, plp); leave_dec_rel: if (dnlp != NULL) dhcp_free_dd_list(pnd->dh, dnlp); } /* * 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 dhcp_inform(dsvc_clnt_t *pcd, PKT_LIST *plp) { uint_t replen; int used_pkt_len; PKT *rep_pktp = NULL; uchar_t *optp; ENCODE *ecp, *vecp, *class_ecp, *class_vecp, *cid_ecp, *cid_vecp, *net_ecp, *net_vecp; MACRO *net_mp, *class_mp, *cid_mp; dsvc_dnet_t *pnd; char *class_id; char class_idbuf[DSYM_CLASS_SIZE]; IF *ifp = pcd->ifp; pnd = pcd->pnd; class_id = get_class_id(plp, class_idbuf, sizeof (class_idbuf)); /* * 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). */ ecp = vecp = NULL; net_vecp = net_ecp = NULL; class_vecp = class_ecp = NULL; cid_vecp = cid_ecp = NULL; if (!no_dhcptab) { open_macros(); if ((net_mp = get_macro(pnd->network)) != NULL) net_ecp = net_mp->head; if ((cid_mp = get_macro(pcd->cidbuf)) != NULL) cid_ecp = cid_mp->head; if (class_id != NULL) { if ((class_mp = get_macro(class_id)) != NULL) { class_vecp = vendor_encodes(class_mp, class_id); class_ecp = class_mp->head; } if (net_mp != NULL) net_vecp = vendor_encodes(net_mp, class_id); if (cid_mp != NULL) cid_vecp = vendor_encodes(cid_mp, class_id); vecp = combine_encodes(class_vecp, net_vecp, ENC_COPY); vecp = combine_encodes(vecp, cid_vecp, ENC_DONT_COPY); } ecp = combine_encodes(class_ecp, net_ecp, ENC_COPY); ecp = combine_encodes(ecp, cid_ecp, ENC_DONT_COPY); } /* First get a generic reply packet. */ rep_pktp = gen_reply_pkt(pcd, plp, ACK, &replen, &optp, &ifp->addr); /* * Client is requesting specific options. let's try and ensure it * gets what it wants, if at all possible. */ if (plp->opts[CD_REQUEST_LIST] != NULL) add_request_list(ifp, plp, &ecp, &plp->pkt->ciaddr); /* * Explicitly set the ciaddr to be that which the client gave * us. */ rep_pktp->ciaddr.s_addr = plp->pkt->ciaddr.s_addr; /* * Now load all the asked for / configured options. DON'T send * any lease time info! */ used_pkt_len = load_options(DHCP_DHCP_CLNT, plp, rep_pktp, replen, optp, ecp, vecp); free_encode_list(ecp); free_encode_list(vecp); if (!no_dhcptab) close_macros(); if (used_pkt_len < sizeof (PKT)) used_pkt_len = sizeof (PKT); (void) send_reply(ifp, rep_pktp, used_pkt_len, &plp->pkt->ciaddr); logtrans(P_DHCP, L_INFORM, 0, plp->pkt->ciaddr, server_ip, plp); leave_inform: if (rep_pktp != NULL) free(rep_pktp); } static char * disp_clnt_msg(PKT_LIST *plp, char *bufp, int len) { uchar_t tlen; bufp[0] = '\0'; /* null string */ if (plp && plp->opts[CD_MESSAGE]) { tlen = ((uchar_t)len < plp->opts[CD_MESSAGE]->len) ? (len - 1) : plp->opts[CD_MESSAGE]->len; (void) memcpy(bufp, plp->opts[CD_MESSAGE]->value, tlen); bufp[tlen] = '\0'; } return (bufp); } /* * serverip expected in host order */ static PKT * gen_reply_pkt(dsvc_clnt_t *pcd, PKT_LIST *plp, int type, uint_t *len, uchar_t **optpp, struct in_addr *serverip) { PKT *reply_pktp; uint16_t plen; /* * We need to determine the packet size. Perhaps the client has told * us? */ if (plp->opts[CD_MAX_DHCP_SIZE]) { if (plp->opts[CD_MAX_DHCP_SIZE]->len != sizeof (uint16_t)) { dhcpmsg(LOG_ERR, "Garbled MAX DHCP message size option " "from\nclient: '%1$s'. Len is %2$d, when it should " "be %3$d. Defaulting to %4$d.\n", pcd->cidbuf, plp->opts[CD_MAX_DHCP_SIZE]->len, sizeof (uint16_t), DHCP_DEF_MAX_SIZE); plen = DHCP_DEF_MAX_SIZE; } else { (void) memcpy(&plen, plp->opts[CD_MAX_DHCP_SIZE]->value, sizeof (uint16_t)); plen = ntohs(plen); } } else { /* * Define size to be a fixed length. Too hard to add up all * possible class id, macro, and hostname/lease time options * without doing just about as much work as constructing the * whole reply packet. */ plen = DHCP_MAX_REPLY_SIZE; } /* Generate a generically initialized BOOTP packet */ reply_pktp = gen_bootp_pkt(plen, plp->pkt); reply_pktp->op = BOOTREPLY; *optpp = reply_pktp->options; /* * Set pkt type. */ *(*optpp)++ = (uchar_t)CD_DHCP_TYPE; *(*optpp)++ = (uchar_t)1; *(*optpp)++ = (uchar_t)type; /* * All reply packets have server id set. */ *(*optpp)++ = (uchar_t)CD_SERVER_ID; *(*optpp)++ = (uchar_t)4; #if defined(_LITTLE_ENDIAN) *(*optpp)++ = (uchar_t)(serverip->s_addr & 0xff); *(*optpp)++ = (uchar_t)((serverip->s_addr >> 8) & 0xff); *(*optpp)++ = (uchar_t)((serverip->s_addr >> 16) & 0xff); *(*optpp)++ = (uchar_t)((serverip->s_addr >> 24) & 0xff); #else *(*optpp)++ = (uchar_t)((serverip->s_addr >> 24) & 0xff); *(*optpp)++ = (uchar_t)((serverip->s_addr >> 16) & 0xff); *(*optpp)++ = (uchar_t)((serverip->s_addr >> 8) & 0xff); *(*optpp)++ = (uchar_t)(serverip->s_addr & 0xff); #endif /* _LITTLE_ENDIAN */ *len = plen; 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 add_request_list(IF *ifp, PKT_LIST *plp, ENCODE **ecp, struct in_addr *ip) { ENCODE *ep, *ifecp, *end_ecp = NULL; struct hostent h, *hp; char hbuf[NSS_BUFLEN_HOSTS]; int herrno; /* Find the end. */ if (*ecp) { for (ep = *ecp; ep->next; ep = ep->next) /* null */; end_ecp = ep; } /* HOSTNAME */ if (is_option_requested(plp, CD_HOSTNAME) && (find_encode(*ecp, DSYM_STANDARD, CD_HOSTNAME) == NULL) && (find_encode(*ecp, DSYM_INTERNAL, CD_BOOL_HOSTNAME) == NULL)) { hp = gethostbyaddr_r((char *)ip, sizeof (struct in_addr), AF_INET, &h, hbuf, sizeof (hbuf), &herrno); if (hp != NULL) { if (end_ecp) { end_ecp->next = make_encode(DSYM_STANDARD, CD_HOSTNAME, strlen(hp->h_name), hp->h_name, ENC_COPY); end_ecp = end_ecp->next; } else { end_ecp = make_encode(DSYM_STANDARD, CD_HOSTNAME, strlen(hp->h_name), hp->h_name, ENC_COPY); } } } /* * all bets off for the following if thru a relay agent. */ if (plp->pkt->giaddr.s_addr != 0L) return; /* SUBNET MASK */ if (is_option_requested(plp, CD_SUBNETMASK) && find_encode(*ecp, DSYM_STANDARD, CD_SUBNETMASK) == NULL) { ifecp = find_encode(ifp->ecp, DSYM_STANDARD, CD_SUBNETMASK); if (end_ecp) { end_ecp->next = dup_encode(ifecp); end_ecp = end_ecp->next; } else end_ecp = dup_encode(ifecp); } /* BROADCAST ADDRESS */ if (is_option_requested(plp, CD_BROADCASTADDR) && find_encode(*ecp, DSYM_STANDARD, CD_BROADCASTADDR) == NULL) { ifecp = find_encode(ifp->ecp, DSYM_STANDARD, CD_BROADCASTADDR); if (end_ecp) { end_ecp->next = dup_encode(ifecp); end_ecp = end_ecp->next; } else end_ecp = dup_encode(ifecp); } /* IP MTU */ if (is_option_requested(plp, CD_MTU) && find_encode(*ecp, DSYM_STANDARD, CD_MTU) == NULL) { ifecp = find_encode(ifp->ecp, DSYM_STANDARD, CD_MTU); if (end_ecp) { end_ecp->next = dup_encode(ifecp); end_ecp = end_ecp->next; } else end_ecp = dup_encode(ifecp); } if (*ecp == NULL) *ecp = end_ecp; } /* * Is a specific option requested? Returns True if so, False otherwise. */ static int is_option_requested(PKT_LIST *plp, ushort_t code) { uchar_t c, *tp; DHCP_OPT *cp = plp->opts[CD_REQUEST_LIST]; for (c = 0, tp = (uchar_t *)cp->value; c < cp->len; c++, tp++) { if (*tp == (uchar_t)code) 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 set_lease_option(ENCODE **ecpp, lease_t lease) { ENCODE *ep, *prev_ep, *lease_ep; lease = htonl(lease); if (ecpp != NULL && (lease_ep = find_encode(*ecpp, DSYM_STANDARD, CD_LEASE_TIME)) != NULL && lease_ep->len == sizeof (lease_t)) { (void) memcpy(lease_ep->data, (void *)&lease, sizeof (lease_t)); } else { if (*ecpp != NULL) { for (prev_ep = ep = *ecpp; ep != NULL; ep = ep->next) prev_ep = ep; prev_ep->next = make_encode(DSYM_STANDARD, CD_LEASE_TIME, sizeof (lease_t), &lease, ENC_COPY); } else { *ecpp = make_encode(DSYM_STANDARD, CD_LEASE_TIME, sizeof (lease_t), &lease, ENC_COPY); (*ecpp)->next = NULL; } } } /* * Sets appropriate option in passed ENCODE list for lease. Returns * calculated relative lease time. */ static int config_lease(PKT_LIST *plp, dn_rec_t *dnp, ENCODE **ecpp, lease_t oldlease, boolean_t negot) { lease_t newlease, rel_current; ENCODE *lease_ecp; if (ecpp != NULL && (lease_ecp = find_encode(*ecpp, DSYM_STANDARD, CD_LEASE_TIME)) != NULL && lease_ecp->len == sizeof (lease_t)) { (void) memcpy((void *)&rel_current, lease_ecp->data, sizeof (lease_t)); rel_current = htonl(rel_current); } else rel_current = (lease_t)DEFAULT_LEASE; if (dnp->dn_flags & DN_FAUTOMATIC || !negot) { if (dnp->dn_flags & DN_FAUTOMATIC) newlease = ntohl(DHCP_PERM); else { /* sorry! */ if (oldlease) newlease = oldlease; else newlease = rel_current; } } 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) rel_current = oldlease; if (plp->opts[CD_LEASE_TIME] && plp->opts[CD_LEASE_TIME]->len == sizeof (lease_t)) { /* * Client is requesting a lease renegotiation. */ (void) memcpy((void *)&newlease, plp->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); newlease = ntohl(newlease); /* * Note that this comparison handles permanent * leases as well. Limit lease to configured value. */ if (newlease > rel_current) newlease = rel_current; } else newlease = rel_current; } set_lease_option(ecpp, newlease); return (newlease); } /* * If a packet has the classid set, return the value, else return null. */ char * get_class_id(PKT_LIST *plp, char *bufp, int len) { uchar_t *ucp, ulen; char *retp; if (plp->opts[CD_CLASS_ID]) { /* * 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. */ ucp = plp->opts[CD_CLASS_ID]->value; ulen = plp->opts[CD_CLASS_ID]->len; if (len < ulen) ulen = len; (void) memcpy(bufp, ucp, ulen); bufp[ulen] = '\0'; retp = bufp; } else retp = NULL; 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 check_offer(dsvc_dnet_t *pnd, struct in_addr *reservep) { dsvc_clnt_t tpcd; tpcd.off_ip.s_addr = reservep->s_addr; return (hash_Lookup(pnd->itable, reservep, sizeof (struct in_addr), clnt_netcmp, &tpcd, B_FALSE) == NULL ? B_TRUE : B_FALSE); } /* * 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(). */ boolean_t update_offer(dsvc_clnt_t *pcd, dn_rec_list_t *dnlp, lease_t nlease, struct in_addr *reservep, boolean_t purge_cache) { char ntoab[INET_ADDRSTRLEN]; boolean_t insert = B_TRUE; boolean_t update = B_FALSE; boolean_t offer = B_FALSE; dsvc_dnet_t *pnd = pcd->pnd; IF *ifp = pcd->ifp; dn_rec_t *dnp = NULL; struct in_addr off_ip; /* Save the original datastore record. */ if (dnlp != NULL) { if (pcd->dnlp != NULL && pcd->dnlp != dnlp) dhcp_free_dd_list(pnd->dh, pcd->dnlp); pcd->dnlp = dnlp; } if (pcd->dnlp != NULL) dnp = pcd->dnlp->dnl_rec; /* Determine the offer address. */ if (reservep == NULL && dnp != NULL) off_ip.s_addr = htonl(dnp->dn_cip.s_addr); else if (reservep != NULL) off_ip.s_addr = reservep->s_addr; else { dhcpmsg(LOG_DEBUG, "Neither offer IP nor IP to reserve present\n"); assert(B_FALSE); return (B_FALSE); } /* If updating, release the old offer address. */ if (pcd->off_ip.s_addr == htonl(INADDR_ANY)) { offer = B_TRUE; } else { update = B_TRUE; if (pcd->off_ip.s_addr != off_ip.s_addr) { purge_offer(pcd, B_FALSE, purge_cache); offer = B_TRUE; } else insert = B_FALSE; } if (nlease != 0) pcd->lease = nlease; /* Prepare to insert pcd into the offer hash table. */ pcd->mtime = reinit_time; pcd->off_ip.s_addr = off_ip.s_addr; assert(pcd->off_ip.s_addr != htonl(INADDR_ANY)); if (insert) { if ((pcd->ihand = hash_Insert(pnd->itable, &pcd->off_ip, sizeof (struct in_addr), clnt_netcmp, pcd, pcd)) == NULL) { if (reservep == NULL) { dhcpmsg(LOG_WARNING, "Duplicate offer of %1$s " "to client: %2$s\n", inet_ntop(AF_INET, &pcd->off_ip, ntoab, sizeof (ntoab)), pcd->cidbuf); } pcd->off_ip.s_addr = htonl(INADDR_ANY); dhcp_free_dd_list(pnd->dh, pcd->dnlp); pcd->dnlp = NULL; return (B_FALSE); } } else hash_Dtime(pcd->ihand, time(NULL) + off_secs); if (offer) { (void) mutex_lock(&ifp->ifp_mtx); ifp->offers++; (void) mutex_unlock(&ifp->ifp_mtx); } if (debug) { if (reservep != NULL) { dhcpmsg(LOG_INFO, "Reserved offer: %s\n", inet_ntop(AF_INET, &pcd->off_ip, ntoab, sizeof (ntoab))); } else if (update) { dhcpmsg(LOG_INFO, "Updated offer: %s\n", inet_ntop(AF_INET, &pcd->off_ip, ntoab, sizeof (ntoab))); } else { dhcpmsg(LOG_INFO, "Added offer: %s\n", inet_ntop(AF_INET, &pcd->off_ip, ntoab, sizeof (ntoab))); } } 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 purge_offer(dsvc_clnt_t *pcd, boolean_t expired, boolean_t purge_cache) { char ntoab[INET_ADDRSTRLEN]; dsvc_dnet_t *pnd = pcd->pnd; IF *ifp = pcd->ifp; if (pcd->off_ip.s_addr != htonl(INADDR_ANY)) { if (debug) { if (expired == B_TRUE) dhcpmsg(LOG_INFO, "Freeing offer: %s\n", inet_ntop(AF_INET, &pcd->off_ip, ntoab, sizeof (ntoab))); else dhcpmsg(LOG_INFO, "Purging offer: %s\n", inet_ntop(AF_INET, &pcd->off_ip, ntoab, sizeof (ntoab))); } /* * 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 (pcd->state == ACK && pcd->dnlp != NULL) { if (purge_cache) purge_dnet_cache(pnd, pcd->dnlp->dnl_rec); dhcp_free_dd_list(pnd->dh, pcd->dnlp); pcd->dnlp = NULL; } /* Prepare to delete pcd from the offer hash table. */ (void) hash_Delete(pnd->itable, &pcd->off_ip, sizeof (struct in_addr), clnt_netcmp, pcd, NULL); pcd->off_ip.s_addr = htonl(INADDR_ANY); (void) mutex_lock(&ifp->ifp_mtx); ifp->offers--; assert((int)ifp->offers >= 0); if (expired) ifp->expired++; (void) mutex_unlock(&ifp->ifp_mtx); } } /* * 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*/ boolean_t select_offer(dsvc_dnet_t *pnd, PKT_LIST *plp, dsvc_clnt_t *pcd, dn_rec_list_t **dnlpp) { struct in_addr req_ip, *req_ipp = &req_ip, tip; boolean_t found = B_FALSE; time_t now; dn_rec_t dn, *dnp; dn_rec_list_t *dncp, *dnsp, *tlp; int nrecords; uint32_t query; int retry; boolean_t io_done, is_bootp; struct in_addr *oip; if (plp->opts[CD_DHCP_TYPE] == NULL) is_bootp = B_TRUE; else is_bootp = B_FALSE; *dnlpp = NULL; if (!is_bootp) { /* * Is the DHCP client requesting a specific address? Is so, and * we can satisfy him, do so. */ if (plp->opts[CD_REQUESTED_IP_ADDR] != NULL) { (void) memcpy((void *)&req_ip, plp->opts[CD_REQUESTED_IP_ADDR]->value, sizeof (struct in_addr)); if ((req_ip.s_addr & pnd->subnet.s_addr) == pnd->net.s_addr) found = B_TRUE; } else if (plp->opts[CD_HOSTNAME] != NULL) { char hname[MAXHOSTNAMELEN + 1]; int hlen; /* turn hostname option into a string */ hlen = plp->opts[CD_HOSTNAME]->len; hlen = MIN(hlen, MAXHOSTNAMELEN); (void) memcpy(hname, plp->opts[CD_HOSTNAME]->value, hlen); hname[hlen] = '\0'; dhcpmsg(LOG_DEBUG, "select_offer: hostname request for %s\n", hname); if (name_avail(hname, pcd, plp, dnlpp, NULL, &req_ipp) && req_ipp) { if ((req_ip.s_addr & pnd->subnet.s_addr) == pnd->net.s_addr) { found = B_TRUE; } else if (*dnlpp != NULL) { dhcp_free_dd_list(pnd->dh, *dnlpp); *dnlpp = NULL; } dhcpmsg(LOG_DEBUG, "select_offer: hostname %s " "available, req_ip %x\n", hname, ntohl(req_ip.s_addr)); } else dhcpmsg(LOG_DEBUG, "select_offer: name_avail " "false or no address for %s\n", hname); } } /* * Check the offer list and table entry. */ if (found && *dnlpp == NULL) found = addr_avail(pnd, pcd, dnlpp, req_ip, B_FALSE); 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). */ for (oip = owner_ip; oip->s_addr != INADDR_ANY; oip++) { /* * Initialize query. */ DSVC_QINIT(query); DSVC_QEQ(query, DN_QCID|DN_QSIP); dn.dn_cid[0] = '\0'; dn.dn_cid_len = 1; dn.dn_sip.s_addr = ntohl(oip->s_addr); /* * Decide whether a bootp record is required. */ dn.dn_flags = 0; DSVC_QEQ(query, DN_QFBOOTP_ONLY); if (is_bootp) dn.dn_flags = DN_FBOOTP_ONLY; /* * 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). */ DSVC_QEQ(query, DN_QFUNUSABLE|DN_QFMANUAL); for (retry = 0; !found && retry < 3; retry++) { now = time(NULL); (void) mutex_lock(&pnd->thr_mtx); nrecords = pnd->nthreads < DHCP_MIN_RECORDS ? DHCP_MIN_RECORDS : pnd->nthreads; (void) mutex_unlock(&pnd->thr_mtx); /* * Purge cached records when expired or database * re-read. */ (void) mutex_lock(&pnd->free_mtx); dncp = pnd->freerec; if (dncp != NULL && PND_FREE_TIMEOUT(pnd, now)) { pnd->freerec = NULL; dhcp_free_dd_list(pnd->dh, dncp); dncp = NULL; } if (dncp != NULL) { if (retry == 0) { /* Try the next cached record */ pnd->freerec = dncp->dnl_next; dncp->dnl_next = NULL; } else if (retry == 1) { /* * Try all remaining cached * records */ pnd->freerec = NULL; } } if (retry > 1) { /* Try all possible records in datastore. */ pnd->freerec = NULL; nrecords = -1; if (dncp != NULL) { dhcp_free_dd_list( pnd->dh, dncp); dncp = NULL; } } (void) mutex_unlock(&pnd->free_mtx); io_done = (dncp == NULL); *dnlpp = dhcp_lookup_dd_classify(pcd->pnd, nrecords == -1 ? B_FALSE : B_TRUE, query, nrecords, &dn, (void **)&dncp, S_CID | S_FREE); if (*dnlpp != NULL) { dnp = (*dnlpp)->dnl_rec; tip.s_addr = htonl(dnp->dn_cip.s_addr); (void) update_offer(pcd, NULL, 0, &tip, B_TRUE); found = B_TRUE; } (void) mutex_lock(&pnd->free_mtx); if (io_done) { /* * Note time when records were read. */ if (dncp != NULL) { now = time(NULL); pnd->free_mtime = reinit_time; pnd->free_stamp = now + cache_secs; } } /* Save any leftover records for later use. */ if (dncp != NULL) { for (tlp = dncp; tlp != NULL && tlp->dnl_next; tlp = tlp->dnl_next) /* null statement */; tlp->dnl_next = pnd->freerec; pnd->freerec = dncp; } (void) mutex_unlock(&pnd->free_mtx); } } } if (!found && !is_bootp) { /* * 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). */ for (oip = owner_ip; oip->s_addr != INADDR_ANY; oip++) { /* * Initialize query. */ DSVC_QINIT(query); DSVC_QEQ(query, DN_QSIP); dn.dn_sip.s_addr = ntohl(oip->s_addr); /* * 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). */ DSVC_QEQ(query, DN_QFBOOTP_ONLY| DN_QFMANUAL|DN_QFUNUSABLE); dn.dn_flags = 0; for (retry = 0; !found && retry < 3; retry++) { now = time(NULL); (void) mutex_lock(&pnd->thr_mtx); nrecords = pnd->nthreads < DHCP_MIN_RECORDS ? DHCP_MIN_RECORDS : pnd->nthreads; (void) mutex_unlock(&pnd->thr_mtx); /* * Purge cached records when expired or database * re-read. */ (void) mutex_lock(&pnd->lru_mtx); dnsp = pnd->lrurec; if (dnsp != NULL && PND_LRU_TIMEOUT(pnd, now)) { pnd->lrurec = NULL; dhcp_free_dd_list(pnd->dh, dnsp); dnsp = NULL; } if (dnsp != NULL) { if (retry == 0) { /* Try the next cached record */ pnd->lrurec = dnsp->dnl_next; dnsp->dnl_next = NULL; } else if (retry == 1) { /* * Try all remaining cached * records */ pnd->lrurec = NULL; } } if (retry > 1) { /* Try all possible records */ pnd->lrurec = NULL; nrecords = -1; if (dnsp != NULL) { dhcp_free_dd_list(pnd->dh, dnsp); dnsp = NULL; } } (void) mutex_unlock(&pnd->lru_mtx); io_done = (dnsp == NULL); *dnlpp = dhcp_lookup_dd_classify(pcd->pnd, nrecords == -1 ? B_FALSE : B_TRUE, query, nrecords, &dn, (void **)&dnsp, S_LRU); if (*dnlpp != NULL) { dnp = (*dnlpp)->dnl_rec; tip.s_addr = htonl(dnp->dn_cip.s_addr); (void) update_offer(pcd, NULL, 0, &tip, B_TRUE); found = B_TRUE; } (void) mutex_lock(&pnd->lru_mtx); if (io_done) { if (dnsp != NULL) { now = time(NULL); pnd->lru_mtime = reinit_time; pnd->lru_stamp = now + cache_secs; } } /* * Save any leftover records for possible * later use */ if (dnsp != NULL) { for (tlp = dnsp; tlp != NULL && tlp->dnl_next; tlp = tlp->dnl_next) /* null statement */; tlp->dnl_next = pnd->lrurec; pnd->lrurec = dnsp; } (void) mutex_unlock(&pnd->lru_mtx); } } } 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 * dnp - pointer to cached/modified entry */ static void purge_dnet_cache(dsvc_dnet_t *pnd, dn_rec_t *dnp) { dn_rec_list_t *tlp; dn_rec_list_t *plp; (void) mutex_lock(&pnd->free_mtx); for (plp = tlp = pnd->freerec; tlp != NULL; tlp = tlp->dnl_next) { if (tlp->dnl_rec->dn_cip.s_addr == dnp->dn_cip.s_addr) { if (tlp == plp) { pnd->freerec = tlp->dnl_next; } else { plp->dnl_next = tlp->dnl_next; } tlp->dnl_next = NULL; break; } plp = tlp; } (void) mutex_unlock(&pnd->free_mtx); if (tlp != NULL) dhcp_free_dd_list(pnd->dh, tlp); (void) mutex_lock(&pnd->lru_mtx); for (plp = tlp = pnd->lrurec; tlp != NULL; tlp = tlp->dnl_next) { if (tlp->dnl_rec->dn_cip.s_addr == dnp->dn_cip.s_addr) { if (tlp == plp) { pnd->lrurec = tlp->dnl_next; } else { plp->dnl_next = tlp->dnl_next; } tlp->dnl_next = NULL; break; } plp = tlp; } (void) mutex_unlock(&pnd->lru_mtx); if (tlp != NULL) dhcp_free_dd_list(pnd->dh, tlp); } /* * 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 * dnlp - pointer to cached/modified entry. */ static void add_dnet_cache(dsvc_dnet_t *pnd, dn_rec_list_t *dnlp) { (void) mutex_lock(&pnd->free_mtx); dnlp->dnl_next = pnd->freerec; pnd->freerec = dnlp; (void) mutex_unlock(&pnd->free_mtx); } 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 addr_avail(dsvc_dnet_t *pnd, dsvc_clnt_t *pcd, dn_rec_list_t **dnlpp, struct in_addr req_ip, boolean_t isname) { dn_rec_t dn; dn_rec_list_t *dnip; uint32_t query; *dnlpp = NULL; /* * first, check the ICMP list or offer list. */ if (isname) { if (pcd->off_ip.s_addr != req_ip.s_addr && check_offer(pnd, &req_ip) == B_FALSE) { /* Offered to someone else. Sorry. */ dhcpmsg(LOG_DEBUG, "name_avail(F):" " check_offer failed\n"); return (B_FALSE); } } else { if (update_offer(pcd, NULL, 0, &req_ip, B_TRUE) == B_FALSE) { /* Offered to someone else. Sorry. */ if (isname) { dhcpmsg(LOG_DEBUG, "name_avail(F):" " check_other_offers failed\n"); } return (B_FALSE); } } /* * entry_available() searches for owner_ips * query on DN_QCIP will suffice here */ DSVC_QINIT(query); DSVC_QEQ(query, DN_QCIP); dn.dn_cip.s_addr = ntohl(req_ip.s_addr); dnip = NULL; *dnlpp = dhcp_lookup_dd_classify(pnd, B_FALSE, query, -1, &dn, (void **)&dnip, 0); dhcp_free_dd_list(pnd->dh, dnip); if (*dnlpp != NULL) { /* * Ok, the requested IP exists. But is it available? */ if (!entry_available(pcd, (*dnlpp)->dnl_rec)) { dhcp_free_dd_list(pnd->dh, *dnlpp); *dnlpp = NULL; purge_offer(pcd, B_FALSE, B_TRUE); return (B_FALSE); } } else { if (isname) dhcpmsg(LOG_DEBUG, "name_avail(F): %s", unowned_addr); else purge_offer(pcd, B_FALSE, B_TRUE); 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 name_avail(char *name, dsvc_clnt_t *pcd, PKT_LIST *plp, dn_rec_list_t **dnlpp, ENCODE *ecp, struct in_addr **iap) { struct hostent h, *hp, *owner_hp; char hbuf[NSS_BUFLEN_HOSTS]; char fqname [NS_MAXDNAME+1]; char owner [NS_MAXDNAME+1]; int err, ho_len; struct in_addr ia, ma; dsvc_dnet_t *pnd; boolean_t isopen = B_FALSE; ENCODE *ep; *dnlpp = NULL; /* * 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. */ if (strchr(name, '.') != NULL) { (void) strlcpy(fqname, name, sizeof (fqname)); if (fqname[(strlen(fqname))-1] != '.') (void) strcat(fqname, "."); } 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. */ if ((ecp != NULL) && ((ep = find_encode(ecp, DSYM_STANDARD, CD_DNSDOMAIN)) != NULL)) { DHCP_OPT *ho = plp->opts[CD_HOSTNAME]; /* * name_avail() should never be called unless the * CD_HOSTNAME option is present in the client's * packet. */ assert(ho != NULL); ho_len = ho->len; if (ho->value[ho_len - 1] == '\0') { /* null at end of the hostname */ ho_len = strlen((char *)ho->value); } if (qualify_hostname(fqname, (char *)ho->value, (char *)ep->data, ho_len, ep->len) == -1) return (B_FALSE); dhcpmsg(LOG_DEBUG, "name_avail: unqualified name\n" "found CD_DNSDOMAIN and qualified: %s\n", fqname); } else { /* * No DNS domain in the ENCODE list, have to use * local domain name. */ if ((resolv_conf.defdname == NULL) || (qualify_hostname(fqname, name, resolv_conf.defdname, strlen(name), strlen(resolv_conf.defdname)) == -1)) return (B_FALSE); dhcpmsg(LOG_DEBUG, "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. */ hp = gethostbyname_r(fqname, &h, hbuf, sizeof (hbuf), &err); if (hp == NULL) if ((err == HOST_NOT_FOUND) || (err == NO_DATA)) { *iap = NULL; dhcpmsg(LOG_DEBUG, "name_avail(T): gethostbyname_r failed\n"); return (B_TRUE); } else { dhcpmsg(LOG_DEBUG, "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. */ (void) memcpy(&ia, hp->h_addr, sizeof (struct in_addr)); owner_hp = gethostbyaddr_r((char *)&ia, sizeof (struct in_addr), AF_INET, &h, hbuf, sizeof (hbuf), &err); if (owner_hp == NULL) { /* If there's no PTR record the address can't be in use */ if ((err == HOST_NOT_FOUND) || (err == NO_DATA)) { *iap = NULL; dhcpmsg(LOG_DEBUG, "name_avail(T): gethostbyaddr_r failed\n"); return (B_TRUE); } else { dhcpmsg(LOG_DEBUG, "name_avail(F): gethostbyaddr_r failed\n"); return (B_FALSE); } } /* If name returned is not a FQDN, qualify with local domain name */ if (strchr(owner_hp->h_name, '.') != NULL) { (void) strlcpy(owner, owner_hp->h_name, sizeof (owner)); if (owner[(strlen(owner))-1] != '.') (void) strcat(owner, "."); } else { if ((resolv_conf.defdname == NULL) || (qualify_hostname(owner, owner_hp->h_name, resolv_conf.defdname, strlen(owner_hp->h_name), strlen(resolv_conf.defdname)) == -1)) return (B_FALSE); dhcpmsg(LOG_DEBUG, "name_avail: address owner qualified with %s\n", resolv_conf.defdname); } if ((strncmp(owner, fqname, NS_MAXDNAME)) != 0) { /* Forward lookup found an inactive record - ignore it */ *iap = NULL; dhcpmsg(LOG_DEBUG, "name_avail(T): 'A' record inactive: %s\n", owner); return (B_TRUE); } /* Get pnd of the current client */ pnd = pcd->pnd; get_netmask(&ia, &ma); if (pnd->net.s_addr != (ia.s_addr & ma.s_addr)) { /* get pnd of previous owner of the hostname */ if (open_dnet(&pnd, &ia, &ma) != DSVC_SUCCESS) { /* we must not manage the net containing this address */ dhcpmsg(LOG_DEBUG, "name_avail(F): %s", unowned_net); return (B_FALSE); } isopen = B_TRUE; } /* * Test that the address has not been offered to someone else. */ if (!addr_avail(pnd, pcd, dnlpp, ia, B_TRUE)) { if (isopen) { close_dnet(pnd, B_FALSE); } return (B_FALSE); } if (isopen) close_dnet(pnd, B_FALSE); /* LINTED */ **iap = *((struct in_addr *)hp->h_addr); dhcpmsg(LOG_DEBUG, "name_avail(T)\n"); return (B_TRUE); } static boolean_t entry_available(dsvc_clnt_t *pcd, dn_rec_t *dnp) { boolean_t isme = dnp->dn_cid_len == pcd->cid_len && memcmp(pcd->cid, dnp->dn_cid, pcd->cid_len) == 0; if ((dnp->dn_flags & (DN_FMANUAL|DN_FUNUSABLE)) != 0) return (B_FALSE); if (dnp->dn_cid_len != 0 && isme == B_FALSE && (dnp->dn_flags & (DN_FAUTOMATIC|DN_FBOOTP_ONLY))) return (B_FALSE); if (dnp->dn_cid_len != 0 && isme == B_FALSE && (lease_t)time(NULL) < (lease_t)ntohl(dnp->dn_lease)) return (B_FALSE); if (match_ownerip(htonl(dnp->dn_sip.s_addr)) == NULL) return (B_FALSE); 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 do_nsupdate(struct in_addr ia, ENCODE *ecp, PKT_LIST *plp) { struct hostent *hp; DHCP_OPT *ho; ENCODE *ep; 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. */ hp = (struct hostent *)smalloc(sizeof (struct hostent)); hp->h_addr_list = (char **)smalloc(2 * sizeof (char **)); hp->h_addr_list[1] = NULL; hp->h_addr = smalloc(sizeof (struct in_addr)); hp->h_aliases = NULL; hp->h_addrtype = AF_INET; hp->h_length = sizeof (struct in_addr); /* * Convert address to network order, as that's what hostent's are * expected to be. */ /* LINTED */ ((struct in_addr *)hp->h_addr)->s_addr = htonl(ia.s_addr); /* * 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. */ ho = plp->opts[CD_HOSTNAME]; if (memchr(ho->value, '.', ho->len) == NULL) { /* * See whether we can dredge up the DNS domain from the * ENCODE list. */ if ((ep = find_encode(ecp, DSYM_STANDARD, CD_DNSDOMAIN)) != NULL) { char *fqname; int ho_len = ho->len; /* * 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). */ if (ho->value[ho_len - 1] == '\0') { ho_len = strlen((char *)ho->value); } fqname = smalloc(ho_len + ep->len + 1 + 1 + 1); /* first copy host name, ... */ (void) memcpy(fqname, ho->value, ho_len); /* then '.', ... */ (void) memcpy(fqname + ho_len, ".", 1); /* ... then domain name, */ (void) memcpy(fqname + ho_len + 1, ep->data, ep->len); /* then a trailing '.', ... */ (void) memcpy(fqname + ho_len + ep->len + 1, ".", 1); /* no need to null-terminate - smalloc() did it */ hp->h_name = fqname; dhcpmsg(LOG_DEBUG, "do_nsupdate: unqualified name\n" "found CD_DNSDOMAIN and qualified: %s\n", fqname); } else { char cidbuf[BUFSIZ]; (void) disp_cid(plp, cidbuf, sizeof (cidbuf)); dhcpmsg(LOG_INFO, no_domain, cidbuf); } } else { hp->h_name = smalloc(ho->len + 1); (void) memcpy(hp->h_name, ho->value, ho->len); dhcpmsg(LOG_DEBUG, "do_nsupdate: fully qualified name: %s\n", hp->h_name); } /* returns -1 or the number of name service updates done */ puthostent_ret = dns_puthostent(hp, nsutimeout_secs); dhcpmsg(LOG_DEBUG, "do_nsupdate: dns_puthostent returned %d\n", puthostent_ret); 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. */ if (((get_class_id(plp, class_idbuf, sizeof (class_idbuf))) != NULL) && (strncmp(msft_classid, class_idbuf, sizeof (msft_classid)) == 0)) { dhcpmsg(LOG_DEBUG, "do_nsupdate: class ID \"%s\"\n", class_idbuf); return (B_TRUE); } else return (B_FALSE); } else { return (B_TRUE); } }