Added reception of DHCP responses via PACKET socket
[strongswan.git] / src / libcharon / plugins / dhcp / dhcp_socket.c
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>
25 #include <linux/if_ether.h>
26 #include <linux/filter.h>
27
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>
33
34 #include <daemon.h>
35 #include <processing/jobs/callback_job.h>
36
37 #define DHCP_SERVER_PORT 67
38 #define DHCP_CLIENT_PORT 68
39
40 typedef struct private_dhcp_socket_t private_dhcp_socket_t;
41
42 /**
43 * Private data of an dhcp_socket_t object.
44 */
45 struct private_dhcp_socket_t {
46
47 /**
48 * Public dhcp_socket_t interface.
49 */
50 dhcp_socket_t public;
51
52 /**
53 * Random number generator
54 */
55 rng_t *rng;
56
57 /**
58 * List of transactions in DISCOVER
59 */
60 linked_list_t *discover;
61
62 /**
63 * List of transactions in REQUEST
64 */
65 linked_list_t *request;
66
67 /**
68 * List of successfully completed transactions
69 */
70 linked_list_t *completed;
71
72 /**
73 * Lock for transactions
74 */
75 mutex_t *mutex;
76
77 /**
78 * Condvar to wait for transaction completion
79 */
80 condvar_t *condvar;
81
82 /**
83 * Threads waiting in condvar
84 */
85 int waiting;
86
87 /**
88 * DHCP send socket
89 */
90 int send;
91
92 /**
93 * DHCP receive socket
94 */
95 int receive;
96
97 /**
98 * DHCP server address, or broadcast
99 */
100 host_t *dst;
101
102 /**
103 * Callback job receiving DHCP responses
104 */
105 callback_job_t *job;
106 };
107
108 typedef enum {
109 BOOTREQUEST = 1,
110 BOOTREPLY = 2,
111 } dhcp_opcode_t;
112
113 typedef enum {
114 DHCP_HOST_NAME = 12,
115 DHCP_MESSAGE_TYPE = 53,
116 DHCP_PARAM_REQ_LIST = 55,
117 DHCP_OPTEND = 255,
118 } dhcp_option_type_t;
119
120 typedef enum {
121 DHCP_DISCOVER = 1,
122 DHCP_OFFER = 2,
123 DHCP_REQUEST = 3,
124 DHCP_DECLINE = 4,
125 DHCP_ACK = 5,
126 DHCP_NAK = 6,
127 DHCP_RELEASE = 7,
128 DHCP_INFORM = 8,
129 } dhcp_message_type_t;
130
131 typedef enum {
132 DHCP_ROUTER = 3,
133 DHCP_DNS_SERVER = 6,
134 } dhcp_parameter_t;
135
136 typedef struct __attribute__((packed)) {
137 u_int8_t type;
138 u_int8_t len;
139 char data[];
140 } dhcp_option_t;
141
142 typedef struct __attribute__((packed)) {
143 u_int8_t opcode;
144 u_int8_t hw_type;
145 u_int8_t hw_addr_len;
146 u_int8_t hop_count;
147 u_int32_t transaction_id;
148 u_int16_t number_of_seconds;
149 u_int16_t flags;
150 u_int32_t client_address;
151 u_int32_t your_address;
152 u_int32_t server_address;
153 u_int32_t gateway_address;
154 char client_hw_addr[6];
155 char client_hw_padding[10];
156 char server_hostname[64];
157 char boot_filename[128];
158 u_int32_t magic_cookie;
159 char options[252];
160 } dhcp_t;
161
162 /**
163 * Prepare a DHCP message for a given transaction
164 */
165 static int prepare_dhcp(private_dhcp_socket_t *this,
166 dhcp_transaction_t *transaction, dhcp_t *dhcp)
167 {
168 chunk_t chunk, broadcast = chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
169 identification_t *identity;
170 dhcp_option_t *option;
171 int optlen = 0;
172 host_t *src;
173 u_int hash;
174
175 memset(dhcp, 0, sizeof(*dhcp));
176 dhcp->opcode = BOOTREQUEST;
177 dhcp->hw_type = ARPHRD_ETHER;
178 dhcp->hw_addr_len = 6;
179 dhcp->transaction_id = transaction->get_id(transaction);
180 if (chunk_equals(broadcast, this->dst->get_address(this->dst)))
181 {
182 /* TODO: send with 0.0.0.0 source address */
183 }
184 else
185 {
186 /* act as relay agent */
187 src = charon->kernel_interface->get_source_addr(
188 charon->kernel_interface, this->dst, NULL);
189 if (src)
190 {
191 memcpy(&dhcp->gateway_address, src->get_address(src).ptr,
192 sizeof(dhcp->gateway_address));
193 src->destroy(src);
194 }
195 }
196
197 identity = transaction->get_identity(transaction);
198 chunk = identity->get_encoding(identity);
199 /* magic bytes, a locally administered unicast MAC */
200 dhcp->client_hw_addr[0] = 0x7A;
201 dhcp->client_hw_addr[1] = 0xA7;
202 /* with ID specific postfix */
203 hash = htonl(chunk_hash(chunk));
204 memcpy(&dhcp->client_hw_addr[2], &hash, 4);
205
206 dhcp->magic_cookie = htonl(0x63825363);
207
208 option = (dhcp_option_t*)&dhcp->options[optlen];
209 option->type = DHCP_MESSAGE_TYPE;
210 option->len = 1;
211 option->data[0] = DHCP_DISCOVER;
212 optlen += sizeof(dhcp_option_t) + option->len;
213
214 option = (dhcp_option_t*)&dhcp->options[optlen];
215 option->type = DHCP_HOST_NAME;
216 option->len = min(chunk.len, 64);
217 memcpy(option->data, chunk.ptr, option->len);
218 optlen += sizeof(dhcp_option_t) + option->len;
219
220 return optlen;
221 }
222
223 /**
224 * Send DHCP discover using a given transaction
225 */
226 static bool discover(private_dhcp_socket_t *this,
227 dhcp_transaction_t *transaction)
228 {
229 dhcp_option_t *option;
230 dhcp_t dhcp;
231 ssize_t len;
232 int optlen;
233
234 optlen = prepare_dhcp(this, transaction, &dhcp);
235
236 DBG1(DBG_CFG, "sending DHCP DISCOVER to %H", this->dst);
237
238 option = (dhcp_option_t*)&dhcp.options[optlen];
239 option->type = DHCP_PARAM_REQ_LIST;
240 option->len = 2;
241 option->data[0] = DHCP_ROUTER;
242 option->data[1] = DHCP_DNS_SERVER;
243 optlen += sizeof(dhcp_option_t) + option->len;
244
245 dhcp.options[optlen++] = DHCP_OPTEND;
246
247 len = offsetof(dhcp_t, magic_cookie) + ((optlen + 4) / 64 * 64 + 64);
248 if (sendto(this->send, &dhcp, len, 0, this->dst->get_sockaddr(this->dst),
249 *this->dst->get_sockaddr_len(this->dst)) != len)
250 {
251 DBG1(DBG_CFG, "sending DHCP DISCOVER failed: %s", strerror(errno));
252 return FALSE;
253 }
254 return TRUE;
255 }
256
257 /**
258 * Send DHCP request using a given transaction
259 */
260 static bool request(private_dhcp_socket_t *this,
261 dhcp_transaction_t *transaction)
262 {
263 return FALSE;
264 }
265
266 METHOD(dhcp_socket_t, enroll, dhcp_transaction_t*,
267 private_dhcp_socket_t *this, identification_t *identity)
268 {
269 dhcp_transaction_t *transaction;
270 u_int32_t id;
271 int tries;
272
273 this->rng->get_bytes(this->rng, sizeof(id), (u_int8_t*)&id);
274 transaction = dhcp_transaction_create(id, identity);
275
276 this->mutex->lock(this->mutex);
277 this->discover->insert_last(this->discover, transaction);
278 tries = 3;
279 while (tries && discover(this, transaction))
280 {
281 if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000))
282 {
283 break;
284 }
285 tries--;
286 }
287 if (this->discover->remove(this->discover, transaction, NULL))
288 { /* no OFFER received */
289 this->mutex->unlock(this->mutex);
290 transaction->destroy(transaction);
291 return NULL;
292 }
293
294 tries = 3;
295 while (tries && request(this, transaction))
296 {
297 if (!this->condvar->timed_wait(this->condvar, this->mutex, 1000))
298 {
299 break;
300 }
301 tries--;
302 }
303 if (this->request->remove(this->request, transaction, NULL))
304 { /* no ACK received */
305 this->mutex->unlock(this->mutex);
306 transaction->destroy(transaction);
307 return NULL;
308 }
309 this->mutex->unlock(this->mutex);
310
311 transaction->destroy(transaction);
312 return NULL;
313 }
314
315 /**
316 * Handle a DHCP offer
317 */
318 static void handle_offer(private_dhcp_socket_t *this, dhcp_t *dhcp, int optlen)
319 {
320 dhcp_transaction_t *transaction;
321 enumerator_t *enumerator;
322 host_t *offer;
323
324 offer = host_create_from_chunk(AF_INET,
325 chunk_from_thing(dhcp->your_address), 0);
326 if (!offer)
327 {
328 return;
329 }
330 DBG1(DBG_CFG, "received DHCP OFFER %H", offer);
331
332 this->mutex->lock(this->mutex);
333 enumerator = this->discover->create_enumerator(this->discover);
334 while (enumerator->enumerate(enumerator, &transaction))
335 {
336 if (transaction->get_id(transaction) == dhcp->transaction_id)
337 {
338 this->discover->remove_at(this->discover, enumerator);
339 this->request->insert_last(this->request, transaction);
340 transaction->set_address(transaction, offer->clone(offer));
341 break;
342 }
343 }
344 enumerator->destroy(enumerator);
345 this->mutex->unlock(this->mutex);
346 this->condvar->broadcast(this->condvar);
347 offer->destroy(offer);
348 }
349
350 /**
351 * Receive DHCP responses
352 */
353 static job_requeue_t receive_dhcp(private_dhcp_socket_t *this)
354 {
355 struct sockaddr_ll addr;
356 socklen_t addr_len = sizeof(addr);
357 struct __attribute__((packed)) {
358 struct iphdr ip;
359 struct udphdr udp;
360 dhcp_t dhcp;
361 } packet;
362 int oldstate, optlen, origoptlen, optsize, optpos = 0;
363 ssize_t len;
364 dhcp_option_t *option;
365
366 oldstate = thread_cancelability(TRUE);
367 len = recvfrom(this->receive, &packet, sizeof(packet), 0,
368 (struct sockaddr*)&addr, &addr_len);
369 thread_cancelability(oldstate);
370
371 if (len >= sizeof(struct iphdr) + sizeof(struct udphdr) +
372 offsetof(dhcp_t, options))
373 {
374 origoptlen = optlen = len - sizeof(struct iphdr) +
375 sizeof(struct udphdr) + offsetof(dhcp_t, options);
376 while (optlen > sizeof(dhcp_option_t))
377 {
378 option = (dhcp_option_t*)&packet.dhcp.options[optpos];
379 optsize = sizeof(dhcp_option_t) + option->len;
380 if (option->type == DHCP_OPTEND || optlen < optsize)
381 {
382 break;
383 }
384 if (option->type == DHCP_MESSAGE_TYPE && option->len == 1)
385 {
386 switch (option->data[0])
387 {
388 case DHCP_OFFER:
389 handle_offer(this, &packet.dhcp, origoptlen);
390 break;
391 default:
392 break;
393 }
394 break;
395 }
396 optlen -= optsize;
397 optpos += optsize;
398 }
399 }
400 return JOB_REQUEUE_DIRECT;
401 }
402
403 METHOD(dhcp_socket_t, destroy, void,
404 private_dhcp_socket_t *this)
405 {
406 if (this->job)
407 {
408 this->job->cancel(this->job);
409 }
410 while (this->waiting)
411 {
412 this->condvar->signal(this->condvar);
413 }
414 if (this->send > 0)
415 {
416 close(this->send);
417 }
418 if (this->receive > 0)
419 {
420 close(this->receive);
421 }
422 this->mutex->destroy(this->mutex);
423 this->condvar->destroy(this->condvar);
424 this->discover->destroy_offset(this->discover,
425 offsetof(dhcp_transaction_t, destroy));
426 this->request->destroy_offset(this->request,
427 offsetof(dhcp_transaction_t, destroy));
428 this->completed->destroy_offset(this->completed,
429 offsetof(dhcp_transaction_t, destroy));
430 DESTROY_IF(this->rng);
431 DESTROY_IF(this->dst);
432 free(this);
433 }
434
435 /**
436 * See header
437 */
438 dhcp_socket_t *dhcp_socket_create()
439 {
440 private_dhcp_socket_t *this;
441 struct sockaddr_in src;
442 int on = 1;
443 struct sock_filter dhcp_filter_code[] = {
444 BPF_STMT(BPF_LD+BPF_B+BPF_ABS,
445 offsetof(struct iphdr, protocol)),
446 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, IPPROTO_UDP, 0, 14),
447 BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) +
448 offsetof(struct udphdr, source)),
449 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_SERVER_PORT, 0, 12),
450 BPF_STMT(BPF_LD+BPF_H+BPF_ABS, sizeof(struct iphdr) +
451 offsetof(struct udphdr, dest)),
452 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, DHCP_CLIENT_PORT, 0, 10),
453 BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
454 sizeof(struct udphdr) + offsetof(dhcp_t, opcode)),
455 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, BOOTREPLY, 0, 8),
456 BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
457 sizeof(struct udphdr) + offsetof(dhcp_t, hw_type)),
458 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARPHRD_ETHER, 0, 6),
459 BPF_STMT(BPF_LD+BPF_B+BPF_ABS, sizeof(struct iphdr) +
460 sizeof(struct udphdr) + offsetof(dhcp_t, hw_addr_len)),
461 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 6, 0, 4),
462 BPF_STMT(BPF_LD+BPF_W+BPF_ABS, sizeof(struct iphdr) +
463 sizeof(struct udphdr) + offsetof(dhcp_t, magic_cookie)),
464 BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0x63825363, 0, 2),
465 BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0),
466 BPF_STMT(BPF_RET+BPF_A, 0),
467 BPF_STMT(BPF_RET+BPF_K, 0),
468 };
469 struct sock_fprog dhcp_filter = {
470 sizeof(dhcp_filter_code) / sizeof(struct sock_filter),
471 dhcp_filter_code,
472 };
473
474 INIT(this,
475 .public = {
476 .enroll = _enroll,
477 .destroy = _destroy,
478 },
479 .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
480 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
481 .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
482 .discover = linked_list_create(),
483 .request = linked_list_create(),
484 .completed = linked_list_create(),
485 );
486
487 if (!this->rng)
488 {
489 DBG1(DBG_CFG, "unable to create RNG");
490 destroy(this);
491 return NULL;
492 }
493 this->dst = host_create_from_string(lib->settings->get_str(lib->settings,
494 "charon.plugins.dhcp.server", "255.255.255.255"),
495 DHCP_SERVER_PORT);
496 if (!this->dst)
497 {
498 DBG1(DBG_CFG, "configured DHCP server address invalid");
499 destroy(this);
500 return NULL;
501 }
502
503 this->send = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
504 if (this->send == -1)
505 {
506 DBG1(DBG_CFG, "unable to create DHCP send socket: %s", strerror(errno));
507 destroy(this);
508 return NULL;
509 }
510 if (setsockopt(this->send, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
511 {
512 DBG1(DBG_CFG, "unable to reuse DHCP socket address: %s", strerror(errno));
513 destroy(this);
514 return NULL;
515 }
516 if (setsockopt(this->send, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
517 {
518 DBG1(DBG_CFG, "unable to broadcast on DHCP socket: %s", strerror(errno));
519 destroy(this);
520 return NULL;
521 }
522 src.sin_family = AF_INET;
523 src.sin_port = htons(DHCP_CLIENT_PORT);
524 src.sin_addr.s_addr = INADDR_ANY;
525 if (bind(this->send, (struct sockaddr*)&src, sizeof(src)) == -1)
526 {
527 DBG1(DBG_CFG, "unable to bind DHCP send socket: %s", strerror(errno));
528 destroy(this);
529 return NULL;
530 }
531
532 this->receive = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
533 if (this->receive == -1)
534 {
535 DBG1(DBG_NET, "opening DHCP receive socket failed: %s", strerror(errno));
536 destroy(this);
537 return NULL;
538 }
539 if (setsockopt(this->receive, SOL_SOCKET, SO_ATTACH_FILTER,
540 &dhcp_filter, sizeof(dhcp_filter)) < 0)
541 {
542 DBG1(DBG_CFG, "installing DHCP socket filter failed: %s",
543 strerror(errno));
544 destroy(this);
545 return NULL;
546 }
547
548 this->job = callback_job_create((callback_job_cb_t)receive_dhcp,
549 this, NULL, NULL);
550 charon->processor->queue_job(charon->processor, (job_t*)this->job);
551
552 return &this->public;
553 }
554