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