diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index a6f9b96269..b13e4b72aa 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -19,7 +19,9 @@ package android.net; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringDef; import android.content.Context; +import android.os.Binder; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; @@ -79,6 +81,128 @@ public class ConnectivityDiagnosticsManager { /** Class that includes connectivity information for a specific Network at a specific time. */ public static final class ConnectivityReport implements Parcelable { + /** + * The overall status of the network is that it is invalid; it neither provides + * connectivity nor has been exempted from validation. + */ + public static final int NETWORK_VALIDATION_RESULT_INVALID = 0; + + /** + * The overall status of the network is that it is valid, this may be because it provides + * full Internet access (all probes succeeded), or because other properties of the network + * caused probes not to be run. + */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID + public static final int NETWORK_VALIDATION_RESULT_VALID = 1; + + /** + * The overall status of the network is that it provides partial connectivity; some + * probed services succeeded but others failed. + */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL; + public static final int NETWORK_VALIDATION_RESULT_PARTIALLY_VALID = 2; + + /** + * Due to the properties of the network, validation was not performed. + */ + public static final int NETWORK_VALIDATION_RESULT_SKIPPED = 3; + + /** @hide */ + @IntDef( + prefix = {"NETWORK_VALIDATION_RESULT_"}, + value = { + NETWORK_VALIDATION_RESULT_INVALID, + NETWORK_VALIDATION_RESULT_VALID, + NETWORK_VALIDATION_RESULT_PARTIALLY_VALID, + NETWORK_VALIDATION_RESULT_SKIPPED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkValidationResult {} + + /** + * The overall validation result for the Network being reported on. + * + *

The possible values for this key are: + * {@link #NETWORK_VALIDATION_RESULT_INVALID}, + * {@link #NETWORK_VALIDATION_RESULT_VALID}, + * {@link #NETWORK_VALIDATION_RESULT_PARTIALLY_VALID}, + * {@link #NETWORK_VALIDATION_RESULT_SKIPPED}. + * + * @see android.net.NetworkCapabilities#CAPABILITY_VALIDATED + */ + @NetworkValidationResult + public static final String KEY_NETWORK_VALIDATION_RESULT = "networkValidationResult"; + + /** DNS probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS + public static final int NETWORK_PROBE_DNS = 0x04; + + /** HTTP probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP + public static final int NETWORK_PROBE_HTTP = 0x08; + + /** HTTPS probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS; + public static final int NETWORK_PROBE_HTTPS = 0x10; + + /** Captive portal fallback probe. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_FALLBACK + public static final int NETWORK_PROBE_FALLBACK = 0x20; + + /** Private DNS (DNS over TLS) probd. */ + // TODO: link to INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS + public static final int NETWORK_PROBE_PRIVATE_DNS = 0x40; + + /** @hide */ + @IntDef( + prefix = {"NETWORK_PROBE_"}, + value = { + NETWORK_PROBE_DNS, + NETWORK_PROBE_HTTP, + NETWORK_PROBE_HTTPS, + NETWORK_PROBE_FALLBACK, + NETWORK_PROBE_PRIVATE_DNS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface NetworkProbe {} + + /** + * A bitmask of network validation probes that succeeded. + * + *

The possible bits values reported by this key are: + * {@link #NETWORK_PROBE_DNS}, + * {@link #NETWORK_PROBE_HTTP}, + * {@link #NETWORK_PROBE_HTTPS}, + * {@link #NETWORK_PROBE_FALLBACK}, + * {@link #NETWORK_PROBE_PRIVATE_DNS}. + */ + @NetworkProbe + public static final String KEY_NETWORK_PROBES_SUCCEEDED_BITMASK = + "networkProbesSucceeded"; + + /** + * A bitmask of network validation probes that were attempted. + * + *

These probes may have failed or may be incomplete at the time of this report. + * + *

The possible bits values reported by this key are: + * {@link #NETWORK_PROBE_DNS}, + * {@link #NETWORK_PROBE_HTTP}, + * {@link #NETWORK_PROBE_HTTPS}, + * {@link #NETWORK_PROBE_FALLBACK}, + * {@link #NETWORK_PROBE_PRIVATE_DNS}. + */ + @NetworkProbe + public static final String KEY_NETWORK_PROBES_ATTEMPTED_BITMASK = + "networkProbesAttemped"; + + /** @hide */ + @StringDef(prefix = {"KEY_"}, value = { + KEY_NETWORK_VALIDATION_RESULT, KEY_NETWORK_PROBES_SUCCEEDED_BITMASK, + KEY_NETWORK_PROBES_ATTEMPTED_BITMASK}) + @Retention(RetentionPolicy.SOURCE) + public @interface ConnectivityReportBundleKeys {} + /** The Network for which this ConnectivityReport applied */ @NonNull private final Network mNetwork; @@ -218,7 +342,7 @@ public class ConnectivityDiagnosticsManager { /** Implement the Parcelable interface */ public static final @NonNull Creator CREATOR = - new Creator<>() { + new Creator() { public ConnectivityReport createFromParcel(Parcel in) { return new ConnectivityReport( in.readParcelable(null), @@ -246,6 +370,49 @@ public class ConnectivityDiagnosticsManager { value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS}) public @interface DetectionMethod {} + /** + * This key represents the period in milliseconds over which other included TCP metrics + * were measured. + * + *

This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_TCP_METRICS}. + * + *

This value is an int. + */ + public static final String KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS = + "tcpMetricsCollectionPeriodMillis"; + + /** + * This key represents the fail rate of TCP packets when the suspected data stall was + * detected. + * + *

This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_TCP_METRICS}. + * + *

This value is an int percentage between 0 and 100. + */ + public static final String KEY_TCP_PACKET_FAIL_RATE = "tcpPacketFailRate"; + + /** + * This key represents the consecutive number of DNS timeouts that have occurred. + * + *

The consecutive count will be reset any time a DNS response is received. + * + *

This key will be included if the data stall detection method is + * {@link #DETECTION_METHOD_DNS_EVENTS}. + * + *

This value is an int. + */ + public static final String KEY_DNS_CONSECUTIVE_TIMEOUTS = "dnsConsecutiveTimeouts"; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef(prefix = {"KEY_"}, value = { + KEY_TCP_PACKET_FAIL_RATE, + KEY_DNS_CONSECUTIVE_TIMEOUTS + }) + public @interface DataStallReportBundleKeys {} + /** The Network for which this DataStallReport applied */ @NonNull private final Network mNetwork; @@ -315,6 +482,9 @@ public class ConnectivityDiagnosticsManager { /** * Returns a PersistableBundle with additional info for this report. * + *

Gets a bundle with details about the suspected data stall including information + * specific to the monitoring method that detected the data stall. + * * @return PersistableBundle that may contain additional information on the suspected data * stall */ @@ -375,6 +545,53 @@ public class ConnectivityDiagnosticsManager { }; } + /** @hide */ + @VisibleForTesting + public static class ConnectivityDiagnosticsBinder + extends IConnectivityDiagnosticsCallback.Stub { + @NonNull private final ConnectivityDiagnosticsCallback mCb; + @NonNull private final Executor mExecutor; + + /** @hide */ + @VisibleForTesting + public ConnectivityDiagnosticsBinder( + @NonNull ConnectivityDiagnosticsCallback cb, @NonNull Executor executor) { + this.mCb = cb; + this.mExecutor = executor; + } + + /** @hide */ + @VisibleForTesting + public void onConnectivityReport(@NonNull ConnectivityReport report) { + Binder.withCleanCallingIdentity(() -> { + mExecutor.execute(() -> { + mCb.onConnectivityReport(report); + }); + }); + } + + /** @hide */ + @VisibleForTesting + public void onDataStallSuspected(@NonNull DataStallReport report) { + Binder.withCleanCallingIdentity(() -> { + mExecutor.execute(() -> { + mCb.onDataStallSuspected(report); + }); + }); + } + + /** @hide */ + @VisibleForTesting + public void onNetworkConnectivityReported( + @NonNull Network network, boolean hasConnectivity) { + Binder.withCleanCallingIdentity(() -> { + mExecutor.execute(() -> { + mCb.onNetworkConnectivityReported(network, hasConnectivity); + }); + }); + } + } + /** * Abstract base class for Connectivity Diagnostics callbacks. Used for notifications about * network connectivity events. Must be extended by applications wanting notifications. diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 753e754602..ce9693d88a 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -33,7 +33,9 @@ import android.content.Context; import android.content.Intent; import android.net.IpSecManager.UdpEncapsulationSocket; import android.net.SocketKeepalive.Callback; +import android.net.TetheringManager.StartTetheringCallback; import android.net.TetheringManager.TetheringEventCallback; +import android.net.TetheringManager.TetheringRequest; import android.os.Binder; import android.os.Build; import android.os.Build.VERSION_CODES; @@ -2452,10 +2454,12 @@ public class ConnectivityManager { * * @param iface the interface name to tether. * @return error a {@code TETHER_ERROR} value indicating success or failure type + * @deprecated Use {@link TetheringManager#startTethering} instead * * {@hide} */ @UnsupportedAppUsage + @Deprecated public int tether(String iface) { return getTetheringManager().tether(iface); } @@ -2512,9 +2516,12 @@ public class ConnectivityManager { /** * Callback for use with {@link #startTethering} to find out whether tethering succeeded. + * + * @deprecated Use {@link TetheringManager.StartTetheringCallback} instead. * @hide */ @SystemApi + @Deprecated public static abstract class OnStartTetheringCallback { /** * Called when tethering has been successfully started. @@ -2531,9 +2538,12 @@ public class ConnectivityManager { * Convenient overload for * {@link #startTethering(int, boolean, OnStartTetheringCallback, Handler)} which passes a null * handler to run on the current thread's {@link Looper}. + * + * @deprecated Use {@link TetheringManager#startTethering} instead. * @hide */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback) { @@ -2557,26 +2567,44 @@ public class ConnectivityManager { * @param callback an {@link OnStartTetheringCallback} which will be called to notify the caller * of the result of trying to tether. * @param handler {@link Handler} to specify the thread upon which the callback will be invoked. + * + * @deprecated Use {@link TetheringManager#startTethering} instead. * @hide */ @SystemApi + @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int type, boolean showProvisioningUi, final OnStartTetheringCallback callback, Handler handler) { Preconditions.checkNotNull(callback, "OnStartTetheringCallback cannot be null."); - ResultReceiver wrappedCallback = new ResultReceiver(handler) { + final Executor executor = new Executor() { @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode == TETHER_ERROR_NO_ERROR) { - callback.onTetheringStarted(); + public void execute(Runnable command) { + if (handler == null) { + command.run(); } else { - callback.onTetheringFailed(); + handler.post(command); } } }; - getTetheringManager().startTethering(type, wrappedCallback, showProvisioningUi); + final StartTetheringCallback tetheringCallback = new StartTetheringCallback() { + @Override + public void onTetheringStarted() { + callback.onTetheringStarted(); + } + + @Override + public void onTetheringFailed(final int resultCode) { + callback.onTetheringFailed(); + } + }; + + final TetheringRequest request = new TetheringRequest.Builder(type) + .setSilentProvisioning(!showProvisioningUi).build(); + + getTetheringManager().startTethering(request, executor, tetheringCallback); } /** @@ -2602,7 +2630,7 @@ public class ConnectivityManager { * Callback for use with {@link registerTetheringEventCallback} to find out tethering * upstream status. * - * @deprecated Use {@line TetheringManager#OnTetheringEventCallback} instead. + * @deprecated Use {@link TetheringManager#OnTetheringEventCallback} instead. * @hide */ @SystemApi @@ -2632,7 +2660,7 @@ public class ConnectivityManager { * @param executor the executor on which callback will be invoked. * @param callback the callback to be called when tethering has change events. * - * @deprecated Use {@line TetheringManager#registerTetheringEventCallback} instead. + * @deprecated Use {@link TetheringManager#registerTetheringEventCallback} instead. * @hide */ @SystemApi @@ -2749,10 +2777,12 @@ public class ConnectivityManager { * * @param enable a boolean - {@code true} to enable tethering * @return error a {@code TETHER_ERROR} value indicating success or failure type + * @deprecated Use {@link TetheringManager#startTethering} instead * * {@hide} */ @UnsupportedAppUsage + @Deprecated public int setUsbTethering(boolean enable) { return getTetheringManager().setUsbTethering(enable); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 186196bd31..3e9e7faccb 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -18,6 +18,7 @@ package android.net; import android.app.PendingIntent; import android.net.ConnectionInfo; +import android.net.IConnectivityDiagnosticsCallback; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgentConfig; @@ -211,5 +212,9 @@ interface IConnectivityManager boolean isCallerCurrentAlwaysOnVpnApp(); boolean isCallerCurrentAlwaysOnVpnLockdownApp(); + void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, + in NetworkRequest request); + void unregisterConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback); + IBinder startOrGetTestNetworkService(); } diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index e83f5e4e81..732ceb560c 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -82,7 +82,8 @@ public final class LinkProperties implements Parcelable { private final transient boolean mParcelSensitiveFields; private static final int MIN_MTU = 68; - private static final int MIN_MTU_V6 = 1280; + /* package-visibility - Used in other files (such as Ikev2VpnProfile) as minimum iface MTU. */ + static final int MIN_MTU_V6 = 1280; private static final int MAX_MTU = 10000; private static final int INET6_ADDR_LENGTH = 16; diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 738070b092..4f4e27b446 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -26,6 +26,7 @@ import android.net.ConnectivityManager.NetworkCallback; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; +import android.os.Process; import android.util.ArraySet; import android.util.proto.ProtoOutputStream; @@ -35,6 +36,9 @@ import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; @@ -55,7 +59,6 @@ import java.util.StringJoiner; */ public final class NetworkCapabilities implements Parcelable { private static final String TAG = "NetworkCapabilities"; - private static final int INVALID_UID = -1; // Set to true when private DNS is broken. private boolean mPrivateDnsBroken; @@ -82,7 +85,8 @@ public final class NetworkCapabilities implements Parcelable { mTransportInfo = null; mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; mUids = null; - mEstablishingVpnAppUid = INVALID_UID; + mAdministratorUids.clear(); + mOwnerUid = Process.INVALID_UID; mSSID = null; mPrivateDnsBroken = false; } @@ -100,7 +104,8 @@ public final class NetworkCapabilities implements Parcelable { mTransportInfo = nc.mTransportInfo; mSignalStrength = nc.mSignalStrength; setUids(nc.mUids); // Will make the defensive copy - mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid; + setAdministratorUids(nc.mAdministratorUids); + mOwnerUid = nc.mOwnerUid; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; mSSID = nc.mSSID; mPrivateDnsBroken = nc.mPrivateDnsBroken; @@ -805,31 +810,76 @@ public final class NetworkCapabilities implements Parcelable { } /** - * UID of the app that manages this network, or INVALID_UID if none/unknown. + * UID of the app that owns this network, or INVALID_UID if none/unknown. * - * This field keeps track of the UID of the app that created this network and is in charge - * of managing it. In the practice, it is used to store the UID of VPN apps so it is named - * accordingly, but it may be renamed if other mechanisms are offered for third party apps - * to create networks. - * - * Because this field is only used in the services side (and to avoid apps being able to - * set this to whatever they want), this field is not parcelled and will not be conserved - * across the IPC boundary. - * @hide + *

This field keeps track of the UID of the app that created this network and is in charge of + * its lifecycle. This could be the UID of apps such as the Wifi network suggestor, the running + * VPN, or Carrier Service app managing a cellular data connection. */ - private int mEstablishingVpnAppUid = INVALID_UID; + private int mOwnerUid = Process.INVALID_UID; /** - * Set the UID of the managing app. - * @hide + * Set the UID of the owner app. */ - public void setEstablishingVpnAppUid(final int uid) { - mEstablishingVpnAppUid = uid; + public void setOwnerUid(final int uid) { + mOwnerUid = uid; } - /** @hide */ - public int getEstablishingVpnAppUid() { - return mEstablishingVpnAppUid; + /** + * Retrieves the UID of the owner app. + */ + public int getOwnerUid() { + return mOwnerUid; + } + + /** + * UIDs of packages that are administrators of this network, or empty if none. + * + *

This field tracks the UIDs of packages that have permission to manage this network. + * + *

Network owners will also be listed as administrators. + * + *

For NetworkCapability instances being sent from the System Server, this value MUST be + * empty unless the destination is 1) the System Server, or 2) Telephony. In either case, the + * receiving entity must have the ACCESS_FINE_LOCATION permission and target R+. + */ + private final List mAdministratorUids = new ArrayList<>(); + + /** + * Sets the list of UIDs that are administrators of this network. + * + *

UIDs included in administratorUids gain administrator privileges over this Network. + * Examples of UIDs that should be included in administratorUids are: + *

+ * + *

In general, user-supplied networks (such as WiFi networks) do not have an administrator. + * + *

An app is granted owner privileges over Networks that it supplies. Owner privileges + * implicitly include administrator privileges. + * + * @param administratorUids the UIDs to be set as administrators of this Network. + * @hide + */ + @SystemApi + public void setAdministratorUids(@NonNull final List administratorUids) { + mAdministratorUids.clear(); + mAdministratorUids.addAll(administratorUids); + } + + /** + * Retrieves the list of UIDs that are administrators of this Network. + * + * @return the List of UIDs that are administrators of this Network + * @hide + */ + @NonNull + @SystemApi + public List getAdministratorUids() { + return Collections.unmodifiableList(mAdministratorUids); } /** @@ -1102,7 +1152,7 @@ public final class NetworkCapabilities implements Parcelable { * member is null, then the network is not restricted by app UID. If it's an empty list, then * it means nobody can use it. * As a special exception, the app managing this network (as identified by its UID stored in - * mEstablishingVpnAppUid) can always see this network. This is embodied by a special check in + * mOwnerUid) can always see this network. This is embodied by a special check in * satisfiedByUids. That still does not mean the network necessarily applies * to the app that manages it as determined by #appliesToUid. *

@@ -1209,7 +1259,7 @@ public final class NetworkCapabilities implements Parcelable { * in the passed nc (representing the UIDs that this network is available to). *

* As a special exception, the UID that created the passed network (as represented by its - * mEstablishingVpnAppUid field) always satisfies a NetworkRequest requiring it (of LISTEN + * mOwnerUid field) always satisfies a NetworkRequest requiring it (of LISTEN * or REQUEST types alike), even if the network does not apply to it. That is so a VPN app * can see its own network when it listens for it. *

@@ -1220,7 +1270,7 @@ public final class NetworkCapabilities implements Parcelable { public boolean satisfiedByUids(@NonNull NetworkCapabilities nc) { if (null == nc.mUids || null == mUids) return true; // The network satisfies everything. for (UidRange requiredRange : mUids) { - if (requiredRange.contains(nc.mEstablishingVpnAppUid)) return true; + if (requiredRange.contains(nc.mOwnerUid)) return true; if (!nc.appliesToUidRange(requiredRange)) { return false; } @@ -1471,6 +1521,7 @@ public final class NetworkCapabilities implements Parcelable { public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mNetworkCapabilities); @@ -1484,6 +1535,8 @@ public final class NetworkCapabilities implements Parcelable { dest.writeArraySet(mUids); dest.writeString(mSSID); dest.writeBoolean(mPrivateDnsBroken); + dest.writeList(mAdministratorUids); + dest.writeInt(mOwnerUid); } public static final @android.annotation.NonNull Creator CREATOR = @@ -1504,6 +1557,8 @@ public final class NetworkCapabilities implements Parcelable { null /* ClassLoader, null for default */); netCap.mSSID = in.readString(); netCap.mPrivateDnsBroken = in.readBoolean(); + netCap.setAdministratorUids(in.readArrayList(null)); + netCap.mOwnerUid = in.readInt(); return netCap; } @Override @@ -1553,8 +1608,12 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" Uids: <").append(mUids).append(">"); } } - if (mEstablishingVpnAppUid != INVALID_UID) { - sb.append(" EstablishingAppUid: ").append(mEstablishingVpnAppUid); + if (mOwnerUid != Process.INVALID_UID) { + sb.append(" OwnerUid: ").append(mOwnerUid); + } + + if (!mAdministratorUids.isEmpty()) { + sb.append(" AdministratorUids: ").append(mAdministratorUids); } if (null != mSSID) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1c9f5dc9c2..dd33566322 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -64,6 +64,7 @@ import android.net.CaptivePortal; import android.net.ConnectionInfo; import android.net.ConnectivityManager; import android.net.ICaptivePortal; +import android.net.IConnectivityDiagnosticsCallback; import android.net.IConnectivityManager; import android.net.IDnsResolver; import android.net.IIpConnectivityMetrics; @@ -210,6 +211,7 @@ import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.HashMap; @@ -1622,7 +1624,8 @@ public class ConnectivityService extends IConnectivityManager.Stub return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network)); } - private NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions( + @VisibleForTesting + NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions( NetworkCapabilities nc, int callerPid, int callerUid) { final NetworkCapabilities newNc = new NetworkCapabilities(nc); if (!checkSettingsPermission(callerPid, callerUid)) { @@ -1632,9 +1635,24 @@ public class ConnectivityService extends IConnectivityManager.Stub if (newNc.getNetworkSpecifier() != null) { newNc.setNetworkSpecifier(newNc.getNetworkSpecifier().redact()); } + newNc.setAdministratorUids(Collections.EMPTY_LIST); + + maybeSanitizeLocationInfoForCaller(newNc, callerUid); + return newNc; } + private void maybeSanitizeLocationInfoForCaller( + NetworkCapabilities nc, int callerUid) { + // TODO(b/142072839): Conditionally reset the owner UID if the following + // conditions are not met: + // 1. The destination app is the network owner + // 2. The destination app has the ACCESS_COARSE_LOCATION permission granted + // if target SDK<29 or otherwise has the ACCESS_FINE_LOCATION permission granted + // 3. The user's location toggle is on + nc.setOwnerUid(INVALID_UID); + } + private LinkProperties linkPropertiesRestrictedForCallerPermissions( LinkProperties lp, int callerPid, int callerUid) { if (lp == null) return new LinkProperties(); @@ -1662,6 +1680,10 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!checkSettingsPermission()) { nc.setSingleUid(Binder.getCallingUid()); } + nc.setAdministratorUids(Collections.EMPTY_LIST); + + // Clear owner UID; this can never come from an app. + nc.setOwnerUid(INVALID_UID); } private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) { @@ -5798,7 +5820,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } final Set ranges = nai.networkCapabilities.getUids(); - final int vpnAppUid = nai.networkCapabilities.getEstablishingVpnAppUid(); + final int vpnAppUid = nai.networkCapabilities.getOwnerUid(); // TODO: this create a window of opportunity for apps to receive traffic between the time // when the old rules are removed and the time when new rules are added. To fix this, // make eBPF support two whitelisted interfaces so here new rules can be added before the @@ -5997,7 +6019,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nc == null || lp == null) return false; return nai.isVPN() && !nai.networkAgentConfig.allowBypass - && nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID + && nc.getOwnerUid() != Process.SYSTEM_UID && lp.getInterfaceName() != null && (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute()); } @@ -6045,12 +6067,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO Fix this window by computing an accurate diff on Set, so the old range // to be removed will never overlap with the new range to be added. if (wasFiltering && !prevRanges.isEmpty()) { - mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, - prevNc.getEstablishingVpnAppUid()); + mPermissionMonitor.onVpnUidRangesRemoved(iface, prevRanges, prevNc.getOwnerUid()); } if (shouldFilter && !newRanges.isEmpty()) { - mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, - newNc.getEstablishingVpnAppUid()); + mPermissionMonitor.onVpnUidRangesAdded(iface, newRanges, newNc.getOwnerUid()); } } catch (Exception e) { // Never crash! @@ -7311,4 +7331,20 @@ public class ConnectivityService extends IConnectivityManager.Stub return mTNS; } } + + @Override + public void registerConnectivityDiagnosticsCallback( + @NonNull IConnectivityDiagnosticsCallback callback, @NonNull NetworkRequest request) { + // TODO(b/146444622): implement register IConnectivityDiagnosticsCallback functionality + throw new UnsupportedOperationException( + "registerConnectivityDiagnosticsCallback not yet implemented"); + } + + @Override + public void unregisterConnectivityDiagnosticsCallback( + @NonNull IConnectivityDiagnosticsCallback callback) { + // TODO(b/146444622): implement register IConnectivityDiagnosticsCallback functionality + throw new UnsupportedOperationException( + "unregisterConnectivityDiagnosticsCallback not yet implemented"); + } } diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java index 096f7bdca0..e566e44c81 100644 --- a/tests/net/common/java/android/net/LinkAddressTest.java +++ b/tests/net/common/java/android/net/LinkAddressTest.java @@ -326,6 +326,7 @@ public class LinkAddressTest { assertParcelSane(l, 6); } + /* @Test public void testDeprecationTime() { try { @@ -392,7 +393,7 @@ public class LinkAddressTest { SystemClock.elapsedRealtime() + 100000); // Check if the permanent flag is removed assertTrue((l.getFlags() & IFA_F_PERMANENT) == 0); - } + }*/ private void assertGlobalPreferred(LinkAddress l, String msg) { diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index 15691127ca..3e4f3d8188 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -269,9 +269,10 @@ public class NetworkCapabilitiesTest { .setUids(uids) .addCapability(NET_CAPABILITY_EIMS) .addCapability(NET_CAPABILITY_NOT_METERED); + netCap.setOwnerUid(123); assertParcelingIsLossless(netCap); netCap.setSSID(TEST_SSID); - assertParcelSane(netCap, 12); + assertParcelSane(netCap, 13); } @Test diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java index 065add4fc2..7ab4b56fae 100644 --- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -16,6 +16,8 @@ package android.net; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsBinder; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import static android.net.ConnectivityDiagnosticsManager.DataStallReport; @@ -25,12 +27,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import android.os.PersistableBundle; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.Mock; + +import java.util.concurrent.Executor; @RunWith(JUnit4.class) public class ConnectivityDiagnosticsManagerTest { @@ -41,6 +50,19 @@ public class ConnectivityDiagnosticsManagerTest { private static final String BUNDLE_KEY = "key"; private static final String BUNDLE_VALUE = "value"; + private static final Executor INLINE_EXECUTOR = x -> x.run(); + + @Mock private ConnectivityDiagnosticsCallback mCb; + + private ConnectivityDiagnosticsBinder mBinder; + + @Before + public void setUp() { + mCb = mock(ConnectivityDiagnosticsCallback.class); + + mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR); + } + private ConnectivityReport createSampleConnectivityReport() { final LinkProperties linkProperties = new LinkProperties(); linkProperties.setInterfaceName(INTERFACE_NAME); @@ -193,4 +215,34 @@ public class ConnectivityDiagnosticsManagerTest { public void testDataStallReportParcelUnparcel() { assertParcelSane(createSampleDataStallReport(), 4); } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReport() { + mBinder.onConnectivityReport(createSampleConnectivityReport()); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onConnectivityReport(eq(createSampleConnectivityReport())); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() { + mBinder.onDataStallSuspected(createSampleDataStallReport()); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onDataStallSuspected(eq(createSampleDataStallReport())); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnNetworkConnectivityReported() { + final Network n = new Network(NET_ID); + final boolean connectivity = true; + + mBinder.onNetworkConnectivityReported(n, connectivity); + + // The callback will be invoked synchronously by inline executor. Immediately check the + // latch without waiting. + verify(mCb).onNetworkConnectivityReported(eq(n), eq(connectivity)); + } } diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java new file mode 100644 index 0000000000..d6a2176d7e --- /dev/null +++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; + +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.net.VpnProfile; +import com.android.org.bouncycastle.x509.X509V1CertificateGenerator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import javax.security.auth.x500.X500Principal; + +/** Unit tests for {@link Ikev2VpnProfile.Builder}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class Ikev2VpnProfileTest { + private static final String SERVER_ADDR_STRING = "1.2.3.4"; + private static final String IDENTITY_STRING = "Identity"; + private static final String USERNAME_STRING = "username"; + private static final String PASSWORD_STRING = "pa55w0rd"; + private static final String EXCL_LIST = "exclList"; + private static final byte[] PSK_BYTES = "preSharedKey".getBytes(); + private static final int TEST_MTU = 1300; + + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + private final ProxyInfo mProxy = new ProxyInfo(SERVER_ADDR_STRING, -1, EXCL_LIST); + + private X509Certificate mUserCert; + private X509Certificate mServerRootCa; + private PrivateKey mPrivateKey; + + @Before + public void setUp() throws Exception { + mServerRootCa = generateRandomCertAndKeyPair().cert; + + final CertificateAndKey userCertKey = generateRandomCertAndKeyPair(); + mUserCert = userCertKey.cert; + mPrivateKey = userCertKey.key; + } + + private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() { + final Ikev2VpnProfile.Builder builder = + new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING); + + builder.setBypassable(true); + builder.setProxy(mProxy); + builder.setMaxMtu(TEST_MTU); + builder.setMetered(true); + + return builder; + } + + @Test + public void testBuildValidProfileWithOptions() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + // Check non-auth parameters correctly stored + assertEquals(SERVER_ADDR_STRING, profile.getServerAddr()); + assertEquals(IDENTITY_STRING, profile.getUserIdentity()); + assertEquals(mProxy, profile.getProxyInfo()); + assertTrue(profile.isBypassable()); + assertTrue(profile.isMetered()); + assertEquals(TEST_MTU, profile.getMaxMtu()); + } + + @Test + public void testBuildUsernamePasswordProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(USERNAME_STRING, profile.getUsername()); + assertEquals(PASSWORD_STRING, profile.getPassword()); + assertEquals(mServerRootCa, profile.getServerRootCaCert()); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildDigitalSignatureProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertEquals(profile.getUserCert(), mUserCert); + assertEquals(mPrivateKey, profile.getRsaPrivateKey()); + assertEquals(profile.getServerRootCaCert(), mServerRootCa); + + assertNull(profile.getPresharedKey()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + } + + @Test + public void testBuildPresharedKeyProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile profile = builder.build(); + assertNotNull(profile); + + assertArrayEquals(PSK_BYTES, profile.getPresharedKey()); + + assertNull(profile.getServerRootCaCert()); + assertNull(profile.getUsername()); + assertNull(profile.getPassword()); + assertNull(profile.getRsaPrivateKey()); + assertNull(profile.getUserCert()); + } + + @Test + public void testBuildNoAuthMethodSet() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.build(); + fail("Expected exception due to lack of auth method"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testBuildInvalidMtu() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + try { + builder.setMaxMtu(500); + fail("Expected exception due to too-small MTU"); + } catch (IllegalArgumentException expected) { + } + } + + private void verifyVpnProfileCommon(VpnProfile profile) { + assertEquals(SERVER_ADDR_STRING, profile.server); + assertEquals(IDENTITY_STRING, profile.ipsecIdentifier); + assertEquals(mProxy, profile.proxy); + assertTrue(profile.isBypassable); + assertTrue(profile.isMetered); + assertEquals(TEST_MTU, profile.maxMtu); + } + + @Test + public void testPskConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecCaCert); + } + + @Test + public void testUsernamePasswordConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(USERNAME_STRING, profile.username); + assertEquals(PASSWORD_STRING, profile.password); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.ipsecUserCert); + assertEquals("", profile.ipsecSecret); + } + + @Test + public void testRsaConvertToVpnProfile() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + + verifyVpnProfileCommon(profile); + assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert); + assertEquals( + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()), + profile.ipsecSecret); + assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert); + + // Check nothing else is set + assertEquals("", profile.username); + assertEquals("", profile.password); + } + + @Test + public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + assertNull(result.getServerRootCaCert()); + } + + @Test + public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.ipsecSecret = new String(PSK_BYTES); + profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert); + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getPresharedKey()); + assertNull(result.getUserCert()); + assertNull(result.getRsaPrivateKey()); + } + + @Test + public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final VpnProfile profile = builder.build().toVpnProfile(); + profile.username = USERNAME_STRING; + profile.password = PASSWORD_STRING; + + final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile); + assertNull(result.getUsername()); + assertNull(result.getPassword()); + assertNull(result.getPresharedKey()); + } + + @Test + public void testPskConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthPsk(PSK_BYTES); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testUsernamePasswordConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + @Test + public void testRsaConversionIsLossless() throws Exception { + final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions(); + + builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa); + final Ikev2VpnProfile ikeProfile = builder.build(); + + assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile())); + } + + private static class CertificateAndKey { + public final X509Certificate cert; + public final PrivateKey key; + + CertificateAndKey(X509Certificate cert, PrivateKey key) { + this.cert = cert; + this.key = key; + } + } + + private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception { + final Date validityBeginDate = + new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L)); + final Date validityEndDate = + new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L)); + + // Generate a keypair + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(512); + final KeyPair keyPair = keyPairGenerator.generateKeyPair(); + + final X500Principal dnName = new X500Principal("CN=test.android.com"); + final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); + certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); + certGen.setSubjectDN(dnName); + certGen.setIssuerDN(dnName); + certGen.setNotBefore(validityBeginDate); + certGen.setNotAfter(validityEndDate); + certGen.setPublicKey(keyPair.getPublic()); + certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); + + final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL"); + return new CertificateAndKey(cert, keyPair.getPrivate()); + } +} diff --git a/tests/net/java/android/net/VpnManagerTest.java b/tests/net/java/android/net/VpnManagerTest.java new file mode 100644 index 0000000000..655c4d1185 --- /dev/null +++ b/tests/net/java/android/net/VpnManagerTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import static org.mockito.Mockito.mock; + +import android.test.mock.MockContext; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Unit tests for {@link VpnManager}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class VpnManagerTest { + private static final String VPN_PROFILE_KEY = "KEY"; + + private IConnectivityManager mMockCs; + private VpnManager mVpnManager; + private final MockContext mMockContext = + new MockContext() { + @Override + public String getOpPackageName() { + return "fooPackage"; + } + }; + + @Before + public void setUp() throws Exception { + mMockCs = mock(IConnectivityManager.class); + mVpnManager = new VpnManager(mMockContext, mMockCs); + } + + @Test + public void testProvisionVpnProfile() throws Exception { + try { + mVpnManager.provisionVpnProfile(mock(PlatformVpnProfile.class)); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testDeleteProvisionedVpnProfile() throws Exception { + try { + mVpnManager.deleteProvisionedVpnProfile(); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testStartProvisionedVpnProfile() throws Exception { + try { + mVpnManager.startProvisionedVpnProfile(); + } catch (UnsupportedOperationException expected) { + } + } + + @Test + public void testStopProvisionedVpnProfile() throws Exception { + try { + mVpnManager.stopProvisionedVpnProfile(); + } catch (UnsupportedOperationException expected) { + } + } +} diff --git a/tests/net/java/com/android/internal/net/VpnProfileTest.java b/tests/net/java/com/android/internal/net/VpnProfileTest.java new file mode 100644 index 0000000000..8a4b53343c --- /dev/null +++ b/tests/net/java/com/android/internal/net/VpnProfileTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.net; + +import static com.android.testutils.ParcelUtilsKt.assertParcelSane; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.net.IpSecAlgorithm; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; + +/** Unit tests for {@link VpnProfile}. */ +@SmallTest +@RunWith(JUnit4.class) +public class VpnProfileTest { + private static final String DUMMY_PROFILE_KEY = "Test"; + + @Test + public void testDefaults() throws Exception { + final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY); + + assertEquals(DUMMY_PROFILE_KEY, p.key); + assertEquals("", p.name); + assertEquals(VpnProfile.TYPE_PPTP, p.type); + assertEquals("", p.server); + assertEquals("", p.username); + assertEquals("", p.password); + assertEquals("", p.dnsServers); + assertEquals("", p.searchDomains); + assertEquals("", p.routes); + assertTrue(p.mppe); + assertEquals("", p.l2tpSecret); + assertEquals("", p.ipsecIdentifier); + assertEquals("", p.ipsecSecret); + assertEquals("", p.ipsecUserCert); + assertEquals("", p.ipsecCaCert); + assertEquals("", p.ipsecServerCert); + assertEquals(null, p.proxy); + assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty()); + assertFalse(p.isBypassable); + assertFalse(p.isMetered); + assertEquals(1400, p.maxMtu); + assertFalse(p.areAuthParamsInline); + } + + private VpnProfile getSampleIkev2Profile(String key) { + final VpnProfile p = new VpnProfile(key); + + p.name = "foo"; + p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS; + p.server = "bar"; + p.username = "baz"; + p.password = "qux"; + p.dnsServers = "8.8.8.8"; + p.searchDomains = ""; + p.routes = "0.0.0.0/0"; + p.mppe = false; + p.l2tpSecret = ""; + p.ipsecIdentifier = "quux"; + p.ipsecSecret = "quuz"; + p.ipsecUserCert = "corge"; + p.ipsecCaCert = "grault"; + p.ipsecServerCert = "garply"; + p.proxy = null; + p.setAllowedAlgorithms( + Arrays.asList( + IpSecAlgorithm.AUTH_CRYPT_AES_GCM, + IpSecAlgorithm.AUTH_HMAC_SHA512, + IpSecAlgorithm.CRYPT_AES_CBC)); + p.isBypassable = true; + p.isMetered = true; + p.maxMtu = 1350; + p.areAuthParamsInline = true; + + // Not saved, but also not compared. + p.saveLogin = true; + + return p; + } + + @Test + public void testEquals() { + assertEquals( + getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY)); + + final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + modified.maxMtu--; + assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified); + } + + @Test + public void testParcelUnparcel() { + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 22); + } + + @Test + public void testSetInvalidAlgorithmValueDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.VALUE_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testSetInvalidAlgorithmListDelimiter() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + + try { + profile.setAllowedAlgorithms( + Arrays.asList("test" + VpnProfile.LIST_DELIMITER + "test")); + fail("Expected failure due to value separator in algorithm name"); + } catch (IllegalArgumentException expected) { + } + } + + @Test + public void testEncodeDecode() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertEquals(profile, decoded); + } + + @Test + public void testEncodeDecodeTooManyValues() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final byte[] tooManyValues = + (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes(); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues)); + } + + @Test + public void testEncodeDecodeInvalidNumberOfValues() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + final String encoded = new String(profile.encode()); + final byte[] tooFewValues = + encoded.substring(0, encoded.lastIndexOf(VpnProfile.VALUE_DELIMITER)).getBytes(); + + assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues)); + } + + @Test + public void testEncodeDecodeLoginsNotSaved() { + final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY); + profile.saveLogin = false; + + final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode()); + assertNotEquals(profile, decoded); + + // Add the username/password back, everything else must be equal. + decoded.username = profile.username; + decoded.password = profile.password; + assertEquals(profile, decoded); + } +} diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 09cc69e83f..a0a1352a63 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -6313,12 +6313,24 @@ public class ConnectivityServiceTest { assertEquals(wifiLp, mService.getActiveLinkProperties()); } + @Test + public void testNetworkCapabilitiesRestrictedForCallerPermissions() { + int callerUid = Process.myUid(); + final NetworkCapabilities originalNc = new NetworkCapabilities(); + originalNc.setOwnerUid(callerUid); - private TestNetworkAgentWrapper establishVpn(LinkProperties lp, int establishingUid, - Set vpnRange) throws Exception { + final NetworkCapabilities newNc = + mService.networkCapabilitiesRestrictedForCallerPermissions( + originalNc, Process.myPid(), callerUid); + + assertEquals(Process.INVALID_UID, newNc.getOwnerUid()); + } + + private TestNetworkAgentWrapper establishVpn( + LinkProperties lp, int ownerUid, Set vpnRange) throws Exception { final TestNetworkAgentWrapper vpnNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp); - vpnNetworkAgent.getNetworkCapabilities().setEstablishingVpnAppUid(establishingUid); + vpnNetworkAgent.getNetworkCapabilities().setOwnerUid(ownerUid); mMockVpn.setNetworkAgent(vpnNetworkAgent); mMockVpn.connect(); mMockVpn.setUids(vpnRange);