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