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;
287 mBuilderAdapter
.setProfile(mCurrentProfile
);
288 if (initializeCharon(mBuilderAdapter
, mLogFile
, mAppDir
, mCurrentProfile
.getVpnType().has(VpnTypeFeature
.BYOD
)))
290 Log
.i(TAG
, "charon started");
292 if (mCurrentProfile
.getVpnType().has(VpnTypeFeature
.USER_PASS
) &&
293 mCurrentProfile
.getPassword() == null)
294 { /* this can happen if Always-on VPN is enabled with an incomplete profile */
295 setError(ErrorState
.PASSWORD_MISSING
);
299 SettingsWriter writer
= new SettingsWriter();
300 writer
.setValue("global.language", Locale
.getDefault().getLanguage());
301 writer
.setValue("global.mtu", mCurrentProfile
.getMTU());
302 writer
.setValue("global.nat_keepalive", mCurrentProfile
.getNATKeepAlive());
303 writer
.setValue("global.crl", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_DISABLE_CRL
) == 0);
304 writer
.setValue("global.ocsp", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_DISABLE_OCSP
) == 0);
305 writer
.setValue("connection.type", mCurrentProfile
.getVpnType().getIdentifier());
306 writer
.setValue("connection.server", mCurrentProfile
.getGateway());
307 writer
.setValue("connection.port", mCurrentProfile
.getPort());
308 writer
.setValue("connection.username", mCurrentProfile
.getUsername());
309 writer
.setValue("connection.password", mCurrentProfile
.getPassword());
310 writer
.setValue("connection.local_id", mCurrentProfile
.getLocalId());
311 writer
.setValue("connection.remote_id", mCurrentProfile
.getRemoteId());
312 writer
.setValue("connection.certreq", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_SUPPRESS_CERT_REQS
) == 0);
313 writer
.setValue("connection.strict_revocation", (mCurrentProfile
.getFlags() & VpnProfile
.FLAGS_STRICT_REVOCATION
) != 0);
314 writer
.setValue("connection.ike_proposal", mCurrentProfile
.getIkeProposal());
315 writer
.setValue("connection.esp_proposal", mCurrentProfile
.getEspProposal());
316 initiate(writer
.serialize());
320 Log
.e(TAG
, "failed to start charon");
321 setError(ErrorState
.GENERIC_ERROR
);
322 setState(State
.DISABLED
);
323 mCurrentProfile
= null;
327 catch (InterruptedException ex
)
329 stopCurrentConnection();
330 setState(State
.DISABLED
);
337 * Stop any existing connection by deinitializing charon.
339 private void stopCurrentConnection()
343 if (mNextProfile
!= null && Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
345 mBuilderAdapter
.setProfile(mNextProfile
);
346 mBuilderAdapter
.establishBlocking();
349 if (mCurrentProfile
!= null)
351 setState(State
.DISCONNECTING
);
352 mIsDisconnecting
= true;
353 deinitializeCharon();
354 Log
.i(TAG
, "charon stopped");
355 mCurrentProfile
= null;
356 if (mNextProfile
== null)
357 { /* only do this if we are not connecting to another profile */
358 removeNotification();
359 mBuilderAdapter
.closeBlocking();
366 * Add a permanent notification while we are connected to avoid the service getting killed by
367 * the system when low on memory.
369 private void addNotification()
371 mHandler
.post(new Runnable()
376 mShowNotification
= true;
377 startForeground(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
383 * Remove the permanent notification.
385 private void removeNotification()
387 mHandler
.post(new Runnable()
392 mShowNotification
= false;
393 stopForeground(true);
399 * Create a notification channel for Android 8+
401 private void createNotificationChannel()
403 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.O
)
405 NotificationChannel channel
;
406 channel
= new NotificationChannel(NOTIFICATION_CHANNEL
, getString(R
.string
.permanent_notification_name
),
407 NotificationManager
.IMPORTANCE_LOW
);
408 channel
.setDescription(getString(R
.string
.permanent_notification_description
));
409 channel
.setLockscreenVisibility(Notification
.VISIBILITY_SECRET
);
410 channel
.setShowBadge(false);
411 NotificationManager notificationManager
= getSystemService(NotificationManager
.class);
412 notificationManager
.createNotificationChannel(channel
);
418 * Build a notification matching the current state
420 private Notification
buildNotification(boolean publicVersion
)
422 VpnProfile profile
= mService
.getProfile();
423 State state
= mService
.getState();
424 ErrorState error
= mService
.getErrorState();
426 boolean add_action
= false;
430 name
= profile
.getName();
432 NotificationCompat
.Builder builder
= new NotificationCompat
.Builder(this, NOTIFICATION_CHANNEL
)
433 .setSmallIcon(R
.drawable
.ic_notification
)
434 .setCategory(NotificationCompat
.CATEGORY_SERVICE
)
435 .setVisibility(publicVersion ? NotificationCompat
.VISIBILITY_PUBLIC
436 : NotificationCompat
.VISIBILITY_PRIVATE
);
437 int s
= R
.string
.state_disabled
;
438 if (error
!= ErrorState
.NO_ERROR
)
440 s
= mService
.getErrorText();
441 builder
.setSmallIcon(R
.drawable
.ic_notification_warning
);
442 builder
.setColor(ContextCompat
.getColor(this, R
.color
.error_text
));
444 if (!publicVersion
&& profile
!= null)
446 int retry
= mService
.getRetryIn();
449 builder
.setContentText(getResources().getQuantityString(R
.plurals
.retry_in
, retry
, retry
));
450 builder
.setProgress(mService
.getRetryTimeout(), retry
, false);
453 Intent intent
= new Intent(getApplicationContext(), VpnProfileControlActivity
.class);
454 intent
.addFlags(Intent
.FLAG_ACTIVITY_NEW_TASK
);
455 intent
.setAction(VpnProfileControlActivity
.START_PROFILE
);
456 intent
.putExtra(VpnProfileControlActivity
.EXTRA_VPN_PROFILE_ID
, profile
.getUUID().toString());
457 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
458 PendingIntent
.FLAG_UPDATE_CURRENT
);
459 builder
.addAction(R
.drawable
.ic_notification_connecting
, getString(R
.string
.retry
), pending
);
465 builder
.setProgress(0, 0, false);
470 s
= R
.string
.state_connecting
;
471 builder
.setSmallIcon(R
.drawable
.ic_notification_connecting
);
472 builder
.setColor(ContextCompat
.getColor(this, R
.color
.warning_text
));
476 s
= R
.string
.state_connected
;
477 builder
.setColor(ContextCompat
.getColor(this, R
.color
.success_text
));
478 builder
.setUsesChronometer(true);
482 s
= R
.string
.state_disconnecting
;
486 builder
.setContentTitle(getString(s
));
491 Intent intent
= new Intent(getApplicationContext(), VpnProfileControlActivity
.class);
492 intent
.setAction(VpnProfileControlActivity
.DISCONNECT
);
493 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
494 PendingIntent
.FLAG_UPDATE_CURRENT
);
495 builder
.addAction(R
.drawable
.ic_notification_disconnect
, getString(R
.string
.disconnect
), pending
);
497 if (error
== ErrorState
.NO_ERROR
)
499 builder
.setContentText(name
);
501 builder
.setPublicVersion(buildNotification(true));
504 Intent intent
= new Intent(getApplicationContext(), MainActivity
.class);
505 PendingIntent pending
= PendingIntent
.getActivity(getApplicationContext(), 0, intent
,
506 PendingIntent
.FLAG_UPDATE_CURRENT
);
507 builder
.setContentIntent(pending
);
508 return builder
.build();
512 public void stateChanged() {
513 if (mShowNotification
)
515 NotificationManager manager
= (NotificationManager
) getSystemService(Context
.NOTIFICATION_SERVICE
);
516 manager
.notify(VPN_STATE_NOTIFICATION_ID
, buildNotification(false));
521 * Notify the state service about a new connection attempt.
522 * Called by the handler thread.
524 * @param profile currently active VPN profile
526 private void startConnection(VpnProfile profile
)
528 synchronized (mServiceLock
)
530 if (mService
!= null)
532 mService
.startConnection(profile
);
538 * Update the current VPN state on the state service. Called by the handler
539 * thread and any of charon's threads.
541 * @param state current state
543 private void setState(State state
)
545 synchronized (mServiceLock
)
547 if (mService
!= null)
549 mService
.setState(state
);
555 * Set an error on the state service. Called by the handler thread and any
556 * of charon's threads.
558 * @param error error state
560 private void setError(ErrorState error
)
562 synchronized (mServiceLock
)
564 if (mService
!= null)
566 mService
.setError(error
);
572 * Set the IMC state on the state service. Called by the handler thread and
573 * any of charon's threads.
575 * @param state IMC state
577 private void setImcState(ImcState state
)
579 synchronized (mServiceLock
)
581 if (mService
!= null)
583 mService
.setImcState(state
);
589 * Set an error on the state service. Called by the handler thread and any
590 * of charon's threads.
592 * @param error error state
594 private void setErrorDisconnect(ErrorState error
)
596 synchronized (mServiceLock
)
598 if (mService
!= null)
600 if (!mIsDisconnecting
)
602 mService
.setError(error
);
609 * Updates the state of the current connection.
610 * Called via JNI by different threads (but not concurrently).
612 * @param status new state
614 public void updateStatus(int status
)
618 case STATE_CHILD_SA_DOWN
:
619 if (!mIsDisconnecting
)
621 setState(State
.CONNECTING
);
624 case STATE_CHILD_SA_UP
:
625 setState(State
.CONNECTED
);
627 case STATE_AUTH_ERROR
:
628 setErrorDisconnect(ErrorState
.AUTH_FAILED
);
630 case STATE_PEER_AUTH_ERROR
:
631 setErrorDisconnect(ErrorState
.PEER_AUTH_FAILED
);
633 case STATE_LOOKUP_ERROR
:
634 setErrorDisconnect(ErrorState
.LOOKUP_FAILED
);
636 case STATE_UNREACHABLE_ERROR
:
637 setErrorDisconnect(ErrorState
.UNREACHABLE
);
639 case STATE_CERTIFICATE_UNAVAILABLE
:
640 setErrorDisconnect(ErrorState
.CERTIFICATE_UNAVAILABLE
);
642 case STATE_GENERIC_ERROR
:
643 setErrorDisconnect(ErrorState
.GENERIC_ERROR
);
646 Log
.e(TAG
, "Unknown status code received");
652 * Updates the IMC state of the current connection.
653 * Called via JNI by different threads (but not concurrently).
655 * @param value new state
657 public void updateImcState(int value
)
659 ImcState state
= ImcState
.fromValue(value
);
667 * Add a remediation instruction to the VPN state service.
668 * Called via JNI by different threads (but not concurrently).
670 * @param xml XML text
672 public void addRemediationInstruction(String xml
)
674 for (RemediationInstruction instruction
: RemediationInstruction
.fromXml(xml
))
676 synchronized (mServiceLock
)
678 if (mService
!= null)
680 mService
.addRemediationInstruction(instruction
);
687 * Function called via JNI to generate a list of DER encoded CA certificates
690 * @return a list of DER encoded CA certificates
692 private byte[][] getTrustedCertificates()
694 ArrayList
<byte[]> certs
= new ArrayList
<byte[]>();
695 TrustedCertificateManager certman
= TrustedCertificateManager
.getInstance().load();
698 String alias
= this.mCurrentCertificateAlias
;
701 X509Certificate cert
= certman
.getCACertificateFromAlias(alias
);
706 certs
.add(cert
.getEncoded());
710 for (X509Certificate cert
: certman
.getAllCACertificates().values())
712 certs
.add(cert
.getEncoded());
716 catch (CertificateEncodingException e
)
721 return certs
.toArray(new byte[certs
.size()][]);
725 * Function called via JNI to get a list containing the DER encoded certificates
726 * of the user selected certificate chain (beginning with the user certificate).
728 * Since this method is called from a thread of charon's thread pool we are safe
729 * to call methods on KeyChain directly.
731 * @return list containing the certificates (first element is the user certificate)
732 * @throws InterruptedException
733 * @throws KeyChainException
734 * @throws CertificateEncodingException
736 private byte[][] getUserCertificate() throws KeyChainException
, InterruptedException
, CertificateEncodingException
738 ArrayList
<byte[]> encodings
= new ArrayList
<byte[]>();
739 X509Certificate
[] chain
= KeyChain
.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias
);
740 if (chain
== null || chain
.length
== 0)
744 for (X509Certificate cert
: chain
)
746 encodings
.add(cert
.getEncoded());
748 return encodings
.toArray(new byte[encodings
.size()][]);
752 * Function called via JNI to get the private key the user selected.
754 * Since this method is called from a thread of charon's thread pool we are safe
755 * to call methods on KeyChain directly.
757 * @return the private key
758 * @throws InterruptedException
759 * @throws KeyChainException
761 private PrivateKey
getUserKey() throws KeyChainException
, InterruptedException
763 return KeyChain
.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias
);
767 * Initialization of charon, provided by libandroidbridge.so
769 * @param builder BuilderAdapter for this connection
770 * @param logfile absolute path to the logfile
771 * @param appdir absolute path to the data directory of the app
772 * @param byod enable BYOD features
773 * @return TRUE if initialization was successful
775 public native boolean initializeCharon(BuilderAdapter builder
, String logfile
, String appdir
, boolean byod
);
778 * Deinitialize charon, provided by libandroidbridge.so
780 public native void deinitializeCharon();
783 * Initiate VPN, provided by libandroidbridge.so
785 public native void initiate(String config
);
788 * Adapter for VpnService.Builder which is used to access it safely via JNI.
789 * There is a corresponding C object to access it from native code.
791 public class BuilderAdapter
793 private VpnProfile mProfile
;
794 private VpnService
.Builder mBuilder
;
795 private BuilderCache mCache
;
796 private BuilderCache mEstablishedCache
;
797 private PacketDropper mDropper
= new PacketDropper();
799 public synchronized void setProfile(VpnProfile profile
)
802 mBuilder
= createBuilder(mProfile
.getName());
803 mCache
= new BuilderCache(mProfile
);
806 private VpnService
.Builder
createBuilder(String name
)
808 VpnService
.Builder builder
= new CharonVpnService
.Builder();
809 builder
.setSession(name
);
811 /* even though the option displayed in the system dialog says "Configure"
812 * we just use our main Activity */
813 Context context
= getApplicationContext();
814 Intent intent
= new Intent(context
, MainActivity
.class);
815 PendingIntent pending
= PendingIntent
.getActivity(context
, 0, intent
,
816 PendingIntent
.FLAG_UPDATE_CURRENT
);
817 builder
.setConfigureIntent(pending
);
821 public synchronized boolean addAddress(String address
, int prefixLength
)
825 mCache
.addAddress(address
, prefixLength
);
827 catch (IllegalArgumentException ex
)
834 public synchronized boolean addDnsServer(String address
)
838 mBuilder
.addDnsServer(address
);
839 mCache
.recordAddressFamily(address
);
841 catch (IllegalArgumentException ex
)
848 public synchronized boolean addRoute(String address
, int prefixLength
)
852 mCache
.addRoute(address
, prefixLength
);
854 catch (IllegalArgumentException ex
)
861 public synchronized boolean addSearchDomain(String domain
)
865 mBuilder
.addSearchDomain(domain
);
867 catch (IllegalArgumentException ex
)
874 public synchronized boolean setMtu(int mtu
)
880 catch (IllegalArgumentException ex
)
887 private synchronized ParcelFileDescriptor
establishIntern()
889 ParcelFileDescriptor fd
;
892 mCache
.applyData(mBuilder
);
893 fd
= mBuilder
.establish();
901 ex
.printStackTrace();
908 /* now that the TUN device is created we don't need the current
909 * builder anymore, but we might need another when reestablishing */
910 mBuilder
= createBuilder(mProfile
.getName());
911 mEstablishedCache
= mCache
;
912 mCache
= new BuilderCache(mProfile
);
916 public synchronized int establish()
918 ParcelFileDescriptor fd
= establishIntern();
919 return fd
!= null ? fd
.detachFd() : -1;
922 @TargetApi(Build
.VERSION_CODES
.LOLLIPOP
)
923 public synchronized void establishBlocking()
925 /* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
926 mCache
.addAddress("172.16.252.1", 32);
927 mCache
.addAddress("fd00::fd02:1", 128);
928 mCache
.addRoute("0.0.0.0", 0);
929 mCache
.addRoute("::", 0);
930 /* use blocking mode to simplify packet dropping */
931 mBuilder
.setBlocking(true);
932 ParcelFileDescriptor fd
= establishIntern();
939 public synchronized void closeBlocking()
944 public synchronized int establishNoDns()
946 ParcelFileDescriptor fd
;
948 if (mEstablishedCache
== null)
954 Builder builder
= createBuilder(mProfile
.getName());
955 mEstablishedCache
.applyData(builder
);
956 fd
= builder
.establish();
960 ex
.printStackTrace();
967 return fd
.detachFd();
970 private class PacketDropper
implements Runnable
972 private ParcelFileDescriptor mFd
;
973 private Thread mThread
;
975 public void start(ParcelFileDescriptor fd
)
978 mThread
= new Thread(this);
992 catch (InterruptedException e
)
996 catch (IOException e
)
1005 public synchronized void run()
1009 FileInputStream plain
= new FileInputStream(mFd
.getFileDescriptor());
1010 ByteBuffer packet
= ByteBuffer
.allocate(mCache
.mMtu
);
1012 { /* just read and ignore all data, regular read() is not properly interruptible */
1013 int len
= plain
.getChannel().read(packet
);
1021 catch (ClosedByInterruptException e
)
1023 /* regular interruption */
1027 e
.printStackTrace();
1034 * Cache non DNS related information so we can recreate the builder without
1035 * that information when reestablishing IKE_SAs
1037 public class BuilderCache
1039 private final List
<IPRange
> mAddresses
= new ArrayList
<>();
1040 private final List
<IPRange
> mRoutesIPv4
= new ArrayList
<>();
1041 private final List
<IPRange
> mRoutesIPv6
= new ArrayList
<>();
1042 private final IPRangeSet mIncludedSubnetsv4
= new IPRangeSet();
1043 private final IPRangeSet mIncludedSubnetsv6
= new IPRangeSet();
1044 private final IPRangeSet mExcludedSubnets
;
1045 private final int mSplitTunneling
;
1046 private final SelectedAppsHandling mAppHandling
;
1047 private final SortedSet
<String
> mSelectedApps
;
1049 private boolean mIPv4Seen
, mIPv6Seen
;
1051 public BuilderCache(VpnProfile profile
)
1053 IPRangeSet included
= IPRangeSet
.fromString(profile
.getIncludedSubnets());
1054 for (IPRange range
: included
)
1056 if (range
.getFrom() instanceof Inet4Address
)
1058 mIncludedSubnetsv4
.add(range
);
1060 else if (range
.getFrom() instanceof Inet6Address
)
1062 mIncludedSubnetsv6
.add(range
);
1065 mExcludedSubnets
= IPRangeSet
.fromString(profile
.getExcludedSubnets());
1066 Integer splitTunneling
= profile
.getSplitTunneling();
1067 mSplitTunneling
= splitTunneling
!= null ? splitTunneling
: 0;
1068 SelectedAppsHandling appHandling
= profile
.getSelectedAppsHandling();
1069 mSelectedApps
= profile
.getSelectedAppsSet();
1070 /* exclude our own app, otherwise the fetcher is blocked */
1071 switch (appHandling
)
1073 case SELECTED_APPS_DISABLE
:
1074 appHandling
= SelectedAppsHandling
.SELECTED_APPS_EXCLUDE
;
1075 mSelectedApps
.clear();
1077 case SELECTED_APPS_EXCLUDE
:
1078 mSelectedApps
.add(getPackageName());
1080 case SELECTED_APPS_ONLY
:
1081 mSelectedApps
.remove(getPackageName());
1084 mAppHandling
= appHandling
;
1086 /* set a default MTU, will be set by the daemon for regular interfaces */
1087 Integer mtu
= profile
.getMTU();
1088 mMtu
= mtu
== null ? Constants
.MTU_MAX
: mtu
;
1091 public void addAddress(String address
, int prefixLength
)
1095 mAddresses
.add(new IPRange(address
, prefixLength
));
1096 recordAddressFamily(address
);
1098 catch (UnknownHostException ex
)
1100 ex
.printStackTrace();
1104 public void addRoute(String address
, int prefixLength
)
1108 if (isIPv6(address
))
1110 mRoutesIPv6
.add(new IPRange(address
, prefixLength
));
1114 mRoutesIPv4
.add(new IPRange(address
, prefixLength
));
1117 catch (UnknownHostException ex
)
1119 ex
.printStackTrace();
1123 public void setMtu(int mtu
)
1128 public void recordAddressFamily(String address
)
1132 if (isIPv6(address
))
1141 catch (UnknownHostException ex
)
1143 ex
.printStackTrace();
1147 @TargetApi(Build
.VERSION_CODES
.LOLLIPOP
)
1148 public void applyData(VpnService
.Builder builder
)
1150 for (IPRange address
: mAddresses
)
1152 builder
.addAddress(address
.getFrom(), address
.getPrefix());
1154 /* add routes depending on whether split tunneling is allowed or not,
1155 * that is, whether we have to handle and block non-VPN traffic */
1156 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV4
) == 0)
1159 { /* split tunneling is used depending on the routes and configuration */
1160 IPRangeSet ranges
= new IPRangeSet();
1161 if (mIncludedSubnetsv4
.size() > 0)
1163 ranges
.add(mIncludedSubnetsv4
);
1167 ranges
.addAll(mRoutesIPv4
);
1169 ranges
.remove(mExcludedSubnets
);
1170 for (IPRange subnet
: ranges
.subnets())
1174 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1176 catch (IllegalArgumentException e
)
1177 { /* some Android versions don't seem to like multicast addresses here,
1178 * ignore it for now */
1179 if (!subnet
.getFrom().isMulticastAddress())
1186 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1187 { /* allow traffic that would otherwise be blocked to bypass the VPN */
1188 builder
.allowFamily(OsConstants
.AF_INET
);
1192 { /* only needed if we've seen any addresses. otherwise, traffic
1193 * is blocked by default (we also install no routes in that case) */
1194 builder
.addRoute("0.0.0.0", 0);
1196 /* same thing for IPv6 */
1197 if ((mSplitTunneling
& VpnProfile
.SPLIT_TUNNELING_BLOCK_IPV6
) == 0)
1201 IPRangeSet ranges
= new IPRangeSet();
1202 if (mIncludedSubnetsv6
.size() > 0)
1204 ranges
.add(mIncludedSubnetsv6
);
1208 ranges
.addAll(mRoutesIPv6
);
1210 ranges
.remove(mExcludedSubnets
);
1211 for (IPRange subnet
: ranges
.subnets())
1215 builder
.addRoute(subnet
.getFrom(), subnet
.getPrefix());
1217 catch (IllegalArgumentException e
)
1219 if (!subnet
.getFrom().isMulticastAddress())
1226 else if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1228 builder
.allowFamily(OsConstants
.AF_INET6
);
1233 builder
.addRoute("::", 0);
1235 /* apply selected applications */
1236 if (mSelectedApps
.size() > 0 &&
1237 Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.LOLLIPOP
)
1239 switch (mAppHandling
)
1241 case SELECTED_APPS_EXCLUDE
:
1242 for (String app
: mSelectedApps
)
1246 builder
.addDisallowedApplication(app
);
1248 catch (PackageManager
.NameNotFoundException e
)
1250 // possible if not configured via GUI or app was uninstalled
1254 case SELECTED_APPS_ONLY
:
1255 for (String app
: mSelectedApps
)
1259 builder
.addAllowedApplication(app
);
1261 catch (PackageManager
.NameNotFoundException e
)
1263 // possible if not configured via GUI or app was uninstalled
1271 builder
.setMtu(mMtu
);
1274 private boolean isIPv6(String address
) throws UnknownHostException
1276 InetAddress addr
= InetAddress
.getByName(address
);
1277 if (addr
instanceof Inet4Address
)
1281 else if (addr
instanceof Inet6Address
)
1290 * Function called via JNI to determine information about the Android version.
1292 private static String
getAndroidVersion()
1294 String version
= "Android " + Build
.VERSION
.RELEASE
+ " - " + Build
.DISPLAY
;
1295 if (Build
.VERSION
.SDK_INT
>= Build
.VERSION_CODES
.M
)
1297 version
+= "/" + Build
.VERSION
.SECURITY_PATCH
;
1303 * Function called via JNI to determine information about the device.
1305 private static String
getDeviceString()
1307 return Build
.MODEL
+ " - " + Build
.BRAND
+ "/" + Build
.PRODUCT
+ "/" + Build
.MANUFACTURER
;