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.rsa_pss", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_RSA_PSS
) != 0);
305 writer
.setValue("global.crl", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_DISABLE_CRL
) == 0);
306 writer
.setValue("global.ocsp", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_DISABLE_OCSP
) == 0);
307 writer
.setValue("connection.type", mCurrentProfile
.getVpnType().getIdentifier());
308 writer
.setValue("connection.server", mCurrentProfile
.getGateway());
309 writer
.setValue("connection.port", mCurrentProfile
.getPort());
310 writer
.setValue("connection.username", mCurrentProfile
.getUsername());
311 writer
.setValue("connection.password", mCurrentProfile
.getPassword());
312 writer
.setValue("connection.local_id", mCurrentProfile
.getLocalId());
313 writer
.setValue("connection.remote_id", mCurrentProfile
.getRemoteId());
314 writer
.setValue("connection.certreq", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_SUPPRESS_CERT_REQS
) == 0);
315 writer
.setValue("connection.strict_revocation", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_STRICT_REVOCATION
) != 0);
316 writer
.setValue("connection.ike_proposal", mCurrentProfile
.getIkeProposal());
317 writer
.setValue("connection.esp_proposal", mCurrentProfile
.getEspProposal());
318 initiate(writer
.serialize());
322 Log
.e(TAG
, "failed to start charon");
323 setError(ErrorState
.GENERIC_ERROR
);
324 setState(State
.DISABLED
);
325 mCurrentProfile
= null;
329 catch (InterruptedException ex
)
331 stopCurrentConnection();
332 setState(State
.DISABLED
);
339 * Stop any existing connection by deinitializing charon.
341 private void stopCurrentConnection()
345 if (mNextProfile
!= null && Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
347 mBuilderAdapter
.setProfile(mNextProfile
);
348 mBuilderAdapter
.establishBlocking();
351 if (mCurrentProfile
!= null)
353 setState(State
.DISCONNECTING
);
354 mIsDisconnecting
= true;
355 SimpleFetcher
.disable();
356 deinitializeCharon();
357 Log
.i(TAG
, "charon stopped");
358 mCurrentProfile
= null;
359 if (mNextProfile
== null)
360 { /* only do this if we are not connecting to another profile */
361 removeNotification();
362 mBuilderAdapter
.closeBlocking();
369 * Add a permanent notification while we are connected to avoid the service getting killed by
370 * the system when low on memory.
372 private void addNotification()
374 mHandler
.post(new Runnable()
379 mShowNotification
= true;
380 startForeground(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
386 * Remove the permanent notification.
388 private void removeNotification()
390 mHandler
.post(new Runnable()
395 mShowNotification
= false;
396 stopForeground(true);
402 * Create a notification channel for Android 8+
404 private void createNotificationChannel()
406 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.O
)
408 NotificationChannel channel
;
409 channel
= new NotificationChannel(NOTIFICATION_CHANNEL
, getString(R
.string
.permanent_notification_name
),
410 NotificationManager
.IMPORTANCE_LOW
);
411 channel
.setDescription(getString(R
.string
.permanent_notification_description
));
412 channel
.setLockscreenVisibility(Notification
.VISIBILITY_SECRET
);
413 channel
.setShowBadge(false);
414 NotificationManager notificationManager
= getSystemService(NotificationManager
.class);
415 notificationManager
.createNotificationChannel(channel
);
421 * Build a notification matching the current state
423 private Notification
buildNotification(boolean publicVersion
)
425 VpnProfile profile
= mService
.getProfile();
426 State state
= mService
.getState();
427 ErrorState error
= mService
.getErrorState();
429 boolean add_action
= false;
433 name
= profile
.getName();
435 NotificationCompat
.Builder builder
= new NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL
)
436 .setSmallIcon(R
.drawable
.ic_notification
)
437 .setCategory(NotificationCompat
.CATEGORY_SERVICE
)
438 .setVisibility(publicVersion ? NotificationCompat
.VISIBILITY_PUBLIC
439 : NotificationCompat
.VISIBILITY_PRIVATE
);
440 int s
= R
.string
.state_disabled
;
441 if (error
!= ErrorState
.NO_ERROR
)
443 s
= mService
.getErrorText();
444 builder
.setSmallIcon(R
.drawable
.ic_notification_warning
);
445 builder
.setColor(ContextCompat
.getColor(this, R
.color
.error_text
));
447 if (!publicVersion
&& profile
!= null)
449 int retry
= mService
.getRetryIn();
452 builder
.setContentText(getResources().getQuantityString(R
.plurals
.retry_in
, retry
, retry
));
453 builder
.setProgress(mService
.getRetryTimeout(), retry
, false);
456 Intent intent
= new Intent(getApplicationContext(), VpnProfileControlActivity
.class);
457 intent
.addFlags(Intent
.FLAG_ACTIVITY_NEW_TASK
);
458 intent
.setAction(VpnProfileControlActivity
.START_PROFILE
);
459 intent
.putExtra(VpnProfileControlActivity
.EXTRA_VPN_PROFILE_ID
, profile
.getUUID().toString());
460 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
461 PendingIntent
.FLAG_UPDATE_CURRENT
);
462 builder
.addAction(R
.drawable
.ic_notification_connecting
, getString(R
.string
.retry
), pending
);
468 builder
.setProgress(0, 0, false);
473 s
= R
.string
.state_connecting
;
474 builder
.setSmallIcon(R
.drawable
.ic_notification_connecting
);
475 builder
.setColor(ContextCompat
.getColor(this, R
.color
.warning_text
));
479 s
= R
.string
.state_connected
;
480 builder
.setColor(ContextCompat
.getColor(this, R
.color
.success_text
));
481 builder
.setUsesChronometer(true);
485 s
= R
.string
.state_disconnecting
;
489 builder
.setContentTitle(getString(s
));
494 Intent intent
= new Intent(getApplicationContext(), VpnProfileControlActivity
.class);
495 intent
.setAction(VpnProfileControlActivity
.DISCONNECT
);
496 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
497 PendingIntent
.FLAG_UPDATE_CURRENT
);
498 builder
.addAction(R
.drawable
.ic_notification_disconnect
, getString(R
.string
.disconnect
), pending
);
500 if (error
== ErrorState
.NO_ERROR
)
502 builder
.setContentText(name
);
504 builder
.setPublicVersion(buildNotification(true));
507 Intent intent
= new Intent(getApplicationContext(), MainActivity
.class);
508 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
509 PendingIntent
.FLAG_UPDATE_CURRENT
);
510 builder
.setContentIntent(pending
);
511 return builder
.build();
515 public void stateChanged() {
516 if (mShowNotification
)
518 NotificationManager manager
= (NotificationManager
) getSystemService(Context
.NOTIFICATION_SERVICE
);
519 manager
.notify(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
524 * Notify the state service about a new connection attempt.
525 * Called by the handler thread.
527 * @param profile currently active VPN profile
529 private void startConnection(VpnProfile profile
)
531 synchronized (mServiceLock
)
533 if (mService
!= null)
535 mService
.startConnection(profile
);
541 * Update the current VPN state on the state service. Called by the handler
542 * thread and any of charon's threads.
544 * @param state current state
546 private void setState(State state
)
548 synchronized (mServiceLock
)
550 if (mService
!= null)
552 mService
.setState(state
);
558 * Set an error on the state service. Called by the handler thread and any
559 * of charon's threads.
561 * @param error error state
563 private void setError(ErrorState error
)
565 synchronized (mServiceLock
)
567 if (mService
!= null)
569 mService
.setError(error
);
575 * Set the IMC state on the state service. Called by the handler thread and
576 * any of charon's threads.
578 * @param state IMC state
580 private void setImcState(ImcState state
)
582 synchronized (mServiceLock
)
584 if (mService
!= null)
586 mService
.setImcState(state
);
592 * Set an error on the state service. Called by the handler thread and any
593 * of charon's threads.
595 * @param error error state
597 private void setErrorDisconnect(ErrorState error
)
599 synchronized (mServiceLock
)
601 if (mService
!= null)
603 if (!mIsDisconnecting
)
605 mService
.setError(error
);
612 * Updates the state of the current connection.
613 * Called via JNI by different threads (but not concurrently).
615 * @param status new state
617 public void updateStatus(int status
)
621 case STATE_CHILD_SA_DOWN
:
622 if (!mIsDisconnecting
)
624 setState(State
.CONNECTING
);
627 case STATE_CHILD_SA_UP
:
628 setState(State
.CONNECTED
);
630 case STATE_AUTH_ERROR
:
631 setErrorDisconnect(ErrorState
.AUTH_FAILED
);
633 case STATE_PEER_AUTH_ERROR
:
634 setErrorDisconnect(ErrorState
.PEER_AUTH_FAILED
);
636 case STATE_LOOKUP_ERROR
:
637 setErrorDisconnect(ErrorState
.LOOKUP_FAILED
);
639 case STATE_UNREACHABLE_ERROR
:
640 setErrorDisconnect(ErrorState
.UNREACHABLE
);
642 case STATE_CERTIFICATE_UNAVAILABLE
:
643 setErrorDisconnect(ErrorState
.CERTIFICATE_UNAVAILABLE
);
645 case STATE_GENERIC_ERROR
:
646 setErrorDisconnect(ErrorState
.GENERIC_ERROR
);
649 Log
.e(TAG
, "Unknown status code received");
655 * Updates the IMC state of the current connection.
656 * Called via JNI by different threads (but not concurrently).
658 * @param value new state
660 public void updateImcState(int value
)
662 ImcState state
= ImcState
.fromValue(value
);
670 * Add a remediation instruction to the VPN state service.
671 * Called via JNI by different threads (but not concurrently).
673 * @param xml XML text
675 public void addRemediationInstruction(String xml
)
677 for (RemediationInstruction instruction
: RemediationInstruction
.fromXml(xml
))
679 synchronized (mServiceLock
)
681 if (mService
!= null)
683 mService
.addRemediationInstruction(instruction
);
690 * Function called via JNI to generate a list of DER encoded CA certificates
693 * @return a list of DER encoded CA certificates
695 private byte[][] getTrustedCertificates()
697 ArrayList
<byte[]> certs
= new ArrayList
<byte[]>();
698 TrustedCertificateManager certman
= TrustedCertificateManager
.getInstance().load();
701 String alias
= this.mCurrentCertificateAlias
;
704 X509Certificate cert
= certman
.getCACertificateFromAlias(alias
);
709 certs
.add(cert
.getEncoded());
713 for (X509Certificate cert
: certman
.getAllCACertificates().values())
715 certs
.add(cert
.getEncoded());
719 catch (CertificateEncodingException e
)
724 return certs
.toArray(new byte[certs
.size()][]);
728 * Function called via JNI to get a list containing the DER encoded certificates
729 * of the user selected certificate chain (beginning with the user certificate).
731 * Since this method is called from a thread of charon's thread pool we are safe
732 * to call methods on KeyChain directly.
734 * @return list containing the certificates (first element is the user certificate)
735 * @throws InterruptedException
736 * @throws KeyChainException
737 * @throws CertificateEncodingException
739 private byte[][] getUserCertificate() throws KeyChainException
, InterruptedException
, CertificateEncodingException
741 ArrayList
<byte[]> encodings
= new ArrayList
<byte[]>();
742 X509Certificate
[] chain
= KeyChain
.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias
);
743 if (chain
== null || chain
.length
== 0)
747 for (X509Certificate cert
: chain
)
749 encodings
.add(cert
.getEncoded());
751 return encodings
.toArray(new byte[encodings
.size()][]);
755 * Function called via JNI to get the private key the user selected.
757 * Since this method is called from a thread of charon's thread pool we are safe
758 * to call methods on KeyChain directly.
760 * @return the private key
761 * @throws InterruptedException
762 * @throws KeyChainException
764 private PrivateKey
getUserKey() throws KeyChainException
, InterruptedException
766 return KeyChain
.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias
);
770 * Initialization of charon, provided by libandroidbridge.so
772 * @param builder BuilderAdapter for this connection
773 * @param logfile absolute path to the logfile
774 * @param appdir absolute path to the data directory of the app
775 * @param byod enable BYOD features
776 * @return TRUE if initialization was successful
778 public native boolean initializeCharon(BuilderAdapter builder
, String logfile
, String appdir
, boolean byod
);
781 * Deinitialize charon, provided by libandroidbridge.so
783 public native void deinitializeCharon();
786 * Initiate VPN, provided by libandroidbridge.so
788 public native void initiate(String config
);
791 * Adapter for VpnService.Builder which is used to access it safely via JNI.
792 * There is a corresponding C object to access it from native code.
794 public class BuilderAdapter
796 private VpnProfile mProfile
;
797 private VpnService
.Builder mBuilder
;
798 private BuilderCache mCache
;
799 private BuilderCache mEstablishedCache
;
800 private PacketDropper mDropper
= new PacketDropper();
802 public synchronized void setProfile(VpnProfile profile
)
805 mBuilder
= createBuilder(mProfile
.getName());
806 mCache
= new BuilderCache(mProfile
);
809 private VpnService
.Builder
createBuilder(String name
)
811 VpnService
.Builder builder
= new CharonVpnService
.Builder();
812 builder
.setSession(name
);
814 /* even though the option displayed in the system dialog says "Configure"
815 * we just use our main Activity */
816 Context context
= getApplicationContext();
817 Intent intent
= new Intent(context
, MainActivity
.class);
818 PendingIntent pending
= PendingIntent
.getActivity(context
, 0, intent
,
819 PendingIntent
.FLAG_UPDATE_CURRENT
);
820 builder
.setConfigureIntent(pending
);
824 public synchronized boolean addAddress(String address
, int prefixLength
)
828 mCache
.addAddress(address
, prefixLength
);
830 catch (IllegalArgumentException ex
)
837 public synchronized boolean addDnsServer(String address
)
841 mBuilder
.addDnsServer(address
);
842 mCache
.recordAddressFamily(address
);
844 catch (IllegalArgumentException ex
)
851 public synchronized boolean addRoute(String address
, int prefixLength
)
855 mCache
.addRoute(address
, prefixLength
);
857 catch (IllegalArgumentException ex
)
864 public synchronized boolean addSearchDomain(String domain
)
868 mBuilder
.addSearchDomain(domain
);
870 catch (IllegalArgumentException ex
)
877 public synchronized boolean setMtu(int mtu
)
883 catch (IllegalArgumentException ex
)
890 private synchronized ParcelFileDescriptor
establishIntern()
892 ParcelFileDescriptor fd
;
895 mCache
.applyData(mBuilder
);
896 fd
= mBuilder
.establish();
904 ex
.printStackTrace();
911 /* now that the TUN device is created we don't need the current
912 * builder anymore, but we might need another when reestablishing */
913 mBuilder
= createBuilder(mProfile
.getName());
914 mEstablishedCache
= mCache
;
915 mCache
= new BuilderCache(mProfile
);
919 public synchronized int establish()
921 ParcelFileDescriptor fd
= establishIntern();
922 return fd
!= null ? fd
.detachFd() : -1;
925 @TargetApi(Build
.VERSION_CODES
.LOLLIPOP
)
926 public synchronized void establishBlocking()
928 /* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
929 mCache
.addAddress("172.16.252.1", 32);
930 mCache
.addAddress("fd00::fd02:1", 128);
931 mCache
.addRoute("0.0.0.0", 0);
932 mCache
.addRoute("::", 0);
933 /* use blocking mode to simplify packet dropping */
934 mBuilder
.setBlocking(true);
935 ParcelFileDescriptor fd
= establishIntern();
942 public synchronized void closeBlocking()
947 public synchronized int establishNoDns()
949 ParcelFileDescriptor fd
;
951 if (mEstablishedCache
== null)
957 Builder builder
= createBuilder(mProfile
.getName());
958 mEstablishedCache
.applyData(builder
);
959 fd
= builder
.establish();
963 ex
.printStackTrace();
970 return fd
.detachFd();
973 private class PacketDropper
implements Runnable
975 private ParcelFileDescriptor mFd
;
976 private Thread mThread
;
978 public void start(ParcelFileDescriptor fd
)
981 mThread
= new Thread(this);
995 catch (InterruptedException e
)
999 catch (IOException e
)
1001 e
.printStackTrace();
1008 public synchronized void run()
1012 FileInputStream plain
= new FileInputStream(mFd
.getFileDescriptor());
1013 ByteBuffer packet
= ByteBuffer
.allocate(mCache
.mMtu
);
1015 { /* just read and ignore all data, regular read() is not properly interruptible */
1016 int len
= plain
.getChannel().read(packet
);
1024 catch (ClosedByInterruptException e
)
1026 /* regular interruption */
1030 e
.printStackTrace();
1037 * Cache non DNS related information so we can recreate the builder without
1038 * that information when reestablishing IKE_SAs
1040 public class BuilderCache
1042 private final List
<IPRange
> mAddresses
= new ArrayList
<>();
1043 private final List
<IPRange
> mRoutesIPv4
= new ArrayList
<>();
1044 private final List
<IPRange
> mRoutesIPv6
= new ArrayList
<>();
1045 private final IPRangeSet mIncludedSubnetsv4
= new IPRangeSet();
1046 private final IPRangeSet mIncludedSubnetsv6
= new IPRangeSet();
1047 private final IPRangeSet mExcludedSubnets
;
1048 private final int mSplitTunneling
;
1049 private final SelectedAppsHandling mAppHandling
;
1050 private final SortedSet
<String
> mSelectedApps
;
1052 private boolean mIPv4Seen
, mIPv6Seen
;
1054 public BuilderCache(VpnProfile profile
)
1056 IPRangeSet included
= IPRangeSet
.fromString(profile
.getIncludedSubnets());
1057 for (IPRange range
: included
)
1059 if (range
.getFrom() instanceof Inet4Address
)
1061 mIncludedSubnetsv4
.add(range
);
1063 else if (range
.getFrom() instanceof Inet6Address
)
1065 mIncludedSubnetsv6
.add(range
);
1068 mExcludedSubnets
= IPRangeSet
.fromString(profile
.getExcludedSubnets());
1069 Integer splitTunneling
= profile
.getSplitTunneling();
1070 mSplitTunneling
= splitTunneling
!= null ? splitTunneling
: 0;
1071 SelectedAppsHandling appHandling
= profile
.getSelectedAppsHandling();
1072 mSelectedApps
= profile
.getSelectedAppsSet();
1073 /* exclude our own app, otherwise the fetcher is blocked */
1074 switch (appHandling
)
1076 case SELECTED_APPS_DISABLE
:
1077 appHandling
= SelectedAppsHandling
.SELECTED_APPS_EXCLUDE
;
1078 mSelectedApps
.clear();
1080 case SELECTED_APPS_EXCLUDE
:
1081 mSelectedApps
.add(getPackageName());
1083 case SELECTED_APPS_ONLY
:
1084 mSelectedApps
.remove(getPackageName());
1087 mAppHandling
= appHandling
;
1089 /* set a default MTU, will be set by the daemon for regular interfaces */
1090 Integer mtu
= profile
.getMTU();
1091 mMtu
= mtu
== null ? Constants
.MTU_MAX
: mtu
;
1094 public void addAddress(String address
, int prefixLength
)
1098 mAddresses
.add(new IPRange(address
, prefixLength
));
1099 recordAddressFamily(address
);
1101 catch (UnknownHostException ex
)
1103 ex
.printStackTrace();
1107 public void addRoute(String address
, int prefixLength
)
1111 if (isIPv6(address
))
1113 mRoutesIPv6
.add(new IPRange(address
, prefixLength
));
1117 mRoutesIPv4
.add(new IPRange(address
, prefixLength
));
1120 catch (UnknownHostException ex
)
1122 ex
.printStackTrace();
1126 public void setMtu(int mtu
)
1131 public void recordAddressFamily(String address
)
1135 if (isIPv6(address
))
1144 catch (UnknownHostException ex
)
1146 ex
.printStackTrace();
1150 @TargetApi(Build
.VERSION_CODES
.LOLLIPOP
)
1151 public void applyData(VpnService
.Builder builder
)
1153 for (IPRange address
: mAddresses
)
1155 builder
.addAddress(address
.getFrom(), address
.getPrefix());
1157 /* add routes depending on whether split tunneling is allowed or not,
1158 * that is, whether we have to handle and block non-VPN traffic */
1159 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV4
) == 0)
1162 { /* split tunneling is used depending on the routes and configuration */
1163 IPRangeSet ranges
= new IPRangeSet();
1164 if (mIncludedSubnetsv4
.size() > 0)
1166 ranges
.add(mIncludedSubnetsv4
);
1170 ranges
.addAll(mRoutesIPv4
);
1172 ranges
.remove(mExcludedSubnets
);
1173 for (IPRange subnet
: ranges
.subnets())
1177 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1179 catch (IllegalArgumentException e
)
1180 { /* some Android versions don't seem to like multicast addresses here,
1181 * ignore it for now */
1182 if (!subnet
.getFrom().isMulticastAddress())
1189 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1190 { /* allow traffic that would otherwise be blocked to bypass the VPN */
1191 builder
.allowFamily(OsConstants
.AF_INET
);
1195 { /* only needed if we've seen any addresses. otherwise, traffic
1196 * is blocked by default (we also install no routes in that case) */
1197 builder
.addRoute("0.0.0.0", 0);
1199 /* same thing for IPv6 */
1200 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV6
) == 0)
1204 IPRangeSet ranges
= new IPRangeSet();
1205 if (mIncludedSubnetsv6
.size() > 0)
1207 ranges
.add(mIncludedSubnetsv6
);
1211 ranges
.addAll(mRoutesIPv6
);
1213 ranges
.remove(mExcludedSubnets
);
1214 for (IPRange subnet
: ranges
.subnets())
1218 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1220 catch (IllegalArgumentException e
)
1222 if (!subnet
.getFrom().isMulticastAddress())
1229 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1231 builder
.allowFamily(OsConstants
.AF_INET6
);
1236 builder
.addRoute("::", 0);
1238 /* apply selected applications */
1239 if (mSelectedApps
.size() > 0 &&
1240 Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1242 switch (mAppHandling
)
1244 case SELECTED_APPS_EXCLUDE
:
1245 for (String app
: mSelectedApps
)
1249 builder
.addDisallowedApplication(app
);
1251 catch (PackageManager
.NameNotFoundException e
)
1253 // possible if not configured via GUI or app was uninstalled
1257 case SELECTED_APPS_ONLY
:
1258 for (String app
: mSelectedApps
)
1262 builder
.addAllowedApplication(app
);
1264 catch (PackageManager
.NameNotFoundException e
)
1266 // possible if not configured via GUI or app was uninstalled
1274 builder
.setMtu(mMtu
);
1277 private boolean isIPv6(String address
) throws UnknownHostException
1279 InetAddress addr
= InetAddress
.getByName(address
);
1280 if (addr
instanceof Inet4Address
)
1284 else if (addr
instanceof Inet6Address
)
1293 * Function called via JNI to determine information about the Android version.
1295 private static String
getAndroidVersion()
1297 String version
= "Android " + Build
.VERSION
.RELEASE
+ " - " + Build
.DISPLAY
;
1298 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.M
)
1300 version
+= "/" + Build
.VERSION
.SECURITY_PATCH
;
1306 * Function called via JNI to determine information about the device.
1308 private static String
getDeviceString()
1310 return Build
.MODEL
+ " - " + Build
.BRAND
+ "/" + Build
.PRODUCT
+ "/" + Build
.MANUFACTURER
;