/* $OpenBSD: dispatch.c,v 1.172 2021/03/28 17:25:21 krw Exp $ */ /* * Copyright 2004 Henning Brauer * Copyright (c) 1995, 1996, 1997, 1998, 1999 * The Internet Software Consortium. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The Internet Software Consortium nor the names * of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This software has been written for the Internet Software Consortium * by Ted Lemon in cooperation with Vixie * Enterprises. To learn more about the Internet Software Consortium, * see ``http://www.vix.com/isc''. To learn more about Vixie * Enterprises, see ``http://www.vix.com''. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dhcp.h" #include "dhcpd.h" #include "log.h" #include "privsep.h" void bpffd_handler(struct interface_info *); void dhcp_packet_dispatch(struct interface_info *, struct sockaddr_in *, struct ether_addr *); void flush_unpriv_ibuf(void); /* * Loop waiting for packets, timeouts or routing messages. */ void dispatch(struct interface_info *ifi, int routefd) { const struct timespec link_intvl = {config->link_interval, 0}; struct pollfd fds[3]; struct timespec timeout; struct timespec *ts; void (*func)(struct interface_info *); int nfds; log_debug("%s: link is %s", log_procname, LINK_STATE_IS_UP(ifi->link_state) ? "up" : "down"); while (quit == 0 || quit == RESTART) { if (quit == RESTART) { quit = 0; clock_gettime(CLOCK_MONOTONIC, &ifi->link_timeout); timespecadd(&ifi->link_timeout, &link_intvl, &ifi->link_timeout); free(ifi->configured); ifi->configured = NULL; free(ifi->unwind_info); ifi->unwind_info = NULL; ifi->state = S_PREBOOT; state_preboot(ifi); } if (timespecisset(&ifi->timeout)) { clock_gettime(CLOCK_MONOTONIC, &timeout); if (timespeccmp(&timeout, &ifi->timeout, >=)) { func = ifi->timeout_func; cancel_timeout(ifi); (*(func))(ifi); continue; } timespecsub(&ifi->timeout, &timeout, &timeout); ts = &timeout; } else ts = NULL; /* * Set up the descriptors to be polled. * * fds[0] == bpf socket for incoming packets * fds[1] == routing socket for incoming RTM messages * fds[2] == imsg socket to privileged process */ fds[0].fd = ifi->bpffd; fds[1].fd = routefd; fds[2].fd = unpriv_ibuf->fd; fds[0].events = fds[1].events = fds[2].events = POLLIN; if (unpriv_ibuf->w.queued) fds[2].events |= POLLOUT; nfds = ppoll(fds, 3, ts, NULL); if (nfds == -1) { if (errno == EINTR) continue; log_warn("%s: ppoll(bpffd, routefd, unpriv_ibuf)", log_procname); break; } if ((fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) { log_debug("%s: bpffd: ERR|HUP|NVAL", log_procname); break; } if ((fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) { log_debug("%s: routefd: ERR|HUP|NVAL", log_procname); break; } if ((fds[2].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) { log_debug("%s: unpriv_ibuf: ERR|HUP|NVAL", log_procname); break; } if (nfds == 0) continue; if ((fds[0].revents & POLLIN) != 0) bpffd_handler(ifi); if ((fds[1].revents & POLLIN) != 0) routefd_handler(ifi, routefd); if ((fds[2].revents & POLLOUT) != 0) flush_unpriv_ibuf(); if ((fds[2].revents & POLLIN) != 0) break; } } void bpffd_handler(struct interface_info *ifi) { struct sockaddr_in from; struct ether_addr hfrom; unsigned char *next, *lim; ssize_t n; n = read(ifi->bpffd, ifi->rbuf, ifi->rbuf_max); if (n == -1) { log_warn("%s: read(bpffd)", log_procname); ifi->errors++; if (ifi->errors > 20) fatalx("too many read(bpffd) failures"); return; } ifi->errors = 0; lim = ifi->rbuf + n; for (next = ifi->rbuf; quit == 0 && n > 0; next += n) { n = receive_packet(next, lim, &from, &hfrom, &ifi->recv_packet); if (n > 0) dhcp_packet_dispatch(ifi, &from, &hfrom); } } void dhcp_packet_dispatch(struct interface_info *ifi, struct sockaddr_in *from, struct ether_addr *hfrom) { struct in_addr ifrom; struct dhcp_packet *packet = &ifi->recv_packet; struct reject_elem *ap; struct option_data *options; char *src; int i, rslt; ifrom.s_addr = from->sin_addr.s_addr; if (packet->hlen != ETHER_ADDR_LEN) { log_debug("%s: discarding packet with hlen == %u", log_procname, packet->hlen); return; } else if (memcmp(&ifi->hw_address, packet->chaddr, sizeof(ifi->hw_address)) != 0) { log_debug("%s: discarding packet with chaddr == %s", log_procname, ether_ntoa((struct ether_addr *)packet->chaddr)); return; } if (ifi->xid != packet->xid) { log_debug("%s: discarding packet with XID != %u (%u)", log_procname, ifi->xid, packet->xid); return; } TAILQ_FOREACH(ap, &config->reject_list, next) if (ifrom.s_addr == ap->addr.s_addr) { log_debug("%s: discarding packet from address on reject " "list (%s)", log_procname, inet_ntoa(ifrom)); return; } options = unpack_options(&ifi->recv_packet); /* * RFC 6842 says if the server sends a client identifier * that doesn't match then the packet must be dropped. */ i = DHO_DHCP_CLIENT_IDENTIFIER; if ((options[i].len != 0) && ((options[i].len != config->send_options[i].len) || memcmp(options[i].data, config->send_options[i].data, options[i].len) != 0)) { log_debug("%s: discarding packet with client-identifier %s'", log_procname, pretty_print_option(i, &options[i], 0)); return; } rslt = asprintf(&src, "%s (%s)", inet_ntoa(ifrom), ether_ntoa(hfrom)); if (rslt == -1) fatal("src"); i = DHO_DHCP_MESSAGE_TYPE; if (options[i].data != NULL) { /* Always try a DHCP packet, even if a bad option was seen. */ switch (options[i].data[0]) { case DHCPOFFER: dhcpoffer(ifi, options, src); break; case DHCPNAK: dhcpnak(ifi, src); break; case DHCPACK: dhcpack(ifi, options, src); break; default: log_debug("%s: discarding DHCP packet of unknown type " "(%d)", log_procname, options[i].data[0]); break; } } else if (packet->op == BOOTREPLY) { bootreply(ifi, options, src); } else { log_debug("%s: discarding packet which is neither DHCP nor " "BOOTP", log_procname); } free(src); } /* * flush_unpriv_ibuf stuffs queued messages into the imsg socket. */ void flush_unpriv_ibuf(void) { while (unpriv_ibuf->w.queued) { if (msgbuf_write(&unpriv_ibuf->w) <= 0) { if (errno == EAGAIN) break; if (quit == 0) quit = TERMINATE; if (errno != EPIPE && errno != 0) log_warn("%s: msgbuf_write(unpriv_ibuf)", log_procname); break; } } } void set_timeout(struct interface_info *ifi, time_t secs, void (*where)(struct interface_info *)) { struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); timespecclear(&ifi->timeout); ifi->timeout.tv_sec = secs; timespecadd(&ifi->timeout, &now, &ifi->timeout); ifi->timeout_func = where; } void cancel_timeout(struct interface_info *ifi) { timespecclear(&ifi->timeout); ifi->timeout_func = NULL; }