c3bb1adc6b0ebd630dd0cb9637f451ff2c8265fb
[strongswan.git] / src / frontends / android / src / org / strongswan / android / logic / CharonVpnService.java
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 package org.strongswan.android.logic;
19
20 import org.strongswan.android.data.VpnProfile;
21 import org.strongswan.android.data.VpnProfileDataSource;
22 import org.strongswan.android.logic.VpnStateService.ErrorState;
23 import org.strongswan.android.logic.VpnStateService.State;
24
25 import android.app.Service;
26 import android.content.ComponentName;
27 import android.content.Intent;
28 import android.content.ServiceConnection;
29 import android.net.VpnService;
30 import android.os.Bundle;
31 import android.os.IBinder;
32 import android.util.Log;
33
34 public class CharonVpnService extends VpnService implements Runnable
35 {
36 private static final String TAG = CharonVpnService.class.getSimpleName();
37 private VpnProfileDataSource mDataSource;
38 private Thread mConnectionHandler;
39 private VpnProfile mCurrentProfile;
40 private VpnProfile mNextProfile;
41 private volatile boolean mProfileUpdated;
42 private volatile boolean mTerminate;
43 private VpnStateService mService;
44 private final Object mServiceLock = new Object();
45 private final ServiceConnection mServiceConnection = new ServiceConnection() {
46 @Override
47 public void onServiceDisconnected(ComponentName name)
48 { /* since the service is local this is theoretically only called when the process is terminated */
49 synchronized (mServiceLock)
50 {
51 mService = null;
52 }
53 }
54
55 @Override
56 public void onServiceConnected(ComponentName name, IBinder service)
57 {
58 synchronized (mServiceLock)
59 {
60 mService = ((VpnStateService.LocalBinder)service).getService();
61 }
62 /* we are now ready to start the handler thread */
63 mConnectionHandler.start();
64 }
65 };
66
67 /**
68 * as defined in charonservice.h
69 */
70 static final int STATE_CHILD_SA_UP = 1;
71 static final int STATE_CHILD_SA_DOWN = 2;
72 static final int STATE_AUTH_ERROR = 3;
73 static final int STATE_PEER_AUTH_ERROR = 4;
74 static final int STATE_LOOKUP_ERROR = 5;
75 static final int STATE_UNREACHABLE_ERROR = 6;
76 static final int STATE_GENERIC_ERROR = 7;
77
78 @Override
79 public int onStartCommand(Intent intent, int flags, int startId)
80 {
81 if (intent != null)
82 {
83 Bundle bundle = intent.getExtras();
84 VpnProfile profile = null;
85 if (bundle != null)
86 {
87 profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
88 if (profile != null)
89 {
90 String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
91 profile.setPassword(password);
92 }
93 }
94 setNextProfile(profile);
95 }
96 return START_NOT_STICKY;
97 }
98
99 @Override
100 public void onCreate()
101 {
102 mDataSource = new VpnProfileDataSource(this);
103 mDataSource.open();
104 /* use a separate thread as main thread for charon */
105 mConnectionHandler = new Thread(this);
106 /* the thread is started when the service is bound */
107 bindService(new Intent(this, VpnStateService.class),
108 mServiceConnection, Service.BIND_AUTO_CREATE);
109 }
110
111 @Override
112 public void onRevoke()
113 { /* the system revoked the rights grated with the initial prepare() call.
114 * called when the user clicks disconnect in the system's VPN dialog */
115 setNextProfile(null);
116 }
117
118 @Override
119 public void onDestroy()
120 {
121 mTerminate = true;
122 setNextProfile(null);
123 try
124 {
125 mConnectionHandler.join();
126 }
127 catch (InterruptedException e)
128 {
129 e.printStackTrace();
130 }
131 if (mService != null)
132 {
133 unbindService(mServiceConnection);
134 }
135 mDataSource.close();
136 }
137
138 /**
139 * Set the profile that is to be initiated next. Notify the handler thread.
140 *
141 * @param profile the profile to initiate
142 */
143 private void setNextProfile(VpnProfile profile)
144 {
145 synchronized (this)
146 {
147 this.mNextProfile = profile;
148 mProfileUpdated = true;
149 notifyAll();
150 }
151 }
152
153 @Override
154 public void run()
155 {
156 while (true)
157 {
158 synchronized (this)
159 {
160 try
161 {
162 while (!mProfileUpdated)
163 {
164 wait();
165 }
166
167 mProfileUpdated = false;
168 stopCurrentConnection();
169 if (mNextProfile == null)
170 {
171 setProfile(null);
172 setState(State.DISABLED);
173 if (mTerminate)
174 {
175 break;
176 }
177 }
178 else
179 {
180 mCurrentProfile = mNextProfile;
181 mNextProfile = null;
182
183 setProfile(mCurrentProfile);
184 setError(ErrorState.NO_ERROR);
185 setState(State.CONNECTING);
186
187 initializeCharon();
188 Log.i(TAG, "charon started");
189 }
190 }
191 catch (InterruptedException ex)
192 {
193 stopCurrentConnection();
194 setState(State.DISABLED);
195 }
196 }
197 }
198 }
199
200 /**
201 * Stop any existing connection by deinitializing charon.
202 */
203 private void stopCurrentConnection()
204 {
205 synchronized (this)
206 {
207 if (mCurrentProfile != null)
208 {
209 setState(State.DISCONNECTING);
210 deinitializeCharon();
211 Log.i(TAG, "charon stopped");
212 mCurrentProfile = null;
213 }
214 }
215 }
216
217 /**
218 * Update the VPN profile on the state service. Called by the handler thread.
219 *
220 * @param profile currently active VPN profile
221 */
222 private void setProfile(VpnProfile profile)
223 {
224 synchronized (mServiceLock)
225 {
226 if (mService != null)
227 {
228 mService.setProfile(profile);
229 }
230 }
231 }
232
233 /**
234 * Update the current VPN state on the state service. Called by the handler
235 * thread and any of charon's threads.
236 *
237 * @param state current state
238 */
239 private void setState(State state)
240 {
241 synchronized (mServiceLock)
242 {
243 if (mService != null)
244 {
245 mService.setState(state);
246 }
247 }
248 }
249
250 /**
251 * Set an error on the state service. Called by the handler thread and any
252 * of charon's threads.
253 *
254 * @param error error state
255 */
256 private void setError(ErrorState error)
257 {
258 synchronized (mServiceLock)
259 {
260 if (mService != null)
261 {
262 mService.setError(error);
263 }
264 }
265 }
266
267 /**
268 * Set an error on the state service and disconnect the current connection.
269 * This is not done by calling stopCurrentConnection() above, but instead
270 * is done asynchronously via state service.
271 *
272 * @param error error state
273 */
274 private void setErrorDisconnect(ErrorState error)
275 {
276 synchronized (mServiceLock)
277 {
278 if (mService != null)
279 {
280 mService.setError(error);
281 mService.disconnect();
282 }
283 }
284 }
285
286 /**
287 * Updates the state of the current connection.
288 * Called via JNI by different threads (but not concurrently).
289 *
290 * @param status new state
291 */
292 public void updateStatus(int status)
293 {
294 switch (status)
295 {
296 case STATE_CHILD_SA_DOWN:
297 synchronized (mServiceLock)
298 {
299 /* since this state is also reached when the SA is closed remotely,
300 * we call disconnect() to make sure charon is properly deinitialized */
301 if (mService != null)
302 {
303 mService.disconnect();
304 }
305 }
306 break;
307 case STATE_CHILD_SA_UP:
308 setState(State.CONNECTED);
309 break;
310 case STATE_AUTH_ERROR:
311 setErrorDisconnect(ErrorState.AUTH_FAILED);
312 break;
313 case STATE_PEER_AUTH_ERROR:
314 setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
315 break;
316 case STATE_LOOKUP_ERROR:
317 setErrorDisconnect(ErrorState.LOOKUP_FAILED);
318 break;
319 case STATE_UNREACHABLE_ERROR:
320 setErrorDisconnect(ErrorState.UNREACHABLE);
321 break;
322 case STATE_GENERIC_ERROR:
323 setErrorDisconnect(ErrorState.GENERIC_ERROR);
324 break;
325 default:
326 Log.e(TAG, "Unknown status code received");
327 break;
328 }
329 }
330
331 /**
332 * Initialization of charon, provided by libandroidbridge.so
333 */
334 public native void initializeCharon();
335
336 /**
337 * Deinitialize charon, provided by libandroidbridge.so
338 */
339 public native void deinitializeCharon();
340
341 /*
342 * The libraries are extracted to /data/data/org.strongswan.android/...
343 * during installation.
344 */
345 static
346 {
347 System.loadLibrary("crypto");
348 System.loadLibrary("strongswan");
349 System.loadLibrary("hydra");
350 System.loadLibrary("charon");
351 System.loadLibrary("ipsec");
352 System.loadLibrary("androidbridge");
353 }
354 }