Home | History | Annotate | Download | only in dhcpagent
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  *
     25  * DECLINE/RELEASE configuration functionality for the DHCP client.
     26  */
     27 
     28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     29 
     30 #include <sys/types.h>
     31 #include <unistd.h>
     32 #include <string.h>
     33 #include <netinet/in.h>
     34 #include <net/if.h>
     35 #include <netinet/dhcp.h>
     36 #include <netinet/dhcp6.h>
     37 #include <dhcpmsg.h>
     38 #include <dhcp_hostconf.h>
     39 #include <dhcpagent_util.h>
     40 
     41 #include "agent.h"
     42 #include "packet.h"
     43 #include "interface.h"
     44 #include "states.h"
     45 
     46 static boolean_t stop_release_decline(dhcp_smach_t *, unsigned int);
     47 
     48 /*
     49  * send_declines(): sends a DECLINE message (broadcasted for IPv4) to the
     50  *		    server to indicate a problem with the offered addresses.
     51  *		    The failing addresses are removed from the leases.
     52  *
     53  *   input: dhcp_smach_t *: the state machine sending DECLINE
     54  *  output: void
     55  */
     56 
     57 void
     58 send_declines(dhcp_smach_t *dsmp)
     59 {
     60 	dhcp_pkt_t	*dpkt;
     61 	dhcp_lease_t	*dlp, *dlpn;
     62 	uint_t		nlifs;
     63 	dhcp_lif_t	*lif, *lifn;
     64 	boolean_t	got_one;
     65 
     66 	/*
     67 	 * Create an empty DECLINE message.  We'll stuff the information into
     68 	 * this message as we find it.
     69 	 */
     70 	if (dsmp->dsm_isv6) {
     71 		if ((dpkt = init_pkt(dsmp, DHCPV6_MSG_DECLINE)) == NULL)
     72 			return;
     73 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
     74 		    dsmp->dsm_serverid, dsmp->dsm_serveridlen);
     75 	} else {
     76 		ipaddr_t serverip;
     77 
     78 		/*
     79 		 * If this ack is from BOOTP, then there's no way to send a
     80 		 * decline.  Note that since we haven't bound yet, we can't
     81 		 * just check the BOOTP flag.
     82 		 */
     83 		if (dsmp->dsm_ack->opts[CD_DHCP_TYPE] == NULL)
     84 			return;
     85 
     86 		if ((dpkt = init_pkt(dsmp, DECLINE)) == NULL)
     87 			return;
     88 		IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip);
     89 		(void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip);
     90 	}
     91 
     92 	/*
     93 	 * Loop over the leases, looking for ones with now-broken LIFs.  Add
     94 	 * each one found to the DECLINE message, and remove it from the list.
     95 	 * Also remove any completely declined leases.
     96 	 */
     97 	got_one = B_FALSE;
     98 	for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlpn) {
     99 		dlpn = dlp->dl_next;
    100 		lif = dlp->dl_lifs;
    101 		for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lifn) {
    102 			lifn = lif->lif_next;
    103 			if (lif->lif_declined != NULL) {
    104 				(void) add_pkt_lif(dpkt, lif,
    105 				    DHCPV6_STAT_UNSPECFAIL, lif->lif_declined);
    106 				unplumb_lif(lif);
    107 				got_one = B_TRUE;
    108 			}
    109 		}
    110 		if (dlp->dl_nlifs == 0)
    111 			remove_lease(dlp);
    112 	}
    113 
    114 	if (!got_one)
    115 		return;
    116 
    117 	(void) set_smach_state(dsmp, DECLINING);
    118 
    119 	if (dsmp->dsm_isv6) {
    120 		(void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
    121 		    stop_release_decline, DHCPV6_DEC_TIMEOUT, 0);
    122 	} else {
    123 		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
    124 
    125 		(void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), NULL);
    126 	}
    127 }
    128 
    129 /*
    130  * dhcp_release(): sends a RELEASE message to a DHCP server and removes
    131  *		   the all interfaces for the given state machine from DHCP
    132  *		   control.  Called back by script handler.
    133  *
    134  *   input: dhcp_smach_t *: the state machine to send the RELEASE on and remove
    135  *	    void *: an optional text explanation to send with the message
    136  *  output: int: 1 on success, 0 on failure
    137  */
    138 
    139 int
    140 dhcp_release(dhcp_smach_t *dsmp, void *arg)
    141 {
    142 	const char	*msg = arg;
    143 	dhcp_pkt_t	*dpkt;
    144 	dhcp_lease_t	*dlp;
    145 	dhcp_lif_t	*lif;
    146 	ipaddr_t	serverip;
    147 	uint_t		nlifs;
    148 
    149 	if ((dsmp->dsm_dflags & DHCP_IF_BOOTP) ||
    150 	    !check_cmd_allowed(dsmp->dsm_state, DHCP_RELEASE)) {
    151 		ipc_action_finish(dsmp, DHCP_IPC_E_INT);
    152 		return (0);
    153 	}
    154 
    155 	dhcpmsg(MSG_INFO, "releasing leases for state machine %s",
    156 	    dsmp->dsm_name);
    157 	(void) set_smach_state(dsmp, RELEASING);
    158 
    159 	if (dsmp->dsm_isv6) {
    160 		dpkt = init_pkt(dsmp, DHCPV6_MSG_RELEASE);
    161 		(void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
    162 		    dsmp->dsm_serverid, dsmp->dsm_serveridlen);
    163 
    164 		for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
    165 			lif = dlp->dl_lifs;
    166 			for (nlifs = dlp->dl_nlifs; nlifs > 0;
    167 			    nlifs--, lif = lif->lif_next) {
    168 				(void) add_pkt_lif(dpkt, lif,
    169 				    DHCPV6_STAT_SUCCESS, NULL);
    170 			}
    171 		}
    172 
    173 		/*
    174 		 * Must kill off the leases before attempting to tell the
    175 		 * server.
    176 		 */
    177 		deprecate_leases(dsmp);
    178 
    179 		/*
    180 		 * For DHCPv6, this is a transaction, rather than just a
    181 		 * one-shot message.  When this transaction is done, we'll
    182 		 * finish the invoking async operation.
    183 		 */
    184 		(void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
    185 		    stop_release_decline, DHCPV6_REL_TIMEOUT, 0);
    186 	} else {
    187 		if ((dlp = dsmp->dsm_leases) != NULL && dlp->dl_nlifs > 0) {
    188 			dpkt = init_pkt(dsmp, RELEASE);
    189 			if (msg != NULL) {
    190 				(void) add_pkt_opt(dpkt, CD_MESSAGE, msg,
    191 				    strlen(msg) + 1);
    192 			}
    193 			lif = dlp->dl_lifs;
    194 			(void) add_pkt_lif(dpkt, dlp->dl_lifs, 0, NULL);
    195 
    196 			IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, serverip);
    197 			(void) add_pkt_opt32(dpkt, CD_SERVER_ID, serverip);
    198 			(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
    199 			(void) send_pkt(dsmp, dpkt, serverip, NULL);
    200 		}
    201 
    202 		/*
    203 		 * XXX this totally sucks, but since udp is best-effort,
    204 		 * without this delay, there's a good chance that the packet
    205 		 * that we just enqueued for sending will get pitched
    206 		 * when we canonize the interface through remove_smach.
    207 		 */
    208 
    209 		(void) usleep(500);
    210 		deprecate_leases(dsmp);
    211 
    212 		finished_smach(dsmp, DHCP_IPC_SUCCESS);
    213 	}
    214 	return (1);
    215 }
    216 
    217 /*
    218  * dhcp_drop(): drops the interface from DHCP control; callback from script
    219  *		handler
    220  *
    221  *   input: dhcp_smach_t *: the state machine dropping leases
    222  *	    void *: unused
    223  *  output: int: always 1
    224  */
    225 
    226 /* ARGSUSED1 */
    227 int
    228 dhcp_drop(dhcp_smach_t *dsmp, void *arg)
    229 {
    230 	dhcpmsg(MSG_INFO, "dropping leases for state machine %s",
    231 	    dsmp->dsm_name);
    232 
    233 	if (dsmp->dsm_state == PRE_BOUND || dsmp->dsm_state == BOUND ||
    234 	    dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) {
    235 		if (dsmp->dsm_dflags & DHCP_IF_BOOTP) {
    236 			dhcpmsg(MSG_INFO,
    237 			    "used bootp; not writing lease file for %s",
    238 			    dsmp->dsm_name);
    239 		} else {
    240 			PKT_LIST *plp[2];
    241 			const char *hcfile;
    242 
    243 			hcfile = ifname_to_hostconf(dsmp->dsm_name,
    244 			    dsmp->dsm_isv6);
    245 			plp[0] = dsmp->dsm_ack;
    246 			plp[1] = dsmp->dsm_orig_ack;
    247 			if (write_hostconf(dsmp->dsm_name, plp, 2,
    248 			    monosec_to_time(dsmp->dsm_curstart_monosec),
    249 			    dsmp->dsm_isv6) != -1) {
    250 				dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile);
    251 			} else if (errno == EROFS) {
    252 				dhcpmsg(MSG_DEBUG, "%s is on a read-only file "
    253 				    "system; not saving lease", hcfile);
    254 			} else {
    255 				dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
    256 				    "not use cached configuration)", hcfile);
    257 			}
    258 		}
    259 	} else {
    260 		dhcpmsg(MSG_DEBUG, "%s in state %s; not saving lease",
    261 		    dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state));
    262 	}
    263 	deprecate_leases(dsmp);
    264 	finished_smach(dsmp, DHCP_IPC_SUCCESS);
    265 	return (1);
    266 }
    267 
    268 /*
    269  * stop_release_decline(): decides when to stop retransmitting RELEASE/DECLINE
    270  *			   messages for DHCPv6.  When we stop, if there are no
    271  *			   more leases left, then restart the state machine.
    272  *
    273  *   input: dhcp_smach_t *: the state machine messages are being sent from
    274  *	    unsigned int: the number of messages sent so far
    275  *  output: boolean_t: B_TRUE if retransmissions should stop
    276  */
    277 
    278 static boolean_t
    279 stop_release_decline(dhcp_smach_t *dsmp, unsigned int n_requests)
    280 {
    281 	if (dsmp->dsm_state == RELEASING) {
    282 		if (n_requests >= DHCPV6_REL_MAX_RC) {
    283 			dhcpmsg(MSG_INFO, "no Reply to Release, finishing "
    284 			    "transaction on %s", dsmp->dsm_name);
    285 			finished_smach(dsmp, DHCP_IPC_SUCCESS);
    286 			return (B_TRUE);
    287 		} else {
    288 			return (B_FALSE);
    289 		}
    290 	} else {
    291 		if (n_requests >= DHCPV6_DEC_MAX_RC) {
    292 			dhcpmsg(MSG_INFO, "no Reply to Decline on %s",
    293 			    dsmp->dsm_name);
    294 
    295 			if (dsmp->dsm_leases == NULL) {
    296 				dhcpmsg(MSG_VERBOSE, "stop_release_decline: "
    297 				    "%s has no leases left", dsmp->dsm_name);
    298 				dhcp_restart(dsmp);
    299 			}
    300 			return (B_TRUE);
    301 		} else {
    302 			return (B_FALSE);
    303 		}
    304 	}
    305 }
    306