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