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
58static 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 */
80void
81dhcp_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 */
153void
154dhcp_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
255static int
256dhcp_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 */
312void
313dhcp_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 */
331void
332dhcp_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
404boolean_t
405dhcp_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 */
517static boolean_t
518stop_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