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