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 *
24 * DECLINE/RELEASE configuration functionality for the DHCP client.
25 */
26
27#include <sys/types.h>
28#include <unistd.h>
29#include <string.h>
30#include <netinet/in.h>
31#include <net/if.h>
32#include <netinet/dhcp.h>
33#include <netinet/dhcp6.h>
34#include <dhcpmsg.h>
35#include <dhcp_hostconf.h>
36#include <dhcpagent_util.h>
37
38#include "agent.h"
39#include "packet.h"
40#include "interface.h"
41#include "states.h"
42
43static boolean_t stop_release_decline(dhcp_smach_t *, unsigned int);
44
45/*
46 * send_declines(): sends a DECLINE message (broadcasted for IPv4) to the
47 *		    server to indicate a problem with the offered addresses.
48 *		    The failing addresses are removed from the leases.
49 *
50 *   input: dhcp_smach_t *: the state machine sending DECLINE
51 *  output: void
52 */
53
54void
55send_declines(dhcp_smach_t *dsmp)
56{
57	dhcp_pkt_t	*dpkt;
58	dhcp_lease_t	*dlp, *dlpn;
59	uint_t		nlifs;
60	dhcp_lif_t	*lif, *lifn;
61	boolean_t	got_one;
62
63	/*
64	 * Create an empty DECLINE message.  We'll stuff the information into
65	 * this message as we find it.
66	 */
67	if (dsmp->dsm_isv6) {
68		if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_DECLINE)) == NULL)
69			return;
70		(void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
71		    dsmp->dsm_serverid, dsmp->dsm_serveridlen);
72	} else {
73		ipaddr_t serverip;
74
75		/*
76		 * If this ack is from BOOTP, then there's no way to send a
77		 * decline.  Note that since we haven't bound yet, we can't
78		 * just check the BOOTP flag.
79		 */
80		if (dsmp->dsm_ack->opts[CD_DHCP_TYPE] == NULL)
81			return;
82
83		if ((dpkt = init_pkt(dsmp, DECLINE)) == NULL)
84			return;
85		IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip);
86		(void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip);
87	}
88
89	/*
90	 * Loop over the leases, looking for ones with now-broken LIFs.  Add
91	 * each one found to the DECLINE message, and remove it from the list.
92	 * Also remove any completely declined leases.
93	 */
94	got_one = B_FALSE;
95	for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) {
96		dlpn = dlp->dl_next;
97		lif = dlp->dl_lifs;
98		for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lifn) {
99			lifn = lif->lif_next;
100			if (lif->lif_declined != NULL) {
101				(void) add_pkt_lif(dpkt, lif,
102				    DHCPV6_STAT_UNSPECFAIL, lif->lif_declined);
103				unplumb_lif(lif);
104				got_one = B_TRUE;
105			}
106		}
107		if (dlp->dl_nlifs == 0)
108			remove_lease(dlp);
109	}
110
111	if (!got_one)
112		return;
113
114	(void) set_smach_state(dsmp, DECLINING);
115
116	if (dsmp->dsm_isv6) {
117		(void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
118		    stop_release_decline, DHCPV6_DEC_TIMEOUT, 0);
119	} else {
120		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
121
122		(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), NULL);
123	}
124}
125
126/*
127 * dhcp_release(): sends a RELEASE message to a DHCP server and removes
128 *		   the all interfaces for the given state machine from DHCP
129 *		   control.  Called back by script handler.
130 *
131 *   input: dhcp_smach_t *: the state machine to send the RELEASE on and remove
132 *	    void *: an optional text explanation to send with the message
133 *  output: int: 1 on success, 0 on failure
134 */
135
136int
137dhcp_release(dhcp_smach_t *dsmp, void *arg)
138{
139	const char	*msg = arg;
140	dhcp_pkt_t	*dpkt;
141	dhcp_lease_t	*dlp;
142	dhcp_lif_t	*lif;
143	ipaddr_t	serverip;
144	uint_t		nlifs;
145
146	if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) ||
147	    !check_cmd_allowed(dsmp->dsm_state, DHCP_RELEASE)) {
148		ipc_action_finish(dsmp, DHCP_IPC_E_INT);
149		return (0);
150	}
151
152	dhcpmsg(MSG_INFO, "releasing leases for state machine %s",
153	    dsmp->dsm_name);
154	(void) set_smach_state(dsmp, RELEASING);
155
156	(void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
157
158	if (dsmp->dsm_isv6) {
159		dpkt = init_pkt(dsmp, DHCPV6_MSG_RELEASE);
160		(void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
161		    dsmp->dsm_serverid, dsmp->dsm_serveridlen);
162
163		for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
164			lif = dlp->dl_lifs;
165			for (nlifs = dlp->dl_nlifs; nlifs > 0;
166			    nlifs--, lif = lif->lif_next) {
167				(void) add_pkt_lif(dpkt, lif,
168				    DHCPV6_STAT_SUCCESS, NULL);
169			}
170		}
171
172		/*
173		 * Must kill off the leases before attempting to tell the
174		 * server.
175		 */
176		deprecate_leases(dsmp);
177
178		/*
179		 * For DHCPv6, this is a transaction, rather than just a
180		 * one-shot message.  When this transaction is done, we'll
181		 * finish the invoking async operation.
182		 */
183		(void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
184		    stop_release_decline, DHCPV6_REL_TIMEOUT, 0);
185	} else {
186		if ((dlp = dsmp->dsm_leases) != NULL && dlp->dl_nlifs > 0) {
187			dpkt = init_pkt(dsmp, RELEASE);
188			if (msg != NULL) {
189				(void) add_pkt_opt(dpkt, CD_MESSAGE, msg,
190				    strlen(msg) + 1);
191			}
192			lif = dlp->dl_lifs;
193			(void) add_pkt_lif(dpkt, dlp->dl_lifs, 0, NULL);
194
195			IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip);
196			(void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip);
197			(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
198			(void) send_pkt(dsmp, dpkt, serverip, NULL);
199		}
200
201		/*
202		 * XXX this totally sucks, but since udp is best-effort,
203		 * without this delay, there's a good chance that the packet
204		 * that we just enqueued for sending will get pitched
205		 * when we canonize the interface through remove_smach.
206		 */
207
208		(void) usleep(500);
209		deprecate_leases(dsmp);
210
211		finished_smach(dsmp, DHCP_IPC_SUCCESS);
212	}
213	return (1);
214}
215
216/*
217 * dhcp_drop(): drops the interface from DHCP control; callback from script
218 *		handler
219 *
220 *   input: dhcp_smach_t *: the state machine dropping leases
221 *	    void *: unused
222 *  output: int: always 1
223 */
224
225/* ARGSUSED1 */
226int
227dhcp_drop(dhcp_smach_t *dsmp, void *arg)
228{
229	dhcpmsg(MSG_INFO, "dropping leases for state machine %s",
230	    dsmp->dsm_name);
231
232	if (dsmp->dsm_state == PRE_BOUND || dsmp->dsm_state == BOUND ||
233	    dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) {
234		if (dsmp->dsm_dflags & DHCP_IF_BOOTP) {
235			dhcpmsg(MSG_INFO,
236			    "used bootp; not writing lease file for %s",
237			    dsmp->dsm_name);
238		} else {
239			write_lease_to_hostconf(dsmp);
240		}
241	} else {
242		dhcpmsg(MSG_DEBUG, "%s in state %s; not saving lease",
243		    dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state));
244	}
245	deprecate_leases(dsmp);
246	finished_smach(dsmp, DHCP_IPC_SUCCESS);
247	return (1);
248}
249
250/*
251 * stop_release_decline(): decides when to stop retransmitting RELEASE/DECLINE
252 *			   messages for DHCPv6.  When we stop, if there are no
253 *			   more leases left, then restart the state machine.
254 *
255 *   input: dhcp_smach_t *: the state machine messages are being sent from
256 *	    unsigned int: the number of messages sent so far
257 *  output: boolean_t: B_TRUE if retransmissions should stop
258 */
259
260static boolean_t
261stop_release_decline(dhcp_smach_t *dsmp, unsigned int n_requests)
262{
263	if (dsmp->dsm_state == RELEASING) {
264		if (n_requests >= DHCPV6_REL_MAX_RC) {
265			dhcpmsg(MSG_INFO, "no Reply to Release, finishing "
266			    "transaction on %s", dsmp->dsm_name);
267			finished_smach(dsmp, DHCP_IPC_SUCCESS);
268			return (B_TRUE);
269		} else {
270			return (B_FALSE);
271		}
272	} else {
273		if (n_requests >= DHCPV6_DEC_MAX_RC) {
274			dhcpmsg(MSG_INFO, "no Reply to Decline on %s",
275			    dsmp->dsm_name);
276
277			if (dsmp->dsm_leases == NULL) {
278				dhcpmsg(MSG_VERBOSE, "stop_release_decline: "
279				    "%s has no leases left", dsmp->dsm_name);
280				dhcp_restart(dsmp);
281			}
282			return (B_TRUE);
283		} else {
284			return (B_FALSE);
285		}
286	}
287}
288