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  * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
25  */
26 
27 #include <stdlib.h>
28 #include <sys/types.h>
29 #include <dhcpmsg.h>
30 #include <dhcpagent_ipc.h>
31 
32 #include "agent.h"
33 #include "states.h"
34 #include "interface.h"
35 #include "ipc_action.h"
36 #include "util.h"
37 
38 static iu_tq_callback_t	ipc_action_timeout;
39 
40 /*
41  * ipc_action_init(): initializes the ipc_action structure
42  *
43  *   input: ipc_action_t *: the structure to initialize
44  *  output: void
45  */
46 
47 void
ipc_action_init(ipc_action_t * ia)48 ipc_action_init(ipc_action_t *ia)
49 {
50 	ia->ia_cmd = 0;
51 	ia->ia_fd = -1;
52 	ia->ia_tid = -1;
53 	ia->ia_eid = -1;
54 	ia->ia_request = NULL;
55 }
56 
57 /*
58  * ipc_action_start(): starts an ipc_action request on a DHCP state machine
59  *
60  *   input: dhcp_smach_t *: the state machine to start the action on
61  *	    ipc_action_t *: request structure
62  *  output: B_TRUE if the request is started successfully, B_FALSE otherwise
63  *	    original request is still valid on failure, consumed otherwise.
64  */
65 
66 boolean_t
ipc_action_start(dhcp_smach_t * dsmp,ipc_action_t * iareq)67 ipc_action_start(dhcp_smach_t *dsmp, ipc_action_t *iareq)
68 {
69 	struct ipc_action *ia = &dsmp->dsm_ia;
70 
71 	if (ia->ia_fd != -1 || ia->ia_tid != -1 || iareq->ia_fd == -1) {
72 		dhcpmsg(MSG_CRIT, "ipc_action_start: attempted restart on %s",
73 		    dsmp->dsm_name);
74 		return (B_FALSE);
75 	}
76 
77 	if (!async_cancel(dsmp)) {
78 		dhcpmsg(MSG_WARNING, "ipc_action_start: unable to cancel "
79 		    "action on %s", dsmp->dsm_name);
80 		return (B_FALSE);
81 	}
82 
83 	if (iareq->ia_request->timeout == DHCP_IPC_WAIT_DEFAULT)
84 		iareq->ia_request->timeout = DHCP_IPC_DEFAULT_WAIT;
85 
86 	if (iareq->ia_request->timeout == DHCP_IPC_WAIT_FOREVER) {
87 		iareq->ia_tid = -1;
88 	} else {
89 		iareq->ia_tid = iu_schedule_timer(tq,
90 		    iareq->ia_request->timeout, ipc_action_timeout, dsmp);
91 
92 		if (iareq->ia_tid == -1) {
93 			dhcpmsg(MSG_ERROR, "ipc_action_start: failed to set "
94 			    "timer for %s on %s",
95 			    dhcp_ipc_type_to_string(iareq->ia_cmd),
96 			    dsmp->dsm_name);
97 			return (B_FALSE);
98 		}
99 
100 		hold_smach(dsmp);
101 	}
102 
103 	*ia = *iareq;
104 
105 	/* We've taken ownership, so the input request is now invalid */
106 	ipc_action_init(iareq);
107 
108 	dhcpmsg(MSG_DEBUG, "ipc_action_start: started %s (command %d) on %s,"
109 	    " buffer length %u",
110 	    dhcp_ipc_type_to_string(ia->ia_cmd), ia->ia_cmd, dsmp->dsm_name,
111 	    ia->ia_request == NULL ? 0 : ia->ia_request->data_length);
112 
113 	dsmp->dsm_dflags |= DHCP_IF_BUSY;
114 
115 	/* This cannot fail due to the async_cancel above */
116 	(void) async_start(dsmp, ia->ia_cmd, B_TRUE);
117 
118 	return (B_TRUE);
119 }
120 
121 /*
122  * ipc_action_finish(): completes an ipc_action request on an interface
123  *
124  *   input: dhcp_smach_t *: the state machine to complete the action on
125  *	    int: the reason why the action finished (nonzero on error)
126  *  output: void
127  */
128 
129 void
ipc_action_finish(dhcp_smach_t * dsmp,int reason)130 ipc_action_finish(dhcp_smach_t *dsmp, int reason)
131 {
132 	struct ipc_action *ia = &dsmp->dsm_ia;
133 
134 	dsmp->dsm_dflags &= ~DHCP_IF_BUSY;
135 
136 	if (dsmp->dsm_ia.ia_fd == -1) {
137 		dhcpmsg(MSG_ERROR,
138 		    "ipc_action_finish: attempted to finish unknown action "
139 		    "on %s", dsmp->dsm_name);
140 		return;
141 	}
142 
143 	dhcpmsg(MSG_DEBUG,
144 	    "ipc_action_finish: finished %s (command %d) on %s: %d",
145 	    dhcp_ipc_type_to_string(ia->ia_cmd), (int)ia->ia_cmd,
146 	    dsmp->dsm_name, reason);
147 
148 	/*
149 	 * if we can't cancel this timer, we're really in the
150 	 * twilight zone.  however, as long as we don't drop the
151 	 * reference to the state machine, it shouldn't hurt us
152 	 */
153 
154 	if (dsmp->dsm_ia.ia_tid != -1 &&
155 	    iu_cancel_timer(tq, dsmp->dsm_ia.ia_tid, NULL) == 1) {
156 		dsmp->dsm_ia.ia_tid = -1;
157 		release_smach(dsmp);
158 	}
159 
160 	if (reason == 0)
161 		send_ok_reply(ia);
162 	else
163 		send_error_reply(ia, reason);
164 
165 	async_finish(dsmp);
166 }
167 
168 /*
169  * ipc_action_timeout(): times out an ipc_action on a state machine (the
170  *			 request continues asynchronously, however)
171  *
172  *   input: iu_tq_t *: unused
173  *	    void *: the dhcp_smach_t * the ipc_action was pending on
174  *  output: void
175  */
176 
177 /* ARGSUSED */
178 static void
ipc_action_timeout(iu_tq_t * tq,void * arg)179 ipc_action_timeout(iu_tq_t *tq, void *arg)
180 {
181 	dhcp_smach_t		*dsmp = arg;
182 	struct ipc_action	*ia = &dsmp->dsm_ia;
183 
184 	dsmp->dsm_dflags &= ~DHCP_IF_BUSY;
185 
186 	ia->ia_tid = -1;
187 
188 	dhcpmsg(MSG_VERBOSE, "ipc timeout waiting for agent to complete "
189 	    "%s (command %d) for %s", dhcp_ipc_type_to_string(ia->ia_cmd),
190 	    ia->ia_cmd, dsmp->dsm_name);
191 
192 	send_error_reply(ia, DHCP_IPC_E_TIMEOUT);
193 
194 	async_finish(dsmp);
195 	release_smach(dsmp);
196 }
197 
198 /*
199  * send_ok_reply(): sends an "ok" reply to a request and closes the ipc
200  *		    connection
201  *
202  *   input: ipc_action_t *: the request to reply to
203  *  output: void
204  *    note: the request is freed (thus the request must be on the heap).
205  */
206 
207 void
send_ok_reply(ipc_action_t * ia)208 send_ok_reply(ipc_action_t *ia)
209 {
210 	send_error_reply(ia, 0);
211 }
212 
213 /*
214  * send_error_reply(): sends an "error" reply to a request and closes the ipc
215  *		       connection
216  *
217  *   input: ipc_action_t *: the request to reply to
218  *	    int: the error to send back on the ipc connection
219  *  output: void
220  *    note: the request is freed (thus the request must be on the heap).
221  */
222 
223 void
send_error_reply(ipc_action_t * ia,int error)224 send_error_reply(ipc_action_t *ia, int error)
225 {
226 	send_data_reply(ia, error, DHCP_TYPE_NONE, NULL, 0);
227 }
228 
229 /*
230  * send_data_reply(): sends a reply to a request and closes the ipc connection
231  *
232  *   input: ipc_action_t *: the request to reply to
233  *	    int: the status to send back on the ipc connection (zero for
234  *		 success, DHCP_IPC_E_* otherwise).
235  *	    dhcp_data_type_t: the type of the payload in the reply
236  *	    const void *: the payload for the reply, or NULL if there is no
237  *			  payload
238  *	    size_t: the size of the payload
239  *  output: void
240  *    note: the request is freed (thus the request must be on the heap).
241  */
242 
243 void
send_data_reply(ipc_action_t * ia,int error,dhcp_data_type_t type,const void * buffer,size_t size)244 send_data_reply(ipc_action_t *ia, int error, dhcp_data_type_t type,
245     const void *buffer, size_t size)
246 {
247 	dhcp_ipc_reply_t	*reply;
248 	int retval;
249 
250 	if (ia->ia_fd == -1 || ia->ia_request == NULL)
251 		return;
252 
253 	reply = dhcp_ipc_alloc_reply(ia->ia_request, error, buffer, size,
254 	    type);
255 	if (reply == NULL) {
256 		dhcpmsg(MSG_ERR, "send_data_reply: cannot allocate reply");
257 
258 	} else if ((retval = dhcp_ipc_send_reply(ia->ia_fd, reply)) != 0) {
259 		dhcpmsg(MSG_ERROR, "send_data_reply: dhcp_ipc_send_reply: %s",
260 		    dhcp_ipc_strerror(retval));
261 	}
262 
263 	/*
264 	 * free the request since we've now used it to send our reply.
265 	 * we can also close the socket since the reply has been sent.
266 	 */
267 
268 	free(reply);
269 	free(ia->ia_request);
270 	if (ia->ia_eid != -1)
271 		(void) iu_unregister_event(eh, ia->ia_eid, NULL);
272 	(void) dhcp_ipc_close(ia->ia_fd);
273 	ia->ia_request = NULL;
274 	ia->ia_fd = -1;
275 	ia->ia_eid = -1;
276 }
277