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