boolean_t set_start_timer(dhcp_smach_t *dsmp) { if (dsmp->dsm_start_timer != -1) return (B_TRUE); dsmp->dsm_start_timer = iu_schedule_timer_ms(tq, lrand48() % DHCP_SELECT_WAIT, dhcp_start, dsmp); if (dsmp->dsm_start_timer == -1) return (B_FALSE); hold_smach(dsmp); return (B_TRUE); }
/* ARGSUSED */ static void ipc_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { dhcp_ipc_request_t *request; struct ifslist *ifsp, *primary_ifsp; int error, is_priv = (int)arg; PKT_LIST *plp[2]; dhcp_ipc_type_t cmd; (void) iu_unregister_event(eh, id, NULL); if (dhcp_ipc_recv_request(fd, &request, DHCP_IPC_REQUEST_WAIT) != 0) { dhcpmsg(MSG_ERROR, "ipc_event: dhcp_ipc_recv_request failed"); (void) dhcp_ipc_close(fd); return; } cmd = DHCP_IPC_CMD(request->message_type); if (cmd >= DHCP_NIPC) { send_error_reply(request, DHCP_IPC_E_CMD_UNKNOWN, &fd); return; } /* return EPERM for any of the privileged actions */ if (!is_priv) { switch (cmd) { case DHCP_STATUS: case DHCP_PING: case DHCP_GET_TAG: break; default: dhcpmsg(MSG_WARNING, "ipc_event: privileged ipc " "command (%i) attempted on %s", cmd, request->ifname); send_error_reply(request, DHCP_IPC_E_PERM, &fd); return; } } /* * try to locate the ifs associated with this command. if the * command is DHCP_START or DHCP_INFORM, then if there isn't * an ifs already, make one (there may already be one from a * previous failed attempt to START or INFORM). otherwise, * verify the interface is still valid. */ ifsp = lookup_ifs(request->ifname); switch (cmd) { case DHCP_START: /* FALLTHRU */ case DHCP_INFORM: /* * it's possible that the interface already exists, but * has been abandoned. usually in those cases we should * return DHCP_IPC_E_UNKIF, but that makes little sense * in the case of "start" or "inform", so just ignore * the abandoned interface and start over anew. */ if (ifsp != NULL && verify_ifs(ifsp) == 0) ifsp = NULL; /* * as part of initializing the ifs, insert_ifs() * creates a DLPI stream at ifsp->if_dlpi_fd. */ if (ifsp == NULL) { ifsp = insert_ifs(request->ifname, B_FALSE, &error); if (ifsp == NULL) { send_error_reply(request, error, &fd); return; } } break; default: if (ifsp == NULL) { if (request->ifname[0] == '\0') error = DHCP_IPC_E_NOPRIMARY; else error = DHCP_IPC_E_UNKIF; send_error_reply(request, error, &fd); return; } break; } if (verify_ifs(ifsp) == 0) { send_error_reply(request, DHCP_IPC_E_UNKIF, &fd); return; } if (ifsp->if_dflags & DHCP_IF_BOOTP) { switch (cmd) { case DHCP_EXTEND: case DHCP_RELEASE: case DHCP_INFORM: send_error_reply(request, DHCP_IPC_E_BOOTP, &fd); return; default: break; } } /* * verify that the interface is in a state which will allow the * command. we do this up front so that we can return an error * *before* needlessly cancelling an in-progress transaction. */ if (!ipc_cmd_allowed[ifsp->if_state][cmd]) { send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd); return; } if ((request->message_type & DHCP_PRIMARY) && is_priv) { if ((primary_ifsp = lookup_ifs("")) != NULL) primary_ifsp->if_dflags &= ~DHCP_IF_PRIMARY; ifsp->if_dflags |= DHCP_IF_PRIMARY; } /* * current design dictates that there can be only one * outstanding transaction per interface -- this simplifies * the code considerably and also fits well with RFC2131. * it is worth classifying the different DHCP commands into * synchronous (those which we will handle now and be done * with) and asynchronous (those which require transactions * and will be completed at an indeterminate time in the * future): * * DROP: removes the agent's management of an interface. * asynchronous as the script program may be invoked. * * PING: checks to see if the agent controls an interface. * synchronous, since no packets need to be sent * to the DHCP server. * * STATUS: returns information about the an interface. * synchronous, since no packets need to be sent * to the DHCP server. * * RELEASE: releases the agent's management of an interface * and brings the interface down. asynchronous as * the script program may be invoked. * * EXTEND: renews a lease. asynchronous, since the agent * needs to wait for an ACK, etc. * * START: starts DHCP on an interface. asynchronous since * the agent needs to wait for OFFERs, ACKs, etc. * * INFORM: obtains configuration parameters for an externally * configured interface. asynchronous, since the * agent needs to wait for an ACK. * * notice that EXTEND, INFORM, START, DROP and RELEASE are * asynchronous. notice also that asynchronous commands may * occur from within the agent -- for instance, the agent * will need to do implicit EXTENDs to extend the lease. in * order to make the code simpler, the following rules apply * for asynchronous commands: * * there can only be one asynchronous command at a time per * interface. the current asynchronous command is managed by * the async_* api: async_start(), async_finish(), * async_timeout(), async_cancel(), and async_pending(). * async_start() starts management of a new asynchronous * command on an interface, which should only be done after * async_pending() is called to check that there are no * pending asynchronous commands on that interface. when the * command is completed, async_finish() should be called. all * asynchronous commands have an associated timer, which calls * async_timeout() when it times out. if async_timeout() * decides that the asynchronous command should be cancelled * (see below), it calls async_cancel() to attempt * cancellation. * * asynchronous commands started by a user command have an * associated ipc_action which provides the agent with * information for how to get in touch with the user command * when the action completes. these ipc_action records also * have an associated timeout which may be infinite. * ipc_action_start() should be called when starting an * asynchronous command requested by a user, which sets up the * timer and keeps track of the ipc information (file * descriptor, request type). when the asynchronous command * completes, ipc_action_finish() should be called to return a * command status code to the user and close the ipc * connection). if the command does not complete before the * timer fires, ipc_action_timeout() is called which closes * the ipc connection and returns DHCP_IPC_E_TIMEOUT to the * user. note that independent of ipc_action_timeout(), * ipc_action_finish() should be called. * * on a case-by-case basis, here is what happens (per interface): * * o when an asynchronous command is requested, then * async_pending() is called to see if there is already * an asynchronous event. if so, the command does not * proceed, and if there is an associated ipc_action, * the user command is sent DHCP_IPC_E_PEND. * * o otherwise, the the transaction is started with * async_start(). if the transaction is on behalf * of a user, ipc_action_start() is called to keep * track of the ipc information and set up the * ipc_action timer. * * o if the command completes normally and before a * timeout fires, then async_finish() is called. * if there was an associated ipc_action, * ipc_action_finish() is called to complete it. * * o if the command fails before a timeout fires, then * async_finish() is called, and the interface is * is returned to a known state based on the command. * if there was an associated ipc_action, * ipc_action_finish() is called to complete it. * * o if the ipc_action timer fires before command * completion, then DHCP_IPC_E_TIMEOUT is returned to * the user. however, the transaction continues to * be carried out asynchronously. * * o if async_timeout() fires before command completion, * then if the command was internal to the agent, it * is cancelled. otherwise, if it was a user command, * then if the user is still waiting for the command * to complete, the command continues and async_timeout() * is rescheduled. */ switch (cmd) { case DHCP_DROP: /* FALLTHRU */ case DHCP_RELEASE: /* FALLTHRU */ case DHCP_EXTEND: /* FALLTHRU */ case DHCP_INFORM: /* FALLTHRU */ case DHCP_START: /* * if shutdown request has been received, send back an error. */ if (shutdown_started) { send_error_reply(request, DHCP_IPC_E_OUTSTATE, &fd); return; } if (async_pending(ifsp)) { send_error_reply(request, DHCP_IPC_E_PEND, &fd); return; } if (ipc_action_start(ifsp, request, fd) == 0) { dhcpmsg(MSG_WARNING, "ipc_event: ipc_action_start " "failed for %s", ifsp->if_name); send_error_reply(request, DHCP_IPC_E_MEMORY, &fd); return; } if (async_start(ifsp, cmd, B_TRUE) == 0) { ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); return; } break; default: break; } switch (cmd) { case DHCP_DROP: (void) script_start(ifsp, EVENT_DROP, dhcp_drop, NULL, NULL); return; case DHCP_EXTEND: (void) dhcp_extending(ifsp); break; case DHCP_GET_TAG: { dhcp_optnum_t optnum; DHCP_OPT *opt = NULL; boolean_t did_alloc = B_FALSE; PKT_LIST *ack = ifsp->if_ack; /* * verify the request makes sense. */ if (request->data_type != DHCP_TYPE_OPTNUM || request->data_length != sizeof (dhcp_optnum_t)) { send_error_reply(request, DHCP_IPC_E_PROTO, &fd); return; } (void) memcpy(&optnum, request->buffer, sizeof (dhcp_optnum_t)); load_option: switch (optnum.category) { case DSYM_SITE: /* FALLTHRU */ case DSYM_STANDARD: if (optnum.code <= DHCP_LAST_OPT) opt = ack->opts[optnum.code]; break; case DSYM_VENDOR: /* * the test against VS_OPTION_START is broken up into * two tests to avoid compiler warnings under intel. */ if ((optnum.code > VS_OPTION_START || optnum.code == VS_OPTION_START) && optnum.code <= VS_OPTION_END) opt = ack->vs[optnum.code]; break; case DSYM_FIELD: if (optnum.code + optnum.size > sizeof (PKT)) break; /* + 2 to account for option code and length byte */ opt = malloc(optnum.size + 2); if (opt == NULL) { send_error_reply(request, DHCP_IPC_E_MEMORY, &fd); return; } did_alloc = B_TRUE; opt->len = optnum.size; opt->code = optnum.code; (void) memcpy(&opt->value, (caddr_t)ack->pkt + opt->code, opt->len); break; default: send_error_reply(request, DHCP_IPC_E_PROTO, &fd); return; } /* * return the option payload, if there was one. the "+ 2" * accounts for the option code number and length byte. */ if (opt != NULL) { send_data_reply(request, &fd, 0, DHCP_TYPE_OPTION, opt, opt->len + 2); if (did_alloc) free(opt); return; } else if (ack != ifsp->if_orig_ack) { /* * There wasn't any definition for the option in the * current ack, so now retry with the original ack if * the original ack is not the current ack. */ ack = ifsp->if_orig_ack; goto load_option; } /* * note that an "okay" response is returned either in * the case of an unknown option or a known option * with no payload. this is okay (for now) since * dhcpinfo checks whether an option is valid before * ever performing ipc with the agent. */ send_ok_reply(request, &fd); return; } case DHCP_INFORM: dhcp_inform(ifsp); /* next destination: dhcp_acknak() */ return; case DHCP_PING: if (ifsp->if_dflags & DHCP_IF_FAILED) send_error_reply(request, DHCP_IPC_E_FAILEDIF, &fd); else send_ok_reply(request, &fd); return; case DHCP_RELEASE: (void) script_start(ifsp, EVENT_RELEASE, dhcp_release, "Finished with lease.", NULL); return; case DHCP_START: (void) canonize_ifs(ifsp); /* * if we have a valid hostconf lying around, then jump * into INIT_REBOOT. if it fails, we'll end up going * through the whole selecting() procedure again. */ error = read_hostconf(ifsp->if_name, plp, 2); if (error != -1) { ifsp->if_orig_ack = ifsp->if_ack = plp[0]; if (error > 1) { /* * Return indicated we had more than one packet * second one is the original ack. Older * versions of the agent wrote only one ack * to the file, we now keep both the first * ack as well as the last one. */ ifsp->if_orig_ack = plp[1]; } dhcp_init_reboot(ifsp); /* next destination: dhcp_acknak() */ return; } /* * if not debugging, wait for a few seconds before * going into SELECTING. */ if (debug_level == 0) { if (iu_schedule_timer_ms(tq, lrand48() % DHCP_SELECT_WAIT, dhcp_start, ifsp) != -1) { hold_ifs(ifsp); /* next destination: dhcp_start() */ return; } } dhcp_selecting(ifsp); /* next destination: dhcp_requesting() */ return; case DHCP_STATUS: { dhcp_status_t status; status.if_began = monosec_to_time(ifsp->if_curstart_monosec); if (ifsp->if_lease == DHCP_PERM) { status.if_t1 = DHCP_PERM; status.if_t2 = DHCP_PERM; status.if_lease = DHCP_PERM; } else { status.if_t1 = status.if_began + ifsp->if_t1; status.if_t2 = status.if_began + ifsp->if_t2; status.if_lease = status.if_began + ifsp->if_lease; } status.version = DHCP_STATUS_VER; status.if_state = ifsp->if_state; status.if_dflags = ifsp->if_dflags; status.if_sent = ifsp->if_sent; status.if_recv = ifsp->if_received; status.if_bad_offers = ifsp->if_bad_offers; (void) strlcpy(status.if_name, ifsp->if_name, IFNAMSIZ); send_data_reply(request, &fd, 0, DHCP_TYPE_STATUS, &status, sizeof (dhcp_status_t)); return; } default: return; } }
iu_timer_id_t iu_schedule_timer(iu_tq_t *tq, uint32_t sec, iu_tq_callback_t *callback, void *arg) { return (iu_schedule_timer_ms(tq, sec * MILLISEC, callback, arg)); }