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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
24 */
25
26 #include <sys/types.h>
27 #include <time.h>
28 #include <netinet/in.h>
29 #include <netinet/dhcp.h>
30 #include <netinet/udp.h>
31 #include <netinet/ip_var.h>
32 #include <netinet/udp_var.h>
33 #include <libinetutil.h>
34 #include <dhcpmsg.h>
35 #include <dhcp_hostconf.h>
36 #include <string.h>
37
38 #include "packet.h"
39 #include "agent.h"
40 #include "script_handler.h"
41 #include "interface.h"
42 #include "states.h"
43 #include "util.h"
44
45 /*
46 * Number of seconds to wait for a retry if the user is interacting with the
47 * daemon.
48 */
49 #define RETRY_DELAY 10
50
51 /*
52 * If the renew timer fires within this number of seconds of the rebind timer,
53 * then skip renew. This prevents us from sending back-to-back renew and
54 * rebind messages -- a pointless activity.
55 */
56 #define TOO_CLOSE 2
57
58 static boolean_t stop_extending(dhcp_smach_t *, unsigned int);
59
60 /*
61 * dhcp_renew(): attempts to renew a DHCP lease on expiration of the T1 timer.
62 *
63 * input: iu_tq_t *: unused
64 * void *: the lease to renew (dhcp_lease_t)
65 * output: void
66 *
67 * notes: The primary expense involved with DHCP (like most UDP protocols) is
68 * with the generation and handling of packets, not the contents of
69 * those packets. Thus, we try to reduce the number of packets that
70 * are sent. It would be nice to just renew all leases here (each one
71 * added has trivial added overhead), but the DHCPv6 RFC doesn't
72 * explicitly allow that behavior. Rather than having that argument,
73 * we settle for ones that are close in expiry to the one that fired.
74 * For v4, we repeatedly reschedule the T1 timer to do the
75 * retransmissions. For v6, we rely on the common timer computation
76 * in packet.c.
77 */
78
79 /* ARGSUSED */
80 void
dhcp_renew(iu_tq_t * tqp,void * arg)81 dhcp_renew(iu_tq_t *tqp, void *arg)
82 {
83 dhcp_lease_t *dlp = arg;
84 dhcp_smach_t *dsmp = dlp->dl_smach;
85 uint32_t t2;
86
87 dhcpmsg(MSG_VERBOSE, "dhcp_renew: T1 timer expired on %s",
88 dsmp->dsm_name);
89
90 dlp->dl_t1.dt_id = -1;
91
92 if (dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) {
93 dhcpmsg(MSG_DEBUG, "dhcp_renew: already renewing");
94 release_lease(dlp);
95 return;
96 }
97
98 /*
99 * Sanity check: don't send packets if we're past T2, or if we're
100 * extremely close.
101 */
102
103 t2 = dsmp->dsm_curstart_monosec + dlp->dl_t2.dt_start;
104 if (monosec() + TOO_CLOSE >= t2) {
105 dhcpmsg(MSG_DEBUG, "dhcp_renew: %spast T2 on %s",
106 monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
107 release_lease(dlp);
108 return;
109 }
110
111 /*
112 * If there isn't an async event pending, or if we can cancel the one
113 * that's there, then try to renew by sending an extension request. If
114 * that fails, we'll try again when the next timer fires.
115 */
116 if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
117 !dhcp_extending(dsmp)) {
118 if (monosec() + RETRY_DELAY < t2) {
119 /*
120 * Try again in RETRY_DELAY seconds; user command
121 * should be gone.
122 */
123 init_timer(&dlp->dl_t1, RETRY_DELAY);
124 (void) set_smach_state(dsmp, BOUND);
125 if (!schedule_lease_timer(dlp, &dlp->dl_t1,
126 dhcp_renew)) {
127 dhcpmsg(MSG_INFO, "dhcp_renew: unable to "
128 "reschedule renewal around user command "
129 "on %s; will wait for rebind",
130 dsmp->dsm_name);
131 }
132 } else {
133 dhcpmsg(MSG_DEBUG, "dhcp_renew: user busy on %s; will "
134 "wait for rebind", dsmp->dsm_name);
135 }
136 }
137 release_lease(dlp);
138 }
139
140 /*
141 * dhcp_rebind(): attempts to renew a DHCP lease from the REBINDING state (T2
142 * timer expiry).
143 *
144 * input: iu_tq_t *: unused
145 * void *: the lease to renew
146 * output: void
147 * notes: For v4, we repeatedly reschedule the T2 timer to do the
148 * retransmissions. For v6, we rely on the common timer computation
149 * in packet.c.
150 */
151
152 /* ARGSUSED */
153 void
dhcp_rebind(iu_tq_t * tqp,void * arg)154 dhcp_rebind(iu_tq_t *tqp, void *arg)
155 {
156 dhcp_lease_t *dlp = arg;
157 dhcp_smach_t *dsmp = dlp->dl_smach;
158 int nlifs;
159 dhcp_lif_t *lif;
160 boolean_t some_valid;
161 uint32_t expiremax;
162 DHCPSTATE oldstate;
163
164 dhcpmsg(MSG_VERBOSE, "dhcp_rebind: T2 timer expired on %s",
165 dsmp->dsm_name);
166
167 dlp->dl_t2.dt_id = -1;
168
169 if ((oldstate = dsmp->dsm_state) == REBINDING) {
170 dhcpmsg(MSG_DEBUG, "dhcp_renew: already rebinding");
171 release_lease(dlp);
172 return;
173 }
174
175 /*
176 * Sanity check: don't send packets if we've already expired on all of
177 * the addresses. We compute the maximum expiration time here, because
178 * it won't matter for v4 (there's only one lease) and for v6 we need
179 * to know when the last lease ages away.
180 */
181
182 some_valid = B_FALSE;
183 expiremax = monosec();
184 lif = dlp->dl_lifs;
185 for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) {
186 uint32_t expire;
187
188 expire = dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start;
189 if (expire > expiremax) {
190 expiremax = expire;
191 some_valid = B_TRUE;
192 }
193 }
194 if (!some_valid) {
195 dhcpmsg(MSG_DEBUG, "dhcp_rebind: all leases expired on %s",
196 dsmp->dsm_name);
197 release_lease(dlp);
198 return;
199 }
200
201 /*
202 * This is our first venture into the REBINDING state, so reset the
203 * server address. We know the renew timer has already been cancelled
204 * (or we wouldn't be here).
205 */
206 if (dsmp->dsm_isv6) {
207 dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers;
208 } else {
209 IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST),
210 &dsmp->dsm_server);
211 }
212
213 /* {Bound,Renew}->rebind transitions cannot fail */
214 (void) set_smach_state(dsmp, REBINDING);
215
216 /*
217 * If there isn't an async event pending, or if we can cancel the one
218 * that's there, then try to rebind by sending an extension request.
219 * If that fails, we'll clean up when the lease expires.
220 */
221 if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
222 !dhcp_extending(dsmp)) {
223 if (monosec() + RETRY_DELAY < expiremax) {
224 /*
225 * Try again in RETRY_DELAY seconds; user command
226 * should be gone.
227 */
228 init_timer(&dlp->dl_t2, RETRY_DELAY);
229 (void) set_smach_state(dsmp, oldstate);
230 if (!schedule_lease_timer(dlp, &dlp->dl_t2,
231 dhcp_rebind)) {
232 dhcpmsg(MSG_INFO, "dhcp_rebind: unable to "
233 "reschedule rebind around user command on "
234 "%s; lease may expire", dsmp->dsm_name);
235 }
236 } else {
237 dhcpmsg(MSG_WARNING, "dhcp_rebind: user busy on %s; "
238 "will expire", dsmp->dsm_name);
239 }
240 }
241 release_lease(dlp);
242 }
243
244 /*
245 * dhcp_finish_expire(): finish expiration of a lease after the user script
246 * runs. If this is the last lease, then restart DHCP.
247 * The caller has a reference to the LIF, which will be
248 * dropped.
249 *
250 * input: dhcp_smach_t *: the state machine to be restarted
251 * void *: logical interface that has expired
252 * output: int: always 1
253 */
254
255 static int
dhcp_finish_expire(dhcp_smach_t * dsmp,void * arg)256 dhcp_finish_expire(dhcp_smach_t *dsmp, void *arg)
257 {
258 dhcp_lif_t *lif = arg;
259 dhcp_lease_t *dlp;
260
261 dhcpmsg(MSG_DEBUG, "lease expired on %s; removing", lif->lif_name);
262
263 dlp = lif->lif_lease;
264 unplumb_lif(lif);
265 if (dlp->dl_nlifs == 0)
266 remove_lease(dlp);
267 release_lif(lif);
268
269 /* If some valid leases remain, then drive on */
270 if (dsmp->dsm_leases != NULL) {
271 dhcpmsg(MSG_DEBUG,
272 "dhcp_finish_expire: some leases remain on %s",
273 dsmp->dsm_name);
274 return (1);
275 }
276
277 (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
278
279 dhcpmsg(MSG_INFO, "last lease expired on %s -- restarting DHCP",
280 dsmp->dsm_name);
281
282 /*
283 * in the case where the lease is less than DHCP_REBIND_MIN
284 * seconds, we will never enter dhcp_renew() and thus the packet
285 * counters will not be reset. in that case, reset them here.
286 */
287
288 if (dsmp->dsm_state == BOUND) {
289 dsmp->dsm_bad_offers = 0;
290 dsmp->dsm_sent = 0;
291 dsmp->dsm_received = 0;
292 }
293
294 deprecate_leases(dsmp);
295
296 /* reset_smach() in dhcp_selecting() will clean up any leftover state */
297 dhcp_selecting(dsmp);
298
299 return (1);
300 }
301
302 /*
303 * dhcp_deprecate(): deprecates an address on a given logical interface when
304 * the preferred lifetime expires.
305 *
306 * input: iu_tq_t *: unused
307 * void *: the logical interface whose lease is expiring
308 * output: void
309 */
310
311 /* ARGSUSED */
312 void
dhcp_deprecate(iu_tq_t * tqp,void * arg)313 dhcp_deprecate(iu_tq_t *tqp, void *arg)
314 {
315 dhcp_lif_t *lif = arg;
316
317 set_lif_deprecated(lif);
318 release_lif(lif);
319 }
320
321 /*
322 * dhcp_expire(): expires a lease on a given logical interface and, if there
323 * are no more leases, restarts DHCP.
324 *
325 * input: iu_tq_t *: unused
326 * void *: the logical interface whose lease has expired
327 * output: void
328 */
329
330 /* ARGSUSED */
331 void
dhcp_expire(iu_tq_t * tqp,void * arg)332 dhcp_expire(iu_tq_t *tqp, void *arg)
333 {
334 dhcp_lif_t *lif = arg;
335 dhcp_smach_t *dsmp;
336 const char *event;
337
338 dhcpmsg(MSG_VERBOSE, "dhcp_expire: lease timer expired on %s",
339 lif->lif_name);
340
341 lif->lif_expire.dt_id = -1;
342 if (lif->lif_lease == NULL) {
343 release_lif(lif);
344 return;
345 }
346
347 set_lif_deprecated(lif);
348
349 dsmp = lif->lif_lease->dl_smach;
350
351 if (!async_cancel(dsmp)) {
352
353 dhcpmsg(MSG_WARNING,
354 "dhcp_expire: cannot cancel current asynchronous command "
355 "on %s", dsmp->dsm_name);
356
357 /*
358 * Try to schedule ourselves for callback. We're really
359 * situation-critical here; there's not much hope for us if
360 * this fails.
361 */
362 init_timer(&lif->lif_expire, DHCP_EXPIRE_WAIT);
363 if (schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire))
364 return;
365
366 dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule dhcp_expire "
367 "to get called back, proceeding...");
368 }
369
370 if (!async_start(dsmp, DHCP_START, B_FALSE))
371 dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous "
372 "transaction on %s, continuing...", dsmp->dsm_name);
373
374 /*
375 * Determine if this state machine has any non-expired LIFs left in it.
376 * If it doesn't, then this is an "expire" event. Otherwise, if some
377 * valid leases remain, it's a "loss" event. The SOMEEXP case can
378 * occur only with DHCPv6.
379 */
380 if (expired_lif_state(dsmp) == DHCP_EXP_SOMEEXP)
381 event = EVENT_LOSS6;
382 else if (dsmp->dsm_isv6)
383 event = EVENT_EXPIRE6;
384 else
385 event = EVENT_EXPIRE;
386
387 /*
388 * just march on if this fails; at worst someone will be able
389 * to async_start() while we're actually busy with our own
390 * asynchronous transaction. better than not having a lease.
391 */
392
393 (void) script_start(dsmp, event, dhcp_finish_expire, lif, NULL);
394 }
395
396 /*
397 * dhcp_extending(): sends a REQUEST (IPv4 DHCP) or Rebind/Renew (DHCPv6) to
398 * extend a lease on a given state machine
399 *
400 * input: dhcp_smach_t *: the state machine to send the message from
401 * output: boolean_t: B_TRUE if the extension request was sent
402 */
403
404 boolean_t
dhcp_extending(dhcp_smach_t * dsmp)405 dhcp_extending(dhcp_smach_t *dsmp)
406 {
407 dhcp_pkt_t *dpkt;
408
409 stop_pkt_retransmission(dsmp);
410
411 /*
412 * We change state here because this function is also called when
413 * adopting a lease and on demand by the user.
414 */
415 if (dsmp->dsm_state == BOUND) {
416 dsmp->dsm_neg_hrtime = gethrtime();
417 dsmp->dsm_bad_offers = 0;
418 dsmp->dsm_sent = 0;
419 dsmp->dsm_received = 0;
420 /* Bound->renew can't fail */
421 (void) set_smach_state(dsmp, RENEWING);
422 }
423
424 dhcpmsg(MSG_DEBUG, "dhcp_extending: sending request on %s",
425 dsmp->dsm_name);
426
427 if (dsmp->dsm_isv6) {
428 dhcp_lease_t *dlp;
429 dhcp_lif_t *lif;
430 uint_t nlifs;
431 uint_t irt, mrt;
432
433 /*
434 * Start constructing the Renew/Rebind message. Only Renew has
435 * a server ID, as we still think our server might be
436 * reachable.
437 */
438 if (dsmp->dsm_state == RENEWING) {
439 dpkt = init_pkt(dsmp, DHCPV6_MSG_RENEW);
440 (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
441 dsmp->dsm_serverid, dsmp->dsm_serveridlen);
442 irt = DHCPV6_REN_TIMEOUT;
443 mrt = DHCPV6_REN_MAX_RT;
444 } else {
445 dpkt = init_pkt(dsmp, DHCPV6_MSG_REBIND);
446 irt = DHCPV6_REB_TIMEOUT;
447 mrt = DHCPV6_REB_MAX_RT;
448 }
449
450 /*
451 * Loop over the leases, and add an IA_NA for each and an
452 * IAADDR for each address.
453 */
454 for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
455 lif = dlp->dl_lifs;
456 for (nlifs = dlp->dl_nlifs; nlifs > 0;
457 nlifs--, lif = lif->lif_next) {
458 (void) add_pkt_lif(dpkt, lif,
459 DHCPV6_STAT_SUCCESS, NULL);
460 }
461 }
462
463 /* Add required Option Request option */
464 (void) add_pkt_prl(dpkt, dsmp);
465
466 return (send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
467 stop_extending, irt, mrt));
468 } else {
469 dhcp_lif_t *lif = dsmp->dsm_lif;
470 ipaddr_t server;
471
472 /* assemble the DHCPREQUEST message. */
473 dpkt = init_pkt(dsmp, REQUEST);
474 dpkt->pkt->ciaddr.s_addr = lif->lif_addr;
475
476 /*
477 * The max dhcp message size option is set to the interface
478 * max, minus the size of the udp and ip headers.
479 */
480 (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
481 htons(lif->lif_max - sizeof (struct udpiphdr)));
482 (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
483
484 if (class_id_len != 0) {
485 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
486 class_id_len);
487 }
488 (void) add_pkt_prl(dpkt, dsmp);
489 /*
490 * dsm_reqhost was set for this state machine in
491 * dhcp_selecting() if the REQUEST_HOSTNAME option was set and
492 * a host name was found.
493 */
494 if (!dhcp_add_fqdn_opt(dpkt, dsmp) &&
495 dsmp->dsm_reqhost != NULL) {
496 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
497 strlen(dsmp->dsm_reqhost));
498 }
499 (void) add_pkt_opt(dpkt, CD_END, NULL, 0);
500
501 IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server);
502 return (send_pkt(dsmp, dpkt, server, stop_extending));
503 }
504 }
505
506 /*
507 * stop_extending(): decides when to stop retransmitting v4 REQUEST or v6
508 * Renew/Rebind messages. If we're renewing, then stop if
509 * T2 is soon approaching.
510 *
511 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from
512 * unsigned int: the number of REQUESTs sent so far
513 * output: boolean_t: B_TRUE if retransmissions should stop
514 */
515
516 /* ARGSUSED */
517 static boolean_t
stop_extending(dhcp_smach_t * dsmp,unsigned int n_requests)518 stop_extending(dhcp_smach_t *dsmp, unsigned int n_requests)
519 {
520 dhcp_lease_t *dlp;
521
522 /*
523 * If we're renewing and rebind time is soon approaching, then don't
524 * schedule
525 */
526 if (dsmp->dsm_state == RENEWING) {
527 monosec_t t2;
528
529 t2 = 0;
530 for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
531 if (dlp->dl_t2.dt_start > t2)
532 t2 = dlp->dl_t2.dt_start;
533 }
534 t2 += dsmp->dsm_curstart_monosec;
535 if (monosec() + TOO_CLOSE >= t2) {
536 dhcpmsg(MSG_DEBUG, "stop_extending: %spast T2 on %s",
537 monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
538 return (B_TRUE);
539 }
540 }
541
542 /*
543 * Note that returning B_TRUE cancels both this transmission and the
544 * one that would occur at dsm_send_timeout, and that for v4 we cut the
545 * time in half for each retransmission. Thus we check here against
546 * half of the minimum.
547 */
548 if (!dsmp->dsm_isv6 &&
549 dsmp->dsm_send_timeout < DHCP_REBIND_MIN * MILLISEC / 2) {
550 dhcpmsg(MSG_DEBUG, "stop_extending: next retry would be in "
551 "%d.%03d; stopping", dsmp->dsm_send_timeout / MILLISEC,
552 dsmp->dsm_send_timeout % MILLISEC);
553 return (B_TRUE);
554 }
555
556 /* Otherwise, w stop only when the next timer (rebind, expire) fires */
557 return (B_FALSE);
558 }
559