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