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>
35 #include <processing/jobs/callback_job.h>
37 #define DHCP_SERVER_PORT 67
38 #define DHCP_CLIENT_PORT 68
41 typedef struct private_dhcp_socket_t private_dhcp_socket_t
;
44 * Private data of an dhcp_socket_t object.
46 struct private_dhcp_socket_t
{
49 * Public dhcp_socket_t interface.
54 * Random number generator
59 * List of transactions in DISCOVER
61 linked_list_t
*discover
;
64 * List of transactions in REQUEST
66 linked_list_t
*request
;
69 * List of successfully completed transactions
71 linked_list_t
*completed
;
74 * Lock for transactions
79 * Condvar to wait for transaction completion
84 * Threads waiting in condvar
99 * Do we use per-identity or random leases (and MAC addresses)
104 * DHCP server address, or broadcast
109 * Callback job receiving DHCP responses
115 * DHCP opcode (or BOOTP actually)
123 * Some DHCP options used
128 DHCP_NBNS_SERVER
= 44,
129 DHCP_REQUESTED_IP
= 50,
130 DHCP_MESSAGE_TYPE
= 53,
132 DHCP_PARAM_REQ_LIST
= 55,
134 } dhcp_option_type_t
;
137 * DHCP messages types in the DHCP_MESSAGE_TYPE option
148 } dhcp_message_type_t
;
150 * DHCP option encoding, a TLV
152 typedef struct __attribute__((packed
)) {
159 * DHCP message format, with a maximum size options buffer
161 typedef struct __attribute__((packed
)) {
164 u_int8_t hw_addr_len
;
166 u_int32_t transaction_id
;
167 u_int16_t number_of_seconds
;
169 u_int32_t client_address
;
170 u_int32_t your_address
;
171 u_int32_t server_address
;
172 u_int32_t gateway_address
;
173 char client_hw_addr
[6];
174 char client_hw_padding
[10];
175 char server_hostname
[64];
176 char boot_filename
[128];
177 u_int32_t magic_cookie
;
182 * Prepare a DHCP message for a given transaction
184 static int prepare_dhcp(private_dhcp_socket_t
*this,
185 dhcp_transaction_t
*transaction
,
186 dhcp_message_type_t type
, dhcp_t
*dhcp
)
188 chunk_t chunk
, broadcast
= chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
189 identification_t
*identity
;
190 dhcp_option_t
*option
;
195 memset(dhcp
, 0, sizeof(*dhcp
));
196 dhcp
->opcode
= BOOTREQUEST
;
197 dhcp
->hw_type
= ARPHRD_ETHER
;
198 dhcp
->hw_addr_len
= 6;
199 dhcp
->transaction_id
= transaction
->get_id(transaction
);
200 if (chunk_equals(broadcast
, this->dst
->get_address(this->dst
)))
202 /* TODO: send with 0.0.0.0 source address */
206 /* act as relay agent */
207 src
= charon
->kernel_interface
->get_source_addr(
208 charon
->kernel_interface
, this->dst
, NULL
);
211 memcpy(&dhcp
->gateway_address
, src
->get_address(src
).ptr
,
212 sizeof(dhcp
->gateway_address
));
217 identity
= transaction
->get_identity(transaction
);
218 chunk
= identity
->get_encoding(identity
);
219 /* magic bytes, a locally administered unicast MAC */
220 dhcp
->client_hw_addr
[0] = 0x7A;
221 dhcp
->client_hw_addr
[1] = 0xA7;
222 /* with ID specific postfix */
223 if (this->identity_lease
)
225 id
= htonl(chunk_hash(chunk
));
229 id
= transaction
->get_id(transaction
);
231 memcpy(&dhcp
->client_hw_addr
[2], &id
, sizeof(id
));
233 dhcp
->magic_cookie
= htonl(0x63825363);
235 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
236 option
->type
= DHCP_MESSAGE_TYPE
;
238 option
->data
[0] = type
;
239 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
241 option
= (dhcp_option_t
*)&dhcp
->options
[optlen
];
242 option
->type
= DHCP_HOST_NAME
;
243 option
->len
= min(chunk
.len
, 64);
244 memcpy(option
->data
, chunk
.ptr
, option
->len
);
245 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
251 * Send a DHCP message with given options length
253 static bool send_dhcp(private_dhcp_socket_t
*this,
254 dhcp_transaction_t
*transaction
, dhcp_t
*dhcp
, int optlen
)
259 dst
= transaction
->get_server(transaction
);
264 len
= offsetof(dhcp_t
, magic_cookie
) + ((optlen
+ 4) / 64 * 64 + 64);
265 return sendto(this->send
, dhcp
, len
, 0, dst
->get_sockaddr(dst
),
266 *dst
->get_sockaddr_len(dst
)) == len
;
270 * Send DHCP discover using a given transaction
272 static bool discover(private_dhcp_socket_t
*this,
273 dhcp_transaction_t
*transaction
)
275 dhcp_option_t
*option
;
279 optlen
= prepare_dhcp(this, transaction
, DHCP_DISCOVER
, &dhcp
);
281 DBG1(DBG_CFG
, "sending DHCP DISCOVER to %H", this->dst
);
283 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
284 option
->type
= DHCP_PARAM_REQ_LIST
;
286 option
->data
[0] = DHCP_DNS_SERVER
;
287 option
->data
[1] = DHCP_NBNS_SERVER
;
288 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
290 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
292 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
294 DBG1(DBG_CFG
, "sending DHCP DISCOVER failed: %s", strerror(errno
));
301 * Send DHCP request using a given transaction
303 static bool request(private_dhcp_socket_t
*this,
304 dhcp_transaction_t
*transaction
)
306 dhcp_option_t
*option
;
308 host_t
*offer
, *server
;
312 optlen
= prepare_dhcp(this, transaction
, DHCP_REQUEST
, &dhcp
);
314 offer
= transaction
->get_address(transaction
);
315 server
= transaction
->get_server(transaction
);
316 if (!offer
|| !server
)
320 DBG1(DBG_CFG
, "sending DHCP REQUEST for %H to %H", offer
, server
);
322 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
323 option
->type
= DHCP_REQUESTED_IP
;
325 chunk
= offer
->get_address(offer
);
326 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
327 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
329 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
330 option
->type
= DHCP_SERVER_ID
;
332 chunk
= server
->get_address(server
);
333 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
334 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
336 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
337 option
->type
= DHCP_PARAM_REQ_LIST
;
339 option
->data
[0] = DHCP_DNS_SERVER
;
340 option
->data
[1] = DHCP_NBNS_SERVER
;
341 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
343 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
345 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
347 DBG1(DBG_CFG
, "sending DHCP REQUEST failed: %s", strerror(errno
));
353 METHOD(dhcp_socket_t
, enroll
, dhcp_transaction_t
*,
354 private_dhcp_socket_t
*this, identification_t
*identity
)
356 dhcp_transaction_t
*transaction
;
360 this->rng
->get_bytes(this->rng
, sizeof(id
), (u_int8_t
*)&id
);
361 transaction
= dhcp_transaction_create(id
, identity
);
363 this->mutex
->lock(this->mutex
);
364 this->discover
->insert_last(this->discover
, transaction
);
366 while (try <= DHCP_TRIES
&& discover(this, transaction
))
368 if (!this->condvar
->timed_wait(this->condvar
, this->mutex
, 1000 * try) &&
369 this->request
->find_first(this->request
, NULL
,
370 (void**)&transaction
) == SUCCESS
)
376 if (this->discover
->remove(this->discover
, transaction
, NULL
))
377 { /* no OFFER received */
378 this->mutex
->unlock(this->mutex
);
379 transaction
->destroy(transaction
);
380 DBG1(DBG_CFG
, "DHCP disover timed out");
385 while (try <= DHCP_TRIES
&& request(this, transaction
))
387 if (!this->condvar
->timed_wait(this->condvar
, this->mutex
, 1000 * try) &&
388 this->completed
->remove(this->completed
, transaction
, NULL
))
394 if (this->request
->remove(this->request
, transaction
, NULL
))
395 { /* no ACK received */
396 this->mutex
->unlock(this->mutex
);
397 transaction
->destroy(transaction
);
398 DBG1(DBG_CFG
, "DHCP request timed out");
401 this->mutex
->unlock(this->mutex
);
406 METHOD(dhcp_socket_t
, release
, void,
407 private_dhcp_socket_t
*this, dhcp_transaction_t
*transaction
)
409 dhcp_option_t
*option
;
411 host_t
*release
, *server
;
415 optlen
= prepare_dhcp(this, transaction
, DHCP_RELEASE
, &dhcp
);
417 release
= transaction
->get_address(transaction
);
418 server
= transaction
->get_server(transaction
);
419 if (!release
|| !server
)
423 DBG1(DBG_CFG
, "sending DHCP RELEASE for %H to %H", release
, server
);
425 chunk
= release
->get_address(release
);
426 memcpy(&dhcp
.client_address
, chunk
.ptr
,
427 min(chunk
.len
, sizeof(dhcp
.client_address
)));
429 option
= (dhcp_option_t
*)&dhcp
.options
[optlen
];
430 option
->type
= DHCP_SERVER_ID
;
432 chunk
= server
->get_address(server
);
433 memcpy(option
->data
, chunk
.ptr
, min(chunk
.len
, option
->len
));
434 optlen
+= sizeof(dhcp_option_t
) + option
->len
;
436 dhcp
.options
[optlen
++] = DHCP_OPTEND
;
438 if (!send_dhcp(this, transaction
, &dhcp
, optlen
))
440 DBG1(DBG_CFG
, "sending DHCP RELEASE failed: %s", strerror(errno
));
445 * Handle a DHCP OFFER
447 static void handle_offer(private_dhcp_socket_t
*this, dhcp_t
*dhcp
, int optlen
)
449 dhcp_transaction_t
*transaction
;
450 enumerator_t
*enumerator
;
451 host_t
*offer
, *server
;
453 offer
= host_create_from_chunk(AF_INET
,
454 chunk_from_thing(dhcp
->your_address
), 0);
455 server
= host_create_from_chunk(AF_INET
,
456 chunk_from_thing(dhcp
->server_address
), DHCP_SERVER_PORT
);
457 DBG1(DBG_CFG
, "received DHCP OFFER %H from %H", offer
, server
);
459 this->mutex
->lock(this->mutex
);
460 enumerator
= this->discover
->create_enumerator(this->discover
);
461 while (enumerator
->enumerate(enumerator
, &transaction
))
463 if (transaction
->get_id(transaction
) == dhcp
->transaction_id
)
465 this->discover
->remove_at(this->discover
, enumerator
);
466 this->request
->insert_last(this->request
, transaction
);
467 transaction
->set_address(transaction
, offer
->clone(offer
));
468 transaction
->set_server(transaction
, server
->clone(server
));
472 enumerator
->destroy(enumerator
);
473 this->mutex
->unlock(this->mutex
);
474 this->condvar
->broadcast(this->condvar
);
475 offer
->destroy(offer
);
476 server
->destroy(server
);
482 static void handle_ack(private_dhcp_socket_t
*this, dhcp_t
*dhcp
, int optlen
)
484 dhcp_transaction_t
*transaction
;
485 enumerator_t
*enumerator
;
488 offer
= host_create_from_chunk(AF_INET
,
489 chunk_from_thing(dhcp
->your_address
), 0);
490 DBG1(DBG_CFG
, "received DHCP ACK for %H", offer
);
492 this->mutex
->lock(this->mutex
);
493 enumerator
= this->request
->create_enumerator(this->request
);
494 while (enumerator
->enumerate(enumerator
, &transaction
))
496 if (transaction
->get_id(transaction
) == dhcp
->transaction_id
)
498 this->request
->remove_at(this->request
, enumerator
);
499 this->completed
->insert_last(this->completed
, transaction
);
503 enumerator
->destroy(enumerator
);
504 this->mutex
->unlock(this->mutex
);
505 this->condvar
->broadcast(this->condvar
);
506 offer
->destroy(offer
);
510 * Receive DHCP responses
512 static job_requeue_t
receive_dhcp(private_dhcp_socket_t
*this)
514 struct sockaddr_ll addr
;
515 socklen_t addr_len
= sizeof(addr
);
516 struct __attribute__((packed
)) {
521 int oldstate
, optlen
, origoptlen
, optsize
, optpos
= 0;
523 dhcp_option_t
*option
;
525 oldstate
= thread_cancelability(TRUE
);
526 len
= recvfrom(this->receive
, &packet
, sizeof(packet
), 0,
527 (struct sockaddr
*)&addr
, &addr_len
);
528 thread_cancelability(oldstate
);
530 if (len
>= sizeof(struct iphdr
) + sizeof(struct udphdr
) +
531 offsetof(dhcp_t
, options
))
533 origoptlen
= optlen
= len
- sizeof(struct iphdr
) +
534 sizeof(struct udphdr
) + offsetof(dhcp_t
, options
);
535 while (optlen
> sizeof(dhcp_option_t
))
537 option
= (dhcp_option_t
*)&packet
.dhcp
.options
[optpos
];
538 optsize
= sizeof(dhcp_option_t
) + option
->len
;
539 if (option
->type
== DHCP_OPTEND
|| optlen
< optsize
)
543 if (option
->type
== DHCP_MESSAGE_TYPE
&& option
->len
== 1)
545 switch (option
->data
[0])
548 handle_offer(this, &packet
.dhcp
, origoptlen
);
551 handle_ack(this, &packet
.dhcp
, origoptlen
);
561 return JOB_REQUEUE_DIRECT
;
564 METHOD(dhcp_socket_t
, destroy
, void,
565 private_dhcp_socket_t
*this)
569 this->job
->cancel(this->job
);
571 while (this->waiting
)
573 this->condvar
->signal(this->condvar
);
579 if (this->receive
> 0)
581 close(this->receive
);
583 this->mutex
->destroy(this->mutex
);
584 this->condvar
->destroy(this->condvar
);
585 this->discover
->destroy_offset(this->discover
,
586 offsetof(dhcp_transaction_t
, destroy
));
587 this->request
->destroy_offset(this->request
,
588 offsetof(dhcp_transaction_t
, destroy
));
589 this->completed
->destroy_offset(this->completed
,
590 offsetof(dhcp_transaction_t
, destroy
));
591 DESTROY_IF(this->rng
);
592 DESTROY_IF(this->dst
);
599 dhcp_socket_t
*dhcp_socket_create()
601 private_dhcp_socket_t
*this;
602 struct sockaddr_in src
;
604 struct sock_filter dhcp_filter_code
[] = {
605 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
,
606 offsetof(struct iphdr
, protocol
)),
607 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, IPPROTO_UDP
, 0, 14),
608 BPF_STMT(BPF_LD
+BPF_H
+BPF_ABS
, sizeof(struct iphdr
) +
609 offsetof(struct udphdr
, source
)),
610 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_SERVER_PORT
, 0, 12),
611 BPF_STMT(BPF_LD
+BPF_H
+BPF_ABS
, sizeof(struct iphdr
) +
612 offsetof(struct udphdr
, dest
)),
613 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, DHCP_CLIENT_PORT
, 0, 10),
614 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
615 sizeof(struct udphdr
) + offsetof(dhcp_t
, opcode
)),
616 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, BOOTREPLY
, 0, 8),
617 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
618 sizeof(struct udphdr
) + offsetof(dhcp_t
, hw_type
)),
619 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, ARPHRD_ETHER
, 0, 6),
620 BPF_STMT(BPF_LD
+BPF_B
+BPF_ABS
, sizeof(struct iphdr
) +
621 sizeof(struct udphdr
) + offsetof(dhcp_t
, hw_addr_len
)),
622 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, 6, 0, 4),
623 BPF_STMT(BPF_LD
+BPF_W
+BPF_ABS
, sizeof(struct iphdr
) +
624 sizeof(struct udphdr
) + offsetof(dhcp_t
, magic_cookie
)),
625 BPF_JUMP(BPF_JMP
+BPF_JEQ
+BPF_K
, 0x63825363, 0, 2),
626 BPF_STMT(BPF_LD
+BPF_W
+BPF_LEN
, 0),
627 BPF_STMT(BPF_RET
+BPF_A
, 0),
628 BPF_STMT(BPF_RET
+BPF_K
, 0),
630 struct sock_fprog dhcp_filter
= {
631 sizeof(dhcp_filter_code
) / sizeof(struct sock_filter
),
641 .rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
),
642 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
643 .condvar
= condvar_create(CONDVAR_TYPE_DEFAULT
),
644 .discover
= linked_list_create(),
645 .request
= linked_list_create(),
646 .completed
= linked_list_create(),
651 DBG1(DBG_CFG
, "unable to create RNG");
655 this->identity_lease
= lib
->settings
->get_bool(lib
->settings
,
656 "charon.plugins.dhcp.identity_lease", FALSE
);
657 this->dst
= host_create_from_string(lib
->settings
->get_str(lib
->settings
,
658 "charon.plugins.dhcp.server", "255.255.255.255"),
662 DBG1(DBG_CFG
, "configured DHCP server address invalid");
667 this->send
= socket(AF_INET
, SOCK_DGRAM
, IPPROTO_UDP
);
668 if (this->send
== -1)
670 DBG1(DBG_CFG
, "unable to create DHCP send socket: %s", strerror(errno
));
674 if (setsockopt(this->send
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof(on
)) == -1)
676 DBG1(DBG_CFG
, "unable to reuse DHCP socket address: %s", strerror(errno
));
680 if (setsockopt(this->send
, SOL_SOCKET
, SO_BROADCAST
, &on
, sizeof(on
)) == -1)
682 DBG1(DBG_CFG
, "unable to broadcast on DHCP socket: %s", strerror(errno
));
686 src
.sin_family
= AF_INET
;
687 src
.sin_port
= htons(DHCP_CLIENT_PORT
);
688 src
.sin_addr
.s_addr
= INADDR_ANY
;
689 if (bind(this->send
, (struct sockaddr
*)&src
, sizeof(src
)) == -1)
691 DBG1(DBG_CFG
, "unable to bind DHCP send socket: %s", strerror(errno
));
696 this->receive
= socket(AF_PACKET
, SOCK_DGRAM
, htons(ETH_P_IP
));
697 if (this->receive
== -1)
699 DBG1(DBG_NET
, "opening DHCP receive socket failed: %s", strerror(errno
));
703 if (setsockopt(this->receive
, SOL_SOCKET
, SO_ATTACH_FILTER
,
704 &dhcp_filter
, sizeof(dhcp_filter
)) < 0)
706 DBG1(DBG_CFG
, "installing DHCP socket filter failed: %s",
712 this->job
= callback_job_create((callback_job_cb_t
)receive_dhcp
,
714 charon
->processor
->queue_job(charon
->processor
, (job_t
*)this->job
);
716 return &this->public;