2 * Copyright (C) 2012-2018 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * HSR Hochschule fuer Technik Rapperswil
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>.
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
18 package org
.strongswan
.android
.logic
;
20 import android
.annotation
.TargetApi
;
21 import android
.app
.Notification
;
22 import android
.app
.NotificationChannel
;
23 import android
.app
.NotificationManager
;
24 import android
.app
.PendingIntent
;
25 import android
.app
.Service
;
26 import android
.content
.ComponentName
;
27 import android
.content
.Context
;
28 import android
.content
.Intent
;
29 import android
.content
.ServiceConnection
;
30 import android
.content
.SharedPreferences
;
31 import android
.content
.pm
.PackageManager
;
32 import android
.net
.VpnService
;
33 import android
.os
.Build
;
34 import android
.os
.Bundle
;
35 import android
.os
.Handler
;
36 import android
.os
.IBinder
;
37 import android
.os
.ParcelFileDescriptor
;
38 import android
.preference
.PreferenceManager
;
39 import android
.security
.KeyChain
;
40 import android
.security
.KeyChainException
;
41 import android
.support
.v4
.app
.NotificationCompat
;
42 import android
.support
.v4
.content
.ContextCompat
;
43 import android
.system
.OsConstants
;
44 import android
.util
.Log
;
46 import org
.strongswan
.android
.R
;
47 import org
.strongswan
.android
.data
.VpnProfile
;
48 import org
.strongswan
.android
.data
.VpnProfile
.SelectedAppsHandling
;
49 import org
.strongswan
.android
.data
.VpnProfileDataSource
;
50 import org
.strongswan
.android
.data
.VpnType
.VpnTypeFeature
;
51 import org
.strongswan
.android
.logic
.VpnStateService
.ErrorState
;
52 import org
.strongswan
.android
.logic
.VpnStateService
.State
;
53 import org
.strongswan
.android
.logic
.imc
.ImcState
;
54 import org
.strongswan
.android
.logic
.imc
.RemediationInstruction
;
55 import org
.strongswan
.android
.ui
.MainActivity
;
56 import org
.strongswan
.android
.ui
.VpnProfileControlActivity
;
57 import org
.strongswan
.android
.utils
.Constants
;
58 import org
.strongswan
.android
.utils
.IPRange
;
59 import org
.strongswan
.android
.utils
.IPRangeSet
;
60 import org
.strongswan
.android
.utils
.SettingsWriter
;
63 import java
.net
.Inet4Address
;
64 import java
.net
.Inet6Address
;
65 import java
.net
.InetAddress
;
66 import java
.net
.UnknownHostException
;
67 import java
.security
.PrivateKey
;
68 import java
.security
.cert
.CertificateEncodingException
;
69 import java
.security
.cert
.X509Certificate
;
70 import java
.util
.ArrayList
;
71 import java
.util
.List
;
72 import java
.util
.Locale
;
73 import java
.util
.SortedSet
;
75 public class CharonVpnService
extends VpnService
implements Runnable
, VpnStateService
.VpnStateListener
77 private static final String TAG
= CharonVpnService
.class.getSimpleName();
78 private static final String VPN_SERVICE_ACTION
= "android.net.VpnService";
79 public static final String DISCONNECT_ACTION
= "org.strongswan.android.CharonVpnService.DISCONNECT";
80 private static final String NOTIFICATION_CHANNEL
= "org.strongswan.android.CharonVpnService.VPN_STATE_NOTIFICATION";
81 public static final String LOG_FILE
= "charon.log";
82 public static final int VPN_STATE_NOTIFICATION_ID
= 1;
84 private String mLogFile
;
85 private String mAppDir
;
86 private VpnProfileDataSource mDataSource
;
87 private Thread mConnectionHandler
;
88 private VpnProfile mCurrentProfile
;
89 private volatile String mCurrentCertificateAlias
;
90 private volatile String mCurrentUserCertificateAlias
;
91 private VpnProfile mNextProfile
;
92 private volatile boolean mProfileUpdated
;
93 private volatile boolean mTerminate
;
94 private volatile boolean mIsDisconnecting
;
95 private volatile boolean mShowNotification
;
96 private Handler mHandler
;
97 private VpnStateService mService
;
98 private final Object mServiceLock
= new Object();
99 private final ServiceConnection mServiceConnection
= new ServiceConnection() {
101 public void onServiceDisconnected(ComponentName name
)
102 { /* since the service is local this is theoretically only called when the process is terminated */
103 synchronized (mServiceLock
)
110 public void onServiceConnected(ComponentName name
, IBinder service
)
112 synchronized (mServiceLock
)
114 mService
= ((VpnStateService
.LocalBinder
)service
).getService();
116 /* we are now ready to start the handler thread */
117 mService
.registerListener(CharonVpnService
.this);
118 mConnectionHandler
.start();
123 * as defined in charonservice.h
125 static final int STATE_CHILD_SA_UP
= 1;
126 static final int STATE_CHILD_SA_DOWN
= 2;
127 static final int STATE_AUTH_ERROR
= 3;
128 static final int STATE_PEER_AUTH_ERROR
= 4;
129 static final int STATE_LOOKUP_ERROR
= 5;
130 static final int STATE_UNREACHABLE_ERROR
= 6;
131 static final int STATE_CERTIFICATE_UNAVAILABLE
= 7;
132 static final int STATE_GENERIC_ERROR
= 8;
135 public int onStartCommand(Intent intent
, int flags
, int startId
)
139 VpnProfile profile
= null;
141 if (VPN_SERVICE_ACTION
.equals(intent
.getAction()))
142 { /* triggered when Always-on VPN is activated */
143 SharedPreferences pref
= PreferenceManager
.getDefaultSharedPreferences(this);
144 String uuid
= pref
.getString(Constants
.PREF_DEFAULT_VPN_PROFILE
, null);
145 if (uuid
== null || uuid
.equals(Constants
.PREF_DEFAULT_VPN_PROFILE_MRU
))
147 uuid
= pref
.getString(Constants
.PREF_MRU_VPN_PROFILE
, null);
149 profile
= mDataSource
.getVpnProfile(uuid
);
151 else if (!DISCONNECT_ACTION
.equals(intent
.getAction()))
153 Bundle bundle
= intent
.getExtras();
156 profile
= mDataSource
.getVpnProfile(bundle
.getLong(VpnProfileDataSource
.KEY_ID
));
159 String password
= bundle
.getString(VpnProfileDataSource
.KEY_PASSWORD
);
160 profile
.setPassword(password
);
162 SharedPreferences pref
= PreferenceManager
.getDefaultSharedPreferences(this);
163 pref
.edit().putString(Constants
.PREF_MRU_VPN_PROFILE
, profile
.getUUID().toString())
168 setNextProfile(profile
);
170 return START_NOT_STICKY
;
174 public void onCreate()
176 mLogFile
= getFilesDir().getAbsolutePath() + File
.separator
+ LOG_FILE
;
177 mAppDir
= getFilesDir().getAbsolutePath();
179 /* handler used to do changes in the main UI thread */
180 mHandler
= new Handler();
182 mDataSource
= new VpnProfileDataSource(this);
184 /* use a separate thread as main thread for charon */
185 mConnectionHandler
= new Thread(this);
186 /* the thread is started when the service is bound */
187 bindService(new Intent(this, VpnStateService
.class),
188 mServiceConnection
, Service
.BIND_AUTO_CREATE
);
190 createNotificationChannel();
194 public void onRevoke()
195 { /* the system revoked the rights grated with the initial prepare() call.
196 * called when the user clicks disconnect in the system's VPN dialog */
197 setNextProfile(null);
201 public void onDestroy()
204 setNextProfile(null);
207 mConnectionHandler
.join();
209 catch (InterruptedException e
)
213 if (mService
!= null)
215 mService
.unregisterListener(this);
216 unbindService(mServiceConnection
);
222 * Set the profile that is to be initiated next. Notify the handler thread.
224 * @param profile the profile to initiate
226 private void setNextProfile(VpnProfile profile
)
230 this.mNextProfile
= profile
;
231 mProfileUpdated
= true;
245 while (!mProfileUpdated
)
250 mProfileUpdated
= false;
251 stopCurrentConnection();
252 if (mNextProfile
== null)
254 setState(State
.DISABLED
);
262 mCurrentProfile
= mNextProfile
;
265 /* store this in a separate (volatile) variable to avoid
266 * a possible deadlock during deinitialization */
267 mCurrentCertificateAlias
= mCurrentProfile
.getCertificateAlias();
268 mCurrentUserCertificateAlias
= mCurrentProfile
.getUserCertificateAlias();
270 startConnection(mCurrentProfile
);
271 mIsDisconnecting
= false;
274 BuilderAdapter builder
= new BuilderAdapter(mCurrentProfile
);
275 if (initializeCharon(builder
, mLogFile
, mAppDir
, mCurrentProfile
.getVpnType().has(VpnTypeFeature
.BYOD
)))
277 Log
.i(TAG
, "charon started");
279 if (mCurrentProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
) &&
280 mCurrentProfile
.getPassword() == null)
281 { /* this can happen if Always-on VPN is enabled with an incomplete profile */
282 setError(ErrorState
.PASSWORD_MISSING
);
286 SettingsWriter writer
= new SettingsWriter();
287 writer
.setValue("global.language", Locale
.getDefault().getLanguage());
288 writer
.setValue("global.mtu", mCurrentProfile
.getMTU());
289 writer
.setValue("global.nat_keepalive", mCurrentProfile
.getNATKeepAlive());
290 writer
.setValue("connection.type", mCurrentProfile
.getVpnType().getIdentifier());
291 writer
.setValue("connection.server", mCurrentProfile
.getGateway());
292 writer
.setValue("connection.port", mCurrentProfile
.getPort());
293 writer
.setValue("connection.username", mCurrentProfile
.getUsername());
294 writer
.setValue("connection.password", mCurrentProfile
.getPassword());
295 writer
.setValue("connection.local_id", mCurrentProfile
.getLocalId());
296 writer
.setValue("connection.remote_id", mCurrentProfile
.getRemoteId());
297 writer
.setValue("connection.certreq", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_SUPPRESS_CERT_REQS
) == 0);
298 writer
.setValue("connection.ike_proposal", mCurrentProfile
.getIkeProposal());
299 writer
.setValue("connection.esp_proposal", mCurrentProfile
.getEspProposal());
300 initiate(writer
.serialize());
304 Log
.e(TAG
, "failed to start charon");
305 setError(ErrorState
.GENERIC_ERROR
);
306 setState(State
.DISABLED
);
307 mCurrentProfile
= null;
311 catch (InterruptedException ex
)
313 stopCurrentConnection();
314 setState(State
.DISABLED
);
321 * Stop any existing connection by deinitializing charon.
323 private void stopCurrentConnection()
327 if (mCurrentProfile
!= null)
329 setState(State
.DISCONNECTING
);
330 mIsDisconnecting
= true;
331 deinitializeCharon();
332 Log
.i(TAG
, "charon stopped");
333 mCurrentProfile
= null;
334 removeNotification();
340 * Add a permanent notification while we are connected to avoid the service getting killed by
341 * the system when low on memory.
343 private void addNotification()
345 mHandler
.post(new Runnable()
350 mShowNotification
= true;
351 startForeground(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
357 * Remove the permanent notification.
359 private void removeNotification()
361 mHandler
.post(new Runnable()
366 mShowNotification
= false;
367 stopForeground(true);
373 * Create a notification channel for Android 8+
375 private void createNotificationChannel()
377 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.O
)
379 NotificationChannel channel
;
380 channel
= new NotificationChannel(NOTIFICATION_CHANNEL
, getString(R
.string
.permanent_notification_name
),
381 NotificationManager
.IMPORTANCE_LOW
);
382 channel
.setDescription(getString(R
.string
.permanent_notification_description
));
383 channel
.setLockscreenVisibility(Notification
.VISIBILITY_SECRET
);
384 channel
.setShowBadge(false);
385 NotificationManager notificationManager
= getSystemService(NotificationManager
.class);
386 notificationManager
.createNotificationChannel(channel
);
392 * Build a notification matching the current state
394 private Notification
buildNotification(boolean publicVersion
)
396 VpnProfile profile
= mService
.getProfile();
397 State state
= mService
.getState();
398 ErrorState error
= mService
.getErrorState();
400 boolean add_action
= false;
404 name
= profile
.getName();
406 NotificationCompat
.Builder builder
= new NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL
)
407 .setSmallIcon(R
.drawable
.ic_notification
)
408 .setCategory(NotificationCompat
.CATEGORY_SERVICE
)
409 .setVisibility(publicVersion ? NotificationCompat
.VISIBILITY_PUBLIC
410 : NotificationCompat
.VISIBILITY_PRIVATE
);
411 int s
= R
.string
.state_disabled
;
412 if (error
!= ErrorState
.NO_ERROR
)
414 s
= mService
.getErrorText();
415 builder
.setSmallIcon(R
.drawable
.ic_notification_warning
);
416 builder
.setColor(ContextCompat
.getColor(this, R
.color
.error_text
));
417 builder
.setContentText(getString(R
.string
.tap_for_details
));
424 s
= R
.string
.state_connecting
;
425 builder
.setSmallIcon(R
.drawable
.ic_notification_connecting
);
426 builder
.setColor(ContextCompat
.getColor(this, R
.color
.warning_text
));
430 s
= R
.string
.state_connected
;
431 builder
.setColor(ContextCompat
.getColor(this, R
.color
.success_text
));
432 builder
.setUsesChronometer(true);
436 s
= R
.string
.state_disconnecting
;
440 builder
.setContentTitle(getString(s
));
445 Intent intent
= new Intent(getApplicationContext(), VpnProfileControlActivity
.class);
446 intent
.setAction(VpnProfileControlActivity
.DISCONNECT
);
447 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
448 PendingIntent
.FLAG_UPDATE_CURRENT
);
449 builder
.addAction(R
.drawable
.ic_notification_disconnect
, getString(R
.string
.disconnect
), pending
);
451 if (error
== ErrorState
.NO_ERROR
)
453 builder
.setContentText(name
);
455 builder
.setPublicVersion(buildNotification(true));
458 Intent intent
= new Intent(getApplicationContext(), MainActivity
.class);
459 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
460 PendingIntent
.FLAG_UPDATE_CURRENT
);
461 builder
.setContentIntent(pending
);
462 return builder
.build();
466 public void stateChanged() {
467 if (mShowNotification
)
469 NotificationManager manager
= (NotificationManager
) getSystemService(Context
.NOTIFICATION_SERVICE
);
470 manager
.notify(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
475 * Notify the state service about a new connection attempt.
476 * Called by the handler thread.
478 * @param profile currently active VPN profile
480 private void startConnection(VpnProfile profile
)
482 synchronized (mServiceLock
)
484 if (mService
!= null)
486 mService
.startConnection(profile
);
492 * Update the current VPN state on the state service. Called by the handler
493 * thread and any of charon's threads.
495 * @param state current state
497 private void setState(State state
)
499 synchronized (mServiceLock
)
501 if (mService
!= null)
503 mService
.setState(state
);
509 * Set an error on the state service. Called by the handler thread and any
510 * of charon's threads.
512 * @param error error state
514 private void setError(ErrorState error
)
516 synchronized (mServiceLock
)
518 if (mService
!= null)
520 mService
.setError(error
);
526 * Set the IMC state on the state service. Called by the handler thread and
527 * any of charon's threads.
529 * @param state IMC state
531 private void setImcState(ImcState state
)
533 synchronized (mServiceLock
)
535 if (mService
!= null)
537 mService
.setImcState(state
);
543 * Set an error on the state service. Called by the handler thread and any
544 * of charon's threads.
546 * @param error error state
548 private void setErrorDisconnect(ErrorState error
)
550 synchronized (mServiceLock
)
552 if (mService
!= null)
554 if (!mIsDisconnecting
)
556 mService
.setError(error
);
563 * Updates the state of the current connection.
564 * Called via JNI by different threads (but not concurrently).
566 * @param status new state
568 public void updateStatus(int status
)
572 case STATE_CHILD_SA_DOWN
:
573 if (!mIsDisconnecting
)
575 setState(State
.CONNECTING
);
578 case STATE_CHILD_SA_UP
:
579 setState(State
.CONNECTED
);
581 case STATE_AUTH_ERROR
:
582 setErrorDisconnect(ErrorState
.AUTH_FAILED
);
584 case STATE_PEER_AUTH_ERROR
:
585 setErrorDisconnect(ErrorState
.PEER_AUTH_FAILED
);
587 case STATE_LOOKUP_ERROR
:
588 setErrorDisconnect(ErrorState
.LOOKUP_FAILED
);
590 case STATE_UNREACHABLE_ERROR
:
591 setErrorDisconnect(ErrorState
.UNREACHABLE
);
593 case STATE_CERTIFICATE_UNAVAILABLE
:
594 setErrorDisconnect(ErrorState
.CERTIFICATE_UNAVAILABLE
);
596 case STATE_GENERIC_ERROR
:
597 setErrorDisconnect(ErrorState
.GENERIC_ERROR
);
600 Log
.e(TAG
, "Unknown status code received");
606 * Updates the IMC state of the current connection.
607 * Called via JNI by different threads (but not concurrently).
609 * @param value new state
611 public void updateImcState(int value
)
613 ImcState state
= ImcState
.fromValue(value
);
621 * Add a remediation instruction to the VPN state service.
622 * Called via JNI by different threads (but not concurrently).
624 * @param xml XML text
626 public void addRemediationInstruction(String xml
)
628 for (RemediationInstruction instruction
: RemediationInstruction
.fromXml(xml
))
630 synchronized (mServiceLock
)
632 if (mService
!= null)
634 mService
.addRemediationInstruction(instruction
);
641 * Function called via JNI to generate a list of DER encoded CA certificates
644 * @return a list of DER encoded CA certificates
646 private byte[][] getTrustedCertificates()
648 ArrayList
<byte[]> certs
= new ArrayList
<byte[]>();
649 TrustedCertificateManager certman
= TrustedCertificateManager
.getInstance().load();
652 String alias
= this.mCurrentCertificateAlias
;
655 X509Certificate cert
= certman
.getCACertificateFromAlias(alias
);
660 certs
.add(cert
.getEncoded());
664 for (X509Certificate cert
: certman
.getAllCACertificates().values())
666 certs
.add(cert
.getEncoded());
670 catch (CertificateEncodingException e
)
675 return certs
.toArray(new byte[certs
.size()][]);
679 * Function called via JNI to get a list containing the DER encoded certificates
680 * of the user selected certificate chain (beginning with the user certificate).
682 * Since this method is called from a thread of charon's thread pool we are safe
683 * to call methods on KeyChain directly.
685 * @return list containing the certificates (first element is the user certificate)
686 * @throws InterruptedException
687 * @throws KeyChainException
688 * @throws CertificateEncodingException
690 private byte[][] getUserCertificate() throws KeyChainException
, InterruptedException
, CertificateEncodingException
692 ArrayList
<byte[]> encodings
= new ArrayList
<byte[]>();
693 X509Certificate
[] chain
= KeyChain
.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias
);
694 if (chain
== null || chain
.length
== 0)
698 for (X509Certificate cert
: chain
)
700 encodings
.add(cert
.getEncoded());
702 return encodings
.toArray(new byte[encodings
.size()][]);
706 * Function called via JNI to get the private key the user selected.
708 * Since this method is called from a thread of charon's thread pool we are safe
709 * to call methods on KeyChain directly.
711 * @return the private key
712 * @throws InterruptedException
713 * @throws KeyChainException
715 private PrivateKey
getUserKey() throws KeyChainException
, InterruptedException
717 return KeyChain
.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias
);
721 * Initialization of charon, provided by libandroidbridge.so
723 * @param builder BuilderAdapter for this connection
724 * @param logfile absolute path to the logfile
725 * @param appdir absolute path to the data directory of the app
726 * @param byod enable BYOD features
727 * @return TRUE if initialization was successful
729 public native boolean initializeCharon(BuilderAdapter builder
, String logfile
, String appdir
, boolean byod
);
732 * Deinitialize charon, provided by libandroidbridge.so
734 public native void deinitializeCharon();
737 * Initiate VPN, provided by libandroidbridge.so
739 public native void initiate(String config
);
742 * Adapter for VpnService.Builder which is used to access it safely via JNI.
743 * There is a corresponding C object to access it from native code.
745 public class BuilderAdapter
747 private final VpnProfile mProfile
;
748 private VpnService
.Builder mBuilder
;
749 private BuilderCache mCache
;
750 private BuilderCache mEstablishedCache
;
752 public BuilderAdapter(VpnProfile profile
)
755 mBuilder
= createBuilder(mProfile
.getName());
756 mCache
= new BuilderCache(mProfile
);
759 private VpnService
.Builder
createBuilder(String name
)
761 VpnService
.Builder builder
= new CharonVpnService
.Builder();
762 builder
.setSession(name
);
764 /* even though the option displayed in the system dialog says "Configure"
765 * we just use our main Activity */
766 Context context
= getApplicationContext();
767 Intent intent
= new Intent(context
, MainActivity
.class);
768 PendingIntent pending
= PendingIntent
.getActivity(context
, 0, intent
,
769 PendingIntent
.FLAG_UPDATE_CURRENT
);
770 builder
.setConfigureIntent(pending
);
774 public synchronized boolean addAddress(String address
, int prefixLength
)
778 mCache
.addAddress(address
, prefixLength
);
780 catch (IllegalArgumentException ex
)
787 public synchronized boolean addDnsServer(String address
)
791 mBuilder
.addDnsServer(address
);
792 mCache
.recordAddressFamily(address
);
794 catch (IllegalArgumentException ex
)
801 public synchronized boolean addRoute(String address
, int prefixLength
)
805 mCache
.addRoute(address
, prefixLength
);
807 catch (IllegalArgumentException ex
)
814 public synchronized boolean addSearchDomain(String domain
)
818 mBuilder
.addSearchDomain(domain
);
820 catch (IllegalArgumentException ex
)
827 public synchronized boolean setMtu(int mtu
)
833 catch (IllegalArgumentException ex
)
840 public synchronized int establish()
842 ParcelFileDescriptor fd
;
845 mCache
.applyData(mBuilder
);
846 fd
= mBuilder
.establish();
850 ex
.printStackTrace();
857 /* now that the TUN device is created we don't need the current
858 * builder anymore, but we might need another when reestablishing */
859 mBuilder
= createBuilder(mProfile
.getName());
860 mEstablishedCache
= mCache
;
861 mCache
= new BuilderCache(mProfile
);
862 return fd
.detachFd();
865 public synchronized int establishNoDns()
867 ParcelFileDescriptor fd
;
869 if (mEstablishedCache
== null)
875 Builder builder
= createBuilder(mProfile
.getName());
876 mEstablishedCache
.applyData(builder
);
877 fd
= builder
.establish();
881 ex
.printStackTrace();
888 return fd
.detachFd();
893 * Cache non DNS related information so we can recreate the builder without
894 * that information when reestablishing IKE_SAs
896 public class BuilderCache
898 private final List
<IPRange
> mAddresses
= new ArrayList
<>();
899 private final List
<IPRange
> mRoutesIPv4
= new ArrayList
<>();
900 private final List
<IPRange
> mRoutesIPv6
= new ArrayList
<>();
901 private final IPRangeSet mIncludedSubnetsv4
= new IPRangeSet();
902 private final IPRangeSet mIncludedSubnetsv6
= new IPRangeSet();
903 private final IPRangeSet mExcludedSubnets
;
904 private final int mSplitTunneling
;
905 private final SelectedAppsHandling mAppHandling
;
906 private final SortedSet
<String
> mSelectedApps
;
908 private boolean mIPv4Seen
, mIPv6Seen
;
910 public BuilderCache(VpnProfile profile
)
912 IPRangeSet included
= IPRangeSet
.fromString(profile
.getIncludedSubnets());
913 for (IPRange range
: included
)
915 if (range
.getFrom() instanceof Inet4Address
)
917 mIncludedSubnetsv4
.add(range
);
919 else if (range
.getFrom() instanceof Inet6Address
)
921 mIncludedSubnetsv6
.add(range
);
924 mExcludedSubnets
= IPRangeSet
.fromString(profile
.getExcludedSubnets());
925 Integer splitTunneling
= profile
.getSplitTunneling();
926 mSplitTunneling
= splitTunneling
!= null ? splitTunneling
: 0;
927 mAppHandling
= profile
.getSelectedAppsHandling();
928 mSelectedApps
= profile
.getSelectedAppsSet();
931 public void addAddress(String address
, int prefixLength
)
935 mAddresses
.add(new IPRange(address
, prefixLength
));
936 recordAddressFamily(address
);
938 catch (UnknownHostException ex
)
940 ex
.printStackTrace();
944 public void addRoute(String address
, int prefixLength
)
950 mRoutesIPv6
.add(new IPRange(address
, prefixLength
));
954 mRoutesIPv4
.add(new IPRange(address
, prefixLength
));
957 catch (UnknownHostException ex
)
959 ex
.printStackTrace();
963 public void setMtu(int mtu
)
968 public void recordAddressFamily(String address
)
981 catch (UnknownHostException ex
)
983 ex
.printStackTrace();
987 @TargetApi(Build
.VERSION_CODES
.LOLLIPOP
)
988 public void applyData(VpnService
.Builder builder
)
990 for (IPRange address
: mAddresses
)
992 builder
.addAddress(address
.getFrom(), address
.getPrefix());
994 /* add routes depending on whether split tunneling is allowed or not,
995 * that is, whether we have to handle and block non-VPN traffic */
996 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV4
) == 0)
999 { /* split tunneling is used depending on the routes and configuration */
1000 IPRangeSet ranges
= new IPRangeSet();
1001 if (mIncludedSubnetsv4
.size() > 0)
1003 ranges
.add(mIncludedSubnetsv4
);
1007 ranges
.addAll(mRoutesIPv4
);
1009 ranges
.remove(mExcludedSubnets
);
1010 for (IPRange subnet
: ranges
.subnets())
1014 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1016 catch (IllegalArgumentException e
)
1017 { /* some Android versions don't seem to like multicast addresses here,
1018 * ignore it for now */
1019 if (!subnet
.getFrom().isMulticastAddress())
1026 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1027 { /* allow traffic that would otherwise be blocked to bypass the VPN */
1028 builder
.allowFamily(OsConstants
.AF_INET
);
1032 { /* only needed if we've seen any addresses. otherwise, traffic
1033 * is blocked by default (we also install no routes in that case) */
1034 builder
.addRoute("0.0.0.0", 0);
1036 /* same thing for IPv6 */
1037 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV6
) == 0)
1041 IPRangeSet ranges
= new IPRangeSet();
1042 if (mIncludedSubnetsv6
.size() > 0)
1044 ranges
.add(mIncludedSubnetsv6
);
1048 ranges
.addAll(mRoutesIPv6
);
1050 ranges
.remove(mExcludedSubnets
);
1051 for (IPRange subnet
: ranges
.subnets())
1055 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1057 catch (IllegalArgumentException e
)
1059 if (!subnet
.getFrom().isMulticastAddress())
1066 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1068 builder
.allowFamily(OsConstants
.AF_INET6
);
1073 builder
.addRoute("::", 0);
1075 /* apply selected applications */
1076 if (mSelectedApps
.size() > 0 &&
1077 Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1079 switch (mAppHandling
)
1081 case SELECTED_APPS_EXCLUDE
:
1082 for (String app
: mSelectedApps
)
1086 builder
.addDisallowedApplication(app
);
1088 catch (PackageManager
.NameNotFoundException e
)
1090 // possible if not configured via GUI or app was uninstalled
1094 case SELECTED_APPS_ONLY
:
1095 for (String app
: mSelectedApps
)
1099 builder
.addAllowedApplication(app
);
1101 catch (PackageManager
.NameNotFoundException e
)
1103 // possible if not configured via GUI or app was uninstalled
1111 builder
.setMtu(mMtu
);
1114 private boolean isIPv6(String address
) throws UnknownHostException
1116 InetAddress addr
= InetAddress
.getByName(address
);
1117 if (addr
instanceof Inet4Address
)
1121 else if (addr
instanceof Inet6Address
)
1130 * Function called via JNI to determine information about the Android version.
1132 private static String
getAndroidVersion()
1134 String version
= "Android " + Build
.VERSION
.RELEASE
+ " - " + Build
.DISPLAY
;
1135 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.M
)
1137 version
+= "/" + Build
.VERSION
.SECURITY_PATCH
;
1143 * Function called via JNI to determine information about the device.
1145 private static String
getDeviceString()
1147 return Build
.MODEL
+ " - " + Build
.BRAND
+ "/" + Build
.PRODUCT
+ "/" + Build
.MANUFACTURER
;