]> git.ipfire.org Git - thirdparty/strongswan.git/blame - src/libcharon/plugins/android/android_service.c
support of xfrm marks for IKEv2
[thirdparty/strongswan.git] / src / libcharon / plugins / android / android_service.c
CommitLineData
be00d219
TB
1/*
2 * Copyright (C) 2010 Tobias Brunner
3 * Hochschule fuer Technik Rapperswil
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 <unistd.h>
17#include <cutils/sockets.h>
18#include <cutils/properties.h>
e9e2a4fe 19#include <signal.h>
be00d219
TB
20
21#include "android_service.h"
22
23#include <daemon.h>
24#include <threading/thread.h>
25#include <processing/jobs/callback_job.h>
26
27typedef struct private_android_service_t private_android_service_t;
28
29/**
30 * private data of Android service
31 */
32struct private_android_service_t {
33
34 /**
35 * public interface
36 */
37 android_service_t public;
38
39 /**
8b775e99 40 * current IKE_SA
be00d219 41 */
8b775e99 42 ike_sa_t *ike_sa;
be00d219
TB
43
44 /**
45 * job that handles requests from the Android control socket
46 */
47 callback_job_t *job;
48
49 /**
50 * android credentials
51 */
52 android_creds_t *creds;
53
54 /**
55 * android control socket
56 */
57 int control;
58
59};
60
5eb9eeb1
TB
61/**
62 * Some of the error codes defined in VpnManager.java
63 */
64typedef enum {
65 /** Error code to indicate an error from authentication. */
66 VPN_ERROR_AUTH = 51,
67 /** Error code to indicate the connection attempt failed. */
68 VPN_ERROR_CONNECTION_FAILED = 101,
69 /** Error code to indicate an error of remote server hanging up. */
70 VPN_ERROR_REMOTE_HUNG_UP = 7,
71 /** Error code to indicate an error of losing connectivity. */
72 VPN_ERROR_CONNECTION_LOST = 103,
73} android_vpn_errors_t;
74
94ec9adc
TB
75/**
76 * send a status code back to the Android app
77 */
78static void send_status(private_android_service_t *this, u_char code)
79{
80 DBG1(DBG_CFG, "status of Android plugin changed: %d", code);
81 send(this->control, &code, 1, 0);
82}
83
8b775e99
TB
84METHOD(listener_t, ike_updown, bool,
85 private_android_service_t *this, ike_sa_t *ike_sa, bool up)
86{
7913a74c
TB
87 /* this callback is only registered during initiation, so if the IKE_SA
88 * goes down we assume an authentication error */
89 if (this->ike_sa == ike_sa && !up)
90 {
91 send_status(this, VPN_ERROR_AUTH);
92 return FALSE;
93 }
8b775e99
TB
94 return TRUE;
95}
96
97METHOD(listener_t, child_state_change, bool,
98 private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
99 child_sa_state_t state)
100{
7913a74c
TB
101 /* this callback is only registered during initiation, so we still have
102 * the control socket open */
103 if (this->ike_sa == ike_sa && state == CHILD_DESTROYING)
104 {
105 send_status(this, VPN_ERROR_CONNECTION_FAILED);
106 return FALSE;
107 }
8b775e99
TB
108 return TRUE;
109}
110
e9e2a4fe
TB
111/**
112 * Callback used to shutdown the daemon
113 */
114static job_requeue_t shutdown_callback(void *data)
115{
116 kill(0, SIGTERM);
117 return JOB_REQUEUE_NONE;
118}
119
8b775e99
TB
120METHOD(listener_t, child_updown, bool,
121 private_android_service_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa,
122 bool up)
123{
7913a74c
TB
124 if (this->ike_sa == ike_sa)
125 {
126 if (up)
127 {
128 /* disable the hooks registered to catch initiation failures */
129 this->public.listener.ike_updown = NULL;
130 this->public.listener.child_state_change = NULL;
131 property_set("vpn.status", "ok");
132 }
e9e2a4fe
TB
133 else
134 {
135 callback_job_t *job;
136 /* the control socket is closed as soon as vpn.status is set to "ok"
137 * and the daemon proxy then only checks for terminated daemons to
138 * detect lost connections, so... */
139 DBG1(DBG_CFG, "connection lost, raising delayed SIGTERM");
140 /* to avoid any conflicts we send the SIGTERM not directly from this
141 * callback, but from a different thread. we also delay it to avoid
142 * a race condition during a regular shutdown */
143 job = callback_job_create(shutdown_callback, NULL, NULL, NULL);
144 charon->scheduler->schedule_job(charon->scheduler, (job_t*)job, 1);
145 return FALSE;
146 }
7913a74c 147 }
8b775e99
TB
148 return TRUE;
149}
150
151METHOD(listener_t, ike_rekey, bool,
152 private_android_service_t *this, ike_sa_t *old, ike_sa_t *new)
153{
154 if (this->ike_sa == old)
155 {
156 this->ike_sa = new;
157 }
158 return TRUE;
159}
160
be00d219
TB
161/**
162 * Read a string argument from the Android control socket
163 */
164static char *read_argument(int fd, u_char length)
165{
166 int offset = 0;
167 char *data = malloc(length + 1);
168 while (offset < length)
169 {
170 int n = recv(fd, &data[offset], length - offset, 0);
171 if (n < 0)
172 {
173 DBG1(DBG_CFG, "failed to read argument from Android"
174 " control socket: %s", strerror(errno));
175 free(data);
176 return NULL;
177 }
178 offset += n;
179 }
180 data[length] = '\0';
c0914c45 181 DBG3(DBG_CFG, "received argument from Android control socket: %s", data);
be00d219
TB
182 return data;
183}
184
185/**
186 * handle the request received from the Android control socket
187 */
188static job_requeue_t initiate(private_android_service_t *this)
189{
190 bool oldstate;
191 int fd, i = 0;
192 char *hostname = NULL, *cacert = NULL, *username = NULL, *password = NULL;
193 identification_t *gateway = NULL, *user = NULL;
194 ike_cfg_t *ike_cfg;
195 peer_cfg_t *peer_cfg;
196 child_cfg_t *child_cfg;
197 traffic_selector_t *ts;
198 ike_sa_t *ike_sa;
199 auth_cfg_t *auth;
200 lifetime_cfg_t lifetime = {
201 .time = {
202 .life = 10800, /* 3h */
203 .rekey = 10200, /* 2h50min */
204 .jitter = 300 /* 5min */
205 }
206 };
207
208 fd = accept(this->control, NULL, 0);
209 if (fd < 0)
210 {
211 DBG1(DBG_CFG, "accept on Android control socket failed: %s",
212 strerror(errno));
213 return JOB_REQUEUE_NONE;
214 }
94ec9adc 215 /* the original control socket is not used anymore */
be00d219 216 close(this->control);
94ec9adc 217 this->control = fd;
be00d219
TB
218
219 while (TRUE)
220 {
221 u_char length;
222 if (recv(fd, &length, 1, 0) != 1)
223 {
224 DBG1(DBG_CFG, "failed to read from Android control socket: %s",
225 strerror(errno));
226 return JOB_REQUEUE_NONE;
227 }
228
229 if (length == 0xFF)
230 { /* last argument */
231 break;
232 }
233 else
234 {
235 switch (i++)
236 {
237 case 0: /* gateway */
238 hostname = read_argument(fd, length);
239 break;
240 case 1: /* CA certificate name */
241 cacert = read_argument(fd, length);
242 break;
243 case 2: /* username */
244 username = read_argument(fd, length);
245 break;
246 case 3: /* password */
247 password = read_argument(fd, length);
248 break;
249 }
250 }
251 }
252
253 if (cacert)
254 {
255 if (!this->creds->add_certificate(this->creds, cacert))
256 {
257 DBG1(DBG_CFG, "failed to load CA certificate");
258 }
259 /* if this is a server cert we could use the cert subject as id
260 * but we have to test first if that possible to configure */
261 }
262
263 gateway = identification_create_from_string(hostname);
264 DBG1(DBG_CFG, "using CA certificate, gateway identitiy '%Y'", gateway);
265
266 if (username)
267 {
268 user = identification_create_from_string(username);
269 this->creds->set_username_password(this->creds, user, password);
270 }
271
272 ike_cfg = ike_cfg_create(TRUE, FALSE, "0.0.0.0", IKEV2_UDP_PORT,
273 hostname, IKEV2_UDP_PORT);
274 ike_cfg->add_proposal(ike_cfg, proposal_create_default(PROTO_IKE));
275
276 peer_cfg = peer_cfg_create("android", 2, ike_cfg, CERT_SEND_IF_ASKED,
277 UNIQUE_REPLACE, 1, /* keyingtries */
278 36000, 0, /* rekey 10h, reauth none */
279 600, 600, /* jitter, over 10min */
280 TRUE, 0, /* mobike, DPD */
281 host_create_from_string("0.0.0.0", 0) /* virt */,
282 NULL, FALSE, NULL, NULL); /* pool, mediation */
283
284 auth = auth_cfg_create();
285 auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_EAP);
286 auth->add(auth, AUTH_RULE_IDENTITY, user);
287 peer_cfg->add_auth_cfg(peer_cfg, auth, TRUE);
288 auth = auth_cfg_create();
289 auth->add(auth, AUTH_RULE_AUTH_CLASS, AUTH_CLASS_PUBKEY);
290 auth->add(auth, AUTH_RULE_IDENTITY, gateway);
291 peer_cfg->add_auth_cfg(peer_cfg, auth, FALSE);
292
293 child_cfg = child_cfg_create("android", &lifetime, NULL, TRUE, MODE_TUNNEL,
ee26c537 294 ACTION_NONE, ACTION_NONE, FALSE, 0, 0, NULL);
be00d219
TB
295 child_cfg->add_proposal(child_cfg, proposal_create_default(PROTO_ESP));
296 ts = traffic_selector_create_dynamic(0, 0, 65535);
297 child_cfg->add_traffic_selector(child_cfg, TRUE, ts);
298 ts = traffic_selector_create_from_string(0, TS_IPV4_ADDR_RANGE, "0.0.0.0",
299 0, "255.255.255.255", 65535);
300 child_cfg->add_traffic_selector(child_cfg, FALSE, ts);
301 peer_cfg->add_child_cfg(peer_cfg, child_cfg);
024dd37f
TB
302 /* get an additional reference because initiate consumes one */
303 child_cfg->get_ref(child_cfg);
be00d219 304
7913a74c
TB
305 /* get us an IKE_SA */
306 ike_sa = charon->ike_sa_manager->checkout_by_config(charon->ike_sa_manager,
307 peer_cfg);
308 if (!ike_sa->get_peer_cfg(ike_sa))
309 {
310 ike_sa->set_peer_cfg(ike_sa, peer_cfg);
311 }
312 peer_cfg->destroy(peer_cfg);
313
314 /* store the IKE_SA so we can track its progress */
315 this->ike_sa = ike_sa;
be00d219
TB
316
317 /* confirm that we received the request */
94ec9adc 318 send_status(this, i);
be00d219 319
7913a74c 320 if (ike_sa->initiate(ike_sa, child_cfg, 0, NULL, NULL) != SUCCESS)
be00d219
TB
321 {
322 DBG1(DBG_CFG, "failed to initiate tunnel");
7913a74c
TB
323 charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager,
324 ike_sa);
94ec9adc 325 send_status(this, VPN_ERROR_CONNECTION_FAILED);
be00d219
TB
326 return JOB_REQUEUE_NONE;
327 }
7913a74c 328 charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
be00d219
TB
329 return JOB_REQUEUE_NONE;
330}
331
332METHOD(android_service_t, destroy, void,
333 private_android_service_t *this)
334{
8b775e99 335 charon->bus->remove_listener(charon->bus, &this->public.listener);
94ec9adc 336 close(this->control);
be00d219
TB
337 free(this);
338}
339
340/**
341 * See header
342 */
343android_service_t *android_service_create(android_creds_t *creds)
344{
345 private_android_service_t *this;
346
347 INIT(this,
348 .public = {
8b775e99
TB
349 .listener = {
350 .ike_updown = _ike_updown,
351 .child_state_change = _child_state_change,
352 .child_updown = _child_updown,
353 .ike_rekey = _ike_rekey,
354 },
be00d219
TB
355 .destroy = _destroy,
356 },
357 .creds = creds,
358 );
359
360 this->control = android_get_control_socket("charon");
361 if (this->control == -1)
362 {
363 DBG1(DBG_CFG, "failed to get Android control socket");
364 free(this);
365 return NULL;
366 }
367
368 if (listen(this->control, 1) < 0)
369 {
370 DBG1(DBG_CFG, "failed to listen on Android control socket: %s",
371 strerror(errno));
372 close(this->control);
373 free(this);
374 return NULL;
375 }
376
8b775e99 377 charon->bus->add_listener(charon->bus, &this->public.listener);
be00d219
TB
378 this->job = callback_job_create((callback_job_cb_t)initiate, this,
379 NULL, NULL);
380 charon->processor->queue_job(charon->processor, (job_t*)this->job);
381
382 return &this->public;
383}
384