xref: /haiku/src/servers/net/DHCPClient.cpp (revision 9d6d3fcf5fe8308cd020cecf89dede440346f8c4)
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