xref: /illumos-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/release.c (revision cb6207858a9fcc2feaee22e626912fba281ac969)
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 
40 #include "agent.h"
41 #include "packet.h"
42 #include "interface.h"
43 #include "states.h"
44 
45 static boolean_t stop_release_decline(dhcp_smach_t *, unsigned int);
46 
47 /*
48  * send_declines(): sends a DECLINE message (broadcasted for IPv4) to the
49  *		    server to indicate a problem with the offered addresses.
50  *		    The failing addresses are removed from the leases.
51  *
52  *   input: dhcp_smach_t *: the state machine sending DECLINE
53  *  output: void
54  */
55 
56 void
57 send_declines(dhcp_smach_t *dsmp)
58 {
59 	dhcp_pkt_t	*dpkt;
60 	dhcp_lease_t	*dlp, *dlpn;
61 	uint_t		nlifs;
62 	dhcp_lif_t	*lif, *lifn;
63 	boolean_t	got_one;
64 
65 	/*
66 	 * Create an empty DECLINE message.  We'll stuff the information into
67 	 * this message as we find it.
68 	 */
69 	if (dsmp->dsm_isv6) {
70 		if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_DECLINE)) == NULL)
71 			return;
72 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
73 		    dsmp->dsm_serverid, dsmp->dsm_serveridlen);
74 	} else {
75 		ipaddr_t serverip;
76 
77 		/*
78 		 * If this ack is from BOOTP, then there's no way to send a
79 		 * decline.  Note that since we haven't bound yet, we can't
80 		 * just check the BOOTP flag.
81 		 */
82 		if (dsmp->dsm_ack->opts[CD_DHCP_TYPE] == NULL)
83 			return;
84 
85 		if ((dpkt = init_pkt(dsmp, DECLINE)) == NULL)
86 			return;
87 		IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip);
88 		(void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip);
89 	}
90 
91 	/*
92 	 * Loop over the leases, looking for ones with now-broken LIFs.  Add
93 	 * each one found to the DECLINE message, and remove it from the list.
94 	 * Also remove any completely declined leases.
95 	 */
96 	got_one = B_FALSE;
97 	for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) {
98 		dlpn = dlp->dl_next;
99 		lif = dlp->dl_lifs;
100 		for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lifn) {
101 			lifn = lif->lif_next;
102 			if (lif->lif_declined != NULL) {
103 				(void) add_pkt_lif(dpkt, lif,
104 				    DHCPV6_STAT_UNSPECFAIL, lif->lif_declined);
105 				unplumb_lif(lif);
106 				got_one = B_TRUE;
107 			}
108 		}
109 		if (dlp->dl_nlifs == 0)
110 			remove_lease(dlp);
111 	}
112 
113 	if (!got_one)
114 		return;
115 
116 	(void) set_smach_state(dsmp, DECLINING);
117 
118 	if (dsmp->dsm_isv6) {
119 		(void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
120 		    stop_release_decline, DHCPV6_DEC_TIMEOUT, 0);
121 	} else {
122 		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
123 
124 		(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), NULL);
125 	}
126 }
127 
128 /*
129  * dhcp_release(): sends a RELEASE message to a DHCP server and removes
130  *		   the all interfaces for the given state machine from DHCP
131  *		   control.  Called back by script handler.
132  *
133  *   input: dhcp_smach_t *: the state machine to send the RELEASE on and remove
134  *	    void *: an optional text explanation to send with the message
135  *  output: int: 1 on success, 0 on failure
136  */
137 
138 int
139 dhcp_release(dhcp_smach_t *dsmp, void *arg)
140 {
141 	const char	*msg = arg;
142 	dhcp_pkt_t	*dpkt;
143 	dhcp_lease_t	*dlp;
144 	dhcp_lif_t	*lif;
145 	ipaddr_t	serverip;
146 	uint_t		nlifs;
147 
148 	if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) ||
149 	    !check_cmd_allowed(dsmp->dsm_state, DHCP_RELEASE)) {
150 		ipc_action_finish(dsmp, DHCP_IPC_E_INT);
151 		return (0);
152 	}
153 
154 	dhcpmsg(MSG_INFO, "releasing leases for state machine %s",
155 	    dsmp->dsm_name);
156 	(void) set_smach_state(dsmp, RELEASING);
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 */
226 int
227 dhcp_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 			PKT_LIST *plp[2];
240 
241 			plp[0] = dsmp->dsm_ack;
242 			plp[1] = dsmp->dsm_orig_ack;
243 			if (write_hostconf(dsmp->dsm_name, plp, 2,
244 			    monosec_to_time(dsmp->dsm_curstart_monosec),
245 			    dsmp->dsm_isv6) == -1) {
246 				dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
247 				    "not use cached configuration)",
248 				    ifname_to_hostconf(dsmp->dsm_name,
249 				    dsmp->dsm_isv6));
250 			}
251 		}
252 	}
253 	deprecate_leases(dsmp);
254 	finished_smach(dsmp, DHCP_IPC_SUCCESS);
255 	return (1);
256 }
257 
258 /*
259  * stop_release_decline(): decides when to stop retransmitting RELEASE/DECLINE
260  *			   messages for DHCPv6.  When we stop, if there are no
261  *			   more leases left, then restart the state machine.
262  *
263  *   input: dhcp_smach_t *: the state machine messages are being sent from
264  *	    unsigned int: the number of messages sent so far
265  *  output: boolean_t: B_TRUE if retransmissions should stop
266  */
267 
268 static boolean_t
269 stop_release_decline(dhcp_smach_t *dsmp, unsigned int n_requests)
270 {
271 	if (dsmp->dsm_state == RELEASING) {
272 		if (n_requests >= DHCPV6_REL_MAX_RC) {
273 			dhcpmsg(MSG_INFO, "no Reply to Release, finishing "
274 			    "transaction on %s", dsmp->dsm_name);
275 			finished_smach(dsmp, DHCP_IPC_SUCCESS);
276 			return (B_TRUE);
277 		} else {
278 			return (B_FALSE);
279 		}
280 	} else {
281 		if (n_requests >= DHCPV6_DEC_MAX_RC) {
282 			dhcpmsg(MSG_INFO, "no Reply to Decline on %s",
283 			    dsmp->dsm_name);
284 
285 			if (dsmp->dsm_leases == NULL) {
286 				dhcpmsg(MSG_VERBOSE, "stop_release_decline: "
287 				    "%s has no leases left; restarting",
288 				    dsmp->dsm_name);
289 				dhcp_restart(dsmp);
290 			}
291 			return (B_TRUE);
292 		} else {
293 			return (B_FALSE);
294 		}
295 	}
296 }
297