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
.io
.FileInputStream
;
64 import java
.io
.IOException
;
65 import java
.net
.Inet4Address
;
66 import java
.net
.Inet6Address
;
67 import java
.net
.InetAddress
;
68 import java
.net
.UnknownHostException
;
69 import java
.nio
.ByteBuffer
;
70 import java
.nio
.channels
.ClosedByInterruptException
;
71 import java
.security
.PrivateKey
;
72 import java
.security
.cert
.CertificateEncodingException
;
73 import java
.security
.cert
.X509Certificate
;
74 import java
.util
.ArrayList
;
75 import java
.util
.List
;
76 import java
.util
.Locale
;
77 import java
.util
.SortedSet
;
79 public class CharonVpnService
extends VpnService
implements Runnable
, VpnStateService
.VpnStateListener
81 private static final String TAG
= CharonVpnService
.class.getSimpleName();
82 private static final String VPN_SERVICE_ACTION
= "android.net.VpnService";
83 public static final String DISCONNECT_ACTION
= "org.strongswan.android.CharonVpnService.DISCONNECT";
84 private static final String NOTIFICATION_CHANNEL
= "org.strongswan.android.CharonVpnService.VPN_STATE_NOTIFICATION";
85 public static final String LOG_FILE
= "charon.log";
86 public static final String KEY_IS_RETRY
= "retry";
87 public static final int VPN_STATE_NOTIFICATION_ID
= 1;
89 private String mLogFile
;
90 private String mAppDir
;
91 private VpnProfileDataSource mDataSource
;
92 private Thread mConnectionHandler
;
93 private VpnProfile mCurrentProfile
;
94 private volatile String mCurrentCertificateAlias
;
95 private volatile String mCurrentUserCertificateAlias
;
96 private VpnProfile mNextProfile
;
97 private volatile boolean mProfileUpdated
;
98 private volatile boolean mTerminate
;
99 private volatile boolean mIsDisconnecting
;
100 private volatile boolean mShowNotification
;
101 private BuilderAdapter mBuilderAdapter
= new BuilderAdapter();
102 private Handler mHandler
;
103 private VpnStateService mService
;
104 private final Object mServiceLock
= new Object();
105 private final ServiceConnection mServiceConnection
= new ServiceConnection() {
107 public void onServiceDisconnected(ComponentName name
)
108 { /* since the service is local this is theoretically only called when the process is terminated */
109 synchronized (mServiceLock
)
116 public void onServiceConnected(ComponentName name
, IBinder service
)
118 synchronized (mServiceLock
)
120 mService
= ((VpnStateService
.LocalBinder
)service
).getService();
122 /* we are now ready to start the handler thread */
123 mService
.registerListener(CharonVpnService
.this);
124 mConnectionHandler
.start();
129 * as defined in charonservice.h
131 static final int STATE_CHILD_SA_UP
= 1;
132 static final int STATE_CHILD_SA_DOWN
= 2;
133 static final int STATE_AUTH_ERROR
= 3;
134 static final int STATE_PEER_AUTH_ERROR
= 4;
135 static final int STATE_LOOKUP_ERROR
= 5;
136 static final int STATE_UNREACHABLE_ERROR
= 6;
137 static final int STATE_CERTIFICATE_UNAVAILABLE
= 7;
138 static final int STATE_GENERIC_ERROR
= 8;
141 public int onStartCommand(Intent intent
, int flags
, int startId
)
145 VpnProfile profile
= null;
146 boolean retry
= false;
148 if (VPN_SERVICE_ACTION
.equals(intent
.getAction()))
149 { /* triggered when Always-on VPN is activated */
150 SharedPreferences pref
= PreferenceManager
.getDefaultSharedPreferences(this);
151 String uuid
= pref
.getString(Constants
.PREF_DEFAULT_VPN_PROFILE
, null);
152 if (uuid
== null || uuid
.equals(Constants
.PREF_DEFAULT_VPN_PROFILE_MRU
))
154 uuid
= pref
.getString(Constants
.PREF_MRU_VPN_PROFILE
, null);
156 profile
= mDataSource
.getVpnProfile(uuid
);
158 else if (!DISCONNECT_ACTION
.equals(intent
.getAction()))
160 Bundle bundle
= intent
.getExtras();
163 profile
= mDataSource
.getVpnProfile(bundle
.getLong(VpnProfileDataSource
.KEY_ID
));
166 String password
= bundle
.getString(VpnProfileDataSource
.KEY_PASSWORD
);
167 profile
.setPassword(password
);
169 retry
= bundle
.getBoolean(CharonVpnService
.KEY_IS_RETRY
, false);
171 SharedPreferences pref
= PreferenceManager
.getDefaultSharedPreferences(this);
172 pref
.edit().putString(Constants
.PREF_MRU_VPN_PROFILE
, profile
.getUUID().toString())
177 if (profile
!= null && !retry
)
178 { /* delete the log file if this is not an automatic retry */
179 deleteFile(LOG_FILE
);
181 setNextProfile(profile
);
183 return START_NOT_STICKY
;
187 public void onCreate()
189 mLogFile
= getFilesDir().getAbsolutePath() + File
.separator
+ LOG_FILE
;
190 mAppDir
= getFilesDir().getAbsolutePath();
192 /* handler used to do changes in the main UI thread */
193 mHandler
= new Handler();
195 mDataSource
= new VpnProfileDataSource(this);
197 /* use a separate thread as main thread for charon */
198 mConnectionHandler
= new Thread(this);
199 /* the thread is started when the service is bound */
200 bindService(new Intent(this, VpnStateService
.class),
201 mServiceConnection
, Service
.BIND_AUTO_CREATE
);
203 createNotificationChannel();
207 public void onRevoke()
208 { /* the system revoked the rights grated with the initial prepare() call.
209 * called when the user clicks disconnect in the system's VPN dialog */
210 setNextProfile(null);
214 public void onDestroy()
217 setNextProfile(null);
220 mConnectionHandler
.join();
222 catch (InterruptedException e
)
226 if (mService
!= null)
228 mService
.unregisterListener(this);
229 unbindService(mServiceConnection
);
235 * Set the profile that is to be initiated next. Notify the handler thread.
237 * @param profile the profile to initiate
239 private void setNextProfile(VpnProfile profile
)
243 this.mNextProfile
= profile
;
244 mProfileUpdated
= true;
258 while (!mProfileUpdated
)
263 mProfileUpdated
= false;
264 stopCurrentConnection();
265 if (mNextProfile
== null)
267 setState(State
.DISABLED
);
275 mCurrentProfile
= mNextProfile
;
278 /* store this in a separate (volatile) variable to avoid
279 * a possible deadlock during deinitialization */
280 mCurrentCertificateAlias
= mCurrentProfile
.getCertificateAlias();
281 mCurrentUserCertificateAlias
= mCurrentProfile
.getUserCertificateAlias();
283 startConnection(mCurrentProfile
);
284 mIsDisconnecting
= false;
286 SimpleFetcher
.enable();
288 mBuilderAdapter
.setProfile(mCurrentProfile
);
289 if (initializeCharon(mBuilderAdapter
, mLogFile
, mAppDir
, mCurrentProfile
.getVpnType().has(VpnTypeFeature
.BYOD
)))
291 Log
.i(TAG
, "charon started");
293 if (mCurrentProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
) &&
294 mCurrentProfile
.getPassword() == null)
295 { /* this can happen if Always-on VPN is enabled with an incomplete profile */
296 setError(ErrorState
.PASSWORD_MISSING
);
300 SettingsWriter writer
= new SettingsWriter();
301 writer
.setValue("global.language", Locale
.getDefault().getLanguage());
302 writer
.setValue("global.mtu", mCurrentProfile
.getMTU());
303 writer
.setValue("global.nat_keepalive", mCurrentProfile
.getNATKeepAlive());
304 writer
.setValue("global.crl", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_DISABLE_CRL
) == 0);
305 writer
.setValue("global.ocsp", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_DISABLE_OCSP
) == 0);
306 writer
.setValue("connection.type", mCurrentProfile
.getVpnType().getIdentifier());
307 writer
.setValue("connection.server", mCurrentProfile
.getGateway());
308 writer
.setValue("connection.port", mCurrentProfile
.getPort());
309 writer
.setValue("connection.username", mCurrentProfile
.getUsername());
310 writer
.setValue("connection.password", mCurrentProfile
.getPassword());
311 writer
.setValue("connection.local_id", mCurrentProfile
.getLocalId());
312 writer
.setValue("connection.remote_id", mCurrentProfile
.getRemoteId());
313 writer
.setValue("connection.certreq", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_SUPPRESS_CERT_REQS
) == 0);
314 writer
.setValue("connection.strict_revocation", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_STRICT_REVOCATION
) != 0);
315 writer
.setValue("connection.ike_proposal", mCurrentProfile
.getIkeProposal());
316 writer
.setValue("connection.esp_proposal", mCurrentProfile
.getEspProposal());
317 initiate(writer
.serialize());
321 Log
.e(TAG
, "failed to start charon");
322 setError(ErrorState
.GENERIC_ERROR
);
323 setState(State
.DISABLED
);
324 mCurrentProfile
= null;
328 catch (InterruptedException ex
)
330 stopCurrentConnection();
331 setState(State
.DISABLED
);
338 * Stop any existing connection by deinitializing charon.
340 private void stopCurrentConnection()
344 if (mNextProfile
!= null && Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
346 mBuilderAdapter
.setProfile(mNextProfile
);
347 mBuilderAdapter
.establishBlocking();
350 if (mCurrentProfile
!= null)
352 setState(State
.DISCONNECTING
);
353 mIsDisconnecting
= true;
354 SimpleFetcher
.disable();
355 deinitializeCharon();
356 Log
.i(TAG
, "charon stopped");
357 mCurrentProfile
= null;
358 if (mNextProfile
== null)
359 { /* only do this if we are not connecting to another profile */
360 removeNotification();
361 mBuilderAdapter
.closeBlocking();
368 * Add a permanent notification while we are connected to avoid the service getting killed by
369 * the system when low on memory.
371 private void addNotification()
373 mHandler
.post(new Runnable()
378 mShowNotification
= true;
379 startForeground(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
385 * Remove the permanent notification.
387 private void removeNotification()
389 mHandler
.post(new Runnable()
394 mShowNotification
= false;
395 stopForeground(true);
401 * Create a notification channel for Android 8+
403 private void createNotificationChannel()
405 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.O
)
407 NotificationChannel channel
;
408 channel
= new NotificationChannel(NOTIFICATION_CHANNEL
, getString(R
.string
.permanent_notification_name
),
409 NotificationManager
.IMPORTANCE_LOW
);
410 channel
.setDescription(getString(R
.string
.permanent_notification_description
));
411 channel
.setLockscreenVisibility(Notification
.VISIBILITY_SECRET
);
412 channel
.setShowBadge(false);
413 NotificationManager notificationManager
= getSystemService(NotificationManager
.class);
414 notificationManager
.createNotificationChannel(channel
);
420 * Build a notification matching the current state
422 private Notification
buildNotification(boolean publicVersion
)
424 VpnProfile profile
= mService
.getProfile();
425 State state
= mService
.getState();
426 ErrorState error
= mService
.getErrorState();
428 boolean add_action
= false;
432 name
= profile
.getName();
434 NotificationCompat
.Builder builder
= new NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL
)
435 .setSmallIcon(R
.drawable
.ic_notification
)
436 .setCategory(NotificationCompat
.CATEGORY_SERVICE
)
437 .setVisibility(publicVersion ? NotificationCompat
.VISIBILITY_PUBLIC
438 : NotificationCompat
.VISIBILITY_PRIVATE
);
439 int s
= R
.string
.state_disabled
;
440 if (error
!= ErrorState
.NO_ERROR
)
442 s
= mService
.getErrorText();
443 builder
.setSmallIcon(R
.drawable
.ic_notification_warning
);
444 builder
.setColor(ContextCompat
.getColor(this, R
.color
.error_text
));
446 if (!publicVersion
&& profile
!= null)
448 int retry
= mService
.getRetryIn();
451 builder
.setContentText(getResources().getQuantityString(R
.plurals
.retry_in
, retry
, retry
));
452 builder
.setProgress(mService
.getRetryTimeout(), retry
, false);
455 Intent intent
= new Intent(getApplicationContext(), VpnProfileControlActivity
.class);
456 intent
.addFlags(Intent
.FLAG_ACTIVITY_NEW_TASK
);
457 intent
.setAction(VpnProfileControlActivity
.START_PROFILE
);
458 intent
.putExtra(VpnProfileControlActivity
.EXTRA_VPN_PROFILE_ID
, profile
.getUUID().toString());
459 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
460 PendingIntent
.FLAG_UPDATE_CURRENT
);
461 builder
.addAction(R
.drawable
.ic_notification_connecting
, getString(R
.string
.retry
), pending
);
467 builder
.setProgress(0, 0, false);
472 s
= R
.string
.state_connecting
;
473 builder
.setSmallIcon(R
.drawable
.ic_notification_connecting
);
474 builder
.setColor(ContextCompat
.getColor(this, R
.color
.warning_text
));
478 s
= R
.string
.state_connected
;
479 builder
.setColor(ContextCompat
.getColor(this, R
.color
.success_text
));
480 builder
.setUsesChronometer(true);
484 s
= R
.string
.state_disconnecting
;
488 builder
.setContentTitle(getString(s
));
493 Intent intent
= new Intent(getApplicationContext(), VpnProfileControlActivity
.class);
494 intent
.setAction(VpnProfileControlActivity
.DISCONNECT
);
495 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
496 PendingIntent
.FLAG_UPDATE_CURRENT
);
497 builder
.addAction(R
.drawable
.ic_notification_disconnect
, getString(R
.string
.disconnect
), pending
);
499 if (error
== ErrorState
.NO_ERROR
)
501 builder
.setContentText(name
);
503 builder
.setPublicVersion(buildNotification(true));
506 Intent intent
= new Intent(getApplicationContext(), MainActivity
.class);
507 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
508 PendingIntent
.FLAG_UPDATE_CURRENT
);
509 builder
.setContentIntent(pending
);
510 return builder
.build();
514 public void stateChanged() {
515 if (mShowNotification
)
517 NotificationManager manager
= (NotificationManager
) getSystemService(Context
.NOTIFICATION_SERVICE
);
518 manager
.notify(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
523 * Notify the state service about a new connection attempt.
524 * Called by the handler thread.
526 * @param profile currently active VPN profile
528 private void startConnection(VpnProfile profile
)
530 synchronized (mServiceLock
)
532 if (mService
!= null)
534 mService
.startConnection(profile
);
540 * Update the current VPN state on the state service. Called by the handler
541 * thread and any of charon's threads.
543 * @param state current state
545 private void setState(State state
)
547 synchronized (mServiceLock
)
549 if (mService
!= null)
551 mService
.setState(state
);
557 * Set an error on the state service. Called by the handler thread and any
558 * of charon's threads.
560 * @param error error state
562 private void setError(ErrorState error
)
564 synchronized (mServiceLock
)
566 if (mService
!= null)
568 mService
.setError(error
);
574 * Set the IMC state on the state service. Called by the handler thread and
575 * any of charon's threads.
577 * @param state IMC state
579 private void setImcState(ImcState state
)
581 synchronized (mServiceLock
)
583 if (mService
!= null)
585 mService
.setImcState(state
);
591 * Set an error on the state service. Called by the handler thread and any
592 * of charon's threads.
594 * @param error error state
596 private void setErrorDisconnect(ErrorState error
)
598 synchronized (mServiceLock
)
600 if (mService
!= null)
602 if (!mIsDisconnecting
)
604 mService
.setError(error
);
611 * Updates the state of the current connection.
612 * Called via JNI by different threads (but not concurrently).
614 * @param status new state
616 public void updateStatus(int status
)
620 case STATE_CHILD_SA_DOWN
:
621 if (!mIsDisconnecting
)
623 setState(State
.CONNECTING
);
626 case STATE_CHILD_SA_UP
:
627 setState(State
.CONNECTED
);
629 case STATE_AUTH_ERROR
:
630 setErrorDisconnect(ErrorState
.AUTH_FAILED
);
632 case STATE_PEER_AUTH_ERROR
:
633 setErrorDisconnect(ErrorState
.PEER_AUTH_FAILED
);
635 case STATE_LOOKUP_ERROR
:
636 setErrorDisconnect(ErrorState
.LOOKUP_FAILED
);
638 case STATE_UNREACHABLE_ERROR
:
639 setErrorDisconnect(ErrorState
.UNREACHABLE
);
641 case STATE_CERTIFICATE_UNAVAILABLE
:
642 setErrorDisconnect(ErrorState
.CERTIFICATE_UNAVAILABLE
);
644 case STATE_GENERIC_ERROR
:
645 setErrorDisconnect(ErrorState
.GENERIC_ERROR
);
648 Log
.e(TAG
, "Unknown status code received");
654 * Updates the IMC state of the current connection.
655 * Called via JNI by different threads (but not concurrently).
657 * @param value new state
659 public void updateImcState(int value
)
661 ImcState state
= ImcState
.fromValue(value
);
669 * Add a remediation instruction to the VPN state service.
670 * Called via JNI by different threads (but not concurrently).
672 * @param xml XML text
674 public void addRemediationInstruction(String xml
)
676 for (RemediationInstruction instruction
: RemediationInstruction
.fromXml(xml
))
678 synchronized (mServiceLock
)
680 if (mService
!= null)
682 mService
.addRemediationInstruction(instruction
);
689 * Function called via JNI to generate a list of DER encoded CA certificates
692 * @return a list of DER encoded CA certificates
694 private byte[][] getTrustedCertificates()
696 ArrayList
<byte[]> certs
= new ArrayList
<byte[]>();
697 TrustedCertificateManager certman
= TrustedCertificateManager
.getInstance().load();
700 String alias
= this.mCurrentCertificateAlias
;
703 X509Certificate cert
= certman
.getCACertificateFromAlias(alias
);
708 certs
.add(cert
.getEncoded());
712 for (X509Certificate cert
: certman
.getAllCACertificates().values())
714 certs
.add(cert
.getEncoded());
718 catch (CertificateEncodingException e
)
723 return certs
.toArray(new byte[certs
.size()][]);
727 * Function called via JNI to get a list containing the DER encoded certificates
728 * of the user selected certificate chain (beginning with the user certificate).
730 * Since this method is called from a thread of charon's thread pool we are safe
731 * to call methods on KeyChain directly.
733 * @return list containing the certificates (first element is the user certificate)
734 * @throws InterruptedException
735 * @throws KeyChainException
736 * @throws CertificateEncodingException
738 private byte[][] getUserCertificate() throws KeyChainException
, InterruptedException
, CertificateEncodingException
740 ArrayList
<byte[]> encodings
= new ArrayList
<byte[]>();
741 X509Certificate
[] chain
= KeyChain
.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias
);
742 if (chain
== null || chain
.length
== 0)
746 for (X509Certificate cert
: chain
)
748 encodings
.add(cert
.getEncoded());
750 return encodings
.toArray(new byte[encodings
.size()][]);
754 * Function called via JNI to get the private key the user selected.
756 * Since this method is called from a thread of charon's thread pool we are safe
757 * to call methods on KeyChain directly.
759 * @return the private key
760 * @throws InterruptedException
761 * @throws KeyChainException
763 private PrivateKey
getUserKey() throws KeyChainException
, InterruptedException
765 return KeyChain
.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias
);
769 * Initialization of charon, provided by libandroidbridge.so
771 * @param builder BuilderAdapter for this connection
772 * @param logfile absolute path to the logfile
773 * @param appdir absolute path to the data directory of the app
774 * @param byod enable BYOD features
775 * @return TRUE if initialization was successful
777 public native boolean initializeCharon(BuilderAdapter builder
, String logfile
, String appdir
, boolean byod
);
780 * Deinitialize charon, provided by libandroidbridge.so
782 public native void deinitializeCharon();
785 * Initiate VPN, provided by libandroidbridge.so
787 public native void initiate(String config
);
790 * Adapter for VpnService.Builder which is used to access it safely via JNI.
791 * There is a corresponding C object to access it from native code.
793 public class BuilderAdapter
795 private VpnProfile mProfile
;
796 private VpnService
.Builder mBuilder
;
797 private BuilderCache mCache
;
798 private BuilderCache mEstablishedCache
;
799 private PacketDropper mDropper
= new PacketDropper();
801 public synchronized void setProfile(VpnProfile profile
)
804 mBuilder
= createBuilder(mProfile
.getName());
805 mCache
= new BuilderCache(mProfile
);
808 private VpnService
.Builder
createBuilder(String name
)
810 VpnService
.Builder builder
= new CharonVpnService
.Builder();
811 builder
.setSession(name
);
813 /* even though the option displayed in the system dialog says "Configure"
814 * we just use our main Activity */
815 Context context
= getApplicationContext();
816 Intent intent
= new Intent(context
, MainActivity
.class);
817 PendingIntent pending
= PendingIntent
.getActivity(context
, 0, intent
,
818 PendingIntent
.FLAG_UPDATE_CURRENT
);
819 builder
.setConfigureIntent(pending
);
823 public synchronized boolean addAddress(String address
, int prefixLength
)
827 mCache
.addAddress(address
, prefixLength
);
829 catch (IllegalArgumentException ex
)
836 public synchronized boolean addDnsServer(String address
)
840 mBuilder
.addDnsServer(address
);
841 mCache
.recordAddressFamily(address
);
843 catch (IllegalArgumentException ex
)
850 public synchronized boolean addRoute(String address
, int prefixLength
)
854 mCache
.addRoute(address
, prefixLength
);
856 catch (IllegalArgumentException ex
)
863 public synchronized boolean addSearchDomain(String domain
)
867 mBuilder
.addSearchDomain(domain
);
869 catch (IllegalArgumentException ex
)
876 public synchronized boolean setMtu(int mtu
)
882 catch (IllegalArgumentException ex
)
889 private synchronized ParcelFileDescriptor
establishIntern()
891 ParcelFileDescriptor fd
;
894 mCache
.applyData(mBuilder
);
895 fd
= mBuilder
.establish();
903 ex
.printStackTrace();
910 /* now that the TUN device is created we don't need the current
911 * builder anymore, but we might need another when reestablishing */
912 mBuilder
= createBuilder(mProfile
.getName());
913 mEstablishedCache
= mCache
;
914 mCache
= new BuilderCache(mProfile
);
918 public synchronized int establish()
920 ParcelFileDescriptor fd
= establishIntern();
921 return fd
!= null ? fd
.detachFd() : -1;
924 @TargetApi(Build
.VERSION_CODES
.LOLLIPOP
)
925 public synchronized void establishBlocking()
927 /* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
928 mCache
.addAddress("172.16.252.1", 32);
929 mCache
.addAddress("fd00::fd02:1", 128);
930 mCache
.addRoute("0.0.0.0", 0);
931 mCache
.addRoute("::", 0);
932 /* use blocking mode to simplify packet dropping */
933 mBuilder
.setBlocking(true);
934 ParcelFileDescriptor fd
= establishIntern();
941 public synchronized void closeBlocking()
946 public synchronized int establishNoDns()
948 ParcelFileDescriptor fd
;
950 if (mEstablishedCache
== null)
956 Builder builder
= createBuilder(mProfile
.getName());
957 mEstablishedCache
.applyData(builder
);
958 fd
= builder
.establish();
962 ex
.printStackTrace();
969 return fd
.detachFd();
972 private class PacketDropper
implements Runnable
974 private ParcelFileDescriptor mFd
;
975 private Thread mThread
;
977 public void start(ParcelFileDescriptor fd
)
980 mThread
= new Thread(this);
994 catch (InterruptedException e
)
998 catch (IOException e
)
1000 e
.printStackTrace();
1007 public synchronized void run()
1011 FileInputStream plain
= new FileInputStream(mFd
.getFileDescriptor());
1012 ByteBuffer packet
= ByteBuffer
.allocate(mCache
.mMtu
);
1014 { /* just read and ignore all data, regular read() is not properly interruptible */
1015 int len
= plain
.getChannel().read(packet
);
1023 catch (ClosedByInterruptException e
)
1025 /* regular interruption */
1029 e
.printStackTrace();
1036 * Cache non DNS related information so we can recreate the builder without
1037 * that information when reestablishing IKE_SAs
1039 public class BuilderCache
1041 private final List
<IPRange
> mAddresses
= new ArrayList
<>();
1042 private final List
<IPRange
> mRoutesIPv4
= new ArrayList
<>();
1043 private final List
<IPRange
> mRoutesIPv6
= new ArrayList
<>();
1044 private final IPRangeSet mIncludedSubnetsv4
= new IPRangeSet();
1045 private final IPRangeSet mIncludedSubnetsv6
= new IPRangeSet();
1046 private final IPRangeSet mExcludedSubnets
;
1047 private final int mSplitTunneling
;
1048 private final SelectedAppsHandling mAppHandling
;
1049 private final SortedSet
<String
> mSelectedApps
;
1051 private boolean mIPv4Seen
, mIPv6Seen
;
1053 public BuilderCache(VpnProfile profile
)
1055 IPRangeSet included
= IPRangeSet
.fromString(profile
.getIncludedSubnets());
1056 for (IPRange range
: included
)
1058 if (range
.getFrom() instanceof Inet4Address
)
1060 mIncludedSubnetsv4
.add(range
);
1062 else if (range
.getFrom() instanceof Inet6Address
)
1064 mIncludedSubnetsv6
.add(range
);
1067 mExcludedSubnets
= IPRangeSet
.fromString(profile
.getExcludedSubnets());
1068 Integer splitTunneling
= profile
.getSplitTunneling();
1069 mSplitTunneling
= splitTunneling
!= null ? splitTunneling
: 0;
1070 SelectedAppsHandling appHandling
= profile
.getSelectedAppsHandling();
1071 mSelectedApps
= profile
.getSelectedAppsSet();
1072 /* exclude our own app, otherwise the fetcher is blocked */
1073 switch (appHandling
)
1075 case SELECTED_APPS_DISABLE
:
1076 appHandling
= SelectedAppsHandling
.SELECTED_APPS_EXCLUDE
;
1077 mSelectedApps
.clear();
1079 case SELECTED_APPS_EXCLUDE
:
1080 mSelectedApps
.add(getPackageName());
1082 case SELECTED_APPS_ONLY
:
1083 mSelectedApps
.remove(getPackageName());
1086 mAppHandling
= appHandling
;
1088 /* set a default MTU, will be set by the daemon for regular interfaces */
1089 Integer mtu
= profile
.getMTU();
1090 mMtu
= mtu
== null ? Constants
.MTU_MAX
: mtu
;
1093 public void addAddress(String address
, int prefixLength
)
1097 mAddresses
.add(new IPRange(address
, prefixLength
));
1098 recordAddressFamily(address
);
1100 catch (UnknownHostException ex
)
1102 ex
.printStackTrace();
1106 public void addRoute(String address
, int prefixLength
)
1110 if (isIPv6(address
))
1112 mRoutesIPv6
.add(new IPRange(address
, prefixLength
));
1116 mRoutesIPv4
.add(new IPRange(address
, prefixLength
));
1119 catch (UnknownHostException ex
)
1121 ex
.printStackTrace();
1125 public void setMtu(int mtu
)
1130 public void recordAddressFamily(String address
)
1134 if (isIPv6(address
))
1143 catch (UnknownHostException ex
)
1145 ex
.printStackTrace();
1149 @TargetApi(Build
.VERSION_CODES
.LOLLIPOP
)
1150 public void applyData(VpnService
.Builder builder
)
1152 for (IPRange address
: mAddresses
)
1154 builder
.addAddress(address
.getFrom(), address
.getPrefix());
1156 /* add routes depending on whether split tunneling is allowed or not,
1157 * that is, whether we have to handle and block non-VPN traffic */
1158 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV4
) == 0)
1161 { /* split tunneling is used depending on the routes and configuration */
1162 IPRangeSet ranges
= new IPRangeSet();
1163 if (mIncludedSubnetsv4
.size() > 0)
1165 ranges
.add(mIncludedSubnetsv4
);
1169 ranges
.addAll(mRoutesIPv4
);
1171 ranges
.remove(mExcludedSubnets
);
1172 for (IPRange subnet
: ranges
.subnets())
1176 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1178 catch (IllegalArgumentException e
)
1179 { /* some Android versions don't seem to like multicast addresses here,
1180 * ignore it for now */
1181 if (!subnet
.getFrom().isMulticastAddress())
1188 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1189 { /* allow traffic that would otherwise be blocked to bypass the VPN */
1190 builder
.allowFamily(OsConstants
.AF_INET
);
1194 { /* only needed if we've seen any addresses. otherwise, traffic
1195 * is blocked by default (we also install no routes in that case) */
1196 builder
.addRoute("0.0.0.0", 0);
1198 /* same thing for IPv6 */
1199 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV6
) == 0)
1203 IPRangeSet ranges
= new IPRangeSet();
1204 if (mIncludedSubnetsv6
.size() > 0)
1206 ranges
.add(mIncludedSubnetsv6
);
1210 ranges
.addAll(mRoutesIPv6
);
1212 ranges
.remove(mExcludedSubnets
);
1213 for (IPRange subnet
: ranges
.subnets())
1217 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1219 catch (IllegalArgumentException e
)
1221 if (!subnet
.getFrom().isMulticastAddress())
1228 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1230 builder
.allowFamily(OsConstants
.AF_INET6
);
1235 builder
.addRoute("::", 0);
1237 /* apply selected applications */
1238 if (mSelectedApps
.size() > 0 &&
1239 Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1241 switch (mAppHandling
)
1243 case SELECTED_APPS_EXCLUDE
:
1244 for (String app
: mSelectedApps
)
1248 builder
.addDisallowedApplication(app
);
1250 catch (PackageManager
.NameNotFoundException e
)
1252 // possible if not configured via GUI or app was uninstalled
1256 case SELECTED_APPS_ONLY
:
1257 for (String app
: mSelectedApps
)
1261 builder
.addAllowedApplication(app
);
1263 catch (PackageManager
.NameNotFoundException e
)
1265 // possible if not configured via GUI or app was uninstalled
1273 builder
.setMtu(mMtu
);
1276 private boolean isIPv6(String address
) throws UnknownHostException
1278 InetAddress addr
= InetAddress
.getByName(address
);
1279 if (addr
instanceof Inet4Address
)
1283 else if (addr
instanceof Inet6Address
)
1292 * Function called via JNI to determine information about the Android version.
1294 private static String
getAndroidVersion()
1296 String version
= "Android " + Build
.VERSION
.RELEASE
+ " - " + Build
.DISPLAY
;
1297 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.M
)
1299 version
+= "/" + Build
.VERSION
.SECURITY_PATCH
;
1305 * Function called via JNI to determine information about the device.
1307 private static String
getDeviceString()
1309 return Build
.MODEL
+ " - " + Build
.BRAND
+ "/" + Build
.PRODUCT
+ "/" + Build
.MANUFACTURER
;