]> git.ipfire.org Git - thirdparty/strongswan.git/blame - src/frontends/android/app/src/main/java/org/strongswan/android/logic/CharonVpnService.java
Update copyright headers after acquisition by secunet
[thirdparty/strongswan.git] / src / frontends / android / app / src / main / java / org / strongswan / android / logic / CharonVpnService.java
CommitLineData
a4f9028e 1/*
6ca0b46c 2 * Copyright (C) 2012-2018 Tobias Brunner
a4f9028e
TB
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
19ef2aec
TB
5 *
6 * Copyright (C) secunet Security Networks AG
a4f9028e
TB
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * for more details.
17 */
18
8bf30276 19package org.strongswan.android.logic;
4a208143 20
acc8948f 21import android.annotation.TargetApi;
d5070425 22import android.app.Notification;
6ca0b46c 23import android.app.NotificationChannel;
d5070425 24import android.app.NotificationManager;
9c0be3ac 25import android.app.PendingIntent;
03de55ad
TB
26import android.app.Service;
27import android.content.ComponentName;
9c0be3ac 28import android.content.Context;
4a208143 29import android.content.Intent;
03de55ad 30import android.content.ServiceConnection;
08c79d51 31import android.content.SharedPreferences;
f2e7156d 32import android.content.pm.PackageManager;
4a208143 33import android.net.VpnService;
69e0215b 34import android.os.Build;
a4f9028e 35import android.os.Bundle;
70d6a0cf 36import android.os.Handler;
03de55ad 37import android.os.IBinder;
5215d512 38import android.os.ParcelFileDescriptor;
3aba3386
TB
39import android.security.KeyChain;
40import android.security.KeyChainException;
acc8948f 41import android.system.OsConstants;
a4f9028e 42import android.util.Log;
4a208143 43
d5070425
TB
44import org.strongswan.android.R;
45import org.strongswan.android.data.VpnProfile;
f2e7156d 46import org.strongswan.android.data.VpnProfile.SelectedAppsHandling;
d5070425
TB
47import org.strongswan.android.data.VpnProfileDataSource;
48import org.strongswan.android.data.VpnType.VpnTypeFeature;
49import org.strongswan.android.logic.VpnStateService.ErrorState;
50import org.strongswan.android.logic.VpnStateService.State;
51import org.strongswan.android.logic.imc.ImcState;
52import org.strongswan.android.logic.imc.RemediationInstruction;
53import org.strongswan.android.ui.MainActivity;
58d139da 54import org.strongswan.android.ui.VpnProfileControlActivity;
08c79d51 55import org.strongswan.android.utils.Constants;
72b7c289
TB
56import org.strongswan.android.utils.IPRange;
57import org.strongswan.android.utils.IPRangeSet;
d5070425 58import org.strongswan.android.utils.SettingsWriter;
2ef473be 59import org.strongswan.android.utils.Utils;
d5070425
TB
60
61import java.io.File;
fe1f1432
TB
62import java.io.FileInputStream;
63import java.io.IOException;
d5070425
TB
64import java.net.Inet4Address;
65import java.net.Inet6Address;
66import java.net.InetAddress;
67import java.net.UnknownHostException;
fe1f1432
TB
68import java.nio.ByteBuffer;
69import java.nio.channels.ClosedByInterruptException;
d5070425
TB
70import java.security.PrivateKey;
71import java.security.cert.CertificateEncodingException;
72import java.security.cert.X509Certificate;
73import java.util.ArrayList;
74import java.util.List;
75import java.util.Locale;
800f881a 76import java.util.SortedSet;
d5070425 77
3b9696fc
TB
78import androidx.core.app.NotificationCompat;
79import androidx.core.content.ContextCompat;
e106fce4 80import androidx.preference.PreferenceManager;
3b9696fc 81
d5070425 82public class CharonVpnService extends VpnService implements Runnable, VpnStateService.VpnStateListener
a4057603 83{
a4f9028e 84 private static final String TAG = CharonVpnService.class.getSimpleName();
56f59956 85 private static final String VPN_SERVICE_ACTION = "android.net.VpnService";
59693d6c 86 public static final String DISCONNECT_ACTION = "org.strongswan.android.CharonVpnService.DISCONNECT";
6ca0b46c 87 private static final String NOTIFICATION_CHANNEL = "org.strongswan.android.CharonVpnService.VPN_STATE_NOTIFICATION";
fe05f1f0 88 public static final String LOG_FILE = "charon.log";
fb3772ec 89 public static final String KEY_IS_RETRY = "retry";
d5070425 90 public static final int VPN_STATE_NOTIFICATION_ID = 1;
fe05f1f0
TB
91
92 private String mLogFile;
3fe9a436 93 private String mAppDir;
a4f9028e
TB
94 private VpnProfileDataSource mDataSource;
95 private Thread mConnectionHandler;
96 private VpnProfile mCurrentProfile;
9d0f8a3a 97 private volatile String mCurrentCertificateAlias;
3aba3386 98 private volatile String mCurrentUserCertificateAlias;
a4f9028e
TB
99 private VpnProfile mNextProfile;
100 private volatile boolean mProfileUpdated;
101 private volatile boolean mTerminate;
a7c8b166 102 private volatile boolean mIsDisconnecting;
d5070425 103 private volatile boolean mShowNotification;
fe1f1432 104 private BuilderAdapter mBuilderAdapter = new BuilderAdapter();
70d6a0cf 105 private Handler mHandler;
03de55ad
TB
106 private VpnStateService mService;
107 private final Object mServiceLock = new Object();
108 private final ServiceConnection mServiceConnection = new ServiceConnection() {
109 @Override
110 public void onServiceDisconnected(ComponentName name)
111 { /* since the service is local this is theoretically only called when the process is terminated */
112 synchronized (mServiceLock)
113 {
114 mService = null;
115 }
116 }
117
118 @Override
119 public void onServiceConnected(ComponentName name, IBinder service)
120 {
121 synchronized (mServiceLock)
122 {
123 mService = ((VpnStateService.LocalBinder)service).getService();
124 }
125 /* we are now ready to start the handler thread */
d5070425 126 mService.registerListener(CharonVpnService.this);
03de55ad
TB
127 mConnectionHandler.start();
128 }
129 };
4a208143 130
8c2af60c
TB
131 /**
132 * as defined in charonservice.h
133 */
134 static final int STATE_CHILD_SA_UP = 1;
135 static final int STATE_CHILD_SA_DOWN = 2;
136 static final int STATE_AUTH_ERROR = 3;
137 static final int STATE_PEER_AUTH_ERROR = 4;
138 static final int STATE_LOOKUP_ERROR = 5;
139 static final int STATE_UNREACHABLE_ERROR = 6;
ab5dbbc4
TB
140 static final int STATE_CERTIFICATE_UNAVAILABLE = 7;
141 static final int STATE_GENERIC_ERROR = 8;
8c2af60c 142
4a208143 143 @Override
a4057603
TB
144 public int onStartCommand(Intent intent, int flags, int startId)
145 {
a4f9028e
TB
146 if (intent != null)
147 {
56f59956 148 VpnProfile profile = null;
fb3772ec 149 boolean retry = false;
56f59956
TB
150
151 if (VPN_SERVICE_ACTION.equals(intent.getAction()))
152 { /* triggered when Always-on VPN is activated */
153 SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
154 String uuid = pref.getString(Constants.PREF_DEFAULT_VPN_PROFILE, null);
155 if (uuid == null || uuid.equals(Constants.PREF_DEFAULT_VPN_PROFILE_MRU))
156 {
157 uuid = pref.getString(Constants.PREF_MRU_VPN_PROFILE, null);
158 }
159 profile = mDataSource.getVpnProfile(uuid);
59693d6c 160 }
56f59956 161 else if (!DISCONNECT_ACTION.equals(intent.getAction()))
59693d6c
TB
162 {
163 Bundle bundle = intent.getExtras();
59693d6c 164 if (bundle != null)
a4f9028e 165 {
6f9b96ac 166 profile = mDataSource.getVpnProfile(bundle.getString(VpnProfileDataSource.KEY_UUID));
59693d6c
TB
167 if (profile != null)
168 {
169 String password = bundle.getString(VpnProfileDataSource.KEY_PASSWORD);
170 profile.setPassword(password);
08c79d51 171
fb3772ec
TB
172 retry = bundle.getBoolean(CharonVpnService.KEY_IS_RETRY, false);
173
08c79d51
TB
174 SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
175 pref.edit().putString(Constants.PREF_MRU_VPN_PROFILE, profile.getUUID().toString())
176 .apply();
59693d6c 177 }
a4f9028e
TB
178 }
179 }
fb3772ec
TB
180 if (profile != null && !retry)
181 { /* delete the log file if this is not an automatic retry */
182 deleteFile(LOG_FILE);
183 }
56f59956 184 setNextProfile(profile);
a4f9028e
TB
185 }
186 return START_NOT_STICKY;
4a208143
TB
187 }
188
189 @Override
a4057603
TB
190 public void onCreate()
191 {
fe05f1f0 192 mLogFile = getFilesDir().getAbsolutePath() + File.separator + LOG_FILE;
3fe9a436 193 mAppDir = getFilesDir().getAbsolutePath();
fe05f1f0 194
70d6a0cf 195 /* handler used to do changes in the main UI thread */
dc351a30 196 mHandler = new Handler(getMainLooper());
70d6a0cf 197
a4f9028e
TB
198 mDataSource = new VpnProfileDataSource(this);
199 mDataSource.open();
200 /* use a separate thread as main thread for charon */
201 mConnectionHandler = new Thread(this);
03de55ad
TB
202 /* the thread is started when the service is bound */
203 bindService(new Intent(this, VpnStateService.class),
204 mServiceConnection, Service.BIND_AUTO_CREATE);
6ca0b46c
TB
205
206 createNotificationChannel();
a4f9028e
TB
207 }
208
209 @Override
210 public void onRevoke()
211 { /* the system revoked the rights grated with the initial prepare() call.
212 * called when the user clicks disconnect in the system's VPN dialog */
213 setNextProfile(null);
4a208143
TB
214 }
215
216 @Override
a4057603
TB
217 public void onDestroy()
218 {
a4f9028e
TB
219 mTerminate = true;
220 setNextProfile(null);
221 try
222 {
223 mConnectionHandler.join();
224 }
225 catch (InterruptedException e)
226 {
227 e.printStackTrace();
228 }
03de55ad
TB
229 if (mService != null)
230 {
d5070425 231 mService.unregisterListener(this);
03de55ad
TB
232 unbindService(mServiceConnection);
233 }
a4f9028e
TB
234 mDataSource.close();
235 }
236
237 /**
238 * Set the profile that is to be initiated next. Notify the handler thread.
239 *
240 * @param profile the profile to initiate
241 */
242 private void setNextProfile(VpnProfile profile)
243 {
244 synchronized (this)
245 {
246 this.mNextProfile = profile;
247 mProfileUpdated = true;
248 notifyAll();
249 }
250 }
251
252 @Override
253 public void run()
254 {
255 while (true)
256 {
257 synchronized (this)
258 {
259 try
260 {
261 while (!mProfileUpdated)
262 {
263 wait();
264 }
265
266 mProfileUpdated = false;
267 stopCurrentConnection();
268 if (mNextProfile == null)
269 {
03de55ad 270 setState(State.DISABLED);
a4f9028e
TB
271 if (mTerminate)
272 {
273 break;
274 }
275 }
276 else
277 {
278 mCurrentProfile = mNextProfile;
279 mNextProfile = null;
280
9d0f8a3a
TB
281 /* store this in a separate (volatile) variable to avoid
282 * a possible deadlock during deinitialization */
283 mCurrentCertificateAlias = mCurrentProfile.getCertificateAlias();
3aba3386 284 mCurrentUserCertificateAlias = mCurrentProfile.getUserCertificateAlias();
9d0f8a3a 285
38172313 286 startConnection(mCurrentProfile);
a7c8b166 287 mIsDisconnecting = false;
03de55ad 288
ad2d20e5 289 SimpleFetcher.enable();
d5070425 290 addNotification();
fe1f1432 291 mBuilderAdapter.setProfile(mCurrentProfile);
35819143
TB
292 if (initializeCharon(mBuilderAdapter, mLogFile, mAppDir, mCurrentProfile.getVpnType().has(VpnTypeFeature.BYOD),
293 (mCurrentProfile.getFlags() & VpnProfile.FLAGS_IPv6_TRANSPORT) != 0))
c3ee829e
TB
294 {
295 Log.i(TAG, "charon started");
f0b3e303
TB
296
297 if (mCurrentProfile.getVpnType().has(VpnTypeFeature.USER_PASS) &&
298 mCurrentProfile.getPassword() == null)
299 { /* this can happen if Always-on VPN is enabled with an incomplete profile */
300 setError(ErrorState.PASSWORD_MISSING);
301 continue;
302 }
303
79af70c6 304 SettingsWriter writer = new SettingsWriter();
6830cb1c 305 writer.setValue("global.language", Locale.getDefault().getLanguage());
4d02c49e 306 writer.setValue("global.mtu", mCurrentProfile.getMTU());
db599d6b 307 writer.setValue("global.nat_keepalive", mCurrentProfile.getNATKeepAlive());
205ec47d 308 writer.setValue("global.rsa_pss", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_RSA_PSS) != 0);
a7060581
TB
309 writer.setValue("global.crl", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_DISABLE_CRL) == 0);
310 writer.setValue("global.ocsp", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_DISABLE_OCSP) == 0);
79af70c6
TB
311 writer.setValue("connection.type", mCurrentProfile.getVpnType().getIdentifier());
312 writer.setValue("connection.server", mCurrentProfile.getGateway());
cda167c8 313 writer.setValue("connection.port", mCurrentProfile.getPort());
79af70c6
TB
314 writer.setValue("connection.username", mCurrentProfile.getUsername());
315 writer.setValue("connection.password", mCurrentProfile.getPassword());
9c556441
TB
316 writer.setValue("connection.local_id", mCurrentProfile.getLocalId());
317 writer.setValue("connection.remote_id", mCurrentProfile.getRemoteId());
3f0592d0 318 writer.setValue("connection.certreq", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_SUPPRESS_CERT_REQS) == 0);
a7060581 319 writer.setValue("connection.strict_revocation", (mCurrentProfile.getFlags() & VpnProfile.FLAGS_STRICT_REVOCATION) != 0);
a7c43544
TB
320 writer.setValue("connection.ike_proposal", mCurrentProfile.getIkeProposal());
321 writer.setValue("connection.esp_proposal", mCurrentProfile.getEspProposal());
79af70c6 322 initiate(writer.serialize());
c3ee829e
TB
323 }
324 else
325 {
326 Log.e(TAG, "failed to start charon");
327 setError(ErrorState.GENERIC_ERROR);
328 setState(State.DISABLED);
329 mCurrentProfile = null;
330 }
a4f9028e
TB
331 }
332 }
333 catch (InterruptedException ex)
334 {
335 stopCurrentConnection();
03de55ad 336 setState(State.DISABLED);
a4f9028e
TB
337 }
338 }
339 }
340 }
341
342 /**
343 * Stop any existing connection by deinitializing charon.
344 */
345 private void stopCurrentConnection()
346 {
347 synchronized (this)
348 {
fe1f1432
TB
349 if (mNextProfile != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
350 {
351 mBuilderAdapter.setProfile(mNextProfile);
352 mBuilderAdapter.establishBlocking();
353 }
354
a4f9028e
TB
355 if (mCurrentProfile != null)
356 {
03de55ad 357 setState(State.DISCONNECTING);
a7c8b166 358 mIsDisconnecting = true;
ad2d20e5 359 SimpleFetcher.disable();
a4f9028e
TB
360 deinitializeCharon();
361 Log.i(TAG, "charon stopped");
362 mCurrentProfile = null;
bc528681 363 if (mNextProfile == null)
fe1f1432 364 { /* only do this if we are not connecting to another profile */
bc528681 365 removeNotification();
fe1f1432 366 mBuilderAdapter.closeBlocking();
bc528681 367 }
a4f9028e
TB
368 }
369 }
4a208143
TB
370 }
371
d5070425
TB
372 /**
373 * Add a permanent notification while we are connected to avoid the service getting killed by
374 * the system when low on memory.
375 */
376 private void addNotification()
377 {
70d6a0cf
TB
378 mHandler.post(new Runnable()
379 {
380 @Override
381 public void run()
382 {
383 mShowNotification = true;
384 startForeground(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
385 }
386 });
d5070425
TB
387 }
388
389 /**
390 * Remove the permanent notification.
391 */
392 private void removeNotification()
393 {
70d6a0cf
TB
394 mHandler.post(new Runnable()
395 {
396 @Override
397 public void run()
398 {
399 mShowNotification = false;
400 stopForeground(true);
401 }
402 });
d5070425
TB
403 }
404
6ca0b46c
TB
405 /**
406 * Create a notification channel for Android 8+
407 */
408 private void createNotificationChannel()
409 {
410 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
411 {
412 NotificationChannel channel;
413 channel = new NotificationChannel(NOTIFICATION_CHANNEL, getString(R.string.permanent_notification_name),
414 NotificationManager.IMPORTANCE_LOW);
415 channel.setDescription(getString(R.string.permanent_notification_description));
416 channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
417 channel.setShowBadge(false);
418 NotificationManager notificationManager = getSystemService(NotificationManager.class);
419 notificationManager.createNotificationChannel(channel);
420 }
421 }
422
423
d5070425
TB
424 /**
425 * Build a notification matching the current state
426 */
85059424 427 private Notification buildNotification(boolean publicVersion)
d5070425
TB
428 {
429 VpnProfile profile = mService.getProfile();
430 State state = mService.getState();
431 ErrorState error = mService.getErrorState();
432 String name = "";
64c2d3ca 433 boolean add_action = false;
d5070425
TB
434
435 if (profile != null)
436 {
437 name = profile.getName();
438 }
6ca0b46c 439 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
d5070425
TB
440 .setSmallIcon(R.drawable.ic_notification)
441 .setCategory(NotificationCompat.CATEGORY_SERVICE)
85059424
TB
442 .setVisibility(publicVersion ? NotificationCompat.VISIBILITY_PUBLIC
443 : NotificationCompat.VISIBILITY_PRIVATE);
d5070425
TB
444 int s = R.string.state_disabled;
445 if (error != ErrorState.NO_ERROR)
446 {
a7d679ff 447 s = mService.getErrorText();
d5070425
TB
448 builder.setSmallIcon(R.drawable.ic_notification_warning);
449 builder.setColor(ContextCompat.getColor(this, R.color.error_text));
2ec6ad71
TB
450
451 if (!publicVersion && profile != null)
452 {
453 int retry = mService.getRetryIn();
454 if (retry > 0)
455 {
456 builder.setContentText(getResources().getQuantityString(R.plurals.retry_in, retry, retry));
457 builder.setProgress(mService.getRetryTimeout(), retry, false);
458 }
459
460 Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
461 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
462 intent.setAction(VpnProfileControlActivity.START_PROFILE);
463 intent.putExtra(VpnProfileControlActivity.EXTRA_VPN_PROFILE_ID, profile.getUUID().toString());
464 PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
465 PendingIntent.FLAG_UPDATE_CURRENT);
466 builder.addAction(R.drawable.ic_notification_connecting, getString(R.string.retry), pending);
467 add_action = true;
468 }
d5070425
TB
469 }
470 else
471 {
2ec6ad71
TB
472 builder.setProgress(0, 0, false);
473
d5070425
TB
474 switch (state)
475 {
476 case CONNECTING:
477 s = R.string.state_connecting;
52aaffde 478 builder.setSmallIcon(R.drawable.ic_notification_connecting);
d5070425 479 builder.setColor(ContextCompat.getColor(this, R.color.warning_text));
64c2d3ca 480 add_action = true;
d5070425
TB
481 break;
482 case CONNECTED:
483 s = R.string.state_connected;
484 builder.setColor(ContextCompat.getColor(this, R.color.success_text));
485 builder.setUsesChronometer(true);
64c2d3ca 486 add_action = true;
d5070425
TB
487 break;
488 case DISCONNECTING:
489 s = R.string.state_disconnecting;
490 break;
491 }
492 }
493 builder.setContentTitle(getString(s));
85059424
TB
494 if (!publicVersion)
495 {
64c2d3ca
TB
496 if (add_action)
497 {
58d139da
TB
498 Intent intent = new Intent(getApplicationContext(), VpnProfileControlActivity.class);
499 intent.setAction(VpnProfileControlActivity.DISCONNECT);
64c2d3ca
TB
500 PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
501 PendingIntent.FLAG_UPDATE_CURRENT);
502 builder.addAction(R.drawable.ic_notification_disconnect, getString(R.string.disconnect), pending);
503 }
a7d679ff
TB
504 if (error == ErrorState.NO_ERROR)
505 {
506 builder.setContentText(name);
507 }
85059424
TB
508 builder.setPublicVersion(buildNotification(true));
509 }
d5070425
TB
510
511 Intent intent = new Intent(getApplicationContext(), MainActivity.class);
512 PendingIntent pending = PendingIntent.getActivity(getApplicationContext(), 0, intent,
513 PendingIntent.FLAG_UPDATE_CURRENT);
514 builder.setContentIntent(pending);
515 return builder.build();
516 }
517
518 @Override
519 public void stateChanged() {
520 if (mShowNotification)
521 {
522 NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
85059424 523 manager.notify(VPN_STATE_NOTIFICATION_ID, buildNotification(false));
d5070425
TB
524 }
525 }
526
03de55ad 527 /**
38172313
TB
528 * Notify the state service about a new connection attempt.
529 * Called by the handler thread.
03de55ad
TB
530 *
531 * @param profile currently active VPN profile
532 */
38172313 533 private void startConnection(VpnProfile profile)
03de55ad
TB
534 {
535 synchronized (mServiceLock)
536 {
537 if (mService != null)
538 {
38172313 539 mService.startConnection(profile);
03de55ad
TB
540 }
541 }
542 }
543
544 /**
545 * Update the current VPN state on the state service. Called by the handler
546 * thread and any of charon's threads.
547 *
548 * @param state current state
549 */
550 private void setState(State state)
551 {
552 synchronized (mServiceLock)
553 {
554 if (mService != null)
555 {
556 mService.setState(state);
557 }
558 }
559 }
560
561 /**
562 * Set an error on the state service. Called by the handler thread and any
563 * of charon's threads.
564 *
565 * @param error error state
566 */
567 private void setError(ErrorState error)
568 {
569 synchronized (mServiceLock)
570 {
571 if (mService != null)
572 {
573 mService.setError(error);
574 }
575 }
576 }
577
dc52cfab
TB
578 /**
579 * Set the IMC state on the state service. Called by the handler thread and
580 * any of charon's threads.
581 *
582 * @param state IMC state
583 */
584 private void setImcState(ImcState state)
585 {
586 synchronized (mServiceLock)
587 {
588 if (mService != null)
589 {
590 mService.setImcState(state);
591 }
592 }
593 }
594
8c2af60c 595 /**
394be2d5
TB
596 * Set an error on the state service. Called by the handler thread and any
597 * of charon's threads.
8c2af60c
TB
598 *
599 * @param error error state
600 */
601 private void setErrorDisconnect(ErrorState error)
602 {
603 synchronized (mServiceLock)
604 {
605 if (mService != null)
606 {
a7c8b166
TB
607 if (!mIsDisconnecting)
608 {
38172313 609 mService.setError(error);
a7c8b166 610 }
8c2af60c
TB
611 }
612 }
613 }
614
615 /**
616 * Updates the state of the current connection.
617 * Called via JNI by different threads (but not concurrently).
618 *
619 * @param status new state
620 */
621 public void updateStatus(int status)
622 {
623 switch (status)
624 {
625 case STATE_CHILD_SA_DOWN:
1435bd2e
TB
626 if (!mIsDisconnecting)
627 {
628 setState(State.CONNECTING);
629 }
8c2af60c
TB
630 break;
631 case STATE_CHILD_SA_UP:
632 setState(State.CONNECTED);
633 break;
634 case STATE_AUTH_ERROR:
635 setErrorDisconnect(ErrorState.AUTH_FAILED);
636 break;
637 case STATE_PEER_AUTH_ERROR:
638 setErrorDisconnect(ErrorState.PEER_AUTH_FAILED);
639 break;
640 case STATE_LOOKUP_ERROR:
641 setErrorDisconnect(ErrorState.LOOKUP_FAILED);
642 break;
643 case STATE_UNREACHABLE_ERROR:
644 setErrorDisconnect(ErrorState.UNREACHABLE);
645 break;
ab5dbbc4
TB
646 case STATE_CERTIFICATE_UNAVAILABLE:
647 setErrorDisconnect(ErrorState.CERTIFICATE_UNAVAILABLE);
648 break;
8c2af60c
TB
649 case STATE_GENERIC_ERROR:
650 setErrorDisconnect(ErrorState.GENERIC_ERROR);
651 break;
652 default:
653 Log.e(TAG, "Unknown status code received");
654 break;
655 }
656 }
657
dc52cfab
TB
658 /**
659 * Updates the IMC state of the current connection.
660 * Called via JNI by different threads (but not concurrently).
661 *
662 * @param value new state
663 */
664 public void updateImcState(int value)
665 {
666 ImcState state = ImcState.fromValue(value);
667 if (state != null)
668 {
669 setImcState(state);
670 }
671 }
672
a05acd76
TB
673 /**
674 * Add a remediation instruction to the VPN state service.
675 * Called via JNI by different threads (but not concurrently).
676 *
677 * @param xml XML text
678 */
679 public void addRemediationInstruction(String xml)
680 {
681 for (RemediationInstruction instruction : RemediationInstruction.fromXml(xml))
682 {
683 synchronized (mServiceLock)
684 {
685 if (mService != null)
686 {
687 mService.addRemediationInstruction(instruction);
688 }
689 }
690 }
691 }
692
2bec193a
TB
693 /**
694 * Function called via JNI to generate a list of DER encoded CA certificates
695 * as byte array.
696 *
2bec193a
TB
697 * @return a list of DER encoded CA certificates
698 */
9d994ba5 699 private byte[][] getTrustedCertificates()
2bec193a
TB
700 {
701 ArrayList<byte[]> certs = new ArrayList<byte[]>();
3e85b5a4 702 TrustedCertificateManager certman = TrustedCertificateManager.getInstance().load();
2bec193a
TB
703 try
704 {
9d994ba5
TB
705 String alias = this.mCurrentCertificateAlias;
706 if (alias != null)
2bec193a 707 {
2bec193a
TB
708 X509Certificate cert = certman.getCACertificateFromAlias(alias);
709 if (cert == null)
2bec193a
TB
710 {
711 return null;
712 }
713 certs.add(cert.getEncoded());
714 }
715 else
716 {
9d994ba5 717 for (X509Certificate cert : certman.getAllCACertificates().values())
2bec193a 718 {
2bec193a
TB
719 certs.add(cert.getEncoded());
720 }
2bec193a
TB
721 }
722 }
723 catch (CertificateEncodingException e)
724 {
725 e.printStackTrace();
726 return null;
727 }
728 return certs.toArray(new byte[certs.size()][]);
729 }
730
3aba3386 731 /**
64595464
TB
732 * Function called via JNI to get a list containing the DER encoded certificates
733 * of the user selected certificate chain (beginning with the user certificate).
3aba3386
TB
734 *
735 * Since this method is called from a thread of charon's thread pool we are safe
736 * to call methods on KeyChain directly.
737 *
64595464 738 * @return list containing the certificates (first element is the user certificate)
3aba3386
TB
739 * @throws InterruptedException
740 * @throws KeyChainException
741 * @throws CertificateEncodingException
742 */
743 private byte[][] getUserCertificate() throws KeyChainException, InterruptedException, CertificateEncodingException
744 {
745 ArrayList<byte[]> encodings = new ArrayList<byte[]>();
64595464 746 X509Certificate[] chain = KeyChain.getCertificateChain(getApplicationContext(), mCurrentUserCertificateAlias);
3aba3386
TB
747 if (chain == null || chain.length == 0)
748 {
749 return null;
750 }
751 for (X509Certificate cert : chain)
752 {
753 encodings.add(cert.getEncoded());
754 }
755 return encodings.toArray(new byte[encodings.size()][]);
756 }
757
406d680e
TB
758 /**
759 * Function called via JNI to get the private key the user selected.
760 *
761 * Since this method is called from a thread of charon's thread pool we are safe
762 * to call methods on KeyChain directly.
763 *
764 * @return the private key
765 * @throws InterruptedException
766 * @throws KeyChainException
406d680e
TB
767 */
768 private PrivateKey getUserKey() throws KeyChainException, InterruptedException
769 {
770 return KeyChain.getPrivateKey(getApplicationContext(), mCurrentUserCertificateAlias);
406d680e
TB
771 }
772
4a208143
TB
773 /**
774 * Initialization of charon, provided by libandroidbridge.so
ae4f1ea1
TB
775 *
776 * @param builder BuilderAdapter for this connection
fe05f1f0 777 * @param logfile absolute path to the logfile
3fe9a436 778 * @param appdir absolute path to the data directory of the app
d5070425 779 * @param byod enable BYOD features
35819143 780 * @param ipv6 enable IPv6 transport
c3ee829e 781 * @return TRUE if initialization was successful
4a208143 782 */
35819143 783 public native boolean initializeCharon(BuilderAdapter builder, String logfile, String appdir, boolean byod, boolean ipv6);
4a208143
TB
784
785 /**
786 * Deinitialize charon, provided by libandroidbridge.so
787 */
788 public native void deinitializeCharon();
789
c6c39c78
TB
790 /**
791 * Initiate VPN, provided by libandroidbridge.so
792 */
79af70c6 793 public native void initiate(String config);
dffee9e2 794
5215d512
TB
795 /**
796 * Adapter for VpnService.Builder which is used to access it safely via JNI.
ae4f1ea1 797 * There is a corresponding C object to access it from native code.
5215d512
TB
798 */
799 public class BuilderAdapter
800 {
fe1f1432 801 private VpnProfile mProfile;
0326ceda 802 private VpnService.Builder mBuilder;
36aab70a
TB
803 private BuilderCache mCache;
804 private BuilderCache mEstablishedCache;
fe1f1432 805 private PacketDropper mDropper = new PacketDropper();
5215d512 806
fe1f1432 807 public synchronized void setProfile(VpnProfile profile)
5215d512 808 {
f2e7156d
TB
809 mProfile = profile;
810 mBuilder = createBuilder(mProfile.getName());
811 mCache = new BuilderCache(mProfile);
0326ceda
TB
812 }
813
814 private VpnService.Builder createBuilder(String name)
815 {
816 VpnService.Builder builder = new CharonVpnService.Builder();
f2e7156d 817 builder.setSession(name);
9c0be3ac
TB
818
819 /* even though the option displayed in the system dialog says "Configure"
820 * we just use our main Activity */
821 Context context = getApplicationContext();
822 Intent intent = new Intent(context, MainActivity.class);
823 PendingIntent pending = PendingIntent.getActivity(context, 0, intent,
c3bce1aa 824 PendingIntent.FLAG_UPDATE_CURRENT);
9c0be3ac 825 builder.setConfigureIntent(pending);
b32a9be4
TB
826
827 /* mark all VPN connections as unmetered (default changed for Android 10) */
828 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
829 {
830 builder.setMetered(false);
831 }
0326ceda 832 return builder;
5215d512
TB
833 }
834
835 public synchronized boolean addAddress(String address, int prefixLength)
836 {
837 try
838 {
36aab70a 839 mCache.addAddress(address, prefixLength);
5215d512
TB
840 }
841 catch (IllegalArgumentException ex)
842 {
843 return false;
844 }
845 return true;
846 }
847
848 public synchronized boolean addDnsServer(String address)
849 {
850 try
851 {
dd5de792 852 mCache.addDnsServer(address);
5215d512
TB
853 }
854 catch (IllegalArgumentException ex)
855 {
856 return false;
857 }
858 return true;
859 }
860
861 public synchronized boolean addRoute(String address, int prefixLength)
862 {
863 try
864 {
36aab70a 865 mCache.addRoute(address, prefixLength);
5215d512
TB
866 }
867 catch (IllegalArgumentException ex)
868 {
869 return false;
870 }
871 return true;
872 }
873
874 public synchronized boolean addSearchDomain(String domain)
875 {
876 try
877 {
0326ceda 878 mBuilder.addSearchDomain(domain);
5215d512
TB
879 }
880 catch (IllegalArgumentException ex)
881 {
882 return false;
883 }
884 return true;
885 }
886
887 public synchronized boolean setMtu(int mtu)
888 {
889 try
890 {
36aab70a 891 mCache.setMtu(mtu);
5215d512
TB
892 }
893 catch (IllegalArgumentException ex)
894 {
895 return false;
896 }
897 return true;
898 }
899
fe1f1432 900 private synchronized ParcelFileDescriptor establishIntern()
5215d512
TB
901 {
902 ParcelFileDescriptor fd;
903 try
904 {
acc8948f 905 mCache.applyData(mBuilder);
0326ceda 906 fd = mBuilder.establish();
fe1f1432
TB
907 if (fd != null)
908 {
909 closeBlocking();
910 }
5215d512
TB
911 }
912 catch (Exception ex)
913 {
914 ex.printStackTrace();
fe1f1432 915 return null;
5215d512
TB
916 }
917 if (fd == null)
918 {
fe1f1432 919 return null;
5215d512 920 }
0326ceda
TB
921 /* now that the TUN device is created we don't need the current
922 * builder anymore, but we might need another when reestablishing */
f2e7156d 923 mBuilder = createBuilder(mProfile.getName());
36aab70a 924 mEstablishedCache = mCache;
f2e7156d 925 mCache = new BuilderCache(mProfile);
fe1f1432
TB
926 return fd;
927 }
928
929 public synchronized int establish()
930 {
931 ParcelFileDescriptor fd = establishIntern();
932 return fd != null ? fd.detachFd() : -1;
933 }
934
935 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
936 public synchronized void establishBlocking()
937 {
938 /* just choose some arbitrary values to block all traffic (except for what's configured in the profile) */
939 mCache.addAddress("172.16.252.1", 32);
940 mCache.addAddress("fd00::fd02:1", 128);
941 mCache.addRoute("0.0.0.0", 0);
942 mCache.addRoute("::", 0);
84ee9577
TB
943 /* set DNS servers to avoid DNS leak later */
944 mBuilder.addDnsServer("8.8.8.8");
945 mBuilder.addDnsServer("2001:4860:4860::8888");
fe1f1432
TB
946 /* use blocking mode to simplify packet dropping */
947 mBuilder.setBlocking(true);
948 ParcelFileDescriptor fd = establishIntern();
949 if (fd != null)
950 {
951 mDropper.start(fd);
952 }
953 }
954
955 public synchronized void closeBlocking()
956 {
957 mDropper.stop();
5215d512 958 }
36aab70a
TB
959
960 public synchronized int establishNoDns()
961 {
962 ParcelFileDescriptor fd;
963
964 if (mEstablishedCache == null)
965 {
966 return -1;
967 }
968 try
969 {
f2e7156d 970 Builder builder = createBuilder(mProfile.getName());
36aab70a
TB
971 mEstablishedCache.applyData(builder);
972 fd = builder.establish();
973 }
974 catch (Exception ex)
975 {
976 ex.printStackTrace();
977 return -1;
978 }
979 if (fd == null)
980 {
981 return -1;
982 }
983 return fd.detachFd();
984 }
fe1f1432
TB
985
986 private class PacketDropper implements Runnable
987 {
988 private ParcelFileDescriptor mFd;
989 private Thread mThread;
990
991 public void start(ParcelFileDescriptor fd)
992 {
993 mFd = fd;
994 mThread = new Thread(this);
995 mThread.start();
996 }
997
998 public void stop()
999 {
1000 if (mFd != null)
1001 {
1002 try
1003 {
1004 mThread.interrupt();
1005 mThread.join();
1006 mFd.close();
1007 }
1008 catch (InterruptedException e)
1009 {
1010 e.printStackTrace();
1011 }
1012 catch (IOException e)
1013 {
1014 e.printStackTrace();
1015 }
1016 mFd = null;
1017 }
1018 }
1019
1020 @Override
1021 public synchronized void run()
1022 {
1023 try
1024 {
1025 FileInputStream plain = new FileInputStream(mFd.getFileDescriptor());
1026 ByteBuffer packet = ByteBuffer.allocate(mCache.mMtu);
1027 while (true)
4e905b96
TB
1028 {
1029 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
1030 { /* just read and ignore all data, regular read() is not interruptible */
1031 int len = plain.getChannel().read(packet);
1032 packet.clear();
1033 if (len < 0)
1034 {
1035 break;
1036 }
1037 }
1038 else
1039 { /* this is rather ugly but on older platforms not even the NIO version of read() is interruptible */
1040 boolean wait = true;
1041 if (plain.available() > 0)
1042 {
1043 int len = plain.read(packet.array());
1044 packet.clear();
1045 if (len < 0 || Thread.interrupted())
1046 {
1047 break;
1048 }
1049 /* check again right away, there may be another packet */
1050 wait = false;
1051 }
1052 if (wait)
1053 {
1054 Thread.sleep(250);
1055 }
fe1f1432
TB
1056 }
1057 }
1058 }
4e905b96 1059 catch (ClosedByInterruptException|InterruptedException e)
fe1f1432
TB
1060 {
1061 /* regular interruption */
1062 }
4e905b96 1063 catch (IOException e)
fe1f1432
TB
1064 {
1065 e.printStackTrace();
1066 }
1067 }
1068 }
36aab70a
TB
1069 }
1070
1071 /**
1072 * Cache non DNS related information so we can recreate the builder without
1073 * that information when reestablishing IKE_SAs
1074 */
1075 public class BuilderCache
1076 {
72b7c289
TB
1077 private final List<IPRange> mAddresses = new ArrayList<>();
1078 private final List<IPRange> mRoutesIPv4 = new ArrayList<>();
1079 private final List<IPRange> mRoutesIPv6 = new ArrayList<>();
4471a934
TB
1080 private final IPRangeSet mIncludedSubnetsv4 = new IPRangeSet();
1081 private final IPRangeSet mIncludedSubnetsv6 = new IPRangeSet();
72b7c289 1082 private final IPRangeSet mExcludedSubnets;
acc8948f 1083 private final int mSplitTunneling;
f2e7156d 1084 private final SelectedAppsHandling mAppHandling;
800f881a 1085 private final SortedSet<String> mSelectedApps;
dd5de792 1086 private final List<InetAddress> mDnsServers = new ArrayList<>();
36aab70a 1087 private int mMtu;
dd5de792 1088 private boolean mIPv4Seen, mIPv6Seen, mDnsServersConfigured;
acc8948f 1089
f2e7156d 1090 public BuilderCache(VpnProfile profile)
acc8948f 1091 {
f2e7156d 1092 IPRangeSet included = IPRangeSet.fromString(profile.getIncludedSubnets());
4471a934
TB
1093 for (IPRange range : included)
1094 {
1095 if (range.getFrom() instanceof Inet4Address)
1096 {
1097 mIncludedSubnetsv4.add(range);
1098 }
1099 else if (range.getFrom() instanceof Inet6Address)
1100 {
1101 mIncludedSubnetsv6.add(range);
1102 }
1103 }
f2e7156d
TB
1104 mExcludedSubnets = IPRangeSet.fromString(profile.getExcludedSubnets());
1105 Integer splitTunneling = profile.getSplitTunneling();
acc8948f 1106 mSplitTunneling = splitTunneling != null ? splitTunneling : 0;
99cc2d82 1107 SelectedAppsHandling appHandling = profile.getSelectedAppsHandling();
800f881a 1108 mSelectedApps = profile.getSelectedAppsSet();
99cc2d82
TB
1109 /* exclude our own app, otherwise the fetcher is blocked */
1110 switch (appHandling)
1111 {
1112 case SELECTED_APPS_DISABLE:
1113 appHandling = SelectedAppsHandling.SELECTED_APPS_EXCLUDE;
1114 mSelectedApps.clear();
1115 /* fall-through */
1116 case SELECTED_APPS_EXCLUDE:
1117 mSelectedApps.add(getPackageName());
1118 break;
1119 case SELECTED_APPS_ONLY:
1120 mSelectedApps.remove(getPackageName());
1121 break;
1122 }
1123 mAppHandling = appHandling;
fe1f1432 1124
dd5de792
TB
1125 if (profile.getDnsServers() != null)
1126 {
1127 for (String server : profile.getDnsServers().split("\\s+"))
1128 {
1129 try
1130 {
2ef473be 1131 mDnsServers.add(Utils.parseInetAddress(server));
dd5de792
TB
1132 recordAddressFamily(server);
1133 mDnsServersConfigured = true;
1134 }
1135 catch (UnknownHostException e)
1136 {
1137 e.printStackTrace();
1138 }
1139 }
1140 }
1141
fe1f1432
TB
1142 /* set a default MTU, will be set by the daemon for regular interfaces */
1143 Integer mtu = profile.getMTU();
1144 mMtu = mtu == null ? Constants.MTU_MAX : mtu;
acc8948f 1145 }
36aab70a
TB
1146
1147 public void addAddress(String address, int prefixLength)
1148 {
72b7c289
TB
1149 try
1150 {
1151 mAddresses.add(new IPRange(address, prefixLength));
1152 recordAddressFamily(address);
1153 }
1154 catch (UnknownHostException ex)
1155 {
1156 ex.printStackTrace();
1157 }
36aab70a
TB
1158 }
1159
dd5de792
TB
1160 public void addDnsServer(String address)
1161 {
1162 /* ignore received DNS servers if any were configured */
1163 if (mDnsServersConfigured)
1164 {
1165 return;
1166 }
1167
1168 try
1169 {
2ef473be 1170 mDnsServers.add(Utils.parseInetAddress(address));
dd5de792
TB
1171 recordAddressFamily(address);
1172 }
1173 catch (UnknownHostException e)
1174 {
1175 e.printStackTrace();
1176 }
1177 }
1178
36aab70a
TB
1179 public void addRoute(String address, int prefixLength)
1180 {
acc8948f
TB
1181 try
1182 {
1183 if (isIPv6(address))
1184 {
72b7c289 1185 mRoutesIPv6.add(new IPRange(address, prefixLength));
acc8948f
TB
1186 }
1187 else
1188 {
72b7c289 1189 mRoutesIPv4.add(new IPRange(address, prefixLength));
acc8948f
TB
1190 }
1191 }
1192 catch (UnknownHostException ex)
1193 {
1194 ex.printStackTrace();
1195 }
36aab70a
TB
1196 }
1197
1198 public void setMtu(int mtu)
1199 {
1200 mMtu = mtu;
1201 }
1202
acc8948f
TB
1203 public void recordAddressFamily(String address)
1204 {
1205 try
1206 {
1207 if (isIPv6(address))
1208 {
1209 mIPv6Seen = true;
1210 }
1211 else
1212 {
1213 mIPv4Seen = true;
1214 }
1215 }
1216 catch (UnknownHostException ex)
1217 {
1218 ex.printStackTrace();
1219 }
1220 }
1221
1222 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
36aab70a
TB
1223 public void applyData(VpnService.Builder builder)
1224 {
72b7c289 1225 for (IPRange address : mAddresses)
36aab70a 1226 {
72b7c289 1227 builder.addAddress(address.getFrom(), address.getPrefix());
36aab70a 1228 }
dd5de792
TB
1229 for (InetAddress server : mDnsServers)
1230 {
1231 builder.addDnsServer(server);
1232 }
acc8948f
TB
1233 /* add routes depending on whether split tunneling is allowed or not,
1234 * that is, whether we have to handle and block non-VPN traffic */
1235 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV4) == 0)
1236 {
1237 if (mIPv4Seen)
72b7c289
TB
1238 { /* split tunneling is used depending on the routes and configuration */
1239 IPRangeSet ranges = new IPRangeSet();
4471a934
TB
1240 if (mIncludedSubnetsv4.size() > 0)
1241 {
1242 ranges.add(mIncludedSubnetsv4);
1243 }
1244 else
1245 {
1246 ranges.addAll(mRoutesIPv4);
1247 }
72b7c289
TB
1248 ranges.remove(mExcludedSubnets);
1249 for (IPRange subnet : ranges.subnets())
acc8948f 1250 {
66b7a088
TB
1251 try
1252 {
1253 builder.addRoute(subnet.getFrom(), subnet.getPrefix());
1254 }
1255 catch (IllegalArgumentException e)
1256 { /* some Android versions don't seem to like multicast addresses here,
1257 * ignore it for now */
1258 if (!subnet.getFrom().isMulticastAddress())
1259 {
1260 throw e;
1261 }
1262 }
acc8948f
TB
1263 }
1264 }
1265 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
1266 { /* allow traffic that would otherwise be blocked to bypass the VPN */
1267 builder.allowFamily(OsConstants.AF_INET);
1268 }
1269 }
1270 else if (mIPv4Seen)
1271 { /* only needed if we've seen any addresses. otherwise, traffic
1272 * is blocked by default (we also install no routes in that case) */
1273 builder.addRoute("0.0.0.0", 0);
1274 }
1275 /* same thing for IPv6 */
1276 if ((mSplitTunneling & VpnProfile.SPLIT_TUNNELING_BLOCK_IPV6) == 0)
1277 {
1278 if (mIPv6Seen)
1279 {
72b7c289 1280 IPRangeSet ranges = new IPRangeSet();
4471a934
TB
1281 if (mIncludedSubnetsv6.size() > 0)
1282 {
1283 ranges.add(mIncludedSubnetsv6);
1284 }
1285 else
1286 {
1287 ranges.addAll(mRoutesIPv6);
1288 }
72b7c289
TB
1289 ranges.remove(mExcludedSubnets);
1290 for (IPRange subnet : ranges.subnets())
acc8948f 1291 {
66b7a088
TB
1292 try
1293 {
1294 builder.addRoute(subnet.getFrom(), subnet.getPrefix());
1295 }
1296 catch (IllegalArgumentException e)
1297 {
1298 if (!subnet.getFrom().isMulticastAddress())
1299 {
1300 throw e;
1301 }
1302 }
acc8948f
TB
1303 }
1304 }
1305 else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
1306 {
1307 builder.allowFamily(OsConstants.AF_INET6);
1308 }
1309 }
1310 else if (mIPv6Seen)
36aab70a 1311 {
acc8948f 1312 builder.addRoute("::", 0);
36aab70a 1313 }
f2e7156d 1314 /* apply selected applications */
0b4f7d64
TB
1315 if (mSelectedApps.size() > 0 &&
1316 Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
f2e7156d
TB
1317 {
1318 switch (mAppHandling)
1319 {
1320 case SELECTED_APPS_EXCLUDE:
1321 for (String app : mSelectedApps)
1322 {
1323 try
1324 {
1325 builder.addDisallowedApplication(app);
1326 }
1327 catch (PackageManager.NameNotFoundException e)
1328 {
1329 // possible if not configured via GUI or app was uninstalled
1330 }
1331 }
1332 break;
1333 case SELECTED_APPS_ONLY:
1334 for (String app : mSelectedApps)
1335 {
1336 try
1337 {
1338 builder.addAllowedApplication(app);
1339 }
1340 catch (PackageManager.NameNotFoundException e)
1341 {
1342 // possible if not configured via GUI or app was uninstalled
1343 }
1344 }
1345 break;
1346 default:
1347 break;
1348 }
1349 }
36aab70a
TB
1350 builder.setMtu(mMtu);
1351 }
1352
acc8948f
TB
1353 private boolean isIPv6(String address) throws UnknownHostException
1354 {
2ef473be 1355 InetAddress addr = Utils.parseInetAddress(address);
acc8948f
TB
1356 if (addr instanceof Inet4Address)
1357 {
1358 return false;
1359 }
1360 else if (addr instanceof Inet6Address)
1361 {
1362 return true;
1363 }
1364 return false;
1365 }
5215d512
TB
1366 }
1367
c5ba3817
TB
1368 /**
1369 * Function called via JNI to determine information about the Android version.
1370 */
1371 private static String getAndroidVersion()
1372 {
a63b0f99
TB
1373 String version = "Android " + Build.VERSION.RELEASE + " - " + Build.DISPLAY;
1374 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
1375 {
1376 version += "/" + Build.VERSION.SECURITY_PATCH;
1377 }
1378 return version;
c5ba3817
TB
1379 }
1380
1381 /**
1382 * Function called via JNI to determine information about the device.
1383 */
1384 private static String getDeviceString()
1385 {
1386 return Build.MODEL + " - " + Build.BRAND + "/" + Build.PRODUCT + "/" + Build.MANUFACTURER;
1387 }
4a208143 1388}