boolean_t schedule_timer(dhcp_timer_t *dt, iu_tq_callback_t *cbfunc, void *arg) { if (dt->dt_id != -1) return (B_FALSE); dt->dt_id = iu_schedule_timer(tq, dt->dt_start, cbfunc, arg); return (dt->dt_id != -1); }
boolean_t ipc_action_start(dhcp_smach_t *dsmp, ipc_action_t *iareq) { struct ipc_action *ia = &dsmp->dsm_ia; if (ia->ia_fd != -1 || ia->ia_tid != -1 || iareq->ia_fd == -1) { dhcpmsg(MSG_CRIT, "ipc_action_start: attempted restart on %s", dsmp->dsm_name); return (B_FALSE); } if (!async_cancel(dsmp)) { dhcpmsg(MSG_WARNING, "ipc_action_start: unable to cancel " "action on %s", dsmp->dsm_name); return (B_FALSE); } if (iareq->ia_request->timeout == DHCP_IPC_WAIT_DEFAULT) iareq->ia_request->timeout = DHCP_IPC_DEFAULT_WAIT; if (iareq->ia_request->timeout == DHCP_IPC_WAIT_FOREVER) { iareq->ia_tid = -1; } else { iareq->ia_tid = iu_schedule_timer(tq, iareq->ia_request->timeout, ipc_action_timeout, dsmp); if (iareq->ia_tid == -1) { dhcpmsg(MSG_ERROR, "ipc_action_start: failed to set " "timer for %s on %s", dhcp_ipc_type_to_string(iareq->ia_cmd), dsmp->dsm_name); return (B_FALSE); } hold_smach(dsmp); } *ia = *iareq; /* We've taken ownership, so the input request is now invalid */ ipc_action_init(iareq); dhcpmsg(MSG_DEBUG, "ipc_action_start: started %s (command %d) on %s," " buffer length %u", dhcp_ipc_type_to_string(ia->ia_cmd), ia->ia_cmd, dsmp->dsm_name, ia->ia_request == NULL ? 0 : ia->ia_request->data_length); dsmp->dsm_dflags |= DHCP_IF_BUSY; /* This cannot fail due to the async_cancel above */ (void) async_start(dsmp, ia->ia_cmd, B_TRUE); return (B_TRUE); }
/* ARGSUSED */ void dhcp_expire(iu_tq_t *tqp, void *arg) { struct ifslist *ifsp = (struct ifslist *)arg; ifsp->if_timer[DHCP_LEASE_TIMER] = -1; if (check_ifs(ifsp) == 0) { (void) release_ifs(ifsp); return; } if (async_pending(ifsp)) if (async_cancel(ifsp) == 0) { dhcpmsg(MSG_WARNING, "dhcp_expire: cannot cancel " "current asynchronous command against %s", ifsp->if_name); /* * try to schedule ourselves for callback. * we're really situation critical here * there's not much hope for us if this fails. */ if (iu_schedule_timer(tq, DHCP_EXPIRE_WAIT, dhcp_expire, ifsp) != -1) { hold_ifs(ifsp); return; } dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule " "dhcp_expire to get called back, proceeding..."); } /* * just march on if this fails; at worst someone will be able * to async_start() while we're actually busy with our own * asynchronous transaction. better than not having a lease. */ if (async_start(ifsp, DHCP_START, B_FALSE) == 0) dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous " "transaction on %s, continuing...", ifsp->if_name); (void) script_start(ifsp, EVENT_EXPIRE, dhcp_restart_lease, NULL, NULL); }
int main(int argc, char **argv) { boolean_t is_daemon = B_TRUE; boolean_t is_verbose = B_FALSE; int ipc_fd; int c; struct rlimit rl; /* * -l is ignored for compatibility with old agent. */ while ((c = getopt(argc, argv, "vd:l:fa")) != EOF) { switch (c) { case 'a': do_adopt = B_TRUE; grandparent = getpid(); break; case 'd': debug_level = strtoul(optarg, NULL, 0); break; case 'f': is_daemon = B_FALSE; break; case 'v': is_verbose = B_TRUE; break; case '?': (void) fprintf(stderr, "usage: %s [-a] [-d n] [-f] [-v]" "\n", argv[0]); return (EXIT_FAILURE); default: break; } } (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if (geteuid() != 0) { dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level); dhcpmsg(MSG_ERROR, "must be super-user"); dhcpmsg_fini(); return (EXIT_FAILURE); } if (is_daemon && daemonize() == 0) { dhcpmsg_init(argv[0], B_FALSE, is_verbose, debug_level); dhcpmsg(MSG_ERR, "cannot become daemon, exiting"); dhcpmsg_fini(); return (EXIT_FAILURE); } dhcpmsg_init(argv[0], is_daemon, is_verbose, debug_level); (void) atexit(dhcpmsg_fini); tq = iu_tq_create(); eh = iu_eh_create(); if (eh == NULL || tq == NULL) { errno = ENOMEM; dhcpmsg(MSG_ERR, "cannot create timer queue or event handler"); return (EXIT_FAILURE); } /* * ignore most signals that could be reasonably generated. */ (void) signal(SIGTERM, graceful_shutdown); (void) signal(SIGQUIT, graceful_shutdown); (void) signal(SIGPIPE, SIG_IGN); (void) signal(SIGUSR1, SIG_IGN); (void) signal(SIGUSR2, SIG_IGN); (void) signal(SIGINT, SIG_IGN); (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGCHLD, SIG_IGN); /* * upon SIGTHAW we need to refresh any non-infinite leases. */ (void) iu_eh_register_signal(eh, SIGTHAW, refresh_ifslist, NULL); class_id = get_class_id(); if (class_id != NULL) class_id_len = strlen(class_id); else dhcpmsg(MSG_WARNING, "get_class_id failed, continuing " "with no vendor class id"); /* * the inactivity timer is enabled any time there are no * interfaces under DHCP control. if DHCP_INACTIVITY_WAIT * seconds transpire without an interface under DHCP control, * the agent shuts down. */ inactivity_id = iu_schedule_timer(tq, DHCP_INACTIVITY_WAIT, inactivity_shutdown, NULL); /* * max out the number available descriptors, just in case.. */ rl.rlim_cur = RLIM_INFINITY; rl.rlim_max = RLIM_INFINITY; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) dhcpmsg(MSG_ERR, "setrlimit failed"); (void) enable_extended_FILE_stdio(-1, -1); /* * create the ipc channel that the agent will listen for * requests on, and register it with the event handler so that * `accept_event' will be called back. */ switch (dhcp_ipc_init(&ipc_fd)) { case 0: break; case DHCP_IPC_E_BIND: dhcpmsg(MSG_ERROR, "dhcp_ipc_init: cannot bind to port " "%i (agent already running?)", IPPORT_DHCPAGENT); return (EXIT_FAILURE); default: dhcpmsg(MSG_ERROR, "dhcp_ipc_init failed"); return (EXIT_FAILURE); } if (iu_register_event(eh, ipc_fd, POLLIN, accept_event, 0) == -1) { dhcpmsg(MSG_ERR, "cannot register ipc fd for messages"); return (EXIT_FAILURE); } /* * Create the global routing socket. This is used for monitoring * interface transitions, so that we learn about the kernel's Duplicate * Address Detection status, and for inserting and removing default * routes as learned from DHCP servers. */ rtsock_fd = socket(PF_ROUTE, SOCK_RAW, AF_INET); if (rtsock_fd == -1) { dhcpmsg(MSG_ERR, "cannot open routing socket"); return (EXIT_FAILURE); } if (iu_register_event(eh, rtsock_fd, POLLIN, rtsock_event, 0) == -1) { dhcpmsg(MSG_ERR, "cannot register routing socket for messages"); return (EXIT_FAILURE); } /* * if the -a (adopt) option was specified, try to adopt the * kernel-managed interface before we start. */ if (do_adopt && dhcp_adopt() == 0) return (EXIT_FAILURE); /* * enter the main event loop; this is where all the real work * takes place (through registering events and scheduling timers). * this function only returns when the agent is shutting down. */ switch (iu_handle_events(eh, tq)) { case -1: dhcpmsg(MSG_WARNING, "iu_handle_events exited abnormally"); break; case DHCP_REASON_INACTIVITY: dhcpmsg(MSG_INFO, "no interfaces to manage, shutting down..."); break; case DHCP_REASON_TERMINATE: dhcpmsg(MSG_INFO, "received SIGTERM, shutting down..."); break; case DHCP_REASON_SIGNAL: dhcpmsg(MSG_WARNING, "received unexpected signal, shutting " "down..."); break; } (void) iu_eh_unregister_signal(eh, SIGTHAW, NULL); iu_eh_destroy(eh); iu_tq_destroy(tq); return (EXIT_SUCCESS); }
void dhcp_requesting(iu_tq_t *tqp, void *arg) { dhcp_smach_t *dsmp = arg; dhcp_pkt_t *dpkt; PKT_LIST *offer; lease_t lease; boolean_t isv6 = dsmp->dsm_isv6; /* * We assume here that if tqp is set, then this means we're being * called back by the offer wait timer. If so, then drop our hold * on the state machine. Otherwise, cancel the timer if it's running. */ if (tqp != NULL) { dhcpmsg(MSG_VERBOSE, "dhcp_requesting: offer wait timer on v%d %s", isv6 ? 6 : 4, dsmp->dsm_name); dsmp->dsm_offer_timer = -1; if (!verify_smach(dsmp)) return; } else { cancel_offer_timer(dsmp); } /* * select the best OFFER; all others pitched. */ offer = select_best(dsmp); if (offer == NULL) { dhcpmsg(MSG_VERBOSE, "no OFFERs/Advertisements on %s, waiting...", dsmp->dsm_name); /* * no acceptable OFFERs have come in. reschedule * ourself for callback. */ if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) { /* * ugh. the best we can do at this point is * revert back to INIT and wait for a user to * restart us. */ dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " "reschedule callback, reverting to INIT state on " "%s", dsmp->dsm_name); stop_pkt_retransmission(dsmp); (void) set_smach_state(dsmp, INIT); dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); } else { hold_smach(dsmp); } return; } /* * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly * declines all other offers we've received. We can no longer use any * cached offers, so we must discard them now. With DHCPv6, though, * we're permitted to hang onto the advertisements (offers) and try * them if the preferred one doesn't pan out. */ if (!isv6) free_pkt_list(&dsmp->dsm_recv_pkt_list); /* stop collecting packets. */ stop_pkt_retransmission(dsmp); /* * For IPv4, check to see whether we got an OFFER or a BOOTP packet. * If we got a BOOTP packet, go to the BOUND state now. */ if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { free_pkt_list(&dsmp->dsm_recv_pkt_list); if (!set_smach_state(dsmp, REQUESTING)) { dhcp_restart(dsmp); return; } if (!dhcp_bound(dsmp, offer)) { dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " "failed for %s", dsmp->dsm_name); dhcp_restart(dsmp); return; } return; } if (isv6) { const char *estr, *msg; const dhcpv6_option_t *d6o; uint_t olen, msglen; /* If there's a Status Code option, print the message */ d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, &olen); (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); print_server_msg(dsmp, msg, msglen); /* Copy in the Server ID (guaranteed to be present now) */ if (!save_server_id(dsmp, offer)) goto failure; /* * Determine how to send this message. If the Advertisement * (offer) has the unicast option, then use the address * specified in the option. Otherwise, send via multicast. */ server_unicast_option(dsmp, offer); send_v6_request(dsmp); } else { /* if we got a message from the server, display it. */ if (offer->opts[CD_MESSAGE] != NULL) { print_server_msg(dsmp, (char *)offer->opts[CD_MESSAGE]->value, offer->opts[CD_MESSAGE]->len); } /* * assemble a DHCPREQUEST, with the ciaddr field set to 0, * since we got here from the INIT state. */ dpkt = init_pkt(dsmp, REQUEST); /* * Grab the lease out of the OFFER; we know it's valid because * select_best() already checked. The max dhcp message size * option is set to the interface max, minus the size of the * udp and ip headers. */ (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, offer->pkt->yiaddr.s_addr); (void) add_pkt_opt(dpkt, CD_SERVER_ID, offer->opts[CD_SERVER_ID]->value, offer->opts[CD_SERVER_ID]->len); if (class_id_len != 0) { (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); } (void) add_pkt_prl(dpkt, dsmp); /* * dsm_reqhost was set for this state machine in * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a * host name was found */ if (dsmp->dsm_reqhost != NULL) { (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, strlen(dsmp->dsm_reqhost)); } (void) add_pkt_opt(dpkt, CD_END, NULL, 0); /* * send out the REQUEST, trying retransmissions. either a NAK * or too many REQUEST attempts will revert us to SELECTING. */ if (!set_smach_state(dsmp, REQUESTING)) { dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " "REQUESTING state; reverting to INIT on %s", dsmp->dsm_name); goto failure; } (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), stop_requesting); } /* all done with the offer */ free_pkt_entry(offer); return; failure: dsmp->dsm_dflags |= DHCP_IF_FAILED; (void) set_smach_state(dsmp, INIT); ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); free_pkt_list(&dsmp->dsm_recv_pkt_list); }
void dhcp_selecting(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; /* * We first set up to collect OFFER/Advertise packets as they arrive. * We then send out DISCOVER/Solicit probes. Then we wait a * user-tunable number of seconds before seeing if OFFERs/ * Advertisements have come in response to our DISCOVER/Solicit. If * none have come in, we continue to wait, sending out our DISCOVER/ * Solicit probes with exponential backoff. If no OFFER/Advertisement * is ever received, we will wait forever (note that since we're * event-driven though, we're still able to service other state * machines). * * Note that we do an reset_smach() here because we may be landing in * dhcp_selecting() as a result of restarting DHCP, so the state * machine may not be fresh. */ reset_smach(dsmp); if (!set_smach_state(dsmp, SELECTING)) { dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot switch to SELECTING state; " "reverting to INIT on %s", dsmp->dsm_name); goto failed; } /* Remove the stale hostconf file, if there is any */ (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); dsmp->dsm_offer_timer = iu_schedule_timer(tq, dsmp->dsm_offer_wait, dhcp_requesting, dsmp); if (dsmp->dsm_offer_timer == -1) { dhcpmsg(MSG_ERROR, "dhcp_selecting: cannot schedule to read " "%s packets", dsmp->dsm_isv6 ? "Advertise" : "OFFER"); goto failed; } hold_smach(dsmp); /* * Assemble and send the DHCPDISCOVER or Solicit message. * * If this fails, we'll wait for the select timer to go off * before trying again. */ if (dsmp->dsm_isv6) { dhcpv6_ia_na_t d6in; if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_SOLICIT)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " "Solicit packet"); return; } /* Add an IA_NA option for our controlling LIF */ d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); d6in.d6in_t1 = htonl(0); d6in.d6in_t2 = htonl(0); (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, (dhcpv6_option_t *)&d6in + 1, sizeof (d6in) - sizeof (dhcpv6_option_t)); /* Option Request option for desired information */ (void) add_pkt_prl(dpkt, dsmp); /* Enable Rapid-Commit */ (void) add_pkt_opt(dpkt, DHCPV6_OPT_RAPID_COMMIT, NULL, 0); /* xxx add Reconfigure Accept */ (void) send_pkt_v6(dsmp, dpkt, ipv6_all_dhcp_relay_and_servers, stop_selecting, DHCPV6_SOL_TIMEOUT, DHCPV6_SOL_MAX_RT); } else { if ((dpkt = init_pkt(dsmp, DISCOVER)) == NULL) { dhcpmsg(MSG_ERROR, "dhcp_selecting: unable to set up " "DISCOVER packet"); return; } /* * The max DHCP message size option is set to the interface * MTU, minus the size of the UDP and IP headers. */ (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM)); if (class_id_len != 0) { (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); } (void) add_pkt_prl(dpkt, dsmp); if (!dhcp_add_fqdn_opt(dpkt, dsmp)) (void) dhcp_add_hostname_opt(dpkt, dsmp); (void) add_pkt_opt(dpkt, CD_END, NULL, 0); (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), stop_selecting); } return; failed: (void) set_smach_state(dsmp, INIT); dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); }
/* * Registers the attributes of a running method passed as arguments so that * the method's termination is noticed and any further processing of the * associated instance is carried out. The function also sets up any * necessary timers so we can detect hung methods. * Returns -1 if either it failed to open the /proc psinfo file which is used * to monitor the method process, it failed to setup a required timer or * memory allocation failed; else 0. */ int register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd, char *proto_name) { char path[MAXPATHLEN]; int fd; method_el_t *me; /* open /proc psinfo file of process to listen for POLLHUP events on */ (void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid); for (;;) { if ((fd = open(path, O_RDONLY)) >= 0) { break; } else if (errno != EINTR) { /* * Don't output an error for ENOENT; we get this * if a method has gone away whilst we were stopped, * and we're now trying to re-listen for it. */ if (errno != ENOENT) { error_msg(gettext("Failed to open %s: %s"), path, strerror(errno)); } return (-1); } } /* add method record to in-memory list */ if ((me = calloc(1, sizeof (method_el_t))) == NULL) { error_msg(strerror(errno)); (void) close(fd); return (-1); } me->fd = fd; me->inst = (instance_t *)ins; me->method = mthd; me->pid = pid; me->cid = cid; if (proto_name != NULL) { if ((me->proto_name = strdup(proto_name)) == NULL) { error_msg(strerror(errno)); free(me); (void) close(fd); return (-1); } } else me->proto_name = NULL; /* register a timeout for the method, if required */ if (mthd != IM_START) { method_info_t *mi = ins->config->methods[mthd]; if (mi->timeout > 0) { assert(ins->timer_id == -1); ins->timer_id = iu_schedule_timer(timer_queue, mi->timeout, method_timeout, me); if (ins->timer_id == -1) { error_msg(gettext( "Failed to schedule method timeout")); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } } } /* * Add fd of psinfo file to poll set, but pass 0 for events to * poll for, so we should only get a POLLHUP event on the fd. */ if (set_pollfd(fd, 0) == -1) { cancel_inst_timer(ins); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } uu_list_node_init(me, &me->link, method_pool); (void) uu_list_insert_after(method_list, NULL, me); return (0); }