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