/* * 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 * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dhcpd.h" #include "per_dnet.h" #include "interfaces.h" #include #include static int socksize = 64 * 1024; /* large socket window size for data */ static const uchar_t magic_cookie[] = BOOTMAGIC; static void disp_if(IF *); /* * Network interface configuration. This file contains routines which * handle the input side of the DHCP/BOOTP/Relay agent. Multiple interfaces * are handled by identifying explicitly each interface, and creating a * stream for each. If only one usable interface exists, then a "normal" * UDP socket is used for simplicity's sake. */ IF *if_head; /* head of interfaces list */ mutex_t if_head_mtx; /* mutex for adding/deleting IF list entries */ char *interfaces; /* user specified interfaces */ static int num_interfaces; /* # of usable interfaces on the system */ static char * dsrvr_socktype(dsrvr_socktype_t stype) { char *rp; switch (stype) { case DSRVR_LBCAST: rp = "limited broadcast"; break; case DSRVR_DBCAST: rp = "directed broadcast"; break; case DSRVR_UCAST: rp = "unicast"; break; } return (rp); } /* * Given two packets, match them based on BOOTP header operation, packet len, * hardware type, flags, ciaddr, DHCP type, client id, or chaddr. * Returns B_TRUE if they match, B_FALSE otherwise. */ static boolean_t match_plp(PKT_LIST *alp, PKT_LIST *blp) { DHCP_OPT *a, *b; assert(alp != NULL && blp != NULL); if (alp->pkt->op != blp->pkt->op || alp->len != alp->len || alp->pkt->htype != blp->pkt->htype || alp->pkt->flags != blp->pkt->flags || alp->pkt->ciaddr.s_addr != blp->pkt->ciaddr.s_addr) return (B_FALSE); /* not even the same BOOTP type. */ #ifdef DEBUG if (alp->pkt->giaddr.s_addr != blp->pkt->giaddr.s_addr) { dhcpmsg(LOG_DEBUG, "%04d match_plp: giaddr mismatch on 0x%x, 0x%x\n", thr_self()); } #endif /* DEBUG */ a = alp->opts[CD_DHCP_TYPE]; b = blp->opts[CD_DHCP_TYPE]; if (a == NULL && b == NULL) { /* bootp */ if (memcmp(alp->pkt->chaddr, blp->pkt->chaddr, alp->pkt->hlen) == 0) return (B_TRUE); } else if (a != NULL && b != NULL) { if (a->value[0] == b->value[0]) { /* dhcp - packet types match. */ a = alp->opts[CD_CLIENT_ID]; b = blp->opts[CD_CLIENT_ID]; if (a != NULL && b != NULL) { if (memcmp(a->value, b->value, a->len) == 0) return (B_TRUE); } else { if (memcmp(alp->pkt->chaddr, blp->pkt->chaddr, alp->pkt->hlen) == 0) return (B_TRUE); } } } return (B_FALSE); } /* * Given a packet, searches for a later packet in the * interface's client list. If the search is successful, the argument * packet is deleted, and the later packet is returned with the appropriate * fields/options modified. * * Matches are based on match_plp(). The list is scanned until the final packet * which "matches" is found. The last match replaces * the argument plp. Duplicates are deleted. * * General Notes: After the first candidate is found, the list is checked to * the tail of the list for other matches. For each packet which is deleted. * the duplicate statistic is incremented for each one. If no candidate is * found, then the argument plp is returned. * * Caveats: What about length and contents of packets? By definition, a * client is not supposed to be altering this between frames, so we should * be ok. Since the argument plp may be destroyed, it is assumed to be * detached. */ PKT_LIST * refresh_pktlist(dsvc_clnt_t *pcd, PKT_LIST *plp) { PKT_LIST *wplp, *tplp, *retplp = NULL; IF *ifp = pcd->ifp; assert(_mutex_held(&pcd->pkt_mtx)); wplp = pcd->pkthead; while (wplp != NULL) { if (match_plp(plp, wplp)) { pcd->pending--; (void) mutex_lock(&ifp->ifp_mtx); ifp->duplicate++; (void) mutex_unlock(&ifp->ifp_mtx); /* * Note that tplp, retplp can be synonyms for * wplp. The synonyms are used because moldy plp's * will be nuked, and the plp to return will be * detached. */ tplp = wplp; wplp = wplp->next; if (retplp != NULL) { /* moldy duplicates */ free_plp(retplp); } retplp = tplp; detach_plp(pcd, retplp); } else { wplp = wplp->next; } } if (retplp == NULL) retplp = plp; else { if (debug) { dhcpmsg(LOG_DEBUG, "%04d: Refreshed (0x%x) to (0x%x)\n", thr_self(), plp, retplp); } free_plp(plp); } return (retplp); } /* * Queries the IP transport layer for configured interfaces. Those that * are acceptable for use by our daemon have these characteristics: * * Not loopback * Is UP * * Sets num_interfaces global to number of valid, selected interfaces. * * Returns: 0 for success, the appropriate errno on fatal failure. * * Notes: Code gleaned from the in.rarpd, solaris 2.2. */ static int find_interfaces(void) { int i, k, ip, reqsize, numifs; boolean_t found; ushort_t mtu_tmp; struct ifreq *reqbuf, *ifr; struct ifconf ifconf; IF *ifp, *if_tail; struct sockaddr_in *sin; char **user_if; ENCODE *hecp; if ((ip = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { dhcpmsg(LOG_ERR, "Error opening socket: %s\n", strerror(errno)); return (1); } if (ioctl(ip, SIOCGIFNUM, &numifs) < 0) { dhcpmsg(LOG_WARNING, "Error discovering number of network interfaces: %s\n", strerror(errno)); (void) close(ip); return (1); } reqsize = numifs * sizeof (struct ifreq); reqbuf = (struct ifreq *)smalloc(reqsize); ifconf.ifc_len = reqsize; ifconf.ifc_buf = (caddr_t)reqbuf; if (ioctl(ip, SIOCGIFCONF, &ifconf) < 0) { dhcpmsg(LOG_ERR, "Error getting network interface information: %s\n", strerror(errno)); free(reqbuf); (void) close(ip); return (1); } /* * Verify that user specified interfaces are valid. */ user_if = (char **)smalloc(numifs * sizeof (char *)); if (interfaces != NULL) { for (i = 0; i < numifs; i++) { user_if[i] = strtok(interfaces, ","); if (user_if[i] == NULL) break; /* we're done */ interfaces = NULL; /* for next call to strtok() */ for (found = B_FALSE, ifr = ifconf.ifc_req; ifr < &ifconf.ifc_req[ifconf.ifc_len / sizeof (struct ifreq)]; ifr++) { if (strcmp(user_if[i], ifr->ifr_name) == 0) { found = B_TRUE; break; } } if (!found) { dhcpmsg(LOG_ERR, "Invalid network interface: %s\n", user_if[i]); free(reqbuf); free(user_if); (void) close(ip); return (1); } } if (i < numifs) user_if[i] = NULL; } else user_if[0] = NULL; /* * For each interface, build an interface structure. Ignore any * LOOPBACK or down interfaces. */ if_tail = if_head = NULL; for (ifr = ifconf.ifc_req; ifr < &ifconf.ifc_req[ifconf.ifc_len / sizeof (struct ifreq)]; ifr++) { if (ioctl(ip, SIOCGIFFLAGS, ifr) < 0) { dhcpmsg(LOG_ERR, "Error encountered getting interface: %s flags: %s\n", ifr->ifr_name, strerror(errno)); continue; } if ((ifr->ifr_flags & IFF_LOOPBACK) || !(ifr->ifr_flags & IFF_UP)) continue; num_interfaces++; /* all possible interfaces counted */ /* * If the user specified a list of interfaces, * we'll only consider the ones specified. */ if (user_if[0] != NULL) { for (i = 0; i < numifs; i++) { if (user_if[i] == NULL) break; /* skip this interface */ if (strcmp(user_if[i], ifr->ifr_name) == 0) break; /* user wants this one */ } if (i == numifs || user_if[i] == NULL) continue; /* skip this interface */ } else if (strchr(ifr->ifr_name, ':') != NULL) continue; /* skip virtual interfaces */ ifp = (IF *)smalloc(sizeof (IF)); (void) strcpy(ifp->nm, ifr->ifr_name); ifp->ifceno = if_nametoindex(ifp->nm); ifp->flags = ifr->ifr_flags; for (k = 0; k < DSRVR_NUM_DESC; k++) ifp->descs[k] = -1; /* * Broadcast address. Not valid for POINTOPOINT * connections. */ if ((ifp->flags & IFF_POINTOPOINT) == 0) { if (ifp->flags & IFF_BROADCAST) { if (ioctl(ip, SIOCGIFBRDADDR, ifr) < 0) { dhcpmsg(LOG_ERR, "Error encountered \ getting interface: %s broadcast address: %s\n", ifp->nm, strerror(errno)); free(ifp); num_interfaces--; continue; } /* LINTED [alignment ok] */ sin = (struct sockaddr_in *)&ifr->ifr_addr; ifp->bcast = sin->sin_addr; } else ifp->bcast.s_addr = htonl(INADDR_ANY); hecp = make_encode(DSYM_STANDARD, CD_BROADCASTADDR, sizeof (struct in_addr), &ifp->bcast, ENC_COPY); replace_encode(&ifp->ecp, hecp, ENC_DONT_COPY); } /* Subnet mask */ if (ioctl(ip, SIOCGIFNETMASK, ifr) < 0) { dhcpmsg(LOG_ERR, "Error encountered getting \ interface: %s netmask: %s\n", ifp->nm, strerror(errno)); free_encode_list(ifp->ecp); free(ifp); num_interfaces--; continue; } /* LINTED [alignment ok] */ sin = (struct sockaddr_in *)&ifr->ifr_addr; ifp->mask = sin->sin_addr; hecp = make_encode(DSYM_STANDARD, CD_SUBNETMASK, sizeof (struct in_addr), &ifp->mask, ENC_COPY); replace_encode(&ifp->ecp, hecp, ENC_DONT_COPY); /* Address */ if (ioctl(ip, SIOCGIFADDR, ifr) < 0) { dhcpmsg(LOG_ERR, "Error encountered getting \ interface: %s address: %s\n", ifp->nm, strerror(errno)); free_encode_list(ifp->ecp); free(ifp); num_interfaces--; continue; } /* LINTED [alignment ok] */ sin = (struct sockaddr_in *)&ifr->ifr_addr; ifp->addr = sin->sin_addr; /* MTU */ if (ioctl(ip, SIOCGIFMTU, ifr) < 0) { dhcpmsg(LOG_ERR, "Error encountered getting \ interface: %s MTU: %s\n", ifp->nm, strerror(errno)); free_encode_list(ifp->ecp); free(ifp); num_interfaces--; continue; } ifp->mtu = ifr->ifr_metric; mtu_tmp = htons(ifp->mtu); hecp = make_encode(DSYM_STANDARD, CD_MTU, 2, &mtu_tmp, ENC_COPY); replace_encode(&ifp->ecp, hecp, ENC_DONT_COPY); /* Attach to interface list */ if (!if_tail) { (void) mutex_init(&if_head_mtx, USYNC_THREAD, 0); (void) mutex_lock(&if_head_mtx); if_tail = if_head = ifp; (void) mutex_unlock(&if_head_mtx); } else { (void) mutex_lock(&if_head_mtx); if_tail->next = ifp; if_tail = ifp; (void) mutex_unlock(&if_head_mtx); } } free(reqbuf); free(user_if); (void) close(ip); if (if_head == NULL) { num_interfaces = 0; dhcpmsg(LOG_ERR, "Cannot find any valid interfaces.\n"); (void) mutex_destroy(&if_head_mtx); return (EINVAL); } return (0); } /* * Destroy an *uninitialized* IF structure - returns next ifp. */ static IF * zap_ifp(IF **ifp_prevpp, IF *ifp) { IF *tifp; assert(_mutex_held(&if_head_mtx)); if (*ifp_prevpp == ifp) { if_head = ifp->next; *ifp_prevpp = if_head; } else (*ifp_prevpp)->next = ifp->next; tifp = ifp->next; free(ifp); return (tifp); } /* * Monitor thread function. Poll on interface descriptors. Add valid BOOTP * packets to interfaces PKT_LIST. * * Because the buffer will potentially contain the ip/udp headers, we flag * this by setting the 'offset' field to the length of the two headers so that * free_plp() can "do the right thing" * * Monitor the given interface. Signals are handled by sig_client thread. * * We make some attempt to deal with marginal interfaces as follows. We * keep track of system errors (errors) and protocol errors (ifp->errors). * If we encounter more than DHCP_MON_SYSERRS in DHCP_MON_ERRINTVL, * then the interface thread will put itself to sleep for DHCP_MON_SLEEP * minutes. * * MT SAFE */ static void * monitor_interface(void *argp) { PKT_LIST *plp, *tplp; IF *ifp = (IF *)argp; int errors, err, i; uint_t verify_len; struct pollfd pfd[DSRVR_NUM_DESC]; struct strbuf data; char cbuf[DN_MAX_CID_LEN], ntoab[INET_ADDRSTRLEN]; time_t err_interval; dn_rec_t dn; dsvc_dnet_t *pnd; dsvc_clnt_t *pcd; struct in_addr netaddr, subnetaddr; dsvc_pendclnt_t *workp; int open_ret; dsvc_thr_t *freep; thread_t tid; boolean_t existing_allocation; if (debug) { dhcpmsg(LOG_DEBUG, "Monitor (%04d/%s) started...\n", ifp->if_thread, ifp->nm); } if (verbose) disp_if(ifp); pfd[DSRVR_LBCAST].fd = ifp->descs[DSRVR_LBCAST]; pfd[DSRVR_LBCAST].events = POLLIN | POLLPRI; pfd[DSRVR_DBCAST].fd = ifp->descs[DSRVR_DBCAST]; pfd[DSRVR_DBCAST].events = POLLIN | POLLPRI; pfd[DSRVR_UCAST].fd = ifp->descs[DSRVR_UCAST]; pfd[DSRVR_UCAST].events = POLLIN | POLLPRI; err_interval = time(NULL) + DHCP_MON_ERRINTVL; errors = 0; while (time_to_go == 0) { if (errors > DHCP_MON_SYSERRS) { if (time(NULL) < err_interval) { dhcpmsg(LOG_WARNING, "Monitor (%04d/%s): Too many system errors (%d), pausing for %d minute(s)...\n", ifp->if_thread, ifp->nm, errors, DHCP_MON_SYSERRS); (void) sleep(DHCP_MON_SLEEP); err_interval = time(NULL) + DHCP_MON_ERRINTVL; } errors = 0; } pfd[DSRVR_LBCAST].revents = 0; pfd[DSRVR_DBCAST].revents = 0; pfd[DSRVR_UCAST].revents = 0; if (poll(&pfd[0], (nfds_t)DSRVR_NUM_DESC, INFTIM) < 0) { dhcpmsg(LOG_ERR, "Monitor (%04d/%s) Polling error: (%s).\n", ifp->if_thread, ifp->nm, strerror(errno)); errors++; continue; } /* * See if we are to exit. We can't be holding any locks... */ (void) mutex_lock(&ifp->ifp_mtx); if (ifp->thr_exit) { if (debug) { dhcpmsg(LOG_DEBUG, "Monitor (%04d/%s): exiting.\n", ifp->if_thread, ifp->nm); } (void) mutex_unlock(&ifp->ifp_mtx); break; } (void) mutex_unlock(&ifp->ifp_mtx); /* examine each socket for packets in turn */ for (i = 0; i < DSRVR_NUM_DESC; i++) { if (pfd[i].revents == 0) continue; if (pfd[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { dhcpmsg(LOG_ERR, "Network interface " "error on device: %s(%s)\n", ifp->nm, dsrvr_socktype(i)); errors++; continue; } if (!(pfd[i].revents & (POLLIN | POLLRDNORM))) { dhcpmsg(LOG_INFO, "Unsupported event " "on device %s(%s): %d\n", ifp->nm, dsrvr_socktype(i), pfd[i].revents); errors++; continue; } data.buf = smalloc(ifp->mtu); data.len = recv(ifp->descs[i], data.buf, ifp->mtu, 0); if (data.len < 0) { dhcpmsg(LOG_ERR, "Error: %s receiving UDP " "datagrams on %s(%s)\n", strerror(errno), ifp->nm, dsrvr_socktype(i)); free(data.buf); errors++; continue; } else verify_len = data.len; if (debug) { dhcpmsg(LOG_INFO, "Datagram received on network device: " "%s(%s)\n", ifp->nm, dsrvr_socktype(i)); } (void) mutex_lock(&ifp->ifp_mtx); ifp->received++; (void) mutex_unlock(&ifp->ifp_mtx); if (verify_len < BASE_PKT_SIZE) { if (verbose) { dhcpmsg(LOG_INFO, "Short packet %d < " "%d on %s(%s) ignored\n", verify_len, sizeof (PKT), ifp->nm, dsrvr_socktype(i)); } free(data.buf); (void) mutex_lock(&ifp->ifp_mtx); ifp->errors++; (void) mutex_unlock(&ifp->ifp_mtx); continue; } plp = (PKT_LIST *)smalloc(sizeof (PKT_LIST)); plp->offset = 0; plp->len = data.len; /* LINTED [alignment ok] */ plp->pkt = (PKT *)data.buf; if (plp->pkt->hops >= max_hops + 1) { if (verbose) { dhcpmsg(LOG_INFO, "%s(%s): Packet " "dropped: too many hops: %d\n", ifp->nm, dsrvr_socktype(i), plp->pkt->hops); } free_plp(plp); (void) mutex_lock(&ifp->ifp_mtx); ifp->errors++; (void) mutex_unlock(&ifp->ifp_mtx); continue; } /* validate hardware len */ if (plp->pkt->hlen > sizeof (plp->pkt->chaddr)) plp->pkt->hlen = sizeof (plp->pkt->chaddr); if (debug && plp->pkt->giaddr.s_addr != 0L && plp->pkt->giaddr.s_addr != ifp->addr.s_addr) { dhcpmsg(LOG_INFO, "%s(%s): Packet received " "from relay agent: %s\n", ifp->nm, dsrvr_socktype(i), inet_ntop(AF_INET, &plp->pkt->giaddr, ntoab, sizeof (ntoab))); } if (!server_mode) { /* * Relay agent mode. No further processing * required ; we'll handle it here. */ (void) mutex_lock(&if_head_mtx); err = relay_agent(ifp, plp); (void) mutex_unlock(&if_head_mtx); if (err != 0) { dhcpmsg(LOG_ERR, "Relay agent mode " "failed: %d (%s) on: %s(%s)\n", err, (plp->pkt->op == BOOTREPLY) ? "reply" : "request", ifp->nm, dsrvr_socktype(i)); errors++; /* considered system error */ } else { /* update statistics */ (void) mutex_lock(&ifp->ifp_mtx); ifp->processed++; ifp->received++; (void) mutex_unlock(&ifp->ifp_mtx); } free_plp(plp); continue; } /* ============ Packets destined for bootp and dhcp server modules ========== */ /* * Allow packets without RFC1048 magic cookies. * Just don't do an options scan on them, * thus we treat them as plain BOOTP packets. * The BOOTP server can deal with requests of * this type. */ if (memcmp(plp->pkt->cookie, magic_cookie, sizeof (magic_cookie)) != 0) { if (verbose) { dhcpmsg(LOG_INFO, "%s(%s): Client: %s " "using non-RFC1048 BOOTP cookie.\n", ifp->nm, dsrvr_socktype(i), disp_cid(plp, cbuf, sizeof (cbuf))); } plp->rfc1048 = B_FALSE; } else { /* * Scan the options in the packet and fill in * the opts and vs fields in the * clientlist * structure. If there's a DHCP message type * in the packet then it's a DHCP packet; * otherwise it's a BOOTP packet. Standard * options are RFC1048 style. */ if (dhcp_options_scan(plp, B_FALSE) != 0) { dhcpmsg(LOG_ERR, "Garbled DHCP/BOOTP " "packet received on: %s(%s)\n", ifp->nm, dsrvr_socktype(i)); free_plp(plp); (void) mutex_lock(&ifp->ifp_mtx); ifp->errors++; (void) mutex_unlock(&ifp->ifp_mtx); continue; } plp->rfc1048 = B_TRUE; } /* * Link the new packet to the list of packets * for this network/client. No need to lock plp, * since it isn't visible outside this function yet. */ if (plp->pkt->op != BOOTREQUEST) { dhcpmsg(LOG_ERR, "Unexpected packet received " "on %s(%s), BOOTP server port. Ignored.\n", ifp->nm, dsrvr_socktype(i)); free_plp(plp); (void) mutex_lock(&ifp->ifp_mtx); ifp->errors++; (void) mutex_unlock(&ifp->ifp_mtx); continue; } determine_network(ifp, plp, &netaddr, &subnetaddr); if ((err = open_dnet(&pnd, &netaddr, &subnetaddr)) != DSVC_SUCCESS) { if (verbose && err == DSVC_NO_TABLE) { netaddr.s_addr &= subnetaddr.s_addr; dhcpmsg(LOG_INFO, "%s(%s): There is no " "%s dhcp-network table for DHCP " "client's network.\n", ifp->nm, dsrvr_socktype(i), inet_ntop(AF_INET, &netaddr, ntoab, sizeof (ntoab))); } free_plp(plp); continue; } /* Find client */ get_clnt_id(plp, (uchar_t *)dn.dn_cid, sizeof (dn.dn_cid), &dn.dn_cid_len); open_ret = open_clnt(pnd, &pcd, dn.dn_cid, dn.dn_cid_len, B_FALSE); if (pcd == NULL) { free_plp(plp); close_dnet(pnd, B_FALSE); continue; } /* * DOS via Packet flooding: ensure that each client's * PKT_LIST never exceeds DHCP_MON_THRESHOLD pkts in * length. If it does, we prune it from the head of * the list, dropping sequential packets. Note that * since DHCP is a multi-transaction protocol, we would * like to be sure not to discard a REQUEST for an OFFER * we've extended. * * TODO: we are still vulnerable to flooding attacks * where bogus client ids are presented. This can be * manually controlled via the MAX_CLIENTS and * MAX_THREADS config file knobs. */ (void) mutex_lock(&pcd->pkt_mtx); if (pcd->pending > DHCP_MON_THRESHOLD) { if ((tplp = pcd->pkthead) != NULL) { detach_plp(pcd, tplp); free_plp(tplp); pcd->pending--; } } if (pcd->pkthead == NULL) pcd->pkthead = plp; else { pcd->pkttail->next = plp; plp->prev = pcd->pkttail; } pcd->pkttail = plp; pcd->pending++; (void) mutex_unlock(&pcd->pkt_mtx); /* * Manage worker threads and deferred thread work list. */ (void) mutex_lock(&pcd->pcd_mtx); pcd->ifp = ifp; if (pcd->clnt_thread == NULL && (pcd->flags & DHCP_PCD_CLOSING) == 0) { existing_allocation = B_FALSE; (void) mutex_lock(&pnd->thr_mtx); if ((freep = pnd->thrhead) != NULL) { existing_allocation = B_TRUE; /* * Restart a suspended thread. */ pnd->thrhead = freep->thr_next; if (pnd->thrhead == NULL) pnd->thrtail = NULL; (void) mutex_unlock(&pnd->thr_mtx); (void) mutex_lock(&freep->thr_mtx); freep->thr_flags &= ~DHCP_THR_LIST; freep->thr_next = NULL; freep->thr_pcd = pcd; (void) mutex_unlock(&freep->thr_mtx); pcd->clnt_thread = freep; } else if (max_threads != -1 && pnd->nthreads >= max_threads) { /* * Add client once to deferred work * list, to keep track of future work. */ if ((pcd->flags & DHCP_PCD_WORK) == 0) { pcd->flags |= DHCP_PCD_WORK; workp = (dsvc_pendclnt_t *) smalloc( sizeof (dsvc_pendclnt_t)); get_clnt_id(plp, (uchar_t *)workp->pnd_cid, sizeof (workp->pnd_cid), &workp->pnd_cid_len); if (pnd->workhead == NULL) pnd->workhead = workp; else { pnd->worktail-> pnd_next = workp; } pnd->worktail = workp; } (void) mutex_unlock(&pnd->thr_mtx); (void) mutex_unlock(&pcd->pcd_mtx); if (open_ret == DSVC_SUCCESS) close_clnt(pcd, B_FALSE); close_dnet(pnd, B_FALSE); continue; } if (pcd->clnt_thread == NULL) { pnd->nthreads++; (void) mutex_unlock(&pnd->thr_mtx); freep = pcd->clnt_thread = (dsvc_thr_t *) smalloc(sizeof (dsvc_thr_t)); (void) mutex_init(&freep->thr_mtx, USYNC_THREAD, 0); freep->thr_pcd = pcd; /* Fire up a client thread. */ if (thr_create(NULL, 0, monitor_client, freep, THR_BOUND | THR_SUSPENDED | THR_DETACHED, &freep->thr_tid) != 0) { dhcpmsg(LOG_ERR, "%s(%s): " "Error %s starting client " "monitor thread.\n", ifp->nm, dsrvr_socktype(i), strerror(errno)); (void) mutex_lock( &pnd->thr_mtx); pnd->nthreads--; (void) mutex_unlock( &pnd->thr_mtx); free(freep); freep = pcd->clnt_thread = NULL; } } if (freep != NULL) { /* * Continue the new or reused thread. * Let it close the client. */ open_ret = DSVC_BUSY; tid = freep->thr_tid; (void) mutex_unlock(&pcd->pcd_mtx); pcd = NULL; if (existing_allocation) { (void) cond_signal( &freep->thr_cv); } else { (void) thr_continue(tid); } } } if (pcd != NULL) { (void) mutex_unlock(&pcd->pcd_mtx); if (open_ret == DSVC_SUCCESS) close_clnt(pcd, B_FALSE); } close_dnet(pnd, B_FALSE); } } return (NULL); } /* * close interface sockets */ static void close_sockets(IF *ifp) { int i; for (i = 0; i < DSRVR_NUM_DESC; i++) { if (ifp->descs[i] == -1) continue; (void) close(ifp->descs[i]); ifp->descs[i] = -1; } } /* * initialize interface sockets. * * Returns: 0 for success, -1 otherwise. */ static int init_sockets(IF *ifp) { int i, soptbuf = 1; struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons((short)IPPORT_BOOTPS + port_offset); ifp->descs[DSRVR_LBCAST] = -1; ifp->descs[DSRVR_DBCAST] = -1; ifp->descs[DSRVR_UCAST] = -1; for (i = 0; i < DSRVR_NUM_DESC; i++) { ifp->descs[i] = socket(AF_INET, SOCK_DGRAM, 0); if (ifp->descs[i] < 0) { dhcpmsg(LOG_ERR, "Error opening socket on %s(%s) for " "receiving UDP datagrams: %s\n", ifp->nm, dsrvr_socktype(i), strerror(errno)); return (-1); } if (setsockopt(ifp->descs[i], SOL_SOCKET, SO_REUSEADDR, &soptbuf, (int)sizeof (soptbuf)) < 0) { dhcpmsg(LOG_DEBUG, "Setting socket option on %s(%s) " "to allow reuse on send descriptor failed: %s\n", ifp->nm, dsrvr_socktype(i), strerror(errno)); close_sockets(ifp); return (-1); } (void) setsockopt(ifp->descs[i], SOL_SOCKET, SO_RCVBUF, &socksize, sizeof (socksize)); (void) setsockopt(ifp->descs[i], SOL_SOCKET, SO_SNDBUF, &socksize, sizeof (socksize)); switch (i) { case DSRVR_LBCAST: if (setsockopt(ifp->descs[i], IPPROTO_IP, IP_BOUND_IF, &ifp->ifceno, (int)sizeof (char *)) < 0) { dhcpmsg(LOG_ERR, "Bind to index failed on %s: %s\n", ifp->nm, strerror(errno)); close_sockets(ifp); return (-1); } sin.sin_addr.s_addr = htonl(INADDR_BROADCAST); break; case DSRVR_DBCAST: sin.sin_addr.s_addr = ifp->addr.s_addr & ifp->mask.s_addr; break; case DSRVR_UCAST: /* We send out the unicast socket */ if (setsockopt(ifp->descs[i], SOL_SOCKET, SO_BROADCAST, &soptbuf, (int)sizeof (soptbuf)) < 0) { dhcpmsg(LOG_ERR, "Setting socket " "option on %s to allow broadcast " "on send descriptor failed: %s\n", ifp->nm, strerror(errno)); close_sockets(ifp); return (-1); } sin.sin_addr.s_addr = ifp->addr.s_addr; break; } if (bind(ifp->descs[i], (struct sockaddr *)&sin, sizeof (sin)) < 0) { dhcpmsg(LOG_ERR, "Error binding to UDP socket on %s(%s): %s\n", ifp->nm, dsrvr_socktype(i), strerror(errno)); close_sockets(ifp); return (-1); } } return (0); } /* * Based on the list generated by find_interfaces(), possibly modified by * user arguments, open a stream for each valid / requested interface. * * If: * * 1) Only one interface exists, open a standard bidirectional UDP * socket. Note that this is different than if only ONE * interface is requested (but more exist). * * 2) If more than one valid interface exists, then attach to the * datalink layer, push on the packet filter and buffering * modules, and wait for fragment 0 IP packets that contain * UDP packets with port 67 (server port). * * Comments: * Using DLPI to identify the interface thru which BOOTP * packets pass helps in providing the correct response. * Note that I will open a socket for use in transmitting * responses, suitably specifying the destination relay agent * or host. Note that if I'm unicasting to the client (broadcast * flag not set), that somehow I have to clue the IP layer about * the client's hw address. The only way I can see doing this is * making the appropriate ARP table entry. * * The only remaining unknown is dealing with clients that * require broadcasting, and multiple interfaces exist. I assume * that if I specify the interface's source address when * opening the socket, that a limited broadcast will be * directed to the correct net, and only the correct net. * * Returns: 0 for success, non-zero for failure. */ int open_interfaces(void) { int inum, err = 0; IF *ifp, *ifp_prevp; /* Uncover list of valid, user-selected interfaces to monitor */ if ((err = find_interfaces()) != 0) return (err); (void) mutex_lock(&if_head_mtx); /* * Setup valid interfaces. */ ifp = ifp_prevp = if_head; err = inum = 0; while (ifp != NULL) { if (init_sockets(ifp) < 0) { ifp = zap_ifp(&ifp_prevp, ifp); num_interfaces--; continue; } /* Accounting */ ifp->transmit = ifp->received = 0; ifp->duplicate = ifp->dropped = 0; ifp->processed = 0; /* ifp structure lock */ (void) mutex_init(&ifp->ifp_mtx, USYNC_THREAD, 0); ifp->thr_exit = 0; /* fire up monitor thread */ if (thr_create(NULL, 0, monitor_interface, ifp, THR_BOUND, &ifp->if_thread) != 0) { dhcpmsg(LOG_ERR, "Interface: %s - Error %s starting monitor thread.\n", ifp->nm, strerror(errno)); close_sockets(ifp); (void) mutex_destroy(&ifp->ifp_mtx); ifp = zap_ifp(&ifp_prevp, ifp); num_interfaces--; continue; } inum++; ifp_prevp = ifp; ifp = ifp->next; } (void) mutex_unlock(&if_head_mtx); /* * We must succeed in configuring at least one interface * to be considered successful. */ if (num_interfaces == 0) { err = EINVAL; dhcpmsg(LOG_ERR, "Cannot configure any interfaces.\n"); } return (err); } /* * Detach the referenced plp from the client list. */ void detach_plp(dsvc_clnt_t *pcd, PKT_LIST *plp) { assert(_mutex_held(&pcd->pkt_mtx)); if (plp->prev == NULL) { pcd->pkthead = plp->next; if (pcd->pkthead != NULL) pcd->pkthead->prev = NULL; } else plp->prev->next = plp->next; if (plp->next != NULL) plp->next->prev = plp->prev; else { pcd->pkttail = plp->prev; if (pcd->pkttail != NULL) pcd->pkttail->next = NULL; } plp->prev = plp->next = NULL; } /* * Write a packet to an interface. * * Returns 0 on success otherwise non-zero. */ int write_interface(IF *ifp, PKT *clientp, int len, struct sockaddr_in *to) { int err; to->sin_family = AF_INET; if ((err = sendto(ifp->descs[DSRVR_UCAST], clientp, len, 0, (struct sockaddr *)to, sizeof (struct sockaddr))) < 0) { dhcpmsg(LOG_ERR, "SENDTO: %s.\n", strerror(errno)); return (err); } (void) mutex_lock(&ifp->ifp_mtx); ifp->transmit++; (void) mutex_unlock(&ifp->ifp_mtx); return (0); } /* * Pop any packet filters, buffering modules, close stream, free encode * list, terminate monitor thread, free ifp. Return ifp next ptr. */ static IF * close_interface(IF *ifp) { int err; IF *tifp; assert(ifp != NULL); assert(_mutex_held(&if_head_mtx)); (void) mutex_lock(&ifp->ifp_mtx); ifp->thr_exit = 1; close_sockets(ifp); /* thread will exit poll ... */ (void) mutex_unlock(&ifp->ifp_mtx); /* * Wait for the thread to exit. We release the if_head_mtx * lock, since the monitor thread(s) need to acquire it to traverse * the list - and we don't want to deadlock. Once the monitor thread * notices the thr_exit flag, it'll be gone anyway. Note that if_head * is changing (in close_interfaces()). At this point, only monitor * threads that haven't been reaped could be walking the interface * list. They will "see" the change in if_head. */ (void) mutex_unlock(&if_head_mtx); if ((err = thr_join(ifp->if_thread, NULL, NULL)) != 0) { dhcpmsg(LOG_ERR, "Error %d while waiting for monitor %d of %s\n", err, ifp->if_thread, ifp->nm); } (void) mutex_lock(&if_head_mtx); /* * Note: clients and their associated packet lists are freed prior * to interfaces being closed. */ /* free encode list */ free_encode_list(ifp->ecp); /* display statistics */ disp_if_stats(ifp); ifp->received = ifp->processed = 0; (void) mutex_unlock(&ifp->ifp_mtx); (void) mutex_destroy(&ifp->ifp_mtx); tifp = ifp->next; free(ifp); return (tifp); } /* * Close all interfaces, freeing up associated resources. * This should only be called from main() during final exit. */ void close_interfaces(void) { (void) mutex_lock(&if_head_mtx); for (; if_head != NULL; if_head = close_interface(if_head)) { if (verbose) { dhcpmsg(LOG_INFO, "Closing interface: %s\n", if_head->nm); } } (void) mutex_unlock(&if_head_mtx); (void) mutex_destroy(&if_head_mtx); } /* * display IF info. Must be MT Safe - called from monitor threads. */ static void disp_if(IF *ifp) { char ntoab[INET_ADDRSTRLEN]; dhcpmsg(LOG_INFO, "Thread Id: %04d - Monitoring Interface: %s *****\n", ifp->if_thread, ifp->nm); dhcpmsg(LOG_INFO, "MTU: %d\tType: %s\n", ifp->mtu, "SOCKET"); if ((ifp->flags & IFF_POINTOPOINT) == 0) dhcpmsg(LOG_INFO, "Broadcast: %s\n", inet_ntop(AF_INET, &ifp->bcast, ntoab, sizeof (ntoab))); dhcpmsg(LOG_INFO, "Netmask: %s\n", inet_ntop(AF_INET, &ifp->mask, ntoab, sizeof (ntoab))); dhcpmsg(LOG_INFO, "Address: %s\n", inet_ntop(AF_INET, &ifp->addr, ntoab, sizeof (ntoab))); } /* * Display IF statistics. */ void disp_if_stats(IF *ifp) { dhcpmsg(LOG_INFO, "Interface statistics for: %s **************\n", ifp->nm); dhcpmsg(LOG_INFO, "Pending DHCP offers: %d\n", ifp->offers); dhcpmsg(LOG_INFO, "Total Packets Transmitted: %d\n", ifp->transmit); dhcpmsg(LOG_INFO, "Total Packets Received: %d\n", ifp->received); dhcpmsg(LOG_INFO, "Total Packet Duplicates: %d\n", ifp->duplicate); dhcpmsg(LOG_INFO, "Total Packets Dropped: %d\n", ifp->dropped); dhcpmsg(LOG_INFO, "Total Packets Processed: %d\n", ifp->processed); dhcpmsg(LOG_INFO, "Total Protocol Errors: %d\n", ifp->errors); } /* * Setup the arp cache so that IP address 'ia' will be temporarily * bound to hardware address 'ha' of length 'len'. 'ia' is expected in * network order. * * Returns: 0 if the arp entry was made, 1 otherwise. */ int set_arp(IF *ifp, struct in_addr *ia, uchar_t *ha, int len, uchar_t flags) { struct sockaddr_in *si; struct xarpreq arpreq; int err = 0; char scratch[DHCP_SCRATCH]; uint_t scratch_len; char ntoab[INET_ADDRSTRLEN]; (void) memset((caddr_t)&arpreq, 0, sizeof (arpreq)); arpreq.xarp_ha.sdl_family = AF_LINK; si = (struct sockaddr_in *)&arpreq.xarp_pa; si->sin_family = AF_INET; si->sin_addr = *ia; /* struct copy */ switch (flags) { case DHCP_ARP_ADD: if (debug) { scratch_len = sizeof (scratch); if (octet_to_hexascii(ha, len, scratch, &scratch_len) != 0) { dhcpmsg(LOG_DEBUG, "Cannot convert ARP \ request to ASCII: %s: len: %d\n", inet_ntop(AF_INET, ia, ntoab, sizeof (ntoab)), len); } else { dhcpmsg(LOG_DEBUG, "Adding ARP entry: %s == %s\n", inet_ntop(AF_INET, ia, ntoab, sizeof (ntoab)), scratch); } } arpreq.xarp_flags = ATF_INUSE | ATF_COM; (void) memcpy(LLADDR(&arpreq.xarp_ha), ha, len); arpreq.xarp_ha.sdl_alen = len; if (ioctl(ifp->descs[DSRVR_UCAST], SIOCSXARP, &arpreq) < 0) { dhcpmsg(LOG_ERR, "ADD: Cannot modify ARP table to add: %s\n", inet_ntop(AF_INET, ia, ntoab, sizeof (ntoab))); err = 1; } break; case DHCP_ARP_DEL: /* give it a good effort, but don't worry... */ (void) ioctl(ifp->descs[DSRVR_UCAST], SIOCDXARP, &arpreq); break; default: err = 1; break; } return (err); } /* * Address and send a BOOTP reply packet appropriately. Does right thing * based on BROADCAST flag. Also checks if giaddr field is set, and * WE are the relay agent... * * Returns: 0 for success, nonzero otherwise (fatal) */ int send_reply(IF *ifp, PKT *pp, int len, struct in_addr *dstp) { int local = B_FALSE; struct sockaddr_in to; struct in_addr if_in, cl_in; char ntoab[INET_ADDRSTRLEN]; if (pp->giaddr.s_addr != 0L && ifp->addr.s_addr != pp->giaddr.s_addr) { /* Going thru a relay agent */ to.sin_addr.s_addr = pp->giaddr.s_addr; to.sin_port = htons(IPPORT_BOOTPS + port_offset); } else { to.sin_port = htons(IPPORT_BOOTPC + port_offset); if (ntohs(pp->flags) & BCAST_MASK) { /* * TODO - what should we do if broadcast * flag is set, but ptp connection? */ if (debug) dhcpmsg(LOG_INFO, "Sending datagram to broadcast address.\n"); to.sin_addr.s_addr = INADDR_BROADCAST; } else { /* * By default, we assume unicast! */ to.sin_addr.s_addr = dstp->s_addr; if (debug) { dhcpmsg(LOG_INFO, "Unicasting datagram to %s address.\n", inet_ntop(AF_INET, dstp, ntoab, sizeof (ntoab))); } if (ifp->addr.s_addr == pp->giaddr.s_addr) { /* * No doubt a reply packet which we, as * the relay agent, are supposed to deliver. * Local Delivery! */ local = B_TRUE; } else { /* * We can't use the giaddr field to * determine whether the client is local * or remote. Use the client's address, * our interface's address, and our * interface's netmask to make this * determination. */ if_in.s_addr = ntohl(ifp->addr.s_addr); if_in.s_addr &= ntohl(ifp->mask.s_addr); cl_in.s_addr = ntohl(dstp->s_addr); cl_in.s_addr &= ntohl(ifp->mask.s_addr); if (if_in.s_addr == cl_in.s_addr) local = B_TRUE; } if (local) { /* * Local delivery. If we can make an * ARP entry we'll unicast. But only in * cases when we do have the chaddr handy. * RFC2855 and IPoIB are cases that do not * send chaddr and set hlen = 0. Identify * such media by their htype, and rely on * in-kernel ARP for them. */ if ((ifp->flags & IFF_NOARP) == 0 && ((pp->htype == ARPHRD_IB) || (set_arp(ifp, dstp, pp->chaddr, pp->hlen, DHCP_ARP_ADD) == 0))) { to.sin_addr.s_addr = dstp->s_addr; } else { to.sin_addr.s_addr = INADDR_BROADCAST; } } } } return (write_interface(ifp, pp, len, &to)); } /* * Free pkts */ void free_pktlist(dsvc_clnt_t *pcd) { PKT_LIST *plp, *plp_next; IF *ifp = pcd->ifp; assert(_mutex_held(&pcd->pcd_mtx)); plp = pcd->pkthead; while (plp != NULL) { plp_next = plp; plp = plp->next; free_plp(plp_next); ifp->dropped++; pcd->pending--; } pcd->pkthead = NULL; }