]>
Commit | Line | Data |
---|---|---|
ddc93db6 MW |
1 | /* |
2 | * Copyright (C) 2010 Martin Willi | |
3 | * Copyright (C) 2010 revosec AG | |
4 | * | |
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>. | |
9 | * | |
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 | |
13 | * for more details. | |
14 | */ | |
15 | ||
16 | #include "dhcp_socket.h" | |
17 | ||
18 | #include <unistd.h> | |
19 | #include <errno.h> | |
20 | #include <string.h> | |
21 | #include <netinet/in.h> | |
22 | #include <netinet/ip.h> | |
23 | #include <netinet/udp.h> | |
24 | #include <linux/if_arp.h> | |
20ee54d0 MW |
25 | #include <linux/if_ether.h> |
26 | #include <linux/filter.h> | |
ddc93db6 | 27 | |
12642a68 | 28 | #include <collections/linked_list.h> |
ddc93db6 MW |
29 | #include <utils/identification.h> |
30 | #include <threading/mutex.h> | |
31 | #include <threading/condvar.h> | |
32 | #include <threading/thread.h> | |
33 | ||
c5f7146b | 34 | #include <hydra.h> |
ddc93db6 MW |
35 | #include <daemon.h> |
36 | #include <processing/jobs/callback_job.h> | |
37 | ||
38 | #define DHCP_SERVER_PORT 67 | |
39 | #define DHCP_CLIENT_PORT 68 | |
4f0932ec | 40 | #define DHCP_TRIES 5 |
ddc93db6 MW |
41 | |
42 | typedef struct private_dhcp_socket_t private_dhcp_socket_t; | |
43 | ||
44 | /** | |
45 | * Private data of an dhcp_socket_t object. | |
46 | */ | |
47 | struct private_dhcp_socket_t { | |
48 | ||
49 | /** | |
50 | * Public dhcp_socket_t interface. | |
51 | */ | |
52 | dhcp_socket_t public; | |
53 | ||
54 | /** | |
55 | * Random number generator | |
56 | */ | |
57 | rng_t *rng; | |
58 | ||
59 | /** | |
20ee54d0 | 60 | * List of transactions in DISCOVER |
ddc93db6 | 61 | */ |
20ee54d0 MW |
62 | linked_list_t *discover; |
63 | ||
64 | /** | |
65 | * List of transactions in REQUEST | |
66 | */ | |
67 | linked_list_t *request; | |
ddc93db6 MW |
68 | |
69 | /** | |
70 | * List of successfully completed transactions | |
71 | */ | |
72 | linked_list_t *completed; | |
73 | ||
74 | /** | |
75 | * Lock for transactions | |
76 | */ | |
77 | mutex_t *mutex; | |
78 | ||
79 | /** | |
80 | * Condvar to wait for transaction completion | |
81 | */ | |
82 | condvar_t *condvar; | |
83 | ||
84 | /** | |
85 | * Threads waiting in condvar | |
86 | */ | |
87 | int waiting; | |
88 | ||
89 | /** | |
20ee54d0 MW |
90 | * DHCP send socket |
91 | */ | |
92 | int send; | |
93 | ||
94 | /** | |
95 | * DHCP receive socket | |
ddc93db6 | 96 | */ |
20ee54d0 | 97 | int receive; |
ddc93db6 | 98 | |
e06a6154 MW |
99 | /** |
100 | * Do we use per-identity or random leases (and MAC addresses) | |
101 | */ | |
102 | bool identity_lease; | |
103 | ||
ddc93db6 MW |
104 | /** |
105 | * DHCP server address, or broadcast | |
106 | */ | |
107 | host_t *dst; | |
bc6ec4de TB |
108 | |
109 | /** | |
110 | * Force configured destination address | |
111 | */ | |
112 | bool force_dst; | |
ddc93db6 MW |
113 | }; |
114 | ||
4f0932ec MW |
115 | /** |
116 | * DHCP opcode (or BOOTP actually) | |
117 | */ | |
ddc93db6 MW |
118 | typedef enum { |
119 | BOOTREQUEST = 1, | |
120 | BOOTREPLY = 2, | |
121 | } dhcp_opcode_t; | |
122 | ||
4f0932ec MW |
123 | /** |
124 | * Some DHCP options used | |
125 | */ | |
ddc93db6 | 126 | typedef enum { |
913eb696 | 127 | DHCP_DNS_SERVER = 6, |
ddc93db6 | 128 | DHCP_HOST_NAME = 12, |
913eb696 | 129 | DHCP_NBNS_SERVER = 44, |
4f0932ec | 130 | DHCP_REQUESTED_IP = 50, |
ddc93db6 | 131 | DHCP_MESSAGE_TYPE = 53, |
4f0932ec | 132 | DHCP_SERVER_ID = 54, |
ddc93db6 | 133 | DHCP_PARAM_REQ_LIST = 55, |
12821bd6 | 134 | DHCP_CLIENT_ID = 61, |
20ee54d0 | 135 | DHCP_OPTEND = 255, |
ddc93db6 MW |
136 | } dhcp_option_type_t; |
137 | ||
4f0932ec MW |
138 | /** |
139 | * DHCP messages types in the DHCP_MESSAGE_TYPE option | |
140 | */ | |
ddc93db6 MW |
141 | typedef enum { |
142 | DHCP_DISCOVER = 1, | |
20ee54d0 MW |
143 | DHCP_OFFER = 2, |
144 | DHCP_REQUEST = 3, | |
145 | DHCP_DECLINE = 4, | |
146 | DHCP_ACK = 5, | |
147 | DHCP_NAK = 6, | |
148 | DHCP_RELEASE = 7, | |
149 | DHCP_INFORM = 8, | |
ddc93db6 | 150 | } dhcp_message_type_t; |
4f0932ec MW |
151 | /** |
152 | * DHCP option encoding, a TLV | |
153 | */ | |
ddc93db6 MW |
154 | typedef struct __attribute__((packed)) { |
155 | u_int8_t type; | |
156 | u_int8_t len; | |
157 | char data[]; | |
158 | } dhcp_option_t; | |
159 | ||
4f0932ec MW |
160 | /** |
161 | * DHCP message format, with a maximum size options buffer | |
162 | */ | |
ddc93db6 MW |
163 | typedef struct __attribute__((packed)) { |
164 | u_int8_t opcode; | |
165 | u_int8_t hw_type; | |
166 | u_int8_t hw_addr_len; | |
167 | u_int8_t hop_count; | |
168 | u_int32_t transaction_id; | |
169 | u_int16_t number_of_seconds; | |
170 | u_int16_t flags; | |
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; | |
180 | char options[252]; | |
181 | } dhcp_t; | |
182 | ||
183 | /** | |
20ee54d0 | 184 | * Prepare a DHCP message for a given transaction |
ddc93db6 | 185 | */ |
20ee54d0 | 186 | static int prepare_dhcp(private_dhcp_socket_t *this, |
4f0932ec MW |
187 | dhcp_transaction_t *transaction, |
188 | dhcp_message_type_t type, dhcp_t *dhcp) | |
ddc93db6 | 189 | { |
20ee54d0 | 190 | chunk_t chunk, broadcast = chunk_from_chars(0xFF,0xFF,0xFF,0xFF); |
ddc93db6 MW |
191 | identification_t *identity; |
192 | dhcp_option_t *option; | |
ddc93db6 | 193 | int optlen = 0; |
ddc93db6 | 194 | host_t *src; |
e06a6154 | 195 | u_int32_t id; |
ddc93db6 | 196 | |
20ee54d0 MW |
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); | |
ddc93db6 MW |
202 | if (chunk_equals(broadcast, this->dst->get_address(this->dst))) |
203 | { | |
2b3c87b4 MW |
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); | |
ddc93db6 MW |
207 | /* TODO: send with 0.0.0.0 source address */ |
208 | } | |
209 | else | |
210 | { | |
211 | /* act as relay agent */ | |
f6659688 TB |
212 | src = hydra->kernel_interface->get_source_addr(hydra->kernel_interface, |
213 | this->dst, NULL); | |
ddc93db6 MW |
214 | if (src) |
215 | { | |
20ee54d0 MW |
216 | memcpy(&dhcp->gateway_address, src->get_address(src).ptr, |
217 | sizeof(dhcp->gateway_address)); | |
ddc93db6 MW |
218 | src->destroy(src); |
219 | } | |
220 | } | |
221 | ||
222 | identity = transaction->get_identity(transaction); | |
20ee54d0 | 223 | chunk = identity->get_encoding(identity); |
ddc93db6 | 224 | /* magic bytes, a locally administered unicast MAC */ |
20ee54d0 MW |
225 | dhcp->client_hw_addr[0] = 0x7A; |
226 | dhcp->client_hw_addr[1] = 0xA7; | |
ddc93db6 | 227 | /* with ID specific postfix */ |
e06a6154 MW |
228 | if (this->identity_lease) |
229 | { | |
230 | id = htonl(chunk_hash(chunk)); | |
231 | } | |
232 | else | |
233 | { | |
234 | id = transaction->get_id(transaction); | |
235 | } | |
236 | memcpy(&dhcp->client_hw_addr[2], &id, sizeof(id)); | |
ddc93db6 | 237 | |
20ee54d0 | 238 | dhcp->magic_cookie = htonl(0x63825363); |
ddc93db6 | 239 | |
20ee54d0 | 240 | option = (dhcp_option_t*)&dhcp->options[optlen]; |
ddc93db6 MW |
241 | option->type = DHCP_MESSAGE_TYPE; |
242 | option->len = 1; | |
4f0932ec | 243 | option->data[0] = type; |
ddc93db6 MW |
244 | optlen += sizeof(dhcp_option_t) + option->len; |
245 | ||
12821bd6 AS |
246 | if (identity->get_type(identity) == ID_FQDN) |
247 | { | |
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; | |
253 | } | |
254 | ||
20ee54d0 | 255 | option = (dhcp_option_t*)&dhcp->options[optlen]; |
12821bd6 | 256 | option->type = DHCP_CLIENT_ID; |
20ee54d0 MW |
257 | option->len = min(chunk.len, 64); |
258 | memcpy(option->data, chunk.ptr, option->len); | |
ddc93db6 MW |
259 | optlen += sizeof(dhcp_option_t) + option->len; |
260 | ||
20ee54d0 MW |
261 | return optlen; |
262 | } | |
263 | ||
4f0932ec MW |
264 | /** |
265 | * Send a DHCP message with given options length | |
266 | */ | |
267 | static bool send_dhcp(private_dhcp_socket_t *this, | |
268 | dhcp_transaction_t *transaction, dhcp_t *dhcp, int optlen) | |
269 | { | |
270 | host_t *dst; | |
271 | ssize_t len; | |
272 | ||
273 | dst = transaction->get_server(transaction); | |
bc6ec4de | 274 | if (!dst || this->force_dst) |
4f0932ec MW |
275 | { |
276 | dst = this->dst; | |
277 | } | |
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; | |
281 | } | |
282 | ||
20ee54d0 MW |
283 | /** |
284 | * Send DHCP discover using a given transaction | |
285 | */ | |
286 | static bool discover(private_dhcp_socket_t *this, | |
287 | dhcp_transaction_t *transaction) | |
288 | { | |
913eb696 | 289 | dhcp_option_t *option; |
20ee54d0 | 290 | dhcp_t dhcp; |
20ee54d0 MW |
291 | int optlen; |
292 | ||
4f0932ec | 293 | optlen = prepare_dhcp(this, transaction, DHCP_DISCOVER, &dhcp); |
20ee54d0 MW |
294 | |
295 | DBG1(DBG_CFG, "sending DHCP DISCOVER to %H", this->dst); | |
296 | ||
913eb696 MW |
297 | option = (dhcp_option_t*)&dhcp.options[optlen]; |
298 | option->type = DHCP_PARAM_REQ_LIST; | |
299 | option->len = 2; | |
300 | option->data[0] = DHCP_DNS_SERVER; | |
301 | option->data[1] = DHCP_NBNS_SERVER; | |
302 | optlen += sizeof(dhcp_option_t) + option->len; | |
303 | ||
20ee54d0 | 304 | dhcp.options[optlen++] = DHCP_OPTEND; |
ddc93db6 | 305 | |
4f0932ec | 306 | if (!send_dhcp(this, transaction, &dhcp, optlen)) |
ddc93db6 MW |
307 | { |
308 | DBG1(DBG_CFG, "sending DHCP DISCOVER failed: %s", strerror(errno)); | |
20ee54d0 | 309 | return FALSE; |
ddc93db6 | 310 | } |
20ee54d0 MW |
311 | return TRUE; |
312 | } | |
313 | ||
314 | /** | |
315 | * Send DHCP request using a given transaction | |
316 | */ | |
317 | static bool request(private_dhcp_socket_t *this, | |
4f0932ec | 318 | dhcp_transaction_t *transaction) |
20ee54d0 | 319 | { |
4f0932ec MW |
320 | dhcp_option_t *option; |
321 | dhcp_t dhcp; | |
322 | host_t *offer, *server; | |
323 | chunk_t chunk; | |
324 | int optlen; | |
325 | ||
326 | optlen = prepare_dhcp(this, transaction, DHCP_REQUEST, &dhcp); | |
327 | ||
328 | offer = transaction->get_address(transaction); | |
329 | server = transaction->get_server(transaction); | |
330 | if (!offer || !server) | |
331 | { | |
332 | return FALSE; | |
333 | } | |
334 | DBG1(DBG_CFG, "sending DHCP REQUEST for %H to %H", offer, server); | |
335 | ||
336 | option = (dhcp_option_t*)&dhcp.options[optlen]; | |
337 | option->type = DHCP_REQUESTED_IP; | |
338 | option->len = 4; | |
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; | |
342 | ||
343 | option = (dhcp_option_t*)&dhcp.options[optlen]; | |
344 | option->type = DHCP_SERVER_ID; | |
345 | option->len = 4; | |
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; | |
349 | ||
913eb696 MW |
350 | option = (dhcp_option_t*)&dhcp.options[optlen]; |
351 | option->type = DHCP_PARAM_REQ_LIST; | |
352 | option->len = 2; | |
353 | option->data[0] = DHCP_DNS_SERVER; | |
354 | option->data[1] = DHCP_NBNS_SERVER; | |
355 | optlen += sizeof(dhcp_option_t) + option->len; | |
356 | ||
4f0932ec MW |
357 | dhcp.options[optlen++] = DHCP_OPTEND; |
358 | ||
359 | if (!send_dhcp(this, transaction, &dhcp, optlen)) | |
360 | { | |
361 | DBG1(DBG_CFG, "sending DHCP REQUEST failed: %s", strerror(errno)); | |
362 | return FALSE; | |
363 | } | |
364 | return TRUE; | |
ddc93db6 MW |
365 | } |
366 | ||
367 | METHOD(dhcp_socket_t, enroll, dhcp_transaction_t*, | |
368 | private_dhcp_socket_t *this, identification_t *identity) | |
369 | { | |
370 | dhcp_transaction_t *transaction; | |
371 | u_int32_t id; | |
4f0932ec | 372 | int try; |
ddc93db6 | 373 | |
7ae26710 TB |
374 | if (!this->rng->get_bytes(this->rng, sizeof(id), (u_int8_t*)&id)) |
375 | { | |
376 | DBG1(DBG_CFG, "DHCP DISCOVER failed, no transaction ID"); | |
377 | return NULL; | |
378 | } | |
ddc93db6 | 379 | transaction = dhcp_transaction_create(id, identity); |
ddc93db6 | 380 | |
20ee54d0 MW |
381 | this->mutex->lock(this->mutex); |
382 | this->discover->insert_last(this->discover, transaction); | |
4f0932ec MW |
383 | try = 1; |
384 | while (try <= DHCP_TRIES && discover(this, transaction)) | |
20ee54d0 | 385 | { |
4f0932ec MW |
386 | if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000 * try) && |
387 | this->request->find_first(this->request, NULL, | |
388 | (void**)&transaction) == SUCCESS) | |
20ee54d0 MW |
389 | { |
390 | break; | |
391 | } | |
4f0932ec | 392 | try++; |
20ee54d0 MW |
393 | } |
394 | if (this->discover->remove(this->discover, transaction, NULL)) | |
395 | { /* no OFFER received */ | |
396 | this->mutex->unlock(this->mutex); | |
397 | transaction->destroy(transaction); | |
500a6d38 | 398 | DBG1(DBG_CFG, "DHCP DISCOVER timed out"); |
20ee54d0 MW |
399 | return NULL; |
400 | } | |
401 | ||
4f0932ec MW |
402 | try = 1; |
403 | while (try <= DHCP_TRIES && request(this, transaction)) | |
20ee54d0 | 404 | { |
4f0932ec MW |
405 | if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000 * try) && |
406 | this->completed->remove(this->completed, transaction, NULL)) | |
20ee54d0 MW |
407 | { |
408 | break; | |
409 | } | |
4f0932ec | 410 | try++; |
20ee54d0 MW |
411 | } |
412 | if (this->request->remove(this->request, transaction, NULL)) | |
413 | { /* no ACK received */ | |
414 | this->mutex->unlock(this->mutex); | |
415 | transaction->destroy(transaction); | |
19d49af5 | 416 | DBG1(DBG_CFG, "DHCP REQUEST timed out"); |
20ee54d0 MW |
417 | return NULL; |
418 | } | |
419 | this->mutex->unlock(this->mutex); | |
420 | ||
4f0932ec | 421 | return transaction; |
ddc93db6 MW |
422 | } |
423 | ||
913eb696 MW |
424 | METHOD(dhcp_socket_t, release, void, |
425 | private_dhcp_socket_t *this, dhcp_transaction_t *transaction) | |
426 | { | |
427 | dhcp_option_t *option; | |
428 | dhcp_t dhcp; | |
429 | host_t *release, *server; | |
430 | chunk_t chunk; | |
431 | int optlen; | |
432 | ||
433 | optlen = prepare_dhcp(this, transaction, DHCP_RELEASE, &dhcp); | |
434 | ||
435 | release = transaction->get_address(transaction); | |
436 | server = transaction->get_server(transaction); | |
437 | if (!release || !server) | |
438 | { | |
439 | return; | |
440 | } | |
441 | DBG1(DBG_CFG, "sending DHCP RELEASE for %H to %H", release, server); | |
442 | ||
443 | chunk = release->get_address(release); | |
444 | memcpy(&dhcp.client_address, chunk.ptr, | |
445 | min(chunk.len, sizeof(dhcp.client_address))); | |
446 | ||
447 | option = (dhcp_option_t*)&dhcp.options[optlen]; | |
448 | option->type = DHCP_SERVER_ID; | |
449 | option->len = 4; | |
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; | |
453 | ||
454 | dhcp.options[optlen++] = DHCP_OPTEND; | |
455 | ||
456 | if (!send_dhcp(this, transaction, &dhcp, optlen)) | |
457 | { | |
458 | DBG1(DBG_CFG, "sending DHCP RELEASE failed: %s", strerror(errno)); | |
459 | } | |
460 | } | |
461 | ||
20ee54d0 | 462 | /** |
4f0932ec | 463 | * Handle a DHCP OFFER |
20ee54d0 MW |
464 | */ |
465 | static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen) | |
466 | { | |
b262429e | 467 | dhcp_transaction_t *transaction = NULL; |
20ee54d0 | 468 | enumerator_t *enumerator; |
9bac426b | 469 | host_t *offer, *server = NULL; |
20ee54d0 MW |
470 | |
471 | offer = host_create_from_chunk(AF_INET, | |
4f0932ec | 472 | chunk_from_thing(dhcp->your_address), 0); |
20ee54d0 MW |
473 | |
474 | this->mutex->lock(this->mutex); | |
475 | enumerator = this->discover->create_enumerator(this->discover); | |
476 | while (enumerator->enumerate(enumerator, &transaction)) | |
477 | { | |
478 | if (transaction->get_id(transaction) == dhcp->transaction_id) | |
479 | { | |
480 | this->discover->remove_at(this->discover, enumerator); | |
481 | this->request->insert_last(this->request, transaction); | |
4f0932ec MW |
482 | break; |
483 | } | |
484 | } | |
485 | enumerator->destroy(enumerator); | |
b262429e MW |
486 | |
487 | if (transaction) | |
488 | { | |
489 | int optsize, optpos = 0, pos; | |
490 | dhcp_option_t *option; | |
491 | ||
492 | while (optlen > sizeof(dhcp_option_t)) | |
493 | { | |
494 | option = (dhcp_option_t*)&dhcp->options[optpos]; | |
495 | optsize = sizeof(dhcp_option_t) + option->len; | |
496 | if (option->type == DHCP_OPTEND || optlen < optsize) | |
497 | { | |
498 | break; | |
499 | } | |
500 | if (option->type == DHCP_DNS_SERVER || | |
501 | option->type == DHCP_NBNS_SERVER) | |
502 | { | |
503 | for (pos = 0; pos + 4 <= option->len; pos += 4) | |
504 | { | |
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)); | |
508 | } | |
509 | } | |
9bac426b | 510 | if (!server && option->type == DHCP_SERVER_ID && option->len == 4) |
e3bde0ef MW |
511 | { |
512 | server = host_create_from_chunk(AF_INET, | |
513 | chunk_create(option->data, 4), DHCP_SERVER_PORT); | |
514 | } | |
b262429e MW |
515 | optlen -= optsize; |
516 | optpos += optsize; | |
517 | } | |
e3bde0ef MW |
518 | if (!server) |
519 | { | |
520 | server = host_create_from_chunk(AF_INET, | |
521 | chunk_from_thing(dhcp->server_address), DHCP_SERVER_PORT); | |
522 | } | |
523 | DBG1(DBG_CFG, "received DHCP OFFER %H from %H", offer, server); | |
524 | transaction->set_address(transaction, offer->clone(offer)); | |
9bac426b | 525 | transaction->set_server(transaction, server); |
b262429e | 526 | } |
4f0932ec MW |
527 | this->mutex->unlock(this->mutex); |
528 | this->condvar->broadcast(this->condvar); | |
529 | offer->destroy(offer); | |
4f0932ec MW |
530 | } |
531 | ||
532 | /** | |
533 | * Handle a DHCP ACK | |
534 | */ | |
535 | static void handle_ack(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen) | |
536 | { | |
537 | dhcp_transaction_t *transaction; | |
538 | enumerator_t *enumerator; | |
539 | host_t *offer; | |
540 | ||
541 | offer = host_create_from_chunk(AF_INET, | |
542 | chunk_from_thing(dhcp->your_address), 0); | |
4f0932ec MW |
543 | |
544 | this->mutex->lock(this->mutex); | |
545 | enumerator = this->request->create_enumerator(this->request); | |
546 | while (enumerator->enumerate(enumerator, &transaction)) | |
547 | { | |
548 | if (transaction->get_id(transaction) == dhcp->transaction_id) | |
549 | { | |
f0212e88 | 550 | DBG1(DBG_CFG, "received DHCP ACK for %H", offer); |
4f0932ec MW |
551 | this->request->remove_at(this->request, enumerator); |
552 | this->completed->insert_last(this->completed, transaction); | |
20ee54d0 MW |
553 | break; |
554 | } | |
555 | } | |
556 | enumerator->destroy(enumerator); | |
557 | this->mutex->unlock(this->mutex); | |
558 | this->condvar->broadcast(this->condvar); | |
559 | offer->destroy(offer); | |
560 | } | |
561 | ||
562 | /** | |
563 | * Receive DHCP responses | |
564 | */ | |
565 | static job_requeue_t receive_dhcp(private_dhcp_socket_t *this) | |
566 | { | |
567 | struct sockaddr_ll addr; | |
568 | socklen_t addr_len = sizeof(addr); | |
569 | struct __attribute__((packed)) { | |
570 | struct iphdr ip; | |
571 | struct udphdr udp; | |
572 | dhcp_t dhcp; | |
573 | } packet; | |
574 | int oldstate, optlen, origoptlen, optsize, optpos = 0; | |
575 | ssize_t len; | |
576 | dhcp_option_t *option; | |
577 | ||
578 | oldstate = thread_cancelability(TRUE); | |
579 | len = recvfrom(this->receive, &packet, sizeof(packet), 0, | |
580 | (struct sockaddr*)&addr, &addr_len); | |
581 | thread_cancelability(oldstate); | |
582 | ||
583 | if (len >= sizeof(struct iphdr) + sizeof(struct udphdr) + | |
584 | offsetof(dhcp_t, options)) | |
585 | { | |
586 | origoptlen = optlen = len - sizeof(struct iphdr) + | |
587 | sizeof(struct udphdr) + offsetof(dhcp_t, options); | |
588 | while (optlen > sizeof(dhcp_option_t)) | |
589 | { | |
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) | |
593 | { | |
594 | break; | |
595 | } | |
596 | if (option->type == DHCP_MESSAGE_TYPE && option->len == 1) | |
597 | { | |
598 | switch (option->data[0]) | |
599 | { | |
600 | case DHCP_OFFER: | |
601 | handle_offer(this, &packet.dhcp, origoptlen); | |
602 | break; | |
4f0932ec MW |
603 | case DHCP_ACK: |
604 | handle_ack(this, &packet.dhcp, origoptlen); | |
20ee54d0 MW |
605 | default: |
606 | break; | |
607 | } | |
608 | break; | |
609 | } | |
610 | optlen -= optsize; | |
611 | optpos += optsize; | |
612 | } | |
613 | } | |
614 | return JOB_REQUEUE_DIRECT; | |
615 | } | |
616 | ||
ddc93db6 MW |
617 | METHOD(dhcp_socket_t, destroy, void, |
618 | private_dhcp_socket_t *this) | |
619 | { | |
ddc93db6 MW |
620 | while (this->waiting) |
621 | { | |
622 | this->condvar->signal(this->condvar); | |
623 | } | |
20ee54d0 MW |
624 | if (this->send > 0) |
625 | { | |
626 | close(this->send); | |
627 | } | |
628 | if (this->receive > 0) | |
ddc93db6 | 629 | { |
20ee54d0 | 630 | close(this->receive); |
ddc93db6 MW |
631 | } |
632 | this->mutex->destroy(this->mutex); | |
633 | this->condvar->destroy(this->condvar); | |
20ee54d0 MW |
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)); | |
ddc93db6 MW |
640 | DESTROY_IF(this->rng); |
641 | DESTROY_IF(this->dst); | |
642 | free(this); | |
643 | } | |
644 | ||
645 | /** | |
646 | * See header | |
647 | */ | |
648 | dhcp_socket_t *dhcp_socket_create() | |
649 | { | |
650 | private_dhcp_socket_t *this; | |
9d5b688a TB |
651 | struct sockaddr_in src = { |
652 | .sin_family = AF_INET, | |
653 | .sin_port = htons(DHCP_CLIENT_PORT), | |
654 | .sin_addr = { | |
655 | .s_addr = INADDR_ANY, | |
656 | }, | |
657 | }; | |
ddc93db6 | 658 | int on = 1; |
20ee54d0 MW |
659 | struct sock_filter dhcp_filter_code[] = { |
660 | BPF_STMT(BPF_LD+BPF_B+BPF_ABS, | |
661 | offsetof(struct iphdr, protocol)), | |
f0212e88 | 662 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_UDP, 0, 16), |
20ee54d0 MW |
663 | BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) + |
664 | offsetof(struct udphdr, source)), | |
f0212e88 | 665 | BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 0, 14), |
20ee54d0 MW |
666 | BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) + |
667 | offsetof(struct udphdr, dest)), | |
f0212e88 MW |
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), | |
20ee54d0 MW |
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), | |
686 | }; | |
687 | struct sock_fprog dhcp_filter = { | |
688 | sizeof(dhcp_filter_code) / sizeof(struct sock_filter), | |
689 | dhcp_filter_code, | |
690 | }; | |
ddc93db6 MW |
691 | |
692 | INIT(this, | |
693 | .public = { | |
694 | .enroll = _enroll, | |
913eb696 | 695 | .release = _release, |
ddc93db6 MW |
696 | .destroy = _destroy, |
697 | }, | |
698 | .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK), | |
699 | .mutex = mutex_create(MUTEX_TYPE_DEFAULT), | |
700 | .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), | |
20ee54d0 MW |
701 | .discover = linked_list_create(), |
702 | .request = linked_list_create(), | |
ddc93db6 MW |
703 | .completed = linked_list_create(), |
704 | ); | |
705 | ||
706 | if (!this->rng) | |
707 | { | |
708 | DBG1(DBG_CFG, "unable to create RNG"); | |
709 | destroy(this); | |
710 | return NULL; | |
711 | } | |
e06a6154 | 712 | this->identity_lease = lib->settings->get_bool(lib->settings, |
42500c27 TB |
713 | "%s.plugins.dhcp.identity_lease", FALSE, |
714 | charon->name); | |
bc6ec4de TB |
715 | this->force_dst = lib->settings->get_str(lib->settings, |
716 | "%s.plugins.dhcp.force_server_address", FALSE, | |
717 | charon->name); | |
ddc93db6 | 718 | this->dst = host_create_from_string(lib->settings->get_str(lib->settings, |
42500c27 TB |
719 | "%s.plugins.dhcp.server", "255.255.255.255", |
720 | charon->name), DHCP_SERVER_PORT); | |
ddc93db6 MW |
721 | if (!this->dst) |
722 | { | |
723 | DBG1(DBG_CFG, "configured DHCP server address invalid"); | |
724 | destroy(this); | |
725 | return NULL; | |
726 | } | |
727 | ||
20ee54d0 MW |
728 | this->send = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); |
729 | if (this->send == -1) | |
ddc93db6 MW |
730 | { |
731 | DBG1(DBG_CFG, "unable to create DHCP send socket: %s", strerror(errno)); | |
732 | destroy(this); | |
733 | return NULL; | |
734 | } | |
20ee54d0 | 735 | if (setsockopt(this->send, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) |
ddc93db6 MW |
736 | { |
737 | DBG1(DBG_CFG, "unable to reuse DHCP socket address: %s", strerror(errno)); | |
738 | destroy(this); | |
739 | return NULL; | |
740 | } | |
20ee54d0 | 741 | if (setsockopt(this->send, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1) |
ddc93db6 MW |
742 | { |
743 | DBG1(DBG_CFG, "unable to broadcast on DHCP socket: %s", strerror(errno)); | |
744 | destroy(this); | |
745 | return NULL; | |
746 | } | |
20ee54d0 | 747 | if (bind(this->send, (struct sockaddr*)&src, sizeof(src)) == -1) |
ddc93db6 MW |
748 | { |
749 | DBG1(DBG_CFG, "unable to bind DHCP send socket: %s", strerror(errno)); | |
750 | destroy(this); | |
751 | return NULL; | |
752 | } | |
753 | ||
20ee54d0 MW |
754 | this->receive = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); |
755 | if (this->receive == -1) | |
756 | { | |
757 | DBG1(DBG_NET, "opening DHCP receive socket failed: %s", strerror(errno)); | |
758 | destroy(this); | |
759 | return NULL; | |
760 | } | |
761 | if (setsockopt(this->receive, SOL_SOCKET, SO_ATTACH_FILTER, | |
762 | &dhcp_filter, sizeof(dhcp_filter)) < 0) | |
763 | { | |
764 | DBG1(DBG_CFG, "installing DHCP socket filter failed: %s", | |
765 | strerror(errno)); | |
766 | destroy(this); | |
767 | return NULL; | |
768 | } | |
769 | ||
26d77eb3 TB |
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)); | |
20ee54d0 | 773 | |
ddc93db6 MW |
774 | return &this->public; |
775 | } | |
776 |