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 2006 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * 25 * REQUESTING state of the client state machine. 26 */ 27 28 #pragma ident "%Z%%M% %I% %E% SMI" 29 30 #include <sys/types.h> 31 #include <sys/stropts.h> /* FLUSHR/FLUSHW */ 32 #include <netinet/in.h> 33 #include <netinet/dhcp.h> 34 #include <netinet/udp.h> 35 #include <netinet/ip_var.h> 36 #include <netinet/udp_var.h> 37 #include <dhcp_hostconf.h> 38 #include <arpa/inet.h> 39 #include <string.h> 40 #include <unistd.h> 41 #include <dhcpmsg.h> 42 43 #include "states.h" 44 #include "util.h" 45 #include "packet.h" 46 #include "interface.h" 47 #include "agent.h" 48 49 static PKT_LIST *select_best(PKT_LIST **); 50 static stop_func_t stop_requesting; 51 52 /* 53 * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers. 54 * if so, chooses the best one, sends a REQUEST to the 55 * server and registers an event handler to receive 56 * the ACK/NAK 57 * 58 * input: iu_tq_t *: unused 59 * void *: the interface receiving OFFER packets 60 * output: void 61 */ 62 63 /* ARGSUSED */ 64 void 65 dhcp_requesting(iu_tq_t *tqp, void *arg) 66 { 67 struct ifslist *ifsp = (struct ifslist *)arg; 68 dhcp_pkt_t *dpkt; 69 PKT_LIST *offer; 70 lease_t lease; 71 72 ifsp->if_offer_timer = -1; 73 74 if (check_ifs(ifsp) == 0) { 75 (void) release_ifs(ifsp); 76 return; 77 } 78 79 /* 80 * select the best OFFER; all others pitched. 81 */ 82 83 offer = select_best(&ifsp->if_recv_pkt_list); 84 if (offer == NULL) { 85 86 dhcpmsg(MSG_VERBOSE, "no OFFERs on %s, waiting...", 87 ifsp->if_name); 88 89 /* 90 * no acceptable OFFERs have come in. reschedule 91 * ourselves for callback. 92 */ 93 94 if ((ifsp->if_offer_timer = iu_schedule_timer(tq, 95 ifsp->if_offer_wait, dhcp_requesting, ifsp)) == -1) { 96 97 /* 98 * ugh. the best we can do at this point is 99 * revert back to INIT and wait for a user to 100 * restart us. 101 */ 102 103 ifsp->if_state = INIT; 104 ifsp->if_dflags |= DHCP_IF_FAILED; 105 106 stop_pkt_retransmission(ifsp); 107 ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); 108 async_finish(ifsp); 109 110 dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " 111 "reschedule callback, reverting to INIT state on " 112 "%s", ifsp->if_name); 113 } else 114 hold_ifs(ifsp); 115 116 return; 117 } 118 119 stop_pkt_retransmission(ifsp); 120 121 /* 122 * stop collecting packets. check to see whether we got an 123 * OFFER or a BOOTP packet. if we got a BOOTP packet, go to 124 * the BOUND state now. 125 */ 126 127 if (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0) { 128 (void) release_ifs(ifsp); 129 ifsp->if_offer_id = -1; 130 } 131 132 if (offer->opts[CD_DHCP_TYPE] == NULL) { 133 134 ifsp->if_state = REQUESTING; 135 136 if (dhcp_bound(ifsp, offer) == 0) { 137 dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " 138 "failed for %s", ifsp->if_name); 139 dhcp_restart(ifsp); 140 return; 141 } 142 143 return; 144 } 145 146 /* 147 * if we got a message from the server, display it. 148 */ 149 150 if (offer->opts[CD_MESSAGE] != NULL) 151 print_server_msg(ifsp, offer->opts[CD_MESSAGE]); 152 153 /* 154 * assemble a DHCPREQUEST, with the ciaddr field set to 0, 155 * since we got here from the INIT state. 156 */ 157 158 dpkt = init_pkt(ifsp, REQUEST); 159 160 /* 161 * grab the lease out of the OFFER; we know it's valid since 162 * select_best() already checked. The max dhcp message size 163 * option is set to the interface max, minus the size of the udp and 164 * ip headers. 165 */ 166 167 (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, 168 sizeof (lease_t)); 169 170 add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); 171 add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max - 172 sizeof (struct udpiphdr))); 173 add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, offer->pkt->yiaddr.s_addr); 174 add_pkt_opt(dpkt, CD_SERVER_ID, offer->opts[CD_SERVER_ID]->value, 175 offer->opts[CD_SERVER_ID]->len); 176 177 add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); 178 add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen); 179 180 /* 181 * if_reqhost was set for this interface in dhcp_selecting() 182 * if the DF_REQUEST_HOSTNAME option set and a host name was 183 * found 184 */ 185 if (ifsp->if_reqhost != NULL) { 186 add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost, 187 strlen(ifsp->if_reqhost)); 188 } 189 add_pkt_opt(dpkt, CD_END, NULL, 0); 190 191 /* all done with the offer */ 192 free_pkt_list(&offer); 193 194 /* 195 * send out the REQUEST, trying retransmissions. either a NAK 196 * or too many REQUEST attempts will revert us to SELECTING. 197 */ 198 199 ifsp->if_state = REQUESTING; 200 (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_requesting); 201 202 /* 203 * wait for an ACK or NAK to come back from the server. if 204 * we can't register this event handler, then we won't be able 205 * to see the server's responses. the best we can really do 206 * in that case is drop back to INIT and hope someone notices. 207 */ 208 209 if (register_acknak(ifsp) == 0) { 210 211 ifsp->if_state = INIT; 212 ifsp->if_dflags |= DHCP_IF_FAILED; 213 214 ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); 215 async_finish(ifsp); 216 217 dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot register to " 218 "collect ACK/NAK packets, reverting to INIT on %s", 219 ifsp->if_name); 220 } 221 } 222 223 /* 224 * select_best(): selects the best OFFER packet from a list of OFFER packets 225 * 226 * input: PKT_LIST **: a list of packets to select the best from 227 * output: PKT_LIST *: the best packet, or NULL if none are acceptable 228 */ 229 230 static PKT_LIST * 231 select_best(PKT_LIST **pkts) 232 { 233 PKT_LIST *current, *best = NULL; 234 uint32_t points, best_points = 0; 235 236 /* 237 * pick out the best offer. point system. 238 * what's important? 239 * 240 * 0) DHCP 241 * 1) no option overload 242 * 2) encapsulated vendor option 243 * 3) non-null sname and siaddr fields 244 * 4) non-null file field 245 * 5) hostname 246 * 6) subnetmask 247 * 7) router 248 */ 249 250 for (current = *pkts; current != NULL; current = current->next) { 251 252 points = 0; 253 254 if (current->opts[CD_DHCP_TYPE] == NULL) { 255 dhcpmsg(MSG_VERBOSE, "valid BOOTP reply"); 256 goto valid_offer; 257 } 258 259 if (current->opts[CD_LEASE_TIME] == NULL) { 260 dhcpmsg(MSG_WARNING, "select_best: OFFER without " 261 "lease time"); 262 continue; 263 } 264 265 if (current->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 266 dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled " 267 "lease time"); 268 continue; 269 } 270 271 if (current->opts[CD_SERVER_ID] == NULL) { 272 dhcpmsg(MSG_WARNING, "select_best: OFFER without " 273 "server id"); 274 continue; 275 } 276 277 if (current->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 278 dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled " 279 "server id"); 280 continue; 281 } 282 283 /* valid DHCP OFFER. see if we got our parameters. */ 284 dhcpmsg(MSG_VERBOSE, "valid OFFER packet"); 285 points += 30; 286 287 valid_offer: 288 if (current->rfc1048) 289 points += 5; 290 291 /* 292 * also could be faked, though more difficult because 293 * the encapsulation is hard to encode on a BOOTP 294 * server; plus there's not as much real estate in the 295 * packet for options, so it's likely this option 296 * would get dropped. 297 */ 298 299 if (current->opts[CD_VENDOR_SPEC] != NULL) 300 points += 80; 301 302 if (current->opts[CD_SUBNETMASK] != NULL) 303 points++; 304 305 if (current->opts[CD_ROUTER] != NULL) 306 points++; 307 308 if (current->opts[CD_HOSTNAME] != NULL) 309 points += 5; 310 311 dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); 312 313 if (points >= best_points) { 314 best_points = points; 315 best = current; 316 } 317 } 318 319 if (best != NULL) { 320 dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); 321 remove_from_pkt_list(pkts, best); 322 } else 323 dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); 324 325 free_pkt_list(pkts); 326 return (best); 327 } 328 329 /* 330 * dhcp_acknak(): processes reception of an ACK or NAK packet on an interface 331 * 332 * input: iu_eh_t *: unused 333 * int: the file descriptor the ACK/NAK arrived on 334 * short: unused 335 * iu_event_id_t: the id of this event callback with the handler 336 * void *: the interface that received the ACK or NAK 337 * output: void 338 */ 339 340 /* ARGSUSED */ 341 void 342 dhcp_acknak(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) 343 { 344 struct ifslist *ifsp = (struct ifslist *)arg; 345 PKT_LIST *plp; 346 347 if (check_ifs(ifsp) == 0) { 348 /* unregister_acknak() does our release_ifs() */ 349 (void) unregister_acknak(ifsp); 350 (void) ioctl(fd, I_FLUSH, FLUSHR|FLUSHW); 351 return; 352 } 353 354 /* 355 * note that check_ifs() did our release_ifs() but we're not 356 * sure we're done yet; call hold_ifs() to reacquire our hold; 357 * if we're done, unregister_acknak() will release_ifs() below. 358 */ 359 360 hold_ifs(ifsp); 361 362 if (recv_pkt(ifsp, fd, DHCP_PACK|DHCP_PNAK, B_FALSE) == 0) 363 return; 364 365 /* 366 * we've got a packet; make sure it's acceptable before 367 * cancelling the REQUEST retransmissions. 368 */ 369 370 plp = ifsp->if_recv_pkt_list; 371 remove_from_pkt_list(&ifsp->if_recv_pkt_list, plp); 372 373 if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { 374 if (plp->opts[CD_LEASE_TIME] == NULL || 375 plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 376 dhcpmsg(MSG_WARNING, "dhcp_acknak: ACK packet on %s " 377 "missing mandatory lease option, ignored", 378 ifsp->if_name); 379 ifsp->if_bad_offers++; 380 free_pkt_list(&plp); 381 return; 382 } 383 if ((ifsp->if_state == RENEWING || 384 ifsp->if_state == REBINDING) && 385 ifsp->if_addr.s_addr != plp->pkt->yiaddr.s_addr) { 386 dhcpmsg(MSG_WARNING, "dhcp_acknak: renewal ACK packet " 387 "has a different IP address (%s), ignored", 388 inet_ntoa(plp->pkt->yiaddr)); 389 ifsp->if_bad_offers++; 390 free_pkt_list(&plp); 391 return; 392 } 393 } 394 395 /* 396 * looks good; cancel the retransmission timer and unregister 397 * the acknak handler. ACK to BOUND, NAK back to SELECTING. 398 */ 399 400 stop_pkt_retransmission(ifsp); 401 (void) unregister_acknak(ifsp); 402 403 if (*(plp->opts[CD_DHCP_TYPE]->value) == NAK) { 404 dhcpmsg(MSG_WARNING, "dhcp_acknak: NAK on interface %s", 405 ifsp->if_name); 406 ifsp->if_bad_offers++; 407 free_pkt_list(&plp); 408 dhcp_restart(ifsp); 409 410 /* 411 * remove any bogus cached configuration we might have 412 * around (right now would only happen if we got here 413 * from INIT_REBOOT). 414 */ 415 416 (void) remove_hostconf(ifsp->if_name); 417 return; 418 } 419 420 if (plp->opts[CD_SERVER_ID] == NULL || 421 plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 422 dhcpmsg(MSG_ERROR, "dhcp_acknak: ACK with no valid server id, " 423 "restarting DHCP on %s", ifsp->if_name); 424 ifsp->if_bad_offers++; 425 free_pkt_list(&plp); 426 dhcp_restart(ifsp); 427 return; 428 } 429 430 if (plp->opts[CD_MESSAGE] != NULL) 431 print_server_msg(ifsp, plp->opts[CD_MESSAGE]); 432 433 if (dhcp_bound(ifsp, plp) == 0) { 434 dhcpmsg(MSG_WARNING, "dhcp_acknak: dhcp_bound failed " 435 "for %s", ifsp->if_name); 436 dhcp_restart(ifsp); 437 return; 438 } 439 440 dhcpmsg(MSG_VERBOSE, "ACK on interface %s", ifsp->if_name); 441 } 442 443 /* 444 * dhcp_restart(): restarts DHCP (from INIT) on a given interface 445 * 446 * input: struct ifslist *: the interface to restart DHCP on 447 * output: void 448 */ 449 450 void 451 dhcp_restart(struct ifslist *ifsp) 452 { 453 if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, ifsp) == -1) { 454 455 ifsp->if_state = INIT; 456 ifsp->if_dflags |= DHCP_IF_FAILED; 457 458 ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); 459 async_finish(ifsp); 460 461 dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " 462 "reverting to INIT state on %s", ifsp->if_name); 463 } else 464 hold_ifs(ifsp); 465 } 466 467 /* 468 * stop_requesting(): decides when to stop retransmitting REQUESTs 469 * 470 * input: struct ifslist *: the interface REQUESTs are being sent on 471 * unsigned int: the number of REQUESTs sent so far 472 * output: boolean_t: B_TRUE if retransmissions should stop 473 */ 474 475 static boolean_t 476 stop_requesting(struct ifslist *ifsp, unsigned int n_requests) 477 { 478 if (n_requests >= DHCP_MAX_REQUESTS) { 479 480 (void) unregister_acknak(ifsp); 481 482 dhcpmsg(MSG_INFO, "no ACK/NAK to REQUESTING REQUEST, " 483 "restarting DHCP on %s", ifsp->if_name); 484 485 dhcp_selecting(ifsp); 486 return (B_TRUE); 487 } 488 489 return (B_FALSE); 490 } 491