ffb20452ec4240629a497b01ac3f02a97d48ab6f
[strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / logic / CharonVpnService.java
1 /*
2 * Copyright (C) 2012-2017 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * HSR 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 android.annotation.TargetApi;
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.Service;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.ServiceConnection;
29 import android.content.pm.PackageManager;
30 import android.net.VpnService;
31 import android.os.Build;
32 import android.os.Bundle;
33 import android.os.IBinder;
34 import android.os.ParcelFileDescriptor;
35 import android.security.KeyChain;
36 import android.security.KeyChainException;
37 import android.support.v4.app.NotificationCompat;
38 import android.support.v4.content.ContextCompat;
39 import android.system.OsConstants;
40 import android.util.Log;
41
42 import org.strongswan.android.R;
43 import org.strongswan.android.data.VpnProfile;
44 import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
45 import org.strongswan.android.data.VpnProfileDataSource;
46 import org.strongswan.android.data.VpnType.VpnTypeFeature;
47 import org.strongswan.android.logic.VpnStateService.ErrorState;
48 import org.strongswan.android.logic.VpnStateService.State;
49 import org.strongswan.android.logic.imc.ImcState;
50 import org.strongswan.android.logic.imc.RemediationInstruction;
51 import org.strongswan.android.ui.MainActivity;
52 import org.strongswan.android.utils.IPRange;
53 import org.strongswan.android.utils.IPRangeSet;
54 import org.strongswan.android.utils.SettingsWriter;
55
56 import java.io.File;
57 import java.net.Inet4Address;
58 import java.net.Inet6Address;
59 import java.net.InetAddress;
60 import java.net.UnknownHostException;
61 import java.security.PrivateKey;
62 import java.security.cert.CertificateEncodingException;
63 import java.security.cert.X509Certificate;
64 import java.util.ArrayList;
65 import java.util.List;
66 import java.util.Locale;
67
68 public class CharonVpnService extends VpnService implements Runnable, VpnStateService.VpnStateListener
69 {
70 private static final String TAG = CharonVpnService.class.getSimpleName();
71 public static final String DISCONNECT_ACTION = "org.strongswan.android.CharonVpnService.DISCONNECT";
72 public static final String LOG_FILE = "charon.log";
73 public static final int VPN_STATE_NOTIFICATION_ID = 1;
74
75 private String mLogFile;
76 private VpnProfileDataSource mDataSource;
77 private Thread mConnectionHandler;
78 private VpnProfile mCurrentProfile;
79 private volatile String mCurrentCertificateAlias;
80 private volatile String mCurrentUserCertificateAlias;
81 private VpnProfile mNextProfile;
82 private volatile boolean mProfileUpdated;
83 private volatile boolean mTerminate;
84 private volatile boolean mIsDisconnecting;
85 private volatile boolean mShowNotification;
86 private VpnStateService mService;
87 private final Object mServiceLock = new Object();
88 private final ServiceConnection mServiceConnection = new ServiceConnection() {
89 @Override
90 public void onServiceDisconnected(ComponentName name)
91 { /* since the service is local this is theoretically only called when the process is terminated */
92 synchronized (mServiceLock)
93 {
94 mService = null;
95 }
96 }
97
98 @Override
99 public void onServiceConnected(ComponentName name, IBinder service)
100 {
101 synchronized (mServiceLock)
102 {
103 mService = ((VpnStateService.LocalBinder)service).getService();
104 }
105 /* we are now ready to start the handler thread */
106 mService.registerListener(CharonVpnService.this);
107 mConnectionHandler.start();
108 }
109 };
110
111 /**
112 * as defined in charonservice.h
113 */
114 static final int STATE_CHILD_SA_UP = 1;
115 static final int STATE_CHILD_SA_DOWN = 2;
116 static final int STATE_AUTH_ERROR = 3;
117 static final int STATE_PEER_AUTH_ERROR = 4;
118 static final int STATE_LOOKUP_ERROR = 5;
119 static final int STATE_UNREACHABLE_ERROR = 6;
120 static final int STATE_GENERIC_ERROR = 7;
121
122 @Override
123 public int onStartCommand(Intent intent, int flags, int startId)
124 {
125 if (intent != null)
126 {
127 if (DISCONNECT_ACTION.equals(intent.getAction()))
128 {
129 setNextProfile(null);
130 }
131 else
132 {
133 Bundle bundle = intent.getExtras();
134 VpnProfile profile = null;
135 if (bundle != null)
136 {
137 profile = mDataSource.getVpnProfile(bundle.getLong(VpnProfileDataSource.KEY_ID));
138 if (profile != null)
139 {
140 String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
141 profile.setPassword(password);
142 }
143 }
144 setNextProfile(profile);
145 }
146 }
147 return START_NOT_STICKY;
148 }
149
150 @Override
151 public void onCreate()
152 {
153 mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE;
154
155 mDataSource = new VpnProfileDataSource(this);
156 mDataSource.open();
157 /* use a separate thread as main thread for charon */
158 mConnectionHandler = new Thread(this);
159 /* the thread is started when the service is bound */
160 bindService(new Intent(this, VpnStateService.class),
161 mServiceConnection, Service.BIND_AUTO_CREATE);
162 }
163
164 @Override
165 public void onRevoke()
166 { /* the system revoked the rights grated with the initial prepare() call.
167 * called when the user clicks disconnect in the system's VPN dialog */
168 setNextProfile(null);
169 }
170
171 @Override
172 public void onDestroy()
173 {
174 mTerminate = true;
175 setNextProfile(null);
176 try
177 {
178 mConnectionHandler.join();
179 }
180 catch (InterruptedException e)
181 {
182 e.printStackTrace();
183 }
184 if (mService != null)
185 {
186 mService.unregisterListener(this);
187 unbindService(mServiceConnection);
188 }
189 mDataSource.close();
190 }
191
192 /**
193 * Set the profile that is to be initiated next. Notify the handler thread.
194 *
195 * @param profile the profile to initiate
196 */
197 private void setNextProfile(VpnProfile profile)
198 {
199 synchronized (this)
200 {
201 this.mNextProfile = profile;
202 mProfileUpdated = true;
203 notifyAll();
204 }
205 }
206
207 @Override
208 public void run()
209 {
210 while (true)
211 {
212 synchronized (this)
213 {
214 try
215 {
216 while (!mProfileUpdated)
217 {
218 wait();
219 }
220
221 mProfileUpdated = false;
222 stopCurrentConnection();
223 if (mNextProfile == null)
224 {
225 setState(State.DISABLED);
226 if (mTerminate)
227 {
228 break;
229 }
230 }
231 else
232 {
233 mCurrentProfile = mNextProfile;
234 mNextProfile = null;
235
236 /* store this in a separate (volatile) variable to avoid
237 * a possible deadlock during deinitialization */
238 mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
239 mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
240
241 startConnection(mCurrentProfile);
242 mIsDisconnecting = false;
243
244 addNotification();
245 BuilderAdapter builder = new BuilderAdapter(mCurrentProfile);
246 if (initializeCharon(builder, mLogFile, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD)))
247 {
248 Log.i(TAG, "charon started");
249 SettingsWriter writer = new SettingsWriter();
250 writer.setValue("global.language", Locale.getDefault().getLanguage());
251 writer.setValue("global.mtu", mCurrentProfile.getMTU());
252 writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier());
253 writer.setValue("connection.server", mCurrentProfile.getGateway());
254 writer.setValue("connection.port", mCurrentProfile.getPort());
255 writer.setValue("connection.username", mCurrentProfile.getUsername());
256 writer.setValue("connection.password", mCurrentProfile.getPassword());
257 writer.setValue("connection.local_id", mCurrentProfile.getLocalId());
258 writer.setValue("connection.remote_id", mCurrentProfile.getRemoteId());
259 initiate(writer.serialize());
260 }
261 else
262 {
263 Log.e(TAG, "failed to start charon");
264 setError(ErrorState.GENERIC_ERROR);
265 setState(State.DISABLED);
266 mCurrentProfile = null;
267 }
268 }
269 }
270 catch (InterruptedException ex)
271 {
272 stopCurrentConnection();
273 setState(State.DISABLED);
274 }
275 }
276 }
277 }
278
279 /**
280 * Stop any existing connection by deinitializing charon.
281 */
282 private void stopCurrentConnection()
283 {
284 synchronized (this)
285 {
286 if (mCurrentProfile != null)
287 {
288 setState(State.DISCONNECTING);
289 mIsDisconnecting = true;
290 deinitializeCharon();
291 Log.i(TAG, "charon stopped");
292 mCurrentProfile = null;
293 removeNotification();
294 }
295 }
296 }
297
298 /**
299 * Add a permanent notification while we are connected to avoid the service getting killed by
300 * the system when low on memory.
301 */
302 private void addNotification()
303 {
304 mShowNotification = true;
305 startForeground(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
306 }
307
308 /**
309 * Remove the permanent notification.
310 */
311 private void removeNotification()
312 {
313 mShowNotification = false;
314 stopForeground(true);
315 }
316
317 /**
318 * Build a notification matching the current state
319 */
320 private Notification buildNotification(boolean publicVersion)
321 {
322 VpnProfile profile = mService.getProfile();
323 State state = mService.getState();
324 ErrorState error = mService.getErrorState();
325 String name = "";
326
327 if (profile != null)
328 {
329 name = profile.getName();
330 }
331 android.support.v4.app.NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
332 .setSmallIcon(R.drawable.ic_notification)
333 .setCategory(NotificationCompat.CATEGORY_SERVICE)
334 .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC
335 : NotificationCompat.VISIBILITY_PRIVATE);
336 int s = R.string.state_disabled;
337 if (error != ErrorState.NO_ERROR)
338 {
339 s = R.string.state_error;
340 builder.setSmallIcon(R.drawable.ic_notification_warning);
341 builder.setColor(ContextCompat.getColor(this, R.color.error_text));
342 }
343 else
344 {
345 switch (state)
346 {
347 case CONNECTING:
348 s = R.string.state_connecting;
349 builder.setSmallIcon(R.drawable.ic_notification_warning);
350 builder.setColor(ContextCompat.getColor(this, R.color.warning_text));
351 break;
352 case CONNECTED:
353 s = R.string.state_connected;
354 builder.setColor(ContextCompat.getColor(this, R.color.success_text));
355 builder.setUsesChronometer(true);
356 break;
357 case DISCONNECTING:
358 s = R.string.state_disconnecting;
359 break;
360 }
361 }
362 builder.setContentTitle(getString(s));
363 if (!publicVersion)
364 {
365 builder.setContentText(name);
366 builder.setPublicVersion(buildNotification(true));
367 }
368
369 Intent intent = new Intent(getApplicationContext(), MainActivity.class);
370 PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
371 PendingIntent.FLAG_UPDATE_CURRENT);
372 builder.setContentIntent(pending);
373 return builder.build();
374 }
375
376 @Override
377 public void stateChanged() {
378 if (mShowNotification)
379 {
380 NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
381 manager.notify(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
382 }
383 }
384
385 /**
386 * Notify the state service about a new connection attempt.
387 * Called by the handler thread.
388 *
389 * @param profile currently active VPN profile
390 */
391 private void startConnection(VpnProfile profile)
392 {
393 synchronized (mServiceLock)
394 {
395 if (mService != null)
396 {
397 mService.startConnection(profile);
398 }
399 }
400 }
401
402 /**
403 * Update the current VPN state on the state service. Called by the handler
404 * thread and any of charon's threads.
405 *
406 * @param state current state
407 */
408 private void setState(State state)
409 {
410 synchronized (mServiceLock)
411 {
412 if (mService != null)
413 {
414 mService.setState(state);
415 }
416 }
417 }
418
419 /**
420 * Set an error on the state service. Called by the handler thread and any
421 * of charon's threads.
422 *
423 * @param error error state
424 */
425 private void setError(ErrorState error)
426 {
427 synchronized (mServiceLock)
428 {
429 if (mService != null)
430 {
431 mService.setError(error);
432 }
433 }
434 }
435
436 /**
437 * Set the IMC state on the state service. Called by the handler thread and
438 * any of charon's threads.
439 *
440 * @param state IMC state
441 */
442 private void setImcState(ImcState state)
443 {
444 synchronized (mServiceLock)
445 {
446 if (mService != null)
447 {
448 mService.setImcState(state);
449 }
450 }
451 }
452
453 /**
454 * Set an error on the state service. Called by the handler thread and any
455 * of charon's threads.
456 *
457 * @param error error state
458 */
459 private void setErrorDisconnect(ErrorState error)
460 {
461 synchronized (mServiceLock)
462 {
463 if (mService != null)
464 {
465 if (!mIsDisconnecting)
466 {
467 mService.setError(error);
468 }
469 }
470 }
471 }
472
473 /**
474 * Updates the state of the current connection.
475 * Called via JNI by different threads (but not concurrently).
476 *
477 * @param status new state
478 */
479 public void updateStatus(int status)
480 {
481 switch (status)
482 {
483 case STATE_CHILD_SA_DOWN:
484 if (!mIsDisconnecting)
485 {
486 setState(State.CONNECTING);
487 }
488 break;
489 case STATE_CHILD_SA_UP:
490 setState(State.CONNECTED);
491 break;
492 case STATE_AUTH_ERROR:
493 setErrorDisconnect(ErrorState.AUTH_FAILED);
494 break;
495 case STATE_PEER_AUTH_ERROR:
496 setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
497 break;
498 case STATE_LOOKUP_ERROR:
499 setErrorDisconnect(ErrorState.LOOKUP_FAILED);
500 break;
501 case STATE_UNREACHABLE_ERROR:
502 setErrorDisconnect(ErrorState.UNREACHABLE);
503 break;
504 case STATE_GENERIC_ERROR:
505 setErrorDisconnect(ErrorState.GENERIC_ERROR);
506 break;
507 default:
508 Log.e(TAG, "Unknown status code received");
509 break;
510 }
511 }
512
513 /**
514 * Updates the IMC state of the current connection.
515 * Called via JNI by different threads (but not concurrently).
516 *
517 * @param value new state
518 */
519 public void updateImcState(int value)
520 {
521 ImcState state = ImcState.fromValue(value);
522 if (state != null)
523 {
524 setImcState(state);
525 }
526 }
527
528 /**
529 * Add a remediation instruction to the VPN state service.
530 * Called via JNI by different threads (but not concurrently).
531 *
532 * @param xml XML text
533 */
534 public void addRemediationInstruction(String xml)
535 {
536 for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml))
537 {
538 synchronized (mServiceLock)
539 {
540 if (mService != null)
541 {
542 mService.addRemediationInstruction(instruction);
543 }
544 }
545 }
546 }
547
548 /**
549 * Function called via JNI to generate a list of DER encoded CA certificates
550 * as byte array.
551 *
552 * @return a list of DER encoded CA certificates
553 */
554 private byte[][] getTrustedCertificates()
555 {
556 ArrayList<byte[]> certs = new ArrayList<byte[]>();
557 TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load();
558 try
559 {
560 String alias = this.mCurrentCertificateAlias;
561 if (alias != null)
562 {
563 X509Certificate cert = certman.getCACertificateFromAlias(alias);
564 if (cert == null)
565 {
566 return null;
567 }
568 certs.add(cert.getEncoded());
569 }
570 else
571 {
572 for (X509Certificate cert : certman.getAllCACertificates().values())
573 {
574 certs.add(cert.getEncoded());
575 }
576 }
577 }
578 catch (CertificateEncodingException e)
579 {
580 e.printStackTrace();
581 return null;
582 }
583 return certs.toArray(new byte[certs.size()][]);
584 }
585
586 /**
587 * Function called via JNI to get a list containing the DER encoded certificates
588 * of the user selected certificate chain (beginning with the user certificate).
589 *
590 * Since this method is called from a thread of charon's thread pool we are safe
591 * to call methods on KeyChain directly.
592 *
593 * @return list containing the certificates (first element is the user certificate)
594 * @throws InterruptedException
595 * @throws KeyChainException
596 * @throws CertificateEncodingException
597 */
598 private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
599 {
600 ArrayList<byte[]> encodings = new ArrayList<byte[]>();
601 X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias);
602 if (chain == null || chain.length == 0)
603 {
604 return null;
605 }
606 for (X509Certificate cert : chain)
607 {
608 encodings.add(cert.getEncoded());
609 }
610 return encodings.toArray(new byte[encodings.size()][]);
611 }
612
613 /**
614 * Function called via JNI to get the private key the user selected.
615 *
616 * Since this method is called from a thread of charon's thread pool we are safe
617 * to call methods on KeyChain directly.
618 *
619 * @return the private key
620 * @throws InterruptedException
621 * @throws KeyChainException
622 * @throws CertificateEncodingException
623 */
624 private PrivateKey getUserKey() throws KeyChainException, InterruptedException
625 {
626 return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
627 }
628
629 /**
630 * Initialization of charon, provided by libandroidbridge.so
631 *
632 * @param builder BuilderAdapter for this connection
633 * @param logfile absolute path to the logfile
634 * @param byod enable BYOD features
635 * @return TRUE if initialization was successful
636 */
637 public native boolean initializeCharon(BuilderAdapter builder, String logfile, boolean byod);
638
639 /**
640 * Deinitialize charon, provided by libandroidbridge.so
641 */
642 public native void deinitializeCharon();
643
644 /**
645 * Initiate VPN, provided by libandroidbridge.so
646 */
647 public native void initiate(String config);
648
649 /**
650 * Adapter for VpnService.Builder which is used to access it safely via JNI.
651 * There is a corresponding C object to access it from native code.
652 */
653 public class BuilderAdapter
654 {
655 private final VpnProfile mProfile;
656 private VpnService.Builder mBuilder;
657 private BuilderCache mCache;
658 private BuilderCache mEstablishedCache;
659
660 public BuilderAdapter(VpnProfile profile)
661 {
662 mProfile = profile;
663 mBuilder = createBuilder(mProfile.getName());
664 mCache = new BuilderCache(mProfile);
665 }
666
667 private VpnService.Builder createBuilder(String name)
668 {
669 VpnService.Builder builder = new CharonVpnService.Builder();
670 builder.setSession(name);
671
672 /* even though the option displayed in the system dialog says "Configure"
673 * we just use our main Activity */
674 Context context = getApplicationContext();
675 Intent intent = new Intent(context, MainActivity.class);
676 PendingIntent pending = PendingIntent.getActivity(context, 0, intent,
677 PendingIntent.FLAG_UPDATE_CURRENT);
678 builder.setConfigureIntent(pending);
679 return builder;
680 }
681
682 public synchronized boolean addAddress(String address, int prefixLength)
683 {
684 try
685 {
686 mCache.addAddress(address, prefixLength);
687 }
688 catch (IllegalArgumentException ex)
689 {
690 return false;
691 }
692 return true;
693 }
694
695 public synchronized boolean addDnsServer(String address)
696 {
697 try
698 {
699 mBuilder.addDnsServer(address);
700 mCache.recordAddressFamily(address);
701 }
702 catch (IllegalArgumentException ex)
703 {
704 return false;
705 }
706 return true;
707 }
708
709 public synchronized boolean addRoute(String address, int prefixLength)
710 {
711 try
712 {
713 mCache.addRoute(address, prefixLength);
714 }
715 catch (IllegalArgumentException ex)
716 {
717 return false;
718 }
719 return true;
720 }
721
722 public synchronized boolean addSearchDomain(String domain)
723 {
724 try
725 {
726 mBuilder.addSearchDomain(domain);
727 }
728 catch (IllegalArgumentException ex)
729 {
730 return false;
731 }
732 return true;
733 }
734
735 public synchronized boolean setMtu(int mtu)
736 {
737 try
738 {
739 mCache.setMtu(mtu);
740 }
741 catch (IllegalArgumentException ex)
742 {
743 return false;
744 }
745 return true;
746 }
747
748 public synchronized int establish()
749 {
750 ParcelFileDescriptor fd;
751 try
752 {
753 mCache.applyData(mBuilder);
754 fd = mBuilder.establish();
755 }
756 catch (Exception ex)
757 {
758 ex.printStackTrace();
759 return -1;
760 }
761 if (fd == null)
762 {
763 return -1;
764 }
765 /* now that the TUN device is created we don't need the current
766 * builder anymore, but we might need another when reestablishing */
767 mBuilder = createBuilder(mProfile.getName());
768 mEstablishedCache = mCache;
769 mCache = new BuilderCache(mProfile);
770 return fd.detachFd();
771 }
772
773 public synchronized int establishNoDns()
774 {
775 ParcelFileDescriptor fd;
776
777 if (mEstablishedCache == null)
778 {
779 return -1;
780 }
781 try
782 {
783 Builder builder = createBuilder(mProfile.getName());
784 mEstablishedCache.applyData(builder);
785 fd = builder.establish();
786 }
787 catch (Exception ex)
788 {
789 ex.printStackTrace();
790 return -1;
791 }
792 if (fd == null)
793 {
794 return -1;
795 }
796 return fd.detachFd();
797 }
798 }
799
800 /**
801 * Cache non DNS related information so we can recreate the builder without
802 * that information when reestablishing IKE_SAs
803 */
804 public class BuilderCache
805 {
806 private final List<IPRange> mAddresses = new ArrayList<>();
807 private final List<IPRange> mRoutesIPv4 = new ArrayList<>();
808 private final List<IPRange> mRoutesIPv6 = new ArrayList<>();
809 private final IPRangeSet mIncludedSubnetsv4 = new IPRangeSet();
810 private final IPRangeSet mIncludedSubnetsv6 = new IPRangeSet();
811 private final IPRangeSet mExcludedSubnets;
812 private final int mSplitTunneling;
813 private final SelectedAppsHandling mAppHandling;
814 private final String[] mSelectedApps;
815 private int mMtu;
816 private boolean mIPv4Seen, mIPv6Seen;
817
818 public BuilderCache(VpnProfile profile)
819 {
820 IPRangeSet included = IPRangeSet.fromString(profile.getIncludedSubnets());
821 for (IPRange range : included)
822 {
823 if (range.getFrom() instanceof Inet4Address)
824 {
825 mIncludedSubnetsv4.add(range);
826 }
827 else if (range.getFrom() instanceof Inet6Address)
828 {
829 mIncludedSubnetsv6.add(range);
830 }
831 }
832 mExcludedSubnets = IPRangeSet.fromString(profile.getExcludedSubnets());
833 Integer splitTunneling = profile.getSplitTunneling();
834 mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
835 mAppHandling = profile.getSelectedAppsHandling();
836 String selectedApps = profile.getSelectedApps();
837 mSelectedApps = selectedApps != null ? selectedApps.split("\\s+") : null;
838 }
839
840 public void addAddress(String address, int prefixLength)
841 {
842 try
843 {
844 mAddresses.add(new IPRange(address, prefixLength));
845 recordAddressFamily(address);
846 }
847 catch (UnknownHostException ex)
848 {
849 ex.printStackTrace();
850 }
851 }
852
853 public void addRoute(String address, int prefixLength)
854 {
855 try
856 {
857 if (isIPv6(address))
858 {
859 mRoutesIPv6.add(new IPRange(address, prefixLength));
860 }
861 else
862 {
863 mRoutesIPv4.add(new IPRange(address, prefixLength));
864 }
865 }
866 catch (UnknownHostException ex)
867 {
868 ex.printStackTrace();
869 }
870 }
871
872 public void setMtu(int mtu)
873 {
874 mMtu = mtu;
875 }
876
877 public void recordAddressFamily(String address)
878 {
879 try
880 {
881 if (isIPv6(address))
882 {
883 mIPv6Seen = true;
884 }
885 else
886 {
887 mIPv4Seen = true;
888 }
889 }
890 catch (UnknownHostException ex)
891 {
892 ex.printStackTrace();
893 }
894 }
895
896 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
897 public void applyData(VpnService.Builder builder)
898 {
899 for (IPRange address : mAddresses)
900 {
901 builder.addAddress(address.getFrom(), address.getPrefix());
902 }
903 /* add routes depending on whether split tunneling is allowed or not,
904 * that is, whether we have to handle and block non-VPN traffic */
905 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0)
906 {
907 if (mIPv4Seen)
908 { /* split tunneling is used depending on the routes and configuration */
909 IPRangeSet ranges = new IPRangeSet();
910 if (mIncludedSubnetsv4.size() > 0)
911 {
912 ranges.add(mIncludedSubnetsv4);
913 }
914 else
915 {
916 ranges.addAll(mRoutesIPv4);
917 }
918 ranges.remove(mExcludedSubnets);
919 for (IPRange subnet : ranges.subnets())
920 {
921 builder.addRoute(subnet.getFrom(), subnet.getPrefix());
922 }
923 }
924 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
925 { /* allow traffic that would otherwise be blocked to bypass the VPN */
926 builder.allowFamily(OsConstants.AF_INET);
927 }
928 }
929 else if (mIPv4Seen)
930 { /* only needed if we've seen any addresses. otherwise, traffic
931 * is blocked by default (we also install no routes in that case) */
932 builder.addRoute("0.0.0.0", 0);
933 }
934 /* same thing for IPv6 */
935 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0)
936 {
937 if (mIPv6Seen)
938 {
939 IPRangeSet ranges = new IPRangeSet();
940 if (mIncludedSubnetsv6.size() > 0)
941 {
942 ranges.add(mIncludedSubnetsv6);
943 }
944 else
945 {
946 ranges.addAll(mRoutesIPv6);
947 }
948 ranges.remove(mExcludedSubnets);
949 for (IPRange subnet : ranges.subnets())
950 {
951 builder.addRoute(subnet.getFrom(), subnet.getPrefix());
952 }
953 }
954 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
955 {
956 builder.allowFamily(OsConstants.AF_INET6);
957 }
958 }
959 else if (mIPv6Seen)
960 {
961 builder.addRoute("::", 0);
962 }
963 /* apply selected applications */
964 if (mSelectedApps != null && mSelectedApps.length > 0)
965 {
966 switch (mAppHandling)
967 {
968 case SELECTED_APPS_EXCLUDE:
969 for (String app : mSelectedApps)
970 {
971 try
972 {
973 builder.addDisallowedApplication(app);
974 }
975 catch (PackageManager.NameNotFoundException e)
976 {
977 // possible if not configured via GUI or app was uninstalled
978 }
979 }
980 break;
981 case SELECTED_APPS_ONLY:
982 for (String app : mSelectedApps)
983 {
984 try
985 {
986 builder.addAllowedApplication(app);
987 }
988 catch (PackageManager.NameNotFoundException e)
989 {
990 // possible if not configured via GUI or app was uninstalled
991 }
992 }
993 break;
994 default:
995 break;
996 }
997 }
998 builder.setMtu(mMtu);
999 }
1000
1001 private boolean isIPv6(String address) throws UnknownHostException
1002 {
1003 InetAddress addr = InetAddress.getByName(address);
1004 if (addr instanceof Inet4Address)
1005 {
1006 return false;
1007 }
1008 else if (addr instanceof Inet6Address)
1009 {
1010 return true;
1011 }
1012 return false;
1013 }
1014 }
1015
1016 /**
1017 * Function called via JNI to determine information about the Android version.
1018 */
1019 private static String getAndroidVersion()
1020 {
1021 return "Android " + Build.VERSION.RELEASE + " - " + Build.DISPLAY +
1022 "/" + Build.VERSION.SECURITY_PATCH;
1023 }
1024
1025 /**
1026 * Function called via JNI to determine information about the device.
1027 */
1028 private static String getDeviceString()
1029 {
1030 return Build.MODEL + " - " + Build.BRAND + "/" + Build.PRODUCT + "/" + Build.MANUFACTURER;
1031 }
1032
1033 /*
1034 * The libraries are extracted to /data/data/org.strongswan.android/...
1035 * during installation. On newer releases most are loaded in JNI_OnLoad.
1036 */
1037 static
1038 {
1039 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
1040 {
1041 System.loadLibrary("strongswan");
1042
1043 if (MainActivity.USE_BYOD)
1044 {
1045 System.loadLibrary("tpmtss");
1046 System.loadLibrary("tncif");
1047 System.loadLibrary("tnccs");
1048 System.loadLibrary("imcv");
1049 }
1050
1051 System.loadLibrary("charon");
1052 System.loadLibrary("ipsec");
1053 }
1054 System.loadLibrary("androidbridge");
1055 }
1056 }