1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.net; 18 19 import android.annotation.NonNull; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.net.Ikev2VpnProfile; 22 import android.net.PlatformVpnProfile; 23 import android.net.ProxyInfo; 24 import android.net.Uri; 25 import android.net.ipsec.ike.IkeTunnelConnectionParams; 26 import android.net.vcn.persistablebundleutils.TunnelConnectionParamsUtils; 27 import android.os.Build; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.os.PersistableBundle; 31 import android.text.TextUtils; 32 import android.util.Log; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.util.HexDump; 36 import com.android.net.module.util.ProxyUtils; 37 38 import java.io.UnsupportedEncodingException; 39 import java.net.InetAddress; 40 import java.net.URLDecoder; 41 import java.net.URLEncoder; 42 import java.nio.charset.StandardCharsets; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.Collections; 46 import java.util.List; 47 import java.util.Objects; 48 49 /** 50 * Profile storage class for a platform VPN. 51 * 52 * <p>This class supports both the Legacy VPN, as well as application-configurable platform VPNs 53 * (such as IKEv2/IPsec). 54 * 55 * <p>This class is serialized and deserialized via the {@link #encode()} and {@link #decode()} 56 * functions for persistent storage in the Android Keystore. The encoding is entirely custom, but 57 * must be kept for backward compatibility for devices upgrading between Android versions. 58 * 59 * @hide 60 */ 61 public final class VpnProfile implements Cloneable, Parcelable { 62 private static final String TAG = "VpnProfile"; 63 64 @VisibleForTesting static final String VALUE_DELIMITER = "\0"; 65 @VisibleForTesting static final String LIST_DELIMITER = ","; 66 67 // Match these constants with R.array.vpn_types. 68 public static final int TYPE_PPTP = 0; 69 public static final int TYPE_L2TP_IPSEC_PSK = 1; 70 public static final int TYPE_L2TP_IPSEC_RSA = 2; 71 public static final int TYPE_IPSEC_XAUTH_PSK = 3; 72 public static final int TYPE_IPSEC_XAUTH_RSA = 4; 73 public static final int TYPE_IPSEC_HYBRID_RSA = 5; 74 public static final int TYPE_IKEV2_IPSEC_USER_PASS = 6; 75 public static final int TYPE_IKEV2_IPSEC_PSK = 7; 76 public static final int TYPE_IKEV2_IPSEC_RSA = 8; 77 public static final int TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS = 9; 78 public static final int TYPE_MAX = 9; 79 80 // Match these constants with R.array.vpn_proxy_settings. 81 public static final int PROXY_NONE = 0; 82 public static final int PROXY_MANUAL = 1; 83 84 private static final String ENCODED_NULL_PROXY_INFO = "\0\0\0\0"; 85 86 /** Default URL encoding. */ 87 private static final String DEFAULT_ENCODING = StandardCharsets.UTF_8.name(); 88 89 // Entity fields. 90 @UnsupportedAppUsage 91 public final String key; // -1 92 93 @UnsupportedAppUsage 94 public String name = ""; // 0 95 96 @UnsupportedAppUsage 97 public int type = TYPE_PPTP; // 1 98 99 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 100 public String server = ""; // 2 101 102 @UnsupportedAppUsage 103 public String username = ""; // 3 104 public String password = ""; // 4 105 public String dnsServers = ""; // 5 106 public String searchDomains = ""; // 6 107 public String routes = ""; // 7 108 public boolean mppe = true; // 8 109 public String l2tpSecret = ""; // 9 110 public String ipsecIdentifier = ""; // 10 111 112 /** 113 * The RSA private key or pre-shared key used for authentication. 114 * 115 * <p>If areAuthParamsInline is {@code true}, this String will be either: 116 * 117 * <ul> 118 * <li>If this is an IKEv2 RSA profile: a PKCS#8 encoded {@link java.security.PrivateKey} 119 * <li>If this is an IKEv2 PSK profile: a string value representing the PSK. 120 * </ul> 121 */ 122 public String ipsecSecret = ""; // 11 123 124 /** 125 * The RSA certificate to be used for digital signature authentication. 126 * 127 * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link 128 * java.security.X509Certificate} 129 */ 130 public String ipsecUserCert = ""; // 12 131 132 /** 133 * The RSA certificate that should be used to verify the server's end/target certificate. 134 * 135 * <p>If areAuthParamsInline is {@code true}, this String will be a pem-encoded {@link 136 * java.security.X509Certificate} 137 */ 138 public String ipsecCaCert = ""; // 13 139 public String ipsecServerCert = ""; // 14 140 public ProxyInfo proxy = null; // 15~18 141 142 /** 143 * The list of allowable algorithms. 144 */ 145 private List<String> mAllowedAlgorithms = new ArrayList<>(); // 19 146 public boolean isBypassable = false; // 20 147 public boolean isMetered = false; // 21 148 public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; // 22 149 public boolean areAuthParamsInline = false; // 23 150 public final boolean isRestrictedToTestNetworks; // 24 151 152 public final boolean excludeLocalRoutes; // 25 153 public final boolean requiresInternetValidation; // 26 154 public final IkeTunnelConnectionParams ikeTunConnParams; // 27 155 156 // Helper fields. 157 @UnsupportedAppUsage 158 public transient boolean saveLogin = false; 159 VpnProfile(String key)160 public VpnProfile(String key) { 161 this(key, false, false, false, null); 162 } 163 VpnProfile(String key, boolean isRestrictedToTestNetworks)164 public VpnProfile(String key, boolean isRestrictedToTestNetworks) { 165 this(key, isRestrictedToTestNetworks, false, false, null); 166 } 167 VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes, boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams)168 public VpnProfile(String key, boolean isRestrictedToTestNetworks, boolean excludeLocalRoutes, 169 boolean requiresInternetValidation, IkeTunnelConnectionParams ikeTunConnParams) { 170 this.key = key; 171 this.isRestrictedToTestNetworks = isRestrictedToTestNetworks; 172 this.excludeLocalRoutes = excludeLocalRoutes; 173 this.requiresInternetValidation = requiresInternetValidation; 174 this.ikeTunConnParams = ikeTunConnParams; 175 } 176 177 @UnsupportedAppUsage VpnProfile(Parcel in)178 public VpnProfile(Parcel in) { 179 key = in.readString(); 180 name = in.readString(); 181 type = in.readInt(); 182 server = in.readString(); 183 username = in.readString(); 184 password = in.readString(); 185 dnsServers = in.readString(); 186 searchDomains = in.readString(); 187 routes = in.readString(); 188 mppe = in.readInt() != 0; 189 l2tpSecret = in.readString(); 190 ipsecIdentifier = in.readString(); 191 ipsecSecret = in.readString(); 192 ipsecUserCert = in.readString(); 193 ipsecCaCert = in.readString(); 194 ipsecServerCert = in.readString(); 195 saveLogin = in.readInt() != 0; 196 proxy = in.readParcelable(null, android.net.ProxyInfo.class); 197 mAllowedAlgorithms = new ArrayList<>(); 198 in.readList(mAllowedAlgorithms, null, java.lang.String.class); 199 isBypassable = in.readBoolean(); 200 isMetered = in.readBoolean(); 201 maxMtu = in.readInt(); 202 areAuthParamsInline = in.readBoolean(); 203 isRestrictedToTestNetworks = in.readBoolean(); 204 excludeLocalRoutes = in.readBoolean(); 205 requiresInternetValidation = in.readBoolean(); 206 final PersistableBundle bundle = 207 in.readParcelable(PersistableBundle.class.getClassLoader()); 208 ikeTunConnParams = (bundle == null) ? null 209 : TunnelConnectionParamsUtils.fromPersistableBundle(bundle); 210 } 211 212 /** 213 * Retrieves the list of allowed algorithms. 214 * 215 * <p>The contained elements are as listed in {@link IpSecAlgorithm} 216 */ getAllowedAlgorithms()217 public List<String> getAllowedAlgorithms() { 218 return Collections.unmodifiableList(mAllowedAlgorithms); 219 } 220 221 /** 222 * Validates and sets the list of algorithms that can be used for the IPsec transforms. 223 * 224 * @param allowedAlgorithms the list of allowable algorithms, as listed in {@link 225 * IpSecAlgorithm}. 226 */ setAllowedAlgorithms(List<String> allowedAlgorithms)227 public void setAllowedAlgorithms(List<String> allowedAlgorithms) { 228 mAllowedAlgorithms = allowedAlgorithms; 229 } 230 231 @Override writeToParcel(Parcel out, int flags)232 public void writeToParcel(Parcel out, int flags) { 233 out.writeString(key); 234 out.writeString(name); 235 out.writeInt(type); 236 out.writeString(server); 237 out.writeString(username); 238 out.writeString(password); 239 out.writeString(dnsServers); 240 out.writeString(searchDomains); 241 out.writeString(routes); 242 out.writeInt(mppe ? 1 : 0); 243 out.writeString(l2tpSecret); 244 out.writeString(ipsecIdentifier); 245 out.writeString(ipsecSecret); 246 out.writeString(ipsecUserCert); 247 out.writeString(ipsecCaCert); 248 out.writeString(ipsecServerCert); 249 out.writeInt(saveLogin ? 1 : 0); 250 out.writeParcelable(proxy, flags); 251 out.writeList(mAllowedAlgorithms); 252 out.writeBoolean(isBypassable); 253 out.writeBoolean(isMetered); 254 out.writeInt(maxMtu); 255 out.writeBoolean(areAuthParamsInline); 256 out.writeBoolean(isRestrictedToTestNetworks); 257 out.writeBoolean(excludeLocalRoutes); 258 out.writeBoolean(requiresInternetValidation); 259 out.writeParcelable(ikeTunConnParams == null ? null 260 : TunnelConnectionParamsUtils.toPersistableBundle(ikeTunConnParams), flags); 261 } 262 263 /** 264 * Decodes a VpnProfile instance from the encoded byte array. 265 * 266 * <p>See {@link #encode()} 267 */ 268 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) decode(String key, byte[] value)269 public static VpnProfile decode(String key, byte[] value) { 270 try { 271 if (key == null) { 272 return null; 273 } 274 275 String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1); 276 277 // Acceptable numbers of values are: 278 // 14-19: Standard profile, with option for serverCert, proxy 279 // 24: Standard profile with serverCert, proxy and platform-VPN parameters 280 // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks 281 // 26: ...and excludeLocalRoutes 282 // 27: ...and requiresInternetValidation 283 // (26,27 can only be found on dogfood devices) 284 // 28: ...and ikeTunConnParams 285 if ((values.length < 14 || (values.length > 19 && values.length < 24) 286 || values.length > 28)) { 287 return null; 288 } 289 290 final boolean isRestrictedToTestNetworks; 291 if (values.length >= 25) { 292 isRestrictedToTestNetworks = Boolean.parseBoolean(values[24]); 293 } else { 294 isRestrictedToTestNetworks = false; 295 } 296 297 final boolean excludeLocalRoutes; 298 if (values.length >= 26) { 299 excludeLocalRoutes = Boolean.parseBoolean(values[25]); 300 } else { 301 excludeLocalRoutes = false; 302 } 303 304 final boolean requiresInternetValidation; 305 if (values.length >= 27) { 306 requiresInternetValidation = Boolean.parseBoolean(values[26]); 307 } else { 308 requiresInternetValidation = false; 309 } 310 311 final IkeTunnelConnectionParams tempIkeTunConnParams; 312 // Assign null directly if the ikeTunConParams field is empty. 313 if (values.length >= 28 && values[27].length() != 0) { 314 final Parcel parcel = Parcel.obtain(); 315 final byte[] bytes = HexDump.hexStringToByteArray(values[27]); 316 parcel.unmarshall(bytes, 0, bytes.length); 317 parcel.setDataPosition(0); 318 final PersistableBundle bundle = (PersistableBundle) parcel.readValue( 319 PersistableBundle.class.getClassLoader()); 320 tempIkeTunConnParams = TunnelConnectionParamsUtils.fromPersistableBundle(bundle); 321 } else { 322 tempIkeTunConnParams = null; 323 } 324 325 VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks, 326 excludeLocalRoutes, requiresInternetValidation, tempIkeTunConnParams); 327 profile.name = values[0]; 328 profile.type = Integer.parseInt(values[1]); 329 if (profile.type < 0 || profile.type > TYPE_MAX) { 330 return null; 331 } 332 profile.server = values[2]; 333 profile.username = values[3]; 334 profile.password = values[4]; 335 profile.dnsServers = values[5]; 336 profile.searchDomains = values[6]; 337 profile.routes = values[7]; 338 profile.mppe = Boolean.parseBoolean(values[8]); 339 profile.l2tpSecret = values[9]; 340 profile.ipsecIdentifier = values[10]; 341 profile.ipsecSecret = values[11]; 342 profile.ipsecUserCert = values[12]; 343 profile.ipsecCaCert = values[13]; 344 profile.ipsecServerCert = (values.length > 14) ? values[14] : ""; 345 if (values.length > 15) { 346 String host = (values.length > 15) ? values[15] : ""; 347 String port = (values.length > 16) ? values[16] : ""; 348 String exclList = (values.length > 17) ? values[17] : ""; 349 String pacFileUrl = (values.length > 18) ? values[18] : ""; 350 if (!host.isEmpty() || !port.isEmpty() || !exclList.isEmpty()) { 351 profile.proxy = 352 ProxyInfo.buildDirectProxy(host, port.isEmpty() ? 353 0 : Integer.parseInt(port), 354 ProxyUtils.exclusionStringAsList(exclList)); 355 } else if (!pacFileUrl.isEmpty()) { 356 profile.proxy = ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl)); 357 } 358 } // else profile.proxy = null 359 360 // Either all must be present, or none must be. 361 if (values.length >= 24) { 362 profile.mAllowedAlgorithms = new ArrayList<>(); 363 for (String algo : Arrays.asList(values[19].split(LIST_DELIMITER))) { 364 profile.mAllowedAlgorithms.add(URLDecoder.decode(algo, DEFAULT_ENCODING)); 365 } 366 367 profile.isBypassable = Boolean.parseBoolean(values[20]); 368 profile.isMetered = Boolean.parseBoolean(values[21]); 369 profile.maxMtu = Integer.parseInt(values[22]); 370 profile.areAuthParamsInline = Boolean.parseBoolean(values[23]); 371 } 372 373 // isRestrictedToTestNetworks (values[24]) assigned as part of the constructor 374 375 profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty(); 376 return profile; 377 } catch (Exception e) { 378 Log.d(TAG, "Got exception in decode.", e); 379 // ignore 380 } 381 return null; 382 } 383 384 /** 385 * Encodes a VpnProfile instance to a byte array for storage. 386 * 387 * <p>See {@link #decode(String, byte[])} 388 */ encode()389 public byte[] encode() { 390 StringBuilder builder = new StringBuilder(name); 391 builder.append(VALUE_DELIMITER).append(type); 392 builder.append(VALUE_DELIMITER).append(server); 393 builder.append(VALUE_DELIMITER).append(saveLogin ? username : ""); 394 builder.append(VALUE_DELIMITER).append(saveLogin ? password : ""); 395 builder.append(VALUE_DELIMITER).append(dnsServers); 396 builder.append(VALUE_DELIMITER).append(searchDomains); 397 builder.append(VALUE_DELIMITER).append(routes); 398 builder.append(VALUE_DELIMITER).append(mppe); 399 builder.append(VALUE_DELIMITER).append(l2tpSecret); 400 builder.append(VALUE_DELIMITER).append(ipsecIdentifier); 401 builder.append(VALUE_DELIMITER).append(ipsecSecret); 402 builder.append(VALUE_DELIMITER).append(ipsecUserCert); 403 builder.append(VALUE_DELIMITER).append(ipsecCaCert); 404 builder.append(VALUE_DELIMITER).append(ipsecServerCert); 405 if (proxy != null) { 406 builder.append(VALUE_DELIMITER).append(proxy.getHost() != null ? proxy.getHost() : ""); 407 builder.append(VALUE_DELIMITER).append(proxy.getPort()); 408 builder.append(VALUE_DELIMITER) 409 .append( 410 ProxyUtils.exclusionListAsString(proxy.getExclusionList()) != null 411 ? ProxyUtils.exclusionListAsString(proxy.getExclusionList()) 412 : ""); 413 builder.append(VALUE_DELIMITER).append(proxy.getPacFileUrl().toString()); 414 } else { 415 builder.append(ENCODED_NULL_PROXY_INFO); 416 } 417 418 final List<String> encodedAlgoNames = new ArrayList<>(); 419 420 try { 421 for (String algo : mAllowedAlgorithms) { 422 encodedAlgoNames.add(URLEncoder.encode(algo, DEFAULT_ENCODING)); 423 } 424 } catch (UnsupportedEncodingException e) { 425 // Unexpected error 426 throw new IllegalStateException("Failed to encode algorithms.", e); 427 } 428 429 builder.append(VALUE_DELIMITER).append(String.join(LIST_DELIMITER, encodedAlgoNames)); 430 431 builder.append(VALUE_DELIMITER).append(isBypassable); 432 builder.append(VALUE_DELIMITER).append(isMetered); 433 builder.append(VALUE_DELIMITER).append(maxMtu); 434 builder.append(VALUE_DELIMITER).append(areAuthParamsInline); 435 builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks); 436 437 builder.append(VALUE_DELIMITER).append(excludeLocalRoutes); 438 builder.append(VALUE_DELIMITER).append(requiresInternetValidation); 439 440 if (ikeTunConnParams != null) { 441 final PersistableBundle bundle = 442 TunnelConnectionParamsUtils.toPersistableBundle(ikeTunConnParams); 443 final Parcel parcel = Parcel.obtain(); 444 parcel.writeValue(bundle); 445 final byte[] bytes = parcel.marshall(); 446 builder.append(VALUE_DELIMITER).append(HexDump.toHexString(bytes)); 447 } else { 448 builder.append(VALUE_DELIMITER).append(""); 449 } 450 451 return builder.toString().getBytes(StandardCharsets.UTF_8); 452 } 453 454 /** Checks if this profile specifies a LegacyVpn type. */ isLegacyType(int type)455 public static boolean isLegacyType(int type) { 456 switch (type) { 457 case VpnProfile.TYPE_PPTP: 458 case VpnProfile.TYPE_L2TP_IPSEC_PSK: 459 case VpnProfile.TYPE_L2TP_IPSEC_RSA: 460 case VpnProfile.TYPE_IPSEC_XAUTH_PSK: 461 case VpnProfile.TYPE_IPSEC_XAUTH_RSA: 462 case VpnProfile.TYPE_IPSEC_HYBRID_RSA: 463 return true; 464 default: 465 return false; 466 } 467 } 468 isValidLockdownLegacyVpnProfile()469 private boolean isValidLockdownLegacyVpnProfile() { 470 return isLegacyType(type) && isServerAddressNumeric() && hasDns() 471 && areDnsAddressesNumeric(); 472 } 473 isValidLockdownPlatformVpnProfile()474 private boolean isValidLockdownPlatformVpnProfile() { 475 return Ikev2VpnProfile.isValidVpnProfile(this); 476 } 477 478 /** 479 * Tests if profile is valid for lockdown. 480 * 481 * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS. 482 * 483 * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to 484 * be non-null. 485 */ isValidLockdownProfile()486 public boolean isValidLockdownProfile() { 487 return isTypeValidForLockdown() 488 && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile()); 489 } 490 491 /** Returns {@code true} if the VPN type is valid for lockdown. */ isTypeValidForLockdown()492 public boolean isTypeValidForLockdown() { 493 // b/7064069: lockdown firewall blocks ports used for PPTP 494 return type != TYPE_PPTP; 495 } 496 497 /** Returns {@code true} if the server address is numeric, e.g. 8.8.8.8 */ isServerAddressNumeric()498 public boolean isServerAddressNumeric() { 499 try { 500 InetAddress.parseNumericAddress(server); 501 } catch (IllegalArgumentException e) { 502 return false; 503 } 504 return true; 505 } 506 507 /** Returns {@code true} if one or more DNS servers are specified. */ hasDns()508 public boolean hasDns() { 509 return !TextUtils.isEmpty(dnsServers); 510 } 511 512 /** Returns {@code true} if all DNS servers have numeric addresses, e.g. 8.8.8.8 */ areDnsAddressesNumeric()513 public boolean areDnsAddressesNumeric() { 514 try { 515 for (String dnsServer : dnsServers.split(" +")) { 516 InetAddress.parseNumericAddress(dnsServer); 517 } 518 } catch (IllegalArgumentException e) { 519 return false; 520 } 521 return true; 522 } 523 524 /** Generates a hashcode over the VpnProfile. */ 525 @Override hashCode()526 public int hashCode() { 527 return Objects.hash( 528 key, type, server, username, password, dnsServers, searchDomains, routes, mppe, 529 l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert, 530 proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline, 531 isRestrictedToTestNetworks, excludeLocalRoutes, requiresInternetValidation, 532 ikeTunConnParams); 533 } 534 535 /** Checks VPN profiles for interior equality. */ 536 @Override equals(Object obj)537 public boolean equals(Object obj) { 538 if (!(obj instanceof VpnProfile)) { 539 return false; 540 } 541 542 final VpnProfile other = (VpnProfile) obj; 543 return Objects.equals(key, other.key) 544 && Objects.equals(name, other.name) 545 && type == other.type 546 && Objects.equals(server, other.server) 547 && Objects.equals(username, other.username) 548 && Objects.equals(password, other.password) 549 && Objects.equals(dnsServers, other.dnsServers) 550 && Objects.equals(searchDomains, other.searchDomains) 551 && Objects.equals(routes, other.routes) 552 && mppe == other.mppe 553 && Objects.equals(l2tpSecret, other.l2tpSecret) 554 && Objects.equals(ipsecIdentifier, other.ipsecIdentifier) 555 && Objects.equals(ipsecSecret, other.ipsecSecret) 556 && Objects.equals(ipsecUserCert, other.ipsecUserCert) 557 && Objects.equals(ipsecCaCert, other.ipsecCaCert) 558 && Objects.equals(ipsecServerCert, other.ipsecServerCert) 559 && Objects.equals(proxy, other.proxy) 560 && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) 561 && isBypassable == other.isBypassable 562 && isMetered == other.isMetered 563 && maxMtu == other.maxMtu 564 && areAuthParamsInline == other.areAuthParamsInline 565 && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks 566 && excludeLocalRoutes == other.excludeLocalRoutes 567 && requiresInternetValidation == other.requiresInternetValidation 568 && Objects.equals(ikeTunConnParams, other.ikeTunConnParams); 569 } 570 571 @NonNull 572 public static final Creator<VpnProfile> CREATOR = new Creator<>() { 573 @Override 574 public VpnProfile createFromParcel(Parcel in) { 575 return new VpnProfile(in); 576 } 577 578 @Override 579 public VpnProfile[] newArray(int size) { 580 return new VpnProfile[size]; 581 } 582 }; 583 584 @Override describeContents()585 public int describeContents() { 586 return 0; 587 } 588 } 589