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 <utils/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 * Callback job receiving DHCP responses
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 /* TODO: send with 0.0.0.0 source address */
208 /* act as relay agent */
209 src
= hydra
->kernel_interface
->get_source_addr(hydra
->kernel_interface
,
213 memcpy(&dhcp
->gateway_address
, src
->get_address(src
).ptr
,
214 sizeof(dhcp
->gateway_address
));
219 identity
= transaction
->get_identity(transaction
);
220 chunk
= identity
->get_encoding(identity
);
221 /* magic bytes, a locally administered unicast MAC */
222 dhcp
->client_hw_addr
[0] = 0x7A;
223 dhcp
->client_hw_addr
[1] = 0xA7;
224 /* with ID specific postfix */
225 if (this->identity_lease
)
227 id
= htonl(chunk_hash(chunk
));
231 id
= transaction
->get_id(transaction
);
233 memcpy(&dhcp
->client_hw_addr
[2], &id
, sizeof(id
));
235 dhcp
->magic_cookie
= htonl(0x63825363);
237 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
238 option
->type
= DHCP_MESSAGE_TYPE
;
240 option
->data
[0] = type
;
241 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
243 if (identity
->get_type(identity
) == ID_FQDN
)
245 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
246 option
->type
= DHCP_HOST_NAME
;
247 option
->len
= min(chunk
.len
, 64);
248 memcpy(option
->data
, chunk
.ptr
, option
->len
);
249 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
252 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
253 option
->type
= DHCP_CLIENT_ID
;
254 option
->len
= min(chunk
.len
, 64);
255 memcpy(option
->data
, chunk
.ptr
, option
->len
);
256 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
262 * Send a DHCP message with given options length
264 static bool send_dhcp(private_dhcp_socket_t
*this,
265 dhcp_transaction_t
*transaction
, dhcp_t
*dhcp
, int optlen
)
270 dst
= transaction
->get_server(transaction
);
275 len
= offsetof(dhcp_t
, magic_cookie
) + ((optlen
+ 4) / 64 * 64 + 64);
276 return sendto(this->send
, dhcp
, len
, 0, dst
->get_sockaddr(dst
),
277 *dst
->get_sockaddr_len(dst
)) == len
;
281 * Send DHCP discover using a given transaction
283 static bool discover(private_dhcp_socket_t
*this,
284 dhcp_transaction_t
*transaction
)
286 dhcp_option_t
*option
;
290 optlen
= prepare_dhcp(this, transaction
, DHCP_DISCOVER
, &dhcp
);
292 DBG1(DBG_CFG
, "sending DHCP DISCOVER to %H", this->dst
);
294 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
295 option
->type
= DHCP_PARAM_REQ_LIST
;
297 option
->data
[0] = DHCP_DNS_SERVER
;
298 option
->data
[1] = DHCP_NBNS_SERVER
;
299 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
301 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
303 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
305 DBG1(DBG_CFG
, "sending DHCP DISCOVER failed: %s", strerror(errno
));
312 * Send DHCP request using a given transaction
314 static bool request(private_dhcp_socket_t
*this,
315 dhcp_transaction_t
*transaction
)
317 dhcp_option_t
*option
;
319 host_t
*offer
, *server
;
323 optlen
= prepare_dhcp(this, transaction
, DHCP_REQUEST
, &dhcp
);
325 offer
= transaction
->get_address(transaction
);
326 server
= transaction
->get_server(transaction
);
327 if (!offer
|| !server
)
331 DBG1(DBG_CFG
, "sending DHCP REQUEST for %H to %H", offer
, server
);
333 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
334 option
->type
= DHCP_REQUESTED_IP
;
336 chunk
= offer
->get_address(offer
);
337 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
338 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
340 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
341 option
->type
= DHCP_SERVER_ID
;
343 chunk
= server
->get_address(server
);
344 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
345 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
347 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
348 option
->type
= DHCP_PARAM_REQ_LIST
;
350 option
->data
[0] = DHCP_DNS_SERVER
;
351 option
->data
[1] = DHCP_NBNS_SERVER
;
352 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
354 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
356 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
358 DBG1(DBG_CFG
, "sending DHCP REQUEST failed: %s", strerror(errno
));
364 METHOD(dhcp_socket_t
, enroll
, dhcp_transaction_t
*,
365 private_dhcp_socket_t
*this, identification_t
*identity
)
367 dhcp_transaction_t
*transaction
;
371 this->rng
->get_bytes(this->rng
, sizeof(id
), (u_int8_t
*)&id
);
372 transaction
= dhcp_transaction_create(id
, identity
);
374 this->mutex
->lock(this->mutex
);
375 this->discover
->insert_last(this->discover
, transaction
);
377 while (try <= DHCP_TRIES
&& discover(this, transaction
))
379 if (!this->condvar
->timed_wait(this->condvar
, this->mutex
, 1000 * try) &&
380 this->request
->find_first(this->request
, NULL
,
381 (void**)&transaction
) == SUCCESS
)
387 if (this->discover
->remove(this->discover
, transaction
, NULL
))
388 { /* no OFFER received */
389 this->mutex
->unlock(this->mutex
);
390 transaction
->destroy(transaction
);
391 DBG1(DBG_CFG
, "DHCP DISCOVER timed out");
396 while (try <= DHCP_TRIES
&& request(this, transaction
))
398 if (!this->condvar
->timed_wait(this->condvar
, this->mutex
, 1000 * try) &&
399 this->completed
->remove(this->completed
, transaction
, NULL
))
405 if (this->request
->remove(this->request
, transaction
, NULL
))
406 { /* no ACK received */
407 this->mutex
->unlock(this->mutex
);
408 transaction
->destroy(transaction
);
409 DBG1(DBG_CFG
, "DHCP REQUEST timed out");
412 this->mutex
->unlock(this->mutex
);
417 METHOD(dhcp_socket_t
, release
, void,
418 private_dhcp_socket_t
*this, dhcp_transaction_t
*transaction
)
420 dhcp_option_t
*option
;
422 host_t
*release
, *server
;
426 optlen
= prepare_dhcp(this, transaction
, DHCP_RELEASE
, &dhcp
);
428 release
= transaction
->get_address(transaction
);
429 server
= transaction
->get_server(transaction
);
430 if (!release
|| !server
)
434 DBG1(DBG_CFG
, "sending DHCP RELEASE for %H to %H", release
, server
);
436 chunk
= release
->get_address(release
);
437 memcpy(&dhcp
.client_address
, chunk
.ptr
,
438 min(chunk
.len
, sizeof(dhcp
.client_address
)));
440 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
441 option
->type
= DHCP_SERVER_ID
;
443 chunk
= server
->get_address(server
);
444 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
445 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
447 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
449 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
451 DBG1(DBG_CFG
, "sending DHCP RELEASE failed: %s", strerror(errno
));
456 * Handle a DHCP OFFER
458 static void handle_offer(private_dhcp_socket_t
*this, dhcp_t
*dhcp
, int optlen
)
460 dhcp_transaction_t
*transaction
= NULL
;
461 enumerator_t
*enumerator
;
462 host_t
*offer
, *server
;
464 offer
= host_create_from_chunk(AF_INET
,
465 chunk_from_thing(dhcp
->your_address
), 0);
467 this->mutex
->lock(this->mutex
);
468 enumerator
= this->discover
->create_enumerator(this->discover
);
469 while (enumerator
->enumerate(enumerator
, &transaction
))
471 if (transaction
->get_id(transaction
) == dhcp
->transaction_id
)
473 this->discover
->remove_at(this->discover
, enumerator
);
474 this->request
->insert_last(this->request
, transaction
);
478 enumerator
->destroy(enumerator
);
482 int optsize
, optpos
= 0, pos
;
483 dhcp_option_t
*option
;
485 while (optlen
> sizeof(dhcp_option_t
))
487 option
= (dhcp_option_t
*)&dhcp
->options
[optpos
];
488 optsize
= sizeof(dhcp_option_t
) + option
->len
;
489 if (option
->type
== DHCP_OPTEND
|| optlen
< optsize
)
493 if (option
->type
== DHCP_DNS_SERVER
||
494 option
->type
== DHCP_NBNS_SERVER
)
496 for (pos
= 0; pos
+ 4 <= option
->len
; pos
+= 4)
498 transaction
->add_attribute(transaction
, option
->type
==
499 DHCP_DNS_SERVER ? INTERNAL_IP4_DNS
: INTERNAL_IP4_NBNS
,
500 chunk_create((char*)&option
->data
[pos
], 4));
503 if (option
->type
== DHCP_SERVER_ID
&& option
->len
== 4)
505 server
= host_create_from_chunk(AF_INET
,
506 chunk_create(option
->data
, 4), DHCP_SERVER_PORT
);
513 server
= host_create_from_chunk(AF_INET
,
514 chunk_from_thing(dhcp
->server_address
), DHCP_SERVER_PORT
);
516 DBG1(DBG_CFG
, "received DHCP OFFER %H from %H", offer
, server
);
517 transaction
->set_address(transaction
, offer
->clone(offer
));
518 transaction
->set_server(transaction
, server
->clone(server
));
520 this->mutex
->unlock(this->mutex
);
521 this->condvar
->broadcast(this->condvar
);
522 offer
->destroy(offer
);
523 server
->destroy(server
);
529 static void handle_ack(private_dhcp_socket_t
*this, dhcp_t
*dhcp
, int optlen
)
531 dhcp_transaction_t
*transaction
;
532 enumerator_t
*enumerator
;
535 offer
= host_create_from_chunk(AF_INET
,
536 chunk_from_thing(dhcp
->your_address
), 0);
538 this->mutex
->lock(this->mutex
);
539 enumerator
= this->request
->create_enumerator(this->request
);
540 while (enumerator
->enumerate(enumerator
, &transaction
))
542 if (transaction
->get_id(transaction
) == dhcp
->transaction_id
)
544 DBG1(DBG_CFG
, "received DHCP ACK for %H", offer
);
545 this->request
->remove_at(this->request
, enumerator
);
546 this->completed
->insert_last(this->completed
, transaction
);
550 enumerator
->destroy(enumerator
);
551 this->mutex
->unlock(this->mutex
);
552 this->condvar
->broadcast(this->condvar
);
553 offer
->destroy(offer
);
557 * Receive DHCP responses
559 static job_requeue_t
receive_dhcp(private_dhcp_socket_t
*this)
561 struct sockaddr_ll addr
;
562 socklen_t addr_len
= sizeof(addr
);
563 struct __attribute__((packed
)) {
568 int oldstate
, optlen
, origoptlen
, optsize
, optpos
= 0;
570 dhcp_option_t
*option
;
572 oldstate
= thread_cancelability(TRUE
);
573 len
= recvfrom(this->receive
, &packet
, sizeof(packet
), 0,
574 (struct sockaddr
*)&addr
, &addr_len
);
575 thread_cancelability(oldstate
);
577 if (len
>= sizeof(struct iphdr
) + sizeof(struct udphdr
) +
578 offsetof(dhcp_t
, options
))
580 origoptlen
= optlen
= len
- sizeof(struct iphdr
) +
581 sizeof(struct udphdr
) + offsetof(dhcp_t
, options
);
582 while (optlen
> sizeof(dhcp_option_t
))
584 option
= (dhcp_option_t
*)&packet
.dhcp
.options
[optpos
];
585 optsize
= sizeof(dhcp_option_t
) + option
->len
;
586 if (option
->type
== DHCP_OPTEND
|| optlen
< optsize
)
590 if (option
->type
== DHCP_MESSAGE_TYPE
&& option
->len
== 1)
592 switch (option
->data
[0])
595 handle_offer(this, &packet
.dhcp
, origoptlen
);
598 handle_ack(this, &packet
.dhcp
, origoptlen
);
608 return JOB_REQUEUE_DIRECT
;
611 METHOD(dhcp_socket_t
, destroy
, void,
612 private_dhcp_socket_t
*this)
616 this->job
->cancel(this->job
);
618 while (this->waiting
)
620 this->condvar
->signal(this->condvar
);
626 if (this->receive
> 0)
628 close(this->receive
);
630 this->mutex
->destroy(this->mutex
);
631 this->condvar
->destroy(this->condvar
);
632 this->discover
->destroy_offset(this->discover
,
633 offsetof(dhcp_transaction_t
, destroy
));
634 this->request
->destroy_offset(this->request
,
635 offsetof(dhcp_transaction_t
, destroy
));
636 this->completed
->destroy_offset(this->completed
,
637 offsetof(dhcp_transaction_t
, destroy
));
638 DESTROY_IF(this->rng
);
639 DESTROY_IF(this->dst
);
646 dhcp_socket_t
*dhcp_socket_create()
648 private_dhcp_socket_t
*this;
649 struct sockaddr_in src
;
651 struct sock_filter dhcp_filter_code
[] = {
652 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
,
653 offsetof(struct iphdr
, protocol
)),
654 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, IPPROTO_UDP
, 0, 16),
655 BPF_STMT(BPF_LD
+BPF_H
+BPF_ABS
, sizeof(struct iphdr
) +
656 offsetof(struct udphdr
, source
)),
657 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_SERVER_PORT
, 0, 14),
658 BPF_STMT(BPF_LD
+BPF_H
+BPF_ABS
, sizeof(struct iphdr
) +
659 offsetof(struct udphdr
, dest
)),
660 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_CLIENT_PORT
, 0, 2),
661 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_SERVER_PORT
, 0, 1),
662 BPF_JUMP(BPF_JMP
+BPF_JA
, 0, 0, 10),
663 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
664 sizeof(struct udphdr
) + offsetof(dhcp_t
, opcode
)),
665 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, BOOTREPLY
, 0, 8),
666 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
667 sizeof(struct udphdr
) + offsetof(dhcp_t
, hw_type
)),
668 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, ARPHRD_ETHER
, 0, 6),
669 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
670 sizeof(struct udphdr
) + offsetof(dhcp_t
, hw_addr_len
)),
671 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, 6, 0, 4),
672 BPF_STMT(BPF_LD
+BPF_W
+BPF_ABS
, sizeof(struct iphdr
) +
673 sizeof(struct udphdr
) + offsetof(dhcp_t
, magic_cookie
)),
674 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, 0x63825363, 0, 2),
675 BPF_STMT(BPF_LD
+BPF_W
+BPF_LEN
, 0),
676 BPF_STMT(BPF_RET
+BPF_A
, 0),
677 BPF_STMT(BPF_RET
+BPF_K
, 0),
679 struct sock_fprog dhcp_filter
= {
680 sizeof(dhcp_filter_code
) / sizeof(struct sock_filter
),
690 .rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
),
691 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
692 .condvar
= condvar_create(CONDVAR_TYPE_DEFAULT
),
693 .discover
= linked_list_create(),
694 .request
= linked_list_create(),
695 .completed
= linked_list_create(),
700 DBG1(DBG_CFG
, "unable to create RNG");
704 this->identity_lease
= lib
->settings
->get_bool(lib
->settings
,
705 "charon.plugins.dhcp.identity_lease", FALSE
);
706 this->dst
= host_create_from_string(lib
->settings
->get_str(lib
->settings
,
707 "charon.plugins.dhcp.server", "255.255.255.255"),
711 DBG1(DBG_CFG
, "configured DHCP server address invalid");
716 this->send
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
717 if (this->send
== -1)
719 DBG1(DBG_CFG
, "unable to create DHCP send socket: %s", strerror(errno
));
723 if (setsockopt(this->send
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
)) == -1)
725 DBG1(DBG_CFG
, "unable to reuse DHCP socket address: %s", strerror(errno
));
729 if (setsockopt(this->send
, SOL_SOCKET
, SO_BROADCAST
, &on
, sizeof(on
)) == -1)
731 DBG1(DBG_CFG
, "unable to broadcast on DHCP socket: %s", strerror(errno
));
735 src
.sin_family
= AF_INET
;
736 src
.sin_port
= htons(DHCP_CLIENT_PORT
);
737 src
.sin_addr
.s_addr
= INADDR_ANY
;
738 if (bind(this->send
, (struct sockaddr
*)&src
, sizeof(src
)) == -1)
740 DBG1(DBG_CFG
, "unable to bind DHCP send socket: %s", strerror(errno
));
745 this->receive
= socket(AF_PACKET
, SOCK_DGRAM
, htons(ETH_P_IP
));
746 if (this->receive
== -1)
748 DBG1(DBG_NET
, "opening DHCP receive socket failed: %s", strerror(errno
));
752 if (setsockopt(this->receive
, SOL_SOCKET
, SO_ATTACH_FILTER
,
753 &dhcp_filter
, sizeof(dhcp_filter
)) < 0)
755 DBG1(DBG_CFG
, "installing DHCP socket filter failed: %s",
761 this->job
= callback_job_create((callback_job_cb_t
)receive_dhcp
,
763 lib
->processor
->queue_job(lib
->processor
, (job_t
*)this->job
);
765 return &this->public;