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; restarting", 298 dsmp->dsm_name); 299 dhcp_restart(dsmp); 300 } 301 return (B_TRUE); 302 } else { 303 return (B_FALSE); 304 } 305 } 306 } 307