xref: /titanic_50/usr/src/cmd/cmd-inet/sbin/dhcpagent/bound.c (revision 9512fe850e98fdd448c638ca63fdd92a8a510255)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * BOUND state of the DHCP client state machine.
26  */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 #include <sys/socket.h>
31 #include <sys/types.h>
32 #include <string.h>
33 #include <netinet/in.h>
34 #include <sys/sockio.h>
35 #include <unistd.h>
36 #include <time.h>
37 #include <arpa/inet.h>
38 #include <stdlib.h>
39 #include <sys/sysmacros.h>
40 #include <dhcp_hostconf.h>
41 #include <dhcpmsg.h>
42 
43 #include "states.h"
44 #include "packet.h"
45 #include "util.h"
46 #include "agent.h"
47 #include "interface.h"
48 #include "script_handler.h"
49 
50 #define	IS_DHCP(plp)	((plp)->opts[CD_DHCP_TYPE] != NULL)
51 
52 static int	configure_if(struct ifslist *);
53 static int	configure_bound(struct ifslist *);
54 static int	configure_timers(struct ifslist *);
55 
56 /*
57  * bound_event_cb(): callback for script_start on the event EVENT_BOUND
58  *
59  *   input: struct ifslist *: the interface configured
60  *	    const char *: unused
61  *  output: int: always 1
62  */
63 
64 /* ARGSUSED */
65 static int
66 bound_event_cb(struct ifslist *ifsp, const char *msg)
67 {
68 	ipc_action_finish(ifsp, DHCP_IPC_SUCCESS);
69 	async_finish(ifsp);
70 	return (1);
71 }
72 
73 /*
74  * dhcp_bound(): configures an interface and ifs using information contained
75  *		 in the ACK packet and sets up lease timers.  before starting,
76  *		 the requested address is arped to make sure it's not in use.
77  *
78  *   input: struct ifslist *: the interface to move to bound
79  *	    PKT_LIST *: the ACK packet, or NULL if it should use ifsp->if_ack
80  *  output: int: 0 on failure, 1 on success
81  */
82 
83 int
84 dhcp_bound(struct ifslist *ifsp, PKT_LIST *ack)
85 {
86 	lease_t		cur_lease, new_lease;
87 	int		msg_level;
88 	const char	*noext = "lease renewed but time not extended";
89 	uint_t		minleft;
90 
91 	if (ack != NULL) {
92 		/* If ack we're replacing is not the original, then free it */
93 		if (ifsp->if_ack != ifsp->if_orig_ack)
94 			free_pkt_list(&ifsp->if_ack);
95 		ifsp->if_ack = ack;
96 		/* Save the first ack as the original */
97 		if (ifsp->if_orig_ack == NULL)
98 			ifsp->if_orig_ack = ack;
99 	}
100 
101 	switch (ifsp->if_state) {
102 
103 	case ADOPTING:
104 
105 		/*
106 		 * if we're adopting an interface, the lease timers
107 		 * only provide an upper bound since we don't know
108 		 * from what time they are relative to.  assume we
109 		 * have a lease time of at most DHCP_ADOPT_LEASE_MAX.
110 		 */
111 
112 		if (!IS_DHCP(ifsp->if_ack))
113 			return (0);
114 
115 		(void) memcpy(&new_lease,
116 		    ifsp->if_ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t));
117 
118 		new_lease = htonl(MIN(ntohl(new_lease), DHCP_ADOPT_LEASE_MAX));
119 
120 		(void) memcpy(ifsp->if_ack->opts[CD_LEASE_TIME]->value,
121 		    &new_lease, sizeof (lease_t));
122 
123 		/*
124 		 * we have no idea when the REQUEST that generated
125 		 * this ACK was sent, but for diagnostic purposes
126 		 * we'll assume its close to the current time.
127 		 */
128 		ifsp->if_newstart_monosec = monosec();
129 
130 		if (configure_if(ifsp) == 0)
131 			return (0);
132 
133 		if (configure_timers(ifsp) == 0)
134 			return (0);
135 
136 		ifsp->if_curstart_monosec = ifsp->if_newstart_monosec;
137 		break;
138 
139 	case REQUESTING:
140 	case INIT_REBOOT:
141 
142 		if (configure_if(ifsp) == 0)
143 			return (0);
144 
145 		if (configure_timers(ifsp) == 0)
146 			return (0);
147 
148 		/*
149 		 * We will continue configuring this interface via
150 		 * dhcp_bound_complete, once kernel DAD completes.
151 		 */
152 		ifsp->if_state = PRE_BOUND;
153 		break;
154 
155 	case PRE_BOUND:
156 		/* This is just a duplicate ack; silently ignore it */
157 		return (1);
158 
159 	case RENEWING:
160 	case REBINDING:
161 	case BOUND:
162 		cur_lease = ifsp->if_lease;
163 		if (configure_timers(ifsp) == 0)
164 			return (0);
165 
166 		/*
167 		 * if the current lease is mysteriously close to the new
168 		 * lease, warn the user.  unless there's less than a minute
169 		 * left, round to the closest minute.
170 		 */
171 
172 		if (abs((ifsp->if_newstart_monosec + ifsp->if_lease) -
173 		    (ifsp->if_curstart_monosec + cur_lease)) < DHCP_LEASE_EPS) {
174 
175 			if (ifsp->if_lease < DHCP_LEASE_ERROR_THRESH)
176 				msg_level = MSG_ERROR;
177 			else
178 				msg_level = MSG_VERBOSE;
179 
180 			minleft = (ifsp->if_lease + 30) / 60;
181 
182 			if (ifsp->if_lease < 60) {
183 				dhcpmsg(msg_level, "%s; expires in %d seconds",
184 				    noext, ifsp->if_lease);
185 			} else if (minleft == 1) {
186 				dhcpmsg(msg_level, "%s; expires in 1 minute",
187 				    noext);
188 			} else {
189 				dhcpmsg(msg_level, "%s; expires in %d minutes",
190 				    noext, minleft);
191 			}
192 		}
193 
194 		(void) script_start(ifsp, EVENT_EXTEND, bound_event_cb,
195 		    NULL, NULL);
196 
197 		ifsp->if_state = BOUND;
198 		ifsp->if_curstart_monosec = ifsp->if_newstart_monosec;
199 		break;
200 
201 	case INFORM_SENT:
202 
203 		(void) bound_event_cb(ifsp, NULL);
204 		ifsp->if_state = INFORMATION;
205 		break;
206 
207 	default:
208 		/* something is really bizarre... */
209 		dhcpmsg(MSG_DEBUG, "dhcp_bound: called in unexpected state");
210 		return (0);
211 	}
212 
213 	/*
214 	 * remove any stale hostconf file that might be lying around for
215 	 * this interface. (in general, it's harmless, since we'll write a
216 	 * fresh one when we exit anyway, but just to reduce confusion..)
217 	 */
218 
219 	(void) remove_hostconf(ifsp->if_name);
220 	return (1);
221 }
222 
223 /*
224  * dhcp_bound_complete(): complete interface configuration after DAD
225  *
226  *   input: struct ifslist *: the interface to configure
227  *  output: none
228  */
229 
230 void
231 dhcp_bound_complete(struct ifslist *ifsp)
232 {
233 	if (configure_bound(ifsp) == 0)
234 		return;
235 
236 	/*
237 	 * if the state is ADOPTING, event loop has not been started
238 	 * at this time; so don't run the EVENT_BOUND script.
239 	 */
240 	if (ifsp->if_state != ADOPTING) {
241 		(void) script_start(ifsp, EVENT_BOUND, bound_event_cb, NULL,
242 		    NULL);
243 	}
244 
245 	ifsp->if_curstart_monosec = ifsp->if_newstart_monosec;
246 	ifsp->if_state = BOUND;
247 }
248 
249 /*
250  * configure_timers(): configures the lease timers on an interface
251  *
252  *   input: struct ifslist *: the interface to configure (with a valid if_ack)
253  *  output: int: 1 on success, 0 on failure
254  */
255 
256 static int
257 configure_timers(struct ifslist *ifsp)
258 {
259 	lease_t		lease, t1, t2;
260 
261 	if (ifsp->if_ack->opts[CD_DHCP_TYPE] != NULL &&
262 	    (ifsp->if_ack->opts[CD_LEASE_TIME] == NULL ||
263 	    ifsp->if_ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) {
264 		send_decline(ifsp, "Missing or corrupted lease time",
265 		    &ifsp->if_ack->pkt->yiaddr);
266 		dhcpmsg(MSG_WARNING, "configure_timers: missing or corrupted "
267 		    "lease time in ACK on %s", ifsp->if_name);
268 		return (0);
269 	}
270 
271 	cancel_ifs_timers(ifsp);
272 
273 	/*
274 	 * type has already been verified as ACK.  if type is not set,
275 	 * then we got a BOOTP packet.  we now fetch the t1, t2, and
276 	 * lease options out of the packet into variables.  they are
277 	 * returned as relative host-byte-ordered times.
278 	 */
279 
280 	get_pkt_times(ifsp->if_ack, &lease, &t1, &t2);
281 
282 	ifsp->if_t1	= t1;
283 	ifsp->if_t2	= t2;
284 	ifsp->if_lease	= lease;
285 
286 	if (ifsp->if_lease == DHCP_PERM) {
287 		dhcpmsg(MSG_INFO, "%s acquired permanent lease", ifsp->if_name);
288 		return (1);
289 	}
290 
291 	dhcpmsg(MSG_INFO, "%s acquired lease, expires %s", ifsp->if_name,
292 	    monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_lease));
293 
294 	dhcpmsg(MSG_INFO, "%s begins renewal at %s", ifsp->if_name,
295 	    monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_t1));
296 
297 	dhcpmsg(MSG_INFO, "%s begins rebinding at %s", ifsp->if_name,
298 	    monosec_to_string(ifsp->if_newstart_monosec + ifsp->if_t2));
299 
300 	/*
301 	 * according to RFC2131, there is no minimum lease time, but don't
302 	 * set up renew/rebind timers if lease is shorter than DHCP_REBIND_MIN.
303 	 */
304 
305 	if (schedule_ifs_timer(ifsp, DHCP_LEASE_TIMER, lease, dhcp_expire) == 0)
306 		goto failure;
307 
308 	if (lease < DHCP_REBIND_MIN) {
309 		dhcpmsg(MSG_WARNING, "dhcp_bound: lease on %s is for "
310 		    "less than %d seconds!", ifsp->if_name, DHCP_REBIND_MIN);
311 		return (1);
312 	}
313 
314 	if (schedule_ifs_timer(ifsp, DHCP_T1_TIMER, t1, dhcp_renew) == 0)
315 		goto failure;
316 
317 	if (schedule_ifs_timer(ifsp, DHCP_T2_TIMER, t2, dhcp_rebind) == 0)
318 		goto failure;
319 
320 	return (1);
321 
322 failure:
323 	cancel_ifs_timers(ifsp);
324 	dhcpmsg(MSG_WARNING, "dhcp_bound: cannot schedule lease timers");
325 	return (0);
326 }
327 
328 /*
329  * configure_if(): configures an interface with DHCP parameters from an ACK
330  *
331  *   input: struct ifslist *: the interface to configure (with a valid if_ack)
332  *  output: int: 1 on success, 0 on failure
333  */
334 
335 static int
336 configure_if(struct ifslist *ifsp)
337 {
338 	struct ifreq		ifr;
339 	struct sockaddr_in	*sin;
340 	PKT_LIST		*ack = ifsp->if_ack;
341 
342 	/*
343 	 * if we're using DHCP, then we'll have a valid CD_SERVER_ID
344 	 * (we checked in dhcp_acknak()); set it now so that
345 	 * ifsp->if_server is valid in case we need to send_decline().
346 	 * note that we use comparisons against opts[CD_DHCP_TYPE]
347 	 * since we haven't set DHCP_IF_BOOTP yet (we don't do that
348 	 * until we're sure we want the offered address.)
349 	 */
350 
351 	if (ifsp->if_ack->opts[CD_DHCP_TYPE] != NULL)
352 		(void) memcpy(&ifsp->if_server.s_addr,
353 		    ack->opts[CD_SERVER_ID]->value, sizeof (ipaddr_t));
354 
355 	ifsp->if_addr.s_addr = ack->pkt->yiaddr.s_addr;
356 	if (ifsp->if_addr.s_addr == htonl(INADDR_ANY)) {
357 		dhcpmsg(MSG_ERROR, "configure_if: got invalid IP address");
358 		return (0);
359 	}
360 
361 	(void) memset(&ifr, 0, sizeof (struct ifreq));
362 	(void) strlcpy(ifr.ifr_name, ifsp->if_name, IFNAMSIZ);
363 
364 	/*
365 	 * bring the interface online.  note that there is no optimal
366 	 * order here: it is considered bad taste (and in > solaris 7,
367 	 * likely illegal) to bring an interface up before it has an
368 	 * ip address.  however, due to an apparent bug in sun fddi
369 	 * 5.0, fddi will not obtain a network routing entry unless
370 	 * the interface is brought up before it has an ip address.
371 	 * we take the lesser of the two evils; if fddi customers have
372 	 * problems, they can get a newer fddi distribution which
373 	 * fixes the problem.
374 	 */
375 
376 	/* LINTED [ifr_addr is a sockaddr which will be aligned] */
377 	sin = (struct sockaddr_in *)&ifr.ifr_addr;
378 	sin->sin_family = AF_INET;
379 
380 	if (ack->opts[CD_SUBNETMASK] != NULL &&
381 	    ack->opts[CD_SUBNETMASK]->len == sizeof (ipaddr_t)) {
382 
383 		(void) memcpy(&ifsp->if_netmask.s_addr,
384 		    ack->opts[CD_SUBNETMASK]->value, sizeof (ipaddr_t));
385 
386 	} else {
387 
388 		if (ack->opts[CD_SUBNETMASK] != NULL &&
389 		    ack->opts[CD_SUBNETMASK]->len != sizeof (ipaddr_t))
390 			dhcpmsg(MSG_WARNING, "configure_if: specified subnet "
391 			    "mask length is %d instead of %d, ignoring",
392 			    ack->opts[CD_SUBNETMASK]->len, sizeof (ipaddr_t));
393 
394 		/*
395 		 * no legitimate IP subnet mask specified..  use best
396 		 * guess.  recall that if_addr is in network order, so
397 		 * imagine it's 0x11223344: then when it is read into
398 		 * a register on x86, it becomes 0x44332211, so we
399 		 * must ntohl() it to convert it to 0x11223344 in
400 		 * order to use the macros in <netinet/in.h>.
401 		 */
402 
403 		if (IN_CLASSA(ntohl(ifsp->if_addr.s_addr)))
404 			ifsp->if_netmask.s_addr = htonl(IN_CLASSA_NET);
405 		else if (IN_CLASSB(ntohl(ifsp->if_addr.s_addr)))
406 			ifsp->if_netmask.s_addr = htonl(IN_CLASSB_NET);
407 		else if (IN_CLASSC(ntohl(ifsp->if_addr.s_addr)))
408 			ifsp->if_netmask.s_addr = htonl(IN_CLASSC_NET);
409 		else	/* must be class d */
410 			ifsp->if_netmask.s_addr = htonl(IN_CLASSD_NET);
411 
412 		dhcpmsg(MSG_WARNING, "configure_if: no IP netmask specified "
413 		    "for %s, making best guess", ifsp->if_name);
414 	}
415 
416 	dhcpmsg(MSG_INFO, "setting IP netmask to %s on %s",
417 	    inet_ntoa(ifsp->if_netmask), ifsp->if_name);
418 
419 	sin->sin_addr = ifsp->if_netmask;
420 	if (ioctl(ifsp->if_sock_fd, SIOCSIFNETMASK, &ifr) == -1) {
421 		dhcpmsg(MSG_ERR, "cannot set IP netmask on %s", ifsp->if_name);
422 		return (0);
423 	}
424 
425 	dhcpmsg(MSG_INFO, "setting IP address to %s on %s",
426 	    inet_ntoa(ifsp->if_addr), ifsp->if_name);
427 
428 	sin->sin_addr = ifsp->if_addr;
429 	if (ioctl(ifsp->if_sock_fd, SIOCSIFADDR, &ifr) == -1) {
430 		dhcpmsg(MSG_ERR, "configure_if: cannot set IP address on %s",
431 		    ifsp->if_name);
432 		return (0);
433 	}
434 
435 	if (ack->opts[CD_BROADCASTADDR] != NULL &&
436 	    ack->opts[CD_BROADCASTADDR]->len == sizeof (ipaddr_t)) {
437 
438 		(void) memcpy(&ifsp->if_broadcast.s_addr,
439 		    ack->opts[CD_BROADCASTADDR]->value, sizeof (ipaddr_t));
440 
441 	} else {
442 
443 		if (ack->opts[CD_BROADCASTADDR] != NULL &&
444 		    ack->opts[CD_BROADCASTADDR]->len != sizeof (ipaddr_t))
445 			dhcpmsg(MSG_WARNING, "configure_if: specified "
446 			    "broadcast address length is %d instead of %d, "
447 			    "ignoring", ack->opts[CD_BROADCASTADDR]->len,
448 			    sizeof (ipaddr_t));
449 
450 		/*
451 		 * no legitimate IP broadcast specified.  compute it
452 		 * from the IP address and netmask.
453 		 */
454 
455 		ifsp->if_broadcast.s_addr = ifsp->if_addr.s_addr &
456 			ifsp->if_netmask.s_addr | ~ifsp->if_netmask.s_addr;
457 
458 		dhcpmsg(MSG_WARNING, "configure_if: no IP broadcast specified "
459 		    "for %s, making best guess", ifsp->if_name);
460 	}
461 
462 	if (ioctl(ifsp->if_sock_fd, SIOCGIFFLAGS, &ifr) == -1) {
463 		dhcpmsg(MSG_ERR, "configure_if: cannot get interface flags for "
464 		    "%s", ifsp->if_name);
465 		return (0);
466 	}
467 
468 	ifr.ifr_flags |= IFF_UP;
469 
470 	if (ioctl(ifsp->if_sock_fd, SIOCSIFFLAGS, &ifr) == -1) {
471 		dhcpmsg(MSG_ERR, "configure_if: cannot set interface flags for "
472 		    "%s", ifsp->if_name);
473 		return (0);
474 	}
475 
476 	/*
477 	 * the kernel will set the broadcast address for us as part of
478 	 * bringing the interface up.  since experience has shown that dhcp
479 	 * servers sometimes provide a bogus broadcast address, we let the
480 	 * kernel set it so that it's guaranteed to be correct.
481 	 *
482 	 * also, note any inconsistencies and save the broadcast address the
483 	 * kernel set so that we can watch for changes to it.
484 	 */
485 
486 	if (ioctl(ifsp->if_sock_fd, SIOCGIFBRDADDR, &ifr) == -1) {
487 		dhcpmsg(MSG_ERR, "configure_if: cannot get broadcast address "
488 		    "for %s", ifsp->if_name);
489 		return (0);
490 	}
491 
492 	if (ifsp->if_broadcast.s_addr != sin->sin_addr.s_addr) {
493 		dhcpmsg(MSG_WARNING, "incorrect broadcast address %s specified "
494 		    "for %s; ignoring", inet_ntoa(ifsp->if_broadcast),
495 		    ifsp->if_name);
496 	}
497 
498 	ifsp->if_broadcast = sin->sin_addr;
499 	dhcpmsg(MSG_INFO, "using broadcast address %s on %s",
500 	    inet_ntoa(ifsp->if_broadcast), ifsp->if_name);
501 	return (1);
502 }
503 
504 /*
505  * configure_bound(): configures routing with DHCP parameters from an ACK,
506  *		      and sets up the if_sock_ip_fd socket used for lease
507  *		      renewal.
508  *
509  *   input: struct ifslist *: the interface to configure (with a valid if_ack)
510  *  output: int: 1 on success, 0 on failure
511  */
512 
513 static int
514 configure_bound(struct ifslist *ifsp)
515 {
516 	PKT_LIST		*ack = ifsp->if_ack;
517 	DHCP_OPT		*router_list;
518 	int			i;
519 
520 	/*
521 	 * add each provided router; we'll clean them up when the
522 	 * interface goes away or when our lease expires.
523 	 */
524 
525 	router_list = ack->opts[CD_ROUTER];
526 	if (router_list && (router_list->len % sizeof (ipaddr_t)) == 0) {
527 
528 		ifsp->if_nrouters = router_list->len / sizeof (ipaddr_t);
529 		ifsp->if_routers  = malloc(router_list->len);
530 		if (ifsp->if_routers == NULL) {
531 			dhcpmsg(MSG_ERR, "configure_bound: cannot allocate "
532 			    "default router list, ignoring default routers");
533 			ifsp->if_nrouters = 0;
534 		}
535 
536 		for (i = 0; i < ifsp->if_nrouters; i++) {
537 
538 			(void) memcpy(&ifsp->if_routers[i].s_addr,
539 			    router_list->value + (i * sizeof (ipaddr_t)),
540 			    sizeof (ipaddr_t));
541 
542 			if (add_default_route(ifsp->if_name,
543 			    &ifsp->if_routers[i]) == 0) {
544 				dhcpmsg(MSG_ERR, "configure_bound: cannot add "
545 				    "default router %s on %s", inet_ntoa(
546 				    ifsp->if_routers[i]), ifsp->if_name);
547 				ifsp->if_routers[i].s_addr = htonl(INADDR_ANY);
548 				continue;
549 			}
550 
551 			dhcpmsg(MSG_INFO, "added default router %s on %s",
552 			    inet_ntoa(ifsp->if_routers[i]), ifsp->if_name);
553 		}
554 	}
555 
556 	ifsp->if_sock_ip_fd = socket(AF_INET, SOCK_DGRAM, 0);
557 	if (ifsp->if_sock_ip_fd == -1) {
558 		dhcpmsg(MSG_ERR, "configure_bound: cannot create socket on %s",
559 		    ifsp->if_name);
560 		return (0);
561 	}
562 
563 	if (bind_sock(ifsp->if_sock_ip_fd, IPPORT_BOOTPC,
564 	    ntohl(ifsp->if_addr.s_addr)) == 0) {
565 		dhcpmsg(MSG_ERR, "configure_bound: cannot bind socket on %s",
566 		    ifsp->if_name);
567 		return (0);
568 	}
569 
570 	/*
571 	 * we wait until here to bind if_sock_fd because it turns out
572 	 * the kernel has difficulties doing binds before interfaces
573 	 * are up (although it may work sometimes, it doesn't work all
574 	 * the time.)  that's okay, because we don't use if_sock_fd
575 	 * for receiving data until we're BOUND anyway.
576 	 */
577 
578 	if (bind_sock(ifsp->if_sock_fd, IPPORT_BOOTPC, INADDR_BROADCAST) == 0) {
579 		dhcpmsg(MSG_ERR, "configure_bound: cannot bind broadcast "
580 		    "socket on %s", ifsp->if_name);
581 		return (0);
582 	}
583 
584 	/*
585 	 * we'll be using if_sock_fd for the remainder of the lease;
586 	 * blackhole if_dlpi_fd.
587 	 */
588 
589 	set_packet_filter(ifsp->if_dlpi_fd, blackhole_filter, 0, "blackhole");
590 
591 	if (ack->opts[CD_DHCP_TYPE] == NULL)
592 		ifsp->if_dflags	|= DHCP_IF_BOOTP;
593 
594 	dhcpmsg(MSG_DEBUG, "configure_bound: bound ifsp->if_sock_ip_fd");
595 	return (1);
596 }
597