/* * get_lrusort: quick sort of eligible lru container entries. */ static dn_rec_list_t * get_lrusort(dsvc_dnet_t *pnd, dn_rec_list_t *lrup, uint_t *lrecords) { size_t nel; size_t size = *lrecords * sizeof (dn_rec_list_t *); dn_rec_list_t *from, **to, *next, *freerec = NULL; dn_rec_list_t *lrupage; dn_rec_t *rp; time_t reuse_time = time(NULL) - min_lru; uint_t records = 0; #ifndef NDEBUG int cnt = 0; #endif /* !NDEBUG */ (void) mutex_lock(&pnd->lrupage_mtx); if (pnd->lrupage == NULL || pnd->lrusize < size) { if (pnd->lrupage != NULL) free(pnd->lrupage); pnd->lrupage = (dn_rec_list_t **)smalloc(size); pnd->lrusize = size; } if ((to = pnd->lrupage) == NULL) { pnd->lrusize = 0; (void) mutex_unlock(&pnd->lrupage_mtx); return (lrup); } /* * Build a list of entries, discarding those which are in use. */ *to = NULL; for (from = lrup; from != NULL; from = next) { next = from->dnl_next; rp = from->dnl_rec; if (rp->dn_lease > reuse_time || (rp->dn_flags & DN_FAUTOMATIC) || rp->dn_lease == DHCP_PERM) { from->dnl_next = freerec; freerec = from; } else { records++; *(to++) = from; } assert(++cnt <= *lrecords); } /* * Sort any usable elements, and relink. */ nel = (int)(to - pnd->lrupage); if (nel > 0) { if (nel > 1) qsort(pnd->lrupage, nel, sizeof (dn_rec_list_t *), cmp_lrusort); for (to = pnd->lrupage; nel > 0; to++, nel--) (*to)->dnl_next = *(to + 1); to--; (*to)->dnl_next = NULL; } /* * Free any unusable elements, return any usable elements. */ if (freerec) dhcp_free_dd_list(pnd->dh, freerec); *lrecords = records; lrupage = *(pnd->lrupage); (void) mutex_unlock(&pnd->lrupage_mtx); return (lrupage); }
/* * dhcp_lookup_dd_classify: perform lookup_dd(), or use existing records * if supplied, and classify the results based on the type of search criteria * being employed. Centralized policy for DN_FMANUAL and DN_FUNUSABLE flag * processing are implemented here. Classification is specialized * based on these specific search criteria: * * S_CID A CID match is requested. Perform DN_FMANUAL and * DN_FUNUSABLE processing. * S_FREE A search for free records. Only examine first * matching record. * S_LRU A search for lru records. Perform sort if needed, * and only examine first matching record. * * A matching record is detached and returned if found (ok || * manual + unusable). Other successful matches are returned in recordsp as * a cache. */ void * dhcp_lookup_dd_classify(dsvc_dnet_t *pnd, boolean_t partial, uint_t query, int count, const dn_rec_t *targetp, void **recordsp, int searchtype) { int err; uint_t rec_cnt = 0, manual = 0; dn_rec_t *dnp; dn_rec_list_t *nlp = NULL, *dnlp = NULL; dn_rec_list_t *unulp = NULL; /* list of unusables, !manual */ dn_rec_list_t *unu_m_lp = NULL; /* list of unusable + manual */ dn_rec_list_t *m_lp = NULL; /* list of manual records */ dn_rec_list_t *cachep = NULL; /* match cache */ struct in_addr swapaddr; char ntoab[INET_ADDRSTRLEN]; /* * Lookup records matching the specified criteria, or use * records from a previous lookup supplied for classification. */ if (*recordsp == NULL) { TNF_PROBE_1_DEBUG(classify, "classify classify", "classify_query%debug 'in func classify'", tnf_long, query, query); err = dhcp_lookup_dd(pnd->dh, partial, query, count, targetp, (void **)recordsp, &rec_cnt); TNF_PROBE_1_DEBUG(classify_cid_end, "classify classify_end", "classify_end%debug 'in func classify'", tnf_long, rec_cnt, rec_cnt); /* * If any error occurs, mark the dsvc_dnet_t table * for immediate close and reopen. Let the protocol * perform recover, rather than attempting time-consuming * in-place error recovery. */ if (err != DSVC_SUCCESS) { (void) mutex_lock(&pnd->pnd_mtx); pnd->flags |= DHCP_PND_ERROR; hash_Dtime(pnd->hand, 0); (void) mutex_unlock(&pnd->pnd_mtx); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "classify failure %s\n", dhcpsvc_errmsg(err)); #endif /* DEBUG */ *recordsp = NULL; return (NULL); } /* * For LRU classification, sort returned records based * on dn_lease field. Discards records with valid lease * times; adjusts rec_cnt accordingly. */ if (searchtype & S_LRU) *recordsp = get_lrusort(pnd, *recordsp, &rec_cnt); } /* * Record classification: scan through all records, performing * DN_FUNUSABLE and DN_FMANUAL processing. Note that most of the * work has been performed by the datastore query. Remove the matching * entry from the singlely-linked record list, for return. Free any * non-matching entries prior to the match. Pass back any additional * entries after the match in the recordsp pointer for possible re-use * by the caching code. */ for (nlp = detach_dnrec_from_list(NULL, *recordsp, (dn_rec_list_t **)recordsp); nlp != NULL; nlp = detach_dnrec_from_list(NULL, *recordsp, (dn_rec_list_t **)recordsp)) { /* * If we find that there is a DN_FMANUAL entry that is * DN_FUNUSABLE, we fail the request, when performing a * CID search, even though there may be other CID matches. In * the CID case, those other CID matches are errors, because * there should be one and only one record for a client if that * record is marked as being DN_FMANUALly assigned. We tell * the user how many of those CID matches there are. If there * are no DN_FMANUAL records, the first matching record which * is USABLE wins. */ dnp = nlp->dnl_rec; if (dnp->dn_flags & DN_FUNUSABLE) { if ((searchtype & (S_CID|S_FREE|S_LRU)) == S_CID) { char cidbuf[DHCP_MAX_OPT_SIZE]; uint_t blen = sizeof (cidbuf); (void) octet_to_hexascii(targetp->dn_cid, targetp->dn_cid_len, cidbuf, &blen); swapaddr.s_addr = htonl(dnp->dn_cip.s_addr); dhcpmsg(LOG_NOTICE, "(%1$s,%2$s) " "currently marked as unusable.\n", cidbuf, inet_ntop(AF_INET, &swapaddr, ntoab, sizeof (ntoab))); } /* build list of unusable records */ if (dnp->dn_flags & DN_FMANUAL) { attach_dnrec_to_list(nlp, &unu_m_lp); manual++; } else attach_dnrec_to_list(nlp, &unulp); } else { if (dnp->dn_flags & DN_FMANUAL) { attach_dnrec_to_list(nlp, &m_lp); manual++; } else attach_dnrec_to_list(nlp, &cachep); /* * These searches do not require examining all * matches. */ if (searchtype & (S_FREE|S_LRU)) break; } } /* * Warnings are printed for CID searches which end with * DN_FUNUSABLE|DN_FMANUAL match(es). */ if (m_lp != NULL || unu_m_lp != NULL) { if (manual > 1) { char cidbuf[DHCP_MAX_OPT_SIZE]; uint_t blen = sizeof (cidbuf); (void) octet_to_hexascii(targetp->dn_cid, targetp->dn_cid_len, cidbuf, &blen); dhcpmsg(LOG_WARNING, "Manual allocation (%1$s) has %2$d other MANUAL" " records. It should have 0.\n", cidbuf, manual - 1); } if (unu_m_lp != NULL) { dnlp = detach_dnrec_from_list(NULL, unu_m_lp, &unu_m_lp); } else dnlp = detach_dnrec_from_list(NULL, m_lp, &m_lp); } /* Free any unusable entries */ if (unulp != NULL) dhcp_free_dd_list(pnd->dh, unulp); /* any other... */ if (dnlp == NULL) dnlp = detach_dnrec_from_list(NULL, cachep, &cachep); /* * Return any unused elements for possible caching use. These are * the additional manual + unusable (as punishment for having * multiple items), manual, and and any others. */ if (cachep != NULL) attach_dnrec_to_list(cachep, (dn_rec_list_t **)recordsp); if (m_lp != NULL) attach_dnrec_to_list(m_lp, (dn_rec_list_t **)recordsp); if (unu_m_lp != NULL) attach_dnrec_to_list(unu_m_lp, (dn_rec_list_t **)recordsp); /* * Return one of the matching record(s). */ return (dnlp); }
/* * We are guaranteed that the packet received is a BOOTP request packet, * e.g., *NOT* a DHCP packet. */ void bootp(dsvc_clnt_t *pcd, PKT_LIST *plp) { boolean_t result, existing_offer = B_FALSE; int err, write_error = DSVC_SUCCESS, flags = 0; int pkt_len; uint_t crecords = 0, irecords = 0, srecords = 0, clen; uint32_t query; PKT *rep_pktp = NULL; IF *ifp = pcd->ifp; dsvc_dnet_t *pnd = pcd->pnd; uchar_t *optp; dn_rec_t dn, ndn, *dnp; dn_rec_list_t *dncp = NULL, *dnip = NULL, *dnlp = NULL; struct in_addr ciaddr; struct in_addr no_ip; /* network order IP */ ENCODE *ecp, *hecp; MACRO *mp, *nmp, *cmp; time_t now = time(NULL); DHCP_MSG_CATEGORIES log; struct hostent h, *hp; char ntoab[INET_ADDRSTRLEN], cipbuf[INET_ADDRSTRLEN]; char cidbuf[DHCP_MAX_OPT_SIZE]; char hbuf[NSS_BUFLEN_HOSTS]; ciaddr.s_addr = htonl(INADDR_ANY); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "BOOTP request received on %s\n", ifp->nm); #endif /* DEBUG */ 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)) { existing_offer = B_TRUE; dnlp = pcd->dnlp; assert(dnlp != NULL); dnp = dnlp->dnl_rec; no_ip.s_addr = htonl(dnp->dn_cip.s_addr); crecords = 1; } else { /* * Try to find a CID entry for the client. We don't care about * lease info here, since a BOOTP client always has a permanent * lease. We also don't care about the entry owner either, * unless we end up allocating a new 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; DSVC_QEQ(query, DN_QFBOOTP_ONLY); dn.dn_flags = DN_FBOOTP_ONLY; /* * If a client address (ciaddr) is given, we simply trust that * the client knows what it's doing, and we use that IP address * 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 either the client or our * database is inconsistent, and we'll ignore it (notice * message generated). */ ciaddr.s_addr = plp->pkt->ciaddr.s_addr; if (ciaddr.s_addr != htonl(INADDR_ANY)) { DSVC_QEQ(query, DN_QCIP); dn.dn_cip.s_addr = ntohl(ciaddr.s_addr); } dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query, -1, &dn, (void **)&dncp, S_CID); if (dnlp != NULL) { crecords = 1; dnp = dnlp->dnl_rec; if (dnp->dn_flags & DN_FUNUSABLE) goto leave_bootp; no_ip.s_addr = htonl(dnp->dn_cip.s_addr); } } (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf)); if (crecords == 0 && !be_automatic) { if (verbose) { dhcpmsg(LOG_INFO, "BOOTP client: %1$s is looking for " "a configuration on net %2$s\n", pcd->cidbuf, pnd->network); } goto leave_bootp; } /* * If the client thinks it knows who it is (ciaddr), and this doesn't * match our registered IP address, then display an error message and * give up. */ if (ciaddr.s_addr != htonl(INADDR_ANY) && crecords == 0) { /* * If the client specified an IP address, then let's check * whether it is available, since we have no CID mapping * registered for this client. If it is available and * unassigned but owned by a different server, we ignore the * client. */ DSVC_QINIT(query); DSVC_QEQ(query, DN_QCIP); dn.dn_cip.s_addr = ntohl(ciaddr.s_addr); (void) inet_ntop(AF_INET, &ciaddr, cipbuf, sizeof (cipbuf)); DSVC_QEQ(query, DN_QFBOOTP_ONLY); dn.dn_flags = DN_FBOOTP_ONLY; dnip = NULL; dnlp = dhcp_lookup_dd_classify(pcd->pnd, B_FALSE, query, -1, &dn, (void **)&dncp, S_CID); if (dnlp == NULL) { /* * We have no record of this client's IP address, thus * we really can't respond to this client, because it * doesn't have a configuration. */ if (verbose) { dhcpmsg(LOG_INFO, "No configuration for BOOTP " "client: %1$s. IP address: %2$s not " "administered by this server.\n", pcd->cidbuf, inet_ntop(AF_INET, &ciaddr, ntoab, sizeof (ntoab))); } goto leave_bootp; } else irecords = 1; dnp = dnlp->dnl_rec; if (dnp->dn_flags & DN_FUNUSABLE) goto leave_bootp; if (dn.dn_cid_len != 0) { if (dn.dn_cid_len != pcd->cid_len || memcmp(dn.dn_cid, pcd->cid, pcd->cid_len) != 0) { if (verbose) { clen = sizeof (cidbuf); (void) octet_to_hexascii(dn.dn_cid, dn.dn_cid_len, cidbuf, &clen); dhcpmsg(LOG_INFO, "BOOTP client: %1$s " "thinks it owns %2$s, but that " "address belongs to %3$s. Ignoring " "client.\n", pcd->cidbuf, cipbuf, cidbuf); } goto leave_bootp; } } else { if (match_ownerip(htonl(dn.dn_sip.s_addr)) == NULL) { if (verbose) { no_ip.s_addr = htonl(dnp->dn_sip.s_addr); dhcpmsg(LOG_INFO, "BOOTP client: %1$s " "believes it owns %2$s. That " "address is free, but is owned by " "DHCP server %3$s. Ignoring " "client.\n", pcd->cidbuf, cipbuf, inet_ntop(AF_INET, &no_ip, ntoab, sizeof (ntoab))); } goto leave_bootp; } } no_ip.s_addr = htonl(dnp->dn_cip.s_addr); (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf)); } if (crecords == 0) { /* * The dhcp-network table did not have any matching entries. * Try to allocate a new one if possible. */ if (irecords == 0 && select_offer(pnd, plp, pcd, &dnlp)) { dnp = dnlp->dnl_rec; no_ip.s_addr = htonl(dnp->dn_cip.s_addr); (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf)); srecords = 1; } } if (crecords == 0 && irecords == 0 && srecords == 0) { dhcpmsg(LOG_NOTICE, "(%1$s) No more BOOTP IP addresses for %2$s network.\n", pcd->cidbuf, pnd->network); goto leave_bootp; } /* Check the address. But only if client doesn't know its address. */ ndn = *dnp; /* struct copy */ no_ip.s_addr = htonl(ndn.dn_cip.s_addr); (void) inet_ntop(AF_INET, &no_ip, cipbuf, sizeof (cipbuf)); if (ciaddr.s_addr == htonl(INADDR_ANY)) { if ((ifp->flags & IFF_NOARP) == 0) (void) set_arp(ifp, &no_ip, NULL, 0, DHCP_ARP_DEL); if (!noping) { /* * If icmp echo check fails, * let the plp fall by the wayside. */ errno = icmp_echo_check(&no_ip, &result); if (errno != 0) { dhcpmsg(LOG_ERR, "ICMP ECHO check cannot be " "performed for: %s, ignoring\n", cipbuf); goto leave_bootp; } if (result) { dhcpmsg(LOG_ERR, "ICMP ECHO reply to BOOTP " "OFFER candidate: %s, disabling.\n", cipbuf); ndn.dn_flags |= DN_FUNUSABLE; if ((err = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn)) == DSVC_SUCCESS) { /* Keep the cached entry current. */ *dnp = ndn; /* struct copy */ } logtrans(P_BOOTP, L_ICMP_ECHO, 0, no_ip, server_ip, plp); goto leave_bootp; } } } /* * It is possible that the client could specify a REQUEST list, * but then it would be a DHCP client, wouldn't it? Only copy the * std option list, since that potentially could be changed by * load_options(). */ ecp = NULL; if (!no_dhcptab) { open_macros(); if ((nmp = get_macro(pnd->network)) != NULL) ecp = dup_encode_list(nmp->head); if ((mp = get_macro(dnp->dn_macro)) != NULL) ecp = combine_encodes(ecp, mp->head, ENC_DONT_COPY); if ((cmp = get_macro(pcd->cidbuf)) != NULL) ecp = combine_encodes(ecp, cmp->head, ENC_DONT_COPY); /* If dhcptab configured to return hostname, do so. */ if (find_encode(ecp, DSYM_INTERNAL, CD_BOOL_HOSTNAME) != NULL) { hp = gethostbyaddr_r((char *)&ndn.dn_cip, 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); } } } /* Produce a BOOTP reply. */ rep_pktp = gen_bootp_pkt(sizeof (PKT), plp->pkt); rep_pktp->op = BOOTREPLY; optp = rep_pktp->options; /* * Set the client's "your" IP address if client doesn't know it, * otherwise echo the client's ciaddr back to him. */ if (ciaddr.s_addr == htonl(INADDR_ANY)) rep_pktp->yiaddr.s_addr = htonl(ndn.dn_cip.s_addr); else rep_pktp->ciaddr.s_addr = ciaddr.s_addr; /* * Omit lease time options implicitly, e.g. * ~(DHCP_DHCP_CLNT | DHCP_SEND_LEASE) */ if (!plp->rfc1048) flags |= DHCP_NON_RFC1048; /* Now load in configured options. */ pkt_len = load_options(flags, plp, rep_pktp, sizeof (PKT), optp, ecp, NULL); free_encode_list(ecp); if (!no_dhcptab) close_macros(); if (pkt_len < sizeof (PKT)) pkt_len = sizeof (PKT); /* * Only perform a write if we have selected an entry not yet * assigned to the client (a matching DN_FBOOTP_ONLY entry from * ip address lookup, or an unassigned entry from select_offer()). */ if (srecords > 0 || irecords > 0) { (void) memcpy(&ndn.dn_cid, pcd->cid, pcd->cid_len); ndn.dn_cid_len = pcd->cid_len; write_error = dhcp_modify_dd_entry(pnd->dh, dnp, &ndn); /* Keep state of the cached entry current. */ *dnp = ndn; /* struct copy */ log = L_ASSIGN; } else { if (verbose) { dhcpmsg(LOG_INFO, "Database write unnecessary for " "BOOTP client: %1$s, %2$s\n", pcd->cidbuf, cipbuf); } log = L_REPLY; } if (write_error == DSVC_SUCCESS) { if (send_reply(ifp, rep_pktp, pkt_len, &no_ip) != 0) { dhcpmsg(LOG_ERR, "Reply to BOOTP client %1$s with %2$s failed.\n", pcd->cidbuf, cipbuf); } else { /* Note that the conversation has completed. */ pcd->state = ACK; (void) update_offer(pcd, &dnlp, 0, &no_ip, B_TRUE); existing_offer = B_TRUE; } logtrans(P_BOOTP, log, ndn.dn_lease, no_ip, server_ip, plp); } leave_bootp: if (rep_pktp != NULL) free(rep_pktp); if (dncp != NULL) dhcp_free_dd_list(pnd->dh, dncp); if (dnip != NULL) dhcp_free_dd_list(pnd->dh, dnip); if (dnlp != NULL && !existing_offer) dhcp_free_dd_list(pnd->dh, dnlp); }