xref: /openbsd-src/usr.sbin/dhcrelay/dispatch.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*	$OpenBSD: dispatch.c,v 1.11 2016/08/27 01:26:22 guenther Exp $	*/
2 
3 /*
4  * Copyright 2004 Henning Brauer <henning@openbsd.org>
5  * Copyright (c) 1995, 1996, 1997, 1998, 1999
6  * The Internet Software Consortium.   All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of The Internet Software Consortium nor the names
18  *    of its contributors may be used to endorse or promote products derived
19  *    from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND
22  * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
23  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
24  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25  * DISCLAIMED.  IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR
26  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
29  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
30  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  *
35  * This software has been written for the Internet Software Consortium
36  * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie
37  * Enterprises.  To learn more about the Internet Software Consortium,
38  * see ``http://www.vix.com/isc''.  To learn more about Vixie
39  * Enterprises, see ``http://www.vix.com''.
40  */
41 
42 #include <sys/types.h>
43 #include <sys/ioctl.h>
44 #include <sys/socket.h>
45 
46 #include <net/if.h>
47 #include <net/if_dl.h>
48 #include <net/if_media.h>
49 #include <net/if_types.h>
50 
51 #include <netinet/in.h>
52 
53 #include <errno.h>
54 #include <ifaddrs.h>
55 #include <limits.h>
56 #include <poll.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <syslog.h>
60 #include <time.h>
61 #include <unistd.h>
62 
63 #include "dhcp.h"
64 #include "dhcpd.h"
65 
66 struct protocol *protocols;
67 struct timeout *timeouts;
68 static struct timeout *free_timeouts;
69 static int interfaces_invalidated;
70 
71 void (*bootp_packet_handler)(struct interface_info *,
72     struct dhcp_packet *, int, unsigned int,
73     struct iaddr, struct hardware *);
74 
75 static int interface_status(struct interface_info *ifinfo);
76 
77 /*
78  * Use getifaddrs() to get a list of all the attached interfaces.  For
79  * each interface that's of type INET and not the loopback interface,
80  * register that interface with the network I/O software, figure out
81  * what subnet it's on, and add it to the list of interfaces.
82  */
83 void
84 discover_interfaces(struct interface_info *iface)
85 {
86 	struct sockaddr_in foo;
87 	struct ifaddrs *ifap, *ifa;
88 	struct ifreq *tif;
89 
90 	if (getifaddrs(&ifap) != 0)
91 		error("getifaddrs failed");
92 
93 	for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) {
94 		if ((ifa->ifa_flags & IFF_LOOPBACK) ||
95 		    (ifa->ifa_flags & IFF_POINTOPOINT) ||
96 		    (!(ifa->ifa_flags & IFF_UP)))
97 			continue;
98 
99 		if (strcmp(iface->name, ifa->ifa_name))
100 			continue;
101 
102 		/*
103 		 * If we have the capability, extract link information
104 		 * and record it in a linked list.
105 		 */
106 		if (ifa->ifa_addr->sa_family == AF_LINK) {
107 			struct sockaddr_dl *foo =
108 			    (struct sockaddr_dl *)ifa->ifa_addr;
109 			struct if_data *ifi =
110 			    (struct if_data *)ifa->ifa_data;
111 
112 			iface->index = foo->sdl_index;
113 			iface->hw_address.hlen = foo->sdl_alen;
114 			if (ifi->ifi_type == IFT_ENC)
115 				iface->hw_address.htype = HTYPE_IPSEC_TUNNEL;
116 			else
117 				iface->hw_address.htype = HTYPE_ETHER; /* XXX */
118 			memcpy(iface->hw_address.haddr,
119 			    LLADDR(foo), foo->sdl_alen);
120 		} else if (ifa->ifa_addr->sa_family == AF_INET) {
121 			struct iaddr addr;
122 
123 			memcpy(&foo, ifa->ifa_addr, sizeof(foo));
124 			if (foo.sin_addr.s_addr == htonl(INADDR_LOOPBACK))
125 				continue;
126 			if (!iface->ifp) {
127 				int len = IFNAMSIZ + ifa->ifa_addr->sa_len;
128 
129 				if ((tif = malloc(len)) == NULL)
130 					error("no space to remember ifp");
131 				strlcpy(tif->ifr_name, ifa->ifa_name, IFNAMSIZ);
132 				memcpy(&tif->ifr_addr, ifa->ifa_addr,
133 				    ifa->ifa_addr->sa_len);
134 				iface->ifp = tif;
135 				iface->primary_address = foo.sin_addr;
136 			}
137 			addr.len = 4;
138 			memcpy(addr.iabuf, &foo.sin_addr.s_addr, addr.len);
139 		}
140 	}
141 
142 	if (!iface->ifp)
143 		error("%s: not found", iface->name);
144 
145 	/* Register the interface... */
146 	if_register_receive(iface);
147 	if_register_send(iface);
148 	add_protocol(iface->name, iface->rfdesc, got_one, iface);
149 	freeifaddrs(ifap);
150 }
151 
152 /*
153  * Wait for packets to come in using poll().  When a packet comes in,
154  * call receive_packet to receive the packet and possibly strip hardware
155  * addressing information from it, and then call through the
156  * bootp_packet_handler hook to try to do something with it.
157  */
158 void
159 dispatch(void)
160 {
161 	int count, i, to_msec, nfds = 0;
162 	struct protocol *l;
163 	struct pollfd *fds;
164 	time_t howlong;
165 
166 	nfds = 0;
167 	for (l = protocols; l; l = l->next)
168 		nfds++;
169 
170 	fds = calloc(nfds, sizeof(struct pollfd));
171 	if (fds == NULL)
172 		error("Can't allocate poll structures.");
173 
174 	do {
175 		/*
176 		 * Call any expired timeouts, and then if there's still
177 		 * a timeout registered, time out the select call then.
178 		 */
179 another:
180 		if (timeouts) {
181 			if (timeouts->when <= cur_time) {
182 				struct timeout *t = timeouts;
183 
184 				timeouts = timeouts->next;
185 				(*(t->func))(t->what);
186 				t->next = free_timeouts;
187 				free_timeouts = t;
188 				goto another;
189 			}
190 
191 			/*
192 			 * Figure timeout in milliseconds, and check for
193 			 * potential overflow, so we can cram into an
194 			 * int for poll, while not polling with a
195 			 * negative timeout and blocking indefinitely.
196 			 */
197 			howlong = timeouts->when - cur_time;
198 			if (howlong > INT_MAX / 1000)
199 				howlong = INT_MAX / 1000;
200 			to_msec = howlong * 1000;
201 		} else
202 			to_msec = -1;
203 
204 		/* Set up the descriptors to be polled. */
205 		i = 0;
206 
207 		for (l = protocols; l; l = l->next) {
208 			struct interface_info *ip = l->local;
209 
210 			if (ip && (l->handler != got_one || !ip->dead)) {
211 				fds[i].fd = l->fd;
212 				fds[i].events = POLLIN;
213 				fds[i].revents = 0;
214 				i++;
215 			}
216 		}
217 
218 		if (i == 0)
219 			error("No live interfaces to poll on - exiting.");
220 
221 		/* Wait for a packet or a timeout... XXX */
222 		count = poll(fds, nfds, to_msec);
223 
224 		/* Not likely to be transitory... */
225 		if (count == -1) {
226 			if (errno == EAGAIN || errno == EINTR) {
227 				time(&cur_time);
228 				continue;
229 			}
230 			else
231 				error("poll: %m");
232 		}
233 
234 		/* Get the current time... */
235 		time(&cur_time);
236 
237 		i = 0;
238 		for (l = protocols; l; l = l->next) {
239 			struct interface_info *ip = l->local;
240 
241 			if ((fds[i].revents & (POLLIN | POLLHUP))) {
242 				fds[i].revents = 0;
243 				if (ip && (l->handler != got_one ||
244 				    !ip->dead))
245 					(*(l->handler))(l);
246 				if (interfaces_invalidated)
247 					break;
248 			}
249 			i++;
250 		}
251 		interfaces_invalidated = 0;
252 	} while (1);
253 }
254 
255 
256 void
257 got_one(struct protocol *l)
258 {
259 	struct sockaddr_in from;
260 	struct hardware hfrom;
261 	struct iaddr ifrom;
262 	size_t result;
263 	union {
264 		/*
265 		 * Packet input buffer.  Must be as large as largest
266 		 * possible MTU.
267 		 */
268 		unsigned char packbuf[4095];
269 		struct dhcp_packet packet;
270 	} u;
271 	struct interface_info *ip = l->local;
272 
273 	if ((result = receive_packet(ip, u.packbuf, sizeof(u), &from,
274 	    &hfrom)) == -1) {
275 		warning("receive_packet failed on %s: %s", ip->name,
276 		    strerror(errno));
277 		ip->errors++;
278 		if ((!interface_status(ip)) ||
279 		    (ip->noifmedia && ip->errors > 20)) {
280 			/* our interface has gone away. */
281 			warning("Interface %s no longer appears valid.",
282 			    ip->name);
283 			ip->dead = 1;
284 			interfaces_invalidated = 1;
285 			close(l->fd);
286 			remove_protocol(l);
287 			free(ip);
288 		}
289 		return;
290 	}
291 	if (result == 0)
292 		return;
293 
294 	if (bootp_packet_handler) {
295 		ifrom.len = 4;
296 		memcpy(ifrom.iabuf, &from.sin_addr, ifrom.len);
297 
298 		(*bootp_packet_handler)(ip, &u.packet, result,
299 		    from.sin_port, ifrom, &hfrom);
300 	}
301 }
302 
303 int
304 interface_status(struct interface_info *ifinfo)
305 {
306 	char *ifname = ifinfo->name;
307 	int ifsock = ifinfo->rfdesc;
308 	struct ifreq ifr;
309 	struct ifmediareq ifmr;
310 
311 	/* get interface flags */
312 	memset(&ifr, 0, sizeof(ifr));
313 	strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
314 	if (ioctl(ifsock, SIOCGIFFLAGS, &ifr) == -1) {
315 		syslog(LOG_ERR, "ioctl(SIOCGIFFLAGS) on %s: %m", ifname);
316 		goto inactive;
317 	}
318 	/*
319 	 * if one of UP and RUNNING flags is dropped,
320 	 * the interface is not active.
321 	 */
322 	if ((ifr.ifr_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING)) {
323 		goto inactive;
324 	}
325 	/* Next, check carrier on the interface, if possible */
326 	if (ifinfo->noifmedia)
327 		goto active;
328 	memset(&ifmr, 0, sizeof(ifmr));
329 	strlcpy(ifmr.ifm_name, ifname, sizeof(ifmr.ifm_name));
330 	if (ioctl(ifsock, SIOCGIFMEDIA, (caddr_t)&ifmr) == -1) {
331 		if (errno != EINVAL) {
332 			syslog(LOG_DEBUG, "ioctl(SIOCGIFMEDIA) on %s: %m",
333 			    ifname);
334 
335 			ifinfo->noifmedia = 1;
336 			goto active;
337 		}
338 		/*
339 		 * EINVAL (or ENOTTY) simply means that the interface
340 		 * does not support the SIOCGIFMEDIA ioctl. We regard it alive.
341 		 */
342 		ifinfo->noifmedia = 1;
343 		goto active;
344 	}
345 	if (ifmr.ifm_status & IFM_AVALID) {
346 		switch (ifmr.ifm_active & IFM_NMASK) {
347 		case IFM_ETHER:
348 			if (ifmr.ifm_status & IFM_ACTIVE)
349 				goto active;
350 			else
351 				goto inactive;
352 			break;
353 		default:
354 			goto inactive;
355 		}
356 	}
357 inactive:
358 	return (0);
359 active:
360 	return (1);
361 }
362 
363 /* Add a protocol to the list of protocols... */
364 void
365 add_protocol(char *name, int fd, void (*handler)(struct protocol *),
366     void *local)
367 {
368 	struct protocol *p;
369 
370 	p = malloc(sizeof(*p));
371 	if (!p)
372 		error("can't allocate protocol struct for %s", name);
373 
374 	p->fd = fd;
375 	p->handler = handler;
376 	p->local = local;
377 	p->next = protocols;
378 	protocols = p;
379 }
380 
381 void
382 remove_protocol(struct protocol *proto)
383 {
384 	struct protocol *p, *next, *prev;
385 
386 	prev = NULL;
387 	for (p = protocols; p; p = next) {
388 		next = p->next;
389 		if (p == proto) {
390 			if (prev)
391 				prev->next = p->next;
392 			else
393 				protocols = p->next;
394 			free(p);
395 		}
396 	}
397 }
398