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