| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| #include <sys/types.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <locale.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdio_ext.h> |
| #include <dhcp_hostconf.h> |
| #include <dhcpagent_ipc.h> |
| #include <dhcpagent_util.h> |
| #include <dhcpmsg.h> |
| #include <netinet/dhcp.h> |
| #include <net/route.h> |
| #include <sys/sockio.h> |
| #include <sys/stat.h> |
| #include <stropts.h> |
| #include <fcntl.h> |
| #include <sys/scsi/adapters/iscsi_if.h> |
| |
| #include "async.h" |
| #include "agent.h" |
| #include "script_handler.h" |
| #include "util.h" |
| #include "class_id.h" |
| #include "states.h" |
| #include "packet.h" |
| #include "interface.h" |
| #include "defaults.h" |
| |
| #ifndef TEXT_DOMAIN |
| #define TEXT_DOMAIN "SYS_TEST" |
| #endif |
| |
| iu_timer_id_t inactivity_id; |
| int class_id_len = 0; |
| char *class_id; |
| iu_eh_t *eh; |
| iu_tq_t *tq; |
| pid_t grandparent; |
| int rtsock_fd; |
| |
| static boolean_t shutdown_started = B_FALSE; |
| static boolean_t do_adopt = B_FALSE; |
| static unsigned int debug_level = 0; |
| static iu_eh_callback_t accept_event, ipc_event, rtsock_event; |
| |
| /* |
| * The ipc_cmd_allowed[] table indicates which IPC commands are allowed in |
| * which states; a non-zero value indicates the command is permitted. |
| * |
| * START is permitted if the state machine is fresh, or if we are in the |
| * process of trying to obtain a lease (as a convenience to save the |
| * administrator from having to do an explicit DROP). EXTEND, RELEASE, and |
| * GET_TAG require a lease to be obtained in order to make sense. INFORM is |
| * permitted if the interface is fresh or has an INFORM in progress or |
| * previously done on it -- otherwise a DROP or RELEASE is first required. |
| * PING and STATUS always make sense and thus are always permitted, as is DROP |
| * in order to permit the administrator to always bail out. |
| */ |
| static int ipc_cmd_allowed[DHCP_NSTATES][DHCP_NIPC] = { |
| /* D E P R S S I G */ |
| /* R X I E T T N E */ |
| /* O T N L A A F T */ |
| /* P E G E R T O _ */ |
| /* . N . A T U R T */ |
| /* . D . S . S M A */ |
| /* . . . E . . . G */ |
| /* INIT */ { 1, 0, 1, 0, 1, 1, 1, 0 }, |
| /* SELECTING */ { 1, 0, 1, 0, 1, 1, 0, 0 }, |
| /* REQUESTING */ { 1, 0, 1, 0, 1, 1, 0, 0 }, |
| /* PRE_BOUND */ { 1, 1, 1, 1, 0, 1, 0, 1 }, |
| /* BOUND */ { 1, 1, 1, 1, 0, 1, 0, 1 }, |
| /* RENEWING */ { 1, 1, 1, 1, 0, 1, 0, 1 }, |
| /* REBINDING */ { 1, 1, 1, 1, 0, 1, 0, 1 }, |
| /* INFORMATION */ { 1, 0, 1, 0, 1, 1, 1, 1 }, |
| /* INIT_REBOOT */ { 1, 0, 1, 1, 1, 1, 0, 0 }, |
| /* ADOPTING */ { 1, 0, 1, 1, 0, 1, 0, 0 }, |
| /* INFORM_SENT */ { 1, 0, 1, 0, 1, 1, 1, 0 }, |
| /* DECLINING */ { 1, 1, 1, 1, 0, 1, 0, 1 }, |
| /* RELEASING */ { 1, 0, 1, 0, 0, 1, 0, 1 }, |
| }; |
| |
| #define CMD_ISPRIV 0x1 /* Command requires privileges */ |
| #define CMD_CREATE 0x2 /* Command creates an interface */ |
| #define CMD_BOOTP 0x4 /* Command is valid with BOOTP */ |
| #define CMD_IMMED 0x8 /* Reply is immediate (no BUSY state) */ |
| |
| static uint_t ipc_cmd_flags[DHCP_NIPC] = { |
| /* DHCP_DROP */ CMD_ISPRIV|CMD_BOOTP, |
| /* DHCP_EXTEND */ CMD_ISPRIV, |
| /* DHCP_PING */ CMD_BOOTP|CMD_IMMED, |
| /* DHCP_RELEASE */ CMD_ISPRIV, |
| /* DHCP_START */ CMD_CREATE|CMD_ISPRIV|CMD_BOOTP, |
| /* DHCP_STATUS */ CMD_BOOTP|CMD_IMMED, |
| /* DHCP_INFORM */ CMD_CREATE|CMD_ISPRIV, |
| /* DHCP_GET_TAG */ CMD_BOOTP|CMD_IMMED |
| }; |
| |
| static boolean_t |
| is_iscsi_active(void); |
| |
| int |
| main(int argc, char **argv) |
| { |
| boolean_t is_daemon = B_TRUE; |
| boolean_t is_verbose; |
| int ipc_fd; |
| int c; |
| int aware = RTAW_UNDER_IPMP; |
| struct rlimit rl; |
| |
| debug_level = df_get_int("", B_FALSE, DF_DEBUG_LEVEL); |
| is_verbose = df_get_bool("", B_FALSE, DF_VERBOSE); |
| |
| /* |
| * -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); |
| } |
| |
| /* |
| * Seed the random number generator, since we're going to need it |
| * to set transaction id's and for exponential backoff. |
| */ |
| srand48(gethrtime() ^ gethostid() ^ getpid()); |
| |
| 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_smachs, 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 and bind default IP sockets used to control interfaces and to |
| * catch stray packets. |
| */ |
| |
| if (!dhcp_ip_default()) |
| return (EXIT_FAILURE); |
| |
| /* |
| * 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. Both v4 and v6 are handed |
| * with this one socket. |
| */ |
| rtsock_fd = socket(PF_ROUTE, SOCK_RAW, 0); |
| if (rtsock_fd == -1) { |
| dhcpmsg(MSG_ERR, "cannot open routing socket"); |
| return (EXIT_FAILURE); |
| } |
| |
| /* |
| * We're IPMP-aware and can manage IPMP test addresses, so issue |
| * RT_AWARE to get routing socket messages for interfaces under IPMP. |
| */ |
| if (setsockopt(rtsock_fd, SOL_ROUTE, RT_AWARE, &aware, |
| sizeof (aware)) == -1) { |
| dhcpmsg(MSG_ERR, "cannot set RT_AWARE on 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()) |
| return (EXIT_FAILURE); |
| |
| /* |
| * For DHCPv6, we own all of the interfaces marked DHCPRUNNING. As |
| * we're starting operation here, if there are any of those interfaces |
| * lingering around, they're strays, and need to be removed. |
| * |
| * It might be nice to save these addresses off somewhere -- for both |
| * v4 and v6 -- and use them as hints for later negotiation. |
| */ |
| remove_v6_strays(); |
| |
| /* |
| * 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); |
| } |
| |
| /* |
| * drain_script(): event loop callback during shutdown |
| * |
| * input: eh_t *: unused |
| * void *: unused |
| * output: boolean_t: B_TRUE if event loop should exit; B_FALSE otherwise |
| */ |
| |
| /* ARGSUSED */ |
| boolean_t |
| drain_script(iu_eh_t *ehp, void *arg) |
| { |
| if (shutdown_started == B_FALSE) { |
| shutdown_started = B_TRUE; |
| /* |
| * Check if the system is diskless client and/or |
| * there are active iSCSI sessions |
| * |
| * Do not drop the lease, or the system will be |
| * unable to sync(dump) through nfs/iSCSI driver |
| */ |
| if (!do_adopt && !is_iscsi_active()) { |
| nuke_smach_list(); |
| } |
| } |
| return (script_count == 0); |
| } |
| |
| /* |
| * accept_event(): accepts a new connection on the ipc socket and registers |
| * to receive its messages with the event handler |
| * |
| * input: iu_eh_t *: unused |
| * int: the file descriptor in the iu_eh_t * the connection came in on |
| * (other arguments unused) |
| * output: void |
| */ |
| |
| /* ARGSUSED */ |
| static void |
| accept_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) |
| { |
| int client_fd; |
| int is_priv; |
| |
| if (dhcp_ipc_accept(fd, &client_fd, &is_priv) != 0) { |
| dhcpmsg(MSG_ERR, "accept_event: accept on ipc socket"); |
| return; |
| } |
| |
| if (iu_register_event(eh, client_fd, POLLIN, ipc_event, |
| (void *)is_priv) == -1) { |
| dhcpmsg(MSG_ERROR, "accept_event: cannot register ipc socket " |
| "for callback"); |
| } |
| } |
| |
| /* |
| * ipc_event(): processes incoming ipc requests |
| * |
| * input: iu_eh_t *: unused |
| * int: the file descriptor in the iu_eh_t * the request came in on |
| * short: unused |
| * iu_event_id_t: event ID |
| * void *: indicates whether the request is from a privileged client |
| * output: void |
| */ |
| |
| /* ARGSUSED */ |
| static void |
| ipc_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) |
| { |
| ipc_action_t ia, *iap; |
| dhcp_smach_t *dsmp; |
| int error, is_priv = (int)arg; |
| const char *ifname; |
| boolean_t isv6; |
| |
| ipc_action_init(&ia); |
| error = dhcp_ipc_recv_request(fd, &ia.ia_request, |
| DHCP_IPC_REQUEST_WAIT); |
| if (error != DHCP_IPC_SUCCESS) { |
| if (error != DHCP_IPC_E_EOF) { |
| dhcpmsg(MSG_ERROR, |
| "ipc_event: dhcp_ipc_recv_request failed: %s", |
| dhcp_ipc_strerror(error)); |
| } else { |
| dhcpmsg(MSG_DEBUG, "ipc_event: connection closed"); |
| } |
| if ((dsmp = lookup_smach_by_event(id)) != NULL) { |
| ipc_action_finish(dsmp, error); |
| } else { |
| (void) iu_unregister_event(eh, id, NULL); |
| (void) dhcp_ipc_close(fd); |
| } |
| return; |
| } |
| |
| /* Fill in temporary ipc_action structure for utility functions */ |
| ia.ia_cmd = DHCP_IPC_CMD(ia.ia_request->message_type); |
| ia.ia_fd = fd; |
| ia.ia_eid = id; |
| |
| if (ia.ia_cmd >= DHCP_NIPC) { |
| dhcpmsg(MSG_ERROR, |
| "ipc_event: invalid command (%s) attempted on %s", |
| dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname); |
| send_error_reply(&ia, DHCP_IPC_E_CMD_UNKNOWN); |
| return; |
| } |
| |
| /* return EPERM for any of the privileged actions */ |
| |
| if (!is_priv && (ipc_cmd_flags[ia.ia_cmd] & CMD_ISPRIV)) { |
| dhcpmsg(MSG_WARNING, |
| "ipc_event: privileged ipc command (%s) attempted on %s", |
| dhcp_ipc_type_to_string(ia.ia_cmd), ia.ia_request->ifname); |
| send_error_reply(&ia, DHCP_IPC_E_PERM); |
| return; |
| } |
| |
| /* |
| * Try to locate the state machine associated with this command. If |
| * the command is DHCP_START or DHCP_INFORM and there isn't a state |
| * machine already, make one (there may already be one from a previous |
| * failed attempt to START or INFORM). Otherwise, verify the reference |
| * is still valid. |
| * |
| * The interface name may be blank. In that case, we look up the |
| * primary interface, and the requested type (v4 or v6) doesn't matter. |
| */ |
| |
| isv6 = (ia.ia_request->message_type & DHCP_V6) != 0; |
| ifname = ia.ia_request->ifname; |
| if (*ifname == '\0') |
| dsmp = primary_smach(isv6); |
| else |
| dsmp = lookup_smach(ifname, isv6); |
| |
| if (dsmp != NULL) { |
| /* Note that verify_smach drops a reference */ |
| hold_smach(dsmp); |
| if (!verify_smach(dsmp)) |
| dsmp = NULL; |
| } |
| |
| if (dsmp == NULL) { |
| /* |
| * If the user asked for the primary DHCP interface, but there |
| * is none, then report failure. |
| */ |
| if (ifname[0] == '\0') { |
| error = DHCP_IPC_E_NOPRIMARY; |
| |
| /* |
| * If there's no interface, and we're starting up, then create |
| * it now, along with a state machine for it. Note that if |
| * insert_smach fails, it discards the LIF reference. |
| */ |
| } else if (ipc_cmd_flags[ia.ia_cmd] & CMD_CREATE) { |
| dhcp_lif_t *lif; |
| |
| lif = attach_lif(ifname, isv6, &error); |
| if (lif != NULL && |
| (dsmp = insert_smach(lif, &error)) != NULL) { |
| /* |
| * Get client ID and set "DHCPRUNNING" flag on |
| * logical interface. (V4 only, because V6 |
| * plumbs its own interfaces.) |
| */ |
| error = get_smach_cid(dsmp); |
| if (error == DHCP_IPC_SUCCESS) |
| error = set_lif_dhcp(lif, B_FALSE); |
| if (error != DHCP_IPC_SUCCESS) { |
| remove_smach(dsmp); |
| dsmp = NULL; |
| } |
| } |
| |
| /* |
| * Otherwise, this is an operation on an unknown interface. |
| */ |
| } else { |
| error = DHCP_IPC_E_UNKIF; |
| } |
| if (dsmp == NULL) { |
| send_error_reply(&ia, error); |
| return; |
| } |
| } |
| |
| if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) && |
| !(ipc_cmd_flags[ia.ia_cmd] & CMD_BOOTP)) { |
| dhcpmsg(MSG_ERROR, "command %s not valid for BOOTP on %s", |
| dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); |
| send_error_reply(&ia, DHCP_IPC_E_BOOTP); |
| return; |
| } |
| |
| /* |
| * verify that the state machine 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 (!check_cmd_allowed(dsmp->dsm_state, ia.ia_cmd)) { |
| dhcpmsg(MSG_DEBUG, |
| "in state %s; not allowing %s command on %s", |
| dhcp_state_to_string(dsmp->dsm_state), |
| dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); |
| send_error_reply(&ia, |
| ia.ia_cmd == DHCP_START && dsmp->dsm_state != INIT ? |
| DHCP_IPC_E_RUNNING : DHCP_IPC_E_OUTSTATE); |
| return; |
| } |
| |
| dhcpmsg(MSG_DEBUG, "in state %s; allowing %s command on %s", |
| dhcp_state_to_string(dsmp->dsm_state), |
| dhcp_ipc_type_to_string(ia.ia_cmd), dsmp->dsm_name); |
| |
| if ((ia.ia_request->message_type & DHCP_PRIMARY) && is_priv) |
| make_primary(dsmp); |
| |
| /* |
| * The current design dictates that there can be only one outstanding |
| * transaction per state machine -- this simplifies the code |
| * considerably and also fits well with RFCs 2131 and 3315. It is |
| * worth classifying the different DHCP commands into synchronous |
| * (those which we will handle now and reply to immediately) and |
| * asynchronous (those which require transactions and will be completed |
| * at an indeterminate time in the future): |
| * |
| * DROP: removes the agent's management of a state machine. |
| * asynchronous as the script program may be invoked. |
| * |
| * PING: checks to see if the agent has a named state machine. |
| * synchronous, since no packets need to be sent |
| * to the DHCP server. |
| * |
| * STATUS: returns information about a state machine. |
| * synchronous, since no packets need to be sent |
| * to the DHCP server. |
| * |
| * RELEASE: releases the agent's management of a state machine |
| * and brings the associated interfaces 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 a named state machine. asynchronous since |
| * the agent needs to wait for OFFERs, ACKs, etc. |
| * |
| * INFORM: obtains configuration parameters for the system using |
| * 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 state |
| * machine. The current asynchronous command is managed by the async_* |
| * api: async_start(), async_finish(), and async_cancel(). |
| * async_start() starts management of a new asynchronous command on an |
| * state machine, which should only be done after async_cancel() to |
| * terminate a previous command. When the command is completed, |
| * async_finish() should be called. |
| * |
| * 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 state machine): |
| * |
| * o When an asynchronous command is requested, then |
| * async_cancel() is called to terminate any non-user |
| * action in progress. If there's a user action running, |
| * 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 state machine 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. |
| */ |
| |
| if (ipc_cmd_flags[ia.ia_cmd] & CMD_IMMED) { |
| /* |
| * Only immediate commands (ping, status, get_tag) need to |
| * worry about freeing ia through one of the reply functions |
| * before returning. |
| */ |
| iap = &ia; |
| } else { |
| /* |
| * if shutdown request has been received, send back an error. |
| */ |
| if (shutdown_started) { |
| send_error_reply(&ia, DHCP_IPC_E_OUTSTATE); |
| return; |
| } |
| |
| if (dsmp->dsm_dflags & DHCP_IF_BUSY) { |
| send_error_reply(&ia, DHCP_IPC_E_PEND); |
| return; |
| } |
| |
| if (!ipc_action_start(dsmp, &ia)) { |
| dhcpmsg(MSG_WARNING, "ipc_event: ipc_action_start " |
| "failed for %s", dsmp->dsm_name); |
| send_error_reply(&ia, DHCP_IPC_E_MEMORY); |
| return; |
| } |
| |
| /* Action structure consumed by above function */ |
| iap = &dsmp->dsm_ia; |
| } |
| |
| switch (iap->ia_cmd) { |
| |
| case DHCP_DROP: |
| if (dsmp->dsm_droprelease) |
| break; |
| dsmp->dsm_droprelease = B_TRUE; |
| (void) script_start(dsmp, isv6 ? EVENT_DROP6 : EVENT_DROP, |
| dhcp_drop, NULL, NULL); |
| break; /* not an immediate function */ |
| |
| case DHCP_EXTEND: |
| (void) dhcp_extending(dsmp); |
| break; |
| |
| case DHCP_GET_TAG: { |
| dhcp_optnum_t optnum; |
| void *opt = NULL; |
| uint_t optlen; |
| boolean_t did_alloc = B_FALSE; |
| PKT_LIST *ack = dsmp->dsm_ack; |
| |
| /* |
| * verify the request makes sense. |
| */ |
| |
| if (iap->ia_request->data_type != DHCP_TYPE_OPTNUM || |
| iap->ia_request->data_length != sizeof (dhcp_optnum_t)) { |
| send_error_reply(iap, DHCP_IPC_E_PROTO); |
| break; |
| } |
| |
| (void) memcpy(&optnum, iap->ia_request->buffer, |
| sizeof (dhcp_optnum_t)); |
| |
| load_option: |
| switch (optnum.category) { |
| |
| case DSYM_SITE: /* FALLTHRU */ |
| case DSYM_STANDARD: |
| if (isv6) { |
| opt = dhcpv6_pkt_option(ack, NULL, optnum.code, |
| NULL); |
| } else { |
| if (optnum.code <= DHCP_LAST_OPT) |
| opt = ack->opts[optnum.code]; |
| } |
| break; |
| |
| case DSYM_VENDOR: |
| if (isv6) { |
| dhcpv6_option_t *d6o; |
| uint32_t ent; |
| |
| /* |
| * Look through vendor options to find our |
| * enterprise number. |
| */ |
| d6o = NULL; |
| for (;;) { |
| d6o = dhcpv6_pkt_option(ack, d6o, |
| DHCPV6_OPT_VENDOR_OPT, &optlen); |
| if (d6o == NULL) |
| break; |
| optlen -= sizeof (*d6o); |
| if (optlen < sizeof (ent)) |
| continue; |
| (void) memcpy(&ent, d6o + 1, |
| sizeof (ent)); |
| if (ntohl(ent) != DHCPV6_SUN_ENT) |
| continue; |
| break; |
| } |
| if (d6o != NULL) { |
| /* |
| * Now find the requested vendor option |
| * within the vendor options block. |
| */ |
| opt = dhcpv6_find_option( |
| (char *)(d6o + 1) + sizeof (ent), |
| optlen - sizeof (ent), NULL, |
| optnum.code, NULL); |
| } |
| } else { |
| /* |
| * 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 (isv6) { |
| dhcpv6_message_t *d6m = |
| (dhcpv6_message_t *)ack->pkt; |
| dhcpv6_option_t *d6o; |
| |
| /* Validate the packet field the user wants */ |
| optlen = optnum.code + optnum.size; |
| if (d6m->d6m_msg_type == |
| DHCPV6_MSG_RELAY_FORW || |
| d6m->d6m_msg_type == |
| DHCPV6_MSG_RELAY_REPL) { |
| if (optlen > sizeof (dhcpv6_relay_t)) |
| break; |
| } else { |
| if (optlen > sizeof (*d6m)) |
| break; |
| } |
| |
| opt = malloc(sizeof (*d6o) + optnum.size); |
| if (opt != NULL) { |
| d6o = opt; |
| d6o->d6o_code = htons(optnum.code); |
| d6o->d6o_len = htons(optnum.size); |
| (void) memcpy(d6o + 1, (caddr_t)d6m + |
| optnum.code, optnum.size); |
| } |
| } else { |
| 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) { |
| DHCP_OPT *v4opt = opt; |
| |
| v4opt->len = optnum.size; |
| v4opt->code = optnum.code; |
| (void) memcpy(v4opt->value, |
| (caddr_t)ack->pkt + optnum.code, |
| optnum.size); |
| } |
| } |
| |
| if (opt == NULL) { |
| send_error_reply(iap, DHCP_IPC_E_MEMORY); |
| return; |
| } |
| did_alloc = B_TRUE; |
| break; |
| |
| default: |
| send_error_reply(iap, DHCP_IPC_E_PROTO); |
| return; |
| } |
| |
| /* |
| * return the option payload, if there was one. the "+ 2" |
| * accounts for the option code number and length byte. |
| */ |
| |
| if (opt != NULL) { |
| if (isv6) { |
| dhcpv6_option_t d6ov; |
| |
| (void) memcpy(&d6ov, opt, sizeof (d6ov)); |
| optlen = ntohs(d6ov.d6o_len) + sizeof (d6ov); |
| } else { |
| optlen = ((DHCP_OPT *)opt)->len + 2; |
| } |
| send_data_reply(iap, 0, DHCP_TYPE_OPTION, opt, optlen); |
| |
| if (did_alloc) |
| free(opt); |
| break; |
| } else if (ack != dsmp->dsm_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 = dsmp->dsm_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(iap); |
| break; |
| } |
| |
| case DHCP_INFORM: |
| dhcp_inform(dsmp); |
| /* next destination: dhcp_acknak() */ |
| break; /* not an immediate function */ |
| |
| case DHCP_PING: |
| if (dsmp->dsm_dflags & DHCP_IF_FAILED) |
| send_error_reply(iap, DHCP_IPC_E_FAILEDIF); |
| else |
| send_ok_reply(iap); |
| break; |
| |
| case DHCP_RELEASE: |
| if (dsmp->dsm_droprelease) |
| break; |
| dsmp->dsm_droprelease = B_TRUE; |
| (void) script_start(dsmp, isv6 ? EVENT_RELEASE6 : |
| EVENT_RELEASE, dhcp_release, "Finished with lease.", NULL); |
| break; /* not an immediate function */ |
| |
| case DHCP_START: { |
| PKT_LIST *ack, *oack; |
| PKT_LIST *plp[2]; |
| |
| deprecate_leases(dsmp); |
| |
| /* |
| * 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(dsmp->dsm_name, plp, 2, dsmp->dsm_isv6); |
| ack = error > 0 ? plp[0] : NULL; |
| oack = error > 1 ? plp[1] : NULL; |
| |
| /* |
| * If the allocation of the old ack fails, that's fine; |
| * continue without it. |
| */ |
| if (oack == NULL) |
| oack = ack; |
| |
| /* |
| * As long as we've allocated something, start using it. |
| */ |
| if (ack != NULL) { |
| dsmp->dsm_orig_ack = oack; |
| dsmp->dsm_ack = ack; |
| dhcp_init_reboot(dsmp); |
| /* next destination: dhcp_acknak() */ |
| break; |
| } |
| |
| /* |
| * if not debugging, wait for a few seconds before |
| * going into SELECTING. |
| */ |
| |
| if (debug_level == 0 && set_start_timer(dsmp)) { |
| /* next destination: dhcp_start() */ |
| break; |
| } else { |
| dhcp_selecting(dsmp); |
| /* next destination: dhcp_requesting() */ |
| break; |
| } |
| } |
| |
| case DHCP_STATUS: { |
| dhcp_status_t status; |
| dhcp_lease_t *dlp; |
| |
| status.if_began = monosec_to_time(dsmp->dsm_curstart_monosec); |
| |
| /* |
| * We return information on just the first lease as being |
| * representative of the lot. A better status mechanism is |
| * needed. |
| */ |
| dlp = dsmp->dsm_leases; |
| |
| if (dlp == NULL || |
| dlp->dl_lifs->lif_expire.dt_start == DHCP_PERM) { |
| status.if_t1 = DHCP_PERM; |
| status.if_t2 = DHCP_PERM; |
| status.if_lease = DHCP_PERM; |
| } else { |
| status.if_t1 = status.if_began + |
| dlp->dl_t1.dt_start; |
| status.if_t2 = status.if_began + |
| dlp->dl_t2.dt_start; |
| status.if_lease = status.if_began + |
| dlp->dl_lifs->lif_expire.dt_start; |
| } |
| |
| status.version = DHCP_STATUS_VER; |
| status.if_state = dsmp->dsm_state; |
| status.if_dflags = dsmp->dsm_dflags; |
| status.if_sent = dsmp->dsm_sent; |
| status.if_recv = dsmp->dsm_received; |
| status.if_bad_offers = dsmp->dsm_bad_offers; |
| |
| (void) strlcpy(status.if_name, dsmp->dsm_name, IFNAMSIZ); |
| |
| send_data_reply(iap, 0, DHCP_TYPE_STATUS, &status, |
| sizeof (dhcp_status_t)); |
| break; |
| } |
| } |
| } |
| |
| /* |
| * check_rtm_addr(): determine if routing socket message matches interface |
| * address |
| * |
| * input: const struct if_msghdr *: pointer to routing socket message |
| * int: routing socket message length |
| * boolean_t: set to B_TRUE if IPv6 |
| * const in6_addr_t *: pointer to IP address |
| * output: boolean_t: B_TRUE if address is a match |
| */ |
| |
| static boolean_t |
| check_rtm_addr(const struct ifa_msghdr *ifam, int msglen, boolean_t isv6, |
| const in6_addr_t *addr) |
| { |
| const char *cp, *lim; |
| uint_t flag; |
| const struct sockaddr *sa; |
| |
| if (!(ifam->ifam_addrs & RTA_IFA)) |
| return (B_FALSE); |
| |
| cp = (const char *)(ifam + 1); |
| lim = (const char *)ifam + msglen; |
| for (flag = 1; flag < RTA_IFA; flag <<= 1) { |
| if (ifam->ifam_addrs & flag) { |
| /* LINTED: alignment */ |
| sa = (const struct sockaddr *)cp; |
| if ((const char *)(sa + 1) > lim) |
| return (B_FALSE); |
| switch (sa->sa_family) { |
| case AF_INET: |
| cp += sizeof (struct sockaddr_in); |
| break; |
| case AF_LINK: |
| cp += sizeof (struct sockaddr_dl); |
| break; |
| case AF_INET6: |
| cp += sizeof (struct sockaddr_in6); |
| break; |
| default: |
| cp += sizeof (struct sockaddr); |
| break; |
| } |
| } |
| } |
| if (isv6) { |
| const struct sockaddr_in6 *sin6; |
| |
| /* LINTED: alignment */ |
| sin6 = (const struct sockaddr_in6 *)cp; |
| if ((const char *)(sin6 + 1) > lim) |
| return (B_FALSE); |
| if (sin6->sin6_family != AF_INET6) |
| return (B_FALSE); |
| return (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, addr)); |
| } else { |
| const struct sockaddr_in *sinp; |
| ipaddr_t v4addr; |
| |
| /* LINTED: alignment */ |
| sinp = (const struct sockaddr_in *)cp; |
| if ((const char *)(sinp + 1) > lim) |
| return (B_FALSE); |
| if (sinp->sin_family != AF_INET) |
| return (B_FALSE); |
| IN6_V4MAPPED_TO_IPADDR(addr, v4addr); |
| return (sinp->sin_addr.s_addr == v4addr); |
| } |
| } |
| |
| /* |
| * is_rtm_v6(): determine if routing socket message is IPv6 |
| * |
| * input: struct ifa_msghdr *: pointer to routing socket message |
| * int: message length |
| * output: boolean_t |
| */ |
| |
| static boolean_t |
| is_rtm_v6(const struct ifa_msghdr *ifam, int msglen) |
| { |
| const char *cp, *lim; |
| uint_t flag; |
| const struct sockaddr *sa; |
| |
| cp = (const char *)(ifam + 1); |
| lim = (const char *)ifam + msglen; |
| for (flag = ifam->ifam_addrs; flag != 0; flag &= flag - 1) { |
| /* LINTED: alignment */ |
| sa = (const struct sockaddr *)cp; |
| if ((const char *)(sa + 1) > lim) |
| return (B_FALSE); |
| switch (sa->sa_family) { |
| case AF_INET: |
| return (B_FALSE); |
| case AF_LINK: |
| cp += sizeof (struct sockaddr_dl); |
| break; |
| case AF_INET6: |
| return (B_TRUE); |
| default: |
| cp += sizeof (struct sockaddr); |
| break; |
| } |
| } |
| return (B_FALSE); |
| } |
| |
| /* |
| * check_lif(): check the state of a given logical interface and its DHCP |
| * lease. We've been told by the routing socket that the |
| * corresponding ifIndex has changed. This may mean that DAD has |
| * completed or failed. |
| * |
| * input: dhcp_lif_t *: pointer to the LIF |
| * const struct ifa_msghdr *: routing socket message |
| * int: size of routing socket message |
| * output: boolean_t: B_TRUE if DAD has completed on this interface |
| */ |
| |
| static boolean_t |
| check_lif(dhcp_lif_t *lif, const struct ifa_msghdr *ifam, int msglen) |
| { |
| boolean_t isv6, dad_wait, unplumb; |
| int fd; |
| struct lifreq lifr; |
| |
| isv6 = lif->lif_pif->pif_isv6; |
| fd = isv6 ? v6_sock_fd : v4_sock_fd; |
| |
| /* |
| * Get the real (64 bit) logical interface flags. Note that the |
| * routing socket message has flags, but these are just the lower 32 |
| * bits. |
| */ |
| unplumb = B_FALSE; |
| (void) memset(&lifr, 0, sizeof (lifr)); |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name)); |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| /* |
| * Failing to retrieve flags means that the interface is gone. |
| * It hasn't failed to verify with DAD, but we still have to |
| * give up on it. |
| */ |
| lifr.lifr_flags = 0; |
| if (errno == ENXIO) { |
| lif->lif_plumbed = B_FALSE; |
| dhcpmsg(MSG_INFO, "%s has been removed; abandoning", |
| lif->lif_name); |
| if (!isv6) |
| discard_default_routes(lif->lif_smachs); |
| } else { |
| dhcpmsg(MSG_ERR, |
| "unable to retrieve interface flags on %s", |
| lif->lif_name); |
| } |
| unplumb = B_TRUE; |
| } else if (!check_rtm_addr(ifam, msglen, isv6, &lif->lif_v6addr)) { |
| /* |
| * If the message is not about this logical interface, |
| * then just ignore it. |
| */ |
| return (B_FALSE); |
| } else if (lifr.lifr_flags & IFF_DUPLICATE) { |
| dhcpmsg(MSG_ERROR, "interface %s has duplicate address", |
| lif->lif_name); |
| lif_mark_decline(lif, "duplicate address"); |
| close_ip_lif(lif); |
| (void) open_ip_lif(lif, INADDR_ANY, B_TRUE); |
| } |
| |
| dad_wait = lif->lif_dad_wait; |
| if (dad_wait) { |
| dhcpmsg(MSG_VERBOSE, "check_lif: %s has finished DAD", |
| lif->lif_name); |
| lif->lif_dad_wait = B_FALSE; |
| } |
| |
| if (unplumb) |
| unplumb_lif(lif); |
| |
| return (dad_wait); |
| } |
| |
| /* |
| * check_main_lif(): check the state of a main logical interface for a state |
| * machine. This is used only for DHCPv6. |
| * |
| * input: dhcp_smach_t *: pointer to the state machine |
| * const struct ifa_msghdr *: routing socket message |
| * int: size of routing socket message |
| * output: boolean_t: B_TRUE if LIF is ok. |
| */ |
| |
| static boolean_t |
| check_main_lif(dhcp_smach_t *dsmp, const struct ifa_msghdr *ifam, int msglen) |
| { |
| dhcp_lif_t *lif = dsmp->dsm_lif; |
| struct lifreq lifr; |
| |
| /* |
| * Get the real (64 bit) logical interface flags. Note that the |
| * routing socket message has flags, but these are just the lower 32 |
| * bits. |
| */ |
| (void) memset(&lifr, 0, sizeof (lifr)); |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, sizeof (lifr.lifr_name)); |
| if (ioctl(v6_sock_fd, SIOCGLIFFLAGS, &lifr) == -1) { |
| /* |
| * Failing to retrieve flags means that the interface is gone. |
| * Our state machine is now trash. |
| */ |
| if (errno == ENXIO) { |
| dhcpmsg(MSG_INFO, "%s has been removed; abandoning", |
| lif->lif_name); |
| } else { |
| dhcpmsg(MSG_ERR, |
| "unable to retrieve interface flags on %s", |
| lif->lif_name); |
| } |
| return (B_FALSE); |
| } else if (!check_rtm_addr(ifam, msglen, B_TRUE, &lif->lif_v6addr)) { |
| /* |
| * If the message is not about this logical interface, |
| * then just ignore it. |
| */ |
| return (B_TRUE); |
| } else if (lifr.lifr_flags & IFF_DUPLICATE) { |
| dhcpmsg(MSG_ERROR, "interface %s has duplicate address", |
| lif->lif_name); |
| return (B_FALSE); |
| } else { |
| return (B_TRUE); |
| } |
| } |
| |
| /* |
| * process_link_up_down(): check the state of a physical interface for up/down |
| * transitions; must go through INIT_REBOOT state if |
| * the link flaps. |
| * |
| * input: dhcp_pif_t *: pointer to the physical interface to check |
| * const struct if_msghdr *: routing socket message |
| * output: none |
| */ |
| |
| static void |
| process_link_up_down(dhcp_pif_t *pif, const struct if_msghdr *ifm) |
| { |
| struct lifreq lifr; |
| boolean_t isv6; |
| int fd; |
| |
| /* |
| * If the message implies no change of flags, then we're done; no need |
| * to check further. Note that if we have multiple state machines on a |
| * single physical interface, this test keeps us from issuing an ioctl |
| * for each one. |
| */ |
| if ((ifm->ifm_flags & IFF_RUNNING) && pif->pif_running || |
| !(ifm->ifm_flags & IFF_RUNNING) && !pif->pif_running) |
| return; |
| |
| /* |
| * We don't know what the real interface flags are, because the |
| * if_index number is only 16 bits; we must go ask. |
| */ |
| isv6 = pif->pif_isv6; |
| fd = isv6 ? v6_sock_fd : v4_sock_fd; |
| (void) memset(&lifr, 0, sizeof (lifr)); |
| (void) strlcpy(lifr.lifr_name, pif->pif_name, sizeof (lifr.lifr_name)); |
| |
| if (ioctl(fd, SIOCGLIFFLAGS, &lifr) == -1 || |
| !(lifr.lifr_flags & IFF_RUNNING)) { |
| /* |
| * If we've lost the interface or it has gone down, then |
| * nothing special to do; just turn off the running flag. |
| */ |
| pif_status(pif, B_FALSE); |
| } else { |
| /* |
| * Interface has come back up: go through verification process. |
| */ |
| pif_status(pif, B_TRUE); |
| } |
| } |
| |
| /* |
| * rtsock_event(): fetches routing socket messages and updates internal |
| * interface state based on those messages. |
| * |
| * input: iu_eh_t *: unused |
| * int: the routing socket file descriptor |
| * (other arguments unused) |
| * output: void |
| */ |
| |
| /* ARGSUSED */ |
| static void |
| rtsock_event(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) |
| { |
| dhcp_smach_t *dsmp, *dsmnext; |
| union { |
| struct ifa_msghdr ifam; |
| struct if_msghdr ifm; |
| char buf[1024]; |
| } msg; |
| uint16_t ifindex; |
| int msglen; |
| boolean_t isv6; |
| |
| if ((msglen = read(fd, &msg, sizeof (msg))) <= 0) |
| return; |
| |
| /* Note that the routing socket interface index is just 16 bits */ |
| if (msg.ifm.ifm_type == RTM_IFINFO) { |
| ifindex = msg.ifm.ifm_index; |
| isv6 = (msg.ifm.ifm_flags & IFF_IPV6) ? B_TRUE : B_FALSE; |
| } else if (msg.ifam.ifam_type == RTM_DELADDR || |
| msg.ifam.ifam_type == RTM_NEWADDR) { |
| ifindex = msg.ifam.ifam_index; |
| isv6 = is_rtm_v6(&msg.ifam, msglen); |
| } else { |
| return; |
| } |
| |
| for (dsmp = lookup_smach_by_uindex(ifindex, NULL, isv6); |
| dsmp != NULL; dsmp = dsmnext) { |
| DHCPSTATE oldstate; |
| boolean_t lif_finished; |
| boolean_t lease_removed; |
| dhcp_lease_t *dlp, *dlnext; |
| |
| /* |
| * Note that script_start can call dhcp_drop directly, and |
| * that will do release_smach. |
| */ |
| dsmnext = lookup_smach_by_uindex(ifindex, dsmp, isv6); |
| oldstate = dsmp->dsm_state; |
| |
| /* |
| * Ignore state machines that are currently processing drop or |
| * release; there is nothing more we can do for them. |
| */ |
| if (dsmp->dsm_droprelease) |
| continue; |
| |
| /* |
| * Look for link up/down notifications. These occur on a |
| * physical interface basis. |
| */ |
| if (msg.ifm.ifm_type == RTM_IFINFO) { |
| process_link_up_down(dsmp->dsm_lif->lif_pif, &msg.ifm); |
| continue; |
| } |
| |
| /* |
| * Since we cannot trust the flags reported by the routing |
| * socket (they're just 32 bits -- and thus never include |
| * IFF_DUPLICATE), and we can't trust the ifindex (it's only 16 |
| * bits and also doesn't reflect the alias in use), we get |
| * flags on all matching interfaces, and go by that. |
| */ |
| lif_finished = B_FALSE; |
| lease_removed = B_FALSE; |
| for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlnext) { |
| dhcp_lif_t *lif, *lifnext; |
| uint_t nlifs = dlp->dl_nlifs; |
| |
| dlnext = dlp->dl_next; |
| for (lif = dlp->dl_lifs; lif != NULL && nlifs > 0; |
| lif = lifnext, nlifs--) { |
| lifnext = lif->lif_next; |
| if (check_lif(lif, &msg.ifam, msglen)) { |
| dsmp->dsm_lif_wait--; |
| lif_finished = B_TRUE; |
| } |
| } |
| if (dlp->dl_nlifs == 0) { |
| remove_lease(dlp); |
| lease_removed = B_TRUE; |
| } |
| } |
| |
| if ((isv6 && !check_main_lif(dsmp, &msg.ifam, msglen)) || |
| (!isv6 && !verify_lif(dsmp->dsm_lif))) { |
| if (dsmp->dsm_script_pid != -1) |
| script_stop(dsmp); |
| dsmp->dsm_droprelease = B_TRUE; |
| (void) script_start(dsmp, isv6 ? EVENT_DROP6 : |
| EVENT_DROP, dhcp_drop, NULL, NULL); |
| continue; |
| } |
| |
| /* |
| * Ignore this state machine if nothing interesting has |
| * happened. |
| */ |
| if (!lif_finished && dsmp->dsm_lif_down == 0 && |
| (dsmp->dsm_leases != NULL || !lease_removed)) |
| continue; |
| |
| /* |
| * If we're still waiting for DAD to complete on some of the |
| * configured LIFs, then don't send a response. |
| */ |
| if (dsmp->dsm_lif_wait != 0) { |
| dhcpmsg(MSG_VERBOSE, "rtsock_event: %s still has %d " |
| "LIFs waiting on DAD", dsmp->dsm_name, |
| dsmp->dsm_lif_wait); |
| continue; |
| } |
| |
| /* |
| * If we have some failed LIFs, then handle them now. We'll |
| * remove them from the list. Any leases that become empty are |
| * also removed as part of the decline-generation process. |
| */ |
| if (dsmp->dsm_lif_down != 0) |
| send_declines(dsmp); |
| |
| if (dsmp->dsm_leases == NULL) { |
| dsmp->dsm_bad_offers++; |
| /* |
| * For DHCPv6, we'll process the restart once we're |
| * done sending Decline messages, because these are |
| * supposed to be acknowledged. With DHCPv4, there's |
| * no acknowledgment for a DECLINE, so after sending |
| * it, we just restart right away. |
| */ |
| if (!dsmp->dsm_isv6) { |
| dhcpmsg(MSG_VERBOSE, "rtsock_event: %s has no " |
| "LIFs left", dsmp->dsm_name); |
| dhcp_restart(dsmp); |
| } |
| } else { |
| /* |
| * If we're now up on at least some of the leases and |
| * we were waiting for that, then kick off the rest of |
| * configuration. Lease validation and DAD are done. |
| */ |
| dhcpmsg(MSG_VERBOSE, "rtsock_event: all LIFs verified " |
| "on %s in %s state", dsmp->dsm_name, |
| dhcp_state_to_string(oldstate)); |
| if (oldstate == PRE_BOUND || |
| oldstate == ADOPTING) |
| dhcp_bound_complete(dsmp); |
| if (oldstate == ADOPTING) |
| dhcp_adopt_complete(dsmp); |
| } |
| } |
| } |
| |
| /* |
| * check_cmd_allowed(): check whether the requested command is allowed in the |
| * state specified. |
| * |
| * input: DHCPSTATE: current state |
| * dhcp_ipc_type_t: requested command |
| * output: boolean_t: B_TRUE if command is allowed in this state |
| */ |
| |
| boolean_t |
| check_cmd_allowed(DHCPSTATE state, dhcp_ipc_type_t cmd) |
| { |
| return (ipc_cmd_allowed[state][cmd] != 0); |
| } |
| |
| static boolean_t |
| is_iscsi_active(void) |
| { |
| int fd; |
| int active; |
| |
| if ((fd = open(ISCSI_DRIVER_DEVCTL, O_RDONLY)) == -1) { |
| return (B_FALSE); |
| } |
| |
| if ((ioctl(fd, ISCSI_IS_ACTIVE, &active)) != 0) { |
| active = 0; |
| } |
| (void) close(fd); |
| if (active) { |
| return (B_TRUE); |
| } else { |
| return (B_FALSE); |
| } |
| } |