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