6ad600f1ad7a509af32ef8fb0e3036a389ec61e8
[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
26 #include <utils/linked_list.h>
27 #include <utils/identification.h>
28 #include <threading/mutex.h>
29 #include <threading/condvar.h>
30 #include <threading/thread.h>
31
32 #include <daemon.h>
33 #include <processing/jobs/callback_job.h>
34
35 #define DHCP_SERVER_PORT 67
36 #define DHCP_CLIENT_PORT 68
37
38 typedef struct private_dhcp_socket_t private_dhcp_socket_t;
39
40 /**
41 * Private data of an dhcp_socket_t object.
42 */
43 struct private_dhcp_socket_t {
44
45 /**
46 * Public dhcp_socket_t interface.
47 */
48 dhcp_socket_t public;
49
50 /**
51 * Random number generator
52 */
53 rng_t *rng;
54
55 /**
56 * List of active transactions
57 */
58 linked_list_t *active;
59
60 /**
61 * List of successfully completed transactions
62 */
63 linked_list_t *completed;
64
65 /**
66 * Lock for transactions
67 */
68 mutex_t *mutex;
69
70 /**
71 * Condvar to wait for transaction completion
72 */
73 condvar_t *condvar;
74
75 /**
76 * Threads waiting in condvar
77 */
78 int waiting;
79
80 /**
81 * RAW socket
82 */
83 int skt;
84
85 /**
86 * DHCP server address, or broadcast
87 */
88 host_t *dst;
89
90 /**
91 * Callback job receiving DHCP responses
92 */
93 callback_job_t *job;
94 };
95
96 typedef enum {
97 BOOTREQUEST = 1,
98 BOOTREPLY = 2,
99 } dhcp_opcode_t;
100
101 typedef enum {
102 DHCP_HOST_NAME = 12,
103 DHCP_MESSAGE_TYPE = 53,
104 DHCP_PARAM_REQ_LIST = 55,
105 } dhcp_option_type_t;
106
107 typedef enum {
108 DHCP_DISCOVER = 1,
109 } dhcp_message_type_t;
110
111 typedef enum {
112 DHCP_ROUTER = 3,
113 DHCP_DNS_SERVER = 6,
114 } dhcp_parameter_t;
115
116 typedef struct __attribute__((packed)) {
117 u_int8_t type;
118 u_int8_t len;
119 char data[];
120 } dhcp_option_t;
121
122 typedef struct __attribute__((packed)) {
123 u_int8_t opcode;
124 u_int8_t hw_type;
125 u_int8_t hw_addr_len;
126 u_int8_t hop_count;
127 u_int32_t transaction_id;
128 u_int16_t number_of_seconds;
129 u_int16_t flags;
130 u_int32_t client_address;
131 u_int32_t your_address;
132 u_int32_t server_address;
133 u_int32_t gateway_address;
134 char client_hw_addr[6];
135 char client_hw_padding[10];
136 char server_hostname[64];
137 char boot_filename[128];
138 u_int32_t magic_cookie;
139 char options[252];
140 } dhcp_t;
141
142 /**
143 * Send DHCP discover using a given transaction
144 */
145 static void discover(private_dhcp_socket_t *this,
146 dhcp_transaction_t *transaction)
147 {
148 chunk_t id_data, broadcast = chunk_from_chars(0xFF,0xFF,0xFF,0xFF);
149 identification_t *identity;
150 dhcp_option_t *option;
151 dhcp_t dhcp;
152 int optlen = 0;
153 u_int hash;
154 host_t *src;
155 ssize_t len;
156
157 memset(&dhcp, 0, sizeof(dhcp));
158 dhcp.opcode = BOOTREQUEST;
159 dhcp.hw_type = ARPHRD_ETHER;
160 dhcp.hw_addr_len = 6;
161 dhcp.transaction_id = transaction->get_id(transaction);
162 if (chunk_equals(broadcast, this->dst->get_address(this->dst)))
163 {
164 /* TODO: send with 0.0.0.0 source address */
165 }
166 else
167 {
168 /* act as relay agent */
169 src = charon->kernel_interface->get_source_addr(
170 charon->kernel_interface, this->dst, NULL);
171 if (src)
172 {
173 memcpy(&dhcp.gateway_address, src->get_address(src).ptr,
174 sizeof(dhcp.gateway_address));
175 src->destroy(src);
176 }
177 }
178
179 identity = transaction->get_identity(transaction);
180 id_data = identity->get_encoding(identity);
181
182 /* magic bytes, a locally administered unicast MAC */
183 dhcp.client_hw_addr[0] = 0x7A;
184 dhcp.client_hw_addr[1] = 0xA7;
185 /* with ID specific postfix */
186 hash = htonl(chunk_hash(id_data));
187 memcpy(&dhcp.client_hw_addr[2], &hash, 4);
188
189 dhcp.magic_cookie = htonl(0x63825363);
190
191 option = (dhcp_option_t*)&dhcp.options[optlen];
192 option->type = DHCP_MESSAGE_TYPE;
193 option->len = 1;
194 option->data[0] = DHCP_DISCOVER;
195 optlen += sizeof(dhcp_option_t) + option->len;
196
197 option = (dhcp_option_t*)&dhcp.options[optlen];
198 option->type = DHCP_HOST_NAME;
199 option->len = min(id_data.len, 64);
200 memcpy(option->data, id_data.ptr, option->len);
201 optlen += sizeof(dhcp_option_t) + option->len;
202
203 option = (dhcp_option_t*)&dhcp.options[optlen];
204 option->type = DHCP_PARAM_REQ_LIST;
205 option->len = 2;
206 option->data[0] = DHCP_ROUTER;
207 option->data[1] = DHCP_DNS_SERVER;
208 optlen += sizeof(dhcp_option_t) + option->len;
209
210 dhcp.options[optlen++] = 0xFF;
211
212 len = offsetof(dhcp_t, magic_cookie) + ((optlen + 4) / 64 * 64 + 64);
213 if (sendto(this->skt, &dhcp, len, 0, this->dst->get_sockaddr(this->dst),
214 *this->dst->get_sockaddr_len(this->dst)) != len)
215 {
216 DBG1(DBG_CFG, "sending DHCP DISCOVER failed: %s", strerror(errno));
217 }
218 }
219
220 METHOD(dhcp_socket_t, enroll, dhcp_transaction_t*,
221 private_dhcp_socket_t *this, identification_t *identity)
222 {
223 dhcp_transaction_t *transaction;
224 u_int32_t id;
225
226 this->rng->get_bytes(this->rng, sizeof(id), (u_int8_t*)&id);
227 transaction = dhcp_transaction_create(id, identity);
228 discover(this, transaction);
229 transaction->destroy(transaction);
230
231 return NULL;
232 }
233
234 METHOD(dhcp_socket_t, destroy, void,
235 private_dhcp_socket_t *this)
236 {
237 if (this->job)
238 {
239 this->job->cancel(this->job);
240 }
241 while (this->waiting)
242 {
243 this->condvar->signal(this->condvar);
244 }
245 if (this->skt > 0)
246 {
247 close(this->skt);
248 }
249 this->mutex->destroy(this->mutex);
250 this->condvar->destroy(this->condvar);
251 this->active->destroy(this->active);
252 this->completed->destroy(this->completed);
253 DESTROY_IF(this->rng);
254 DESTROY_IF(this->dst);
255 free(this);
256 }
257
258 /**
259 * See header
260 */
261 dhcp_socket_t *dhcp_socket_create()
262 {
263 private_dhcp_socket_t *this;
264 struct sockaddr_in src;
265 int on = 1;
266
267 INIT(this,
268 .public = {
269 .enroll = _enroll,
270 .destroy = _destroy,
271 },
272 .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
273 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
274 .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
275 .active = linked_list_create(),
276 .completed = linked_list_create(),
277 );
278
279 if (!this->rng)
280 {
281 DBG1(DBG_CFG, "unable to create RNG");
282 destroy(this);
283 return NULL;
284 }
285
286 this->dst = host_create_from_string(lib->settings->get_str(lib->settings,
287 "charon.plugins.dhcp.server", "255.255.255.255"),
288 DHCP_SERVER_PORT);
289 if (!this->dst)
290 {
291 DBG1(DBG_CFG, "configured DHCP server address invalid");
292 destroy(this);
293 return NULL;
294 }
295
296 this->skt = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
297 if (this->skt == -1)
298 {
299 DBG1(DBG_CFG, "unable to create DHCP send socket: %s", strerror(errno));
300 destroy(this);
301 return NULL;
302 }
303
304 if (setsockopt(this->skt, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
305 {
306 DBG1(DBG_CFG, "unable to reuse DHCP socket address: %s", strerror(errno));
307 destroy(this);
308 return NULL;
309 }
310 if (setsockopt(this->skt, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
311 {
312 DBG1(DBG_CFG, "unable to broadcast on DHCP socket: %s", strerror(errno));
313 destroy(this);
314 return NULL;
315 }
316
317 src.sin_family = AF_INET;
318 src.sin_port = htons(DHCP_CLIENT_PORT);
319 src.sin_addr.s_addr = INADDR_ANY;
320 if (bind(this->skt, (struct sockaddr*)&src, sizeof(src)) == -1)
321 {
322 DBG1(DBG_CFG, "unable to bind DHCP send socket: %s", strerror(errno));
323 destroy(this);
324 return NULL;
325 }
326
327 return &this->public;
328 }
329