xref: /openbsd-src/usr.sbin/dhcrelay/dhcrelay.c (revision 91f110e064cd7c194e59e019b83bb7496c1c84d4)
1 /*	$OpenBSD: dhcrelay.c,v 1.38 2013/03/04 00:29:56 benno Exp $ */
2 
3 /*
4  * Copyright (c) 2004 Henning Brauer <henning@cvs.openbsd.org>
5  * Copyright (c) 1997, 1998, 1999 The Internet Software Consortium.
6  * 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 "dhcpd.h"
43 #include <sys/ioctl.h>
44 
45 void	 usage(void);
46 void	 relay(struct interface_info *, struct dhcp_packet *, int,
47 	    unsigned int, struct iaddr, struct hardware *);
48 char	*print_hw_addr(int, int, unsigned char *);
49 void	 got_response(struct protocol *);
50 int	 get_rdomain(char *);
51 
52 ssize_t	 relay_agentinfo(struct interface_info *, struct dhcp_packet *,
53 	    size_t, struct in_addr *, struct in_addr *);
54 
55 time_t cur_time;
56 
57 int log_perror = 1;
58 
59 u_int16_t server_port;
60 u_int16_t client_port;
61 int log_priority;
62 struct interface_info *interfaces = NULL;
63 int server_fd;
64 int oflag;
65 
66 struct server_list {
67 	struct server_list *next;
68 	struct sockaddr_in to;
69 	int fd;
70 } *servers;
71 
72 int
73 main(int argc, char *argv[])
74 {
75 	int			 ch, no_daemon = 0, opt, rdomain;
76 	extern char		*__progname;
77 	struct server_list	*sp = NULL;
78 	struct passwd		*pw;
79 	struct sockaddr_in	 laddr;
80 
81 	/* Initially, log errors to stderr as well as to syslogd. */
82 	openlog(__progname, LOG_NDELAY, DHCPD_LOG_FACILITY);
83 	setlogmask(LOG_UPTO(LOG_INFO));
84 
85 	while ((ch = getopt(argc, argv, "adi:o")) != -1) {
86 		switch (ch) {
87 		case 'd':
88 			no_daemon = 1;
89 			break;
90 		case 'i':
91 			if (interfaces != NULL)
92 				usage();
93 			if ((interfaces = calloc(1,
94 			    sizeof(struct interface_info))) == NULL)
95 				error("calloc");
96 			strlcpy(interfaces->name, optarg,
97 			    sizeof(interfaces->name));
98 			break;
99 		case 'o':
100 			/* add the relay agent information option */
101 			oflag++;
102 			break;
103 
104 		default:
105 			usage();
106 			/* not reached */
107 		}
108 	}
109 
110 	argc -= optind;
111 	argv += optind;
112 
113 	if (argc < 1)
114 		usage();
115 
116 	while (argc > 0) {
117 		struct hostent		*he;
118 		struct in_addr		 ia, *iap = NULL;
119 
120 		if (inet_aton(argv[0], &ia))
121 			iap = &ia;
122 		else {
123 			he = gethostbyname(argv[0]);
124 			if (!he)
125 				warning("%s: host unknown", argv[0]);
126 			else
127 				iap = ((struct in_addr *)he->h_addr_list[0]);
128 		}
129 		if (iap) {
130 			if ((sp = calloc(1, sizeof *sp)) == NULL)
131 				error("calloc");
132 			sp->next = servers;
133 			servers = sp;
134 			memcpy(&sp->to.sin_addr, iap, sizeof *iap);
135 		}
136 		argc--;
137 		argv++;
138 	}
139 
140 	if (!no_daemon)
141 		log_perror = 0;
142 
143 	if (interfaces == NULL)
144 		error("no interface given");
145 
146 	/* Default DHCP/BOOTP ports. */
147 	server_port = htons(SERVER_PORT);
148 	client_port = htons(CLIENT_PORT);
149 
150 	/* We need at least one server. */
151 	if (!sp)
152 		usage();
153 
154 	discover_interfaces(interfaces);
155 
156 	rdomain = get_rdomain(interfaces->name);
157 
158 	/* Enable the relay agent option by default for enc0 */
159 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL)
160 		oflag++;
161 
162 	bzero(&laddr, sizeof laddr);
163 	laddr.sin_len = sizeof laddr;
164 	laddr.sin_family = AF_INET;
165 	laddr.sin_port = server_port;
166 	laddr.sin_addr.s_addr = interfaces->primary_address.s_addr;
167 	/* Set up the server sockaddrs. */
168 	for (sp = servers; sp; sp = sp->next) {
169 		sp->to.sin_port = server_port;
170 		sp->to.sin_family = AF_INET;
171 		sp->to.sin_len = sizeof sp->to;
172 		sp->fd = socket(AF_INET, SOCK_DGRAM, 0);
173 		if (sp->fd == -1)
174 			error("socket: %m");
175 		opt = 1;
176 		if (setsockopt(sp->fd, SOL_SOCKET, SO_REUSEPORT,
177 		    &opt, sizeof(opt)) == -1)
178 			error("setsockopt: %m");
179 		if (setsockopt(sp->fd, SOL_SOCKET, SO_RTABLE, &rdomain,
180 		    sizeof(rdomain)) == -1)
181 			error("setsockopt: %m");
182 		if (bind(sp->fd, (struct sockaddr *)&laddr, sizeof laddr) == -1)
183 			error("bind: %m");
184 		if (connect(sp->fd, (struct sockaddr *)&sp->to,
185 		    sizeof sp->to) == -1)
186 			error("connect: %m");
187 		add_protocol("server", sp->fd, got_response, sp);
188 	}
189 
190 	/* Socket used to forward packets to the DHCP client */
191 	if (interfaces->hw_address.htype == HTYPE_IPSEC_TUNNEL) {
192 		laddr.sin_addr.s_addr = INADDR_ANY;
193 		server_fd = socket(AF_INET, SOCK_DGRAM, 0);
194 		if (server_fd == -1)
195 			error("socket: %m");
196 		opt = 1;
197 		if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT,
198 		    &opt, sizeof(opt)) == -1)
199 			error("setsockopt: %m");
200 		if (setsockopt(server_fd, SOL_SOCKET, SO_RTABLE, &rdomain,
201 		    sizeof(rdomain)) == -1)
202 			error("setsockopt: %m");
203 		if (bind(server_fd, (struct sockaddr *)&laddr,
204 		    sizeof(laddr)) == -1)
205 			error("bind: %m");
206 	}
207 
208 	tzset();
209 
210 	time(&cur_time);
211 	bootp_packet_handler = relay;
212 	if (!no_daemon)
213 		daemon(0, 0);
214 
215 	if ((pw = getpwnam("_dhcp")) == NULL)
216 		error("user \"_dhcp\" not found");
217 	if (chroot(_PATH_VAREMPTY) == -1)
218 		error("chroot: %m");
219 	if (chdir("/") == -1)
220 		error("chdir(\"/\"): %m");
221 	if (setgroups(1, &pw->pw_gid) ||
222 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
223 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
224 		error("can't drop privileges: %m");
225 
226 	dispatch();
227 	/* not reached */
228 
229 	exit(0);
230 }
231 
232 void
233 relay(struct interface_info *ip, struct dhcp_packet *packet, int length,
234     unsigned int from_port, struct iaddr from, struct hardware *hfrom)
235 {
236 	struct server_list	*sp;
237 	struct sockaddr_in	 to;
238 	struct hardware		 hto;
239 
240 	if (packet->hlen > sizeof packet->chaddr) {
241 		note("Discarding packet with invalid hlen.");
242 		return;
243 	}
244 
245 	/* If it's a bootreply, forward it to the client. */
246 	if (packet->op == BOOTREPLY) {
247 		bzero(&to, sizeof(to));
248 		if (!(packet->flags & htons(BOOTP_BROADCAST))) {
249 			to.sin_addr = packet->yiaddr;
250 			to.sin_port = client_port;
251 		} else {
252 			to.sin_addr.s_addr = htonl(INADDR_BROADCAST);
253 			to.sin_port = client_port;
254 		}
255 		to.sin_family = AF_INET;
256 		to.sin_len = sizeof to;
257 
258 		/* Set up the hardware destination address. */
259 		hto.hlen = packet->hlen;
260 		if (hto.hlen > sizeof hto.haddr)
261 			hto.hlen = sizeof hto.haddr;
262 		memcpy(hto.haddr, packet->chaddr, hto.hlen);
263 		hto.htype = packet->htype;
264 
265 		if ((length = relay_agentinfo(interfaces,
266 		    packet, length, NULL, &to.sin_addr)) == -1) {
267 			note("ignoring BOOTREPLY with invalid "
268 			    "relay agent information");
269 			return;
270 		}
271 
272 		/*
273 		 * VMware PXE "ROMs" confuse the DHCP gateway address
274 		 * with the IP gateway address. This is a problem if your
275 		 * DHCP relay is running on something that's not your
276 		 * network gateway.
277 		 *
278 		 * It is purely informational from the relay to the client
279 		 * so we can safely clear it.
280 		 */
281 		packet->giaddr.s_addr = 0x0;
282 
283 		if (send_packet(interfaces, packet, length,
284 		    interfaces->primary_address, &to, &hto) != -1)
285 			debug("forwarded BOOTREPLY for %s to %s",
286 			    print_hw_addr(packet->htype, packet->hlen,
287 			    packet->chaddr), inet_ntoa(to.sin_addr));
288 		return;
289 	}
290 
291 	if (ip == NULL) {
292 		note("ignoring non BOOTREPLY from server");
293 		return;
294 	}
295 
296 	/* If giaddr is set on a BOOTREQUEST, ignore it - it's already
297 	   been gatewayed. */
298 	if (packet->giaddr.s_addr) {
299 		note("ignoring BOOTREQUEST with giaddr of %s",
300 		    inet_ntoa(packet->giaddr));
301 		return;
302 	}
303 
304 	/* Set the giaddr so the server can figure out what net it's
305 	   from and so that we can later forward the response to the
306 	   correct net. */
307 	packet->giaddr = ip->primary_address;
308 
309 	if ((length = relay_agentinfo(ip, packet, length,
310 	    (struct in_addr *)from.iabuf, NULL)) == -1) {
311 		note("ignoring BOOTREQUEST with invalid "
312 		    "relay agent information");
313 		return;
314 	}
315 
316 	/* Otherwise, it's a BOOTREQUEST, so forward it to all the
317 	   servers. */
318 	for (sp = servers; sp; sp = sp->next) {
319 		if (send(sp->fd, packet, length, 0) != -1) {
320 			debug("forwarded BOOTREQUEST for %s to %s",
321 			    print_hw_addr(packet->htype, packet->hlen,
322 			    packet->chaddr), inet_ntoa(sp->to.sin_addr));
323 		}
324 	}
325 
326 }
327 
328 void
329 usage(void)
330 {
331 	extern char	*__progname;
332 
333 	fprintf(stderr, "usage: %s [-do] -i interface server1 [... serverN]\n",
334 	    __progname);
335 	exit(1);
336 }
337 
338 char *
339 print_hw_addr(int htype, int hlen, unsigned char *data)
340 {
341 	static char	 habuf[49];
342 	char		*s = habuf;
343 	int		 i, j, slen = sizeof(habuf);
344 
345 	if (htype == 0 || hlen == 0) {
346 bad:
347 		strlcpy(habuf, "<null>", sizeof habuf);
348 		return habuf;
349 	}
350 
351 	for (i = 0; i < hlen; i++) {
352 		j = snprintf(s, slen, "%02x", data[i]);
353 		if (j <= 0 || j >= slen)
354 			goto bad;
355 		j = strlen (s);
356 		s += j;
357 		slen -= (j + 1);
358 		*s++ = ':';
359 	}
360 	*--s = '\0';
361 	return habuf;
362 }
363 
364 void
365 got_response(struct protocol *l)
366 {
367 	ssize_t result;
368 	struct iaddr ifrom;
369 	union {
370 		/*
371 		 * Packet input buffer.  Must be as large as largest
372 		 * possible MTU.
373 		 */
374 		unsigned char packbuf[4095];
375 		struct dhcp_packet packet;
376 	} u;
377 	struct server_list *sp = l->local;
378 
379 	memset(&u, DHO_END, sizeof(u));
380 	if ((result = recv(l->fd, u.packbuf, sizeof(u), 0)) == -1 &&
381 	    errno != ECONNREFUSED) {
382 		/*
383 		 * Ignore ECONNREFUSED as too many dhcp servers send a bogus
384 		 * icmp unreach for every request.
385 		 */
386 		warning("recv failed for %s: %m",
387 		    inet_ntoa(sp->to.sin_addr));
388 		return;
389 	}
390 	if (result == -1 && errno == ECONNREFUSED)
391 		return;
392 
393 	if (result == 0)
394 		return;
395 
396 	if (result < BOOTP_MIN_LEN) {
397 		note("Discarding packet with invalid size.");
398 		return;
399 	}
400 
401 	if (bootp_packet_handler) {
402 		ifrom.len = 4;
403 		memcpy(ifrom.iabuf, &sp->to.sin_addr, ifrom.len);
404 
405 		(*bootp_packet_handler)(NULL, &u.packet, result,
406 		    sp->to.sin_port, ifrom, NULL);
407 	}
408 }
409 
410 ssize_t
411 relay_agentinfo(struct interface_info *info, struct dhcp_packet *packet,
412     size_t length, struct in_addr *from, struct in_addr *to)
413 {
414 	u_int8_t	*p;
415 	u_int		 i, j, railen;
416 	ssize_t		 optlen, maxlen, grow;
417 
418 	if (!oflag)
419 		return (length);
420 
421 	/* Buffer length vs. received packet length */
422 	maxlen = DHCP_MTU_MAX - DHCP_FIXED_LEN - DHCP_OPTIONS_COOKIE_LEN - 1;
423 	optlen = length - DHCP_FIXED_NON_UDP - DHCP_OPTIONS_COOKIE_LEN;
424 	if (maxlen < 1 || optlen < 1)
425 		return (length);
426 
427 	if (memcmp(packet->options, DHCP_OPTIONS_COOKIE,
428 	    DHCP_OPTIONS_COOKIE_LEN) != 0)
429 		return (length);
430 	p = packet->options + DHCP_OPTIONS_COOKIE_LEN;
431 
432 	for (i = 0; i < (u_int)optlen && *p != DHO_END;) {
433 		if (*p == DHO_PAD)
434 			j = 1;
435 		else
436 			j = p[1] + 2;
437 
438 		if ((i + j) > (u_int)optlen) {
439 			warning("truncated dhcp options");
440 			break;
441 		}
442 
443 		/* Revert any other relay agent information */
444 		if (*p == DHO_RELAY_AGENT_INFORMATION) {
445 			if (to != NULL) {
446 				/* Check the relay agent information */
447 				railen = 8 + sizeof(struct in_addr);
448 				if (j >= railen &&
449 				    p[1] == (railen - 2) &&
450 				    p[2] == RAI_CIRCUIT_ID &&
451 				    p[3] == 2 &&
452 				    p[4] == (u_int8_t)(info->index << 8) &&
453 				    p[5] == (info->index & 0xff) &&
454 				    p[6] == RAI_REMOTE_ID &&
455 				    p[7] == sizeof(*to))
456 					memcpy(to, p + 8, sizeof(*to));
457 
458 				/* It should be the last option */
459 				memset(p, 0, j);
460 				*p = DHO_END;
461 			} else {
462 				/* Discard invalid option from a client */
463 				if (!packet->giaddr.s_addr)
464 					return (-1);
465 			}
466 			return (length);
467 		}
468 
469 		p += j;
470 		i += j;
471 
472 		if (from != NULL && (*p == DHO_END || (i >= optlen))) {
473 			j = 8 + sizeof(*from);
474 			if ((i + j) > (u_int)maxlen) {
475 				warning("skipping agent information");
476 				break;
477 			}
478 
479 			/* Append the relay agent information if it fits */
480 			p[0] = DHO_RELAY_AGENT_INFORMATION;
481 			p[1] = j - 2;
482 			p[2] = RAI_CIRCUIT_ID;
483 			p[3] = 2;
484 			p[4] = info->index << 8;
485 			p[5] = info->index & 0xff;
486 			p[6] = RAI_REMOTE_ID;
487 			p[7] = sizeof(*from);
488 			memcpy(p + 8, from, sizeof(*from));
489 
490 			/* Do we need to increase the packet length? */
491 			grow = j + 1 - (optlen - i);
492 			if (grow > 0)
493 				length += grow;
494 			p += j;
495 
496 			*p = DHO_END;
497 			break;
498 		}
499 	}
500 
501 	return (length);
502 }
503 
504 int
505 get_rdomain(char *name)
506 {
507 	int rv = 0, s;
508 	struct  ifreq ifr;
509 
510 	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
511 		error("get_rdomain socket: %m");
512 
513 	bzero(&ifr, sizeof(ifr));
514 	strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
515 	if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) != -1)
516 		rv = ifr.ifr_rdomainid;
517 
518 	close(s);
519 	return rv;
520 }
521