xref: /illumos-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/request.c (revision bdfc6d18da790deeec2e0eb09c625902defe2498)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * REQUESTING state of the client state machine.
27  */
28 
29 #pragma ident	"%Z%%M%	%I%	%E% SMI"
30 
31 #include <sys/types.h>
32 #include <sys/stropts.h>	/* FLUSHR/FLUSHW */
33 #include <netinet/in.h>
34 #include <netinet/dhcp.h>
35 #include <netinet/udp.h>
36 #include <netinet/ip_var.h>
37 #include <netinet/udp_var.h>
38 #include <dhcp_hostconf.h>
39 #include <arpa/inet.h>
40 #include <string.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <dhcpmsg.h>
44 
45 #include "states.h"
46 #include "util.h"
47 #include "packet.h"
48 #include "interface.h"
49 #include "agent.h"
50 #include "defaults.h"
51 
52 static PKT_LIST		*select_best(PKT_LIST **);
53 static void		restart_dhcp(struct ifslist *);
54 static stop_func_t	stop_requesting;
55 
56 /*
57  * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers.
58  *		      if so, chooses the best one, sends a REQUEST to the
59  *		      server and registers an event handler to receive
60  *		      the ACK/NAK
61  *
62  *   input: iu_tq_t *: unused
63  *	    void *: the interface receiving OFFER packets
64  *  output: void
65  */
66 
67 /* ARGSUSED */
68 void
69 dhcp_requesting(iu_tq_t *tqp, void *arg)
70 {
71 	struct ifslist		*ifsp = (struct ifslist *)arg;
72 	dhcp_pkt_t		*dpkt;
73 	PKT_LIST		*offer;
74 	lease_t			lease;
75 
76 	if (check_ifs(ifsp) == 0) {
77 		(void) release_ifs(ifsp);
78 		return;
79 	}
80 
81 	/*
82 	 * select the best OFFER; all others pitched.
83 	 */
84 
85 	offer = select_best(&ifsp->if_recv_pkt_list);
86 	if (offer == NULL) {
87 
88 		dhcpmsg(MSG_VERBOSE, "no OFFERs on %s, waiting...",
89 		    ifsp->if_name);
90 
91 		/*
92 		 * no acceptable OFFERs have come in.  reschedule
93 		 * ourselves for callback.
94 		 */
95 
96 		if (iu_schedule_timer(tq, ifsp->if_offer_wait,
97 		    dhcp_requesting, ifsp) == -1) {
98 
99 			/*
100 			 * ugh.  the best we can do at this point is
101 			 * revert back to INIT and wait for a user to
102 			 * restart us.
103 			 */
104 
105 			ifsp->if_state	 = INIT;
106 			ifsp->if_dflags |= DHCP_IF_FAILED;
107 
108 			stop_pkt_retransmission(ifsp);
109 			ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
110 			async_finish(ifsp);
111 
112 			dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot "
113 			    "reschedule callback, reverting to INIT state on "
114 			    "%s", ifsp->if_name);
115 		} else
116 			hold_ifs(ifsp);
117 
118 		return;
119 	}
120 
121 	stop_pkt_retransmission(ifsp);
122 
123 	/*
124 	 * stop collecting packets.  check to see whether we got an
125 	 * OFFER or a BOOTP packet.  if we got a BOOTP packet, go to
126 	 * the BOUND state now.
127 	 */
128 
129 	if (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0) {
130 		(void) release_ifs(ifsp);
131 		ifsp->if_offer_id = -1;
132 	}
133 
134 	if (offer->opts[CD_DHCP_TYPE] == NULL) {
135 
136 		ifsp->if_state = REQUESTING;
137 
138 		if (dhcp_bound(ifsp, offer) == 0) {
139 			dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound "
140 			    "failed for %s", ifsp->if_name);
141 			restart_dhcp(ifsp);
142 			return;
143 		}
144 
145 		return;
146 	}
147 
148 	/*
149 	 * if we got a message from the server, display it.
150 	 */
151 
152 	if (offer->opts[CD_MESSAGE] != NULL)
153 		print_server_msg(ifsp, offer->opts[CD_MESSAGE]);
154 
155 	/*
156 	 * assemble a DHCPREQUEST, with the ciaddr field set to 0,
157 	 * since we got here from the INIT state.
158 	 */
159 
160 	dpkt = init_pkt(ifsp, REQUEST);
161 
162 	/*
163 	 * grab the lease out of the OFFER; we know it's valid since
164 	 * select_best() already checked.  The max dhcp message size
165 	 * option is set to the interface max, minus the size of the udp and
166 	 * ip headers.
167 	 */
168 
169 	(void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value,
170 	    sizeof (lease_t));
171 
172 	add_pkt_opt32(dpkt, CD_LEASE_TIME, lease);
173 	add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max -
174 			sizeof (struct udpiphdr)));
175 	add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, offer->pkt->yiaddr.s_addr);
176 	add_pkt_opt(dpkt, CD_SERVER_ID, offer->opts[CD_SERVER_ID]->value,
177 	    offer->opts[CD_SERVER_ID]->len);
178 
179 	add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len);
180 	add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen);
181 
182 	/*
183 	 * if_reqhost was set for this interface in dhcp_selecting()
184 	 * if the DF_REQUEST_HOSTNAME option set and a host name was
185 	 * found
186 	 */
187 	if (ifsp->if_reqhost != NULL) {
188 		add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost,
189 		    strlen(ifsp->if_reqhost));
190 	}
191 	add_pkt_opt(dpkt, CD_END, NULL, 0);
192 
193 	/* all done with the offer */
194 	free_pkt_list(&offer);
195 
196 	/*
197 	 * send out the REQUEST, trying retransmissions.  either a NAK
198 	 * or too many REQUEST attempts will revert us to SELECTING.
199 	 */
200 
201 	ifsp->if_state = REQUESTING;
202 	(void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_requesting);
203 
204 	/*
205 	 * wait for an ACK or NAK to come back from the server.  if
206 	 * we can't register this event handler, then we won't be able
207 	 * to see the server's responses.  the best we can really do
208 	 * in that case is drop back to INIT and hope someone notices.
209 	 */
210 
211 	if (register_acknak(ifsp) == 0) {
212 
213 		ifsp->if_state	 = INIT;
214 		ifsp->if_dflags |= DHCP_IF_FAILED;
215 
216 		ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
217 		async_finish(ifsp);
218 
219 		dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot register to "
220 		    "collect ACK/NAK packets, reverting to INIT on %s",
221 		    ifsp->if_name);
222 	}
223 }
224 
225 /*
226  * select_best(): selects the best OFFER packet from a list of OFFER packets
227  *
228  *   input: PKT_LIST **: a list of packets to select the best from
229  *  output: PKT_LIST *: the best packet, or NULL if none are acceptable
230  */
231 
232 static PKT_LIST *
233 select_best(PKT_LIST **pkts)
234 {
235 	PKT_LIST	*current, *best = NULL;
236 	uint32_t	points, best_points = 0;
237 
238 	/*
239 	 * pick out the best offer.  point system.
240 	 * what's important?
241 	 *
242 	 *	0) DHCP
243 	 *	1) no option overload
244 	 *	2) encapsulated vendor option
245 	 *	3) non-null sname and siaddr fields
246 	 *	4) non-null file field
247 	 *	5) hostname
248 	 *	6) subnetmask
249 	 *	7) router
250 	 */
251 
252 	for (current = *pkts; current != NULL; current = current->next) {
253 
254 		points = 0;
255 
256 		if (current->opts[CD_DHCP_TYPE] == NULL) {
257 			dhcpmsg(MSG_VERBOSE, "valid BOOTP reply");
258 			goto valid_offer;
259 		}
260 
261 		if (current->opts[CD_LEASE_TIME] == NULL) {
262 			dhcpmsg(MSG_WARNING, "select_best: OFFER without "
263 			    "lease time");
264 			continue;
265 		}
266 
267 		if (current->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) {
268 			dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled "
269 			    "lease time");
270 			continue;
271 		}
272 
273 		if (current->opts[CD_SERVER_ID] == NULL) {
274 			dhcpmsg(MSG_WARNING, "select_best: OFFER without "
275 			    "server id");
276 			continue;
277 		}
278 
279 		if (current->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) {
280 			dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled "
281 			    "server id");
282 			continue;
283 		}
284 
285 		/* valid DHCP OFFER.  see if we got our parameters. */
286 		dhcpmsg(MSG_VERBOSE, "valid OFFER packet");
287 		points += 30;
288 
289 valid_offer:
290 		if (current->rfc1048)
291 			points += 5;
292 
293 		/*
294 		 * also could be faked, though more difficult because
295 		 * the encapsulation is hard to encode on a BOOTP
296 		 * server; plus there's not as much real estate in the
297 		 * packet for options, so it's likely this option
298 		 * would get dropped.
299 		 */
300 
301 		if (current->opts[CD_VENDOR_SPEC] != NULL)
302 			points += 80;
303 
304 		if (current->opts[CD_SUBNETMASK] != NULL)
305 			points++;
306 
307 		if (current->opts[CD_ROUTER] != NULL)
308 			points++;
309 
310 		if (current->opts[CD_HOSTNAME] != NULL)
311 			points += 5;
312 
313 		dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points);
314 
315 		if (points >= best_points) {
316 			best_points = points;
317 			best = current;
318 		}
319 	}
320 
321 	if (best != NULL) {
322 		dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points);
323 		remove_from_pkt_list(pkts, best);
324 	} else
325 		dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply");
326 
327 	free_pkt_list(pkts);
328 	return (best);
329 }
330 
331 /*
332  * dhcp_acknak(): processes reception of an ACK or NAK packet on an interface
333  *
334  *   input: iu_eh_t *: unused
335  *	    int: the file descriptor the ACK/NAK arrived on
336  *	    short: unused
337  *	    iu_event_id_t: the id of this event callback with the handler
338  *	    void *: the interface that received the ACK or NAK
339  *  output: void
340  */
341 
342 /* ARGSUSED */
343 void
344 dhcp_acknak(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg)
345 {
346 	struct ifslist		*ifsp = (struct ifslist *)arg;
347 	PKT_LIST		*plp;
348 
349 	if (check_ifs(ifsp) == 0) {
350 		/* unregister_acknak() does our release_ifs() */
351 		(void) unregister_acknak(ifsp);
352 		(void) ioctl(fd, I_FLUSH, FLUSHR|FLUSHW);
353 		return;
354 	}
355 
356 	/*
357 	 * note that check_ifs() did our release_ifs() but we're not
358 	 * sure we're done yet; call hold_ifs() to reacquire our hold;
359 	 * if we're done, unregister_acknak() will release_ifs() below.
360 	 */
361 
362 	hold_ifs(ifsp);
363 
364 	if (recv_pkt(ifsp, fd, DHCP_PACK|DHCP_PNAK, B_FALSE) == 0)
365 		return;
366 
367 	/*
368 	 * we've got a packet; make sure it's acceptable before
369 	 * cancelling the REQUEST retransmissions.
370 	 */
371 
372 	plp = ifsp->if_recv_pkt_list;
373 	remove_from_pkt_list(&ifsp->if_recv_pkt_list, plp);
374 
375 	if (*plp->opts[CD_DHCP_TYPE]->value == ACK) {
376 		if (plp->opts[CD_LEASE_TIME] == NULL ||
377 		    plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) {
378 			dhcpmsg(MSG_WARNING, "dhcp_acknak: ACK packet on %s "
379 			    "missing mandatory lease option, ignored",
380 			    ifsp->if_name);
381 			ifsp->if_bad_offers++;
382 			free_pkt_list(&plp);
383 			return;
384 		}
385 		if ((ifsp->if_state == RENEWING ||
386 			ifsp->if_state == REBINDING) &&
387 			ifsp->if_addr.s_addr != plp->pkt->yiaddr.s_addr) {
388 			dhcpmsg(MSG_WARNING, "dhcp_acknak: renewal ACK packet "
389 				"has a different IP address (%s), ignored",
390 				inet_ntoa(plp->pkt->yiaddr));
391 			ifsp->if_bad_offers++;
392 			free_pkt_list(&plp);
393 			return;
394 		}
395 	}
396 
397 	/*
398 	 * looks good; cancel the retransmission timer and unregister
399 	 * the acknak handler. ACK to BOUND, NAK back to SELECTING.
400 	 */
401 
402 	stop_pkt_retransmission(ifsp);
403 	(void) unregister_acknak(ifsp);
404 
405 	if (*(plp->opts[CD_DHCP_TYPE]->value) == NAK) {
406 		dhcpmsg(MSG_WARNING, "dhcp_acknak: NAK on interface %s",
407 		    ifsp->if_name);
408 		ifsp->if_bad_offers++;
409 		free_pkt_list(&plp);
410 		restart_dhcp(ifsp);
411 
412 		/*
413 		 * remove any bogus cached configuration we might have
414 		 * around (right now would only happen if we got here
415 		 * from INIT_REBOOT).
416 		 */
417 
418 		(void) remove_hostconf(ifsp->if_name);
419 		return;
420 	}
421 
422 	if (plp->opts[CD_SERVER_ID] == NULL ||
423 	    plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) {
424 		dhcpmsg(MSG_ERROR, "dhcp_acknak: ACK with no valid server id, "
425 		    "restarting DHCP on %s", ifsp->if_name);
426 		ifsp->if_bad_offers++;
427 		free_pkt_list(&plp);
428 		restart_dhcp(ifsp);
429 		return;
430 	}
431 
432 	if (plp->opts[CD_MESSAGE] != NULL)
433 		print_server_msg(ifsp, plp->opts[CD_MESSAGE]);
434 
435 	if (dhcp_bound(ifsp, plp) == 0) {
436 		dhcpmsg(MSG_WARNING, "dhcp_acknak: dhcp_bound failed "
437 		    "for %s", ifsp->if_name);
438 		restart_dhcp(ifsp);
439 		return;
440 	}
441 
442 	dhcpmsg(MSG_VERBOSE, "ACK on interface %s", ifsp->if_name);
443 }
444 
445 /*
446  * restart_dhcp(): restarts DHCP (from INIT) on a given interface
447  *
448  *   input: struct ifslist *: the interface to restart DHCP on
449  *  output: void
450  */
451 
452 static void
453 restart_dhcp(struct ifslist *ifsp)
454 {
455 	if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, ifsp) == -1) {
456 
457 		ifsp->if_state	 = INIT;
458 		ifsp->if_dflags |= DHCP_IF_FAILED;
459 
460 		ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY);
461 		async_finish(ifsp);
462 
463 		dhcpmsg(MSG_ERROR, "restart_dhcp: cannot schedule dhcp_start, "
464 		    "reverting to INIT state on %s", ifsp->if_name);
465 	} else
466 		hold_ifs(ifsp);
467 }
468 
469 /*
470  * stop_requesting(): decides when to stop retransmitting REQUESTs
471  *
472  *   input: struct ifslist *: the interface REQUESTs are being sent on
473  *	    unsigned int: the number of REQUESTs sent so far
474  *  output: boolean_t: B_TRUE if retransmissions should stop
475  */
476 
477 static boolean_t
478 stop_requesting(struct ifslist *ifsp, unsigned int n_requests)
479 {
480 	if (n_requests >= DHCP_MAX_REQUESTS) {
481 
482 		(void) unregister_acknak(ifsp);
483 
484 		dhcpmsg(MSG_INFO, "no ACK/NAK to REQUESTING REQUEST, "
485 		    "restarting DHCP on %s", ifsp->if_name);
486 
487 		dhcp_selecting(ifsp);
488 		return (B_TRUE);
489 	}
490 
491 	return (B_FALSE);
492 }
493