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