1 /* 2 * Copyright 2006-2007, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler, axeld@pinc-software.de 7 */ 8 9 10 #include "DHCPClient.h" 11 #include "NetServer.h" 12 13 #include <Message.h> 14 #include <MessageRunner.h> 15 16 #include <arpa/inet.h> 17 #include <errno.h> 18 #include <stdio.h> 19 #include <string.h> 20 #include <sys/time.h> 21 22 23 // See RFC 2131 for DHCP, see RFC 1533 for BOOTP/DHCP options 24 25 #define DHCP_CLIENT_PORT 68 26 #define DHCP_SERVER_PORT 67 27 28 #define DEFAULT_TIMEOUT 2 // secs 29 #define MAX_TIMEOUT 15 // secs 30 31 enum message_opcode { 32 BOOT_REQUEST = 1, 33 BOOT_REPLY 34 }; 35 36 enum message_option { 37 OPTION_MAGIC = 0x63825363, 38 39 // generic options 40 OPTION_PAD = 0, 41 OPTION_END = 255, 42 OPTION_SUBNET_MASK = 1, 43 OPTION_TIME_OFFSET = 2, 44 OPTION_ROUTER_ADDRESS = 3, 45 OPTION_DOMAIN_NAME_SERVER = 6, 46 OPTION_HOST_NAME = 12, 47 OPTION_DOMAIN_NAME = 15, 48 OPTION_DATAGRAM_SIZE = 22, 49 OPTION_MTU = 26, 50 OPTION_BROADCAST_ADDRESS = 28, 51 OPTION_NETWORK_TIME_SERVERS = 42, 52 OPTION_NETBIOS_NAME_SERVER = 44, 53 OPTION_NETBIOS_SCOPE = 47, 54 55 // DHCP specific options 56 OPTION_REQUEST_IP_ADDRESS = 50, 57 OPTION_ADDRESS_LEASE_TIME = 51, 58 OPTION_OVERLOAD = 52, 59 OPTION_MESSAGE_TYPE = 53, 60 OPTION_SERVER_ADDRESS = 54, 61 OPTION_REQUEST_PARAMETERS = 55, 62 OPTION_ERROR_MESSAGE = 56, 63 OPTION_MESSAGE_SIZE = 57, 64 OPTION_RENEWAL_TIME = 58, 65 OPTION_REBINDING_TIME = 59, 66 OPTION_CLASS_IDENTIFIER = 60, 67 OPTION_CLIENT_IDENTIFIER = 61, 68 }; 69 70 enum message_type { 71 DHCP_NONE = 0, 72 DHCP_DISCOVER, 73 DHCP_OFFER, 74 DHCP_REQUEST, 75 DHCP_DECLINE, 76 DHCP_ACK, 77 DHCP_NACK, 78 DHCP_RELEASE, 79 DHCP_INFORM 80 }; 81 82 struct dhcp_option_cookie { 83 dhcp_option_cookie() : state(0), file_has_options(false), server_name_has_options(false) {} 84 85 const uint8* next; 86 uint8 state; 87 bool file_has_options; 88 bool server_name_has_options; 89 }; 90 91 struct dhcp_message { 92 dhcp_message(message_type type); 93 94 uint8 opcode; 95 uint8 hardware_type; 96 uint8 hardware_address_length; 97 uint8 hop_count; 98 uint32 transaction_id; 99 uint16 seconds_since_start; 100 uint16 flags; 101 in_addr_t client_address; 102 in_addr_t your_address; 103 in_addr_t server_address; 104 in_addr_t gateway_address; 105 uint8 mac_address[16]; 106 uint8 server_name[64]; 107 uint8 file[128]; 108 uint32 options_magic; 109 uint8 options[1260]; 110 111 size_t MinSize() const { return 576; } 112 size_t Size() const; 113 114 bool HasOptions() const; 115 bool NextOption(dhcp_option_cookie& cookie, message_option& option, 116 const uint8*& data, size_t& size) const; 117 message_type Type() const; 118 const uint8* LastOption() const; 119 120 uint8* PrepareMessage(uint8 type); 121 uint8* PutOption(uint8* options, message_option option); 122 uint8* PutOption(uint8* options, message_option option, uint8 data); 123 uint8* PutOption(uint8* options, message_option option, uint16 data); 124 uint8* PutOption(uint8* options, message_option option, uint32 data); 125 uint8* PutOption(uint8* options, message_option option, const uint8* data, uint32 size); 126 uint8* FinishOptions(uint8 *); 127 } _PACKED; 128 129 #define DHCP_FLAG_BROADCAST 0x8000 130 131 #define ARP_HARDWARE_TYPE_ETHER 1 132 133 const uint32 kMsgLeaseTime = 'lstm'; 134 135 static const uint8 kRequiredParameters[] = { 136 OPTION_SUBNET_MASK, OPTION_ROUTER_ADDRESS, 137 OPTION_DOMAIN_NAME_SERVER, OPTION_BROADCAST_ADDRESS 138 }; 139 140 141 dhcp_message::dhcp_message(message_type type) 142 { 143 memset(this, 0, sizeof(*this)); 144 options_magic = htonl(OPTION_MAGIC); 145 146 uint8* next = PrepareMessage(type); 147 FinishOptions(next); 148 } 149 150 151 bool 152 dhcp_message::HasOptions() const 153 { 154 return options_magic == htonl(OPTION_MAGIC); 155 } 156 157 158 bool 159 dhcp_message::NextOption(dhcp_option_cookie& cookie, 160 message_option& option, const uint8*& data, size_t& size) const 161 { 162 if (cookie.state == 0) { 163 if (!HasOptions()) 164 return false; 165 166 cookie.state++; 167 cookie.next = options; 168 } 169 170 uint32 bytesLeft = 0; 171 172 switch (cookie.state) { 173 case 1: 174 // options from "options" 175 bytesLeft = sizeof(options) + cookie.next - options; 176 break; 177 178 case 2: 179 // options from "file" 180 bytesLeft = sizeof(options) + cookie.next - options; 181 break; 182 183 case 3: 184 // options from "server_name" 185 bytesLeft = sizeof(options) + cookie.next - options; 186 break; 187 } 188 189 while (true) { 190 if (bytesLeft == 0) { 191 // TODO: suppport OPTION_OVERLOAD! 192 cookie.state = 4; 193 return false; 194 } 195 196 option = (message_option)cookie.next[0]; 197 if (option == OPTION_END) { 198 cookie.state = 4; 199 return false; 200 } else if (option == OPTION_PAD) { 201 bytesLeft--; 202 cookie.next++; 203 continue; 204 } 205 206 size = cookie.next[1]; 207 data = &cookie.next[2]; 208 cookie.next += 2 + size; 209 210 if (option == OPTION_OVERLOAD) { 211 cookie.file_has_options = data[0] & 1; 212 cookie.server_name_has_options = data[0] & 2; 213 continue; 214 } 215 216 return true; 217 } 218 } 219 220 221 message_type 222 dhcp_message::Type() const 223 { 224 dhcp_option_cookie cookie; 225 message_option option; 226 const uint8* data; 227 size_t size; 228 while (NextOption(cookie, option, data, size)) { 229 // iterate through all options 230 if (option == OPTION_MESSAGE_TYPE) 231 return (message_type)data[0]; 232 } 233 234 return DHCP_NONE; 235 } 236 237 238 const uint8* 239 dhcp_message::LastOption() const 240 { 241 dhcp_option_cookie cookie; 242 message_option option; 243 const uint8* data; 244 size_t size; 245 while (NextOption(cookie, option, data, size)) { 246 // iterate through all options 247 } 248 249 return cookie.next; 250 } 251 252 253 size_t 254 dhcp_message::Size() const 255 { 256 const uint8* last = LastOption(); 257 return sizeof(dhcp_message) - sizeof(options) + last + 1 - options; 258 } 259 260 261 uint8* 262 dhcp_message::PrepareMessage(uint8 type) 263 { 264 uint8 *next = options; 265 next = PutOption(next, OPTION_MESSAGE_TYPE, type); 266 next = PutOption(next, OPTION_MESSAGE_SIZE, (uint16)htons(sizeof(dhcp_message))); 267 return next; 268 } 269 270 271 uint8* 272 dhcp_message::PutOption(uint8* options, message_option option) 273 { 274 options[0] = option; 275 return options + 1; 276 } 277 278 279 uint8* 280 dhcp_message::PutOption(uint8* options, message_option option, uint8 data) 281 { 282 return PutOption(options, option, &data, 1); 283 } 284 285 286 uint8* 287 dhcp_message::PutOption(uint8* options, message_option option, uint16 data) 288 { 289 return PutOption(options, option, (uint8*)&data, sizeof(data)); 290 } 291 292 293 uint8* 294 dhcp_message::PutOption(uint8* options, message_option option, uint32 data) 295 { 296 return PutOption(options, option, (uint8*)&data, sizeof(data)); 297 } 298 299 300 uint8* 301 dhcp_message::PutOption(uint8* options, message_option option, const uint8* data, uint32 size) 302 { 303 options[0] = option; 304 options[1] = size; 305 memcpy(&options[2], data, size); 306 307 return options + 2 + size; 308 } 309 310 311 uint8* 312 dhcp_message::FinishOptions(uint8 *next) 313 { 314 return PutOption(next, OPTION_END); 315 } 316 317 318 // #pragma mark - 319 320 321 DHCPClient::DHCPClient(BMessenger target, const char* device) 322 : BHandler("dhcp"), 323 fTarget(target), 324 fDevice(device), 325 fConfiguration(kMsgConfigureInterface), 326 fRunner(NULL), 327 fLeaseTime(0) 328 { 329 fStartTime = system_time(); 330 fTransactionID = (uint32)fStartTime; 331 332 fStatus = get_mac_address(device, fMAC); 333 if (fStatus < B_OK) 334 return; 335 336 memset(&fServer, 0, sizeof(struct sockaddr_in)); 337 fServer.sin_family = AF_INET; 338 fServer.sin_len = sizeof(struct sockaddr_in); 339 fServer.sin_port = htons(DHCP_SERVER_PORT); 340 } 341 342 343 DHCPClient::~DHCPClient() 344 { 345 if (fStatus != B_OK) 346 return; 347 348 delete fRunner; 349 350 int socket = ::socket(AF_INET, SOCK_DGRAM, 0); 351 if (socket < 0) 352 return; 353 354 // release lease 355 356 dhcp_message release(DHCP_RELEASE); 357 _PrepareMessage(release, BOUND); 358 359 _SendMessage(socket, release, fServer); 360 close(socket); 361 } 362 363 364 status_t 365 DHCPClient::Initialize() 366 { 367 fStatus = _Negotiate(INIT); 368 printf("DHCP for %s, status: %s\n", fDevice.String(), strerror(fStatus)); 369 return fStatus; 370 } 371 372 373 status_t 374 DHCPClient::_Negotiate(dhcp_state state) 375 { 376 int socket = ::socket(AF_INET, SOCK_DGRAM, 0); 377 if (socket < 0) 378 return errno; 379 380 sockaddr_in local; 381 memset(&local, 0, sizeof(struct sockaddr_in)); 382 local.sin_family = AF_INET; 383 local.sin_len = sizeof(struct sockaddr_in); 384 local.sin_port = htons(DHCP_CLIENT_PORT); 385 local.sin_addr.s_addr = INADDR_ANY; 386 387 if (bind(socket, (struct sockaddr *)&local, sizeof(local)) < 0) { 388 close(socket); 389 return errno; 390 } 391 392 sockaddr_in broadcast; 393 memset(&broadcast, 0, sizeof(struct sockaddr_in)); 394 broadcast.sin_family = AF_INET; 395 broadcast.sin_len = sizeof(struct sockaddr_in); 396 broadcast.sin_port = htons(DHCP_SERVER_PORT); 397 broadcast.sin_addr.s_addr = INADDR_BROADCAST; 398 399 int option = 1; 400 setsockopt(socket, SOL_SOCKET, SO_BROADCAST, &option, sizeof(option)); 401 402 bigtime_t previousLeaseTime = fLeaseTime; 403 fLeaseTime = 0; 404 fRenewalTime = 0; 405 fRebindingTime = 0; 406 407 status_t status = B_ERROR; 408 time_t timeout; 409 uint32 tries; 410 _ResetTimeout(socket, timeout, tries); 411 412 dhcp_message discover(DHCP_DISCOVER); 413 _PrepareMessage(discover, state); 414 415 dhcp_message request(DHCP_REQUEST); 416 _PrepareMessage(request, state); 417 418 // send discover/request message 419 status = _SendMessage(socket, state == INIT ? discover : request, 420 state != RENEWAL ? broadcast : fServer); 421 if (status < B_OK) { 422 close(socket); 423 return status; 424 } 425 426 // receive loop until we've got an offer and acknowledged it 427 428 while (state != ACKNOWLEDGED) { 429 char buffer[2048]; 430 ssize_t bytesReceived = recvfrom(socket, buffer, sizeof(buffer), 431 0, NULL, NULL); 432 if (bytesReceived < 0 && errno == B_TIMED_OUT) { 433 // depending on the state, we'll just try again 434 if (!_TimeoutShift(socket, timeout, tries)) { 435 close(socket); 436 return B_TIMED_OUT; 437 } 438 439 if (state == INIT) 440 status = _SendMessage(socket, discover, broadcast); 441 else 442 status = _SendMessage(socket, request, state != RENEWAL 443 ? broadcast : fServer); 444 445 if (status < B_OK) 446 break; 447 } else if (bytesReceived < B_OK) 448 break; 449 450 dhcp_message *message = (dhcp_message *)buffer; 451 if (message->transaction_id != htonl(fTransactionID) 452 || !message->HasOptions() 453 || memcmp(message->mac_address, discover.mac_address, 454 discover.hardware_address_length)) { 455 // this message is not for us 456 continue; 457 } 458 459 switch (message->Type()) { 460 case DHCP_NONE: 461 default: 462 // ignore this message 463 break; 464 465 case DHCP_OFFER: 466 { 467 // first offer wins 468 if (state != INIT) 469 break; 470 471 // collect interface options 472 473 fAssignedAddress = message->your_address; 474 475 fConfiguration.MakeEmpty(); 476 fConfiguration.AddString("device", fDevice.String()); 477 fConfiguration.AddBool("auto", true); 478 479 BMessage address; 480 address.AddString("family", "inet"); 481 address.AddString("address", _ToString(fAssignedAddress)); 482 _ParseOptions(*message, address); 483 484 fConfiguration.AddMessage("address", &address); 485 486 // request configuration from the server 487 488 _ResetTimeout(socket, timeout, tries); 489 state = REQUESTING; 490 _PrepareMessage(request, state); 491 492 status = _SendMessage(socket, request, broadcast); 493 // we're sending a broadcast so that all potential offers get an answer 494 break; 495 } 496 497 case DHCP_ACK: 498 { 499 if (state != REQUESTING && state != REBINDING && state != RENEWAL) 500 continue; 501 502 // TODO: we might want to configure the stuff, don't we? 503 BMessage address; 504 _ParseOptions(*message, address); 505 // TODO: currently, only lease time and DNS is updated this way 506 507 // our address request has been acknowledged 508 state = ACKNOWLEDGED; 509 510 // configure interface 511 BMessage reply; 512 fTarget.SendMessage(&fConfiguration, &reply); 513 514 if (reply.FindInt32("status", &fStatus) != B_OK) 515 status = B_OK; 516 break; 517 } 518 519 case DHCP_NACK: 520 if (state != REQUESTING) 521 continue; 522 523 // try again (maybe we should prefer other servers if this happens more than once) 524 status = _SendMessage(socket, discover, broadcast); 525 if (status == B_OK) 526 state = INIT; 527 break; 528 } 529 } 530 531 close(socket); 532 533 if (status == B_OK && fLeaseTime > 0) { 534 // notify early enough when the lease is 535 if (fRenewalTime == 0) 536 fRenewalTime = fLeaseTime * 2/3; 537 if (fRebindingTime == 0) 538 fRebindingTime = fLeaseTime * 5/6; 539 540 _RestartLease(fRenewalTime); 541 542 bigtime_t now = system_time(); 543 fLeaseTime += now; 544 fRenewalTime += now; 545 fRebindingTime += now; 546 // make lease times absolute 547 } else { 548 fLeaseTime = previousLeaseTime; 549 bigtime_t now = system_time(); 550 fRenewalTime = (fLeaseTime - now) * 2/3 + now; 551 fRebindingTime = (fLeaseTime - now) * 5/6 + now; 552 } 553 554 return status; 555 } 556 557 558 void 559 DHCPClient::_RestartLease(bigtime_t leaseTime) 560 { 561 if (leaseTime == 0) 562 return; 563 564 BMessage lease(kMsgLeaseTime); 565 fRunner = new BMessageRunner(this, &lease, leaseTime, 1); 566 } 567 568 569 void 570 DHCPClient::_ParseOptions(dhcp_message& message, BMessage& address) 571 { 572 dhcp_option_cookie cookie; 573 message_option option; 574 const uint8* data; 575 size_t size; 576 while (message.NextOption(cookie, option, data, size)) { 577 // iterate through all options 578 switch (option) { 579 case OPTION_ROUTER_ADDRESS: 580 address.AddString("gateway", _ToString(data)); 581 break; 582 case OPTION_SUBNET_MASK: 583 address.AddString("mask", _ToString(data)); 584 break; 585 case OPTION_BROADCAST_ADDRESS: 586 address.AddString("broadcast", _ToString(data)); 587 break; 588 case OPTION_DOMAIN_NAME_SERVER: 589 { 590 // TODO: for now, we write it just out to /etc/resolv.conf 591 FILE* file = fopen("/etc/resolv.conf", "w"); 592 for (uint32 i = 0; i < size / 4; i++) { 593 printf("DNS: %s\n", _ToString(&data[i*4]).String()); 594 if (file != NULL) 595 fprintf(file, "nameserver %s\n", _ToString(&data[i*4]).String()); 596 } 597 fclose(file); 598 break; 599 } 600 case OPTION_SERVER_ADDRESS: 601 fServer.sin_addr.s_addr = *(in_addr_t*)data; 602 break; 603 604 case OPTION_ADDRESS_LEASE_TIME: 605 printf("lease time of %lu seconds\n", htonl(*(uint32*)data)); 606 fLeaseTime = htonl(*(uint32*)data) * 1000000LL; 607 break; 608 case OPTION_RENEWAL_TIME: 609 printf("renewal time of %lu seconds\n", 610 htonl(*(uint32*)data)); 611 fRenewalTime = htonl(*(uint32*)data) * 1000000LL; 612 break; 613 case OPTION_REBINDING_TIME: 614 printf("rebinding time of %lu seconds\n", 615 htonl(*(uint32*)data)); 616 fRebindingTime = htonl(*(uint32*)data) * 1000000LL; 617 break; 618 619 case OPTION_HOST_NAME: 620 { 621 char name[256]; 622 memcpy(name, data, size); 623 name[size] = '\0'; 624 printf("DHCP host name: \"%s\"\n", name); 625 break; 626 } 627 628 case OPTION_DOMAIN_NAME: 629 { 630 char name[256]; 631 memcpy(name, data, size); 632 name[size] = '\0'; 633 printf("DHCP domain name: \"%s\"\n", name); 634 break; 635 } 636 637 case OPTION_MESSAGE_TYPE: 638 break; 639 640 default: 641 printf("unknown option %lu\n", (uint32)option); 642 break; 643 } 644 } 645 } 646 647 648 void 649 DHCPClient::_PrepareMessage(dhcp_message& message, dhcp_state state) 650 { 651 message.opcode = BOOT_REQUEST; 652 message.hardware_type = ARP_HARDWARE_TYPE_ETHER; 653 message.hardware_address_length = 6; 654 message.transaction_id = htonl(fTransactionID); 655 message.seconds_since_start = htons(min_c((fStartTime - system_time()) / 1000000LL, 65535)); 656 memcpy(message.mac_address, fMAC, 6); 657 658 message_type type = message.Type(); 659 660 switch (type) { 661 case DHCP_REQUEST: 662 case DHCP_RELEASE: 663 { 664 // add server identifier option 665 uint8* next = message.PrepareMessage(type); 666 next = message.PutOption(next, OPTION_SERVER_ADDRESS, 667 (uint32)fServer.sin_addr.s_addr); 668 669 // In RENEWAL or REBINDING state, we must set the client_address field, and not 670 // use OPTION_REQUEST_IP_ADDRESS for DHCP_REQUEST messages 671 if (type == DHCP_REQUEST && (state == INIT || state == REQUESTING)) { 672 next = message.PutOption(next, OPTION_REQUEST_IP_ADDRESS, fAssignedAddress); 673 next = message.PutOption(next, OPTION_REQUEST_PARAMETERS, 674 kRequiredParameters, sizeof(kRequiredParameters)); 675 } else 676 message.client_address = fAssignedAddress; 677 678 message.FinishOptions(next); 679 break; 680 } 681 682 case DHCP_DISCOVER: 683 { 684 uint8 *next = message.PrepareMessage(type); 685 next = message.PutOption(next, OPTION_REQUEST_PARAMETERS, 686 kRequiredParameters, sizeof(kRequiredParameters)); 687 message.FinishOptions(next); 688 break; 689 } 690 691 default: 692 // the default options are fine 693 break; 694 } 695 } 696 697 698 void 699 DHCPClient::_ResetTimeout(int socket, time_t& timeout, uint32& tries) 700 { 701 timeout = DEFAULT_TIMEOUT; 702 tries = 0; 703 704 struct timeval value; 705 value.tv_sec = timeout; 706 value.tv_usec = 0; 707 setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &value, sizeof(value)); 708 } 709 710 711 bool 712 DHCPClient::_TimeoutShift(int socket, time_t& timeout, uint32& tries) 713 { 714 timeout += timeout; 715 if (timeout > MAX_TIMEOUT) { 716 timeout = DEFAULT_TIMEOUT; 717 718 if (++tries > 2) 719 return false; 720 } 721 printf("DHCP timeout shift: %lu secs (try %lu)\n", timeout, tries); 722 723 struct timeval value; 724 value.tv_sec = timeout; 725 value.tv_usec = 0; 726 setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &value, sizeof(value)); 727 728 return true; 729 } 730 731 732 BString 733 DHCPClient::_ToString(const uint8* data) const 734 { 735 BString target = inet_ntoa(*(in_addr*)data); 736 return target; 737 } 738 739 740 BString 741 DHCPClient::_ToString(in_addr_t address) const 742 { 743 BString target = inet_ntoa(*(in_addr*)&address); 744 return target; 745 } 746 747 748 status_t 749 DHCPClient::_SendMessage(int socket, dhcp_message& message, sockaddr_in& address) const 750 { 751 ssize_t bytesSent = sendto(socket, &message, message.Size(), 752 address.sin_addr.s_addr == INADDR_BROADCAST ? MSG_BCAST : 0, 753 (struct sockaddr*)&address, sizeof(sockaddr_in)); 754 if (bytesSent < 0) 755 return errno; 756 757 return B_OK; 758 } 759 760 761 dhcp_state 762 DHCPClient::_CurrentState() const 763 { 764 bigtime_t now = system_time(); 765 766 if (now > fLeaseTime || fStatus < B_OK) 767 return INIT; 768 if (now >= fRebindingTime) 769 return REBINDING; 770 if (now >= fRenewalTime) 771 return RENEWAL; 772 773 return BOUND; 774 } 775 776 777 void 778 DHCPClient::MessageReceived(BMessage* message) 779 { 780 switch (message->what) { 781 case kMsgLeaseTime: 782 { 783 dhcp_state state = _CurrentState(); 784 785 bigtime_t next; 786 if (_Negotiate(state) == B_OK) { 787 switch (state) { 788 case RENEWAL: 789 next = fRebindingTime; 790 break; 791 case REBINDING: 792 default: 793 next = fRenewalTime; 794 break; 795 } 796 } else { 797 switch (state) { 798 case RENEWAL: 799 next = (fLeaseTime - fRebindingTime) / 4 + system_time(); 800 break; 801 case REBINDING: 802 default: 803 next = (fLeaseTime - fRenewalTime) / 4 + system_time(); 804 break; 805 } 806 } 807 808 _RestartLease(next - system_time()); 809 break; 810 } 811 812 default: 813 BHandler::MessageReceived(message); 814 break; 815 } 816 } 817 818