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 
43 static 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 
54 void
send_declines(dhcp_smach_t * dsmp)55 send_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 
136 int
dhcp_release(dhcp_smach_t * dsmp,void * arg)137 dhcp_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 */
226 int
dhcp_drop(dhcp_smach_t * dsmp,void * arg)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 			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 
260 static boolean_t
stop_release_decline(dhcp_smach_t * dsmp,unsigned int n_requests)261 stop_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