| /* |
| * 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. |
| * |
| * BOUND state of the DHCP client state machine. |
| */ |
| |
| #include <sys/socket.h> |
| #include <sys/types.h> |
| #include <string.h> |
| #include <netinet/in.h> |
| #include <sys/sockio.h> |
| #include <unistd.h> |
| #include <time.h> |
| #include <arpa/inet.h> |
| #include <stdlib.h> |
| #include <search.h> |
| #include <sys/sysmacros.h> |
| #include <dhcp_hostconf.h> |
| #include <dhcpagent_util.h> |
| #include <dhcpmsg.h> |
| |
| #include "states.h" |
| #include "packet.h" |
| #include "util.h" |
| #include "agent.h" |
| #include "interface.h" |
| #include "script_handler.h" |
| |
| /* |
| * Possible outcomes for IPv6 binding attempt. |
| */ |
| enum v6_bind_result { |
| v6Restart, /* report failure and restart state machine */ |
| v6Resent, /* new Request message has been sent */ |
| v6Done /* successful binding */ |
| }; |
| |
| static enum v6_bind_result configure_v6_leases(dhcp_smach_t *); |
| static boolean_t configure_v4_lease(dhcp_smach_t *); |
| static boolean_t configure_v4_timers(dhcp_smach_t *); |
| |
| /* |
| * bound_event_cb(): callback for script_start on the event EVENT_BOUND |
| * |
| * input: dhcp_smach_t *: the state machine configured |
| * void *: unused |
| * output: int: always 1 |
| */ |
| |
| /* ARGSUSED1 */ |
| static int |
| bound_event_cb(dhcp_smach_t *dsmp, void *arg) |
| { |
| if (dsmp->dsm_ia.ia_fd != -1) |
| ipc_action_finish(dsmp, DHCP_IPC_SUCCESS); |
| else |
| async_finish(dsmp); |
| return (1); |
| } |
| |
| /* |
| * dhcp_bound(): configures an state machine and interfaces using information |
| * contained in the ACK/Reply packet and sets up lease timers. |
| * Before starting, the requested address is verified by |
| * Duplicate Address Detection to make sure it's not in use. |
| * |
| * input: dhcp_smach_t *: the state machine to move to bound |
| * PKT_LIST *: the ACK/Reply packet, or NULL to use dsmp->dsm_ack |
| * output: boolean_t: B_TRUE on success, B_FALSE on failure |
| */ |
| |
| boolean_t |
| dhcp_bound(dhcp_smach_t *dsmp, PKT_LIST *ack) |
| { |
| DHCPSTATE oldstate; |
| lease_t new_lease; |
| dhcp_lif_t *lif; |
| dhcp_lease_t *dlp; |
| enum v6_bind_result v6b; |
| |
| if (ack != NULL) { |
| /* If ack we're replacing is not the original, then free it */ |
| if (dsmp->dsm_ack != dsmp->dsm_orig_ack) |
| free_pkt_entry(dsmp->dsm_ack); |
| dsmp->dsm_ack = ack; |
| /* Save the first ack as the original */ |
| if (dsmp->dsm_orig_ack == NULL) |
| dsmp->dsm_orig_ack = ack; |
| } |
| |
| oldstate = dsmp->dsm_state; |
| switch (oldstate) { |
| |
| case ADOPTING: |
| /* Note that adoption occurs only for IPv4 DHCP. */ |
| |
| /* Ignore BOOTP */ |
| if (ack->opts[CD_DHCP_TYPE] == NULL) |
| return (B_FALSE); |
| |
| /* |
| * if we're adopting a lease, the lease timers |
| * only provide an upper bound since we don't know |
| * from what time they are relative to. assume we |
| * have a lease time of at most DHCP_ADOPT_LEASE_MAX. |
| */ |
| (void) memcpy(&new_lease, ack->opts[CD_LEASE_TIME]->value, |
| sizeof (lease_t)); |
| |
| new_lease = htonl(MIN(ntohl(new_lease), DHCP_ADOPT_LEASE_MAX)); |
| |
| (void) memcpy(ack->opts[CD_LEASE_TIME]->value, &new_lease, |
| sizeof (lease_t)); |
| |
| /* |
| * we have no idea when the REQUEST that generated |
| * this ACK was sent, but for diagnostic purposes |
| * we'll assume its close to the current time. |
| */ |
| dsmp->dsm_newstart_monosec = monosec(); |
| |
| if (dsmp->dsm_isv6) { |
| if ((v6b = configure_v6_leases(dsmp)) != v6Done) |
| return (v6b == v6Resent); |
| } else { |
| if (!configure_v4_lease(dsmp)) |
| return (B_FALSE); |
| |
| if (!configure_v4_timers(dsmp)) |
| return (B_FALSE); |
| } |
| |
| dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; |
| break; |
| |
| case SELECTING: |
| case REQUESTING: |
| case INIT_REBOOT: |
| |
| if (dsmp->dsm_isv6) { |
| if ((v6b = configure_v6_leases(dsmp)) != v6Done) |
| return (v6b == v6Resent); |
| } else { |
| if (!configure_v4_lease(dsmp)) |
| return (B_FALSE); |
| |
| if (!configure_v4_timers(dsmp)) |
| return (B_FALSE); |
| |
| if (!clear_lif_deprecated(dsmp->dsm_lif)) |
| return (B_FALSE); |
| } |
| |
| /* Stop sending requests now */ |
| stop_pkt_retransmission(dsmp); |
| |
| /* |
| * If we didn't end up with any usable leases, then we have a |
| * problem. |
| */ |
| if (dsmp->dsm_leases == NULL) { |
| dhcpmsg(MSG_WARNING, |
| "dhcp_bound: no address lease established"); |
| return (B_FALSE); |
| } |
| |
| /* |
| * If this is a Rapid-Commit (selecting state) or if we're |
| * dealing with a reboot (init-reboot), then we will have a new |
| * server ID to save. |
| */ |
| if (ack != NULL && |
| (oldstate == SELECTING || oldstate == INIT_REBOOT) && |
| dsmp->dsm_isv6 && !save_server_id(dsmp, ack)) { |
| dhcpmsg(MSG_ERROR, |
| "dhcp_bound: unable to save server ID on %s", |
| dsmp->dsm_name); |
| return (B_FALSE); |
| } |
| |
| /* |
| * We will continue configuring the interfaces via |
| * dhcp_bound_complete, once kernel DAD completes. If no new |
| * leases were created (which can happen on an init-reboot used |
| * for link-up confirmation), then go straight to bound state. |
| */ |
| if (!set_smach_state(dsmp, PRE_BOUND)) |
| return (B_FALSE); |
| if (dsmp->dsm_lif_wait == 0) |
| dhcp_bound_complete(dsmp); |
| break; |
| |
| case PRE_BOUND: |
| case BOUND: |
| case INFORMATION: |
| /* This is just a duplicate ack; silently ignore it */ |
| return (B_TRUE); |
| |
| case RENEWING: |
| case REBINDING: |
| |
| if (dsmp->dsm_isv6) { |
| if ((v6b = configure_v6_leases(dsmp)) != v6Done) |
| return (v6b == v6Resent); |
| } else { |
| if (!configure_v4_timers(dsmp)) |
| return (B_FALSE); |
| if (!clear_lif_deprecated(dsmp->dsm_lif)) |
| return (B_FALSE); |
| } |
| |
| /* |
| * If some or all of the leases were torn down by the server, |
| * then handle that as an expiry. When the script is done |
| * running for the LOSS6 event, we'll end up back here. |
| */ |
| if ((lif = find_expired_lif(dsmp)) != NULL) { |
| hold_lif(lif); |
| dhcp_expire(NULL, lif); |
| while ((lif = find_expired_lif(dsmp)) != NULL) { |
| dlp = lif->lif_lease; |
| unplumb_lif(lif); |
| if (dlp->dl_nlifs == 0) |
| remove_lease(dlp); |
| } |
| if (dsmp->dsm_leases == NULL) |
| return (B_FALSE); |
| } |
| |
| if (oldstate == REBINDING && dsmp->dsm_isv6 && |
| !save_server_id(dsmp, ack)) { |
| return (B_FALSE); |
| } |
| |
| /* |
| * Handle Renew/Rebind that fails to address one of our leases. |
| * (Should just never happen, but RFC 3315 section 18.1.8 |
| * requires it, and TAHI tests for it.) |
| */ |
| for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { |
| if (dlp->dl_stale && dlp->dl_nlifs > 0) |
| break; |
| } |
| if (dlp != NULL) { |
| dhcpmsg(MSG_DEBUG, "dhcp_bound: lease not updated; " |
| "allow retransmit"); |
| return (B_TRUE); |
| } |
| |
| if (!set_smach_state(dsmp, BOUND)) |
| return (B_FALSE); |
| |
| (void) script_start(dsmp, dsmp->dsm_isv6 ? EVENT_EXTEND6 : |
| EVENT_EXTEND, bound_event_cb, NULL, NULL); |
| |
| dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; |
| |
| /* Stop sending requests now */ |
| stop_pkt_retransmission(dsmp); |
| break; |
| |
| case INFORM_SENT: |
| |
| if (dsmp->dsm_isv6 && !save_server_id(dsmp, ack)) { |
| return (B_FALSE); |
| } |
| |
| (void) bound_event_cb(dsmp, NULL); |
| if (!set_smach_state(dsmp, INFORMATION)) |
| return (B_FALSE); |
| |
| /* Stop sending requests now */ |
| stop_pkt_retransmission(dsmp); |
| break; |
| |
| default: |
| /* something is really bizarre... */ |
| dhcpmsg(MSG_DEBUG, |
| "dhcp_bound: called in unexpected state: %s", |
| dhcp_state_to_string(dsmp->dsm_state)); |
| return (B_FALSE); |
| } |
| |
| /* |
| * remove any stale hostconf file that might be lying around for |
| * this state machine. (in general, it's harmless, since we'll write a |
| * fresh one when we exit anyway, but just to reduce confusion..) |
| */ |
| |
| (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); |
| return (B_TRUE); |
| } |
| |
| /* |
| * dhcp_bound_complete(): complete interface configuration after DAD |
| * |
| * input: dhcp_smach_t *: the state machine now ready |
| * output: none |
| */ |
| |
| void |
| dhcp_bound_complete(dhcp_smach_t *dsmp) |
| { |
| PKT_LIST *ack; |
| DHCP_OPT *router_list; |
| int i; |
| DHCPSTATE oldstate; |
| dhcp_lif_t *lif; |
| |
| /* |
| * Do bound state entry processing only if running IPv4. There's no |
| * need for this with DHCPv6 because link-locals are used for I/O and |
| * because DHCPv6 isn't entangled with routing. |
| */ |
| if (dsmp->dsm_isv6) { |
| (void) set_smach_state(dsmp, BOUND); |
| dhcpmsg(MSG_DEBUG, "dhcp_bound_complete: bound %s", |
| dsmp->dsm_name); |
| (void) script_start(dsmp, EVENT_BOUND6, bound_event_cb, NULL, |
| NULL); |
| dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; |
| return; |
| } |
| |
| /* |
| * Add each provided router; we'll clean them up when the |
| * state machine goes away or when our lease expires. |
| * |
| * Note that we do not handle default routers on IPv4 logicals; |
| * see README for details. |
| */ |
| |
| ack = dsmp->dsm_ack; |
| router_list = ack->opts[CD_ROUTER]; |
| lif = dsmp->dsm_lif; |
| if (router_list != NULL && |
| (router_list->len % sizeof (ipaddr_t)) == 0 && |
| strchr(lif->lif_name, ':') == NULL && |
| !lif->lif_pif->pif_under_ipmp) { |
| |
| dsmp->dsm_nrouters = router_list->len / sizeof (ipaddr_t); |
| dsmp->dsm_routers = malloc(router_list->len); |
| if (dsmp->dsm_routers == NULL) { |
| dhcpmsg(MSG_ERR, "dhcp_bound_complete: cannot allocate " |
| "default router list, ignoring default routers"); |
| dsmp->dsm_nrouters = 0; |
| } |
| |
| for (i = 0; i < dsmp->dsm_nrouters; i++) { |
| |
| (void) memcpy(&dsmp->dsm_routers[i].s_addr, |
| router_list->value + (i * sizeof (ipaddr_t)), |
| sizeof (ipaddr_t)); |
| |
| if (!add_default_route(lif->lif_pif->pif_index, |
| &dsmp->dsm_routers[i])) { |
| dhcpmsg(MSG_ERR, "dhcp_bound_complete: cannot " |
| "add default router %s on %s", inet_ntoa( |
| dsmp->dsm_routers[i]), dsmp->dsm_name); |
| dsmp->dsm_routers[i].s_addr = htonl(INADDR_ANY); |
| continue; |
| } |
| |
| dhcpmsg(MSG_INFO, "added default router %s on %s", |
| inet_ntoa(dsmp->dsm_routers[i]), dsmp->dsm_name); |
| } |
| } |
| |
| oldstate = dsmp->dsm_state; |
| if (!set_smach_state(dsmp, BOUND)) { |
| dhcpmsg(MSG_ERR, |
| "dhcp_bound_complete: cannot set bound state on %s", |
| dsmp->dsm_name); |
| return; |
| } |
| |
| dhcpmsg(MSG_DEBUG, "dhcp_bound_complete: bound %s", dsmp->dsm_name); |
| |
| /* |
| * We're now committed to this binding, so if it came from BOOTP, set |
| * the flag. |
| */ |
| |
| if (ack->opts[CD_DHCP_TYPE] == NULL) |
| dsmp->dsm_dflags |= DHCP_IF_BOOTP; |
| |
| /* |
| * If the previous state was ADOPTING, event loop has not been started |
| * at this time; so don't run the EVENT_BOUND script. |
| */ |
| if (oldstate != ADOPTING) { |
| (void) script_start(dsmp, EVENT_BOUND, bound_event_cb, NULL, |
| NULL); |
| } |
| |
| dsmp->dsm_curstart_monosec = dsmp->dsm_newstart_monosec; |
| } |
| |
| /* |
| * fuzzify(): adds some "fuzz" to a t1/t2 time, in accordance with RFC2131. |
| * We use up to plus or minus 2% jitter in the time. This is a |
| * small value, but the timers involved are typically long. A |
| * common T1 value is one day, and the fuzz is up to 28.8 minutes; |
| * plenty of time to make sure that individual clients don't renew |
| * all at the same time. |
| * |
| * input: uint32_t: the number of seconds until lease expiration |
| * double: the approximate percentage of that time to return |
| * output: double: a number approximating (sec * pct) |
| */ |
| |
| static double |
| fuzzify(uint32_t sec, double pct) |
| { |
| return (sec * (pct + (drand48() - 0.5) / 25.0)); |
| } |
| |
| /* |
| * get_pkt_times(): pulls the lease times out of a v4 DHCP packet and stores |
| * them as host byte-order relative times in the passed in |
| * parameters. |
| * |
| * input: PKT_LIST *: the packet to pull the packet times from |
| * lease_t *: where to store the relative lease time in hbo |
| * lease_t *: where to store the relative t1 time in hbo |
| * lease_t *: where to store the relative t2 time in hbo |
| * output: void |
| */ |
| |
| static void |
| get_pkt_times(PKT_LIST *ack, lease_t *lease, lease_t *t1, lease_t *t2) |
| { |
| *lease = DHCP_PERM; |
| *t1 = DHCP_PERM; |
| *t2 = DHCP_PERM; |
| |
| if (ack->opts[CD_DHCP_TYPE] == NULL) { |
| dhcpmsg(MSG_VERBOSE, |
| "get_pkt_times: BOOTP response; infinite lease"); |
| return; |
| } |
| if (ack->opts[CD_LEASE_TIME] == NULL) { |
| dhcpmsg(MSG_VERBOSE, |
| "get_pkt_times: no lease option provided"); |
| return; |
| } |
| if (ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { |
| dhcpmsg(MSG_VERBOSE, "get_pkt_times: invalid lease option"); |
| } |
| |
| (void) memcpy(lease, ack->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); |
| *lease = ntohl(*lease); |
| |
| if (*lease == DHCP_PERM) { |
| dhcpmsg(MSG_VERBOSE, "get_pkt_times: infinite lease granted"); |
| return; |
| } |
| |
| if (ack->opts[CD_T1_TIME] != NULL && |
| ack->opts[CD_T1_TIME]->len == sizeof (lease_t)) { |
| (void) memcpy(t1, ack->opts[CD_T1_TIME]->value, sizeof (*t1)); |
| *t1 = ntohl(*t1); |
| } |
| |
| if (ack->opts[CD_T2_TIME] != NULL && |
| ack->opts[CD_T2_TIME]->len == sizeof (lease_t)) { |
| (void) memcpy(t2, ack->opts[CD_T2_TIME]->value, sizeof (*t2)); |
| *t2 = ntohl(*t2); |
| } |
| |
| if ((*t1 == DHCP_PERM) || (*t1 >= *lease)) |
| *t1 = (lease_t)fuzzify(*lease, DHCP_T1_FACT); |
| |
| if ((*t2 == DHCP_PERM) || (*t2 > *lease) || (*t2 <= *t1)) |
| *t2 = (lease_t)fuzzify(*lease, DHCP_T2_FACT); |
| |
| dhcpmsg(MSG_VERBOSE, "get_pkt_times: lease %u t1 %u t2 %u", |
| *lease, *t1, *t2); |
| } |
| |
| /* |
| * configure_v4_timers(): configures the lease timers on a v4 state machine |
| * |
| * input: dhcp_smach_t *: the state machine to configure |
| * output: boolean_t: B_TRUE on success, B_FALSE on failure |
| */ |
| |
| static boolean_t |
| configure_v4_timers(dhcp_smach_t *dsmp) |
| { |
| PKT_LIST *ack = dsmp->dsm_ack; |
| lease_t lease, t1, t2; |
| dhcp_lease_t *dlp; |
| dhcp_lif_t *lif; |
| |
| /* v4 has just one lease per state machine, and one LIF */ |
| dlp = dsmp->dsm_leases; |
| lif = dlp->dl_lifs; |
| |
| /* |
| * If it's DHCP, but there's no valid lease time, then complain, |
| * decline the lease and return error. |
| */ |
| if (ack->opts[CD_DHCP_TYPE] != NULL && |
| (ack->opts[CD_LEASE_TIME] == NULL || |
| ack->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { |
| lif_mark_decline(lif, "Missing or corrupted lease time"); |
| send_declines(dsmp); |
| dhcpmsg(MSG_WARNING, "configure_v4_timers: %s lease time in " |
| "ACK on %s", ack->opts[CD_LEASE_TIME] == NULL ? "missing" : |
| "corrupt", dsmp->dsm_name); |
| return (B_FALSE); |
| } |
| |
| /* Stop the T1 and T2 timers */ |
| cancel_lease_timers(dlp); |
| |
| /* Stop the LEASE timer */ |
| cancel_lif_timers(lif); |
| |
| /* |
| * type has already been verified as ACK. if type is not set, |
| * then we got a BOOTP packet. we now fetch the t1, t2, and |
| * lease options out of the packet into variables. they are |
| * returned as relative host-byte-ordered times. |
| */ |
| |
| get_pkt_times(ack, &lease, &t1, &t2); |
| |
| /* |
| * if the current lease is mysteriously close to the new |
| * lease, warn the user. unless there's less than a minute |
| * left, round to the closest minute. |
| */ |
| |
| if (lif->lif_expire.dt_start != 0 && |
| abs((dsmp->dsm_newstart_monosec + lease) - |
| (dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start)) < |
| DHCP_LEASE_EPS) { |
| const char *noext = "configure_v4_timers: lease renewed but " |
| "time not extended"; |
| int msg_level; |
| uint_t minleft; |
| |
| if (lif->lif_expire.dt_start < DHCP_LEASE_ERROR_THRESH) |
| msg_level = MSG_ERROR; |
| else |
| msg_level = MSG_VERBOSE; |
| |
| minleft = (lif->lif_expire.dt_start + 30) / 60; |
| |
| if (lif->lif_expire.dt_start < 60) { |
| dhcpmsg(msg_level, "%s; expires in %d seconds", |
| noext, lif->lif_expire.dt_start); |
| } else if (minleft == 1) { |
| dhcpmsg(msg_level, "%s; expires in 1 minute", noext); |
| } else if (minleft > 120) { |
| dhcpmsg(msg_level, "%s; expires in %d hours", |
| noext, (minleft + 30) / 60); |
| } else { |
| dhcpmsg(msg_level, "%s; expires in %d minutes", |
| noext, minleft); |
| } |
| } |
| |
| init_timer(&dlp->dl_t1, t1); |
| init_timer(&dlp->dl_t2, t2); |
| init_timer(&lif->lif_expire, lease); |
| |
| if (lease == DHCP_PERM) { |
| dhcpmsg(MSG_INFO, |
| "configure_v4_timers: %s acquired permanent lease", |
| dsmp->dsm_name); |
| return (B_TRUE); |
| } |
| |
| dhcpmsg(MSG_INFO, "configure_v4_timers: %s acquired lease, expires %s", |
| dsmp->dsm_name, |
| monosec_to_string(dsmp->dsm_newstart_monosec + lease)); |
| |
| dhcpmsg(MSG_INFO, "configure_v4_timers: %s begins renewal at %s", |
| dsmp->dsm_name, monosec_to_string(dsmp->dsm_newstart_monosec + |
| dlp->dl_t1.dt_start)); |
| |
| dhcpmsg(MSG_INFO, "configure_v4_timers: %s begins rebinding at %s", |
| dsmp->dsm_name, monosec_to_string(dsmp->dsm_newstart_monosec + |
| dlp->dl_t2.dt_start)); |
| |
| /* |
| * according to RFC2131, there is no minimum lease time, but don't |
| * set up renew/rebind timers if lease is shorter than DHCP_REBIND_MIN. |
| */ |
| |
| if (!schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire)) |
| goto failure; |
| |
| if (lease < DHCP_REBIND_MIN) { |
| dhcpmsg(MSG_WARNING, "configure_v4_timers: lease on %s is for " |
| "less than %d seconds!", dsmp->dsm_name, DHCP_REBIND_MIN); |
| return (B_TRUE); |
| } |
| |
| if (!schedule_lease_timer(dlp, &dlp->dl_t1, dhcp_renew)) |
| goto failure; |
| |
| if (!schedule_lease_timer(dlp, &dlp->dl_t2, dhcp_rebind)) |
| goto failure; |
| |
| return (B_TRUE); |
| |
| failure: |
| cancel_lease_timers(dlp); |
| cancel_lif_timers(lif); |
| dhcpmsg(MSG_WARNING, |
| "configure_v4_timers: cannot schedule lease timers"); |
| return (B_FALSE); |
| } |
| |
| /* |
| * configure_v6_leases(): configures the IPv6 leases on a state machine from |
| * the current DHCPv6 ACK. We need to scan the ACK, |
| * create a lease for each IA_NA, and a new LIF for each |
| * IAADDR. |
| * |
| * input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack) |
| * output: enum v6_bind_result: restart, resend, or done |
| */ |
| |
| static enum v6_bind_result |
| configure_v6_leases(dhcp_smach_t *dsmp) |
| { |
| const dhcpv6_option_t *d6o, *d6so, *d6sso; |
| const char *optbase, *estr, *msg; |
| uint_t olen, solen, ssolen, msglen; |
| dhcpv6_ia_na_t d6in; |
| dhcpv6_iaaddr_t d6ia; |
| dhcp_lease_t *dlp; |
| uint32_t shortest; |
| dhcp_lif_t *lif; |
| uint_t nlifs; |
| boolean_t got_iana = B_FALSE; |
| uint_t scode; |
| |
| for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) |
| dlp->dl_stale = B_TRUE; |
| |
| d6o = NULL; |
| while ((d6o = dhcpv6_pkt_option(dsmp->dsm_ack, d6o, DHCPV6_OPT_IA_NA, |
| &olen)) != NULL) { |
| if (olen < sizeof (d6in)) { |
| dhcpmsg(MSG_WARNING, |
| "configure_v6_leases: garbled IA_NA"); |
| continue; |
| } |
| |
| /* |
| * Check the IAID. It should be for our controlling LIF. If a |
| * single state machine needs to use multiple IAIDs, then this |
| * will need to change. |
| */ |
| (void) memcpy(&d6in, d6o, sizeof (d6in)); |
| d6in.d6in_iaid = ntohl(d6in.d6in_iaid); |
| if (d6in.d6in_iaid != dsmp->dsm_lif->lif_iaid) { |
| dhcpmsg(MSG_WARNING, "configure_v6_leases: ignored " |
| "IA_NA for IAID %x (not %x)", d6in.d6in_iaid, |
| dsmp->dsm_lif->lif_iaid); |
| continue; |
| } |
| |
| /* |
| * See notes below; there's only one IA_NA and a single IAID |
| * for now. |
| */ |
| if ((dlp = dsmp->dsm_leases) != NULL) |
| dlp->dl_stale = B_FALSE; |
| |
| /* |
| * Note that some bug-ridden servers will try to give us |
| * multiple IA_NA options for a single IAID. We ignore |
| * duplicates. |
| */ |
| if (got_iana) { |
| dhcpmsg(MSG_WARNING, "configure_v6_leases: unexpected " |
| "extra IA_NA ignored"); |
| continue; |
| } |
| |
| d6in.d6in_t1 = ntohl(d6in.d6in_t1); |
| d6in.d6in_t2 = ntohl(d6in.d6in_t2); |
| |
| /* RFC 3315 required check for invalid T1/T2 combinations */ |
| if (d6in.d6in_t1 > d6in.d6in_t2 && d6in.d6in_t2 != 0) { |
| dhcpmsg(MSG_WARNING, "configure_v6_leases: ignored " |
| "IA_NA with invalid T1 %u > T2 %u", d6in.d6in_t1, |
| d6in.d6in_t2); |
| continue; |
| } |
| |
| /* |
| * There may be a status code here. Process if present. |
| */ |
| optbase = (const char *)d6o + sizeof (d6in); |
| olen -= sizeof (d6in); |
| d6so = dhcpv6_find_option(optbase, olen, NULL, |
| DHCPV6_OPT_STATUS_CODE, &solen); |
| scode = dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen); |
| if (scode != DHCPV6_STAT_SUCCESS) { |
| dhcpmsg(MSG_WARNING, |
| "configure_v6_leases: IA_NA: %s: %.*s", |
| estr, msglen, msg); |
| } |
| print_server_msg(dsmp, msg, msglen); |
| |
| /* |
| * Other errors are possible here. According to RFC 3315 |
| * section 18.1.8, we ignore the entire IA if it gives the "no |
| * addresses" status code. We may try another server if we |
| * like -- we instead opt to allow the addresses to expire and |
| * then try a new server. |
| * |
| * If the status code is "no binding," then we must go back and |
| * redo the Request. Surprisingly, it doesn't matter if it's |
| * any other code. |
| */ |
| if (scode == DHCPV6_STAT_NOADDRS) { |
| dhcpmsg(MSG_DEBUG, "configure_v6_leases: ignoring " |
| "no-addrs status in IA_NA"); |
| continue; |
| } |
| |
| if (scode == DHCPV6_STAT_NOBINDING) { |
| send_v6_request(dsmp); |
| return (v6Resent); |
| } |
| |
| /* |
| * Find or create the lease structure. This part is simple, |
| * because we support only IA_NA and a single IAID. This means |
| * there's only one lease structure. The design supports |
| * multiple lease structures so that IA_TA and IA_PD can be |
| * added later. |
| */ |
| if ((dlp = dsmp->dsm_leases) == NULL && |
| (dlp = insert_lease(dsmp)) == NULL) { |
| dhcpmsg(MSG_ERROR, "configure_v6_leases: unable to " |
| "allocate memory for lease"); |
| return (v6Restart); |
| } |
| |
| /* |
| * Iterate over the IAADDR options contained within this IA_NA. |
| */ |
| shortest = DHCPV6_INFTIME; |
| d6so = NULL; |
| while ((d6so = dhcpv6_find_option(optbase, olen, d6so, |
| DHCPV6_OPT_IAADDR, &solen)) != NULL) { |
| if (solen < sizeof (d6ia)) { |
| dhcpmsg(MSG_WARNING, |
| "configure_v6_leases: garbled IAADDR"); |
| continue; |
| } |
| (void) memcpy(&d6ia, d6so, sizeof (d6ia)); |
| |
| d6ia.d6ia_preflife = ntohl(d6ia.d6ia_preflife); |
| d6ia.d6ia_vallife = ntohl(d6ia.d6ia_vallife); |
| |
| /* RFC 3315 required validity check */ |
| if (d6ia.d6ia_preflife > d6ia.d6ia_vallife) { |
| dhcpmsg(MSG_WARNING, |
| "configure_v6_leases: ignored IAADDR with " |
| "preferred lifetime %u > valid %u", |
| d6ia.d6ia_preflife, d6ia.d6ia_vallife); |
| continue; |
| } |
| |
| /* |
| * RFC 3315 allows a status code to be buried inside |
| * the IAADDR option. Look for it, and process if |
| * present. Process in a manner similar to that for |
| * the IA itself; TAHI checks for this. Real servers |
| * likely won't do this. |
| */ |
| d6sso = dhcpv6_find_option((const char *)d6so + |
| sizeof (d6ia), solen - sizeof (d6ia), NULL, |
| DHCPV6_OPT_STATUS_CODE, &ssolen); |
| scode = dhcpv6_status_code(d6sso, ssolen, &estr, &msg, |
| &msglen); |
| print_server_msg(dsmp, msg, msglen); |
| if (scode == DHCPV6_STAT_NOADDRS) { |
| dhcpmsg(MSG_DEBUG, "configure_v6_leases: " |
| "ignoring no-addrs status in IAADDR"); |
| continue; |
| } |
| if (scode == DHCPV6_STAT_NOBINDING) { |
| send_v6_request(dsmp); |
| return (v6Resent); |
| } |
| if (scode != DHCPV6_STAT_SUCCESS) { |
| dhcpmsg(MSG_WARNING, |
| "configure_v6_leases: IAADDR: %s", estr); |
| } |
| |
| /* |
| * Locate the existing LIF within the lease associated |
| * with this address, if any. |
| */ |
| lif = dlp->dl_lifs; |
| for (nlifs = dlp->dl_nlifs; nlifs > 0; |
| nlifs--, lif = lif->lif_next) { |
| if (IN6_ARE_ADDR_EQUAL(&d6ia.d6ia_addr, |
| &lif->lif_v6addr)) |
| break; |
| } |
| |
| /* |
| * If the server has set the lifetime to zero, then |
| * delete the LIF. Otherwise, set the new LIF expiry |
| * time, adding the LIF if necessary. |
| */ |
| if (d6ia.d6ia_vallife == 0) { |
| /* If it was found, then it's expired */ |
| if (nlifs != 0) { |
| dhcpmsg(MSG_DEBUG, |
| "configure_v6_leases: lif %s has " |
| "expired", lif->lif_name); |
| lif->lif_expired = B_TRUE; |
| } |
| continue; |
| } |
| |
| /* If it wasn't found, then create it now. */ |
| if (nlifs == 0) { |
| lif = plumb_lif(dsmp->dsm_lif->lif_pif, |
| &d6ia.d6ia_addr); |
| if (lif == NULL) |
| continue; |
| if (++dlp->dl_nlifs == 1) { |
| dlp->dl_lifs = lif; |
| } else { |
| remque(lif); |
| insque(lif, dlp->dl_lifs); |
| } |
| lif->lif_lease = dlp; |
| lif->lif_dad_wait = _B_TRUE; |
| dsmp->dsm_lif_wait++; |
| } else { |
| /* If it was found, cancel timer */ |
| cancel_lif_timers(lif); |
| if (d6ia.d6ia_preflife != 0 && |
| !clear_lif_deprecated(lif)) { |
| unplumb_lif(lif); |
| continue; |
| } |
| } |
| |
| /* Set the new expiry timers */ |
| init_timer(&lif->lif_preferred, d6ia.d6ia_preflife); |
| init_timer(&lif->lif_expire, d6ia.d6ia_vallife); |
| |
| /* |
| * If the preferred lifetime is over now, then the LIF |
| * is deprecated. If it's the same as the expiry time, |
| * then we don't need a separate timer for it. |
| */ |
| if (d6ia.d6ia_preflife == 0) { |
| set_lif_deprecated(lif); |
| } else if (d6ia.d6ia_preflife != DHCPV6_INFTIME && |
| d6ia.d6ia_preflife != d6ia.d6ia_vallife && |
| !schedule_lif_timer(lif, &lif->lif_preferred, |
| dhcp_deprecate)) { |
| unplumb_lif(lif); |
| continue; |
| } |
| |
| if (d6ia.d6ia_vallife != DHCPV6_INFTIME && |
| !schedule_lif_timer(lif, &lif->lif_expire, |
| dhcp_expire)) { |
| unplumb_lif(lif); |
| continue; |
| } |
| |
| if (d6ia.d6ia_preflife < shortest) |
| shortest = d6ia.d6ia_preflife; |
| } |
| |
| if (dlp->dl_nlifs == 0) { |
| dhcpmsg(MSG_WARNING, |
| "configure_v6_leases: no IAADDRs found in IA_NA"); |
| remove_lease(dlp); |
| continue; |
| } |
| |
| if (d6in.d6in_t1 == 0 && d6in.d6in_t2 == 0) { |
| /* Default values from RFC 3315: 0.5 and 0.8 */ |
| if ((d6in.d6in_t1 = shortest / 2) == 0) |
| d6in.d6in_t1 = 1; |
| d6in.d6in_t2 = shortest - shortest / 5; |
| } |
| |
| cancel_lease_timers(dlp); |
| init_timer(&dlp->dl_t1, d6in.d6in_t1); |
| init_timer(&dlp->dl_t2, d6in.d6in_t2); |
| |
| if ((d6in.d6in_t1 != DHCPV6_INFTIME && |
| !schedule_lease_timer(dlp, &dlp->dl_t1, dhcp_renew)) || |
| (d6in.d6in_t2 != DHCPV6_INFTIME && |
| !schedule_lease_timer(dlp, &dlp->dl_t2, dhcp_rebind))) { |
| dhcpmsg(MSG_WARNING, "configure_v6_leases: unable to " |
| "set renew/rebind timers"); |
| } else { |
| got_iana = B_TRUE; |
| } |
| } |
| |
| if (!got_iana) { |
| dhcpmsg(MSG_WARNING, |
| "configure_v6_leases: no usable IA_NA option found"); |
| } |
| |
| return (v6Done); |
| } |
| |
| /* |
| * configure_v4_lease(): configures the IPv4 lease on a state machine from |
| * the current DHCP ACK. There's only one lease and LIF |
| * per state machine in IPv4. |
| * |
| * input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack) |
| * output: boolean_t: B_TRUE on success, B_FALSE on failure |
| */ |
| |
| static boolean_t |
| configure_v4_lease(dhcp_smach_t *dsmp) |
| { |
| struct lifreq lifr; |
| struct sockaddr_in *sin; |
| PKT_LIST *ack = dsmp->dsm_ack; |
| dhcp_lease_t *dlp; |
| dhcp_lif_t *lif; |
| uint32_t addrhbo; |
| struct in_addr inaddr; |
| |
| /* |
| * if we're using DHCP, then we'll have a valid CD_SERVER_ID |
| * (we checked in dhcp_acknak()); set it now so that |
| * dsmp->dsm_server is valid in case we need to send_decline(). |
| * note that we use comparisons against opts[CD_DHCP_TYPE] |
| * since we haven't set DHCP_IF_BOOTP yet (we don't do that |
| * until we're sure we want the offered address.) |
| */ |
| |
| if (ack->opts[CD_DHCP_TYPE] != NULL) { |
| (void) memcpy(&inaddr, ack->opts[CD_SERVER_ID]->value, |
| sizeof (inaddr)); |
| IN6_INADDR_TO_V4MAPPED(&inaddr, &dsmp->dsm_server); |
| } |
| |
| /* |
| * There needs to be exactly one lease for IPv4, and that lease |
| * controls the main LIF for the state machine. If it doesn't exist |
| * yet, then create it now. |
| */ |
| if ((dlp = dsmp->dsm_leases) == NULL && |
| (dlp = insert_lease(dsmp)) == NULL) { |
| dhcpmsg(MSG_ERROR, "configure_v4_lease: unable to allocate " |
| "memory for lease"); |
| return (B_FALSE); |
| } |
| if (dlp->dl_nlifs == 0) { |
| dlp->dl_lifs = dsmp->dsm_lif; |
| dlp->dl_nlifs = 1; |
| |
| /* The lease holds a reference on the LIF */ |
| hold_lif(dlp->dl_lifs); |
| dlp->dl_lifs->lif_lease = dlp; |
| } |
| |
| lif = dlp->dl_lifs; |
| |
| IN6_INADDR_TO_V4MAPPED(&ack->pkt->yiaddr, &lif->lif_v6addr); |
| addrhbo = ntohl(ack->pkt->yiaddr.s_addr); |
| if ((addrhbo & IN_CLASSA_NET) == 0 || |
| (addrhbo >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET || |
| IN_CLASSD(addrhbo)) { |
| dhcpmsg(MSG_ERROR, |
| "configure_v4_lease: got invalid IP address %s for %s", |
| inet_ntoa(ack->pkt->yiaddr), lif->lif_name); |
| return (B_FALSE); |
| } |
| |
| (void) memset(&lifr, 0, sizeof (struct lifreq)); |
| (void) strlcpy(lifr.lifr_name, lif->lif_name, LIFNAMSIZ); |
| |
| /* |
| * bring the interface online. note that there is no optimal |
| * order here: it is considered bad taste (and in > solaris 7, |
| * likely illegal) to bring an interface up before it has an |
| * ip address. however, due to an apparent bug in sun fddi |
| * 5.0, fddi will not obtain a network routing entry unless |
| * the interface is brought up before it has an ip address. |
| * we take the lesser of the two evils; if fddi customers have |
| * problems, they can get a newer fddi distribution which |
| * fixes the problem. |
| */ |
| |
| sin = (struct sockaddr_in *)&lifr.lifr_addr; |
| sin->sin_family = AF_INET; |
| |
| (void) memset(&lif->lif_v6mask, 0xff, sizeof (lif->lif_v6mask)); |
| if (ack->opts[CD_SUBNETMASK] != NULL && |
| ack->opts[CD_SUBNETMASK]->len == sizeof (inaddr)) { |
| |
| (void) memcpy(&inaddr, ack->opts[CD_SUBNETMASK]->value, |
| sizeof (inaddr)); |
| |
| } else { |
| |
| if (ack->opts[CD_SUBNETMASK] != NULL && |
| ack->opts[CD_SUBNETMASK]->len != sizeof (inaddr)) { |
| dhcpmsg(MSG_WARNING, "configure_v4_lease: specified " |
| "subnet mask length is %d instead of %d, ignoring", |
| ack->opts[CD_SUBNETMASK]->len, sizeof (ipaddr_t)); |
| } else { |
| dhcpmsg(MSG_WARNING, "configure_v4_lease: no IP " |
| "netmask specified for %s, making best guess", |
| lif->lif_name); |
| } |
| |
| /* |
| * no legitimate IP subnet mask specified.. use best |
| * guess. recall that lif_addr is in network order, so |
| * imagine it's 0x11223344: then when it is read into |
| * a register on x86, it becomes 0x44332211, so we |
| * must ntohl() it to convert it to 0x11223344 in |
| * order to use the macros in <netinet/in.h>. |
| */ |
| |
| if (IN_CLASSA(addrhbo)) |
| inaddr.s_addr = htonl(IN_CLASSA_NET); |
| else if (IN_CLASSB(addrhbo)) |
| inaddr.s_addr = htonl(IN_CLASSB_NET); |
| else if (IN_CLASSC(addrhbo)) |
| inaddr.s_addr = htonl(IN_CLASSC_NET); |
| else { |
| /* |
| * Cant be Class D as that is multicast |
| * Must be Class E |
| */ |
| inaddr.s_addr = htonl(IN_CLASSE_NET); |
| } |
| } |
| lif->lif_v6mask._S6_un._S6_u32[3] = inaddr.s_addr; |
| |
| sin->sin_addr = inaddr; |
| dhcpmsg(MSG_INFO, "configure_v4_lease: setting IP netmask to %s on %s", |
| inet_ntoa(sin->sin_addr), lif->lif_name); |
| |
| if (ioctl(v4_sock_fd, SIOCSLIFNETMASK, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "configure_v4_lease: cannot set IP netmask " |
| "on %s", lif->lif_name); |
| return (B_FALSE); |
| } |
| |
| IN6_V4MAPPED_TO_INADDR(&lif->lif_v6addr, &sin->sin_addr); |
| dhcpmsg(MSG_INFO, "configure_v4_lease: setting IP address to %s on %s", |
| inet_ntoa(sin->sin_addr), lif->lif_name); |
| |
| if (ioctl(v4_sock_fd, SIOCSLIFADDR, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "configure_v4_lease: cannot set IP address " |
| "on %s", lif->lif_name); |
| return (B_FALSE); |
| } |
| |
| lif->lif_dad_wait = B_TRUE; |
| dsmp->dsm_lif_wait++; |
| |
| if (ack->opts[CD_BROADCASTADDR] != NULL && |
| ack->opts[CD_BROADCASTADDR]->len == sizeof (inaddr)) { |
| |
| (void) memcpy(&inaddr, ack->opts[CD_BROADCASTADDR]->value, |
| sizeof (inaddr)); |
| |
| } else { |
| |
| if (ack->opts[CD_BROADCASTADDR] != NULL && |
| ack->opts[CD_BROADCASTADDR]->len != sizeof (inaddr)) { |
| dhcpmsg(MSG_WARNING, "configure_v4_lease: specified " |
| "broadcast address length is %d instead of %d, " |
| "ignoring", ack->opts[CD_BROADCASTADDR]->len, |
| sizeof (inaddr)); |
| } else { |
| dhcpmsg(MSG_WARNING, "configure_v4_lease: no IP " |
| "broadcast specified for %s, making best guess", |
| lif->lif_name); |
| } |
| |
| /* |
| * no legitimate IP broadcast specified. compute it |
| * from the IP address and netmask. |
| */ |
| |
| IN6_V4MAPPED_TO_INADDR(&lif->lif_v6addr, &inaddr); |
| inaddr.s_addr |= ~lif->lif_v6mask._S6_un._S6_u32[3]; |
| } |
| |
| /* |
| * the kernel will set the broadcast address for us as part of |
| * bringing the interface up. since experience has shown that dhcp |
| * servers sometimes provide a bogus broadcast address, we let the |
| * kernel set it so that it's guaranteed to be correct. |
| * |
| * also, note any inconsistencies and save the broadcast address the |
| * kernel set so that we can watch for changes to it. |
| */ |
| |
| if (ioctl(v4_sock_fd, SIOCGLIFBRDADDR, &lifr) == -1) { |
| dhcpmsg(MSG_ERR, "configure_v4_lease: cannot get broadcast " |
| "address for %s", lif->lif_name); |
| return (B_FALSE); |
| } |
| |
| if (inaddr.s_addr != sin->sin_addr.s_addr) { |
| dhcpmsg(MSG_WARNING, "configure_v4_lease: incorrect broadcast " |
| "address %s specified for %s; ignoring", inet_ntoa(inaddr), |
| lif->lif_name); |
| } |
| |
| lif->lif_broadcast = sin->sin_addr.s_addr; |
| dhcpmsg(MSG_INFO, |
| "configure_v4_lease: using broadcast address %s on %s", |
| inet_ntoa(inaddr), lif->lif_name); |
| return (B_TRUE); |
| } |
| |
| /* |
| * save_server_id(): save off the new DHCPv6 Server ID |
| * |
| * input: dhcp_smach_t *: the state machine to use |
| * PKT_LIST *: the packet with the Reply message |
| * output: boolean_t: B_TRUE on success, B_FALSE on failure |
| */ |
| |
| boolean_t |
| save_server_id(dhcp_smach_t *dsmp, PKT_LIST *msg) |
| { |
| const dhcpv6_option_t *d6o; |
| uint_t olen; |
| |
| d6o = dhcpv6_pkt_option(msg, NULL, DHCPV6_OPT_SERVERID, &olen); |
| if (d6o == NULL) |
| return (B_FALSE); |
| olen -= sizeof (*d6o); |
| free(dsmp->dsm_serverid); |
| if ((dsmp->dsm_serverid = malloc(olen)) == NULL) { |
| return (B_FALSE); |
| } else { |
| dsmp->dsm_serveridlen = olen; |
| (void) memcpy(dsmp->dsm_serverid, d6o + 1, olen); |
| return (B_TRUE); |
| } |
| } |