2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 revosec AG
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 #include "dhcp_socket.h"
21 #include <netinet/in.h>
22 #include <netinet/ip.h>
23 #include <netinet/udp.h>
24 #include <linux/if_arp.h>
25 #include <linux/if_ether.h>
26 #include <linux/filter.h>
28 #include <collections/linked_list.h>
29 #include <utils/identification.h>
30 #include <threading/mutex.h>
31 #include <threading/condvar.h>
32 #include <threading/thread.h>
36 #include <processing/jobs/callback_job.h>
38 #define DHCP_SERVER_PORT 67
39 #define DHCP_CLIENT_PORT 68
42 typedef struct private_dhcp_socket_t private_dhcp_socket_t
;
45 * Private data of an dhcp_socket_t object.
47 struct private_dhcp_socket_t
{
50 * Public dhcp_socket_t interface.
55 * Random number generator
60 * List of transactions in DISCOVER
62 linked_list_t
*discover
;
65 * List of transactions in REQUEST
67 linked_list_t
*request
;
70 * List of successfully completed transactions
72 linked_list_t
*completed
;
75 * Lock for transactions
80 * Condvar to wait for transaction completion
85 * Threads waiting in condvar
100 * Do we use per-identity or random leases (and MAC addresses)
105 * DHCP server address, or broadcast
110 * Force configured destination address
116 * DHCP opcode (or BOOTP actually)
124 * Some DHCP options used
129 DHCP_NBNS_SERVER
= 44,
130 DHCP_REQUESTED_IP
= 50,
131 DHCP_MESSAGE_TYPE
= 53,
133 DHCP_PARAM_REQ_LIST
= 55,
136 } dhcp_option_type_t
;
139 * DHCP messages types in the DHCP_MESSAGE_TYPE option
150 } dhcp_message_type_t
;
152 * DHCP option encoding, a TLV
154 typedef struct __attribute__((packed
)) {
161 * DHCP message format, with a maximum size options buffer
163 typedef struct __attribute__((packed
)) {
166 u_int8_t hw_addr_len
;
168 u_int32_t transaction_id
;
169 u_int16_t number_of_seconds
;
171 u_int32_t client_address
;
172 u_int32_t your_address
;
173 u_int32_t server_address
;
174 u_int32_t gateway_address
;
175 char client_hw_addr
[6];
176 char client_hw_padding
[10];
177 char server_hostname
[64];
178 char boot_filename
[128];
179 u_int32_t magic_cookie
;
184 * Prepare a DHCP message for a given transaction
186 static int prepare_dhcp(private_dhcp_socket_t
*this,
187 dhcp_transaction_t
*transaction
,
188 dhcp_message_type_t type
, dhcp_t
*dhcp
)
190 chunk_t chunk
, broadcast
= chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
191 identification_t
*identity
;
192 dhcp_option_t
*option
;
197 memset(dhcp
, 0, sizeof(*dhcp
));
198 dhcp
->opcode
= BOOTREQUEST
;
199 dhcp
->hw_type
= ARPHRD_ETHER
;
200 dhcp
->hw_addr_len
= 6;
201 dhcp
->transaction_id
= transaction
->get_id(transaction
);
202 if (chunk_equals(broadcast
, this->dst
->get_address(this->dst
)))
204 /* Set broadcast flag to get broadcasted replies, as we actually
205 * do not own the MAC we request an address for. */
206 dhcp
->flags
= htons(0x8000);
207 /* TODO: send with 0.0.0.0 source address */
211 /* act as relay agent */
212 src
= hydra
->kernel_interface
->get_source_addr(hydra
->kernel_interface
,
216 memcpy(&dhcp
->gateway_address
, src
->get_address(src
).ptr
,
217 sizeof(dhcp
->gateway_address
));
222 identity
= transaction
->get_identity(transaction
);
223 chunk
= identity
->get_encoding(identity
);
224 /* magic bytes, a locally administered unicast MAC */
225 dhcp
->client_hw_addr
[0] = 0x7A;
226 dhcp
->client_hw_addr
[1] = 0xA7;
227 /* with ID specific postfix */
228 if (this->identity_lease
)
230 id
= htonl(chunk_hash_static(chunk
));
234 id
= transaction
->get_id(transaction
);
236 memcpy(&dhcp
->client_hw_addr
[2], &id
, sizeof(id
));
238 dhcp
->magic_cookie
= htonl(0x63825363);
240 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
241 option
->type
= DHCP_MESSAGE_TYPE
;
243 option
->data
[0] = type
;
244 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
246 if (identity
->get_type(identity
) == ID_FQDN
)
248 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
249 option
->type
= DHCP_HOST_NAME
;
250 option
->len
= min(chunk
.len
, 64);
251 memcpy(option
->data
, chunk
.ptr
, option
->len
);
252 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
255 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
256 option
->type
= DHCP_CLIENT_ID
;
257 option
->len
= min(chunk
.len
, 64);
258 memcpy(option
->data
, chunk
.ptr
, option
->len
);
259 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
265 * Send a DHCP message with given options length
267 static bool send_dhcp(private_dhcp_socket_t
*this,
268 dhcp_transaction_t
*transaction
, dhcp_t
*dhcp
, int optlen
)
273 dst
= transaction
->get_server(transaction
);
274 if (!dst
|| this->force_dst
)
278 len
= offsetof(dhcp_t
, magic_cookie
) + ((optlen
+ 4) / 64 * 64 + 64);
279 return sendto(this->send
, dhcp
, len
, 0, dst
->get_sockaddr(dst
),
280 *dst
->get_sockaddr_len(dst
)) == len
;
284 * Send DHCP discover using a given transaction
286 static bool discover(private_dhcp_socket_t
*this,
287 dhcp_transaction_t
*transaction
)
289 dhcp_option_t
*option
;
293 optlen
= prepare_dhcp(this, transaction
, DHCP_DISCOVER
, &dhcp
);
295 DBG1(DBG_CFG
, "sending DHCP DISCOVER to %H", this->dst
);
297 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
298 option
->type
= DHCP_PARAM_REQ_LIST
;
300 option
->data
[0] = DHCP_DNS_SERVER
;
301 option
->data
[1] = DHCP_NBNS_SERVER
;
302 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
304 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
306 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
308 DBG1(DBG_CFG
, "sending DHCP DISCOVER failed: %s", strerror(errno
));
315 * Send DHCP request using a given transaction
317 static bool request(private_dhcp_socket_t
*this,
318 dhcp_transaction_t
*transaction
)
320 dhcp_option_t
*option
;
322 host_t
*offer
, *server
;
326 optlen
= prepare_dhcp(this, transaction
, DHCP_REQUEST
, &dhcp
);
328 offer
= transaction
->get_address(transaction
);
329 server
= transaction
->get_server(transaction
);
330 if (!offer
|| !server
)
334 DBG1(DBG_CFG
, "sending DHCP REQUEST for %H to %H", offer
, server
);
336 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
337 option
->type
= DHCP_REQUESTED_IP
;
339 chunk
= offer
->get_address(offer
);
340 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
341 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
343 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
344 option
->type
= DHCP_SERVER_ID
;
346 chunk
= server
->get_address(server
);
347 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
348 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
350 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
351 option
->type
= DHCP_PARAM_REQ_LIST
;
353 option
->data
[0] = DHCP_DNS_SERVER
;
354 option
->data
[1] = DHCP_NBNS_SERVER
;
355 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
357 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
359 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
361 DBG1(DBG_CFG
, "sending DHCP REQUEST failed: %s", strerror(errno
));
367 METHOD(dhcp_socket_t
, enroll
, dhcp_transaction_t
*,
368 private_dhcp_socket_t
*this, identification_t
*identity
)
370 dhcp_transaction_t
*transaction
;
374 if (!this->rng
->get_bytes(this->rng
, sizeof(id
), (u_int8_t
*)&id
))
376 DBG1(DBG_CFG
, "DHCP DISCOVER failed, no transaction ID");
379 transaction
= dhcp_transaction_create(id
, identity
);
381 this->mutex
->lock(this->mutex
);
382 this->discover
->insert_last(this->discover
, transaction
);
384 while (try <= DHCP_TRIES
&& discover(this, transaction
))
386 if (!this->condvar
->timed_wait(this->condvar
, this->mutex
, 1000 * try) &&
387 this->request
->find_first(this->request
, NULL
,
388 (void**)&transaction
) == SUCCESS
)
394 if (this->discover
->remove(this->discover
, transaction
, NULL
))
395 { /* no OFFER received */
396 this->mutex
->unlock(this->mutex
);
397 transaction
->destroy(transaction
);
398 DBG1(DBG_CFG
, "DHCP DISCOVER timed out");
403 while (try <= DHCP_TRIES
&& request(this, transaction
))
405 if (!this->condvar
->timed_wait(this->condvar
, this->mutex
, 1000 * try) &&
406 this->completed
->remove(this->completed
, transaction
, NULL
))
412 if (this->request
->remove(this->request
, transaction
, NULL
))
413 { /* no ACK received */
414 this->mutex
->unlock(this->mutex
);
415 transaction
->destroy(transaction
);
416 DBG1(DBG_CFG
, "DHCP REQUEST timed out");
419 this->mutex
->unlock(this->mutex
);
424 METHOD(dhcp_socket_t
, release
, void,
425 private_dhcp_socket_t
*this, dhcp_transaction_t
*transaction
)
427 dhcp_option_t
*option
;
429 host_t
*release
, *server
;
433 optlen
= prepare_dhcp(this, transaction
, DHCP_RELEASE
, &dhcp
);
435 release
= transaction
->get_address(transaction
);
436 server
= transaction
->get_server(transaction
);
437 if (!release
|| !server
)
441 DBG1(DBG_CFG
, "sending DHCP RELEASE for %H to %H", release
, server
);
443 chunk
= release
->get_address(release
);
444 memcpy(&dhcp
.client_address
, chunk
.ptr
,
445 min(chunk
.len
, sizeof(dhcp
.client_address
)));
447 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
448 option
->type
= DHCP_SERVER_ID
;
450 chunk
= server
->get_address(server
);
451 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
452 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
454 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
456 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
458 DBG1(DBG_CFG
, "sending DHCP RELEASE failed: %s", strerror(errno
));
463 * Handle a DHCP OFFER
465 static void handle_offer(private_dhcp_socket_t
*this, dhcp_t
*dhcp
, int optlen
)
467 dhcp_transaction_t
*transaction
= NULL
;
468 enumerator_t
*enumerator
;
469 host_t
*offer
, *server
= NULL
;
471 offer
= host_create_from_chunk(AF_INET
,
472 chunk_from_thing(dhcp
->your_address
), 0);
474 this->mutex
->lock(this->mutex
);
475 enumerator
= this->discover
->create_enumerator(this->discover
);
476 while (enumerator
->enumerate(enumerator
, &transaction
))
478 if (transaction
->get_id(transaction
) == dhcp
->transaction_id
)
480 this->discover
->remove_at(this->discover
, enumerator
);
481 this->request
->insert_last(this->request
, transaction
);
485 enumerator
->destroy(enumerator
);
489 int optsize
, optpos
= 0, pos
;
490 dhcp_option_t
*option
;
492 while (optlen
> sizeof(dhcp_option_t
))
494 option
= (dhcp_option_t
*)&dhcp
->options
[optpos
];
495 optsize
= sizeof(dhcp_option_t
) + option
->len
;
496 if (option
->type
== DHCP_OPTEND
|| optlen
< optsize
)
500 if (option
->type
== DHCP_DNS_SERVER
||
501 option
->type
== DHCP_NBNS_SERVER
)
503 for (pos
= 0; pos
+ 4 <= option
->len
; pos
+= 4)
505 transaction
->add_attribute(transaction
, option
->type
==
506 DHCP_DNS_SERVER ? INTERNAL_IP4_DNS
: INTERNAL_IP4_NBNS
,
507 chunk_create((char*)&option
->data
[pos
], 4));
510 if (!server
&& option
->type
== DHCP_SERVER_ID
&& option
->len
== 4)
512 server
= host_create_from_chunk(AF_INET
,
513 chunk_create(option
->data
, 4), DHCP_SERVER_PORT
);
520 server
= host_create_from_chunk(AF_INET
,
521 chunk_from_thing(dhcp
->server_address
), DHCP_SERVER_PORT
);
523 DBG1(DBG_CFG
, "received DHCP OFFER %H from %H", offer
, server
);
524 transaction
->set_address(transaction
, offer
->clone(offer
));
525 transaction
->set_server(transaction
, server
);
527 this->mutex
->unlock(this->mutex
);
528 this->condvar
->broadcast(this->condvar
);
529 offer
->destroy(offer
);
535 static void handle_ack(private_dhcp_socket_t
*this, dhcp_t
*dhcp
, int optlen
)
537 dhcp_transaction_t
*transaction
;
538 enumerator_t
*enumerator
;
541 offer
= host_create_from_chunk(AF_INET
,
542 chunk_from_thing(dhcp
->your_address
), 0);
544 this->mutex
->lock(this->mutex
);
545 enumerator
= this->request
->create_enumerator(this->request
);
546 while (enumerator
->enumerate(enumerator
, &transaction
))
548 if (transaction
->get_id(transaction
) == dhcp
->transaction_id
)
550 DBG1(DBG_CFG
, "received DHCP ACK for %H", offer
);
551 this->request
->remove_at(this->request
, enumerator
);
552 this->completed
->insert_last(this->completed
, transaction
);
556 enumerator
->destroy(enumerator
);
557 this->mutex
->unlock(this->mutex
);
558 this->condvar
->broadcast(this->condvar
);
559 offer
->destroy(offer
);
563 * Receive DHCP responses
565 static job_requeue_t
receive_dhcp(private_dhcp_socket_t
*this)
567 struct sockaddr_ll addr
;
568 socklen_t addr_len
= sizeof(addr
);
569 struct __attribute__((packed
)) {
574 int oldstate
, optlen
, origoptlen
, optsize
, optpos
= 0;
576 dhcp_option_t
*option
;
578 oldstate
= thread_cancelability(TRUE
);
579 len
= recvfrom(this->receive
, &packet
, sizeof(packet
), 0,
580 (struct sockaddr
*)&addr
, &addr_len
);
581 thread_cancelability(oldstate
);
583 if (len
>= sizeof(struct iphdr
) + sizeof(struct udphdr
) +
584 offsetof(dhcp_t
, options
))
586 origoptlen
= optlen
= len
- sizeof(struct iphdr
) +
587 sizeof(struct udphdr
) + offsetof(dhcp_t
, options
);
588 while (optlen
> sizeof(dhcp_option_t
))
590 option
= (dhcp_option_t
*)&packet
.dhcp
.options
[optpos
];
591 optsize
= sizeof(dhcp_option_t
) + option
->len
;
592 if (option
->type
== DHCP_OPTEND
|| optlen
< optsize
)
596 if (option
->type
== DHCP_MESSAGE_TYPE
&& option
->len
== 1)
598 switch (option
->data
[0])
601 handle_offer(this, &packet
.dhcp
, origoptlen
);
604 handle_ack(this, &packet
.dhcp
, origoptlen
);
614 return JOB_REQUEUE_DIRECT
;
617 METHOD(dhcp_socket_t
, destroy
, void,
618 private_dhcp_socket_t
*this)
620 while (this->waiting
)
622 this->condvar
->signal(this->condvar
);
628 if (this->receive
> 0)
630 close(this->receive
);
632 this->mutex
->destroy(this->mutex
);
633 this->condvar
->destroy(this->condvar
);
634 this->discover
->destroy_offset(this->discover
,
635 offsetof(dhcp_transaction_t
, destroy
));
636 this->request
->destroy_offset(this->request
,
637 offsetof(dhcp_transaction_t
, destroy
));
638 this->completed
->destroy_offset(this->completed
,
639 offsetof(dhcp_transaction_t
, destroy
));
640 DESTROY_IF(this->rng
);
641 DESTROY_IF(this->dst
);
648 dhcp_socket_t
*dhcp_socket_create()
650 private_dhcp_socket_t
*this;
651 struct sockaddr_in src
= {
652 .sin_family
= AF_INET
,
653 .sin_port
= htons(DHCP_CLIENT_PORT
),
655 .s_addr
= INADDR_ANY
,
659 struct sock_filter dhcp_filter_code
[] = {
660 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
,
661 offsetof(struct iphdr
, protocol
)),
662 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, IPPROTO_UDP
, 0, 16),
663 BPF_STMT(BPF_LD
+BPF_H
+BPF_ABS
, sizeof(struct iphdr
) +
664 offsetof(struct udphdr
, source
)),
665 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_SERVER_PORT
, 0, 14),
666 BPF_STMT(BPF_LD
+BPF_H
+BPF_ABS
, sizeof(struct iphdr
) +
667 offsetof(struct udphdr
, dest
)),
668 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_CLIENT_PORT
, 0, 2),
669 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_SERVER_PORT
, 0, 1),
670 BPF_JUMP(BPF_JMP
+BPF_JA
, 0, 0, 10),
671 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
672 sizeof(struct udphdr
) + offsetof(dhcp_t
, opcode
)),
673 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, BOOTREPLY
, 0, 8),
674 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
675 sizeof(struct udphdr
) + offsetof(dhcp_t
, hw_type
)),
676 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, ARPHRD_ETHER
, 0, 6),
677 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
678 sizeof(struct udphdr
) + offsetof(dhcp_t
, hw_addr_len
)),
679 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, 6, 0, 4),
680 BPF_STMT(BPF_LD
+BPF_W
+BPF_ABS
, sizeof(struct iphdr
) +
681 sizeof(struct udphdr
) + offsetof(dhcp_t
, magic_cookie
)),
682 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, 0x63825363, 0, 2),
683 BPF_STMT(BPF_LD
+BPF_W
+BPF_LEN
, 0),
684 BPF_STMT(BPF_RET
+BPF_A
, 0),
685 BPF_STMT(BPF_RET
+BPF_K
, 0),
687 struct sock_fprog dhcp_filter
= {
688 sizeof(dhcp_filter_code
) / sizeof(struct sock_filter
),
698 .rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
),
699 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
700 .condvar
= condvar_create(CONDVAR_TYPE_DEFAULT
),
701 .discover
= linked_list_create(),
702 .request
= linked_list_create(),
703 .completed
= linked_list_create(),
708 DBG1(DBG_CFG
, "unable to create RNG");
712 this->identity_lease
= lib
->settings
->get_bool(lib
->settings
,
713 "%s.plugins.dhcp.identity_lease", FALSE
,
715 this->force_dst
= lib
->settings
->get_str(lib
->settings
,
716 "%s.plugins.dhcp.force_server_address", FALSE
,
718 this->dst
= host_create_from_string(lib
->settings
->get_str(lib
->settings
,
719 "%s.plugins.dhcp.server", "255.255.255.255",
720 charon
->name
), DHCP_SERVER_PORT
);
723 DBG1(DBG_CFG
, "configured DHCP server address invalid");
728 this->send
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
729 if (this->send
== -1)
731 DBG1(DBG_CFG
, "unable to create DHCP send socket: %s", strerror(errno
));
735 if (setsockopt(this->send
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
)) == -1)
737 DBG1(DBG_CFG
, "unable to reuse DHCP socket address: %s", strerror(errno
));
741 if (setsockopt(this->send
, SOL_SOCKET
, SO_BROADCAST
, &on
, sizeof(on
)) == -1)
743 DBG1(DBG_CFG
, "unable to broadcast on DHCP socket: %s", strerror(errno
));
747 if (bind(this->send
, (struct sockaddr
*)&src
, sizeof(src
)) == -1)
749 DBG1(DBG_CFG
, "unable to bind DHCP send socket: %s", strerror(errno
));
754 this->receive
= socket(AF_PACKET
, SOCK_DGRAM
, htons(ETH_P_IP
));
755 if (this->receive
== -1)
757 DBG1(DBG_NET
, "opening DHCP receive socket failed: %s", strerror(errno
));
761 if (setsockopt(this->receive
, SOL_SOCKET
, SO_ATTACH_FILTER
,
762 &dhcp_filter
, sizeof(dhcp_filter
)) < 0)
764 DBG1(DBG_CFG
, "installing DHCP socket filter failed: %s",
770 lib
->processor
->queue_job(lib
->processor
,
771 (job_t
*)callback_job_create_with_prio((callback_job_cb_t
)receive_dhcp
,
772 this, NULL
, (callback_job_cancel_t
)return_false
, JOB_PRIO_CRITICAL
));
774 return &this->public;