diff options
Diffstat (limited to 'usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/interfaces.c')
-rw-r--r-- | usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/interfaces.c | 1445 |
1 files changed, 1445 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/interfaces.c b/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/interfaces.c new file mode 100644 index 0000000000..1c3333bf69 --- /dev/null +++ b/usr/src/cmd/cmd-inet/usr.lib/in.dhcpd/interfaces.c @@ -0,0 +1,1445 @@ +/* + * 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 1993-2003 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 <unistd.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/time.h> +#include <fcntl.h> +#include <errno.h> +#include <assert.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netinet/if_ether.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <stropts.h> +#include <stdio.h> +#include <ctype.h> +#include <syslog.h> +#include <netinet/dhcp.h> +#include <dhcp_symbol.h> +#include "dhcpd.h" +#include "per_dnet.h" +#include "interfaces.h" +#include <v4_sum_impl.h> +#include <locale.h> + +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 = open("/dev/ip", 0)) < 0) { + dhcpmsg(LOG_ERR, "Error: opening /dev/ip: %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)); + 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; +} |